From ca84a23ff1cc73e91b6fc805aaee5340003f00d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 12 Jun 2024 02:37:51 +0800 Subject: [PATCH] auto-redirect: Add route address set support for nftables --- go.mod | 2 +- go.sum | 4 +- redirect.go | 19 +- redirect_linux.go | 14 ++ redirect_nftables.go | 440 +++++++++++++++++++++++++++----------- redirect_nftables_expr.go | 237 ++++++++++---------- tun.go | 1 + tun_linux.go | 23 +- 8 files changed, 495 insertions(+), 245 deletions(-) diff --git a/go.mod b/go.mod index 5cfda58..32d50f6 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/fsnotify/fsnotify v1.7.0 github.com/go-ole/go-ole v1.3.0 github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f - github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba + github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a github.com/sagernet/nftables v0.3.0-beta.2 github.com/sagernet/sing v0.5.0-alpha.9 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba diff --git a/go.sum b/go.sum index 55a9c72..a3deaaa 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8Ku github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I= github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0= -github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba h1:EY5AS7CCtfmARNv2zXUOrsEMPFDGYxaw65JzA2p51Vk= -github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/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.2 h1:yKqMl4Dpb6nKxAmlE6fXjJRlLO2c1f2wyNFBg4hBr8w= github.com/sagernet/nftables v0.3.0-beta.2/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/sing v0.5.0-alpha.9 h1:Mmg+LCbaKXBeQD/ttzi0/MQa3NcUyfadIgkGzhQW7o0= diff --git a/redirect.go b/redirect.go index d61db97..ac6cabe 100644 --- a/redirect.go +++ b/redirect.go @@ -4,19 +4,24 @@ import ( "context" "github.com/sagernet/sing/common/logger" + + "go4.org/netipx" ) type AutoRedirect interface { Start() error Close() error + UpdateRouteAddressSet() error } type AutoRedirectOptions struct { - TunOptions *Options - Context context.Context - Handler Handler - Logger logger.Logger - TableName string - DisableNFTables bool - CustomRedirectPort func() int + TunOptions *Options + Context context.Context + Handler Handler + Logger logger.Logger + TableName string + DisableNFTables bool + CustomRedirectPort func() int + RouteAddressSet *[]*netipx.IPSet + RouteExcludeAddressSet *[]*netipx.IPSet } diff --git a/redirect_linux.go b/redirect_linux.go index bcad482..d08dcf9 100644 --- a/redirect_linux.go +++ b/redirect_linux.go @@ -12,6 +12,8 @@ import ( E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" + + "go4.org/netipx" ) type autoRedirect struct { @@ -30,6 +32,8 @@ type autoRedirect struct { useNFTables bool androidSu bool suPath string + routeAddressSet *[]*netipx.IPSet + routeExcludeAddressSet *[]*netipx.IPSet } func NewAutoRedirect(options AutoRedirectOptions) (AutoRedirect, error) { @@ -41,6 +45,8 @@ func NewAutoRedirect(options AutoRedirectOptions) (AutoRedirect, error) { tableName: options.TableName, useNFTables: runtime.GOOS != "android" && !options.DisableNFTables, customRedirectPortFunc: options.CustomRedirectPort, + routeAddressSet: options.RouteAddressSet, + routeExcludeAddressSet: options.RouteExcludeAddressSet, } var err error if runtime.GOOS == "android" { @@ -134,6 +140,14 @@ func (r *autoRedirect) Close() error { ) } +func (r *autoRedirect) UpdateRouteAddressSet() error { + if r.useNFTables { + return r.nftablesUpdateRouteAddressSet() + } else { + return nil + } +} + func (r *autoRedirect) initializeNFTables() error { nft, err := nftables.New() if err != nil { diff --git a/redirect_nftables.go b/redirect_nftables.go index 3b3e04c..0c5a153 100644 --- a/redirect_nftables.go +++ b/redirect_nftables.go @@ -3,16 +3,21 @@ package tun import ( + "math" "net/netip" "github.com/sagernet/nftables" "github.com/sagernet/nftables/binaryutil" "github.com/sagernet/nftables/expr" "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" "golang.org/x/sys/unix" ) +const ReturnFWMark = math.MaxUint32 + +// TODO: reimplement `strict_route` via fwmark func (r *autoRedirect) setupNFTables() error { nft, err := nftables.New() if err != nil { @@ -25,28 +30,43 @@ func (r *autoRedirect) setupNFTables() error { Family: nftables.TableFamilyINet, }) - chainForward := nft.AddChain(&nftables.Chain{ - Name: "forward", - Table: table, - Hooknum: nftables.ChainHookForward, - Priority: nftables.ChainPriorityMangle, - }) - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainForward, - Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, r.tunOptions.Name, &expr.Verdict{ - Kind: expr.VerdictAccept, - }), - }) - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainForward, - Exprs: nftablesRuleIfName(expr.MetaKeyOIFNAME, r.tunOptions.Name, &expr.Verdict{ - Kind: expr.VerdictAccept, - }), - }) - - redirectPort := r.redirectPort() + routeAddressSet := *r.routeAddressSet + routeExcludeAddressSet := *r.routeExcludeAddressSet + err = nftablesCreateIPSet(nft, table, 1, "inet4_route_address_set", nftables.TableFamilyIPv4, routeAddressSet, r.tunOptions.Inet4RouteAddress, false) + if err != nil { + return err + } + err = nftablesCreateIPSet(nft, table, 2, "inet6_route_address_set", nftables.TableFamilyIPv6, routeAddressSet, r.tunOptions.Inet6RouteAddress, false) + if err != nil { + return err + } + err = nftablesCreateIPSet(nft, table, 3, "inet4_route_exclude_address_set", nftables.TableFamilyIPv4, routeExcludeAddressSet, r.tunOptions.Inet4RouteExcludeAddress, false) + if err != nil { + return err + } + err = nftablesCreateIPSet(nft, table, 4, "inet6_route_exclude_address_set", nftables.TableFamilyIPv6, routeExcludeAddressSet, r.tunOptions.Inet6RouteExcludeAddress, false) + if err != nil { + return err + } + redirectToPorts := []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(r.redirectPort()), + }, &expr.Redir{ + RegisterProtoMin: 1, + // NF_NAT_RANGE_PROTO_SPECIFIED + Flags: 2, + }, + } chainOutput := nft.AddChain(&nftables.Chain{ Name: "output", Table: table, @@ -57,7 +77,67 @@ func (r *autoRedirect) setupNFTables() error { nft.AddRule(&nftables.Rule{ Table: table, Chain: chainOutput, - Exprs: nftablesRuleIfName(expr.MetaKeyOIFNAME, r.tunOptions.Name, nftablesRuleRedirectToPorts(redirectPort)...), + Exprs: []expr.Any{ + &expr.Meta{ + Key: expr.MetaKeyOIFNAME, + Register: 1, + }, + &expr.Cmp{ + Op: expr.CmpOpNeq, + Register: 1, + Data: nftablesIfname(r.tunOptions.Name), + }, + &expr.Verdict{ + Kind: expr.VerdictReturn, + }, + }, + }) + routeReject := []expr.Any{ + &expr.Immediate{ + Register: 1, + Data: binaryutil.BigEndian.PutUint32(ReturnFWMark), + }, + &expr.Meta{ + Key: expr.MetaKeyMARK, + SourceRegister: true, + Register: 1, + }, + &expr.Verdict{ + Kind: expr.VerdictReturn, + }, + } + if r.enableIPv4 { + nft.AddRule(&nftables.Rule{ + Table: table, + Chain: chainOutput, + Exprs: nftablesRuleDestinationIPSet(1, "inet4_route_address_set", nftables.TableFamilyIPv4, true, routeReject), + }) + } + if r.enableIPv6 { + nft.AddRule(&nftables.Rule{ + Table: table, + Chain: chainOutput, + Exprs: nftablesRuleDestinationIPSet(2, "inet6_route_address_set", nftables.TableFamilyIPv6, true, routeReject), + }) + } + if r.enableIPv4 { + nft.AddRule(&nftables.Rule{ + Table: table, + Chain: chainOutput, + Exprs: nftablesRuleDestinationIPSet(3, "inet4_route_exclude_address_set", nftables.TableFamilyIPv4, false, routeReject), + }) + } + if r.enableIPv6 { + nft.AddRule(&nftables.Rule{ + Table: table, + Chain: chainOutput, + Exprs: nftablesRuleDestinationIPSet(4, "inet6_route_exclude_address_set", nftables.TableFamilyIPv6, false, routeReject), + }) + } + nft.AddRule(&nftables.Rule{ + Table: table, + Chain: chainOutput, + Exprs: redirectToPorts, }) chainPreRouting := nft.AddChain(&nftables.Chain{ @@ -67,58 +147,102 @@ func (r *autoRedirect) setupNFTables() error { Priority: nftables.ChainPriorityMangle, Type: nftables.ChainTypeNAT, }) + nft.AddRule(&nftables.Rule{ Table: table, Chain: chainPreRouting, - Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, r.tunOptions.Name, &expr.Verdict{ - Kind: expr.VerdictReturn, - }), + Exprs: []expr.Any{ + &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: nftablesIfname(r.tunOptions.Name), + }, + &expr.Verdict{ + Kind: expr.VerdictReturn, + }, + }, }) - var ( - routeAddress []netip.Prefix - routeExcludeAddress []netip.Prefix - ) - if r.enableIPv4 { - routeAddress = append(routeAddress, r.tunOptions.Inet4RouteAddress...) - routeExcludeAddress = append(routeExcludeAddress, r.tunOptions.Inet4RouteExcludeAddress...) - } - if r.enableIPv6 { - routeAddress = append(routeAddress, r.tunOptions.Inet6RouteAddress...) - routeExcludeAddress = append(routeExcludeAddress, r.tunOptions.Inet6RouteExcludeAddress...) - } - for _, address := range routeExcludeAddress { + + if len(r.tunOptions.IncludeInterface) > 0 { + if len(r.tunOptions.IncludeInterface) > 1 { + // TODO: support it by nftables set + return E.New("`include_interface` > 1 is not supported") + } nft.AddRule(&nftables.Rule{ Table: table, Chain: chainPreRouting, - Exprs: nftablesRuleDestinationAddress(address, &expr.Verdict{ - Kind: expr.VerdictReturn, - }), + Exprs: []expr.Any{ + &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, + &expr.Cmp{ + Op: expr.CmpOpNeq, + Register: 1, + Data: nftablesIfname(r.tunOptions.IncludeInterface[0]), + }, + &expr.Verdict{ + Kind: expr.VerdictReturn, + }, + }, }) } + for _, name := range r.tunOptions.ExcludeInterface { nft.AddRule(&nftables.Rule{ Table: table, Chain: chainPreRouting, - Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, name, &expr.Verdict{ - Kind: expr.VerdictReturn, - }), + Exprs: []expr.Any{ + &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, + &expr.Cmp{ + Op: expr.CmpOpNeq, + Register: 1, + Data: nftablesIfname(name), + }, + &expr.Verdict{ + Kind: expr.VerdictReturn, + }, + }, }) } - for _, uidRange := range r.tunOptions.ExcludeUID { + + if len(r.tunOptions.IncludeUID) > 0 { + if len(r.tunOptions.IncludeUID) > 1 { + return E.New("`include_uid` > 1 is not supported") + } nft.AddRule(&nftables.Rule{ Table: table, Chain: chainPreRouting, - Exprs: nftablesRuleMetaUInt32Range(expr.MetaKeySKUID, uidRange, &expr.Verdict{ - Kind: expr.VerdictReturn, - }), + Exprs: []expr.Any{ + &expr.Meta{Key: expr.MetaKeySKUID, Register: 1}, + &expr.Range{ + Op: expr.CmpOpNeq, + Register: 1, + FromData: binaryutil.BigEndian.PutUint32(r.tunOptions.IncludeUID[0].Start), + ToData: binaryutil.BigEndian.PutUint32(r.tunOptions.IncludeUID[0].End), + }, + &expr.Verdict{ + Kind: expr.VerdictReturn, + }, + }, }) } - var routeExprs []expr.Any - if len(routeAddress) > 0 { - for _, address := range routeAddress { - routeExprs = append(routeExprs, nftablesRuleDestinationAddress(address)...) - } + for _, uidRange := range r.tunOptions.ExcludeUID { + nft.AddRule(&nftables.Rule{ + Table: table, + Chain: chainPreRouting, + Exprs: []expr.Any{ + &expr.Meta{Key: expr.MetaKeySKUID, Register: 1}, + &expr.Range{ + Op: expr.CmpOpNeq, + Register: 1, + FromData: binaryutil.BigEndian.PutUint32(uidRange.Start), + ToData: binaryutil.BigEndian.PutUint32(uidRange.End), + }, + &expr.Verdict{ + Kind: expr.VerdictReturn, + }, + }, + }) } if !r.tunOptions.EXP_DisableDNSHijack { @@ -133,62 +257,55 @@ func (r *autoRedirect) setupNFTables() error { } if r.enableIPv6 && !dnsServer6.IsValid() { dnsServer6 = r.tunOptions.Inet6Address[0].Addr().Next() - } - if len(r.tunOptions.IncludeInterface) > 0 || len(r.tunOptions.IncludeUID) > 0 { - for _, name := range r.tunOptions.IncludeInterface { - if r.enableIPv4 { - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainPreRouting, - Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, name, append(routeExprs, nftablesRuleHijackDNS(nftables.TableFamilyIPv4, dnsServer4)...)...), - }) - } - if r.enableIPv6 { - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainPreRouting, - Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, name, append(routeExprs, nftablesRuleHijackDNS(nftables.TableFamilyIPv6, dnsServer6)...)...), - }) - } - } - for _, uidRange := range r.tunOptions.IncludeUID { - if r.enableIPv4 { - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainPreRouting, - Exprs: nftablesRuleMetaUInt32Range(expr.MetaKeySKUID, uidRange, append(routeExprs, nftablesRuleHijackDNS(nftables.TableFamilyIPv4, dnsServer4)...)...), - }) - } - if r.enableIPv6 { - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainPreRouting, - Exprs: nftablesRuleMetaUInt32Range(expr.MetaKeySKUID, uidRange, append(routeExprs, nftablesRuleHijackDNS(nftables.TableFamilyIPv6, dnsServer6)...)...), - }) - } - } - } else { if r.enableIPv4 { nft.AddRule(&nftables.Rule{ Table: table, Chain: chainPreRouting, - Exprs: append(routeExprs, nftablesRuleHijackDNS(nftables.TableFamilyIPv4, dnsServer4)...), + Exprs: nftablesRuleHijackDNS(nftables.TableFamilyIPv4, dnsServer4), }) } if r.enableIPv6 { nft.AddRule(&nftables.Rule{ Table: table, Chain: chainPreRouting, - Exprs: append(routeExprs, nftablesRuleHijackDNS(nftables.TableFamilyIPv6, dnsServer6)...), + Exprs: nftablesRuleHijackDNS(nftables.TableFamilyIPv6, dnsServer6), }) } } } + if r.enableIPv4 { + nft.AddRule(&nftables.Rule{ + Table: table, + Chain: chainPreRouting, + Exprs: nftablesRuleDestinationIPSet(1, "inet4_route_address_set", nftables.TableFamilyIPv4, true, routeReject), + }) + } + if r.enableIPv6 { + nft.AddRule(&nftables.Rule{ + Table: table, + Chain: chainPreRouting, + Exprs: nftablesRuleDestinationIPSet(2, "inet6_route_address_set", nftables.TableFamilyIPv6, true, routeReject), + }) + } + if r.enableIPv4 { + nft.AddRule(&nftables.Rule{ + Table: table, + Chain: chainPreRouting, + Exprs: nftablesRuleDestinationIPSet(3, "inet4_route_exclude_address_set", nftables.TableFamilyIPv4, false, routeReject), + }) + } + if r.enableIPv6 { + nft.AddRule(&nftables.Rule{ + Table: table, + Chain: chainPreRouting, + Exprs: nftablesRuleDestinationIPSet(4, "inet6_route_exclude_address_set", nftables.TableFamilyIPv6, false, routeReject), + }) + } nft.AddRule(&nftables.Rule{ Table: table, Chain: chainPreRouting, - Exprs: []expr.Any{ + Exprs: append([]expr.Any{ &expr.Fib{ Register: 1, FlagDADDR: true, @@ -199,46 +316,127 @@ func (r *autoRedirect) setupNFTables() error { Register: 1, Data: binaryutil.NativeEndian.PutUint32(unix.RTN_LOCAL), }, - &expr.Verdict{ - Kind: expr.VerdictReturn, - }, - }, + }, routeReject...), + }) + nft.AddRule(&nftables.Rule{ + Table: table, + Chain: chainPreRouting, + Exprs: redirectToPorts, }) - if len(r.tunOptions.IncludeInterface) > 0 || len(r.tunOptions.IncludeUID) > 0 { - for _, name := range r.tunOptions.IncludeInterface { - nft.AddRule(&nftables.Rule{ - Table: table, - Chain: chainPreRouting, - Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, name, append(routeExprs, nftablesRuleRedirectToPorts(redirectPort)...)...), - }) - } - for _, uidRange := range r.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)...), - }) + err = r.configureFW4(nft, false) + if err != nil { + return err } + return nft.Flush() } +func (r *autoRedirect) nftablesUpdateRouteAddressSet() error { + nft, err := nftables.New() + if err != nil { + return err + } + defer nft.CloseLasting() + table, err := nft.ListTableOfFamily(r.tableName, nftables.TableFamilyINet) + if err != nil { + return err + } + routeAddressSet := *r.routeAddressSet + routeExcludeAddressSet := *r.routeExcludeAddressSet + err = nftablesCreateIPSet(nft, table, 1, "inet4_route_address_set", nftables.TableFamilyIPv4, routeAddressSet, r.tunOptions.Inet4RouteAddress, true) + if err != nil { + return err + } + err = nftablesCreateIPSet(nft, table, 2, "inet6_route_address_set", nftables.TableFamilyIPv6, routeAddressSet, r.tunOptions.Inet6RouteAddress, true) + if err != nil { + return err + } + err = nftablesCreateIPSet(nft, table, 3, "inet4_route_exclude_address_set", nftables.TableFamilyIPv4, routeExcludeAddressSet, r.tunOptions.Inet4RouteExcludeAddress, true) + if err != nil { + return err + } + err = nftablesCreateIPSet(nft, table, 4, "inet6_route_exclude_address_set", nftables.TableFamilyIPv6, routeExcludeAddressSet, r.tunOptions.Inet6RouteExcludeAddress, true) + if err != nil { + return err + } + return nil +} + func (r *autoRedirect) cleanupNFTables() { - conn, err := nftables.New() + nft, err := nftables.New() if err != nil { return } - conn.DelTable(&nftables.Table{ + nft.DelTable(&nftables.Table{ Name: r.tableName, Family: nftables.TableFamilyINet, }) - _ = conn.Flush() - _ = conn.CloseLasting() + common.Must(r.configureFW4(nft, true)) + _ = nft.Flush() + _ = nft.CloseLasting() +} + +func (r *autoRedirect) configureFW4(nft *nftables.Conn, undo bool) error { + tableFW4, err := nft.ListTableOfFamily("fw4", nftables.TableFamilyINet) + if err != nil { + return nil + } + chainForward := &nftables.Chain{ + Name: "forward", + Table: tableFW4, + Hooknum: nftables.ChainHookForward, + Priority: nftables.ChainPriorityFilter, + Type: nftables.ChainTypeFilter, + } + ruleIif := &nftables.Rule{ + Table: tableFW4, + Chain: chainForward, + Exprs: []expr.Any{ + &expr.Meta{ + Key: expr.MetaKeyIIFNAME, + Register: 1, + }, + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: nftablesIfname(r.tunOptions.Name), + }, + &expr.Verdict{ + Kind: expr.VerdictAccept, + }, + }, + } + ruleOif := &nftables.Rule{ + Table: tableFW4, + Chain: chainForward, + Exprs: []expr.Any{ + &expr.Meta{ + Key: expr.MetaKeyOIFNAME, + Register: 1, + }, + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: nftablesIfname(r.tunOptions.Name), + }, + &expr.Verdict{ + Kind: expr.VerdictAccept, + }, + }, + } + if !undo { + nft.InsertRule(ruleOif) + nft.InsertRule(ruleIif) + } else { + err = nft.DelRule(ruleIif) + if err != nil { + return err + } + err = nft.DelRule(ruleOif) + if err != nil { + return err + } + } + return nil } diff --git a/redirect_nftables_expr.go b/redirect_nftables_expr.go index 9692d8a..83999d9 100644 --- a/redirect_nftables_expr.go +++ b/redirect_nftables_expr.go @@ -3,14 +3,15 @@ package tun import ( - "net" "net/netip" + "unsafe" "github.com/sagernet/nftables" "github.com/sagernet/nftables/binaryutil" "github.com/sagernet/nftables/expr" - "github.com/sagernet/sing/common/ranges" + "github.com/sagernet/sing/common" + "go4.org/netipx" "golang.org/x/sys/unix" ) @@ -20,92 +21,6 @@ func nftablesIfname(n string) []byte { 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 { - newExprs := []expr.Any{ - &expr.Meta{ - Key: expr.MetaKeyNFPROTO, - Register: 1, - }, - } - if address.Addr().Is4() { - newExprs = append(newExprs, - &expr.Cmp{ - Op: expr.CmpOpEq, - Register: 1, - Data: []byte{unix.NFPROTO_IPV4}, - }, - &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.Cmp{ - Op: expr.CmpOpEq, - Register: 1, - Data: []byte{unix.NFPROTO_IPV6}, - }, - &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{ @@ -127,12 +42,11 @@ func nftablesRuleHijackDNS(family nftables.TableFamily, dnsServerAddress netip.A Data: []byte{unix.IPPROTO_UDP}, }, &expr.Payload{ - OperationType: expr.PayloadLoad, - DestRegister: 1, - SourceRegister: 0, - Base: expr.PayloadBaseTransportHeader, - Offset: 2, - Len: 2, + OperationType: expr.PayloadLoad, + DestRegister: 1, + Base: expr.PayloadBaseTransportHeader, + Offset: 2, + Len: 2, }, &expr.Cmp{ Op: expr.CmpOpEq, Register: 1, @@ -148,32 +62,129 @@ func nftablesRuleHijackDNS(family nftables.TableFamily, dnsServerAddress netip.A } } -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 ipSetHas4(setList []*netipx.IPSet) bool { + /*return common.Any(setList, func(it *netipx.IPSet) bool { + mySet := (*myIPSet)(unsafe.Pointer(it)) + return common.Any(mySet.rr, func(it myIPRange) bool { + return it.from.Is4() + }) + })*/ + return common.Any(setList, func(it *netipx.IPSet) bool { + mySet := (*myIPSet)(unsafe.Pointer(it)) + return mySet.rr[0].from.Is4() + }) +} -func nftablesRuleRedirectToPorts(redirectPort uint16) []expr.Any { - return []expr.Any{ +func ipSetHas6(setList []*netipx.IPSet) bool { + /*return common.Any(setList, func(it *netipx.IPSet) bool { + mySet := (*myIPSet)(unsafe.Pointer(it)) + return common.Any(mySet.rr, func(it myIPRange) bool { + return it.from.Is6() + }) + })*/ + return common.Any(setList, func(it *netipx.IPSet) bool { + mySet := (*myIPSet)(unsafe.Pointer(it)) + return mySet.rr[len(mySet.rr)-1].from.Is6() + }) +} + +func nftablesRuleDestinationIPSet(id uint32, name string, family nftables.TableFamily, invert bool, exprs []expr.Any) []expr.Any { + var newExprs []expr.Any + newExprs = append(newExprs, &expr.Meta{ - Key: expr.MetaKeyL4PROTO, + Key: expr.MetaKeyNFPROTO, 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, + Data: []byte{byte(family)}, }, + ) + if family == nftables.TableFamilyIPv4 { + newExprs = append(newExprs, + &expr.Payload{ + OperationType: expr.PayloadLoad, + DestRegister: 1, + Base: expr.PayloadBaseNetworkHeader, + Offset: 16, + Len: 4, + }, + ) + } else { + newExprs = append(newExprs, + &expr.Payload{ + OperationType: expr.PayloadLoad, + DestRegister: 1, + Base: expr.PayloadBaseNetworkHeader, + Offset: 24, + Len: 16, + }, + ) } + newExprs = append(newExprs, &expr.Lookup{ + SourceRegister: 1, + SetID: id, + SetName: name, + Invert: invert, + }) + newExprs = append(newExprs, exprs...) + return newExprs +} + +func nftablesCreateIPSet(nft *nftables.Conn, table *nftables.Table, id uint32, name string, family nftables.TableFamily, setList []*netipx.IPSet, prefixList []netip.Prefix, update bool) error { + ipSets := make([]*myIPSet, 0, len(setList)) + var rangeLen int + for _, set := range setList { + mySet := (*myIPSet)(unsafe.Pointer(set)) + ipSets = append(ipSets, mySet) + rangeLen += len(mySet.rr) + } + setElements := make([]nftables.SetElement, 0, len(prefixList)+rangeLen) + for _, mySet := range ipSets { + for _, rr := range mySet.rr { + if (family == nftables.TableFamilyIPv4) != rr.from.Is4() { + continue + } + setElements = append(setElements, nftables.SetElement{ + Key: rr.from.AsSlice(), + KeyEnd: rr.to.AsSlice(), + }) + } + } + for _, prefix := range prefixList { + rangeOf := netipx.RangeOfPrefix(prefix) + setElements = append(setElements, nftables.SetElement{ + Key: rangeOf.From().AsSlice(), + KeyEnd: rangeOf.To().AsSlice(), + }) + } + var keyType nftables.SetDatatype + if family == nftables.TableFamilyIPv4 { + keyType = nftables.TypeIPAddr + } else { + keyType = nftables.TypeIP6Addr + } + mySet := &nftables.Set{ + Table: table, + ID: id, + Name: name, + Interval: true, + KeyType: keyType, + } + if update { + nft.FlushSet(mySet) + return nft.SetAddElements(mySet, setElements) + } else { + return nft.AddSet(mySet, setElements) + } +} + +type myIPSet struct { + rr []myIPRange +} + +type myIPRange struct { + from netip.Addr + to netip.Addr } diff --git a/tun.go b/tun.go index 541242b..5db2e74 100644 --- a/tun.go +++ b/tun.go @@ -48,6 +48,7 @@ type Options struct { MTU uint32 GSO bool AutoRoute bool + AutoRedirect bool DNSServers []netip.Addr IPRoute2RuleIndex int StrictRoute bool diff --git a/tun_linux.go b/tun_linux.go index 35d303d..df66479 100644 --- a/tun_linux.go +++ b/tun_linux.go @@ -415,6 +415,27 @@ func (t *NativeTun) rules() []*netlink.Rule { priority6 := priority nopPriority := ruleStart + 10 + if t.options.AutoRedirect { + if p4 { + it = netlink.NewRule() + it.Priority = priority + it.Mark = ReturnFWMark + it.MarkSet = true + it.Family = unix.AF_INET + it.Goto = nopPriority + rules = append(rules, it) + } + if p6 { + it = netlink.NewRule() + it.Priority = priority6 + it.Mark = ReturnFWMark + it.MarkSet = true + it.Family = unix.AF_INET6 + it.Goto = nopPriority + rules = append(rules, it) + } + } + for _, excludeRange := range excludeRanges { if p4 { it = netlink.NewRule() @@ -433,7 +454,7 @@ func (t *NativeTun) rules() []*netlink.Rule { rules = append(rules, it) } } - if len(excludeRanges) > 0 { + if t.options.AutoRedirect || len(excludeRanges) > 0 { if p4 { priority++ }