Files
sing-tun/redirect_linux.go
T

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")
}