auto-redirect: Add route address set support for nftables

This commit is contained in:
世界 2024-06-12 02:37:51 +08:00
parent 85fe25a592
commit 85f5f2dd58
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
14 changed files with 1255 additions and 426 deletions

11
go.mod
View file

@ -6,21 +6,22 @@ require (
github.com/fsnotify/fsnotify v1.7.0 github.com/fsnotify/fsnotify v1.7.0
github.com/go-ole/go-ole v1.3.0 github.com/go-ole/go-ole v1.3.0
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f 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/nftables v0.3.0-beta.4
github.com/sagernet/sing v0.5.0-alpha.9 github.com/sagernet/sing v0.5.0-alpha.10
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
golang.org/x/net v0.26.0 golang.org/x/net v0.26.0
golang.org/x/sys v0.21.0 golang.org/x/sys v0.21.0
) )
require ( require (
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/josharian/native v1.1.0 // indirect github.com/josharian/native v1.1.0 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/socket v0.4.1 // indirect
github.com/vishvananda/netns v0.0.4 // indirect github.com/vishvananda/netns v0.0.4 // indirect
golang.org/x/sync v0.1.0 // indirect golang.org/x/sync v0.7.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
) )

22
go.sum
View file

@ -5,8 +5,8 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
@ -16,21 +16,23 @@ 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/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 h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I=
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0= 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-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= 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.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
github.com/sagernet/nftables v0.3.0-beta.2/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/sing v0.5.0-alpha.9 h1:Mmg+LCbaKXBeQD/ttzi0/MQa3NcUyfadIgkGzhQW7o0= github.com/sagernet/sing v0.5.0-alpha.10 h1:kuHl10gpjbKQAdQfyogQU3u0CVnpqC3wrAHe/+BFaXc=
github.com/sagernet/sing v0.5.0-alpha.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.5.0-alpha.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

View file

@ -154,9 +154,9 @@ func (m *defaultInterfaceMonitor) checkUpdate() error {
if routeMessage.Flags&unix.RTF_GATEWAY == 0 { if routeMessage.Flags&unix.RTF_GATEWAY == 0 {
continue continue
} }
if routeMessage.Flags&unix.RTF_IFSCOPE != 0 { // if routeMessage.Flags&unix.RTF_IFSCOPE != 0 {
// continue //continue
} //}
defaultInterface = routeInterface defaultInterface = routeInterface
break break
} }

View file

@ -3,20 +3,33 @@ package tun
import ( import (
"context" "context"
"github.com/sagernet/sing/common/control"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
"go4.org/netipx"
)
const (
DefaultAutoRedirectInputMark = 0x2023
DefaultAutoRedirectOutputMark = 0x2024
) )
type AutoRedirect interface { type AutoRedirect interface {
Start() error Start() error
Close() error Close() error
UpdateRouteAddressSet()
} }
type AutoRedirectOptions struct { type AutoRedirectOptions struct {
TunOptions *Options TunOptions *Options
Context context.Context Context context.Context
Handler Handler Handler Handler
Logger logger.Logger Logger logger.Logger
TableName string NetworkMonitor NetworkUpdateMonitor
DisableNFTables bool InterfaceFinder control.InterfaceFinder
CustomRedirectPort func() int TableName string
DisableNFTables bool
CustomRedirectPort func() int
RouteAddressSet *[]*netipx.IPSet
RouteExcludeAddressSet *[]*netipx.IPSet
} }

View file

@ -29,8 +29,9 @@ func (r *autoRedirect) setupIPTables() error {
} }
func (r *autoRedirect) setupIPTablesForFamily(iptablesPath string) error { func (r *autoRedirect) setupIPTablesForFamily(iptablesPath string) error {
tableNameOutput := r.tableName + "-output" tableNameInput := r.tableName + "-input"
tableNameForward := r.tableName + "-forward" tableNameForward := r.tableName + "-forward"
tableNameOutput := r.tableName + "-output"
tableNamePreRouteing := r.tableName + "-prerouting" tableNamePreRouteing := r.tableName + "-prerouting"
redirectPort := r.redirectPort() redirectPort := r.redirectPort()
// OUTPUT // OUTPUT
@ -51,6 +52,25 @@ func (r *autoRedirect) setupIPTablesForFamily(iptablesPath string) error {
if r.androidSu { if r.androidSu {
return nil return nil
} }
// INPUT
err = r.runShell(iptablesPath, "-N", tableNameInput)
if err != nil {
return err
}
err = r.runShell(iptablesPath, "-A", tableNameInput,
"-i", r.tunOptions.Name, "-j", "ACCEPT")
if err != nil {
return err
}
err = r.runShell(iptablesPath, "-A", tableNameInput,
"-o", r.tunOptions.Name, "-j", "ACCEPT")
if err != nil {
return err
}
err = r.runShell(iptablesPath, "-I FORWARD -j", tableNameInput)
if err != nil {
return err
}
// FORWARD // FORWARD
err = r.runShell(iptablesPath, "-N", tableNameForward) err = r.runShell(iptablesPath, "-N", tableNameForward)
if err != nil { if err != nil {

View file

@ -6,12 +6,17 @@ import (
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
"time"
"github.com/sagernet/nftables" "github.com/sagernet/nftables"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/x/list"
"go4.org/netipx"
) )
type autoRedirect struct { type autoRedirect struct {
@ -20,6 +25,10 @@ type autoRedirect struct {
handler Handler handler Handler
logger logger.Logger logger logger.Logger
tableName string tableName string
networkMonitor NetworkUpdateMonitor
networkListener *list.Element[NetworkUpdateCallback]
interfaceFinder control.InterfaceFinder
localAddresses []netip.Prefix
customRedirectPortFunc func() int customRedirectPortFunc func() int
customRedirectPort int customRedirectPort int
redirectServer *redirectServer redirectServer *redirectServer
@ -30,6 +39,8 @@ type autoRedirect struct {
useNFTables bool useNFTables bool
androidSu bool androidSu bool
suPath string suPath string
routeAddressSet *[]*netipx.IPSet
routeExcludeAddressSet *[]*netipx.IPSet
} }
func NewAutoRedirect(options AutoRedirectOptions) (AutoRedirect, error) { func NewAutoRedirect(options AutoRedirectOptions) (AutoRedirect, error) {
@ -38,9 +49,13 @@ func NewAutoRedirect(options AutoRedirectOptions) (AutoRedirect, error) {
ctx: options.Context, ctx: options.Context,
handler: options.Handler, handler: options.Handler,
logger: options.Logger, logger: options.Logger,
networkMonitor: options.NetworkMonitor,
interfaceFinder: options.InterfaceFinder,
tableName: options.TableName, tableName: options.TableName,
useNFTables: runtime.GOOS != "android" && !options.DisableNFTables, useNFTables: runtime.GOOS != "android" && !options.DisableNFTables,
customRedirectPortFunc: options.CustomRedirectPort, customRedirectPortFunc: options.CustomRedirectPort,
routeAddressSet: options.RouteAddressSet,
routeExcludeAddressSet: options.RouteExcludeAddressSet,
} }
var err error var err error
if runtime.GOOS == "android" { if runtime.GOOS == "android" {
@ -116,11 +131,18 @@ func (r *autoRedirect) Start() error {
} }
r.redirectServer = server r.redirectServer = server
} }
startAt := time.Now()
var err error
if r.useNFTables { if r.useNFTables {
return r.setupNFTables() err = r.setupNFTables()
} else { } else {
return r.setupIPTables() err = r.setupIPTables()
} }
if err != nil {
return err
}
r.logger.Debug("auto-redirect configured in ", time.Since(startAt))
return nil
} }
func (r *autoRedirect) Close() error { func (r *autoRedirect) Close() error {
@ -134,6 +156,15 @@ func (r *autoRedirect) Close() error {
) )
} }
func (r *autoRedirect) UpdateRouteAddressSet() {
if r.useNFTables {
err := r.nftablesUpdateRouteAddressSet()
if err != nil {
r.logger.Error("update route address set: ", err)
}
}
}
func (r *autoRedirect) initializeNFTables() error { func (r *autoRedirect) initializeNFTables() error {
nft, err := nftables.New() nft, err := nftables.New()
if err != nil { if err != nil {

View file

@ -9,7 +9,9 @@ import (
"github.com/sagernet/nftables/binaryutil" "github.com/sagernet/nftables/binaryutil"
"github.com/sagernet/nftables/expr" "github.com/sagernet/nftables/expr"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/control"
"golang.org/x/exp/slices"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
@ -25,28 +27,21 @@ func (r *autoRedirect) setupNFTables() error {
Family: nftables.TableFamilyINet, Family: nftables.TableFamilyINet,
}) })
chainForward := nft.AddChain(&nftables.Chain{ err = r.nftablesCreateAddressSets(nft, table, false)
Name: "forward", if err != nil {
Table: table, return err
Hooknum: nftables.ChainHookForward, }
Priority: nftables.ChainPriorityMangle,
}) r.localAddresses = common.FlatMap(r.interfaceFinder.Interfaces(), func(it control.Interface) []netip.Prefix {
nft.AddRule(&nftables.Rule{ return common.Filter(it.Addresses, func(prefix netip.Prefix) bool {
Table: table, return it.Name == "lo" || prefix.Addr().IsGlobalUnicast()
Chain: chainForward, })
Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, r.tunOptions.Name, &expr.Verdict{ })
Kind: expr.VerdictAccept, err = r.nftablesCreateLocalAddressSets(nft, table, r.localAddresses, nil)
}), if err != nil {
}) return err
nft.AddRule(&nftables.Rule{ }
Table: table,
Chain: chainForward,
Exprs: nftablesRuleIfName(expr.MetaKeyOIFNAME, r.tunOptions.Name, &expr.Verdict{
Kind: expr.VerdictAccept,
}),
})
redirectPort := r.redirectPort()
chainOutput := nft.AddChain(&nftables.Chain{ chainOutput := nft.AddChain(&nftables.Chain{
Name: "output", Name: "output",
Table: table, Table: table,
@ -54,11 +49,37 @@ func (r *autoRedirect) setupNFTables() error {
Priority: nftables.ChainPriorityMangle, Priority: nftables.ChainPriorityMangle,
Type: nftables.ChainTypeNAT, Type: nftables.ChainTypeNAT,
}) })
nft.AddRule(&nftables.Rule{ if r.tunOptions.AutoRedirectMarkMode {
Table: table, err = r.nftablesCreateExcludeRules(nft, table, chainOutput)
Chain: chainOutput, if err != nil {
Exprs: nftablesRuleIfName(expr.MetaKeyOIFNAME, r.tunOptions.Name, nftablesRuleRedirectToPorts(redirectPort)...), return err
}) }
r.nftablesCreateUnreachable(nft, table, chainOutput)
r.nftablesCreateRedirect(nft, table, chainOutput)
chainOutputUDP := nft.AddChain(&nftables.Chain{
Name: "output_udp",
Table: table,
Hooknum: nftables.ChainHookOutput,
Priority: nftables.ChainPriorityMangle,
Type: nftables.ChainTypeRoute,
})
err = r.nftablesCreateExcludeRules(nft, table, chainOutputUDP)
if err != nil {
return err
}
r.nftablesCreateUnreachable(nft, table, chainOutputUDP)
r.nftablesCreateMark(nft, table, chainOutputUDP)
} else {
r.nftablesCreateRedirect(nft, table, chainOutput, &expr.Meta{
Key: expr.MetaKeyOIFNAME,
Register: 1,
}, &expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: nftablesIfname(r.tunOptions.Name),
})
}
chainPreRouting := nft.AddChain(&nftables.Chain{ chainPreRouting := nft.AddChain(&nftables.Chain{
Name: "prerouting", Name: "prerouting",
@ -67,178 +88,136 @@ func (r *autoRedirect) setupNFTables() error {
Priority: nftables.ChainPriorityMangle, Priority: nftables.ChainPriorityMangle,
Type: nftables.ChainTypeNAT, Type: nftables.ChainTypeNAT,
}) })
nft.AddRule(&nftables.Rule{ err = r.nftablesCreateExcludeRules(nft, table, chainPreRouting)
Table: table, if err != nil {
Chain: chainPreRouting, return err
Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, r.tunOptions.Name, &expr.Verdict{ }
Kind: expr.VerdictReturn, r.nftablesCreateUnreachable(nft, table, chainPreRouting)
}), r.nftablesCreateRedirect(nft, table, chainPreRouting)
r.nftablesCreateMark(nft, table, chainPreRouting)
if r.tunOptions.AutoRedirectMarkMode {
chainPreRoutingUDP := nft.AddChain(&nftables.Chain{
Name: "prerouting_udp",
Table: table,
Hooknum: nftables.ChainHookPrerouting,
Priority: nftables.ChainPriorityRef(*nftables.ChainPriorityMangle + 1),
Type: nftables.ChainTypeFilter,
})
if r.enableIPv4 {
nftablesCreateExcludeDestinationIPSet(nft, table, chainPreRoutingUDP, 5, "inet4_local_address_set", nftables.TableFamilyIPv4, false)
}
if r.enableIPv6 {
nftablesCreateExcludeDestinationIPSet(nft, table, chainPreRoutingUDP, 6, "inet6_local_address_set", nftables.TableFamilyIPv6, false)
}
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chainPreRoutingUDP,
Exprs: []expr.Any{
&expr.Meta{
Key: expr.MetaKeyL4PROTO,
Register: 1,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: []byte{unix.IPPROTO_UDP},
},
&expr.Ct{
Key: expr.CtKeyMARK,
Register: 1,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: binaryutil.NativeEndian.PutUint32(r.tunOptions.AutoRedirectInputMark),
},
&expr.Meta{
Key: expr.MetaKeyMARK,
Register: 1,
SourceRegister: true,
},
&expr.Counter{},
},
})
}
err = r.configureOpenWRTFirewall4(nft, false)
if err != nil {
return err
}
err = nft.Flush()
if err != nil {
return err
}
r.networkListener = r.networkMonitor.RegisterCallback(func() {
err = r.nftablesUpdateLocalAddressSet()
if err != nil {
r.logger.Error("update local address set: ", err)
}
}) })
var ( return nil
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 {
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chainPreRouting,
Exprs: nftablesRuleDestinationAddress(address, &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,
}),
})
}
for _, uidRange := range r.tunOptions.ExcludeUID {
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chainPreRouting,
Exprs: nftablesRuleMetaUInt32Range(expr.MetaKeySKUID, uidRange, &expr.Verdict{
Kind: expr.VerdictReturn,
}),
})
}
var routeExprs []expr.Any // TODO; test is this works
if len(routeAddress) > 0 { func (r *autoRedirect) nftablesUpdateLocalAddressSet() error {
for _, address := range routeAddress { newLocalAddresses := common.FlatMap(r.interfaceFinder.Interfaces(), func(it control.Interface) []netip.Prefix {
routeExprs = append(routeExprs, nftablesRuleDestinationAddress(address)...) return common.Filter(it.Addresses, func(prefix netip.Prefix) bool {
} return it.Name == "lo" || prefix.Addr().IsGlobalUnicast()
}
if !r.tunOptions.EXP_DisableDNSHijack {
dnsServer4 := common.Find(r.tunOptions.DNSServers, func(it netip.Addr) bool {
return it.Is4()
}) })
dnsServer6 := common.Find(r.tunOptions.DNSServers, func(it netip.Addr) bool {
return it.Is6()
})
if r.enableIPv4 && !dnsServer4.IsValid() {
dnsServer4 = r.tunOptions.Inet4Address[0].Addr().Next()
}
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)...),
})
}
if r.enableIPv6 {
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chainPreRouting,
Exprs: append(routeExprs, nftablesRuleHijackDNS(nftables.TableFamilyIPv6, dnsServer6)...),
})
}
}
}
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 slices.Equal(newLocalAddresses, r.localAddresses) {
return nil
}
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
}
err = r.nftablesCreateLocalAddressSets(nft, table, newLocalAddresses, r.localAddresses)
if err != nil {
return err
}
r.localAddresses = newLocalAddresses
return nft.Flush()
}
if len(r.tunOptions.IncludeInterface) > 0 || len(r.tunOptions.IncludeUID) > 0 { func (r *autoRedirect) nftablesUpdateRouteAddressSet() error {
for _, name := range r.tunOptions.IncludeInterface { nft, err := nftables.New()
nft.AddRule(&nftables.Rule{ if err != nil {
Table: table, return err
Chain: chainPreRouting, }
Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, name, append(routeExprs, nftablesRuleRedirectToPorts(redirectPort)...)...), defer nft.CloseLasting()
}) table, err := nft.ListTableOfFamily(r.tableName, nftables.TableFamilyINet)
} if err != nil {
for _, uidRange := range r.tunOptions.IncludeUID { return err
nft.AddRule(&nftables.Rule{ }
Table: table, err = r.nftablesCreateAddressSets(nft, table, true)
Chain: chainPreRouting, if err != nil {
Exprs: nftablesRuleMetaUInt32Range(expr.MetaKeySKUID, uidRange, append(routeExprs, nftablesRuleRedirectToPorts(redirectPort)...)...), return err
})
}
} else {
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chainPreRouting,
Exprs: append(routeExprs, nftablesRuleRedirectToPorts(redirectPort)...),
})
} }
return nft.Flush() return nft.Flush()
} }
func (r *autoRedirect) cleanupNFTables() { func (r *autoRedirect) cleanupNFTables() {
conn, err := nftables.New() if r.networkListener != nil {
r.networkMonitor.UnregisterCallback(r.networkListener)
}
nft, err := nftables.New()
if err != nil { if err != nil {
return return
} }
conn.DelTable(&nftables.Table{ nft.DelTable(&nftables.Table{
Name: r.tableName, Name: r.tableName,
Family: nftables.TableFamilyINet, Family: nftables.TableFamilyINet,
}) })
_ = conn.Flush() common.Must(r.configureOpenWRTFirewall4(nft, true))
_ = conn.CloseLasting() _ = nft.Flush()
_ = nft.CloseLasting()
} }

View file

@ -1,179 +0,0 @@
//go:build linux
package tun
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 {
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{
Key: expr.MetaKeyNFPROTO,
Register: 1,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: []byte{uint8(family)},
},
&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,
},
}
}

181
redirect_nftables_exprs.go Normal file
View file

@ -0,0 +1,181 @@
//go:build linux
package tun
import (
"net/netip"
"unsafe"
"github.com/sagernet/nftables"
"github.com/sagernet/nftables/expr"
"go4.org/netipx"
)
func nftablesIfname(n string) []byte {
b := make([]byte, 16)
copy(b, n+"\x00")
return b
}
func nftablesCreateExcludeDestinationIPSet(
nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain,
id uint32, name string, family nftables.TableFamily, invert bool,
) {
exprs := []expr.Any{
&expr.Meta{
Key: expr.MetaKeyNFPROTO,
Register: 1,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: []byte{byte(family)},
},
}
if family == nftables.TableFamilyIPv4 {
exprs = append(exprs,
&expr.Payload{
OperationType: expr.PayloadLoad,
DestRegister: 1,
Base: expr.PayloadBaseNetworkHeader,
Offset: 16,
Len: 4,
},
)
} else {
exprs = append(exprs,
&expr.Payload{
OperationType: expr.PayloadLoad,
DestRegister: 1,
Base: expr.PayloadBaseNetworkHeader,
Offset: 24,
Len: 16,
},
)
}
exprs = append(exprs,
&expr.Lookup{
SourceRegister: 1,
SetID: id,
SetName: name,
Invert: invert,
},
&expr.Counter{},
&expr.Verdict{
Kind: expr.VerdictReturn,
})
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: exprs,
})
}
func nftablesCreateIPSet(
nft *nftables.Conn, table *nftables.Table,
id uint32, name string, family nftables.TableFamily,
setList []*netipx.IPSet, prefixList []netip.Prefix, appendDefault bool, update bool,
) (*nftables.Set, error) {
if len(prefixList) > 0 {
var builder netipx.IPSetBuilder
if appendDefault && len(setList) == 0 {
if family == nftables.TableFamilyIPv4 {
prefixList = append(prefixList, netip.PrefixFrom(netip.IPv4Unspecified(), 0))
} else {
prefixList = append(prefixList, netip.PrefixFrom(netip.IPv6Unspecified(), 0))
}
}
for _, prefix := range prefixList {
builder.AddPrefix(prefix)
}
ipSet, err := builder.IPSet()
if err != nil {
return nil, err
}
setList = append(setList, ipSet)
}
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
}
endAddr := rr.to.Next()
if !endAddr.IsValid() {
endAddr = rr.from
}
setElements = append(setElements, nftables.SetElement{
Key: rr.from.AsSlice(),
})
setElements = append(setElements, nftables.SetElement{
Key: endAddr.AsSlice(),
IntervalEnd: true,
})
}
}
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 id == 0 {
mySet.Anonymous = true
mySet.Constant = true
}
if id == 0 {
err := nft.AddSet(mySet, setElements)
if err != nil {
return nil, err
}
return mySet, nil
} else if update {
nft.FlushSet(mySet)
} else {
err := nft.AddSet(mySet, nil)
if err != nil {
return nil, err
}
}
for len(setElements) > 0 {
toAdd := setElements
if len(toAdd) > 1000 {
toAdd = toAdd[:1000]
}
setElements = setElements[len(toAdd):]
err := nft.SetAddElements(mySet, toAdd)
if err != nil {
return nil, err
}
err = nft.Flush()
if err != nil {
return nil, err
}
}
return mySet, nil
}
type myIPSet struct {
rr []myIPRange
}
type myIPRange struct {
from netip.Addr
to netip.Addr
}

620
redirect_nftables_rules.go Normal file
View file

@ -0,0 +1,620 @@
//go:build linux
package tun
import (
"net/netip"
_ "unsafe"
"github.com/sagernet/nftables"
"github.com/sagernet/nftables/binaryutil"
"github.com/sagernet/nftables/expr"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/ranges"
"golang.org/x/exp/slices"
"golang.org/x/sys/unix"
)
//go:linkname allocSetID github.com/sagernet/nftables.allocSetID
var allocSetID uint32
func init() {
allocSetID = 6
}
func (r *autoRedirect) nftablesCreateAddressSets(
nft *nftables.Conn, table *nftables.Table,
update bool,
) error {
routeAddressSet := *r.routeAddressSet
routeExcludeAddressSet := *r.routeExcludeAddressSet
if len(routeAddressSet) == 0 && len(routeExcludeAddressSet) == 0 {
return nil
}
if len(routeAddressSet) > 0 {
if r.enableIPv4 {
_, err := nftablesCreateIPSet(nft, table, 1, "inet4_route_address_set", nftables.TableFamilyIPv4, routeAddressSet, nil, true, update)
if err != nil {
return err
}
}
if r.enableIPv6 {
_, err := nftablesCreateIPSet(nft, table, 2, "inet6_route_address_set", nftables.TableFamilyIPv6, routeAddressSet, nil, true, update)
if err != nil {
return err
}
}
}
if len(routeExcludeAddressSet) > 0 {
if r.enableIPv4 {
_, err := nftablesCreateIPSet(nft, table, 3, "inet4_route_exclude_address_set", nftables.TableFamilyIPv4, routeExcludeAddressSet, nil, false, update)
if err != nil {
return err
}
}
if r.enableIPv6 {
_, err := nftablesCreateIPSet(nft, table, 4, "inet6_route_exclude_address_set", nftables.TableFamilyIPv6, routeExcludeAddressSet, nil, false, update)
if err != nil {
return err
}
}
}
return nil
}
func (r *autoRedirect) nftablesCreateLocalAddressSets(
nft *nftables.Conn, table *nftables.Table,
localAddresses []netip.Prefix, lastAddresses []netip.Prefix,
) error {
if r.enableIPv4 {
localAddresses4 := common.Filter(localAddresses, func(it netip.Prefix) bool {
return it.Addr().Is4()
})
updateAddresses4 := common.Filter(localAddresses, func(it netip.Prefix) bool {
return it.Addr().Is4()
})
var update bool
if len(lastAddresses) != 0 {
if !slices.Equal(localAddresses4, updateAddresses4) {
update = true
}
}
if len(lastAddresses) == 0 || update {
_, err := nftablesCreateIPSet(nft, table, 5, "inet4_local_address_set", nftables.TableFamilyIPv4, nil, localAddresses4, false, update)
if err != nil {
return err
}
}
}
if r.enableIPv6 {
localAddresses6 := common.Filter(localAddresses, func(it netip.Prefix) bool {
return it.Addr().Is6()
})
updateAddresses6 := common.Filter(localAddresses, func(it netip.Prefix) bool {
return it.Addr().Is6()
})
var update bool
if len(lastAddresses) != 0 {
if !slices.Equal(localAddresses6, updateAddresses6) {
update = true
}
}
localAddresses6 = common.Filter(localAddresses6, func(it netip.Prefix) bool {
address := it.Addr()
return address.IsLoopback() || address.IsGlobalUnicast() && !address.IsPrivate()
})
if len(lastAddresses) == 0 || update {
_, err := nftablesCreateIPSet(nft, table, 6, "inet6_local_address_set", nftables.TableFamilyIPv6, nil, localAddresses6, false, update)
if err != nil {
return err
}
}
}
return nil
}
func (r *autoRedirect) nftablesCreateExcludeRules(nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain) error {
if r.tunOptions.AutoRedirectMarkMode && chain.Hooknum == nftables.ChainHookOutput {
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Meta{
Key: expr.MetaKeyMARK,
Register: 1,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: binaryutil.NativeEndian.PutUint32(r.tunOptions.AutoRedirectOutputMark),
},
&expr.Counter{},
&expr.Verdict{
Kind: expr.VerdictReturn,
},
},
})
}
if chain.Hooknum == nftables.ChainHookPrerouting {
if len(r.tunOptions.IncludeInterface) > 0 {
if len(r.tunOptions.IncludeInterface) > 1 {
includeInterface := &nftables.Set{
Table: table,
Anonymous: true,
Constant: true,
KeyType: nftables.TypeIFName,
}
err := nft.AddSet(includeInterface, common.Map(r.tunOptions.IncludeInterface, func(it string) nftables.SetElement {
return nftables.SetElement{
Key: nftablesIfname(it),
}
}))
if err != nil {
return err
}
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
&expr.Lookup{
SourceRegister: 1,
SetID: includeInterface.ID,
SetName: includeInterface.Name,
Invert: true,
},
&expr.Counter{},
&expr.Verdict{
Kind: expr.VerdictReturn,
},
},
})
} else {
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
&expr.Cmp{
Op: expr.CmpOpNeq,
Register: 1,
Data: nftablesIfname(r.tunOptions.IncludeInterface[0]),
},
&expr.Counter{},
&expr.Verdict{
Kind: expr.VerdictReturn,
},
},
})
}
}
if len(r.tunOptions.ExcludeInterface) > 0 {
if len(r.tunOptions.ExcludeInterface) > 1 {
excludeInterface := &nftables.Set{
Table: table,
Anonymous: true,
Constant: true,
KeyType: nftables.TypeIFName,
}
err := nft.AddSet(excludeInterface, common.Map(r.tunOptions.ExcludeInterface, func(it string) nftables.SetElement {
return nftables.SetElement{
Key: nftablesIfname(it),
}
}))
if err != nil {
return err
}
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
&expr.Lookup{
SourceRegister: 1,
SetID: excludeInterface.ID,
SetName: excludeInterface.Name,
},
&expr.Counter{},
&expr.Verdict{
Kind: expr.VerdictReturn,
},
},
})
} else {
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: nftablesIfname(r.tunOptions.ExcludeInterface[0]),
},
&expr.Counter{},
&expr.Verdict{
Kind: expr.VerdictReturn,
},
},
})
}
}
} else {
if len(r.tunOptions.IncludeUID) > 0 {
includeUID := &nftables.Set{
Table: table,
Anonymous: true,
Constant: true,
Interval: true,
KeyType: nftables.TypeUID,
}
err := nft.AddSet(includeUID, common.FlatMap(r.tunOptions.IncludeUID, func(it ranges.Range[uint32]) []nftables.SetElement {
return []nftables.SetElement{
{
Key: binaryutil.BigEndian.PutUint32(it.Start),
},
{
Key: binaryutil.BigEndian.PutUint32(it.End + 1),
IntervalEnd: true,
},
}
}))
if err != nil {
return err
}
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Meta{Key: expr.MetaKeySKUID, Register: 1},
&expr.Lookup{
SourceRegister: 1,
SetID: includeUID.ID,
SetName: includeUID.Name,
Invert: true,
},
&expr.Counter{},
&expr.Verdict{
Kind: expr.VerdictReturn,
},
},
})
}
if len(r.tunOptions.ExcludeUID) > 0 {
excludeUID := &nftables.Set{
Table: table,
Anonymous: true,
Constant: true,
Interval: true,
KeyType: nftables.TypeUID,
}
err := nft.AddSet(excludeUID, common.FlatMap(r.tunOptions.ExcludeUID, func(it ranges.Range[uint32]) []nftables.SetElement {
return []nftables.SetElement{
{
Key: binaryutil.BigEndian.PutUint32(it.Start),
},
{
Key: binaryutil.BigEndian.PutUint32(it.End + 1),
IntervalEnd: true,
},
}
}))
if err != nil {
return err
}
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Meta{Key: expr.MetaKeySKUID, Register: 1},
&expr.Lookup{
SourceRegister: 1,
SetID: excludeUID.ID,
SetName: excludeUID.Name,
},
&expr.Counter{},
&expr.Verdict{
Kind: expr.VerdictReturn,
},
},
})
}
}
if len(r.tunOptions.Inet4RouteAddress) > 0 {
inet4RouteAddress, err := nftablesCreateIPSet(nft, table, 0, "", nftables.TableFamilyIPv4, nil, r.tunOptions.Inet4RouteAddress, false, false)
if err != nil {
return err
}
nftablesCreateExcludeDestinationIPSet(nft, table, chain, inet4RouteAddress.ID, inet4RouteAddress.Name, nftables.TableFamilyIPv4, true)
}
if len(r.tunOptions.Inet6RouteAddress) > 0 {
inet6RouteAddress, err := nftablesCreateIPSet(nft, table, 0, "", nftables.TableFamilyIPv6, nil, r.tunOptions.Inet6RouteAddress, false, false)
if err != nil {
return err
}
nftablesCreateExcludeDestinationIPSet(nft, table, chain, inet6RouteAddress.ID, inet6RouteAddress.Name, nftables.TableFamilyIPv6, true)
}
if len(r.tunOptions.Inet4RouteExcludeAddress) > 0 {
inet4RouteExcludeAddress, err := nftablesCreateIPSet(nft, table, 0, "", nftables.TableFamilyIPv4, nil, r.tunOptions.Inet4RouteExcludeAddress, false, false)
if err != nil {
return err
}
nftablesCreateExcludeDestinationIPSet(nft, table, chain, inet4RouteExcludeAddress.ID, inet4RouteExcludeAddress.Name, nftables.TableFamilyIPv4, false)
}
if len(r.tunOptions.Inet6RouteExcludeAddress) > 0 {
inet6RouteExcludeAddress, err := nftablesCreateIPSet(nft, table, 0, "", nftables.TableFamilyIPv6, nil, r.tunOptions.Inet6RouteExcludeAddress, false, false)
if err != nil {
return err
}
nftablesCreateExcludeDestinationIPSet(nft, table, chain, inet6RouteExcludeAddress.ID, inet6RouteExcludeAddress.Name, nftables.TableFamilyIPv6, false)
}
if !r.tunOptions.EXP_DisableDNSHijack && ((chain.Hooknum == nftables.ChainHookPrerouting && chain.Type == nftables.ChainTypeNAT) ||
(r.tunOptions.AutoRedirectMarkMode && chain.Hooknum == nftables.ChainHookOutput && chain.Type == nftables.ChainTypeNAT)) {
if r.enableIPv4 {
err := r.nftablesCreateDNSHijackRulesForFamily(nft, table, chain, nftables.TableFamilyIPv4)
if err != nil {
return err
}
}
if r.enableIPv6 {
err := r.nftablesCreateDNSHijackRulesForFamily(nft, table, chain, nftables.TableFamilyIPv6)
if err != nil {
return err
}
}
}
if r.tunOptions.AutoRedirectMarkMode &&
((chain.Hooknum == nftables.ChainHookOutput && chain.Type == nftables.ChainTypeRoute) ||
(chain.Hooknum == nftables.ChainHookPrerouting && chain.Type == nftables.ChainTypeFilter)) {
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Meta{
Key: expr.MetaKeyL4PROTO,
Register: 1,
},
&expr.Cmp{
Op: expr.CmpOpNeq,
Register: 1,
Data: []byte{unix.IPPROTO_UDP},
},
&expr.Verdict{
Kind: expr.VerdictReturn,
},
},
})
}
if r.enableIPv4 {
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 5, "inet4_local_address_set", nftables.TableFamilyIPv4, false)
}
if r.enableIPv6 {
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 6, "inet6_local_address_set", nftables.TableFamilyIPv6, false)
}
routeAddressSet := *r.routeAddressSet
routeExcludeAddressSet := *r.routeExcludeAddressSet
if r.enableIPv4 && len(routeAddressSet) > 0 {
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 1, "inet4_route_address_set", nftables.TableFamilyIPv4, true)
}
if r.enableIPv6 && len(routeAddressSet) > 0 {
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 2, "inet6_route_address_set", nftables.TableFamilyIPv6, true)
}
if r.enableIPv4 && len(routeExcludeAddressSet) > 0 {
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 3, "inet4_route_exclude_address_set", nftables.TableFamilyIPv4, false)
}
if r.enableIPv6 && len(routeExcludeAddressSet) > 0 {
nftablesCreateExcludeDestinationIPSet(nft, table, chain, 4, "inet6_route_exclude_address_set", nftables.TableFamilyIPv6, false)
}
return nil
}
func (r *autoRedirect) nftablesCreateMark(nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain) {
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Immediate{
Register: 1,
Data: binaryutil.NativeEndian.PutUint32(r.tunOptions.AutoRedirectInputMark),
},
&expr.Meta{
Key: expr.MetaKeyMARK,
Register: 1,
SourceRegister: true,
},
&expr.Meta{
Key: expr.MetaKeyMARK,
Register: 1,
}, // output meta mark set myMark ct mark set meta mark
&expr.Ct{
Key: expr.CtKeyMARK,
Register: 1,
SourceRegister: true,
},
&expr.Counter{},
},
})
}
func (r *autoRedirect) nftablesCreateRedirect(
nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain,
exprs ...expr.Any,
) {
if r.enableIPv4 && !r.enableIPv6 {
exprs = append(exprs,
&expr.Meta{
Key: expr.MetaKeyNFPROTO,
Register: 1,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: []byte{uint8(nftables.TableFamilyIPv4)},
})
} else if !r.enableIPv4 && r.enableIPv6 {
exprs = append(exprs,
&expr.Meta{
Key: expr.MetaKeyNFPROTO,
Register: 1,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: []byte{uint8(nftables.TableFamilyIPv6)},
})
}
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: append(exprs,
&expr.Meta{
Key: expr.MetaKeyL4PROTO,
Register: 1,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: []byte{unix.IPPROTO_TCP},
},
&expr.Counter{},
&expr.Immediate{
Register: 1,
Data: binaryutil.BigEndian.PutUint16(r.redirectPort()),
},
&expr.Redir{
RegisterProtoMin: 1,
Flags: unix.NF_NAT_RANGE_PROTO_SPECIFIED,
},
&expr.Verdict{
Kind: expr.VerdictReturn,
},
),
})
}
func (r *autoRedirect) nftablesCreateDNSHijackRulesForFamily(
nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain,
family nftables.TableFamily,
) error {
ipProto := &nftables.Set{
Table: table,
Anonymous: true,
Constant: true,
KeyType: nftables.TypeInetProto,
}
err := nft.AddSet(ipProto, []nftables.SetElement{
{Key: []byte{unix.IPPROTO_TCP}},
{Key: []byte{unix.IPPROTO_UDP}},
})
if err != nil {
return err
}
dnsServer := common.Find(r.tunOptions.DNSServers, func(it netip.Addr) bool {
return it.Is4() == (family == nftables.TableFamilyIPv4)
})
if !dnsServer.IsValid() {
if family == nftables.TableFamilyIPv4 {
dnsServer = r.tunOptions.Inet4Address[0].Addr().Next()
} else {
dnsServer = r.tunOptions.Inet6Address[0].Addr().Next()
}
}
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Meta{
Key: expr.MetaKeyNFPROTO,
Register: 1,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: []byte{uint8(family)},
},
&expr.Meta{
Key: expr.MetaKeyL4PROTO,
Register: 1,
},
&expr.Lookup{
SourceRegister: 1,
SetID: ipProto.ID,
SetName: ipProto.Name,
},
&expr.Payload{
OperationType: expr.PayloadLoad,
DestRegister: 1,
Base: expr.PayloadBaseTransportHeader,
Offset: 2,
Len: 2,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: binaryutil.BigEndian.PutUint16(53),
},
&expr.Counter{},
&expr.Immediate{
Register: 1,
Data: dnsServer.AsSlice(),
},
&expr.NAT{
Type: expr.NATTypeDestNAT,
Family: uint32(family),
RegAddrMin: 1,
},
},
})
return nil
}
func (r *autoRedirect) nftablesCreateUnreachable(
nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain,
) {
if (r.enableIPv4 && r.enableIPv6) || !r.tunOptions.StrictRoute {
return
}
var nfProto nftables.TableFamily
if r.enableIPv4 {
nfProto = nftables.TableFamilyIPv6
} else {
nfProto = nftables.TableFamilyIPv4
}
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chain,
Exprs: []expr.Any{
&expr.Meta{
Key: expr.MetaKeyNFPROTO,
Register: 1,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: []byte{uint8(nfProto)},
},
&expr.Counter{},
&expr.Verdict{
Kind: expr.VerdictDrop,
},
},
})
}

View file

@ -0,0 +1,103 @@
//go:build linux
package tun
import (
"github.com/sagernet/nftables"
"github.com/sagernet/nftables/expr"
"golang.org/x/exp/slices"
)
func (r *autoRedirect) configureOpenWRTFirewall4(nft *nftables.Conn, cleanup bool) error {
tableFW4, err := nft.ListTableOfFamily("fw4", nftables.TableFamilyINet)
if err != nil {
return nil
}
if !cleanup {
ruleIif := &nftables.Rule{
Table: tableFW4,
Exprs: []expr.Any{
&expr.Meta{
Key: expr.MetaKeyIIFNAME,
Register: 1,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: nftablesIfname(r.tunOptions.Name),
},
&expr.Counter{},
&expr.Verdict{
Kind: expr.VerdictAccept,
},
},
}
ruleOif := &nftables.Rule{
Table: tableFW4,
Exprs: []expr.Any{
&expr.Meta{
Key: expr.MetaKeyOIFNAME,
Register: 1,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: nftablesIfname(r.tunOptions.Name),
},
&expr.Counter{},
&expr.Verdict{
Kind: expr.VerdictAccept,
},
},
}
chainForward := &nftables.Chain{
Name: "forward",
}
ruleIif.Chain = chainForward
ruleOif.Chain = chainForward
nft.InsertRule(ruleOif)
nft.InsertRule(ruleIif)
chainInput := &nftables.Chain{
Name: "input",
}
ruleIif.Chain = chainInput
ruleOif.Chain = chainInput
nft.InsertRule(ruleOif)
nft.InsertRule(ruleIif)
return nil
}
for _, chainName := range []string{"input", "forward"} {
var rules []*nftables.Rule
rules, err = nft.GetRules(tableFW4, &nftables.Chain{
Name: chainName,
})
if err != nil {
return err
}
for _, rule := range rules {
if len(rule.Exprs) != 4 {
continue
}
exprMeta, isMeta := rule.Exprs[0].(*expr.Meta)
if !isMeta {
continue
}
if exprMeta.Key != expr.MetaKeyIIFNAME && exprMeta.Key != expr.MetaKeyOIFNAME {
continue
}
exprCmp, isCmp := rule.Exprs[1].(*expr.Cmp)
if !isCmp {
continue
}
if !slices.Equal(exprCmp.Data, nftablesIfname(r.tunOptions.Name)) {
continue
}
err = nft.DelRule(rule)
if err != nil {
return err
}
}
}
return nil
}

10
tun.go
View file

@ -41,6 +41,11 @@ type LinuxTUN interface {
TXChecksumOffload() bool TXChecksumOffload() bool
} }
const (
DefaultIPRoute2TableIndex = 2022
DefaultIPRoute2RuleIndex = 9000
)
type Options struct { type Options struct {
Name string Name string
Inet4Address []netip.Prefix Inet4Address []netip.Prefix
@ -49,7 +54,11 @@ type Options struct {
GSO bool GSO bool
AutoRoute bool AutoRoute bool
DNSServers []netip.Addr DNSServers []netip.Addr
IPRoute2TableIndex int
IPRoute2RuleIndex int IPRoute2RuleIndex int
AutoRedirectMarkMode bool
AutoRedirectInputMark uint32
AutoRedirectOutputMark uint32
StrictRoute bool StrictRoute bool
Inet4RouteAddress []netip.Prefix Inet4RouteAddress []netip.Prefix
Inet6RouteAddress []netip.Prefix Inet6RouteAddress []netip.Prefix
@ -63,7 +72,6 @@ type Options struct {
IncludePackage []string IncludePackage []string
ExcludePackage []string ExcludePackage []string
InterfaceMonitor DefaultInterfaceMonitor InterfaceMonitor DefaultInterfaceMonitor
TableIndex int
FileDescriptor int FileDescriptor int
Logger logger.Logger Logger logger.Logger

View file

@ -242,6 +242,9 @@ func configure(tunFd int, ifIndex int, name string, options Options) error {
if options.AutoRoute { if options.AutoRoute {
var routeRanges []netip.Prefix var routeRanges []netip.Prefix
routeRanges, err = options.BuildAutoRouteRanges(false) routeRanges, err = options.BuildAutoRouteRanges(false)
if err != nil {
return err
}
for _, routeRange := range routeRanges { for _, routeRange := range routeRanges {
if routeRange.Addr().Is4() { if routeRange.Addr().Is4() {
err = addRoute(routeRange, options.Inet4Address[0].Addr()) err = addRoute(routeRange, options.Inet4Address[0].Addr())

View file

@ -293,10 +293,10 @@ func (t *NativeTun) configure(tunLink netlink.Link) error {
return err return err
} }
if t.options.TableIndex == 0 { if t.options.IPRoute2TableIndex == 0 {
for { for {
t.options.TableIndex = int(rand.Uint32()) t.options.IPRoute2TableIndex = int(rand.Uint32())
routeList, fErr := netlink.RouteListFiltered(netlink.FAMILY_ALL, &netlink.Route{Table: t.options.TableIndex}, netlink.RT_FILTER_TABLE) routeList, fErr := netlink.RouteListFiltered(netlink.FAMILY_ALL, &netlink.Route{Table: t.options.IPRoute2TableIndex}, netlink.RT_FILTER_TABLE)
if len(routeList) == 0 || fErr != nil { if len(routeList) == 0 || fErr != nil {
break break
} }
@ -354,7 +354,7 @@ func (t *NativeTun) routes(tunLink netlink.Link) ([]netlink.Route, error) {
return netlink.Route{ return netlink.Route{
Dst: prefixToIPNet(it), Dst: prefixToIPNet(it),
LinkIndex: tunLink.Attrs().Index, LinkIndex: tunLink.Attrs().Index,
Table: t.options.TableIndex, Table: t.options.IPRoute2TableIndex,
} }
}), nil }), nil
} }
@ -380,7 +380,7 @@ func (t *NativeTun) rules() []*netlink.Rule {
if len(t.options.Inet6Address) > 0 { if len(t.options.Inet6Address) > 0 {
it := netlink.NewRule() it := netlink.NewRule()
it.Priority = t.nextIndex6() it.Priority = t.nextIndex6()
it.Table = t.options.TableIndex it.Table = t.options.IPRoute2TableIndex
it.Family = unix.AF_INET6 it.Family = unix.AF_INET6
it.OifName = t.options.Name it.OifName = t.options.Name
return []*netlink.Rule{it} return []*netlink.Rule{it}
@ -408,13 +408,62 @@ func (t *NativeTun) rules() []*netlink.Rule {
excludeRanges := t.options.ExcludedRanges() excludeRanges := t.options.ExcludedRanges()
ruleStart := t.options.IPRoute2RuleIndex ruleStart := t.options.IPRoute2RuleIndex
if ruleStart == 0 {
ruleStart = 9000
}
priority := ruleStart priority := ruleStart
priority6 := priority priority6 := priority
nopPriority := ruleStart + 10
if t.options.AutoRedirectMarkMode {
if p4 {
it = netlink.NewRule()
it.Priority = priority
it.Mark = t.options.AutoRedirectOutputMark
it.MarkSet = true
it.Goto = priority + 2
it.Family = unix.AF_INET
rules = append(rules, it)
priority++
it = netlink.NewRule()
it.Priority = priority
it.Mark = t.options.AutoRedirectInputMark
it.MarkSet = true
it.Table = t.options.IPRoute2TableIndex
it.Family = unix.AF_INET
rules = append(rules, it)
priority++
it = netlink.NewRule()
it.Priority = priority
it.Family = unix.AF_INET
rules = append(rules, it)
}
if p6 {
it = netlink.NewRule()
it.Priority = priority6
it.Mark = t.options.AutoRedirectOutputMark
it.MarkSet = true
it.Goto = priority6 + 2
it.Family = unix.AF_INET6
rules = append(rules, it)
priority6++
it = netlink.NewRule()
it.Priority = priority6
it.Mark = t.options.AutoRedirectInputMark
it.MarkSet = true
it.Table = t.options.IPRoute2TableIndex
it.Family = unix.AF_INET6
rules = append(rules, it)
priority6++
it = netlink.NewRule()
it.Priority = priority6
it.Family = unix.AF_INET6
rules = append(rules, it)
}
return rules
}
nopPriority := ruleStart + 10
for _, excludeRange := range excludeRanges { for _, excludeRange := range excludeRanges {
if p4 { if p4 {
it = netlink.NewRule() it = netlink.NewRule()
@ -567,7 +616,7 @@ func (t *NativeTun) rules() []*netlink.Rule {
it = netlink.NewRule() it = netlink.NewRule()
it.Priority = priority it.Priority = priority
it.Dst = address.Masked() it.Dst = address.Masked()
it.Table = t.options.TableIndex it.Table = t.options.IPRoute2TableIndex
it.Family = unix.AF_INET it.Family = unix.AF_INET
rules = append(rules, it) rules = append(rules, it)
} }
@ -575,7 +624,7 @@ func (t *NativeTun) rules() []*netlink.Rule {
it = netlink.NewRule() it = netlink.NewRule()
it.Priority = priority it.Priority = priority
it.Table = t.options.TableIndex it.Table = t.options.IPRoute2TableIndex
it.SuppressPrefixlen = 0 it.SuppressPrefixlen = 0
it.Family = unix.AF_INET it.Family = unix.AF_INET
rules = append(rules, it) rules = append(rules, it)
@ -584,13 +633,13 @@ func (t *NativeTun) rules() []*netlink.Rule {
if p6 { if p6 {
it = netlink.NewRule() it = netlink.NewRule()
it.Priority = priority6 it.Priority = priority6
it.Table = t.options.TableIndex it.Table = t.options.IPRoute2TableIndex
it.SuppressPrefixlen = 0 it.SuppressPrefixlen = 0
it.Family = unix.AF_INET6 it.Family = unix.AF_INET6
rules = append(rules, it) rules = append(rules, it)
priority6++ priority6++
} }
if p4 && !t.options.StrictRoute { if p4 {
it = netlink.NewRule() it = netlink.NewRule()
it.Priority = priority it.Priority = priority
it.Invert = true it.Invert = true
@ -599,16 +648,16 @@ func (t *NativeTun) rules() []*netlink.Rule {
it.SuppressPrefixlen = 0 it.SuppressPrefixlen = 0
it.Family = unix.AF_INET it.Family = unix.AF_INET
rules = append(rules, it) rules = append(rules, it)
}
if p4 && !t.options.StrictRoute {
it = netlink.NewRule() it = netlink.NewRule()
it.Priority = priority it.Priority = priority
it.IPProto = syscall.IPPROTO_ICMP it.IPProto = syscall.IPPROTO_ICMP
it.Goto = nopPriority it.Goto = nopPriority
it.Family = unix.AF_INET it.Family = unix.AF_INET
rules = append(rules, it) rules = append(rules, it)
priority++
} }
if p6 && !t.options.StrictRoute { if p6 {
it = netlink.NewRule() it = netlink.NewRule()
it.Priority = priority6 it.Priority = priority6
it.Invert = true it.Invert = true
@ -617,7 +666,9 @@ func (t *NativeTun) rules() []*netlink.Rule {
it.SuppressPrefixlen = 0 it.SuppressPrefixlen = 0
it.Family = unix.AF_INET6 it.Family = unix.AF_INET6
rules = append(rules, it) rules = append(rules, it)
}
if p6 && !t.options.StrictRoute {
it = netlink.NewRule() it = netlink.NewRule()
it.Priority = priority6 it.Priority = priority6
it.IPProto = syscall.IPPROTO_ICMPV6 it.IPProto = syscall.IPPROTO_ICMPV6
@ -640,7 +691,7 @@ func (t *NativeTun) rules() []*netlink.Rule {
it.Priority = priority it.Priority = priority
it.Invert = true it.Invert = true
it.IifName = "lo" it.IifName = "lo"
it.Table = t.options.TableIndex it.Table = t.options.IPRoute2TableIndex
it.Family = unix.AF_INET it.Family = unix.AF_INET
rules = append(rules, it) rules = append(rules, it)
@ -648,7 +699,7 @@ func (t *NativeTun) rules() []*netlink.Rule {
it.Priority = priority it.Priority = priority
it.IifName = "lo" it.IifName = "lo"
it.Src = netip.PrefixFrom(netip.IPv4Unspecified(), 32) it.Src = netip.PrefixFrom(netip.IPv4Unspecified(), 32)
it.Table = t.options.TableIndex it.Table = t.options.IPRoute2TableIndex
it.Family = unix.AF_INET it.Family = unix.AF_INET
rules = append(rules, it) rules = append(rules, it)
@ -657,24 +708,13 @@ func (t *NativeTun) rules() []*netlink.Rule {
it.Priority = priority it.Priority = priority
it.IifName = "lo" it.IifName = "lo"
it.Src = address.Masked() it.Src = address.Masked()
it.Table = t.options.TableIndex it.Table = t.options.IPRoute2TableIndex
it.Family = unix.AF_INET it.Family = unix.AF_INET
rules = append(rules, it) rules = append(rules, it)
} }
priority++ priority++
} }
if p6 { if p6 {
for _, address := range t.options.Inet6Address {
it = netlink.NewRule()
it.Priority = priority6
it.IifName = "lo"
it.Src = address.Masked()
it.Table = t.options.TableIndex
it.Family = unix.AF_INET6
rules = append(rules, it)
}
priority6++
it = netlink.NewRule() it = netlink.NewRule()
it.Priority = priority6 it.Priority = priority6
it.IifName = t.options.Name it.IifName = t.options.Name
@ -697,12 +737,22 @@ func (t *NativeTun) rules() []*netlink.Rule {
it.Goto = nopPriority it.Goto = nopPriority
it.Family = unix.AF_INET6 it.Family = unix.AF_INET6
rules = append(rules, it) rules = append(rules, it)
priority6++
for _, address := range t.options.Inet6Address {
it = netlink.NewRule()
it.Priority = priority6
it.IifName = "lo"
it.Src = address.Masked()
it.Table = t.options.IPRoute2TableIndex
it.Family = unix.AF_INET6
rules = append(rules, it)
}
priority6++ priority6++
it = netlink.NewRule() it = netlink.NewRule()
it.Priority = priority6 it.Priority = priority6
it.Table = t.options.TableIndex it.Table = t.options.IPRoute2TableIndex
it.Family = unix.AF_INET6 it.Family = unix.AF_INET6
rules = append(rules, it) rules = append(rules, it)
priority6++ priority6++
@ -789,9 +839,6 @@ func (t *NativeTun) unsetRules() error {
} }
for _, rule := range ruleList { for _, rule := range ruleList {
ruleStart := t.options.IPRoute2RuleIndex ruleStart := t.options.IPRoute2RuleIndex
if ruleStart == 0 {
ruleStart = 9000
}
ruleEnd := ruleStart + 10 ruleEnd := ruleStart + 10
if rule.Priority >= ruleStart && rule.Priority <= ruleEnd { if rule.Priority >= ruleStart && rule.Priority <= ruleEnd {
ruleToDel := netlink.NewRule() ruleToDel := netlink.NewRule()