Migrate auto-redirect to library

This commit is contained in:
世界 2024-06-01 12:48:45 +08:00
parent 888487021d
commit d528cfcb5c
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
8 changed files with 25 additions and 854 deletions

8
go.mod
View File

@ -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

8
go.sum
View File

@ -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=

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -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,
},
}
}

View File

@ -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
}