mirror of
https://github.com/SagerNet/sing-tun.git
synced 2025-03-31 10:27:39 +03:00
183 lines
4.6 KiB
Go
183 lines
4.6 KiB
Go
package tun
|
|
|
|
import (
|
|
"context"
|
|
"net/netip"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
|
|
"github.com/sagernet/nftables"
|
|
"github.com/sagernet/sing/common"
|
|
"github.com/sagernet/sing/common/control"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
"github.com/sagernet/sing/common/logger"
|
|
M "github.com/sagernet/sing/common/metadata"
|
|
N "github.com/sagernet/sing/common/network"
|
|
"github.com/sagernet/sing/common/x/list"
|
|
|
|
"go4.org/netipx"
|
|
)
|
|
|
|
type autoRedirect struct {
|
|
tunOptions *Options
|
|
ctx context.Context
|
|
handler N.TCPConnectionHandlerEx
|
|
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
|
|
}
|
|
|
|
func NewAutoRedirect(options AutoRedirectOptions) (AutoRedirect, error) {
|
|
return &autoRedirect{
|
|
tunOptions: options.TunOptions,
|
|
ctx: options.Context,
|
|
handler: options.Handler,
|
|
logger: options.Logger,
|
|
networkMonitor: options.NetworkMonitor,
|
|
interfaceFinder: options.InterfaceFinder,
|
|
tableName: options.TableName,
|
|
useNFTables: runtime.GOOS != "android" && !options.DisableNFTables,
|
|
customRedirectPortFunc: options.CustomRedirectPort,
|
|
routeAddressSet: options.RouteAddressSet,
|
|
routeExcludeAddressSet: options.RouteExcludeAddressSet,
|
|
}, nil
|
|
}
|
|
|
|
func (r *autoRedirect) Start() error {
|
|
var err error
|
|
if runtime.GOOS == "android" {
|
|
r.enableIPv4 = true
|
|
r.iptablesPath = "/system/bin/iptables"
|
|
userId := os.Getuid()
|
|
if userId != 0 {
|
|
r.androidSu = true
|
|
for _, suPath := range []string{
|
|
"su",
|
|
"/system/bin/su",
|
|
} {
|
|
r.suPath, err = exec.LookPath(suPath)
|
|
if err == nil {
|
|
break
|
|
}
|
|
}
|
|
if err != nil {
|
|
return E.Extend(E.Cause(err, "root permission is required for auto redirect"), os.Getenv("PATH"))
|
|
}
|
|
}
|
|
} else {
|
|
if r.useNFTables {
|
|
err = r.initializeNFTables()
|
|
if err != nil && err != os.ErrInvalid {
|
|
r.useNFTables = false
|
|
r.logger.Debug("missing nftables support: ", err)
|
|
}
|
|
}
|
|
if len(r.tunOptions.Inet4Address) > 0 {
|
|
r.enableIPv4 = true
|
|
if !r.useNFTables {
|
|
r.iptablesPath, err = exec.LookPath("iptables")
|
|
if err != nil {
|
|
return E.Cause(err, "iptables is required")
|
|
}
|
|
}
|
|
}
|
|
if len(r.tunOptions.Inet6Address) > 0 {
|
|
r.enableIPv6 = true
|
|
if !r.useNFTables {
|
|
r.ip6tablesPath, err = exec.LookPath("ip6tables")
|
|
if err != nil {
|
|
if !r.enableIPv4 {
|
|
return E.Cause(err, "ip6tables is required")
|
|
} else {
|
|
r.enableIPv6 = false
|
|
r.logger.Error("device has no ip6tables nat support: ", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if r.customRedirectPortFunc != nil {
|
|
r.customRedirectPort = r.customRedirectPortFunc()
|
|
}
|
|
if r.customRedirectPort == 0 {
|
|
var listenAddr netip.Addr
|
|
if runtime.GOOS == "android" {
|
|
listenAddr = netip.AddrFrom4([4]byte{127, 0, 0, 1})
|
|
} else if r.enableIPv6 {
|
|
listenAddr = netip.IPv6Unspecified()
|
|
} else {
|
|
listenAddr = netip.IPv4Unspecified()
|
|
}
|
|
server := newRedirectServer(r.ctx, r.handler, r.logger, listenAddr)
|
|
err := server.Start()
|
|
if err != nil {
|
|
return E.Cause(err, "start redirect server")
|
|
}
|
|
r.redirectServer = server
|
|
}
|
|
if r.useNFTables {
|
|
r.cleanupNFTables()
|
|
err = r.setupNFTables()
|
|
} else {
|
|
r.cleanupIPTables()
|
|
err = r.setupIPTables()
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (r *autoRedirect) Close() error {
|
|
if r.useNFTables {
|
|
r.cleanupNFTables()
|
|
} else {
|
|
r.cleanupIPTables()
|
|
}
|
|
return common.Close(
|
|
common.PtrOrNil(r.redirectServer),
|
|
)
|
|
}
|
|
|
|
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 {
|
|
nft, err := nftables.New()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer nft.CloseLasting()
|
|
_, err = nft.ListTablesOfFamily(nftables.TableFamilyIPv4)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.useNFTables = true
|
|
return nil
|
|
}
|
|
|
|
func (r *autoRedirect) redirectPort() uint16 {
|
|
if r.customRedirectPort > 0 {
|
|
return uint16(r.customRedirectPort)
|
|
}
|
|
return M.AddrPortFromNet(r.redirectServer.listener.Addr()).Port()
|
|
}
|