diff --git a/go.mod b/go.mod index 1ee6a66..9aeae0f 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 github.com/sagernet/sing v0.2.15 github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 + go4.org/netipx v0.0.0-20230824141953-6213f710f925 golang.org/x/net v0.17.0 golang.org/x/sys v0.13.0 ) diff --git a/go.sum b/go.sum index 130898d..75ddfc1 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s= 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= +go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ= +go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/tun.go b/tun.go index efeab08..977d4eb 100644 --- a/tun.go +++ b/tun.go @@ -33,25 +33,27 @@ type WinTun interface { } type Options struct { - Name string - Inet4Address []netip.Prefix - Inet6Address []netip.Prefix - MTU uint32 - AutoRoute bool - StrictRoute bool - Inet4RouteAddress []netip.Prefix - Inet6RouteAddress []netip.Prefix - IncludeInterface []string - ExcludeInterface []string - IncludeUID []ranges.Range[uint32] - ExcludeUID []ranges.Range[uint32] - IncludeAndroidUser []int - IncludePackage []string - ExcludePackage []string - InterfaceMonitor DefaultInterfaceMonitor - TableIndex int - FileDescriptor int - Logger logger.Logger + Name string + Inet4Address []netip.Prefix + Inet6Address []netip.Prefix + MTU uint32 + AutoRoute bool + StrictRoute bool + Inet4RouteAddress []netip.Prefix + Inet6RouteAddress []netip.Prefix + Inet4RouteExcludeAddress []netip.Prefix + Inet6RouteExcludeAddress []netip.Prefix + IncludeInterface []string + ExcludeInterface []string + IncludeUID []ranges.Range[uint32] + ExcludeUID []ranges.Range[uint32] + IncludeAndroidUser []int + IncludePackage []string + ExcludePackage []string + InterfaceMonitor DefaultInterfaceMonitor + TableIndex int + FileDescriptor int + Logger logger.Logger } func CalculateInterfaceName(name string) (tunName string) { diff --git a/tun_darwin.go b/tun_darwin.go index a9bba89..7ff2822 100644 --- a/tun_darwin.go +++ b/tun_darwin.go @@ -263,43 +263,16 @@ func configure(tunFd int, ifIndex int, name string, options Options) error { } } if options.AutoRoute { - if len(options.Inet4Address) > 0 { - var routes []netip.Prefix - if len(options.Inet4RouteAddress) > 0 { - routes = append(options.Inet4RouteAddress, netip.PrefixFrom(options.Inet4Address[0].Addr().Next(), 32)) + var routeRanges []netip.Prefix + routeRanges, err = options.BuildAutoRouteRanges() + for _, routeRange := range routeRanges { + if routeRange.Addr().Is4() { + err = addRoute(routeRange, options.Inet4Address[0].Addr()) } else { - routes = []netip.Prefix{ - netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 0, 0, 0}), 8), - netip.PrefixFrom(netip.AddrFrom4([4]byte{2, 0, 0, 0}), 7), - netip.PrefixFrom(netip.AddrFrom4([4]byte{4, 0, 0, 0}), 6), - netip.PrefixFrom(netip.AddrFrom4([4]byte{8, 0, 0, 0}), 5), - netip.PrefixFrom(netip.AddrFrom4([4]byte{16, 0, 0, 0}), 4), - netip.PrefixFrom(netip.AddrFrom4([4]byte{32, 0, 0, 0}), 3), - netip.PrefixFrom(netip.AddrFrom4([4]byte{64, 0, 0, 0}), 2), - netip.PrefixFrom(netip.AddrFrom4([4]byte{128, 0, 0, 0}), 1), - } - } - for _, subnet := range routes { - err = addRoute(subnet, options.Inet4Address[0].Addr()) - if err != nil { - return E.Cause(err, "add ipv4 route ", subnet) - } + err = addRoute(routeRange, options.Inet6Address[0].Addr()) } - } - if len(options.Inet6Address) > 0 { - var routes []netip.Prefix - if len(options.Inet6RouteAddress) > 0 { - routes = append(options.Inet6RouteAddress, netip.PrefixFrom(options.Inet6Address[0].Addr().Next(), 128)) - } else { - routes = []netip.Prefix{ - netip.PrefixFrom(netip.AddrFrom16([16]byte{32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}), 3), - } - } - for _, subnet := range routes { - err = addRoute(subnet, options.Inet6Address[0].Addr()) - if err != nil { - return E.Cause(err, "add ipv6 route ", subnet) - } + if err != nil { + return E.Cause(err, "add route: ", routeRange) } } flushDNSCache() diff --git a/tun_linux.go b/tun_linux.go index 4217548..bf49d2e 100644 --- a/tun_linux.go +++ b/tun_linux.go @@ -188,57 +188,36 @@ func (t *NativeTun) Close() error { return E.Errors(t.unsetRoute(), t.unsetRules(), common.Close(common.PtrOrNil(t.tunFile))) } -func (t *NativeTun) routes(tunLink netlink.Link) []netlink.Route { +func prefixToIPNet(prefix netip.Prefix) *net.IPNet { + return &net.IPNet{ + IP: prefix.Addr().AsSlice(), + Mask: net.CIDRMask(prefix.Bits(), prefix.Addr().BitLen()), + } +} + +func (t *NativeTun) routes(tunLink netlink.Link) ([]netlink.Route, error) { var routes []netlink.Route - if len(t.options.Inet4Address) > 0 { - if t.options.AutoRoute { - if len(t.options.Inet4RouteAddress) > 0 { - for _, addr := range t.options.Inet4RouteAddress { - routes = append(routes, netlink.Route{ - Dst: &net.IPNet{ - IP: addr.Addr().AsSlice(), - Mask: net.CIDRMask(addr.Bits(), 32), - }, - LinkIndex: tunLink.Attrs().Index, - Table: t.options.TableIndex, - }) - } - } else { - routes = append(routes, netlink.Route{ - Dst: &net.IPNet{ - IP: net.IPv4zero, - Mask: net.CIDRMask(0, 32), - }, - LinkIndex: tunLink.Attrs().Index, - Table: t.options.TableIndex, - }) - } - } + for _, address := range t.options.Inet6Address { + routes = append(routes, netlink.Route{ + Dst: prefixToIPNet(address), + LinkIndex: tunLink.Attrs().Index, + Table: t.options.TableIndex, + }) } - if len(t.options.Inet6Address) > 0 { - if len(t.options.Inet6RouteAddress) > 0 { - for _, addr := range t.options.Inet6RouteAddress { - routes = append(routes, netlink.Route{ - Dst: &net.IPNet{ - IP: addr.Addr().AsSlice(), - Mask: net.CIDRMask(addr.Bits(), 128), - }, - LinkIndex: tunLink.Attrs().Index, - Table: t.options.TableIndex, - }) - } - } else { + if t.options.AutoRoute { + routeRanges, err := t.options.BuildAutoRouteRanges() + if err != nil { + return nil, err + } + for _, routeRange := range routeRanges { routes = append(routes, netlink.Route{ - Dst: &net.IPNet{ - IP: net.IPv6zero, - Mask: net.CIDRMask(0, 128), - }, + Dst: prefixToIPNet(routeRange), LinkIndex: tunLink.Attrs().Index, Table: t.options.TableIndex, }) } } - return routes + return routes, nil } const ( @@ -615,7 +594,11 @@ func (t *NativeTun) rules() []*netlink.Rule { } func (t *NativeTun) setRoute(tunLink netlink.Link) error { - for i, route := range t.routes(tunLink) { + routes, err := t.routes(tunLink) + if err != nil { + return err + } + for i, route := range routes { err := netlink.RouteAdd(&route) if err != nil { return E.Cause(err, "add route ", i) @@ -646,8 +629,10 @@ func (t *NativeTun) unsetRoute() error { } func (t *NativeTun) unsetRoute0(tunLink netlink.Link) error { - for _, route := range t.routes(tunLink) { - _ = netlink.RouteDel(&route) + if routes, err := t.routes(tunLink); err == nil { + for _, route := range routes { + _ = netlink.RouteDel(&route) + } } return nil } diff --git a/tun_rules.go b/tun_rules.go index c8bd510..41a9e7c 100644 --- a/tun_rules.go +++ b/tun_rules.go @@ -2,13 +2,17 @@ package tun import ( "context" + "net/netip" "os" + "runtime" "sort" "strconv" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/ranges" + + "go4.org/netipx" ) const ( @@ -96,3 +100,74 @@ func buildExcludedRanges(includeRanges []ranges.Range[uint32], excludeRanges []r } return ranges.Merge(uidRanges) } + +const autoRouteUseSubRanges = runtime.GOOS == "darwin" + +func (o *Options) BuildAutoRouteRanges() ([]netip.Prefix, error) { + var routeRanges []netip.Prefix + if len(o.Inet4Address) > 0 { + var inet4Ranges []netip.Prefix + if len(o.Inet4RouteAddress) > 0 { + inet4Ranges = o.Inet4RouteAddress + } else if autoRouteUseSubRanges { + inet4Ranges = []netip.Prefix{ + netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 0, 0, 0}), 8), + netip.PrefixFrom(netip.AddrFrom4([4]byte{2, 0, 0, 0}), 7), + netip.PrefixFrom(netip.AddrFrom4([4]byte{4, 0, 0, 0}), 6), + netip.PrefixFrom(netip.AddrFrom4([4]byte{8, 0, 0, 0}), 5), + netip.PrefixFrom(netip.AddrFrom4([4]byte{16, 0, 0, 0}), 4), + netip.PrefixFrom(netip.AddrFrom4([4]byte{32, 0, 0, 0}), 3), + netip.PrefixFrom(netip.AddrFrom4([4]byte{64, 0, 0, 0}), 2), + netip.PrefixFrom(netip.AddrFrom4([4]byte{128, 0, 0, 0}), 1), + } + } else { + inet4Ranges = []netip.Prefix{netip.PrefixFrom(netip.IPv4Unspecified(), 0)} + } + if len(o.Inet4RouteExcludeAddress) == 0 { + routeRanges = append(routeRanges, inet4Ranges...) + } else { + var builder netipx.IPSetBuilder + for _, inet4Range := range inet4Ranges { + builder.AddPrefix(inet4Range) + } + for _, prefix := range o.Inet4RouteExcludeAddress { + builder.RemovePrefix(prefix) + } + resultSet, err := builder.IPSet() + if err != nil { + return nil, E.Cause(err, "build IPv4 route address") + } + routeRanges = append(routeRanges, resultSet.Prefixes()...) + } + } + if len(o.Inet6Address) > 0 { + var inet6Ranges []netip.Prefix + if len(o.Inet6RouteAddress) > 0 { + inet6Ranges = o.Inet6RouteAddress + } else if autoRouteUseSubRanges { + inet6Ranges = []netip.Prefix{ + netip.PrefixFrom(netip.IPv6Unspecified(), 1), + netip.PrefixFrom(netip.AddrFrom16([16]byte{0: 128}), 1), + } + } else { + inet6Ranges = []netip.Prefix{netip.PrefixFrom(netip.IPv6Unspecified(), 0)} + } + if len(o.Inet6RouteExcludeAddress) == 0 { + routeRanges = append(routeRanges, inet6Ranges...) + } else { + var builder netipx.IPSetBuilder + for _, inet6Range := range inet6Ranges { + builder.AddPrefix(inet6Range) + } + for _, prefix := range o.Inet6RouteExcludeAddress { + builder.RemovePrefix(prefix) + } + resultSet, err := builder.IPSet() + if err != nil { + return nil, E.Cause(err, "build IPv6 route address") + } + routeRanges = append(routeRanges, resultSet.Prefixes()...) + } + } + return routeRanges, nil +} diff --git a/tun_windows.go b/tun_windows.go index 1d5e941..55a5bf8 100644 --- a/tun_windows.go +++ b/tun_windows.go @@ -92,37 +92,18 @@ func (t *NativeTun) configure() error { _ = luid.DisableDNSRegistration() } if t.options.AutoRoute { - if len(t.options.Inet4Address) > 0 { - if len(t.options.Inet4RouteAddress) > 0 { - for _, addr := range t.options.Inet4RouteAddress { - err := luid.AddRoute(addr, netip.IPv4Unspecified(), 0) - if err != nil { - return E.Cause(err, "add ipv4 route: ", addr) - } - } - } else { - err := luid.AddRoute(netip.PrefixFrom(netip.IPv4Unspecified(), 0), netip.IPv4Unspecified(), 0) - if err != nil { - return E.Cause(err, "set ipv4 route") - } - } + routeRanges, err := t.options.BuildAutoRouteRanges() + if err != nil { + return err } - if len(t.options.Inet6Address) > 0 { - if len(t.options.Inet6RouteAddress) > 0 { - for _, addr := range t.options.Inet6RouteAddress { - err := luid.AddRoute(addr, netip.IPv6Unspecified(), 0) - if err != nil { - return E.Cause(err, "add ipv6 route: ", addr) - } - } + for _, routeRange := range routeRanges { + if routeRange.Addr().Is4() { + err = luid.AddRoute(routeRange, netip.IPv4Unspecified(), 0) } else { - err := luid.AddRoute(netip.PrefixFrom(netip.IPv6Unspecified(), 0), netip.IPv6Unspecified(), 0) - if err != nil { - return E.Cause(err, "set ipv6 route") - } + err = luid.AddRoute(routeRange, netip.IPv6Unspecified(), 0) } } - err := windnsapi.FlushResolverCache() + err = windnsapi.FlushResolverCache() if err != nil { return err }