mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-06-11 22:08:16 +03:00
Add nftables support for auto-redirect
This commit is contained in:
@@ -208,7 +208,7 @@ func (t *Tun) Start() error {
|
||||
}
|
||||
if t.autoRedirect != nil {
|
||||
monitor.Start("initiating auto redirect")
|
||||
err = t.autoRedirect.Start(t.tunOptions.Name)
|
||||
err = t.autoRedirect.Start()
|
||||
monitor.Finish()
|
||||
if err != nil {
|
||||
return E.Cause(err, "auto redirect")
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build linux
|
||||
|
||||
package inbound
|
||||
|
||||
import (
|
||||
@@ -6,51 +8,35 @@ import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"strconv"
|
||||
|
||||
"github.com/sagernet/nftables"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/redir"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const (
|
||||
tableNameOutput = "sing-box-output"
|
||||
tableNameForward = "sing-box-forward"
|
||||
tableNamePreRouteing = "sing-box-prerouting"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type tunAutoRedirect struct {
|
||||
myInboundAdapter
|
||||
tunOptions *tun.Options
|
||||
interfaceFinder control.InterfaceFinder
|
||||
networkMonitor tun.NetworkUpdateMonitor
|
||||
networkCallback *list.Element[tun.NetworkUpdateCallback]
|
||||
enableIPv4 bool
|
||||
enableIPv6 bool
|
||||
localAddresses4 []netip.Prefix
|
||||
localAddresses6 []netip.Prefix
|
||||
iptablesPath string
|
||||
ip6tablesPath string
|
||||
androidSu bool
|
||||
suPath string
|
||||
tunOptions *tun.Options
|
||||
enableIPv4 bool
|
||||
enableIPv6 bool
|
||||
iptablesPath string
|
||||
ip6tablesPath string
|
||||
useNfTables bool
|
||||
androidSu bool
|
||||
suPath string
|
||||
}
|
||||
|
||||
func newAutoRedirect(t *Tun) (*tunAutoRedirect, error) {
|
||||
if !C.IsLinux {
|
||||
return nil, E.New("only supported on linux")
|
||||
}
|
||||
server := &tunAutoRedirect{
|
||||
s := &tunAutoRedirect{
|
||||
myInboundAdapter: myInboundAdapter{
|
||||
protocol: C.TypeRedirect,
|
||||
network: []string{N.NetworkTCP},
|
||||
@@ -62,160 +48,105 @@ func newAutoRedirect(t *Tun) (*tunAutoRedirect, error) {
|
||||
InboundOptions: t.inboundOptions,
|
||||
},
|
||||
},
|
||||
tunOptions: &t.tunOptions,
|
||||
interfaceFinder: t.router.InterfaceFinder(),
|
||||
networkMonitor: t.router.NetworkMonitor(),
|
||||
tunOptions: &t.tunOptions,
|
||||
}
|
||||
server.connHandler = server
|
||||
if len(t.tunOptions.Inet4Address) > 0 {
|
||||
server.enableIPv4 = true
|
||||
if C.IsAndroid {
|
||||
server.iptablesPath = "/system/bin/iptables"
|
||||
userId := os.Getuid()
|
||||
if userId != 0 {
|
||||
var (
|
||||
suPath string
|
||||
err error
|
||||
)
|
||||
if t.platformInterface != nil {
|
||||
suPath, err = exec.LookPath("/bin/su")
|
||||
} else {
|
||||
suPath, err = exec.LookPath("su")
|
||||
}
|
||||
if err == nil {
|
||||
server.androidSu = true
|
||||
server.suPath = suPath
|
||||
} else {
|
||||
return nil, E.Extend(E.Cause(err, "root permission is required for auto redirect"), os.Getenv("PATH"))
|
||||
}
|
||||
s.connHandler = s
|
||||
|
||||
if C.IsAndroid {
|
||||
s.enableIPv4 = true
|
||||
s.iptablesPath = "/system/bin/iptables"
|
||||
userId := os.Getuid()
|
||||
if userId != 0 {
|
||||
var (
|
||||
suPath string
|
||||
err error
|
||||
)
|
||||
if t.platformInterface != nil {
|
||||
suPath, err = exec.LookPath("/bin/su")
|
||||
} else {
|
||||
suPath, err = exec.LookPath("su")
|
||||
}
|
||||
} else {
|
||||
iptablesPath, err := exec.LookPath("iptables")
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "iptables is required")
|
||||
if err == nil {
|
||||
s.androidSu = true
|
||||
s.suPath = suPath
|
||||
} else {
|
||||
return nil, E.Extend(E.Cause(err, "root permission is required for auto redirect"), os.Getenv("PATH"))
|
||||
}
|
||||
server.iptablesPath = iptablesPath
|
||||
}
|
||||
}
|
||||
if !C.IsAndroid && len(t.tunOptions.Inet6Address) > 0 {
|
||||
err := server.initializeIP6Tables()
|
||||
if err != nil {
|
||||
t.logger.Debug("device has no ip6tables nat support: ", err)
|
||||
} else {
|
||||
err := s.initializeNfTables()
|
||||
if err != nil && err != os.ErrInvalid {
|
||||
t.logger.Debug("device has no nftables support: ", err)
|
||||
}
|
||||
if len(t.tunOptions.Inet4Address) > 0 {
|
||||
s.enableIPv4 = true
|
||||
if !s.useNfTables {
|
||||
s.iptablesPath, err = exec.LookPath("iptables")
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "iptables is required")
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(t.tunOptions.Inet6Address) > 0 {
|
||||
s.enableIPv6 = true
|
||||
if !s.useNfTables {
|
||||
s.ip6tablesPath, err = exec.LookPath("ip6tables")
|
||||
if err != nil {
|
||||
if !s.enableIPv4 {
|
||||
return nil, E.Cause(err, "ip6tables is required")
|
||||
} else {
|
||||
s.enableIPv6 = false
|
||||
t.logger.Error("device has no ip6tables nat support: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var listenAddr netip.Addr
|
||||
if C.IsAndroid {
|
||||
listenAddr = netip.AddrFrom4([4]byte{127, 0, 0, 1})
|
||||
} else if server.enableIPv6 {
|
||||
} else if s.enableIPv6 {
|
||||
listenAddr = netip.IPv6Unspecified()
|
||||
} else {
|
||||
listenAddr = netip.IPv4Unspecified()
|
||||
}
|
||||
server.listenOptions.Listen = option.NewListenAddress(listenAddr)
|
||||
return server, nil
|
||||
s.listenOptions.Listen = option.NewListenAddress(listenAddr)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) initializeIP6Tables() error {
|
||||
ip6tablesPath, err := exec.LookPath("ip6tables")
|
||||
func (t *tunAutoRedirect) initializeNfTables() error {
|
||||
disabled, err := strconv.ParseBool(os.Getenv("AUTO_REDIRECT_DISABLE_NFTABLES"))
|
||||
if err == nil && disabled {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
nft, err := nftables.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
/*output, err := exec.Command(ip6tablesPath, "-t nat -L", tableNameOutput).CombinedOutput()
|
||||
switch exitErr := err.(type) {
|
||||
case nil:
|
||||
case *exec.ExitError:
|
||||
if exitErr.ExitCode() != 1 {
|
||||
return E.Extend(err, string(output))
|
||||
}
|
||||
default:
|
||||
defer nft.CloseLasting()
|
||||
_, err = nft.ListTablesOfFamily(unix.AF_INET)
|
||||
if err != nil {
|
||||
return err
|
||||
}*/
|
||||
t.ip6tablesPath = ip6tablesPath
|
||||
t.enableIPv6 = true
|
||||
}
|
||||
t.useNfTables = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) Start(tunName string) error {
|
||||
func (t *tunAutoRedirect) Start() error {
|
||||
err := t.myInboundAdapter.Start()
|
||||
if err != nil {
|
||||
return E.Cause(err, "start redirect server")
|
||||
}
|
||||
if t.enableIPv4 {
|
||||
t.cleanupIPTables(t.iptablesPath)
|
||||
}
|
||||
if t.enableIPv6 {
|
||||
t.cleanupIPTables(t.ip6tablesPath)
|
||||
}
|
||||
err = t.updateInterfaces(false)
|
||||
t.cleanupTables()
|
||||
err = t.setupTables()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if t.enableIPv4 {
|
||||
err = t.setupIPTables(t.iptablesPath, tunName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if t.enableIPv6 {
|
||||
err = t.setupIPTables(t.ip6tablesPath, tunName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
t.networkCallback = t.networkMonitor.RegisterCallback(func() {
|
||||
rErr := t.updateInterfaces(true)
|
||||
if rErr != nil {
|
||||
t.logger.Error("recreate prerouting rules: ", rErr)
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) updateInterfaces(recreate bool) error {
|
||||
addresses := common.Filter(common.FlatMap(common.Filter(t.interfaceFinder.Interfaces(), func(it control.Interface) bool {
|
||||
return it.Name != t.tunOptions.Name
|
||||
}), func(it control.Interface) []netip.Prefix {
|
||||
return it.Addresses
|
||||
}), func(it netip.Prefix) bool {
|
||||
address := it.Addr()
|
||||
return !(address.IsLoopback() || address.IsLinkLocalUnicast())
|
||||
})
|
||||
oldLocalAddresses4 := t.localAddresses4
|
||||
oldLocalAddresses6 := t.localAddresses6
|
||||
localAddresses4 := common.Filter(addresses, func(it netip.Prefix) bool { return it.Addr().Is4() })
|
||||
localAddresses6 := common.Filter(addresses, func(it netip.Prefix) bool { return it.Addr().Is6() })
|
||||
t.localAddresses4 = localAddresses4
|
||||
t.localAddresses6 = localAddresses6
|
||||
if !recreate || t.androidSu {
|
||||
return nil
|
||||
}
|
||||
if t.enableIPv4 {
|
||||
if !slices.Equal(localAddresses4, oldLocalAddresses4) {
|
||||
err := t.setupIPTablesPreRouting(t.iptablesPath, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if t.enableIPv6 {
|
||||
if !slices.Equal(localAddresses6, oldLocalAddresses6) {
|
||||
err := t.setupIPTablesPreRouting(t.ip6tablesPath, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) Close() error {
|
||||
t.networkMonitor.UnregisterCallback(t.networkCallback)
|
||||
if t.enableIPv4 {
|
||||
t.cleanupIPTables(t.iptablesPath)
|
||||
}
|
||||
if t.enableIPv6 {
|
||||
t.cleanupIPTables(t.ip6tablesPath)
|
||||
}
|
||||
t.cleanupTables()
|
||||
return t.myInboundAdapter.Close()
|
||||
}
|
||||
|
||||
@@ -228,44 +159,21 @@ func (t *tunAutoRedirect) NewConnection(ctx context.Context, conn net.Conn, meta
|
||||
return t.newConnection(ctx, conn, metadata)
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) setupIPTables(iptablesPath string, tunName string) error {
|
||||
// OUTPUT
|
||||
err := t.runShell(iptablesPath, "-t nat -N", tableNameOutput)
|
||||
if err != nil {
|
||||
return err
|
||||
func (t *tunAutoRedirect) setupTables() error {
|
||||
var setupTables func(int) error
|
||||
if t.useNfTables {
|
||||
setupTables = t.setupNfTables
|
||||
} else {
|
||||
setupTables = t.setupIPTables
|
||||
}
|
||||
err = t.runShell(iptablesPath, "-t nat -A", tableNameOutput,
|
||||
"-p tcp -o", tunName,
|
||||
"-j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port())
|
||||
if err != nil {
|
||||
return err
|
||||
if t.enableIPv4 {
|
||||
err := setupTables(unix.AF_INET)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = t.runShell(iptablesPath, "-t nat -I OUTPUT -j", tableNameOutput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !t.androidSu {
|
||||
// FORWARD
|
||||
err = t.runShell(iptablesPath, "-N", tableNameForward)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = t.runShell(iptablesPath, "-A", tableNameForward,
|
||||
"-i", tunName, "-j", "ACCEPT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = t.runShell(iptablesPath, "-A", tableNameForward,
|
||||
"-o", tunName, "-j", "ACCEPT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = t.runShell(iptablesPath, "-I FORWARD -j", tableNameForward)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// PREROUTING
|
||||
err = t.setupIPTablesPreRouting(iptablesPath, false)
|
||||
if t.enableIPv6 {
|
||||
err := setupTables(unix.AF_INET6)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -273,134 +181,17 @@ func (t *tunAutoRedirect) setupIPTables(iptablesPath string, tunName string) err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) setupIPTablesPreRouting(iptablesPath string, recreate bool) error {
|
||||
var err error
|
||||
if !recreate {
|
||||
err = t.runShell(iptablesPath, "-t nat -N", tableNamePreRouteing)
|
||||
func (t *tunAutoRedirect) cleanupTables() {
|
||||
var cleanupTables func(int)
|
||||
if t.useNfTables {
|
||||
cleanupTables = t.cleanupNfTables
|
||||
} else {
|
||||
err = t.runShell(iptablesPath, "-t nat -F", tableNamePreRouteing)
|
||||
cleanupTables = t.cleanupIPTables
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
if t.enableIPv4 {
|
||||
cleanupTables(unix.AF_INET)
|
||||
}
|
||||
var (
|
||||
routeAddress []netip.Prefix
|
||||
routeExcludeAddress []netip.Prefix
|
||||
)
|
||||
if t.iptablesPath == iptablesPath {
|
||||
routeAddress = t.tunOptions.Inet4RouteAddress
|
||||
routeExcludeAddress = t.tunOptions.Inet4RouteExcludeAddress
|
||||
} else {
|
||||
routeAddress = t.tunOptions.Inet6RouteAddress
|
||||
routeExcludeAddress = t.tunOptions.Inet6RouteExcludeAddress
|
||||
}
|
||||
if len(routeAddress) > 0 && (len(t.tunOptions.IncludeInterface) > 0 || len(t.tunOptions.IncludeUID) > 0) {
|
||||
return E.New("`*_route_address` is conflict with `include_interface` or `include_uid`")
|
||||
}
|
||||
if len(routeExcludeAddress) > 0 {
|
||||
for _, address := range routeExcludeAddress {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-d", address.String(), "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(t.tunOptions.ExcludeInterface) > 0 {
|
||||
for _, name := range t.tunOptions.ExcludeInterface {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-i", name, "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(t.tunOptions.ExcludeUID) > 0 {
|
||||
for _, uid := range t.tunOptions.ExcludeUID {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-m owner --uid-owner", uid, "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
var addresses []netip.Prefix
|
||||
if t.iptablesPath == iptablesPath {
|
||||
addresses = t.localAddresses4
|
||||
} else {
|
||||
addresses = t.localAddresses6
|
||||
}
|
||||
for _, address := range addresses {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing, "-d", address.String(), "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(routeAddress) > 0 {
|
||||
for _, address := range routeAddress {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-d", address.String(), "-p tcp -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if len(t.tunOptions.IncludeInterface) > 0 || len(t.tunOptions.IncludeUID) > 0 {
|
||||
for _, name := range t.tunOptions.IncludeInterface {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-i", name, "-p tcp -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, uidRange := range t.tunOptions.IncludeUID {
|
||||
for i := uidRange.Start; i <= uidRange.End; i++ {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-m owner --uid-owner", i, "-p tcp -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing,
|
||||
"-p tcp -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = t.runShell(iptablesPath, "-t nat -I PREROUTING -j", tableNamePreRouteing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) cleanupIPTables(iptablesPath string) {
|
||||
_ = t.runShell(iptablesPath, "-t nat -D OUTPUT -j", tableNameOutput)
|
||||
_ = t.runShell(iptablesPath, "-t nat -F", tableNameOutput)
|
||||
_ = t.runShell(iptablesPath, "-t nat -X", tableNameOutput)
|
||||
if !t.androidSu {
|
||||
_ = t.runShell(iptablesPath, "-D FORWARD -j", tableNameForward)
|
||||
_ = t.runShell(iptablesPath, "-F", tableNameForward)
|
||||
_ = t.runShell(iptablesPath, "-X", tableNameForward)
|
||||
_ = t.runShell(iptablesPath, "-t nat -D PREROUTING -j", tableNamePreRouteing)
|
||||
_ = t.runShell(iptablesPath, "-t nat -F", tableNamePreRouteing)
|
||||
_ = t.runShell(iptablesPath, "-t nat -X", tableNamePreRouteing)
|
||||
if t.enableIPv6 {
|
||||
cleanupTables(unix.AF_INET6)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) runShell(commands ...any) error {
|
||||
commandStr := strings.Join(F.MapToString(commands), " ")
|
||||
var command *exec.Cmd
|
||||
if t.androidSu {
|
||||
command = exec.Command(t.suPath, "-c", commandStr)
|
||||
} else {
|
||||
commandArray := strings.Split(commandStr, " ")
|
||||
command = exec.Command(commandArray[0], commandArray[1:]...)
|
||||
}
|
||||
combinedOutput, err := command.CombinedOutput()
|
||||
if err != nil {
|
||||
return E.Extend(err, F.ToString(commandStr, ": ", string(combinedOutput)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
235
inbound/tun_auto_redirect_iptables.go
Normal file
235
inbound/tun_auto_redirect_iptables.go
Normal file
@@ -0,0 +1,235 @@
|
||||
//go:build linux
|
||||
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
iptablesTableNameOutput = "sing-box-output"
|
||||
iptablesTableNameForward = "sing-box-forward"
|
||||
iptablesTableNamePreRouteing = "sing-box-prerouting"
|
||||
)
|
||||
|
||||
func (t *tunAutoRedirect) iptablesPathForFamily(family int) string {
|
||||
if family == unix.AF_INET {
|
||||
return t.iptablesPath
|
||||
} else {
|
||||
return t.ip6tablesPath
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) setupIPTables(family int) error {
|
||||
iptablesPath := t.iptablesPathForFamily(family)
|
||||
// OUTPUT
|
||||
err := t.runShell(iptablesPath, "-t nat -N", iptablesTableNameOutput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNameOutput,
|
||||
"-p tcp -o", t.tunOptions.Name,
|
||||
"-j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = t.runShell(iptablesPath, "-t nat -I OUTPUT -j", iptablesTableNameOutput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !t.androidSu {
|
||||
// FORWARD
|
||||
err = t.runShell(iptablesPath, "-N", iptablesTableNameForward)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = t.runShell(iptablesPath, "-A", iptablesTableNameForward,
|
||||
"-i", t.tunOptions.Name, "-j", "ACCEPT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = t.runShell(iptablesPath, "-A", iptablesTableNameForward,
|
||||
"-o", t.tunOptions.Name, "-j", "ACCEPT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = t.runShell(iptablesPath, "-I FORWARD -j", iptablesTableNameForward)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// PREROUTING
|
||||
err = t.setupIPTablesPreRouting(family)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) setupIPTablesPreRouting(family int) error {
|
||||
iptablesPath := t.iptablesPathForFamily(family)
|
||||
err := t.runShell(iptablesPath, "-t nat -N", iptablesTableNamePreRouteing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
routeAddress []netip.Prefix
|
||||
routeExcludeAddress []netip.Prefix
|
||||
)
|
||||
if family == unix.AF_INET {
|
||||
routeAddress = t.tunOptions.Inet4RouteAddress
|
||||
routeExcludeAddress = t.tunOptions.Inet4RouteExcludeAddress
|
||||
} else {
|
||||
routeAddress = t.tunOptions.Inet6RouteAddress
|
||||
routeExcludeAddress = t.tunOptions.Inet6RouteExcludeAddress
|
||||
}
|
||||
if len(routeAddress) > 0 && (len(t.tunOptions.IncludeInterface) > 0 || len(t.tunOptions.IncludeUID) > 0) {
|
||||
return E.New("`*_route_address` is conflict with `include_interface` or `include_uid`")
|
||||
}
|
||||
err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing,
|
||||
"-i", t.tunOptions.Name, "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, address := range routeExcludeAddress {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing,
|
||||
"-d", address.String(), "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, name := range t.tunOptions.ExcludeInterface {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing,
|
||||
"-i", name, "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, uid := range t.tunOptions.ExcludeUID {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing,
|
||||
"-m owner --uid-owner", uid, "-j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var dnsServerAddress netip.Addr
|
||||
if family == unix.AF_INET {
|
||||
dnsServerAddress = t.tunOptions.Inet4Address[0].Addr().Next()
|
||||
} else {
|
||||
dnsServerAddress = t.tunOptions.Inet6Address[0].Addr().Next()
|
||||
}
|
||||
if len(routeAddress) > 0 {
|
||||
for _, address := range routeAddress {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing,
|
||||
"-d", address.String(), "-p udp --dport 53 -j DNAT --to", dnsServerAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if len(t.tunOptions.IncludeInterface) > 0 || len(t.tunOptions.IncludeUID) > 0 {
|
||||
for _, name := range t.tunOptions.IncludeInterface {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing,
|
||||
"-i", name, "-p udp --dport 53 -j DNAT --to", dnsServerAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, uidRange := range t.tunOptions.IncludeUID {
|
||||
for uid := uidRange.Start; uid <= uidRange.End; uid++ {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing,
|
||||
"-m owner --uid-owner", uid, "-p udp --dport 53 -j DNAT --to", dnsServerAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing,
|
||||
"-p udp --dport 53 -j DNAT --to", dnsServerAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing, "-m addrtype --dst-type LOCAL -j RETURN")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(routeAddress) > 0 {
|
||||
for _, address := range routeAddress {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing,
|
||||
"-d", address.String(), "-p tcp -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if len(t.tunOptions.IncludeInterface) > 0 || len(t.tunOptions.IncludeUID) > 0 {
|
||||
for _, name := range t.tunOptions.IncludeInterface {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing,
|
||||
"-i", name, "-p tcp -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, uidRange := range t.tunOptions.IncludeUID {
|
||||
for uid := uidRange.Start; uid <= uidRange.End; uid++ {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing,
|
||||
"-m owner --uid-owner", uid, "-p tcp -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = t.runShell(iptablesPath, "-t nat -A", iptablesTableNamePreRouteing,
|
||||
"-p tcp -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = t.runShell(iptablesPath, "-t nat -I PREROUTING -j", iptablesTableNamePreRouteing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) cleanupIPTables(family int) {
|
||||
iptablesPath := t.iptablesPathForFamily(family)
|
||||
_ = t.runShell(iptablesPath, "-t nat -D OUTPUT -j", iptablesTableNameOutput)
|
||||
_ = t.runShell(iptablesPath, "-t nat -F", iptablesTableNameOutput)
|
||||
_ = t.runShell(iptablesPath, "-t nat -X", iptablesTableNameOutput)
|
||||
if !t.androidSu {
|
||||
_ = t.runShell(iptablesPath, "-D FORWARD -j", iptablesTableNameForward)
|
||||
_ = t.runShell(iptablesPath, "-F", iptablesTableNameForward)
|
||||
_ = t.runShell(iptablesPath, "-X", iptablesTableNameForward)
|
||||
_ = t.runShell(iptablesPath, "-t nat -D PREROUTING -j", iptablesTableNamePreRouteing)
|
||||
_ = t.runShell(iptablesPath, "-t nat -F", iptablesTableNamePreRouteing)
|
||||
_ = t.runShell(iptablesPath, "-t nat -X", iptablesTableNamePreRouteing)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) runShell(commands ...any) error {
|
||||
commandStr := strings.Join(F.MapToString(commands), " ")
|
||||
var command *exec.Cmd
|
||||
if t.androidSu {
|
||||
command = exec.Command(t.suPath, "-c", commandStr)
|
||||
} else {
|
||||
commandArray := strings.Split(commandStr, " ")
|
||||
command = exec.Command(commandArray[0], commandArray[1:]...)
|
||||
}
|
||||
combinedOutput, err := command.CombinedOutput()
|
||||
if err != nil {
|
||||
return E.Extend(err, F.ToString(commandStr, ": ", string(combinedOutput)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
231
inbound/tun_auto_redirect_nftables.go
Normal file
231
inbound/tun_auto_redirect_nftables.go
Normal file
@@ -0,0 +1,231 @@
|
||||
//go:build linux
|
||||
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/nftables"
|
||||
"github.com/sagernet/nftables/binaryutil"
|
||||
"github.com/sagernet/nftables/expr"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
nftablesTableName = "sing-box"
|
||||
nftablesChainOutput = "output"
|
||||
nftablesChainForward = "forward"
|
||||
nftablesChainPreRouting = "prerouting"
|
||||
)
|
||||
|
||||
func nftablesFamily(family int) nftables.TableFamily {
|
||||
switch family {
|
||||
case unix.AF_INET:
|
||||
return nftables.TableFamilyIPv4
|
||||
case unix.AF_INET6:
|
||||
return nftables.TableFamilyIPv6
|
||||
default:
|
||||
panic(F.ToString("unknown family ", family))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) setupNfTables(family int) error {
|
||||
nft, err := nftables.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer nft.CloseLasting()
|
||||
table := nft.AddTable(&nftables.Table{
|
||||
Name: nftablesTableName,
|
||||
Family: nftablesFamily(family),
|
||||
})
|
||||
chainOutput := nft.AddChain(&nftables.Chain{
|
||||
Name: nftablesChainOutput,
|
||||
Table: table,
|
||||
Hooknum: nftables.ChainHookOutput,
|
||||
Priority: nftables.ChainPriorityMangle,
|
||||
Type: nftables.ChainTypeNAT,
|
||||
})
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainOutput,
|
||||
Exprs: nftablesRuleIfName(expr.MetaKeyOIFNAME, t.tunOptions.Name, nftablesRuleRedirectToPorts(M.AddrPortFromNet(t.tcpListener.Addr()).Port())...),
|
||||
})
|
||||
chainForward := nft.AddChain(&nftables.Chain{
|
||||
Name: nftablesChainForward,
|
||||
Table: table,
|
||||
Hooknum: nftables.ChainHookForward,
|
||||
Priority: nftables.ChainPriorityMangle,
|
||||
})
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainForward,
|
||||
Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, t.tunOptions.Name, &expr.Verdict{
|
||||
Kind: expr.VerdictAccept,
|
||||
}),
|
||||
})
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainForward,
|
||||
Exprs: nftablesRuleIfName(expr.MetaKeyOIFNAME, t.tunOptions.Name, &expr.Verdict{
|
||||
Kind: expr.VerdictAccept,
|
||||
}),
|
||||
})
|
||||
t.setupNfTablesPreRouting(nft, table)
|
||||
return nft.Flush()
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) setupNfTablesPreRouting(nft *nftables.Conn, table *nftables.Table) {
|
||||
chainPreRouting := nft.AddChain(&nftables.Chain{
|
||||
Name: nftablesChainPreRouting,
|
||||
Table: table,
|
||||
Hooknum: nftables.ChainHookPrerouting,
|
||||
Priority: nftables.ChainPriorityMangle,
|
||||
Type: nftables.ChainTypeNAT,
|
||||
})
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, t.tunOptions.Name, &expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
}),
|
||||
})
|
||||
var (
|
||||
routeAddress []netip.Prefix
|
||||
routeExcludeAddress []netip.Prefix
|
||||
)
|
||||
if table.Family == nftables.TableFamilyIPv4 {
|
||||
routeAddress = t.tunOptions.Inet4RouteAddress
|
||||
routeExcludeAddress = t.tunOptions.Inet4RouteExcludeAddress
|
||||
} else {
|
||||
routeAddress = t.tunOptions.Inet6RouteAddress
|
||||
routeExcludeAddress = t.tunOptions.Inet6RouteExcludeAddress
|
||||
}
|
||||
for _, address := range routeExcludeAddress {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: nftablesRuleDestinationAddress(address, &expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
}),
|
||||
})
|
||||
}
|
||||
for _, name := range t.tunOptions.ExcludeInterface {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, name, &expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
}),
|
||||
})
|
||||
}
|
||||
for _, uidRange := range t.tunOptions.ExcludeUID {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: nftablesRuleMetaUInt32Range(expr.MetaKeySKUID, uidRange, &expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
var routeExprs []expr.Any
|
||||
if len(routeAddress) > 0 {
|
||||
for _, address := range routeAddress {
|
||||
routeExprs = append(routeExprs, nftablesRuleDestinationAddress(address)...)
|
||||
}
|
||||
}
|
||||
redirectPort := M.AddrPortFromNet(t.tcpListener.Addr()).Port()
|
||||
var dnsServerAddress netip.Addr
|
||||
if table.Family == nftables.TableFamilyIPv4 {
|
||||
dnsServerAddress = t.tunOptions.Inet4Address[0].Addr().Next()
|
||||
} else {
|
||||
dnsServerAddress = t.tunOptions.Inet6Address[0].Addr().Next()
|
||||
}
|
||||
|
||||
if len(t.tunOptions.IncludeInterface) > 0 || len(t.tunOptions.IncludeUID) > 0 {
|
||||
for _, name := range t.tunOptions.IncludeInterface {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, name, append(routeExprs, nftablesRuleHijackDNS(table.Family, dnsServerAddress)...)...),
|
||||
})
|
||||
}
|
||||
for _, uidRange := range t.tunOptions.IncludeUID {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: nftablesRuleMetaUInt32Range(expr.MetaKeySKUID, uidRange, append(routeExprs, nftablesRuleHijackDNS(table.Family, dnsServerAddress)...)...),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: append(routeExprs, nftablesRuleHijackDNS(table.Family, dnsServerAddress)...),
|
||||
})
|
||||
}
|
||||
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Fib{
|
||||
Register: 1,
|
||||
FlagDADDR: true,
|
||||
ResultADDRTYPE: true,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: binaryutil.NativeEndian.PutUint32(unix.RTN_LOCAL),
|
||||
},
|
||||
&expr.Verdict{
|
||||
Kind: expr.VerdictReturn,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if len(t.tunOptions.IncludeInterface) > 0 || len(t.tunOptions.IncludeUID) > 0 {
|
||||
for _, name := range t.tunOptions.IncludeInterface {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: nftablesRuleIfName(expr.MetaKeyIIFNAME, name, append(routeExprs, nftablesRuleRedirectToPorts(redirectPort)...)...),
|
||||
})
|
||||
}
|
||||
for _, uidRange := range t.tunOptions.IncludeUID {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: nftablesRuleMetaUInt32Range(expr.MetaKeySKUID, uidRange, append(routeExprs, nftablesRuleRedirectToPorts(redirectPort)...)...),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
nft.AddRule(&nftables.Rule{
|
||||
Table: table,
|
||||
Chain: chainPreRouting,
|
||||
Exprs: append(routeExprs, nftablesRuleRedirectToPorts(redirectPort)...),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) cleanupNfTables(family int) {
|
||||
conn, err := nftables.New()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.CloseLasting()
|
||||
conn.FlushTable(&nftables.Table{
|
||||
Name: nftablesTableName,
|
||||
Family: nftablesFamily(family),
|
||||
})
|
||||
conn.DelTable(&nftables.Table{
|
||||
Name: nftablesTableName,
|
||||
Family: nftablesFamily(family),
|
||||
})
|
||||
_ = conn.Flush()
|
||||
}
|
||||
153
inbound/tun_auto_redirect_nftables_expr.go
Normal file
153
inbound/tun_auto_redirect_nftables_expr.go
Normal file
@@ -0,0 +1,153 @@
|
||||
//go:build linux
|
||||
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/nftables"
|
||||
"github.com/sagernet/nftables/binaryutil"
|
||||
"github.com/sagernet/nftables/expr"
|
||||
"github.com/sagernet/sing/common/ranges"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func nftablesIfname(n string) []byte {
|
||||
b := make([]byte, 16)
|
||||
copy(b, n+"\x00")
|
||||
return b
|
||||
}
|
||||
|
||||
func nftablesRuleIfName(key expr.MetaKey, value string, exprs ...expr.Any) []expr.Any {
|
||||
newExprs := []expr.Any{
|
||||
&expr.Meta{Key: key, Register: 1},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: nftablesIfname(value),
|
||||
},
|
||||
}
|
||||
newExprs = append(newExprs, exprs...)
|
||||
return newExprs
|
||||
}
|
||||
|
||||
func nftablesRuleMetaUInt32Range(key expr.MetaKey, uidRange ranges.Range[uint32], exprs ...expr.Any) []expr.Any {
|
||||
newExprs := []expr.Any{
|
||||
&expr.Meta{Key: key, Register: 1},
|
||||
&expr.Range{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
FromData: binaryutil.BigEndian.PutUint32(uidRange.Start),
|
||||
ToData: binaryutil.BigEndian.PutUint32(uidRange.End),
|
||||
},
|
||||
}
|
||||
newExprs = append(newExprs, exprs...)
|
||||
return newExprs
|
||||
}
|
||||
|
||||
func nftablesRuleDestinationAddress(address netip.Prefix, exprs ...expr.Any) []expr.Any {
|
||||
var newExprs []expr.Any
|
||||
if address.Addr().Is4() {
|
||||
newExprs = append(newExprs, &expr.Payload{
|
||||
OperationType: expr.PayloadLoad,
|
||||
DestRegister: 1,
|
||||
SourceRegister: 0,
|
||||
Base: expr.PayloadBaseNetworkHeader,
|
||||
Offset: 16,
|
||||
Len: 4,
|
||||
}, &expr.Bitwise{
|
||||
SourceRegister: 1,
|
||||
DestRegister: 1,
|
||||
Len: 4,
|
||||
Xor: make([]byte, 4),
|
||||
Mask: net.CIDRMask(address.Bits(), 32),
|
||||
})
|
||||
} else {
|
||||
newExprs = append(newExprs, &expr.Payload{
|
||||
OperationType: expr.PayloadLoad,
|
||||
DestRegister: 1,
|
||||
SourceRegister: 0,
|
||||
Base: expr.PayloadBaseNetworkHeader,
|
||||
Offset: 24,
|
||||
Len: 16,
|
||||
}, &expr.Bitwise{
|
||||
SourceRegister: 1,
|
||||
DestRegister: 1,
|
||||
Len: 16,
|
||||
Xor: make([]byte, 16),
|
||||
Mask: net.CIDRMask(address.Bits(), 128),
|
||||
})
|
||||
}
|
||||
newExprs = append(newExprs, &expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: address.Masked().Addr().AsSlice(),
|
||||
})
|
||||
newExprs = append(newExprs, exprs...)
|
||||
return newExprs
|
||||
}
|
||||
|
||||
func nftablesRuleHijackDNS(family nftables.TableFamily, dnsServerAddress netip.Addr) []expr.Any {
|
||||
return []expr.Any{
|
||||
&expr.Meta{
|
||||
Key: expr.MetaKeyL4PROTO,
|
||||
Register: 1,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte{unix.IPPROTO_UDP},
|
||||
},
|
||||
&expr.Payload{
|
||||
OperationType: expr.PayloadLoad,
|
||||
DestRegister: 1,
|
||||
SourceRegister: 0,
|
||||
Base: expr.PayloadBaseTransportHeader,
|
||||
Offset: 2,
|
||||
Len: 2,
|
||||
}, &expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: binaryutil.BigEndian.PutUint16(53),
|
||||
}, &expr.Immediate{
|
||||
Register: 1,
|
||||
Data: dnsServerAddress.AsSlice(),
|
||||
}, &expr.NAT{
|
||||
Type: expr.NATTypeDestNAT,
|
||||
Family: uint32(family),
|
||||
RegAddrMin: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
NF_NAT_RANGE_MAP_IPS = 1 << iota
|
||||
NF_NAT_RANGE_PROTO_SPECIFIED
|
||||
NF_NAT_RANGE_PROTO_RANDOM
|
||||
NF_NAT_RANGE_PERSISTENT
|
||||
NF_NAT_RANGE_PROTO_RANDOM_FULLY
|
||||
NF_NAT_RANGE_PROTO_OFFSET
|
||||
)
|
||||
|
||||
func nftablesRuleRedirectToPorts(redirectPort uint16) []expr.Any {
|
||||
return []expr.Any{
|
||||
&expr.Meta{
|
||||
Key: expr.MetaKeyL4PROTO,
|
||||
Register: 1,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: []byte{unix.IPPROTO_TCP},
|
||||
},
|
||||
&expr.Immediate{
|
||||
Register: 1,
|
||||
Data: binaryutil.BigEndian.PutUint16(redirectPort),
|
||||
}, &expr.Redir{
|
||||
RegisterProtoMin: 1,
|
||||
Flags: NF_NAT_RANGE_PROTO_SPECIFIED,
|
||||
},
|
||||
}
|
||||
}
|
||||
23
inbound/tun_auto_redirect_stub.go
Normal file
23
inbound/tun_auto_redirect_stub.go
Normal file
@@ -0,0 +1,23 @@
|
||||
//go:build !linux
|
||||
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
type tunAutoRedirect struct{}
|
||||
|
||||
func newAutoRedirect(t *Tun) (*tunAutoRedirect, error) {
|
||||
return nil, E.New("only supported on linux")
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) Start() error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (t *tunAutoRedirect) Close() error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
Reference in New Issue
Block a user