From 09be5cbc99f97238aa95b9ceab9db39e53e1b3a9 Mon Sep 17 00:00:00 2001 From: wwqgtxx Date: Mon, 17 Jun 2024 22:04:51 +0800 Subject: [PATCH] feat: tun support `auto-redirect`, `route-address-set` and `route-exclude-address-set` --- common/utils/callback.go | 50 +++++++ component/cidr/ipcidr_set.go | 8 +- component/iface/iface.go | 10 +- config/config.go | 107 +++++++++------ constant/provider/interface.go | 8 +- constant/rule.go | 1 + dns/policy.go | 13 +- dns/resolver.go | 9 +- docs/config.yaml | 16 ++- go.mod | 25 ++-- go.sum | 49 +++---- hub/executor/executor.go | 12 +- hub/route/configs.go | 72 +++++++--- listener/config/tun.go | 47 ++++--- listener/inbound/tun.go | 116 ++++++++++------ listener/listener.go | 28 +++- listener/sing/sing.go | 6 + listener/sing_tun/dns.go | 7 + listener/sing_tun/iface.go | 70 ++++++++++ listener/sing_tun/server.go | 211 +++++++++++++++++++++++++++-- listener/tproxy/tproxy_iptables.go | 4 +- rules/common/base.go | 2 + rules/logic/logic.go | 7 + rules/provider/ipcidr_strategy.go | 6 + rules/provider/patch_android.go | 4 +- rules/provider/provider.go | 34 ++--- rules/provider/rule_set.go | 51 ++++--- tunnel/tunnel.go | 19 ++- 28 files changed, 745 insertions(+), 247 deletions(-) create mode 100644 common/utils/callback.go create mode 100644 listener/sing_tun/iface.go diff --git a/common/utils/callback.go b/common/utils/callback.go new file mode 100644 index 00000000..df950d3a --- /dev/null +++ b/common/utils/callback.go @@ -0,0 +1,50 @@ +package utils + +import ( + "io" + "sync" + + list "github.com/bahlo/generic-list-go" +) + +type Callback[T any] struct { + list list.List[func(T)] + mutex sync.RWMutex +} + +func NewCallback[T any]() *Callback[T] { + return &Callback[T]{} +} + +func (c *Callback[T]) Register(item func(T)) io.Closer { + c.mutex.RLock() + defer c.mutex.RUnlock() + element := c.list.PushBack(item) + return &callbackCloser[T]{ + element: element, + callback: c, + } +} + +func (c *Callback[T]) Emit(item T) { + c.mutex.RLock() + defer c.mutex.RUnlock() + for element := c.list.Front(); element != nil; element = element.Next() { + go element.Value(item) + } +} + +type callbackCloser[T any] struct { + element *list.Element[func(T)] + callback *Callback[T] + once sync.Once +} + +func (c *callbackCloser[T]) Close() error { + c.once.Do(func() { + c.callback.mutex.Lock() + defer c.callback.mutex.Unlock() + c.callback.list.Remove(c.element) + }) + return nil +} diff --git a/component/cidr/ipcidr_set.go b/component/cidr/ipcidr_set.go index 0cb55e36..521fabab 100644 --- a/component/cidr/ipcidr_set.go +++ b/component/cidr/ipcidr_set.go @@ -43,12 +43,12 @@ func (set *IpCidrSet) IsContainForString(ipString string) bool { } func (set *IpCidrSet) IsContain(ip netip.Addr) bool { - return set.toIPSet().Contains(ip.WithZone("")) + return set.ToIPSet().Contains(ip.WithZone("")) } func (set *IpCidrSet) Merge() error { var b netipx.IPSetBuilder - b.AddSet(set.toIPSet()) + b.AddSet(set.ToIPSet()) i, err := b.IPSet() if err != nil { return err @@ -57,7 +57,9 @@ func (set *IpCidrSet) Merge() error { return nil } -func (set *IpCidrSet) toIPSet() *netipx.IPSet { +// ToIPSet not safe convert to *netipx.IPSet +// be careful, must be used after Merge +func (set *IpCidrSet) ToIPSet() *netipx.IPSet { return (*netipx.IPSet)(unsafe.Pointer(set)) } diff --git a/component/iface/iface.go b/component/iface/iface.go index d543725a..272ee737 100644 --- a/component/iface/iface.go +++ b/component/iface/iface.go @@ -11,8 +11,9 @@ import ( type Interface struct { Index int + MTU int Name string - Addrs []netip.Prefix + Addresses []netip.Prefix HardwareAddr net.HardwareAddr } @@ -61,8 +62,9 @@ func Interfaces() (map[string]*Interface, error) { r[iface.Name] = &Interface{ Index: iface.Index, + MTU: iface.MTU, Name: iface.Name, - Addrs: ipNets, + Addresses: ipNets, HardwareAddr: iface.HardwareAddr, } } @@ -92,7 +94,7 @@ func IsLocalIp(ip netip.Addr) (bool, error) { return false, err } for _, iface := range ifaces { - for _, addr := range iface.Addrs { + for _, addr := range iface.Addresses { if addr.Contains(ip) { return true, nil } @@ -120,7 +122,7 @@ func (iface *Interface) PickIPv6Addr(destination netip.Addr) (netip.Prefix, erro func (iface *Interface) pickIPAddr(destination netip.Addr, accept func(addr netip.Prefix) bool) (netip.Prefix, error) { var fallback netip.Prefix - for _, addr := range iface.Addrs { + for _, addr := range iface.Addresses { if !accept(addr) { continue } diff --git a/config/config.go b/config/config.go index fd12d2db..2166b87d 100644 --- a/config/config.go +++ b/config/config.go @@ -246,31 +246,39 @@ type RawTun struct { DNSHijack []string `yaml:"dns-hijack" json:"dns-hijack"` AutoRoute bool `yaml:"auto-route" json:"auto-route"` AutoDetectInterface bool `yaml:"auto-detect-interface"` - RedirectToTun []string `yaml:"-" json:"-"` MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` GSO bool `yaml:"gso" json:"gso,omitempty"` GSOMaxSize uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"` //Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4_address,omitempty"` - Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6_address,omitempty"` - StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"` + Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6_address,omitempty"` + IPRoute2TableIndex int `yaml:"iproute2-table-index" json:"iproute2_table_index,omitempty"` + IPRoute2RuleIndex int `yaml:"iproute2-rule-index" json:"iproute2_rule_index,omitempty"` + AutoRedirect bool `yaml:"auto-redirect" json:"auto_redirect,omitempty"` + AutoRedirectInputMark uint32 `yaml:"auto-redirect-input-mark" json:"auto_redirect_input_mark,omitempty"` + AutoRedirectOutputMark uint32 `yaml:"auto-redirect-output-mark" json:"auto_redirect_output_mark,omitempty"` + StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"` + RouteAddress []netip.Prefix `yaml:"route-address" json:"route_address,omitempty"` + RouteAddressSet []string `yaml:"route-address-set" json:"route_address_set,omitempty"` + RouteExcludeAddress []netip.Prefix `yaml:"route-exclude-address" json:"route_exclude_address,omitempty"` + RouteExcludeAddressSet []string `yaml:"route-exclude-address-set" json:"route_exclude_address_set,omitempty"` + IncludeInterface []string `yaml:"include-interface" json:"include-interface,omitempty"` + ExcludeInterface []string `yaml:"exclude-interface" json:"exclude-interface,omitempty"` + IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"` + IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"` + ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"` + ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"` + IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"` + IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"` + ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"` + EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"` + UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"` + FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"` + Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4_route_address,omitempty"` Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6_route_address,omitempty"` Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4_route_exclude_address,omitempty"` Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6_route_exclude_address,omitempty"` - IncludeInterface []string `yaml:"include-interface" json:"include-interface,omitempty"` - ExcludeInterface []string `yaml:"exclude-interface" json:"exclude-interface,omitempty"` - IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"` - IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"` - ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"` - ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"` - IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"` - IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"` - ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"` - EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"` - UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"` - FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"` - TableIndex int `yaml:"table-index" json:"table-index"` } type RawTuicServer struct { @@ -564,13 +572,13 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { } config.RuleProviders = ruleProviders - subRules, err := parseSubRules(rawCfg, proxies) + subRules, err := parseSubRules(rawCfg, proxies, ruleProviders) if err != nil { return nil, err } config.SubRules = subRules - rules, err := parseRules(rawCfg.Rule, proxies, subRules, "rules") + rules, err := parseRules(rawCfg.Rule, proxies, ruleProviders, subRules, "rules") if err != nil { return nil, err } @@ -666,7 +674,6 @@ func parseGeneral(cfg *RawConfig) (*General, error) { updater.ExternalUIURL = cfg.ExternalUIURL } - cfg.Tun.RedirectToTun = cfg.EBpf.RedirectToTun return &General{ Inbound: Inbound{ Port: cfg.Port, @@ -845,6 +852,7 @@ func parseListeners(cfg *RawConfig) (listeners map[string]C.InboundListener, err } func parseRuleProviders(cfg *RawConfig) (ruleProviders map[string]providerTypes.RuleProvider, err error) { + RP.SetTunnel(T.Tunnel) ruleProviders = map[string]providerTypes.RuleProvider{} // parse rule provider for name, mapping := range cfg.RuleProvider { @@ -854,12 +862,11 @@ func parseRuleProviders(cfg *RawConfig) (ruleProviders map[string]providerTypes. } ruleProviders[name] = rp - RP.SetRuleProvider(rp) } return } -func parseSubRules(cfg *RawConfig, proxies map[string]C.Proxy) (subRules map[string][]C.Rule, err error) { +func parseSubRules(cfg *RawConfig, proxies map[string]C.Proxy, ruleProviders map[string]providerTypes.RuleProvider) (subRules map[string][]C.Rule, err error) { subRules = map[string][]C.Rule{} for name := range cfg.SubRules { subRules[name] = make([]C.Rule, 0) @@ -869,7 +876,7 @@ func parseSubRules(cfg *RawConfig, proxies map[string]C.Proxy) (subRules map[str return nil, fmt.Errorf("sub-rule name is empty") } var rules []C.Rule - rules, err = parseRules(rawRules, proxies, subRules, fmt.Sprintf("sub-rules[%s]", name)) + rules, err = parseRules(rawRules, proxies, ruleProviders, subRules, fmt.Sprintf("sub-rules[%s]", name)) if err != nil { return nil, err } @@ -922,7 +929,7 @@ func verifySubRuleCircularReferences(n string, subRules map[string][]C.Rule, arr return nil } -func parseRules(rulesConfig []string, proxies map[string]C.Proxy, subRules map[string][]C.Rule, format string) ([]C.Rule, error) { +func parseRules(rulesConfig []string, proxies map[string]C.Proxy, ruleProviders map[string]providerTypes.RuleProvider, subRules map[string][]C.Rule, format string) ([]C.Rule, error) { var rules []C.Rule // parse rules @@ -971,6 +978,12 @@ func parseRules(rulesConfig []string, proxies map[string]C.Proxy, subRules map[s return nil, fmt.Errorf("%s[%d] [%s] error: %s", format, idx, line, parseErr.Error()) } + for _, name := range parsed.ProviderNames() { + if _, ok := ruleProviders[name]; !ok { + return nil, fmt.Errorf("%s[%d] [%s] error: rule set [%s] not found", format, idx, line, name) + } + } + rules = append(rules, parsed) } @@ -1455,31 +1468,39 @@ func parseTun(rawTun RawTun, general *General) error { DNSHijack: rawTun.DNSHijack, AutoRoute: rawTun.AutoRoute, AutoDetectInterface: rawTun.AutoDetectInterface, - RedirectToTun: rawTun.RedirectToTun, - MTU: rawTun.MTU, - GSO: rawTun.GSO, - GSOMaxSize: rawTun.GSOMaxSize, - Inet4Address: []netip.Prefix{tunAddressPrefix}, - Inet6Address: rawTun.Inet6Address, - StrictRoute: rawTun.StrictRoute, + MTU: rawTun.MTU, + GSO: rawTun.GSO, + GSOMaxSize: rawTun.GSOMaxSize, + Inet4Address: []netip.Prefix{tunAddressPrefix}, + Inet6Address: rawTun.Inet6Address, + IPRoute2TableIndex: rawTun.IPRoute2TableIndex, + IPRoute2RuleIndex: rawTun.IPRoute2RuleIndex, + AutoRedirect: rawTun.AutoRedirect, + AutoRedirectInputMark: rawTun.AutoRedirectInputMark, + AutoRedirectOutputMark: rawTun.AutoRedirectOutputMark, + StrictRoute: rawTun.StrictRoute, + RouteAddress: rawTun.RouteAddress, + RouteAddressSet: rawTun.RouteAddressSet, + RouteExcludeAddress: rawTun.RouteExcludeAddress, + RouteExcludeAddressSet: rawTun.RouteExcludeAddressSet, + IncludeInterface: rawTun.IncludeInterface, + ExcludeInterface: rawTun.ExcludeInterface, + IncludeUID: rawTun.IncludeUID, + IncludeUIDRange: rawTun.IncludeUIDRange, + ExcludeUID: rawTun.ExcludeUID, + ExcludeUIDRange: rawTun.ExcludeUIDRange, + IncludeAndroidUser: rawTun.IncludeAndroidUser, + IncludePackage: rawTun.IncludePackage, + ExcludePackage: rawTun.ExcludePackage, + EndpointIndependentNat: rawTun.EndpointIndependentNat, + UDPTimeout: rawTun.UDPTimeout, + FileDescriptor: rawTun.FileDescriptor, + Inet4RouteAddress: rawTun.Inet4RouteAddress, Inet6RouteAddress: rawTun.Inet6RouteAddress, Inet4RouteExcludeAddress: rawTun.Inet4RouteExcludeAddress, Inet6RouteExcludeAddress: rawTun.Inet6RouteExcludeAddress, - IncludeInterface: rawTun.IncludeInterface, - ExcludeInterface: rawTun.ExcludeInterface, - IncludeUID: rawTun.IncludeUID, - IncludeUIDRange: rawTun.IncludeUIDRange, - ExcludeUID: rawTun.ExcludeUID, - ExcludeUIDRange: rawTun.ExcludeUIDRange, - IncludeAndroidUser: rawTun.IncludeAndroidUser, - IncludePackage: rawTun.IncludePackage, - ExcludePackage: rawTun.ExcludePackage, - EndpointIndependentNat: rawTun.EndpointIndependentNat, - UDPTimeout: rawTun.UDPTimeout, - FileDescriptor: rawTun.FileDescriptor, - TableIndex: rawTun.TableIndex, } return nil diff --git a/constant/provider/interface.go b/constant/provider/interface.go index bb73d1bc..f7dfc9cc 100644 --- a/constant/provider/interface.go +++ b/constant/provider/interface.go @@ -84,7 +84,7 @@ type RuleProvider interface { Match(*constant.Metadata) bool ShouldResolveIP() bool ShouldFindProcess() bool - AsRule(adaptor string) constant.Rule + Strategy() any } // Rule Behavior @@ -127,3 +127,9 @@ func (rf RuleFormat) String() string { return "Unknown" } } + +type Tunnel interface { + Providers() map[string]ProxyProvider + RuleProviders() map[string]RuleProvider + RuleUpdateCallback() *utils.Callback[RuleProvider] +} diff --git a/constant/rule.go b/constant/rule.go index 161c200a..a91ee6cb 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -116,4 +116,5 @@ type Rule interface { Payload() string ShouldResolveIP() bool ShouldFindProcess() bool + ProviderNames() []string } diff --git a/dns/policy.go b/dns/policy.go index a58123e3..fc60401b 100644 --- a/dns/policy.go +++ b/dns/policy.go @@ -37,14 +37,17 @@ func (p geositePolicy) Match(domain string) []dnsClient { } type domainSetPolicy struct { - domainSetProvider provider.RuleProvider - dnsClients []dnsClient + tunnel provider.Tunnel + name string + dnsClients []dnsClient } func (p domainSetPolicy) Match(domain string) []dnsClient { - metadata := &C.Metadata{Host: domain} - if ok := p.domainSetProvider.Match(metadata); ok { - return p.dnsClients + if ruleProvider, ok := p.tunnel.RuleProviders()[p.name]; ok { + metadata := &C.Metadata{Host: domain} + if ok := ruleProvider.Match(metadata); ok { + return p.dnsClients + } } return nil } diff --git a/dns/resolver.go b/dns/resolver.go index 08de69ad..28ffec6f 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -414,7 +414,7 @@ type Config struct { Pool *fakeip.Pool Hosts *trie.DomainTrie[resolver.HostValue] Policy *orderedmap.OrderedMap[string, []NameServer] - RuleProviders map[string]provider.RuleProvider + Tunnel provider.Tunnel CacheAlgorithm string } @@ -502,11 +502,12 @@ func NewResolver(config Config) *Resolver { key := temp[1] switch prefix { case "rule-set": - if p, ok := config.RuleProviders[key]; ok { + if _, ok := config.Tunnel.RuleProviders()[key]; ok { log.Debugln("Adding rule-set policy: %s ", key) insertPolicy(domainSetPolicy{ - domainSetProvider: p, - dnsClients: cacheTransform(nameserver), + tunnel: config.Tunnel, + name: key, + dnsClients: cacheTransform(nameserver), }) continue } else { diff --git a/docs/config.yaml b/docs/config.yaml index 462b3a44..9c51bc10 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -116,13 +116,25 @@ tun: # mtu: 9000 # 最大传输单元 # gso: false # 启用通用分段卸载,仅支持 Linux # gso-max-size: 65536 # 通用分段卸载包的最大大小 + auto-redirect: false # 自动配置 iptables 以重定向 TCP 连接。仅支持 Linux。带有 auto-redirect 的 auto-route 现在可以在路由器上按预期工作,无需干预。 # strict-route: true # 将所有连接路由到 tun 来防止泄漏,但你的设备将无法其他设备被访问 - inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由 + route-address-set: # 将指定规则集中的目标 IP CIDR 规则添加到防火墙, 不匹配的流量将绕过路由, 仅支持 Linux,且需要 nftables,`auto-route` 和 `auto-redirect` 已启用。 + - ruleset-1 + - ruleset-2 + route-exclude-address-set: # 将指定规则集中的目标 IP CIDR 规则添加到防火墙, 匹配的流量将绕过路由, 仅支持 Linux,且需要 nftables,`auto-route` 和 `auto-redirect` 已启用。 + - ruleset-3 + - ruleset-4 + route-address: # 启用 auto-route 时使用自定义路由而不是默认路由 - 0.0.0.0/1 - 128.0.0.0/1 - inet6-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由 - "::/1" - "8000::/1" + # inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由(旧写法) + # - 0.0.0.0/1 + # - 128.0.0.0/1 + # inet6-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由(旧写法) + # - "::/1" + # - "8000::/1" # endpoint-independent-nat: false # 启用独立于端点的 NAT # include-interface: # 限制被路由的接口。默认不限制,与 `exclude-interface` 冲突 # - "lan0" diff --git a/go.mod b/go.mod index 5d109d8d..ee0eeeb4 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 github.com/metacubex/sing-shadowsocks v0.2.6 github.com/metacubex/sing-shadowsocks2 v0.2.0 - github.com/metacubex/sing-tun v0.2.7-0.20240521155100-e8316a45a414 + github.com/metacubex/sing-tun v0.2.7-0.20240617013029-d05cf9df9cfe github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 github.com/metacubex/tfo-go v0.0.0-20240228025757-be1269474a66 @@ -35,8 +35,8 @@ require ( github.com/oschwald/maxminddb-golang v1.12.0 github.com/puzpuzpuz/xsync/v3 v3.1.0 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a - github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 - github.com/sagernet/sing v0.3.8 + github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a + github.com/sagernet/sing v0.5.0-alpha.10 github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 github.com/sagernet/sing-shadowtls v0.1.4 github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e @@ -47,11 +47,11 @@ require ( github.com/wk8/go-ordered-map/v2 v2.1.8 go.uber.org/automaxprocs v1.5.3 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.23.0 - golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 - golang.org/x/net v0.25.0 + golang.org/x/crypto v0.24.0 + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 + golang.org/x/net v0.26.0 golang.org/x/sync v0.7.0 - golang.org/x/sys v0.20.0 + golang.org/x/sys v0.21.0 google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 lukechampine.com/blake3 v1.3.0 @@ -92,6 +92,7 @@ require ( github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect + github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect @@ -100,14 +101,14 @@ require ( github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect - github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect + github.com/vishvananda/netns v0.0.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect go.uber.org/mock v0.4.0 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/text v0.15.0 // indirect + golang.org/x/mod v0.18.0 // indirect + golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.21.0 // indirect + golang.org/x/tools v0.22.0 // indirect ) -replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240518125217-e63d65a914d1 +replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240617013425-3e3bd9dab6a2 diff --git a/go.sum b/go.sum index 5c8219c2..a1d0cf5a 100644 --- a/go.sum +++ b/go.sum @@ -108,16 +108,16 @@ github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e h1:bLYn3GuRvW github.com/metacubex/quic-go v0.45.1-0.20240610004319-163fee60637e/go.mod h1:Yza2H7Ax1rxWPUcJx0vW+oAt9EsPuSiyQFhFabUPzwU= github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs= github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= -github.com/metacubex/sing v0.0.0-20240518125217-e63d65a914d1 h1:7hDHLTmjgtRoAp59STwPQpe5Pinwi4cWex+FB3Ohvco= -github.com/metacubex/sing v0.0.0-20240518125217-e63d65a914d1/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI= +github.com/metacubex/sing v0.0.0-20240617013425-3e3bd9dab6a2 h1:N5tidgg/FRmkgPw/AjRwhLUinKDx/ODCSbvv9xqRoLM= +github.com/metacubex/sing v0.0.0-20240617013425-3e3bd9dab6a2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72 h1:Wr4g1HCb5Z/QIFwFiVNjO2qL+dRu25+Mdn9xtAZZ+ew= github.com/metacubex/sing-quic v0.0.0-20240518034124-7696d3f7da72/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8= github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwVSgtE9R3QeFQ= github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg= github.com/metacubex/sing-shadowsocks2 v0.2.0 h1:hqwT/AfI5d5UdPefIzR6onGHJfDXs5zgOM5QSgaM/9A= github.com/metacubex/sing-shadowsocks2 v0.2.0/go.mod h1:LCKF6j1P94zN8ZS+LXRK1gmYTVGB3squivBSXAFnOg8= -github.com/metacubex/sing-tun v0.2.7-0.20240521155100-e8316a45a414 h1:IPxTZgQV6fVUBS8tozLMSFPHV3imYc/NbuGfp0bLQq0= -github.com/metacubex/sing-tun v0.2.7-0.20240521155100-e8316a45a414/go.mod h1:4VsMwZH1IlgPGFK1ZbBomZ/B2MYkTgs2+gnBAr5GOIo= +github.com/metacubex/sing-tun v0.2.7-0.20240617013029-d05cf9df9cfe h1:NrWjVEkRmEkdREVSpohMgEBoznS0PrRfJDr6iCV4348= +github.com/metacubex/sing-tun v0.2.7-0.20240617013029-d05cf9df9cfe/go.mod h1:WwJGbCx7bQcBzuQXiDOJvZH27R0kIjKNNlISIWsL6kM= github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ= github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= github.com/metacubex/sing-wireguard v0.0.0-20240321042214-224f96122a63 h1:AGyIB55UfQm/0ZH0HtQO9u3l//yjtHUpjeRjjPGfGRI= @@ -159,8 +159,10 @@ github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58 github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= -github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= -github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= +github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= +github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= +github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= +github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 h1:5bCAkvDDzSMITiHFjolBwpdqYsvycdTu71FsMEFXQ14= github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= @@ -206,8 +208,8 @@ github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= -github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= +github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= @@ -222,18 +224,18 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= @@ -252,19 +254,18 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= -golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 56e71632..55c40b6d 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -97,7 +97,7 @@ func ApplyConfig(cfg *config.Config, force bool) { updateHosts(cfg.Hosts) updateGeneral(cfg.General) updateNTP(cfg.NTP) - updateDNS(cfg.DNS, cfg.RuleProviders, cfg.General.IPv6) + updateDNS(cfg.DNS, cfg.General.IPv6) updateListeners(cfg.General, cfg.Listeners, force) updateIPTables(cfg) updateTun(cfg.General) @@ -211,7 +211,7 @@ func updateNTP(c *config.NTP) { } } -func updateDNS(c *config.DNS, ruleProvider map[string]provider.RuleProvider, generalIPv6 bool) { +func updateDNS(c *config.DNS, generalIPv6 bool) { if !c.Enable { resolver.DefaultResolver = nil resolver.DefaultHostMapper = nil @@ -237,7 +237,7 @@ func updateDNS(c *config.DNS, ruleProvider map[string]provider.RuleProvider, gen Default: c.DefaultNameserver, Policy: c.NameServerPolicy, ProxyServer: c.ProxyServerNameserver, - RuleProviders: ruleProvider, + Tunnel: tunnel.Tunnel, CacheAlgorithm: c.CacheAlgorithm, } @@ -355,7 +355,7 @@ func updateTun(general *config.General) { return } listener.ReCreateTun(general.Tun, tunnel.Tunnel) - listener.ReCreateRedirToTun(general.Tun.RedirectToTun) + listener.ReCreateRedirToTun(general.EBpf.RedirectToTun) } func updateSniffer(sniffer *config.Sniffer) { @@ -507,9 +507,7 @@ func updateIPTables(cfg *config.Config) { inboundInterface = iptables.InboundInterface } - if dialer.DefaultRoutingMark.Load() == 0 { - dialer.DefaultRoutingMark.Store(2158) - } + dialer.DefaultRoutingMark.CompareAndSwap(0, 2158) err = tproxy.SetTProxyIPTables(inboundInterface, bypass, uint16(tProxyPort), DnsRedirect, dnsPort.Port()) if err != nil { diff --git a/hub/route/configs.go b/hub/route/configs.go index f2cf298a..17d858d4 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -68,25 +68,34 @@ type tunSchema struct { GSO *bool `yaml:"gso" json:"gso,omitempty"` GSOMaxSize *uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"` //Inet4Address *[]netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"` - Inet6Address *[]netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"` - StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"` + Inet6Address *[]netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"` + IPRoute2TableIndex *int `yaml:"iproute2-table-index" json:"iproute2_table_index,omitempty"` + IPRoute2RuleIndex *int `yaml:"iproute2-rule-index" json:"iproute2_rule_index,omitempty"` + AutoRedirect *bool `yaml:"auto-redirect" json:"auto_redirect,omitempty"` + AutoRedirectInputMark *uint32 `yaml:"auto-redirect-input-mark" json:"auto_redirect_input_mark,omitempty"` + AutoRedirectOutputMark *uint32 `yaml:"auto-redirect-output-mark" json:"auto_redirect_output_mark,omitempty"` + StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"` + RouteAddress *[]netip.Prefix `yaml:"route-address" json:"route_address,omitempty"` + RouteAddressSet *[]string `yaml:"route-address-set" json:"route_address_set,omitempty"` + RouteExcludeAddress *[]netip.Prefix `yaml:"route-exclude-address" json:"route_exclude_address,omitempty"` + RouteExcludeAddressSet *[]string `yaml:"route-exclude-address-set" json:"route_exclude_address_set,omitempty"` + IncludeInterface *[]string `yaml:"include-interface" json:"include-interface,omitempty"` + ExcludeInterface *[]string `yaml:"exclude-interface" json:"exclude-interface,omitempty"` + IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"` + IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"` + ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"` + ExcludeUIDRange *[]string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"` + IncludeAndroidUser *[]int `yaml:"include-android-user" json:"include-android-user,omitempty"` + IncludePackage *[]string `yaml:"include-package" json:"include-package,omitempty"` + ExcludePackage *[]string `yaml:"exclude-package" json:"exclude-package,omitempty"` + EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"` + UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"` + FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"` + Inet4RouteAddress *[]netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"` Inet6RouteAddress *[]netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"` Inet4RouteExcludeAddress *[]netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"` Inet6RouteExcludeAddress *[]netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"` - IncludeInterface *[]string `yaml:"include-interface" json:"include-interface,omitempty"` - ExcludeInterface *[]string `yaml:"exclude-interface" json:"exclude-interface,omitempty"` - IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"` - IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"` - ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"` - ExcludeUIDRange *[]string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"` - IncludeAndroidUser *[]int `yaml:"include-android-user" json:"include-android-user,omitempty"` - IncludePackage *[]string `yaml:"include-package" json:"include-package,omitempty"` - ExcludePackage *[]string `yaml:"exclude-package" json:"exclude-package,omitempty"` - EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"` - UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"` - FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"` - TableIndex *int `yaml:"table-index" json:"table-index"` } type tuicServerSchema struct { @@ -157,6 +166,36 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun { if p.Inet6Address != nil { def.Inet6Address = *p.Inet6Address } + if p.IPRoute2TableIndex != nil { + def.IPRoute2TableIndex = *p.IPRoute2TableIndex + } + if p.IPRoute2RuleIndex != nil { + def.IPRoute2RuleIndex = *p.IPRoute2RuleIndex + } + if p.AutoRedirect != nil { + def.AutoRedirect = *p.AutoRedirect + } + if p.AutoRedirectInputMark != nil { + def.AutoRedirectInputMark = *p.AutoRedirectInputMark + } + if p.AutoRedirectOutputMark != nil { + def.AutoRedirectOutputMark = *p.AutoRedirectOutputMark + } + if p.StrictRoute != nil { + def.StrictRoute = *p.StrictRoute + } + if p.RouteAddress != nil { + def.RouteAddress = *p.RouteAddress + } + if p.RouteAddressSet != nil { + def.RouteAddressSet = *p.RouteAddressSet + } + if p.RouteExcludeAddress != nil { + def.RouteExcludeAddress = *p.RouteExcludeAddress + } + if p.RouteExcludeAddressSet != nil { + def.RouteExcludeAddressSet = *p.RouteExcludeAddressSet + } if p.Inet4RouteAddress != nil { def.Inet4RouteAddress = *p.Inet4RouteAddress } @@ -205,9 +244,6 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun { if p.FileDescriptor != nil { def.FileDescriptor = *p.FileDescriptor } - if p.TableIndex != nil { - def.TableIndex = *p.TableIndex - } } return def } diff --git a/listener/config/tun.go b/listener/config/tun.go index 7467e4a6..cea22bfd 100644 --- a/listener/config/tun.go +++ b/listener/config/tun.go @@ -27,27 +27,36 @@ type Tun struct { AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"` RedirectToTun []string `yaml:"-" json:"-"` - MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` - GSO bool `yaml:"gso" json:"gso,omitempty"` - GSOMaxSize uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"` - Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"` - Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"` - StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"` + MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` + GSO bool `yaml:"gso" json:"gso,omitempty"` + GSOMaxSize uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"` + Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"` + Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"` + IPRoute2TableIndex int `yaml:"iproute2-table-index" json:"iproute2_table_index,omitempty"` + IPRoute2RuleIndex int `yaml:"iproute2-rule-index" json:"iproute2_rule_index,omitempty"` + AutoRedirect bool `yaml:"auto-redirect" json:"auto_redirect,omitempty"` + AutoRedirectInputMark uint32 `yaml:"auto-redirect-input-mark" json:"auto_redirect_input_mark,omitempty"` + AutoRedirectOutputMark uint32 `yaml:"auto-redirect-output-mark" json:"auto_redirect_output_mark,omitempty"` + StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"` + RouteAddress []netip.Prefix `yaml:"route-address" json:"route_address,omitempty"` + RouteAddressSet []string `yaml:"route-address-set" json:"route_address_set,omitempty"` + RouteExcludeAddress []netip.Prefix `yaml:"route-exclude-address" json:"route_exclude_address,omitempty"` + RouteExcludeAddressSet []string `yaml:"route-exclude-address-set" json:"route_exclude_address_set,omitempty"` + IncludeInterface []string `yaml:"include-interface" json:"include-interface,omitempty"` + ExcludeInterface []string `yaml:"exclude-interface" json:"exclude-interface,omitempty"` + IncludeUID []uint32 `yaml:"include-uid" json:"include-uid,omitempty"` + IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"` + ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"` + ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"` + IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"` + IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"` + ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"` + EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"` + UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"` + FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"` + Inet4RouteAddress []netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"` Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"` Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"` Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"` - IncludeInterface []string `yaml:"include-interface" json:"include-interface,omitempty"` - ExcludeInterface []string `yaml:"exclude-interface" json:"exclude-interface,omitempty"` - IncludeUID []uint32 `yaml:"include-uid" json:"include-uid,omitempty"` - IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"` - ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"` - ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"` - IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"` - IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"` - ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"` - EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"` - UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"` - FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"` - TableIndex int `yaml:"table-index" json:"table-index"` } diff --git a/listener/inbound/tun.go b/listener/inbound/tun.go index 51747c46..a950f80d 100644 --- a/listener/inbound/tun.go +++ b/listener/inbound/tun.go @@ -18,29 +18,38 @@ type TunOption struct { AutoRoute bool `inbound:"auto-route,omitempty"` AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"` - MTU uint32 `inbound:"mtu,omitempty"` - GSO bool `inbound:"gso,omitempty"` - GSOMaxSize uint32 `inbound:"gso-max-size,omitempty"` - Inet4Address []string `inbound:"inet4_address,omitempty"` - Inet6Address []string `inbound:"inet6_address,omitempty"` - StrictRoute bool `inbound:"strict_route,omitempty"` + MTU uint32 `inbound:"mtu,omitempty"` + GSO bool `inbound:"gso,omitempty"` + GSOMaxSize uint32 `inbound:"gso-max-size,omitempty"` + Inet4Address []string `inbound:"inet4_address,omitempty"` + Inet6Address []string `inbound:"inet6_address,omitempty"` + IPRoute2TableIndex int `inbound:"iproute2-table-index"` + IPRoute2RuleIndex int `inbound:"iproute2-rule-index"` + AutoRedirect bool `inbound:"auto-redirect"` + AutoRedirectInputMark uint32 `inbound:"auto-redirect-input-mark"` + AutoRedirectOutputMark uint32 `inbound:"auto-redirect-output-mark"` + StrictRoute bool `inbound:"strict_route,omitempty"` + RouteAddress []string `inbound:"route-address"` + RouteAddressSet []string `inbound:"route-address-set"` + RouteExcludeAddress []string `inbound:"route-exclude-address"` + RouteExcludeAddressSet []string `inbound:"route-exclude-address-set"` + IncludeInterface []string `inbound:"include-interface,omitempty"` + ExcludeInterface []string `inbound:"exclude-interface"` + IncludeUID []uint32 `inbound:"include_uid,omitempty"` + IncludeUIDRange []string `inbound:"include_uid_range,omitempty"` + ExcludeUID []uint32 `inbound:"exclude_uid,omitempty"` + ExcludeUIDRange []string `inbound:"exclude_uid_range,omitempty"` + IncludeAndroidUser []int `inbound:"include_android_user,omitempty"` + IncludePackage []string `inbound:"include_package,omitempty"` + ExcludePackage []string `inbound:"exclude_package,omitempty"` + EndpointIndependentNat bool `inbound:"endpoint_independent_nat,omitempty"` + UDPTimeout int64 `inbound:"udp_timeout,omitempty"` + FileDescriptor int `inbound:"file-descriptor,omitempty"` + Inet4RouteAddress []string `inbound:"inet4_route_address,omitempty"` Inet6RouteAddress []string `inbound:"inet6_route_address,omitempty"` Inet4RouteExcludeAddress []string `inbound:"inet4_route_exclude_address,omitempty"` Inet6RouteExcludeAddress []string `inbound:"inet6_route_exclude_address,omitempty"` - IncludeInterface []string `inbound:"include-interface,omitempty"` - ExcludeInterface []string `inbound:"exclude-interface" json:"exclude-interface,omitempty"` - IncludeUID []uint32 `inbound:"include_uid,omitempty"` - IncludeUIDRange []string `inbound:"include_uid_range,omitempty"` - ExcludeUID []uint32 `inbound:"exclude_uid,omitempty"` - ExcludeUIDRange []string `inbound:"exclude_uid_range,omitempty"` - IncludeAndroidUser []int `inbound:"include_android_user,omitempty"` - IncludePackage []string `inbound:"include_package,omitempty"` - ExcludePackage []string `inbound:"exclude_package,omitempty"` - EndpointIndependentNat bool `inbound:"endpoint_independent_nat,omitempty"` - UDPTimeout int64 `inbound:"udp_timeout,omitempty"` - FileDescriptor int `inbound:"file-descriptor,omitempty"` - TableIndex int `inbound:"table-index,omitempty"` } func (o TunOption) Equal(config C.InboundConfig) bool { @@ -63,6 +72,16 @@ func NewTun(options *TunOption) (*Tun, error) { if !exist { return nil, errors.New("invalid tun stack") } + + routeAddress, err := LC.StringSliceToNetipPrefixSlice(options.RouteAddress) + if err != nil { + return nil, err + } + routeExcludeAddress, err := LC.StringSliceToNetipPrefixSlice(options.RouteExcludeAddress) + if err != nil { + return nil, err + } + inet4Address, err := LC.StringSliceToNetipPrefixSlice(options.Inet4Address) if err != nil { return nil, err @@ -91,35 +110,44 @@ func NewTun(options *TunOption) (*Tun, error) { Base: base, config: options, tun: LC.Tun{ - Enable: true, - Device: options.Device, - Stack: stack, - DNSHijack: options.DNSHijack, - AutoRoute: options.AutoRoute, - AutoDetectInterface: options.AutoDetectInterface, - MTU: options.MTU, - GSO: options.GSO, - GSOMaxSize: options.GSOMaxSize, - Inet4Address: inet4Address, - Inet6Address: inet6Address, - StrictRoute: options.StrictRoute, + Enable: true, + Device: options.Device, + Stack: stack, + DNSHijack: options.DNSHijack, + AutoRoute: options.AutoRoute, + AutoDetectInterface: options.AutoDetectInterface, + MTU: options.MTU, + GSO: options.GSO, + GSOMaxSize: options.GSOMaxSize, + Inet4Address: inet4Address, + Inet6Address: inet6Address, + IPRoute2TableIndex: options.IPRoute2TableIndex, + IPRoute2RuleIndex: options.IPRoute2RuleIndex, + AutoRedirect: options.AutoRedirect, + AutoRedirectInputMark: options.AutoRedirectInputMark, + AutoRedirectOutputMark: options.AutoRedirectOutputMark, + StrictRoute: options.StrictRoute, + RouteAddress: routeAddress, + RouteAddressSet: options.RouteAddressSet, + RouteExcludeAddress: routeExcludeAddress, + RouteExcludeAddressSet: options.RouteExcludeAddressSet, + IncludeInterface: options.IncludeInterface, + ExcludeInterface: options.ExcludeInterface, + IncludeUID: options.IncludeUID, + IncludeUIDRange: options.IncludeUIDRange, + ExcludeUID: options.ExcludeUID, + ExcludeUIDRange: options.ExcludeUIDRange, + IncludeAndroidUser: options.IncludeAndroidUser, + IncludePackage: options.IncludePackage, + ExcludePackage: options.ExcludePackage, + EndpointIndependentNat: options.EndpointIndependentNat, + UDPTimeout: options.UDPTimeout, + FileDescriptor: options.FileDescriptor, + Inet4RouteAddress: inet4RouteAddress, Inet6RouteAddress: inet6RouteAddress, Inet4RouteExcludeAddress: inet4RouteExcludeAddress, Inet6RouteExcludeAddress: inet6RouteExcludeAddress, - IncludeInterface: options.IncludeInterface, - ExcludeInterface: options.ExcludeInterface, - IncludeUID: options.IncludeUID, - IncludeUIDRange: options.IncludeUIDRange, - ExcludeUID: options.ExcludeUID, - ExcludeUIDRange: options.ExcludeUIDRange, - IncludeAndroidUser: options.IncludeAndroidUser, - IncludePackage: options.IncludePackage, - ExcludePackage: options.ExcludePackage, - EndpointIndependentNat: options.EndpointIndependentNat, - UDPTimeout: options.UDPTimeout, - FileDescriptor: options.FileDescriptor, - TableIndex: options.TableIndex, }, }, nil } diff --git a/listener/listener.go b/listener/listener.go index e3506188..76860e0d 100644 --- a/listener/listener.go +++ b/listener/listener.go @@ -820,11 +820,15 @@ func hasTunConfigChange(tunConf *LC.Tun) bool { LastTunConf.MTU != tunConf.MTU || LastTunConf.GSO != tunConf.GSO || LastTunConf.GSOMaxSize != tunConf.GSOMaxSize || + LastTunConf.IPRoute2TableIndex != tunConf.IPRoute2TableIndex || + LastTunConf.IPRoute2RuleIndex != tunConf.IPRoute2RuleIndex || + LastTunConf.AutoRedirect != tunConf.AutoRedirect || + LastTunConf.AutoRedirectInputMark != tunConf.AutoRedirectInputMark || + LastTunConf.AutoRedirectOutputMark != tunConf.AutoRedirectOutputMark || LastTunConf.StrictRoute != tunConf.StrictRoute || LastTunConf.EndpointIndependentNat != tunConf.EndpointIndependentNat || LastTunConf.UDPTimeout != tunConf.UDPTimeout || - LastTunConf.FileDescriptor != tunConf.FileDescriptor || - LastTunConf.TableIndex != tunConf.TableIndex { + LastTunConf.FileDescriptor != tunConf.FileDescriptor { return true } @@ -836,6 +840,22 @@ func hasTunConfigChange(tunConf *LC.Tun) bool { return tunConf.DNSHijack[i] < tunConf.DNSHijack[j] }) + sort.Slice(tunConf.RouteAddress, func(i, j int) bool { + return tunConf.RouteAddress[i].String() < tunConf.RouteAddress[j].String() + }) + + sort.Slice(tunConf.RouteAddressSet, func(i, j int) bool { + return tunConf.RouteAddressSet[i] < tunConf.RouteAddressSet[j] + }) + + sort.Slice(tunConf.RouteExcludeAddress, func(i, j int) bool { + return tunConf.RouteExcludeAddress[i].String() < tunConf.RouteExcludeAddress[j].String() + }) + + sort.Slice(tunConf.RouteExcludeAddressSet, func(i, j int) bool { + return tunConf.RouteExcludeAddressSet[i] < tunConf.RouteExcludeAddressSet[j] + }) + sort.Slice(tunConf.Inet4Address, func(i, j int) bool { return tunConf.Inet4Address[i].String() < tunConf.Inet4Address[j].String() }) @@ -897,6 +917,10 @@ func hasTunConfigChange(tunConf *LC.Tun) bool { }) if !slices.Equal(tunConf.DNSHijack, LastTunConf.DNSHijack) || + !slices.Equal(tunConf.RouteAddress, LastTunConf.RouteAddress) || + !slices.Equal(tunConf.RouteAddressSet, LastTunConf.RouteAddressSet) || + !slices.Equal(tunConf.RouteExcludeAddress, LastTunConf.RouteExcludeAddress) || + !slices.Equal(tunConf.RouteExcludeAddressSet, LastTunConf.RouteExcludeAddressSet) || !slices.Equal(tunConf.Inet4Address, LastTunConf.Inet4Address) || !slices.Equal(tunConf.Inet6Address, LastTunConf.Inet6Address) || !slices.Equal(tunConf.Inet4RouteAddress, LastTunConf.Inet4RouteAddress) || diff --git a/listener/sing/sing.go b/listener/sing/sing.go index 4e31faeb..10390e73 100644 --- a/listener/sing/sing.go +++ b/listener/sing/sing.go @@ -198,6 +198,12 @@ func (h *ListenerHandler) NewError(ctx context.Context, err error) { log.Warnln("%s listener get error: %+v", h.Type.String(), err) } +func (h *ListenerHandler) TypeMutation(typ C.Type) *ListenerHandler { + handler := *h + handler.Type = typ + return &handler +} + func ShouldIgnorePacketError(err error) bool { // ignore simple error if E.IsTimeout(err) || E.IsClosed(err) || E.IsCanceled(err) { diff --git a/listener/sing_tun/dns.go b/listener/sing_tun/dns.go index 42926732..505f16ac 100644 --- a/listener/sing_tun/dns.go +++ b/listener/sing_tun/dns.go @@ -8,6 +8,7 @@ import ( "time" "github.com/metacubex/mihomo/component/resolver" + C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/listener/sing" "github.com/metacubex/mihomo/log" @@ -124,3 +125,9 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network. } return h.ListenerHandler.NewPacketConnection(ctx, conn, metadata) } + +func (h *ListenerHandler) TypeMutation(typ C.Type) *ListenerHandler { + handle := *h + handle.ListenerHandler = h.ListenerHandler.TypeMutation(typ) + return &handle +} diff --git a/listener/sing_tun/iface.go b/listener/sing_tun/iface.go new file mode 100644 index 00000000..cc142078 --- /dev/null +++ b/listener/sing_tun/iface.go @@ -0,0 +1,70 @@ +package sing_tun + +import ( + "errors" + "net/netip" + + "github.com/metacubex/mihomo/component/iface" + + "github.com/sagernet/sing/common/control" +) + +type defaultInterfaceFinder struct{} + +var DefaultInterfaceFinder control.InterfaceFinder = (*defaultInterfaceFinder)(nil) + +func (f *defaultInterfaceFinder) Interfaces() []control.Interface { + ifaces, err := iface.Interfaces() + if err != nil { + return nil + } + interfaces := make([]control.Interface, 0, len(ifaces)) + for _, _interface := range ifaces { + interfaces = append(interfaces, control.Interface(*_interface)) + } + + return interfaces +} + +var errNoSuchInterface = errors.New("no such network interface") + +func (f *defaultInterfaceFinder) InterfaceIndexByName(name string) (int, error) { + ifaces, err := iface.Interfaces() + if err != nil { + return 0, err + } + for _, netInterface := range ifaces { + if netInterface.Name == name { + return netInterface.Index, nil + } + } + return 0, errNoSuchInterface +} + +func (f *defaultInterfaceFinder) InterfaceNameByIndex(index int) (string, error) { + ifaces, err := iface.Interfaces() + if err != nil { + return "", err + } + for _, netInterface := range ifaces { + if netInterface.Index == index { + return netInterface.Name, nil + } + } + return "", errNoSuchInterface +} + +func (f *defaultInterfaceFinder) InterfaceByAddr(addr netip.Addr) (*control.Interface, error) { + ifaces, err := iface.Interfaces() + if err != nil { + return nil, err + } + for _, netInterface := range ifaces { + for _, prefix := range netInterface.Addresses { + if prefix.Contains(addr) { + return (*control.Interface)(netInterface), nil + } + } + } + return nil, errNoSuchInterface +} diff --git a/listener/sing_tun/server.go b/listener/sing_tun/server.go index a5edb77f..53b88528 100644 --- a/listener/sing_tun/server.go +++ b/listener/sing_tun/server.go @@ -3,27 +3,33 @@ package sing_tun import ( "context" "fmt" + "io" "net" "net/netip" + "os" "runtime" "strconv" "strings" + "sync" "github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/iface" "github.com/metacubex/mihomo/component/resolver" C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/provider" LC "github.com/metacubex/mihomo/listener/config" "github.com/metacubex/mihomo/listener/sing" "github.com/metacubex/mihomo/log" tun "github.com/metacubex/sing-tun" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/ranges" + + "go4.org/netipx" + "golang.org/x/exp/maps" "golang.org/x/exp/slices" ) @@ -43,10 +49,21 @@ type Listener struct { networkUpdateMonitor tun.NetworkUpdateMonitor defaultInterfaceMonitor tun.DefaultInterfaceMonitor packageManager tun.PackageManager + autoRedirect tun.AutoRedirect + autoRedirectOutputMark int32 + + ruleUpdateCallbackCloser io.Closer + ruleUpdateMutex sync.Mutex + routeAddressMap map[string]*netipx.IPSet + routeExcludeAddressMap map[string]*netipx.IPSet + routeAddressSet []*netipx.IPSet + routeExcludeAddressSet []*netipx.IPSet dnsServerIp []string } +var emptyAddressSet = []*netipx.IPSet{{}} + func CalculateInterfaceName(name string) (tunName string) { if runtime.GOOS == "darwin" { tunName = "utun" @@ -110,14 +127,45 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis inbound.WithSpecialRules(""), } } + ctx := context.TODO() + rpTunnel := tunnel.(provider.Tunnel) if options.GSOMaxSize == 0 { options.GSOMaxSize = 65536 } + if runtime.GOOS != "linux" { + options.AutoRedirect = false + } tunName := options.Device if tunName == "" || !checkTunName(tunName) { tunName = CalculateInterfaceName(InterfaceName) options.Device = tunName } + routeAddress := options.RouteAddress + if len(options.Inet4RouteAddress) > 0 { + routeAddress = append(routeAddress, options.Inet4RouteAddress...) + } + if len(options.Inet6RouteAddress) > 0 { + routeAddress = append(routeAddress, options.Inet6RouteAddress...) + } + inet4RouteAddress := common.Filter(routeAddress, func(it netip.Prefix) bool { + return it.Addr().Is4() + }) + inet6RouteAddress := common.Filter(routeAddress, func(it netip.Prefix) bool { + return it.Addr().Is6() + }) + routeExcludeAddress := options.RouteExcludeAddress + if len(options.Inet4RouteExcludeAddress) > 0 { + routeExcludeAddress = append(routeExcludeAddress, options.Inet4RouteExcludeAddress...) + } + if len(options.Inet6RouteExcludeAddress) > 0 { + routeExcludeAddress = append(routeExcludeAddress, options.Inet6RouteExcludeAddress...) + } + inet4RouteExcludeAddress := common.Filter(routeExcludeAddress, func(it netip.Prefix) bool { + return it.Addr().Is4() + }) + inet6RouteExcludeAddress := common.Filter(routeExcludeAddress, func(it netip.Prefix) bool { + return it.Addr().Is6() + }) tunMTU := options.MTU if tunMTU == 0 { tunMTU = 9000 @@ -128,9 +176,21 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis } else { udpTimeout = int64(sing.UDPTimeout.Seconds()) } - tableIndex := options.TableIndex + tableIndex := options.IPRoute2TableIndex if tableIndex == 0 { - tableIndex = 2022 + tableIndex = tun.DefaultIPRoute2TableIndex + } + ruleIndex := options.IPRoute2RuleIndex + if ruleIndex == 0 { + ruleIndex = tun.DefaultIPRoute2RuleIndex + } + inputMark := options.AutoRedirectInputMark + if inputMark == 0 { + inputMark = tun.DefaultAutoRedirectInputMark + } + outputMark := options.AutoRedirectOutputMark + if outputMark == 0 { + outputMark = tun.DefaultAutoRedirectOutputMark } includeUID := uidToRange(options.IncludeUID) if len(options.IncludeUIDRange) > 0 { @@ -202,6 +262,8 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis } }() + interfaceFinder := DefaultInterfaceFinder + networkUpdateMonitor, err := tun.NewNetworkUpdateMonitor(log.SingLogger) if err != nil { err = E.Cause(err, "create NetworkUpdateMonitor") @@ -236,11 +298,15 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis Inet4Address: options.Inet4Address, Inet6Address: options.Inet6Address, AutoRoute: options.AutoRoute, + IPRoute2TableIndex: tableIndex, + IPRoute2RuleIndex: ruleIndex, + AutoRedirectInputMark: inputMark, + AutoRedirectOutputMark: outputMark, StrictRoute: options.StrictRoute, - Inet4RouteAddress: options.Inet4RouteAddress, - Inet6RouteAddress: options.Inet6RouteAddress, - Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress, - Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress, + Inet4RouteAddress: inet4RouteAddress, + Inet6RouteAddress: inet6RouteAddress, + Inet4RouteExcludeAddress: inet4RouteExcludeAddress, + Inet6RouteExcludeAddress: inet6RouteExcludeAddress, IncludeInterface: options.IncludeInterface, ExcludeInterface: options.ExcludeInterface, IncludeUID: includeUID, @@ -250,7 +316,56 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis ExcludePackage: options.ExcludePackage, FileDescriptor: options.FileDescriptor, InterfaceMonitor: defaultInterfaceMonitor, - TableIndex: tableIndex, + } + + if options.AutoRedirect { + l.routeAddressMap = make(map[string]*netipx.IPSet) + l.routeExcludeAddressMap = make(map[string]*netipx.IPSet) + + if !options.AutoRoute { + return nil, E.New("`auto-route` is required by `auto-redirect`") + } + disableNFTables, dErr := strconv.ParseBool(os.Getenv("DISABLE_NFTABLES")) + l.autoRedirect, err = tun.NewAutoRedirect(tun.AutoRedirectOptions{ + TunOptions: &tunOptions, + Context: ctx, + Handler: handler.TypeMutation(C.REDIR), + Logger: log.SingLogger, + NetworkMonitor: networkUpdateMonitor, + InterfaceFinder: interfaceFinder, + TableName: "mihomo", + DisableNFTables: dErr == nil && disableNFTables, + RouteAddressSet: &l.routeAddressSet, + RouteExcludeAddressSet: &l.routeExcludeAddressSet, + }) + if err != nil { + err = E.Cause(err, "initialize auto redirect") + return + } + + var markMode bool + for _, routeAddressSet := range options.RouteAddressSet { + rp, loaded := rpTunnel.RuleProviders()[routeAddressSet] + if !loaded { + err = E.New("parse route-address-set: rule-set not found: ", routeAddressSet) + return + } + l.updateRule(rp, false, false) + markMode = true + } + for _, routeExcludeAddressSet := range options.RouteExcludeAddressSet { + rp, loaded := rpTunnel.RuleProviders()[routeExcludeAddressSet] + if !loaded { + err = E.New("parse route-exclude_address-set: rule-set not found: ", routeExcludeAddressSet) + return + } + l.updateRule(rp, true, false) + markMode = true + } + if markMode { + tunOptions.AutoRedirectMarkMode = true + } + } err = l.buildAndroidRules(&tunOptions) @@ -269,14 +384,14 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis resolver.AddSystemDnsBlacklist(dnsServerIp...) stackOptions := tun.StackOptions{ - Context: context.TODO(), + Context: ctx, Tun: tunIf, TunOptions: tunOptions, EndpointIndependentNat: options.EndpointIndependentNat, UDPTimeout: udpTimeout, Handler: handler, Logger: log.SingLogger, - InterfaceFinder: control.DefaultInterfaceFinder(), + InterfaceFinder: interfaceFinder, EnforceBindInterface: EnforceBindInterface, } @@ -299,13 +414,76 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis } l.tunStack = tunStack + if l.autoRedirect != nil { + if len(l.options.RouteAddressSet) > 0 && len(l.routeAddressSet) == 0 { + l.routeAddressSet = emptyAddressSet // without this we can't call UpdateRouteAddressSet after Start + } + if len(l.options.RouteExcludeAddressSet) > 0 && len(l.routeExcludeAddressSet) == 0 { + l.routeExcludeAddressSet = emptyAddressSet // without this we can't call UpdateRouteAddressSet after Start + } + err = l.autoRedirect.Start() + if err != nil { + err = E.Cause(err, "auto redirect") + return + } + if tunOptions.AutoRedirectMarkMode { + l.autoRedirectOutputMark = int32(outputMark) + dialer.DefaultRoutingMark.Store(l.autoRedirectOutputMark) + l.autoRedirect.UpdateRouteAddressSet() + l.ruleUpdateCallbackCloser = rpTunnel.RuleUpdateCallback().Register(l.ruleUpdateCallback) + } + } + //l.openAndroidHotspot(tunOptions) - l.addrStr = fmt.Sprintf("%s(%s,%s), mtu: %d, auto route: %v, ip stack: %s", - tunName, tunOptions.Inet4Address, tunOptions.Inet6Address, tunMTU, options.AutoRoute, options.Stack) + l.addrStr = fmt.Sprintf("%s(%s,%s), mtu: %d, auto route: %v, auto redir: %v, ip stack: %s", + tunName, tunOptions.Inet4Address, tunOptions.Inet6Address, tunMTU, options.AutoRoute, options.AutoRedirect, options.Stack) return } +func (l *Listener) ruleUpdateCallback(ruleProvider provider.RuleProvider) { + name := ruleProvider.Name() + if slices.Contains(l.options.RouteAddressSet, name) { + l.updateRule(ruleProvider, false, true) + return + } + if slices.Contains(l.options.RouteExcludeAddressSet, name) { + l.updateRule(ruleProvider, true, true) + return + } +} + +func (l *Listener) updateRule(ruleProvider provider.RuleProvider, exclude bool, update bool) { + l.ruleUpdateMutex.Lock() + defer l.ruleUpdateMutex.Unlock() + name := ruleProvider.Name() + switch rp := ruleProvider.Strategy().(type) { + case interface{ ToIpCidr() *netipx.IPSet }: + if !exclude { + ipCidr := rp.ToIpCidr() + if ipCidr != nil { + l.routeAddressMap[name] = ipCidr + } else { + delete(l.routeAddressMap, name) + } + l.routeAddressSet = maps.Values(l.routeAddressMap) + } else { + ipCidr := rp.ToIpCidr() + if ipCidr != nil { + l.routeExcludeAddressMap[name] = ipCidr + } else { + delete(l.routeExcludeAddressMap, name) + } + l.routeExcludeAddressSet = maps.Values(l.routeExcludeAddressMap) + } + default: + return + } + if update && l.autoRedirect != nil { + l.autoRedirect.UpdateRouteAddressSet() + } +} + func (l *Listener) FlushDefaultInterface() { if l.options.AutoDetectInterface { for _, destination := range []netip.Addr{netip.IPv4Unspecified(), netip.IPv6Unspecified(), netip.MustParseAddr("1.1.1.1")} { @@ -347,11 +525,11 @@ func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges. } var start, end uint64 var err error - start, err = strconv.ParseUint(uidRange[:subIndex], 10, 32) + start, err = strconv.ParseUint(uidRange[:subIndex], 0, 32) if err != nil { return nil, E.Cause(err, "parse range start") } - end, err = strconv.ParseUint(uidRange[subIndex+1:], 10, 32) + end, err = strconv.ParseUint(uidRange[subIndex+1:], 0, 32) if err != nil { return nil, E.Cause(err, "parse range end") } @@ -363,9 +541,14 @@ func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges. func (l *Listener) Close() error { l.closed = true resolver.RemoveSystemDnsBlacklist(l.dnsServerIp...) + if l.autoRedirectOutputMark != 0 { + dialer.DefaultRoutingMark.CompareAndSwap(l.autoRedirectOutputMark, 0) + } return common.Close( + l.ruleUpdateCallbackCloser, l.tunStack, l.tunIf, + l.autoRedirect, l.defaultInterfaceMonitor, l.networkUpdateMonitor, l.packageManager, diff --git a/listener/tproxy/tproxy_iptables.go b/listener/tproxy/tproxy_iptables.go index 6c6e2cc8..bc26b125 100644 --- a/listener/tproxy/tproxy_iptables.go +++ b/listener/tproxy/tproxy_iptables.go @@ -119,9 +119,7 @@ func CleanupTProxyIPTables() { log.Warnln("Cleanup tproxy linux iptables") - if int(dialer.DefaultRoutingMark.Load()) == 2158 { - dialer.DefaultRoutingMark.Store(0) - } + dialer.DefaultRoutingMark.CompareAndSwap(2158, 0) if _, err := cmd.ExecCmd("iptables -t mangle -L mihomo_divert"); err != nil { return diff --git a/rules/common/base.go b/rules/common/base.go index d912107c..670df1d9 100644 --- a/rules/common/base.go +++ b/rules/common/base.go @@ -20,6 +20,8 @@ func (b *Base) ShouldResolveIP() bool { return false } +func (b *Base) ProviderNames() []string { return nil } + func HasNoResolve(params []string) bool { for _, p := range params { if p == noResolve { diff --git a/rules/logic/logic.go b/rules/logic/logic.go index af8c31a4..397a16b7 100644 --- a/rules/logic/logic.go +++ b/rules/logic/logic.go @@ -298,3 +298,10 @@ func (logic *Logic) ShouldResolveIP() bool { func (logic *Logic) ShouldFindProcess() bool { return logic.needProcess } + +func (logic *Logic) ProviderNames() (names []string) { + for _, rule := range logic.rules { + names = append(names, rule.ProviderNames()...) + } + return +} diff --git a/rules/provider/ipcidr_strategy.go b/rules/provider/ipcidr_strategy.go index c93facd9..d0545c7c 100644 --- a/rules/provider/ipcidr_strategy.go +++ b/rules/provider/ipcidr_strategy.go @@ -4,6 +4,8 @@ import ( "github.com/metacubex/mihomo/component/cidr" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/log" + + "go4.org/netipx" ) type ipcidrStrategy struct { @@ -52,6 +54,10 @@ func (i *ipcidrStrategy) FinishInsert() { i.cidrSet.Merge() } +func (i *ipcidrStrategy) ToIpCidr() *netipx.IPSet { + return i.cidrSet.ToIPSet() +} + func NewIPCidrStrategy() *ipcidrStrategy { return &ipcidrStrategy{} } diff --git a/rules/provider/patch_android.go b/rules/provider/patch_android.go index 2bd5ffc8..7ef1df1b 100644 --- a/rules/provider/patch_android.go +++ b/rules/provider/patch_android.go @@ -12,8 +12,8 @@ type UpdatableProvider interface { UpdatedAt() time.Time } -func (f *ruleSetProvider) UpdatedAt() time.Time { - return f.Fetcher.UpdatedAt +func (rp *ruleSetProvider) UpdatedAt() time.Time { + return rp.Fetcher.UpdatedAt } func (rp *ruleSetProvider) Close() error { diff --git a/rules/provider/provider.go b/rules/provider/provider.go index adc2e44a..7616290f 100644 --- a/rules/provider/provider.go +++ b/rules/provider/provider.go @@ -15,12 +15,14 @@ import ( P "github.com/metacubex/mihomo/constant/provider" ) -var ( - ruleProviders = map[string]P.RuleProvider{} -) +var tunnel P.Tunnel + +func SetTunnel(t P.Tunnel) { + tunnel = t +} type ruleSetProvider struct { - *resource.Fetcher[any] + *resource.Fetcher[ruleStrategy] behavior P.RuleBehavior format P.RuleFormat strategy ruleStrategy @@ -49,16 +51,6 @@ type ruleStrategy interface { FinishInsert() } -func RuleProviders() map[string]P.RuleProvider { - return ruleProviders -} - -func SetRuleProvider(ruleProvider P.RuleProvider) { - if ruleProvider != nil { - ruleProviders[(ruleProvider).Name()] = ruleProvider - } -} - func (rp *ruleSetProvider) Type() P.ProviderType { return P.Rule } @@ -99,8 +91,8 @@ func (rp *ruleSetProvider) ShouldFindProcess() bool { return rp.strategy.ShouldFindProcess() } -func (rp *ruleSetProvider) AsRule(adaptor string) C.Rule { - panic("implement me") +func (rp *ruleSetProvider) Strategy() any { + return rp.strategy } func (rp *ruleSetProvider) MarshalJSON() ([]byte, error) { @@ -123,13 +115,15 @@ func NewRuleSetProvider(name string, behavior P.RuleBehavior, format P.RuleForma format: format, } - onUpdate := func(elm interface{}) { - strategy := elm.(ruleStrategy) + onUpdate := func(strategy ruleStrategy) { rp.strategy = strategy + tunnel.RuleUpdateCallback().Emit(rp) } rp.strategy = newStrategy(behavior, parse) - rp.Fetcher = resource.NewFetcher(name, interval, vehicle, func(bytes []byte) (any, error) { return rulesParse(bytes, newStrategy(behavior, parse), format) }, onUpdate) + rp.Fetcher = resource.NewFetcher(name, interval, vehicle, func(bytes []byte) (ruleStrategy, error) { + return rulesParse(bytes, newStrategy(behavior, parse), format) + }, onUpdate) wrapper := &RuleSetProvider{ rp, @@ -158,7 +152,7 @@ func newStrategy(behavior P.RuleBehavior, parse func(tp, payload, target string, var ErrNoPayload = errors.New("file must have a `payload` field") -func rulesParse(buf []byte, strategy ruleStrategy, format P.RuleFormat) (any, error) { +func rulesParse(buf []byte, strategy ruleStrategy, format P.RuleFormat) (ruleStrategy, error) { strategy.Reset() schema := &RulePayload{} diff --git a/rules/provider/rule_set.go b/rules/provider/rule_set.go index 1d940188..d85db805 100644 --- a/rules/provider/rule_set.go +++ b/rules/provider/rule_set.go @@ -1,7 +1,6 @@ package provider import ( - "fmt" C "github.com/metacubex/mihomo/constant" P "github.com/metacubex/mihomo/constant/provider" "github.com/metacubex/mihomo/rules/common" @@ -11,13 +10,18 @@ type RuleSet struct { *common.Base ruleProviderName string adapter string - ruleProvider P.RuleProvider noResolveIP bool shouldFindProcess bool } func (rs *RuleSet) ShouldFindProcess() bool { - return rs.shouldFindProcess || rs.getProviders().ShouldFindProcess() + if rs.shouldFindProcess { + return true + } + if provider, ok := rs.getProvider(); ok { + return provider.ShouldFindProcess() + } + return false } func (rs *RuleSet) RuleType() C.RuleType { @@ -25,7 +29,10 @@ func (rs *RuleSet) RuleType() C.RuleType { } func (rs *RuleSet) Match(metadata *C.Metadata) (bool, string) { - return rs.getProviders().Match(metadata), rs.adapter + if provider, ok := rs.getProvider(); ok { + return provider.Match(metadata), rs.adapter + } + return false, "" } func (rs *RuleSet) Adapter() string { @@ -33,31 +40,37 @@ func (rs *RuleSet) Adapter() string { } func (rs *RuleSet) Payload() string { - return rs.getProviders().Name() + if provider, ok := rs.getProvider(); ok { + return provider.Name() + } + return "" } func (rs *RuleSet) ShouldResolveIP() bool { - return !rs.noResolveIP && rs.getProviders().ShouldResolveIP() -} -func (rs *RuleSet) getProviders() P.RuleProvider { - if rs.ruleProvider == nil { - rp := RuleProviders()[rs.ruleProviderName] - rs.ruleProvider = rp + if rs.noResolveIP { + return false } + if provider, ok := rs.getProvider(); ok { + return provider.ShouldResolveIP() + } + return false +} - return rs.ruleProvider +func (rs *RuleSet) ProviderNames() []string { + return []string{rs.ruleProviderName} +} + +func (rs *RuleSet) getProvider() (P.RuleProvider, bool) { + pp, ok := tunnel.RuleProviders()[rs.ruleProviderName] + return pp, ok } func NewRuleSet(ruleProviderName string, adapter string, noResolveIP bool) (*RuleSet, error) { - rp, ok := RuleProviders()[ruleProviderName] - if !ok { - return nil, fmt.Errorf("rule set %s not found", ruleProviderName) - } - return &RuleSet{ + rs := &RuleSet{ Base: &common.Base{}, ruleProviderName: ruleProviderName, adapter: adapter, - ruleProvider: rp, noResolveIP: noResolveIP, - }, nil + } + return rs, nil } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 2c1b894f..1a6f104d 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -13,6 +13,7 @@ import ( "time" N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/component/loopback" "github.com/metacubex/mihomo/component/nat" P "github.com/metacubex/mihomo/component/process" @@ -50,11 +51,15 @@ var ( findProcessMode P.FindProcessMode fakeIPRange netip.Prefix + + ruleUpdateCallback = utils.NewCallback[provider.RuleProvider]() ) type tunnel struct{} -var Tunnel C.Tunnel = tunnel{} +var Tunnel = tunnel{} +var _ C.Tunnel = Tunnel +var _ provider.Tunnel = Tunnel func (t tunnel) HandleTCPConn(conn net.Conn, metadata *C.Metadata) { connCtx := icontext.NewConnContext(conn, metadata) @@ -73,6 +78,18 @@ func (t tunnel) NatTable() C.NatTable { return natTable } +func (t tunnel) Providers() map[string]provider.ProxyProvider { + return providers +} + +func (t tunnel) RuleProviders() map[string]provider.RuleProvider { + return ruleProviders +} + +func (t tunnel) RuleUpdateCallback() *utils.Callback[provider.RuleProvider] { + return ruleUpdateCallback +} + func OnSuspend() { status.Store(Suspend) }