mirror of
https://github.com/SagerNet/sing-tun.git
synced 2026-04-23 00:17:19 +08:00
246 lines
6.5 KiB
Go
246 lines
6.5 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"
|
|
"github.com/sagernet/sing/common/x/list"
|
|
|
|
"go4.org/netipx"
|
|
)
|
|
|
|
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
|
|
redirectRouteTableIndex int
|
|
redirectInterfaces []control.Interface
|
|
}
|
|
|
|
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",
|
|
"/product/bin/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 {
|
|
return E.Cause(err, "missing nftables support")
|
|
}
|
|
}
|
|
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 {
|
|
var handler *nfqueueHandler
|
|
handler, err = newNFQueueHandler(nfqueueOptions{
|
|
Context: r.ctx,
|
|
Handler: r.handler,
|
|
Logger: r.logger,
|
|
Queue: r.effectiveNFQueue(),
|
|
OutputMark: r.effectiveOutputMark(),
|
|
ResetMark: r.effectiveResetMark(),
|
|
})
|
|
if err != nil {
|
|
r.logger.Warn("nfqueue not available, pre-match disabled (missing nfnetlink_queue and nft_queue kernel module?): ", err)
|
|
} else if err = handler.Start(); err != nil {
|
|
r.logger.Warn("nfqueue start failed, pre-match disabled (missing nfnetlink_queue and nft_queue kernel module?): ", err)
|
|
} else {
|
|
r.nfqueueHandler = handler
|
|
r.nfqueueEnabled = true
|
|
}
|
|
r.cleanupNFTables()
|
|
err = r.setupNFTables()
|
|
if err != nil {
|
|
return E.Cause(err, "setup nftables")
|
|
}
|
|
if r.tunOptions.AutoRedirectMarkMode {
|
|
err = r.setupRedirectRoutes()
|
|
if err != nil {
|
|
r.cleanupNFTables()
|
|
return E.Cause(err, "setup redirect routes")
|
|
}
|
|
}
|
|
} else {
|
|
r.cleanupIPTables()
|
|
err = r.setupIPTables()
|
|
if err != nil {
|
|
return E.Cause(err, "setup iptables")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *autoRedirect) Close() error {
|
|
if r.nfqueueHandler != nil {
|
|
r.nfqueueHandler.Close()
|
|
}
|
|
if r.useNFTables {
|
|
r.cleanupRedirectRoutes()
|
|
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()
|
|
}
|
|
|
|
func (r *autoRedirect) effectiveOutputMark() uint32 {
|
|
if r.tunOptions.AutoRedirectOutputMark != 0 {
|
|
return r.tunOptions.AutoRedirectOutputMark
|
|
}
|
|
return DefaultAutoRedirectOutputMark
|
|
}
|
|
|
|
func (r *autoRedirect) effectiveResetMark() uint32 {
|
|
if r.tunOptions.AutoRedirectResetMark != 0 {
|
|
return r.tunOptions.AutoRedirectResetMark
|
|
}
|
|
return DefaultAutoRedirectResetMark
|
|
}
|
|
|
|
func (r *autoRedirect) effectiveNFQueue() uint16 {
|
|
if r.tunOptions.AutoRedirectNFQueue != 0 {
|
|
return r.tunOptions.AutoRedirectNFQueue
|
|
}
|
|
return DefaultAutoRedirectNFQueue
|
|
}
|
|
|
|
func (r *autoRedirect) shouldSkipOutputChain() bool {
|
|
return len(r.tunOptions.IncludeInterface) > 0 && !common.Contains(r.tunOptions.IncludeInterface, "lo") || common.Contains(r.tunOptions.ExcludeInterface, "lo")
|
|
}
|