From da151b9c0f695bcaafd231e52afb0336a8f8cea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 29 May 2024 11:32:00 +0800 Subject: [PATCH] Improve auto redirect --- docs/configuration/inbound/redirect.md | 33 ---- docs/configuration/inbound/redirect.zh.md | 34 ---- experimental/libbox/config.go | 9 - experimental/libbox/platform.go | 7 - experimental/libbox/platform/interface.go | 8 - experimental/libbox/service.go | 12 -- inbound/builder.go | 2 +- inbound/redirect.go | 160 +-------------- inbound/tun.go | 25 ++- inbound/tun_auto_redirect.go | 230 ++++++++++++++++++++++ option/redir.go | 6 - option/tun.go | 1 + 12 files changed, 256 insertions(+), 271 deletions(-) create mode 100644 inbound/tun_auto_redirect.go diff --git a/docs/configuration/inbound/redirect.md b/docs/configuration/inbound/redirect.md index e0c5caf2..50a5bacd 100644 --- a/docs/configuration/inbound/redirect.md +++ b/docs/configuration/inbound/redirect.md @@ -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. diff --git a/docs/configuration/inbound/redirect.zh.md b/docs/configuration/inbound/redirect.zh.md index 53572bc5..a03049e5 100644 --- a/docs/configuration/inbound/redirect.zh.md +++ b/docs/configuration/inbound/redirect.zh.md @@ -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 访问权限被拒绝时,忽略错误。 diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 4af96994..b7731143 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -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 { diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index 3afa839e..4078140f 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -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 diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index 75136781..3bec13fa 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -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 } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 299945ba..0a54d7ab 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -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" } diff --git a/inbound/builder.go b/inbound/builder.go index e1909b84..513b016f 100644 --- a/inbound/builder.go +++ b/inbound/builder.go @@ -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: diff --git a/inbound/redirect.go b/inbound/redirect.go index bf0da9be..4c7cf1d5 100644 --- a/inbound/redirect.go +++ b/inbound/redirect.go @@ -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 -} diff --git a/inbound/tun.go b/inbound/tun.go index e82ea122..d7d8603a 100644 --- a/inbound/tun.go +++ b/inbound/tun.go @@ -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,14 @@ 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 { + 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 +203,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 +219,7 @@ func (t *Tun) Close() error { return common.Close( t.tunStack, t.tunIf, + common.PtrOrNil(t.autoRedirect), ) } diff --git a/inbound/tun_auto_redirect.go b/inbound/tun_auto_redirect.go new file mode 100644 index 00000000..b85bc1bd --- /dev/null +++ b/inbound/tun_auto_redirect.go @@ -0,0 +1,230 @@ +package inbound + +import ( + "context" + "net" + "net/netip" + "os/exec" + "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" + 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" +) + +const ( + tableNameOutput = "sing-box-output" + tableNamePreRouteing = "sing-box-prerouting" + tableNameForward = "sing-box-forward" +) + +type tunAutoRedirect struct { + myInboundAdapter + iptablesPath string + androidSu bool + enableIPv6 bool + ip6tablesPath 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, + listenOptions: option.ListenOptions{ + Listen: option.NewListenAddress(netip.AddrFrom4([4]byte{127, 0, 0, 1})), + }, + }, + } + server.connHandler = server + if C.IsAndroid && t.platformInterface != nil { + server.iptablesPath = "/system/bin/iptables" + } 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 { + return nil, err + } + t.logger.Debug("device has no ip6tables nat support: ", err) + } + 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") + } + t.cleanupIPTables(t.iptablesPath) + if t.enableIPv6 { + t.cleanupIPTables(t.ip6tablesPath) + } + 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 + } + } + return nil +} + +func (t *tunAutoRedirect) Close() error { + 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 + _ = t.runShell(iptablesPath, "-t nat -F", tableNameOutput) + _ = t.runShell(iptablesPath, "-t nat -X", tableNameOutput) + 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 + } + // PREROUTING + _ = t.runShell(iptablesPath, "-t nat -F", tableNamePreRouteing) + _ = t.runShell(iptablesPath, "-t nat -X", tableNamePreRouteing) + err = t.runShell(iptablesPath, "-t nat -N", tableNamePreRouteing) + if err != nil { + return err + } + // Hijack DNS requests + err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing, + "! -i", tunName, "-p tcp --dport 53 -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port()) + if err != nil { + return err + } + err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing, + "! -i", tunName, "-p udp --dport 53 -j REDIRECT --to-ports", M.AddrPortFromNet(t.tcpListener.Addr()).Port()) + if err != nil { + return err + } + for _, netIf := range t.router.(adapter.Router).InterfaceFinder().Interfaces() { + for _, addr := range netIf.Addresses { + if !addr.Addr().Is4() { + continue + } + err = t.runShell(iptablesPath, "-t nat -A", tableNamePreRouteing, "-d", addr.String(), "-j RETURN") + if err != nil { + return err + } + } + } + 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 + } + // FORWARD + _ = t.runShell(iptablesPath, "-t nat -F", tableNameForward) + _ = t.runShell(iptablesPath, "-t nat -X", tableNameForward) + err = t.runShell(iptablesPath, "-t nat -N", tableNameForward) + if err != nil { + return err + } + err = t.runShell(iptablesPath, "-t nat -A", tableNameForward, + "-i", tunName, "-j", "ACCEPT") + if err != nil { + return err + } + err = t.runShell(iptablesPath, "-t nat -A", tableNameForward, + "-o", tunName, "-j", "ACCEPT") + if err != nil { + return err + } + err = t.runShell(iptablesPath, "-t nat -I FORWARD -j", tableNameForward) + 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 -D PREROUTING -j", tableNamePreRouteing) + _ = t.runShell(iptablesPath, "-t nat -D FORWARD -j", tableNameForward) +} + +func (t *tunAutoRedirect) runShell(commands ...any) error { + commandStr := strings.Join(F.MapToString(commands), " ") + var command *exec.Cmd + if t.androidSu { + command = exec.Command("/bin/su", "-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 +} diff --git a/option/redir.go b/option/redir.go index a2af951c..743a6e10 100644 --- a/option/redir.go +++ b/option/redir.go @@ -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 { diff --git a/option/tun.go b/option/tun.go index ac66a806..91930866 100644 --- a/option/tun.go +++ b/option/tun.go @@ -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"`