From d528cfcb5cb5fd32bee8863c2d763773ec6045df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 1 Jun 2024 12:48:45 +0800 Subject: [PATCH] Migrate auto-redirect to library --- go.mod | 8 +- go.sum | 8 +- inbound/tun.go | 21 +- inbound/tun_auto_redirect.go | 200 ------------------ inbound/tun_auto_redirect_iptables.go | 235 --------------------- inbound/tun_auto_redirect_nftables.go | 231 -------------------- inbound/tun_auto_redirect_nftables_expr.go | 153 -------------- inbound/tun_auto_redirect_stub.go | 23 -- 8 files changed, 25 insertions(+), 854 deletions(-) delete mode 100644 inbound/tun_auto_redirect.go delete mode 100644 inbound/tun_auto_redirect_iptables.go delete mode 100644 inbound/tun_auto_redirect_nftables.go delete mode 100644 inbound/tun_auto_redirect_nftables_expr.go delete mode 100644 inbound/tun_auto_redirect_stub.go diff --git a/go.mod b/go.mod index daaee0d6..21485217 100644 --- a/go.mod +++ b/go.mod @@ -24,17 +24,16 @@ require ( github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 github.com/sagernet/gomobile v0.1.3 github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f - github.com/sagernet/nftables v0.3.0-beta.2 github.com/sagernet/quic-go v0.43.1-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.5.0-alpha.7 + github.com/sagernet/sing v0.5.0-alpha.8 github.com/sagernet/sing-dns v0.2.0-beta.18 github.com/sagernet/sing-mux v0.2.0 github.com/sagernet/sing-quic v0.2.0-beta.5 github.com/sagernet/sing-shadowsocks v0.2.6 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/sing-tun v0.4.0-beta.4 + github.com/sagernet/sing-tun v0.4.0-beta.6 github.com/sagernet/sing-vmess v0.1.8 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 @@ -46,7 +45,6 @@ require ( go.uber.org/zap v1.27.0 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/sys v0.20.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 @@ -83,11 +81,13 @@ require ( github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba // indirect + github.com/sagernet/nftables v0.3.0-beta.2 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/zeebo/blake3 v0.2.3 // indirect go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/text v0.15.0 // indirect diff --git a/go.sum b/go.sum index ba83799d..5a1a1493 100644 --- a/go.sum +++ b/go.sum @@ -108,8 +108,8 @@ github.com/sagernet/quic-go v0.43.1-beta.1/go.mod h1:BkrQYeop7Jx3hN3TW8/76CXcdhY github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= -github.com/sagernet/sing v0.5.0-alpha.7 h1:yxjiH0vQAotu87JNJ9B0BGO0OJqsFjug84xcYdwyDm4= -github.com/sagernet/sing v0.5.0-alpha.7/go.mod h1:Xh4KO9nGdvm4K/LVg9Xn9jSxJdqe9KcXbAzNC1S2qfw= +github.com/sagernet/sing v0.5.0-alpha.8 h1:2KtzBvKP6hwknsi/G6H4vRgR4it31HQ6quLb0Woze7c= +github.com/sagernet/sing v0.5.0-alpha.8/go.mod h1:Xh4KO9nGdvm4K/LVg9Xn9jSxJdqe9KcXbAzNC1S2qfw= github.com/sagernet/sing-dns v0.2.0-beta.18 h1:6vzXZThRdA7YUzBOpSbUT48XRumtl/KIpIHFSOP0za8= github.com/sagernet/sing-dns v0.2.0-beta.18/go.mod h1:k/dmFcQpg6+m08gC1yQBy+13+QkuLqpKr4bIreq4U24= github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= @@ -122,8 +122,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.4.0-beta.4 h1:94OB9fQupn0qBPvlDzKRAq1lSIPHAvVBaw69/jEZfgQ= -github.com/sagernet/sing-tun v0.4.0-beta.4/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ= +github.com/sagernet/sing-tun v0.4.0-beta.6 h1:Ybp1Yr2iNuh9iI/+f8L6R5MkhMJEMNtRHNBdmvVq9P0= +github.com/sagernet/sing-tun v0.4.0-beta.6/go.mod h1:X9taVYeJXEtK9v6BpS1V/gUXTwY4nQKH4iYDLX1JUXY= github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc= github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= diff --git a/inbound/tun.go b/inbound/tun.go index 2275fa06..a0e7f5fa 100644 --- a/inbound/tun.go +++ b/inbound/tun.go @@ -3,6 +3,7 @@ package inbound import ( "context" "net" + "os" "strconv" "strings" "time" @@ -37,7 +38,7 @@ type Tun struct { tunStack tun.Stack platformInterface platform.Interface platformOptions option.TunPlatformOptions - autoRedirect *tunAutoRedirect + autoRedirect tun.AutoRedirect } func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) { @@ -105,7 +106,15 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger if !options.AutoRoute { return nil, E.New("`auto_route` is required by `auto_redirect`") } - inbound.autoRedirect, err = newAutoRedirect(inbound) + disableNFTables, dErr := strconv.ParseBool(os.Getenv("DISABLE_NFTABLES")) + inbound.autoRedirect, err = tun.NewAutoRedirect(tun.AutoRedirectOptions{ + TunOptions: &inbound.tunOptions, + Context: ctx, + Handler: inbound, + Logger: logger, + TableName: "sing-box", + DisableNFTables: dErr == nil && disableNFTables, + }) if err != nil { return nil, E.Cause(err, "initialize auto redirect") } @@ -222,7 +231,7 @@ func (t *Tun) Close() error { return common.Close( t.tunStack, t.tunIf, - common.PtrOrNil(t.autoRedirect), + t.autoRedirect, ) } @@ -234,7 +243,11 @@ func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata metadata.Source = upstreamMetadata.Source metadata.Destination = upstreamMetadata.Destination metadata.InboundOptions = t.inboundOptions - t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + if upstreamMetadata.Protocol != "" { + t.logger.InfoContext(ctx, "inbound ", upstreamMetadata.Protocol, " connection from ", metadata.Source) + } else { + t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + } t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) err := t.router.RouteConnection(ctx, conn, metadata) if err != nil { diff --git a/inbound/tun_auto_redirect.go b/inbound/tun_auto_redirect.go deleted file mode 100644 index c79232f7..00000000 --- a/inbound/tun_auto_redirect.go +++ /dev/null @@ -1,200 +0,0 @@ -//go:build linux - -package inbound - -import ( - "context" - "net" - "net/netip" - "os" - "os/exec" - "strconv" - - "github.com/sagernet/nftables" - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/redir" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-tun" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - - "golang.org/x/sys/unix" -) - -type tunAutoRedirect struct { - myInboundAdapter - tunOptions *tun.Options - enableIPv4 bool - enableIPv6 bool - iptablesPath string - ip6tablesPath string - useNfTables bool - androidSu bool - suPath string -} - -func newAutoRedirect(t *Tun) (*tunAutoRedirect, error) { - s := &tunAutoRedirect{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeRedirect, - network: []string{N.NetworkTCP}, - ctx: t.ctx, - router: t.router, - logger: t.logger, - tag: t.tag, - listenOptions: option.ListenOptions{ - InboundOptions: t.inboundOptions, - }, - }, - tunOptions: &t.tunOptions, - } - s.connHandler = s - - if C.IsAndroid { - s.enableIPv4 = true - s.iptablesPath = "/system/bin/iptables" - userId := os.Getuid() - if userId != 0 { - var ( - suPath string - err error - ) - for _, suPath = range []string{ - "su", - "/system/bin/su", - } { - suPath, err = exec.LookPath(suPath) - if err == nil { - break - } - } - if err != nil { - return nil, E.Extend(E.Cause(err, "root permission is required for auto redirect"), os.Getenv("PATH")) - } - s.androidSu = true - s.suPath = suPath - } - } else { - err := s.initializeNfTables() - if err != nil && err != os.ErrInvalid { - t.logger.Debug("device has no nftables support: ", err) - } - if len(t.tunOptions.Inet4Address) > 0 { - s.enableIPv4 = true - if !s.useNfTables { - s.iptablesPath, err = exec.LookPath("iptables") - if err != nil { - return nil, E.Cause(err, "iptables is required") - } - } - } - if len(t.tunOptions.Inet6Address) > 0 { - s.enableIPv6 = true - if !s.useNfTables { - s.ip6tablesPath, err = exec.LookPath("ip6tables") - if err != nil { - if !s.enableIPv4 { - return nil, E.Cause(err, "ip6tables is required") - } else { - s.enableIPv6 = false - t.logger.Error("device has no ip6tables nat support: ", err) - } - } - } - } - } - var listenAddr netip.Addr - if C.IsAndroid { - listenAddr = netip.AddrFrom4([4]byte{127, 0, 0, 1}) - } else if s.enableIPv6 { - listenAddr = netip.IPv6Unspecified() - } else { - listenAddr = netip.IPv4Unspecified() - } - s.listenOptions.Listen = option.NewListenAddress(listenAddr) - return s, nil -} - -func (t *tunAutoRedirect) initializeNfTables() error { - disabled, err := strconv.ParseBool(os.Getenv("AUTO_REDIRECT_DISABLE_NFTABLES")) - if err == nil && disabled { - return os.ErrInvalid - } - nft, err := nftables.New() - if err != nil { - return err - } - defer nft.CloseLasting() - _, err = nft.ListTablesOfFamily(unix.AF_INET) - if err != nil { - return err - } - t.useNfTables = true - return nil -} - -func (t *tunAutoRedirect) Start() error { - err := t.myInboundAdapter.Start() - if err != nil { - return E.Cause(err, "start redirect server") - } - t.cleanupTables() - err = t.setupTables() - if err != nil { - return err - } - return nil -} - -func (t *tunAutoRedirect) Close() error { - t.cleanupTables() - return t.myInboundAdapter.Close() -} - -func (t *tunAutoRedirect) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - destination, err := redir.GetOriginalDestination(conn) - if err != nil { - return E.Cause(err, "get redirect destination") - } - metadata.Destination = M.SocksaddrFromNetIP(destination) - return t.newConnection(ctx, conn, metadata) -} - -func (t *tunAutoRedirect) setupTables() error { - var setupTables func(int) error - if t.useNfTables { - setupTables = t.setupNfTables - } else { - setupTables = t.setupIPTables - } - if t.enableIPv4 { - err := setupTables(unix.AF_INET) - if err != nil { - return err - } - } - if t.enableIPv6 { - err := setupTables(unix.AF_INET6) - if err != nil { - return err - } - } - return nil -} - -func (t *tunAutoRedirect) cleanupTables() { - var cleanupTables func(int) - if t.useNfTables { - cleanupTables = t.cleanupNfTables - } else { - cleanupTables = t.cleanupIPTables - } - if t.enableIPv4 { - cleanupTables(unix.AF_INET) - } - if t.enableIPv6 { - cleanupTables(unix.AF_INET6) - } -} diff --git a/inbound/tun_auto_redirect_iptables.go b/inbound/tun_auto_redirect_iptables.go deleted file mode 100644 index b1aa7dff..00000000 --- a/inbound/tun_auto_redirect_iptables.go +++ /dev/null @@ -1,235 +0,0 @@ -//go:build linux - -package inbound - -import ( - "net/netip" - "os/exec" - "strings" - - E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" - M "github.com/sagernet/sing/common/metadata" - - "golang.org/x/sys/unix" -) - -const ( - iptablesTableNameOutput = "sing-box-output" - iptablesTableNameForward = "sing-box-forward" - iptablesTableNamePreRouteing = "sing-box-prerouting" -) - -func (t *tunAutoRedirect) iptablesPathForFamily(family int) string { - if family == unix.AF_INET { - return t.iptablesPath - } else { - return t.ip6tablesPath - } -} - -func (t *tunAutoRedirect) setupIPTables(family int) error { - iptablesPath := t.iptablesPathForFamily(family) - // OUTPUT - err := t.runShell(iptablesPath, "-t nat -N", iptablesTableNameOutput) - if err != nil { - return err - } - err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNameOutput, - "-p tcp -o", t.tunOptions.Name, - "-j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port()) - if err != nil { - return err - } - err = t.runShell(iptablesPath, "-t nat -I OUTPUT -j", iptablesTableNameOutput) - if err != nil { - return err - } - if !t.androidSu { - // FORWARD - err = t.runShell(iptablesPath, "-N", iptablesTableNameForward) - if err != nil { - return err - } - err = t.runShell(iptablesPath, "-A", iptablesTableNameForward, - "-i", t.tunOptions.Name, "-j", "ACCEPT") - if err != nil { - return err - } - err = t.runShell(iptablesPath, "-A", iptablesTableNameForward, - "-o", t.tunOptions.Name, "-j", "ACCEPT") - if err != nil { - return err - } - err = t.runShell(iptablesPath, "-I FORWARD -j", iptablesTableNameForward) - if err != nil { - return err - } - // PREROUTING - err = t.setupIPTablesPreRouting(family) - if err != nil { - return err - } - } - return nil -} - -func (t *tunAutoRedirect) setupIPTablesPreRouting(family int) error { - iptablesPath := t.iptablesPathForFamily(family) - err := t.runShell(iptablesPath, "-t nat -N", iptablesTableNamePreRouteing) - if err != nil { - return err - } - var ( - routeAddress []netip.Prefix - routeExcludeAddress []netip.Prefix - ) - if family == unix.AF_INET { - routeAddress = t.tunOptions.Inet4RouteAddress - routeExcludeAddress = t.tunOptions.Inet4RouteExcludeAddress - } else { - routeAddress = t.tunOptions.Inet6RouteAddress - routeExcludeAddress = t.tunOptions.Inet6RouteExcludeAddress - } - if len(routeAddress) > 0 && (len(t.tunOptions.IncludeInterface) > 0 || len(t.tunOptions.IncludeUID) > 0) { - return E.New("`*_route_address` is conflict with `include_interface` or `include_uid`") - } - err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing, - "-i", t.tunOptions.Name, "-j RETURN") - if err != nil { - return err - } - for _, address := range routeExcludeAddress { - err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing, - "-d", address.String(), "-j RETURN") - if err != nil { - return err - } - } - for _, name := range t.tunOptions.ExcludeInterface { - err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing, - "-i", name, "-j RETURN") - if err != nil { - return err - } - } - for _, uid := range t.tunOptions.ExcludeUID { - err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing, - "-m owner --uid-owner", uid, "-j RETURN") - if err != nil { - return err - } - } - var dnsServerAddress netip.Addr - if family == unix.AF_INET { - dnsServerAddress = t.tunOptions.Inet4Address[0].Addr().Next() - } else { - dnsServerAddress = t.tunOptions.Inet6Address[0].Addr().Next() - } - if len(routeAddress) > 0 { - for _, address := range routeAddress { - err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing, - "-d", address.String(), "-p udp --dport 53 -j DNAT --to", dnsServerAddress) - if err != nil { - return err - } - } - } else if len(t.tunOptions.IncludeInterface) > 0 || len(t.tunOptions.IncludeUID) > 0 { - for _, name := range t.tunOptions.IncludeInterface { - err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing, - "-i", name, "-p udp --dport 53 -j DNAT --to", dnsServerAddress) - if err != nil { - return err - } - } - for _, uidRange := range t.tunOptions.IncludeUID { - for uid := uidRange.Start; uid <= uidRange.End; uid++ { - err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing, - "-m owner --uid-owner", uid, "-p udp --dport 53 -j DNAT --to", dnsServerAddress) - if err != nil { - return err - } - } - } - } else { - err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing, - "-p udp --dport 53 -j DNAT --to", dnsServerAddress) - if err != nil { - return err - } - } - - err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing, "-m addrtype --dst-type LOCAL -j RETURN") - if err != nil { - return err - } - - if len(routeAddress) > 0 { - for _, address := range routeAddress { - err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing, - "-d", address.String(), "-p tcp -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port()) - if err != nil { - return err - } - } - } else if len(t.tunOptions.IncludeInterface) > 0 || len(t.tunOptions.IncludeUID) > 0 { - for _, name := range t.tunOptions.IncludeInterface { - err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing, - "-i", name, "-p tcp -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port()) - if err != nil { - return err - } - } - for _, uidRange := range t.tunOptions.IncludeUID { - for uid := uidRange.Start; uid <= uidRange.End; uid++ { - err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing, - "-m owner --uid-owner", uid, "-p tcp -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port()) - if err != nil { - return err - } - } - } - } else { - err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing, - "-p tcp -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port()) - if err != nil { - return err - } - } - err = t.runShell(iptablesPath, "-t nat -I PREROUTING -j", iptablesTableNamePreRouteing) - if err != nil { - return err - } - return nil -} - -func (t *tunAutoRedirect) cleanupIPTables(family int) { - iptablesPath := t.iptablesPathForFamily(family) - _ = t.runShell(iptablesPath, "-t nat -D OUTPUT -j", iptablesTableNameOutput) - _ = t.runShell(iptablesPath, "-t nat -F", iptablesTableNameOutput) - _ = t.runShell(iptablesPath, "-t nat -X", iptablesTableNameOutput) - if !t.androidSu { - _ = t.runShell(iptablesPath, "-D FORWARD -j", iptablesTableNameForward) - _ = t.runShell(iptablesPath, "-F", iptablesTableNameForward) - _ = t.runShell(iptablesPath, "-X", iptablesTableNameForward) - _ = t.runShell(iptablesPath, "-t nat -D PREROUTING -j", iptablesTableNamePreRouteing) - _ = t.runShell(iptablesPath, "-t nat -F", iptablesTableNamePreRouteing) - _ = t.runShell(iptablesPath, "-t nat -X", iptablesTableNamePreRouteing) - } -} - -func (t *tunAutoRedirect) runShell(commands ...any) error { - commandStr := strings.Join(F.MapToString(commands), " ") - var command *exec.Cmd - if t.androidSu { - command = exec.Command(t.suPath, "-c", commandStr) - } else { - commandArray := strings.Split(commandStr, " ") - command = exec.Command(commandArray[0], commandArray[1:]...) - } - combinedOutput, err := command.CombinedOutput() - if err != nil { - return E.Extend(err, F.ToString(commandStr, ": ", string(combinedOutput))) - } - return nil -} diff --git a/inbound/tun_auto_redirect_nftables.go b/inbound/tun_auto_redirect_nftables.go deleted file mode 100644 index ed84adf4..00000000 --- a/inbound/tun_auto_redirect_nftables.go +++ /dev/null @@ -1,231 +0,0 @@ -//go:build linux - -package inbound - -import ( - "net/netip" - - "github.com/sagernet/nftables" - "github.com/sagernet/nftables/binaryutil" - "github.com/sagernet/nftables/expr" - F "github.com/sagernet/sing/common/format" - M "github.com/sagernet/sing/common/metadata" - - "golang.org/x/sys/unix" -) - -const ( - nftablesTableName = "sing-box" - nftablesChainOutput = "output" - nftablesChainForward = "forward" - nftablesChainPreRouting = "prerouting" -) - -func nftablesFamily(family int) nftables.TableFamily { - switch family { - case unix.AF_INET: - return nftables.TableFamilyIPv4 - case unix.AF_INET6: - return nftables.TableFamilyIPv6 - default: - panic(F.ToString("unknown family ", family)) - } -} - -func (t *tunAutoRedirect) setupNfTables(family int) error { - nft, err := nftables.New() - if err != nil { - return err - } - defer nft.CloseLasting() - table := nft.AddTable(&nftables.Table{ - Name: nftablesTableName, - Family: nftablesFamily(family), - }) - chainOutput := nft.AddChain(&nftables.Chain{ - Name: nftablesChainOutput, - Table: table, - Hooknum: nftables.ChainHookOutput, - Priority: nftables.ChainPriorityMangle, - Type: nftables.ChainTypeNAT, - }) - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainOutput, - Exprs: nftablesRuleIfName(expr.MetaKeyOIFNAME, t.tunOptions.Name, nftablesRuleRedirectToPorts(M.AddrPortFromNet(t.tcpListener.Addr()).Port())...), - }) - chainForward := nft.AddChain(&nftables.Chain{ - Name: nftablesChainForward, - Table: table, - Hooknum: nftables.ChainHookForward, - Priority: nftables.ChainPriorityMangle, - }) - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainForward, - Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, t.tunOptions.Name, &expr.Verdict{ - Kind: expr.VerdictAccept, - }), - }) - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainForward, - Exprs: nftablesRuleIfName(expr.MetaKeyOIFNAME, t.tunOptions.Name, &expr.Verdict{ - Kind: expr.VerdictAccept, - }), - }) - t.setupNfTablesPreRouting(nft, table) - return nft.Flush() -} - -func (t *tunAutoRedirect) setupNfTablesPreRouting(nft *nftables.Conn, table *nftables.Table) { - chainPreRouting := nft.AddChain(&nftables.Chain{ - Name: nftablesChainPreRouting, - Table: table, - Hooknum: nftables.ChainHookPrerouting, - Priority: nftables.ChainPriorityMangle, - Type: nftables.ChainTypeNAT, - }) - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainPreRouting, - Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, t.tunOptions.Name, &expr.Verdict{ - Kind: expr.VerdictReturn, - }), - }) - var ( - routeAddress []netip.Prefix - routeExcludeAddress []netip.Prefix - ) - if table.Family == nftables.TableFamilyIPv4 { - routeAddress = t.tunOptions.Inet4RouteAddress - routeExcludeAddress = t.tunOptions.Inet4RouteExcludeAddress - } else { - routeAddress = t.tunOptions.Inet6RouteAddress - routeExcludeAddress = t.tunOptions.Inet6RouteExcludeAddress - } - for _, address := range routeExcludeAddress { - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainPreRouting, - Exprs: nftablesRuleDestinationAddress(address, &expr.Verdict{ - Kind: expr.VerdictReturn, - }), - }) - } - for _, name := range t.tunOptions.ExcludeInterface { - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainPreRouting, - Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, name, &expr.Verdict{ - Kind: expr.VerdictReturn, - }), - }) - } - for _, uidRange := range t.tunOptions.ExcludeUID { - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainPreRouting, - Exprs: nftablesRuleMetaUInt32Range(expr.MetaKeySKUID, uidRange, &expr.Verdict{ - Kind: expr.VerdictReturn, - }), - }) - } - - var routeExprs []expr.Any - if len(routeAddress) > 0 { - for _, address := range routeAddress { - routeExprs = append(routeExprs, nftablesRuleDestinationAddress(address)...) - } - } - redirectPort := M.AddrPortFromNet(t.tcpListener.Addr()).Port() - var dnsServerAddress netip.Addr - if table.Family == nftables.TableFamilyIPv4 { - dnsServerAddress = t.tunOptions.Inet4Address[0].Addr().Next() - } else { - dnsServerAddress = t.tunOptions.Inet6Address[0].Addr().Next() - } - - if len(t.tunOptions.IncludeInterface) > 0 || len(t.tunOptions.IncludeUID) > 0 { - for _, name := range t.tunOptions.IncludeInterface { - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainPreRouting, - Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, name, append(routeExprs, nftablesRuleHijackDNS(table.Family, dnsServerAddress)...)...), - }) - } - for _, uidRange := range t.tunOptions.IncludeUID { - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainPreRouting, - Exprs: nftablesRuleMetaUInt32Range(expr.MetaKeySKUID, uidRange, append(routeExprs, nftablesRuleHijackDNS(table.Family, dnsServerAddress)...)...), - }) - } - } else { - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainPreRouting, - Exprs: append(routeExprs, nftablesRuleHijackDNS(table.Family, dnsServerAddress)...), - }) - } - - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainPreRouting, - Exprs: []expr.Any{ - &expr.Fib{ - Register: 1, - FlagDADDR: true, - ResultADDRTYPE: true, - }, - &expr.Cmp{ - Op: expr.CmpOpEq, - Register: 1, - Data: binaryutil.NativeEndian.PutUint32(unix.RTN_LOCAL), - }, - &expr.Verdict{ - Kind: expr.VerdictReturn, - }, - }, - }) - - if len(t.tunOptions.IncludeInterface) > 0 || len(t.tunOptions.IncludeUID) > 0 { - for _, name := range t.tunOptions.IncludeInterface { - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainPreRouting, - Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, name, append(routeExprs, nftablesRuleRedirectToPorts(redirectPort)...)...), - }) - } - for _, uidRange := range t.tunOptions.IncludeUID { - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainPreRouting, - Exprs: nftablesRuleMetaUInt32Range(expr.MetaKeySKUID, uidRange, append(routeExprs, nftablesRuleRedirectToPorts(redirectPort)...)...), - }) - } - } else { - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainPreRouting, - Exprs: append(routeExprs, nftablesRuleRedirectToPorts(redirectPort)...), - }) - } -} - -func (t *tunAutoRedirect) cleanupNfTables(family int) { - conn, err := nftables.New() - if err != nil { - return - } - defer conn.CloseLasting() - conn.FlushTable(&nftables.Table{ - Name: nftablesTableName, - Family: nftablesFamily(family), - }) - conn.DelTable(&nftables.Table{ - Name: nftablesTableName, - Family: nftablesFamily(family), - }) - _ = conn.Flush() -} diff --git a/inbound/tun_auto_redirect_nftables_expr.go b/inbound/tun_auto_redirect_nftables_expr.go deleted file mode 100644 index 27c0309f..00000000 --- a/inbound/tun_auto_redirect_nftables_expr.go +++ /dev/null @@ -1,153 +0,0 @@ -//go:build linux - -package inbound - -import ( - "net" - "net/netip" - - "github.com/sagernet/nftables" - "github.com/sagernet/nftables/binaryutil" - "github.com/sagernet/nftables/expr" - "github.com/sagernet/sing/common/ranges" - - "golang.org/x/sys/unix" -) - -func nftablesIfname(n string) []byte { - b := make([]byte, 16) - copy(b, n+"\x00") - return b -} - -func nftablesRuleIfName(key expr.MetaKey, value string, exprs ...expr.Any) []expr.Any { - newExprs := []expr.Any{ - &expr.Meta{Key: key, Register: 1}, - &expr.Cmp{ - Op: expr.CmpOpEq, - Register: 1, - Data: nftablesIfname(value), - }, - } - newExprs = append(newExprs, exprs...) - return newExprs -} - -func nftablesRuleMetaUInt32Range(key expr.MetaKey, uidRange ranges.Range[uint32], exprs ...expr.Any) []expr.Any { - newExprs := []expr.Any{ - &expr.Meta{Key: key, Register: 1}, - &expr.Range{ - Op: expr.CmpOpEq, - Register: 1, - FromData: binaryutil.BigEndian.PutUint32(uidRange.Start), - ToData: binaryutil.BigEndian.PutUint32(uidRange.End), - }, - } - newExprs = append(newExprs, exprs...) - return newExprs -} - -func nftablesRuleDestinationAddress(address netip.Prefix, exprs ...expr.Any) []expr.Any { - var newExprs []expr.Any - if address.Addr().Is4() { - newExprs = append(newExprs, &expr.Payload{ - OperationType: expr.PayloadLoad, - DestRegister: 1, - SourceRegister: 0, - Base: expr.PayloadBaseNetworkHeader, - Offset: 16, - Len: 4, - }, &expr.Bitwise{ - SourceRegister: 1, - DestRegister: 1, - Len: 4, - Xor: make([]byte, 4), - Mask: net.CIDRMask(address.Bits(), 32), - }) - } else { - newExprs = append(newExprs, &expr.Payload{ - OperationType: expr.PayloadLoad, - DestRegister: 1, - SourceRegister: 0, - Base: expr.PayloadBaseNetworkHeader, - Offset: 24, - Len: 16, - }, &expr.Bitwise{ - SourceRegister: 1, - DestRegister: 1, - Len: 16, - Xor: make([]byte, 16), - Mask: net.CIDRMask(address.Bits(), 128), - }) - } - newExprs = append(newExprs, &expr.Cmp{ - Op: expr.CmpOpEq, - Register: 1, - Data: address.Masked().Addr().AsSlice(), - }) - newExprs = append(newExprs, exprs...) - return newExprs -} - -func nftablesRuleHijackDNS(family nftables.TableFamily, dnsServerAddress netip.Addr) []expr.Any { - return []expr.Any{ - &expr.Meta{ - Key: expr.MetaKeyL4PROTO, - Register: 1, - }, - &expr.Cmp{ - Op: expr.CmpOpEq, - Register: 1, - Data: []byte{unix.IPPROTO_UDP}, - }, - &expr.Payload{ - OperationType: expr.PayloadLoad, - DestRegister: 1, - SourceRegister: 0, - Base: expr.PayloadBaseTransportHeader, - Offset: 2, - Len: 2, - }, &expr.Cmp{ - Op: expr.CmpOpEq, - Register: 1, - Data: binaryutil.BigEndian.PutUint16(53), - }, &expr.Immediate{ - Register: 1, - Data: dnsServerAddress.AsSlice(), - }, &expr.NAT{ - Type: expr.NATTypeDestNAT, - Family: uint32(family), - RegAddrMin: 1, - }, - } -} - -const ( - NF_NAT_RANGE_MAP_IPS = 1 << iota - NF_NAT_RANGE_PROTO_SPECIFIED - NF_NAT_RANGE_PROTO_RANDOM - NF_NAT_RANGE_PERSISTENT - NF_NAT_RANGE_PROTO_RANDOM_FULLY - NF_NAT_RANGE_PROTO_OFFSET -) - -func nftablesRuleRedirectToPorts(redirectPort uint16) []expr.Any { - return []expr.Any{ - &expr.Meta{ - Key: expr.MetaKeyL4PROTO, - Register: 1, - }, - &expr.Cmp{ - Op: expr.CmpOpEq, - Register: 1, - Data: []byte{unix.IPPROTO_TCP}, - }, - &expr.Immediate{ - Register: 1, - Data: binaryutil.BigEndian.PutUint16(redirectPort), - }, &expr.Redir{ - RegisterProtoMin: 1, - Flags: NF_NAT_RANGE_PROTO_SPECIFIED, - }, - } -} diff --git a/inbound/tun_auto_redirect_stub.go b/inbound/tun_auto_redirect_stub.go deleted file mode 100644 index 4bfdafa3..00000000 --- a/inbound/tun_auto_redirect_stub.go +++ /dev/null @@ -1,23 +0,0 @@ -//go:build !linux - -package inbound - -import ( - "os" - - E "github.com/sagernet/sing/common/exceptions" -) - -type tunAutoRedirect struct{} - -func newAutoRedirect(t *Tun) (*tunAutoRedirect, error) { - return nil, E.New("only supported on linux") -} - -func (t *tunAutoRedirect) Start() error { - return os.ErrInvalid -} - -func (t *tunAutoRedirect) Close() error { - return os.ErrInvalid -}