Compare commits
17 Commits
v0.8.0-bet
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0329538ecd | ||
|
|
caaf8469e0 | ||
|
|
c81ce6d358 | ||
|
|
90ea7a0b69 | ||
|
|
6ee3db839d | ||
|
|
1fb8456021 | ||
|
|
5715a3919a | ||
|
|
2a33d64abc | ||
|
|
3144f43fc2 | ||
|
|
635920688d | ||
|
|
2e21cd99ef | ||
|
|
983b3caf40 | ||
|
|
1d02d635b9 | ||
|
|
e88ed52dbc | ||
|
|
381bf9d40d | ||
|
|
2377e62d4a | ||
|
|
a23b66f721 |
12
go.mod
12
go.mod
@@ -6,25 +6,25 @@ require (
|
||||
github.com/florianl/go-nfqueue/v2 v2.0.2
|
||||
github.com/go-ole/go-ole v1.3.0
|
||||
github.com/google/btree v1.1.3
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/mdlayher/netlink v1.9.0
|
||||
github.com/sagernet/fswatch v0.1.1
|
||||
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
|
||||
github.com/sagernet/nftables v0.3.0-beta.4
|
||||
github.com/sagernet/sing v0.8.0-beta.2
|
||||
github.com/sagernet/sing v0.8.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
|
||||
golang.org/x/net v0.43.0
|
||||
golang.org/x/sys v0.35.0
|
||||
golang.org/x/net v0.50.0
|
||||
golang.org/x/sys v0.41.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/mdlayher/socket v0.5.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
|
||||
12
go.sum
12
go.sum
@@ -10,12 +10,18 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
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/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco=
|
||||
github.com/mdlayher/netlink v1.9.0/go.mod h1:YBnl5BXsCoRuwBjKKlZ+aYmEoq0r12FDA/3JC+94KDg=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
||||
@@ -28,6 +34,8 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen
|
||||
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||
github.com/sagernet/sing v0.8.0-beta.2 h1:3khO2eE5LMylD/v47+pnVMtFzl6lBY2v/b/V+79qpsE=
|
||||
github.com/sagernet/sing v0.8.0-beta.2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing v0.8.0 h1:OwLEwbcYfZHvu4olZVljxxC1XRicBqJ1HfiFr6F2WEE=
|
||||
github.com/sagernet/sing v0.8.0/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
@@ -38,11 +46,15 @@ golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0J
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
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.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -4,6 +4,7 @@ package tun
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/nftables"
|
||||
"github.com/sagernet/nftables/binaryutil"
|
||||
@@ -192,6 +193,25 @@ func (r *autoRedirect) setupNFTables() error {
|
||||
},
|
||||
},
|
||||
})
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRoutingUDP,
|
||||
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.VerdictReturn,
|
||||
},
|
||||
},
|
||||
})
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRoutingUDP,
|
||||
@@ -273,12 +293,22 @@ 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
|
||||
}
|
||||
|
||||
// TODO: test if this works
|
||||
func (r *autoRedirect) nftablesUpdateLocalAddressSet() error {
|
||||
err := r.interfaceFinder.Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newLocalAddresses := common.FlatMap(r.interfaceFinder.Interfaces(), func(it control.Interface) []netip.Prefix {
|
||||
return common.Filter(it.Addresses, func(prefix netip.Prefix) bool {
|
||||
return it.Name == "lo" || prefix.Addr().IsGlobalUnicast()
|
||||
@@ -287,6 +317,11 @@ func (r *autoRedirect) nftablesUpdateLocalAddressSet() error {
|
||||
if slices.Equal(newLocalAddresses, r.localAddresses) {
|
||||
return nil
|
||||
}
|
||||
if r.logger != nil {
|
||||
r.logger.Debug("updating local address set to [", strings.Join(common.Map(newLocalAddresses, func(it netip.Prefix) string {
|
||||
return it.String()
|
||||
}), ", ")+"]")
|
||||
}
|
||||
nft, err := nftables.New()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -74,12 +74,11 @@ func (r *autoRedirect) nftablesCreateLocalAddressSets(
|
||||
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) {
|
||||
if !slices.Equal(localAddresses4, common.Filter(lastAddresses, func(it netip.Prefix) bool {
|
||||
return it.Addr().Is4()
|
||||
})) {
|
||||
update = true
|
||||
}
|
||||
}
|
||||
@@ -94,19 +93,14 @@ func (r *autoRedirect) nftablesCreateLocalAddressSets(
|
||||
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) {
|
||||
if !slices.Equal(localAddresses6, common.Filter(lastAddresses, func(it netip.Prefix) bool {
|
||||
return it.Addr().Is6()
|
||||
})) {
|
||||
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 {
|
||||
|
||||
179
redirect_route_linux.go
Normal file
179
redirect_route_linux.go
Normal file
@@ -0,0 +1,179 @@
|
||||
//go:build linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"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)
|
||||
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(iface control.Interface) error {
|
||||
if r.enableIPv4 && common.Any(iface.Addresses, func(it netip.Prefix) bool {
|
||||
return it.Addr().Is4()
|
||||
}) {
|
||||
err := netlink.RouteAppend(&netlink.Route{
|
||||
LinkIndex: iface.Index,
|
||||
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 && common.Any(iface.Addresses, func(it netip.Prefix) bool {
|
||||
return it.Addr().Is6() && !it.Addr().Is4In6()
|
||||
}) {
|
||||
err := netlink.RouteAppend(&netlink.Route{
|
||||
LinkIndex: iface.Index,
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,9 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
gHdr "github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
@@ -169,7 +172,7 @@ func (m *Mixed) batchLoopDarwin(darwinTUN DarwinTUN) {
|
||||
for {
|
||||
buffers, err := darwinTUN.BatchRead()
|
||||
if err != nil {
|
||||
if E.IsClosed(err) {
|
||||
if E.IsClosed(err) || errors.Is(err, syscall.EBADF) {
|
||||
return
|
||||
}
|
||||
m.logger.Error(E.Cause(err, "batch read packet"))
|
||||
|
||||
@@ -269,7 +269,7 @@ func (s *System) batchLoopDarwin(darwinTUN DarwinTUN) {
|
||||
for {
|
||||
buffers, err := darwinTUN.BatchRead()
|
||||
if err != nil {
|
||||
if E.IsClosed(err) {
|
||||
if E.IsClosed(err) || errors.Is(err, syscall.EBADF) {
|
||||
return
|
||||
}
|
||||
s.logger.Error(E.Cause(err, "batch read packet"))
|
||||
|
||||
78
tun.go
78
tun.go
@@ -63,47 +63,49 @@ type DarwinTUN interface {
|
||||
}
|
||||
|
||||
const (
|
||||
DefaultIPRoute2TableIndex = 2022
|
||||
DefaultIPRoute2RuleIndex = 9000
|
||||
DefaultIPRoute2TableIndex = 2022
|
||||
DefaultIPRoute2RuleIndex = 9000
|
||||
DefaultIPRoute2AutoRedirectFallbackRuleIndex = 32768
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Name string
|
||||
Inet4Address []netip.Prefix
|
||||
Inet6Address []netip.Prefix
|
||||
MTU uint32
|
||||
GSO bool
|
||||
AutoRoute bool
|
||||
InterfaceScope bool
|
||||
Inet4Gateway netip.Addr
|
||||
Inet6Gateway netip.Addr
|
||||
DNSServers []netip.Addr
|
||||
IPRoute2TableIndex int
|
||||
IPRoute2RuleIndex int
|
||||
AutoRedirectMarkMode bool
|
||||
AutoRedirectInputMark uint32
|
||||
AutoRedirectOutputMark uint32
|
||||
AutoRedirectResetMark uint32
|
||||
AutoRedirectNFQueue uint16
|
||||
ExcludeMPTCP bool
|
||||
Inet4LoopbackAddress []netip.Addr
|
||||
Inet6LoopbackAddress []netip.Addr
|
||||
StrictRoute bool
|
||||
Inet4RouteAddress []netip.Prefix
|
||||
Inet6RouteAddress []netip.Prefix
|
||||
Inet4RouteExcludeAddress []netip.Prefix
|
||||
Inet6RouteExcludeAddress []netip.Prefix
|
||||
IncludeInterface []string
|
||||
ExcludeInterface []string
|
||||
IncludeUID []ranges.Range[uint32]
|
||||
ExcludeUID []ranges.Range[uint32]
|
||||
IncludeAndroidUser []int
|
||||
IncludePackage []string
|
||||
ExcludePackage []string
|
||||
InterfaceFinder control.InterfaceFinder
|
||||
InterfaceMonitor DefaultInterfaceMonitor
|
||||
FileDescriptor int
|
||||
Logger logger.Logger
|
||||
Name string
|
||||
Inet4Address []netip.Prefix
|
||||
Inet6Address []netip.Prefix
|
||||
MTU uint32
|
||||
GSO bool
|
||||
AutoRoute bool
|
||||
InterfaceScope bool
|
||||
Inet4Gateway netip.Addr
|
||||
Inet6Gateway netip.Addr
|
||||
DNSServers []netip.Addr
|
||||
IPRoute2TableIndex int
|
||||
IPRoute2RuleIndex int
|
||||
IPRoute2AutoRedirectFallbackRuleIndex int
|
||||
AutoRedirectMarkMode bool
|
||||
AutoRedirectInputMark uint32
|
||||
AutoRedirectOutputMark uint32
|
||||
AutoRedirectResetMark uint32
|
||||
AutoRedirectNFQueue uint16
|
||||
ExcludeMPTCP bool
|
||||
Inet4LoopbackAddress []netip.Addr
|
||||
Inet6LoopbackAddress []netip.Addr
|
||||
StrictRoute bool
|
||||
Inet4RouteAddress []netip.Prefix
|
||||
Inet6RouteAddress []netip.Prefix
|
||||
Inet4RouteExcludeAddress []netip.Prefix
|
||||
Inet6RouteExcludeAddress []netip.Prefix
|
||||
IncludeInterface []string
|
||||
ExcludeInterface []string
|
||||
IncludeUID []ranges.Range[uint32]
|
||||
ExcludeUID []ranges.Range[uint32]
|
||||
IncludeAndroidUser []int
|
||||
IncludePackage []string
|
||||
ExcludePackage []string
|
||||
InterfaceFinder control.InterfaceFinder
|
||||
InterfaceMonitor DefaultInterfaceMonitor
|
||||
FileDescriptor int
|
||||
Logger logger.Logger
|
||||
|
||||
// No work for TCP, do not use.
|
||||
_TXChecksumOffload bool
|
||||
|
||||
45
tun_linux.go
45
tun_linux.go
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/sagernet/sing-tun/internal/gtcpip/checksum"
|
||||
"github.com/sagernet/sing-tun/internal/gtcpip/header"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
@@ -40,6 +39,7 @@ type NativeTun struct {
|
||||
writeAccess sync.Mutex
|
||||
vnetHdr bool
|
||||
writeBuffer []byte
|
||||
vnetHdrWriteBuf []byte
|
||||
gsoToWrite []int
|
||||
tcpGROTable *tcpGROTable
|
||||
udpGroAccess sync.Mutex
|
||||
@@ -149,7 +149,9 @@ func (t *NativeTun) configure(tunLink netlink.Link) error {
|
||||
if t.options.GSO {
|
||||
err = t.enableGSO()
|
||||
if err != nil {
|
||||
t.options.Logger.Warn(err)
|
||||
if t.options.Logger != nil {
|
||||
t.options.Logger.Warn(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +278,9 @@ func (t *NativeTun) Start() error {
|
||||
if err != nil {
|
||||
t.gro.disableTCPGRO()
|
||||
t.gro.disableUDPGRO()
|
||||
t.options.Logger.Warn(E.Cause(err, "disabled TUN TCP & UDP GRO due to GRO probe error"))
|
||||
if t.options.Logger != nil {
|
||||
t.options.Logger.Warn(E.Cause(err, "disabled TUN TCP & UDP GRO due to GRO probe error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,6 +329,7 @@ func (t *NativeTun) Close() error {
|
||||
if t.options.EXP_ExternalConfiguration {
|
||||
return common.Close(common.PtrOrNil(t.tunFile))
|
||||
}
|
||||
t.unsetSearchDomainForSystemdResolved()
|
||||
t.unsetAddresses()
|
||||
return E.Errors(t.unsetRoute(), t.unsetRules(), common.Close(common.PtrOrNil(t.tunFile)))
|
||||
}
|
||||
@@ -393,10 +398,7 @@ func handleVirtioRead(in []byte, bufs [][]byte, sizes []int, offset int) (int, e
|
||||
|
||||
func (t *NativeTun) Write(p []byte) (n int, err error) {
|
||||
if t.vnetHdr {
|
||||
buffer := buf.Get(virtioNetHdrLen + len(p))
|
||||
copy(buffer[virtioNetHdrLen:], p)
|
||||
_, err = t.BatchWrite([][]byte{buffer}, virtioNetHdrLen)
|
||||
buf.Put(buffer)
|
||||
_, err = t.BatchWrite([][]byte{p}, virtioNetHdrLen)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -630,6 +632,22 @@ func (t *NativeTun) rules() []*netlink.Rule {
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
}
|
||||
// Fallback rules after system default rules (32766: main, 32767: default)
|
||||
// Only reached when main and default tables have no route
|
||||
if p4 {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = t.options.IPRoute2AutoRedirectFallbackRuleIndex
|
||||
it.Table = t.options.IPRoute2TableIndex
|
||||
it.Family = unix.AF_INET
|
||||
rules = append(rules, it)
|
||||
}
|
||||
if p6 {
|
||||
it = netlink.NewRule()
|
||||
it.Priority = t.options.IPRoute2AutoRedirectFallbackRuleIndex
|
||||
it.Table = t.options.IPRoute2TableIndex
|
||||
it.Family = unix.AF_INET6
|
||||
rules = append(rules, it)
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
@@ -1003,7 +1021,7 @@ func (t *NativeTun) unsetRules() error {
|
||||
for _, rule := range ruleList {
|
||||
ruleStart := t.options.IPRoute2RuleIndex
|
||||
ruleEnd := ruleStart + 10
|
||||
if rule.Priority >= ruleStart && rule.Priority <= ruleEnd {
|
||||
if rule.Priority >= ruleStart && rule.Priority <= ruleEnd || (t.options.AutoRedirectMarkMode && rule.Priority == t.options.IPRoute2AutoRedirectFallbackRuleIndex) {
|
||||
ruleToDel := netlink.NewRule()
|
||||
ruleToDel.Family = rule.Family
|
||||
ruleToDel.Priority = rule.Priority
|
||||
@@ -1078,3 +1096,14 @@ func (t *NativeTun) setSearchDomainForSystemdResolved() {
|
||||
_ = shell.Exec(ctlPath, append([]string{"dns", t.options.Name}, common.Map(dnsServer, netip.Addr.String)...)...).Run()
|
||||
}()
|
||||
}
|
||||
|
||||
func (t *NativeTun) unsetSearchDomainForSystemdResolved() {
|
||||
if t.options.EXP_DisableDNSHijack {
|
||||
return
|
||||
}
|
||||
ctlPath, err := exec.LookPath("resolvectl")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = shell.Exec(ctlPath, "revert", t.options.Name).Run()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/rawfile"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/link/fdbased"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
@@ -18,6 +20,37 @@ var _ GVisorTun = (*NativeTun)(nil)
|
||||
|
||||
func (t *NativeTun) WritePacket(pkt *stack.PacketBuffer) (int, error) {
|
||||
iovecs := t.iovecsOutputDefault
|
||||
if t.vnetHdr {
|
||||
if t.vnetHdrWriteBuf == nil {
|
||||
t.vnetHdrWriteBuf = make([]byte, virtioNetHdrLen)
|
||||
}
|
||||
vnetHdr := virtioNetHdr{}
|
||||
if pkt.GSOOptions.Type != stack.GSONone {
|
||||
vnetHdr.hdrLen = uint16(pkt.HeaderSize())
|
||||
if pkt.GSOOptions.NeedsCsum {
|
||||
vnetHdr.flags = unix.VIRTIO_NET_HDR_F_NEEDS_CSUM
|
||||
vnetHdr.csumStart = pkt.GSOOptions.L3HdrLen
|
||||
vnetHdr.csumOffset = pkt.GSOOptions.CsumOffset
|
||||
}
|
||||
if uint16(pkt.Data().Size()) > pkt.GSOOptions.MSS {
|
||||
switch pkt.GSOOptions.Type {
|
||||
case stack.GSOTCPv4:
|
||||
vnetHdr.gsoType = unix.VIRTIO_NET_HDR_GSO_TCPV4
|
||||
case stack.GSOTCPv6:
|
||||
vnetHdr.gsoType = unix.VIRTIO_NET_HDR_GSO_TCPV6
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown gso type: %v", pkt.GSOOptions.Type))
|
||||
}
|
||||
vnetHdr.gsoSize = pkt.GSOOptions.MSS
|
||||
}
|
||||
}
|
||||
if err := vnetHdr.encode(t.vnetHdrWriteBuf); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
iovec := unix.Iovec{Base: &t.vnetHdrWriteBuf[0]}
|
||||
iovec.SetLen(virtioNetHdrLen)
|
||||
iovecs = append(iovecs, iovec)
|
||||
}
|
||||
var dataLen int
|
||||
for _, packetSlice := range pkt.AsSlices() {
|
||||
dataLen += len(packetSlice)
|
||||
|
||||
@@ -184,6 +184,13 @@ func (t *NativeTun) Start() error {
|
||||
return err
|
||||
}
|
||||
if t.options.StrictRoute {
|
||||
major, _, _ := windows.RtlGetNtVersionNumbers()
|
||||
if major < 10 {
|
||||
if t.options.Logger != nil {
|
||||
t.options.Logger.Warn("strict routing is not supported on Windows versions below 10")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var engine uintptr
|
||||
session := &winsys.FWPM_SESSION0{Flags: winsys.FWPM_SESSION_FLAG_DYNAMIC}
|
||||
err := winsys.FwpmEngineOpen0(nil, winsys.RPC_C_AUTHN_DEFAULT, nil, session, unsafe.Pointer(&engine))
|
||||
|
||||
Reference in New Issue
Block a user