Compare commits
2 Commits
v0.8.2
...
sing-vpn-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5485872f60 | ||
|
|
caaf8469e0 |
@@ -19,29 +19,31 @@ import (
|
||||
)
|
||||
|
||||
type autoRedirect struct {
|
||||
tunOptions *Options
|
||||
ctx context.Context
|
||||
handler Handler
|
||||
logger logger.Logger
|
||||
tableName string
|
||||
networkMonitor NetworkUpdateMonitor
|
||||
networkListener *list.Element[NetworkUpdateCallback]
|
||||
interfaceFinder control.InterfaceFinder
|
||||
localAddresses []netip.Prefix
|
||||
customRedirectPortFunc func() int
|
||||
customRedirectPort int
|
||||
redirectServer *redirectServer
|
||||
enableIPv4 bool
|
||||
enableIPv6 bool
|
||||
iptablesPath string
|
||||
ip6tablesPath string
|
||||
useNFTables bool
|
||||
androidSu bool
|
||||
suPath string
|
||||
routeAddressSet *[]*netipx.IPSet
|
||||
routeExcludeAddressSet *[]*netipx.IPSet
|
||||
nfqueueHandler *nfqueueHandler
|
||||
nfqueueEnabled bool
|
||||
tunOptions *Options
|
||||
ctx context.Context
|
||||
handler Handler
|
||||
logger logger.Logger
|
||||
tableName string
|
||||
networkMonitor NetworkUpdateMonitor
|
||||
networkListener *list.Element[NetworkUpdateCallback]
|
||||
interfaceFinder control.InterfaceFinder
|
||||
localAddresses []netip.Prefix
|
||||
customRedirectPortFunc func() int
|
||||
customRedirectPort int
|
||||
redirectServer *redirectServer
|
||||
enableIPv4 bool
|
||||
enableIPv6 bool
|
||||
iptablesPath string
|
||||
ip6tablesPath string
|
||||
useNFTables bool
|
||||
androidSu bool
|
||||
suPath string
|
||||
routeAddressSet *[]*netipx.IPSet
|
||||
routeExcludeAddressSet *[]*netipx.IPSet
|
||||
nfqueueHandler *nfqueueHandler
|
||||
nfqueueEnabled bool
|
||||
redirectRouteTableIndex int
|
||||
redirectInterfaces []control.Interface
|
||||
}
|
||||
|
||||
func NewAutoRedirect(options AutoRedirectOptions) (AutoRedirect, error) {
|
||||
@@ -152,6 +154,12 @@ func (r *autoRedirect) Start() error {
|
||||
}
|
||||
r.cleanupNFTables()
|
||||
err = r.setupNFTables()
|
||||
if err == nil && r.tunOptions.AutoRedirectMarkMode {
|
||||
err = r.setupRedirectRoutes()
|
||||
if err != nil {
|
||||
r.cleanupNFTables()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r.cleanupIPTables()
|
||||
err = r.setupIPTables()
|
||||
@@ -164,6 +172,7 @@ func (r *autoRedirect) Close() error {
|
||||
r.nfqueueHandler.Close()
|
||||
}
|
||||
if r.useNFTables {
|
||||
r.cleanupRedirectRoutes()
|
||||
r.cleanupNFTables()
|
||||
} else {
|
||||
r.cleanupIPTables()
|
||||
|
||||
@@ -293,6 +293,12 @@ func (r *autoRedirect) setupNFTables() error {
|
||||
if err != nil {
|
||||
r.logger.Error("update local address set: ", err)
|
||||
}
|
||||
if r.tunOptions.AutoRedirectMarkMode {
|
||||
err = r.updateRedirectRoutes()
|
||||
if err != nil {
|
||||
r.logger.Error("update redirect routes: ", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
_ "unsafe"
|
||||
|
||||
@@ -373,6 +374,149 @@ func (r *autoRedirect) nftablesCreateExcludeRules(nft *nftables.Conn, table *nft
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(r.tunOptions.IncludeMACAddress) > 0 {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Meta{Key: expr.MetaKeyIIFTYPE, Register: 1},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpNeq,
|
||||
Register: 1,
|
||||
Data: binaryutil.NativeEndian.PutUint16(unix.ARPHRD_ETHER),
|
||||
},
|
||||
&expr.Counter{},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
},
|
||||
},
|
||||
})
|
||||
if len(r.tunOptions.IncludeMACAddress) > 1 {
|
||||
includeMACSet := &nftables.Set{
|
||||
Table: table,
|
||||
Anonymous: true,
|
||||
Constant: true,
|
||||
KeyType: nftables.TypeEtherAddr,
|
||||
}
|
||||
err := nft.AddSet(includeMACSet, common.Map(r.tunOptions.IncludeMACAddress, func(it net.HardwareAddr) nftables.SetElement {
|
||||
return nftables.SetElement{
|
||||
Key: []byte(it),
|
||||
}
|
||||
}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Payload{
|
||||
OperationType: expr.PayloadLoad,
|
||||
DestRegister: 1,
|
||||
Base: expr.PayloadBaseLLHeader,
|
||||
Offset: 6,
|
||||
Len: 6,
|
||||
},
|
||||
&expr.Lookup{
|
||||
SourceRegister: 1,
|
||||
SetID: includeMACSet.ID,
|
||||
SetName: includeMACSet.Name,
|
||||
Invert: true,
|
||||
},
|
||||
&expr.Counter{},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Payload{
|
||||
OperationType: expr.PayloadLoad,
|
||||
DestRegister: 1,
|
||||
Base: expr.PayloadBaseLLHeader,
|
||||
Offset: 6,
|
||||
Len: 6,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpNeq,
|
||||
Register: 1,
|
||||
Data: []byte(r.tunOptions.IncludeMACAddress[0]),
|
||||
},
|
||||
&expr.Counter{},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(r.tunOptions.ExcludeMACAddress) > 0 {
|
||||
if len(r.tunOptions.ExcludeMACAddress) > 1 {
|
||||
excludeMACSet := &nftables.Set{
|
||||
Table: table,
|
||||
Anonymous: true,
|
||||
Constant: true,
|
||||
KeyType: nftables.TypeEtherAddr,
|
||||
}
|
||||
err := nft.AddSet(excludeMACSet, common.Map(r.tunOptions.ExcludeMACAddress, func(it net.HardwareAddr) nftables.SetElement {
|
||||
return nftables.SetElement{
|
||||
Key: []byte(it),
|
||||
}
|
||||
}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Payload{
|
||||
OperationType: expr.PayloadLoad,
|
||||
DestRegister: 1,
|
||||
Base: expr.PayloadBaseLLHeader,
|
||||
Offset: 6,
|
||||
Len: 6,
|
||||
},
|
||||
&expr.Lookup{
|
||||
SourceRegister: 1,
|
||||
SetID: excludeMACSet.ID,
|
||||
SetName: excludeMACSet.Name,
|
||||
},
|
||||
&expr.Counter{},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Payload{
|
||||
OperationType: expr.PayloadLoad,
|
||||
DestRegister: 1,
|
||||
Base: expr.PayloadBaseLLHeader,
|
||||
Offset: 6,
|
||||
Len: 6,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte(r.tunOptions.ExcludeMACAddress[0]),
|
||||
},
|
||||
&expr.Counter{},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if len(r.tunOptions.IncludeUID) > 0 {
|
||||
if len(r.tunOptions.IncludeUID) > 1 || r.tunOptions.IncludeUID[0].Start != r.tunOptions.IncludeUID[0].End {
|
||||
|
||||
174
redirect_route_linux.go
Normal file
174
redirect_route_linux.go
Normal file
@@ -0,0 +1,174 @@
|
||||
//go:build linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/netlink"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const redirectRouteRulePriority = 1
|
||||
|
||||
func (r *autoRedirect) setupRedirectRoutes() error {
|
||||
for {
|
||||
r.redirectRouteTableIndex = int(rand.Uint32())
|
||||
if r.redirectRouteTableIndex == r.tunOptions.IPRoute2TableIndex {
|
||||
continue
|
||||
}
|
||||
routeList, fErr := netlink.RouteListFiltered(netlink.FAMILY_ALL,
|
||||
&netlink.Route{Table: r.redirectRouteTableIndex},
|
||||
netlink.RT_FILTER_TABLE)
|
||||
if len(routeList) == 0 || fErr != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
err := r.interfaceFinder.Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tunName := r.tunOptions.Name
|
||||
r.redirectInterfaces = common.Filter(r.interfaceFinder.Interfaces(), func(it control.Interface) bool {
|
||||
return it.Name != "lo" && it.Name != tunName && it.Flags&net.FlagUp != 0
|
||||
})
|
||||
r.cleanupRedirectRoutes()
|
||||
for _, iface := range r.redirectInterfaces {
|
||||
err = r.addRedirectRoutes(iface.Index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if r.enableIPv4 {
|
||||
rule := netlink.NewRule()
|
||||
rule.Priority = redirectRouteRulePriority
|
||||
rule.Table = r.redirectRouteTableIndex
|
||||
rule.Family = unix.AF_INET
|
||||
err = netlink.RuleAdd(rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if r.enableIPv6 {
|
||||
rule := netlink.NewRule()
|
||||
rule.Priority = redirectRouteRulePriority
|
||||
rule.Table = r.redirectRouteTableIndex
|
||||
rule.Family = unix.AF_INET6
|
||||
err = netlink.RuleAdd(rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *autoRedirect) addRedirectRoutes(linkIndex int) error {
|
||||
if r.enableIPv4 {
|
||||
err := netlink.RouteAppend(&netlink.Route{
|
||||
LinkIndex: linkIndex,
|
||||
Dst: &net.IPNet{IP: net.IPv4(127, 0, 0, 1), Mask: net.CIDRMask(32, 32)},
|
||||
Table: r.redirectRouteTableIndex,
|
||||
Type: unix.RTN_LOCAL,
|
||||
Scope: netlink.SCOPE_HOST,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if r.enableIPv6 {
|
||||
err := netlink.RouteAppend(&netlink.Route{
|
||||
LinkIndex: linkIndex,
|
||||
Dst: &net.IPNet{IP: net.IPv6loopback, Mask: net.CIDRMask(128, 128)},
|
||||
Table: r.redirectRouteTableIndex,
|
||||
Type: unix.RTN_LOCAL,
|
||||
Scope: netlink.SCOPE_HOST,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *autoRedirect) removeRedirectRoutes(linkIndex int) {
|
||||
if r.enableIPv4 {
|
||||
_ = netlink.RouteDel(&netlink.Route{
|
||||
LinkIndex: linkIndex,
|
||||
Dst: &net.IPNet{IP: net.IPv4(127, 0, 0, 1), Mask: net.CIDRMask(32, 32)},
|
||||
Table: r.redirectRouteTableIndex,
|
||||
Type: unix.RTN_LOCAL,
|
||||
})
|
||||
}
|
||||
if r.enableIPv6 {
|
||||
_ = netlink.RouteDel(&netlink.Route{
|
||||
LinkIndex: linkIndex,
|
||||
Dst: &net.IPNet{IP: net.IPv6loopback, Mask: net.CIDRMask(128, 128)},
|
||||
Table: r.redirectRouteTableIndex,
|
||||
Type: unix.RTN_LOCAL,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (r *autoRedirect) updateRedirectRoutes() error {
|
||||
err := r.interfaceFinder.Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tunName := r.tunOptions.Name
|
||||
newInterfaces := common.Filter(r.interfaceFinder.Interfaces(), func(it control.Interface) bool {
|
||||
return it.Name != "lo" && it.Name != tunName && it.Flags&net.FlagUp != 0
|
||||
})
|
||||
oldMap := make(map[int]bool, len(r.redirectInterfaces))
|
||||
for _, iface := range r.redirectInterfaces {
|
||||
oldMap[iface.Index] = true
|
||||
}
|
||||
newMap := make(map[int]bool, len(newInterfaces))
|
||||
for _, iface := range newInterfaces {
|
||||
newMap[iface.Index] = true
|
||||
}
|
||||
for _, iface := range newInterfaces {
|
||||
if !oldMap[iface.Index] {
|
||||
err = r.addRedirectRoutes(iface.Index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, iface := range r.redirectInterfaces {
|
||||
if !newMap[iface.Index] {
|
||||
r.removeRedirectRoutes(iface.Index)
|
||||
}
|
||||
}
|
||||
r.redirectInterfaces = newInterfaces
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *autoRedirect) cleanupRedirectRoutes() {
|
||||
if r.redirectRouteTableIndex == 0 {
|
||||
return
|
||||
}
|
||||
routes, _ := netlink.RouteListFiltered(netlink.FAMILY_ALL,
|
||||
&netlink.Route{Table: r.redirectRouteTableIndex},
|
||||
netlink.RT_FILTER_TABLE)
|
||||
for _, route := range routes {
|
||||
_ = netlink.RouteDel(&route)
|
||||
}
|
||||
if r.enableIPv4 {
|
||||
rule := netlink.NewRule()
|
||||
rule.Priority = redirectRouteRulePriority
|
||||
rule.Table = r.redirectRouteTableIndex
|
||||
rule.Family = unix.AF_INET
|
||||
_ = netlink.RuleDel(rule)
|
||||
}
|
||||
if r.enableIPv6 {
|
||||
rule := netlink.NewRule()
|
||||
rule.Priority = redirectRouteRulePriority
|
||||
rule.Table = r.redirectRouteTableIndex
|
||||
rule.Family = unix.AF_INET6
|
||||
_ = netlink.RuleDel(rule)
|
||||
}
|
||||
}
|
||||
2
tun.go
2
tun.go
@@ -102,6 +102,8 @@ type Options struct {
|
||||
IncludeAndroidUser []int
|
||||
IncludePackage []string
|
||||
ExcludePackage []string
|
||||
IncludeMACAddress []net.HardwareAddr
|
||||
ExcludeMACAddress []net.HardwareAddr
|
||||
InterfaceFinder control.InterfaceFinder
|
||||
InterfaceMonitor DefaultInterfaceMonitor
|
||||
FileDescriptor int
|
||||
|
||||
Reference in New Issue
Block a user