Improve auto redirect

This commit is contained in:
世界 2024-05-29 11:32:00 +08:00
parent f3bfd1562b
commit d6d6f72fea
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
16 changed files with 499 additions and 279 deletions

View File

@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.8.0"
!!! quote "Changes in sing-box 1.9.0"

View File

@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.8.0 起"
!!! quote "sing-box 1.9.0 中的更改"

View File

@ -1,11 +1,3 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.10.0"
:material-plus: [auto_redirect](#auto_redirect)
!!! quote ""
Only supported on Linux and macOS.
@ -17,11 +9,6 @@ icon: material/new-box
"type": "redirect",
"tag": "redirect-in",
"auto_redirect": {
"enabled": false,
"continue_on_no_permission": false
},
... // Listen Fields
}
```
@ -29,23 +16,3 @@ icon: material/new-box
### Listen Fields
See [Listen Fields](/configuration/shared/listen/) for details.
### Fields
#### `auto_redirect`
!!! question "Since sing-box 1.10.0"
!!! quote ""
Only supported on Android.
Automatically add iptables nat rules to hijack **IPv4 TCP** connections.
It is expected to run with the Android graphical client (it will attempt to su at runtime).
#### `auto_redirect.continue_on_no_permission`
!!! question "Since sing-box 1.10.0"
Ignore errors when the Android device is not rooted or is denied root access.

View File

@ -1,11 +1,3 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.10.0 中的更改"
:material-plus: [auto_redirect](#auto_redirect)
!!! quote ""
仅支持 Linux 和 macOS。
@ -17,35 +9,9 @@ icon: material/new-box
"type": "redirect",
"tag": "redirect-in",
"auto_redirect": {
"enabled": false,
"continue_on_no_permission": false
},
... // 监听字段
}
```
### 监听字段
参阅 [监听字段](/zh/configuration/shared/listen/)。
### 字段
#### `auto_redirect`
!!! question "自 sing-box 1.10.0 起"
!!! quote ""
仅支持 Android。
自动添加 iptables nat 规则以劫持 **IPv4 TCP** 连接。
它预计与 Android 图形客户端一起运行(将在运行时尝试 su
#### `auto_redirect.continue_on_no_permission`
!!! question "自 sing-box 1.10.0 起"
当 Android 设备未获得 root 权限或 root 访问权限被拒绝时,忽略错误。

View File

@ -2,6 +2,10 @@
icon: material/new-box
---
!!! quote "Changes in sing-box 1.10.0"
:material-plus: [auto_redirect](#auto_redirect)
!!! quote "Changes in sing-box 1.9.0"
:material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain)
@ -29,6 +33,7 @@ icon: material/new-box
"gso": false,
"auto_route": true,
"strict_route": true,
"auto_redirect": false,
"inet4_route_address": [
"0.0.0.0/1",
"128.0.0.0/1"
@ -156,6 +161,35 @@ It prevents address leaks and makes DNS hijacking work on Android.
It may prevent some applications (such as VirtualBox) from working properly in certain situations.
#### auto_redirect
!!! question "Since sing-box 1.10.0"
!!! quote ""
Only supported on Linux.
Automatically configure iptables to redirect TCP connections.
*In Android*
* Only IPv4 is supported
* Only local connections is forwarded
To share your VPN connection over hotspot or repeater, use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
*In Linux*:
* iptables is required (optional ip6tables)
* `iptables_nat` module is required
For OpenWrt 23.05, required extra packages are:
```bash
opkg update
opkg install iptables-nft iptables-mod-nat-extra ip6tables-nft
```
#### inet4_route_address
Use custom routes instead of default when `auto_route` is enabled.

View File

@ -2,6 +2,10 @@
icon: material/new-box
---
!!! quote "sing-box 1.10.0 中的更改"
:material-plus: [auto_redirect](#auto_redirect)
!!! quote "sing-box 1.9.0 中的更改"
:material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain)
@ -29,6 +33,7 @@ icon: material/new-box
"gso": false,
"auto_route": true,
"strict_route": true,
"auto_redirect": false,
"inet4_route_address": [
"0.0.0.0/1",
"128.0.0.0/1"
@ -157,6 +162,35 @@ tun 接口的 IPv6 前缀。
它可能会使某些应用程序(如 VirtualBox在某些情况下无法正常工作。
#### auto_redirect
!!! question "自 sing-box 1.10.0 起"
!!! quote ""
仅支持 Linux。
自动配置 iptables 以重定向 TCP 连接。
*在 Android 中*
* 仅支持 IPv4
* 仅转发本地连接
要通过热点或中继共享您的 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
*在 Linux 中*:
* 需要 iptables可选 ip6tables
* 需要 `iptables_nat` 模块
对于 OpenWrt 23.05,所需的额外软件包是:
```bash
opkg update
opkg install iptables-nft iptables-mod-nat-extra ip6tables-nft
```
#### inet4_route_address
启用 `auto_route` 时使用自定义路由而不是默认路由。

View File

@ -9,7 +9,6 @@ import (
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/process"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
@ -98,14 +97,6 @@ func (s *platformInterfaceStub) FindProcessInfo(ctx context.Context, network str
return nil, os.ErrInvalid
}
func (s *platformInterfaceStub) PerAppProxyList() ([]uint32, error) {
return nil, os.ErrInvalid
}
func (s *platformInterfaceStub) PerAppProxyMode() int32 {
return platform.PerAppProxyModeDisabled
}
type interfaceMonitorStub struct{}
func (s *interfaceMonitorStub) Start() error {

View File

@ -22,8 +22,6 @@ type PlatformInterface interface {
IncludeAllNetworks() bool
ReadWIFIState() *WIFIState
ClearDNSCache()
PerAppProxyList() (IntegerIterator, error)
PerAppProxyMode() int32
}
type TunInterface interface {
@ -56,11 +54,6 @@ type NetworkInterfaceIterator interface {
HasNext() bool
}
type IntegerIterator interface {
Next() int32
HasNext() bool
}
type OnDemandRule interface {
Target() int32
DNSSearchDomainMatch() StringIterator

View File

@ -11,12 +11,6 @@ import (
"github.com/sagernet/sing/common/logger"
)
const (
PerAppProxyModeDisabled int32 = iota
PerAppProxyModeExclude
PerAppProxyModeInclude
)
type Interface interface {
Initialize(ctx context.Context, router adapter.Router) error
UsePlatformAutoDetectInterfaceControl() bool
@ -30,7 +24,5 @@ type Interface interface {
IncludeAllNetworks() bool
ClearDNSCache()
ReadWIFIState() adapter.WIFIState
PerAppProxyList() ([]uint32, error)
PerAppProxyMode() int32
process.Searcher
}

View File

@ -229,18 +229,6 @@ func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState {
return (adapter.WIFIState)(*wifiState)
}
func (w *platformInterfaceWrapper) PerAppProxyList() ([]uint32, error) {
uidIterator, err := w.iif.PerAppProxyList()
if err != nil {
return nil, err
}
return common.Map(iteratorToArray[int32](uidIterator), func(it int32) uint32 { return uint32(it) }), nil
}
func (w *platformInterfaceWrapper) PerAppProxyMode() int32 {
return w.iif.PerAppProxyMode()
}
func (w *platformInterfaceWrapper) DisableColors() bool {
return runtime.GOOS != "android"
}

View File

@ -19,7 +19,7 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
case C.TypeTun:
return NewTun(ctx, router, logger, options.Tag, options.TunOptions, platformInterface)
case C.TypeRedirect:
return NewRedirect(ctx, router, logger, options.Tag, options.RedirectOptions, platformInterface)
return NewRedirect(ctx, router, logger, options.Tag, options.RedirectOptions), nil
case C.TypeTProxy:
return NewTProxy(ctx, router, logger, options.Tag, options.TProxyOptions), nil
case C.TypeDirect:

View File

@ -2,39 +2,25 @@ package inbound
import (
"context"
"errors"
"net"
"net/netip"
"os"
"os/exec"
"sort"
"strings"
"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/experimental/libbox/platform"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"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"
)
type Redirect struct {
myInboundAdapter
platformInterface platform.Interface
autoRedirect option.AutoRedirectOptions
needSu bool
suPath string
}
func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RedirectInboundOptions, platformInterface platform.Interface) (*Redirect, error) {
func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RedirectInboundOptions) *Redirect {
redirect := &Redirect{
myInboundAdapter: myInboundAdapter{
myInboundAdapter{
protocol: C.TypeRedirect,
network: []string{N.NetworkTCP},
ctx: ctx,
@ -43,28 +29,9 @@ func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextL
tag: tag,
listenOptions: options.ListenOptions,
},
platformInterface: platformInterface,
autoRedirect: common.PtrValueOrDefault(options.AutoRedirect),
}
if redirect.autoRedirect.Enabled {
if !C.IsAndroid {
return nil, E.New("auto redirect is only supported on Android")
}
userId := os.Getuid()
if userId != 0 {
suPath, err := exec.LookPath("/bin/su")
if err == nil {
redirect.needSu = true
redirect.suPath = suPath
} else if redirect.autoRedirect.ContinueOnNoPermission {
redirect.autoRedirect.Enabled = false
} else {
return nil, E.Extend(E.Cause(err, "root permission is required for auto redirect"), os.Getenv("PATH"))
}
}
}
redirect.connHandler = redirect
return redirect, nil
return redirect
}
func (r *Redirect) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
@ -75,124 +42,3 @@ func (r *Redirect) NewConnection(ctx context.Context, conn net.Conn, metadata ad
metadata.Destination = M.SocksaddrFromNetIP(destination)
return r.newConnection(ctx, conn, metadata)
}
func (r *Redirect) Start() error {
err := r.myInboundAdapter.Start()
if err != nil {
return err
}
if r.autoRedirect.Enabled {
r.cleanupRedirect()
err = r.setupRedirect()
if err != nil {
var exitError *exec.ExitError
if errors.As(err, &exitError) && exitError.ExitCode() == 13 && r.autoRedirect.ContinueOnNoPermission {
r.logger.Error(E.Cause(err, "setup auto redirect"))
return nil
}
r.cleanupRedirect()
return E.Cause(err, "setup auto redirect")
}
}
return nil
}
func (r *Redirect) Close() error {
if r.autoRedirect.Enabled {
r.cleanupRedirect()
}
return r.myInboundAdapter.Close()
}
func (r *Redirect) setupRedirect() error {
tableName := "sing-box"
rules := `
set -e -o pipefail
iptables -t nat -N sing-box
`
rules += strings.Join(common.FlatMap(r.router.(adapter.Router).InterfaceFinder().Interfaces(), func(it control.Interface) []string {
return common.Map(common.Filter(it.Addresses, func(it netip.Prefix) bool { return it.Addr().Is4() }), func(it netip.Prefix) string {
return "iptables -t nat -A " + tableName + " -p tcp -j RETURN -d " + it.String()
})
}), "\n")
var (
myUid = uint32(os.Getuid())
perAppProxyList []uint32
perAppProxyMap = make(map[uint32]bool)
perAppProxyMode int32
err error
)
if r.platformInterface != nil {
perAppProxyMode = r.platformInterface.PerAppProxyMode()
if perAppProxyMode != platform.PerAppProxyModeDisabled {
perAppProxyList, err = r.platformInterface.PerAppProxyList()
if err != nil {
return E.Cause(err, "read per app proxy configuration")
}
}
for _, proxyUID := range perAppProxyList {
perAppProxyMap[proxyUID] = true
}
}
excludeUser := func(userID uint32) {
if perAppProxyMode != platform.PerAppProxyModeInclude {
perAppProxyMap[userID] = false
} else {
delete(perAppProxyMap, userID)
}
}
excludeUser(myUid)
if myUid != 0 && myUid != 2000 {
excludeUser(0)
}
perAppProxyList = perAppProxyList[:0]
for uid := range perAppProxyMap {
perAppProxyList = append(perAppProxyList, uid)
}
sort.SliceStable(perAppProxyList, func(i, j int) bool {
return perAppProxyList[i] < perAppProxyList[j]
})
redirectPortStr := F.ToString(M.AddrPortFromNet(r.tcpListener.Addr()).Port())
if perAppProxyMode != platform.PerAppProxyModeInclude {
rules += "\n" + strings.Join(common.Map(perAppProxyList, func(it uint32) string {
return "iptables -t nat -A " + tableName + " -j RETURN -m owner --uid-owner " + F.ToString(it)
}), "\n")
rules += "\niptables -t nat -A " + tableName + " -p tcp -j REDIRECT --to-ports " + redirectPortStr
} else {
rules += "\n" + strings.Join(common.Map(perAppProxyList, func(it uint32) string {
return "iptables -t nat -A " + tableName + " -p tcp -j REDIRECT --to-ports " + redirectPortStr + " -m owner --uid-owner " + F.ToString(it)
}), "\n")
}
rules += "\niptables -t nat -A OUTPUT -p tcp -j " + tableName
for _, ruleLine := range strings.Split(rules, "\n") {
ruleLine = strings.TrimSpace(ruleLine)
if ruleLine == "" {
continue
}
r.logger.Debug("# ", ruleLine)
}
return r.runAndroidShell(rules)
}
func (r *Redirect) cleanupRedirect() {
_ = r.runAndroidShell(`
iptables -t nat -D OUTPUT -p tcp -j sing-box
iptables -t nat -F sing-box
iptables -t nat -X sing-box
`)
}
func (r *Redirect) runAndroidShell(content string) error {
var command *exec.Cmd
if r.needSu {
command = exec.Command(r.suPath, "-c", "sh")
} else {
command = exec.Command("sh")
}
command.Stdin = strings.NewReader(content)
combinedOutput, err := command.CombinedOutput()
if err != nil {
return E.Extend(err, string(combinedOutput))
}
return nil
}

View File

@ -37,6 +37,7 @@ type Tun struct {
tunStack tun.Stack
platformInterface platform.Interface
platformOptions option.TunPlatformOptions
autoRedirect *tunAutoRedirect
}
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) {
@ -50,9 +51,9 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
} else {
udpTimeout = C.UDPTimeout
}
var err error
includeUID := uidToRange(options.IncludeUID)
if len(options.IncludeUIDRange) > 0 {
var err error
includeUID, err = parseRange(includeUID, options.IncludeUIDRange)
if err != nil {
return nil, E.Cause(err, "parse include_uid_range")
@ -60,13 +61,13 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
}
excludeUID := uidToRange(options.ExcludeUID)
if len(options.ExcludeUIDRange) > 0 {
var err error
excludeUID, err = parseRange(excludeUID, options.ExcludeUIDRange)
if err != nil {
return nil, E.Cause(err, "parse exclude_uid_range")
}
}
return &Tun{
inbound := &Tun{
tag: tag,
ctx: ctx,
router: router,
@ -99,7 +100,17 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
stack: options.Stack,
platformInterface: platformInterface,
platformOptions: common.PtrValueOrDefault(options.Platform),
}, nil
}
if options.AutoRedirect {
if !options.AutoRoute {
return nil, E.New("`auto_route` is required by `auto_redirect`")
}
inbound.autoRedirect, err = newAutoRedirect(inbound)
if err != nil {
return nil, E.Cause(err, "initialize auto redirect")
}
}
return inbound, nil
}
func uidToRange(uidList option.Listable[uint32]) []ranges.Range[uint32] {
@ -195,6 +206,14 @@ func (t *Tun) Start() error {
if err != nil {
return err
}
if t.autoRedirect != nil {
monitor.Start("initiating auto redirect")
err = t.autoRedirect.Start(t.tunOptions.Name)
monitor.Finish()
if err != nil {
return E.Cause(err, "auto redirect")
}
}
t.logger.Info("started at ", t.tunOptions.Name)
return nil
}
@ -203,6 +222,7 @@ func (t *Tun) Close() error {
return common.Close(
t.tunStack,
t.tunIf,
common.PtrOrNil(t.autoRedirect),
)
}

View File

@ -0,0 +1,402 @@
package inbound
import (
"context"
"net"
"net/netip"
"os"
"os/exec"
"slices"
"strings"
"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"
)
const (
tableNameOutput = "sing-box-output"
tableNameForward = "sing-box-forward"
tableNamePreRouteing = "sing-box-prerouting"
)
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
}
func newAutoRedirect(t *Tun) (*tunAutoRedirect, error) {
if !C.IsLinux {
return nil, E.New("only supported on linux")
}
server := &tunAutoRedirect{
myInboundAdapter: myInboundAdapter{
protocol: C.TypeRedirect,
network: []string{N.NetworkTCP},
ctx: t.ctx,
router: t.router,
logger: t.logger,
tag: t.tag,
},
tunOptions: &t.tunOptions,
interfaceFinder: t.router.InterfaceFinder(),
networkMonitor: t.router.NetworkMonitor(),
}
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"))
}
}
} else {
iptablesPath, err := exec.LookPath("iptables")
if err != nil {
return nil, E.Cause(err, "iptables is required")
}
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)
}
}
var listenAddr netip.Addr
if C.IsAndroid {
listenAddr = netip.AddrFrom4([4]byte{127, 0, 0, 1})
} else if server.enableIPv6 {
listenAddr = netip.IPv6Unspecified()
} else {
listenAddr = netip.IPv4Unspecified()
}
server.listenOptions.Listen = option.NewListenAddress(listenAddr)
return server, nil
}
func (t *tunAutoRedirect) initializeIP6Tables() error {
ip6tablesPath, err := exec.LookPath("ip6tables")
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:
return err
}*/
t.ip6tablesPath = ip6tablesPath
t.enableIPv6 = true
return nil
}
func (t *tunAutoRedirect) Start(tunName string) 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)
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)
}
return t.myInboundAdapter.Close()
}
func (t *tunAutoRedirect) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
destination, err := redir.GetOriginalDestination(conn)
if err != nil {
return E.Cause(err, "get redirect destination")
}
metadata.Destination = M.SocksaddrFromNetIP(destination)
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
}
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
}
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 err != nil {
return 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)
} else {
err = t.runShell(iptablesPath, "-t nat -F", tableNamePreRouteing)
}
if err != nil {
return err
}
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)
}
}
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
}

View File

@ -2,12 +2,6 @@ package option
type RedirectInboundOptions struct {
ListenOptions
AutoRedirect *AutoRedirectOptions `json:"auto_redirect,omitempty"`
}
type AutoRedirectOptions struct {
Enabled bool `json:"enabled,omitempty"`
ContinueOnNoPermission bool `json:"continue_on_no_permission,omitempty"`
}
type TProxyInboundOptions struct {

View File

@ -9,6 +9,7 @@ type TunInboundOptions struct {
Inet4Address Listable[netip.Prefix] `json:"inet4_address,omitempty"`
Inet6Address Listable[netip.Prefix] `json:"inet6_address,omitempty"`
AutoRoute bool `json:"auto_route,omitempty"`
AutoRedirect bool `json:"auto_redirect,omitempty"`
StrictRoute bool `json:"strict_route,omitempty"`
Inet4RouteAddress Listable[netip.Prefix] `json:"inet4_route_address,omitempty"`
Inet6RouteAddress Listable[netip.Prefix] `json:"inet6_route_address,omitempty"`