mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
Add simple auto redirect for Android
This commit is contained in:
parent
5735227174
commit
2d8d9a1959
@ -1,3 +1,11 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.9.0"
|
||||
|
||||
:material-plus: [auto_redirect](#auto_redirect)
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux and macOS.
|
||||
@ -9,6 +17,11 @@
|
||||
"type": "redirect",
|
||||
"tag": "redirect-in",
|
||||
|
||||
"auto_redirect": {
|
||||
"enabled": false,
|
||||
"continue_on_no_permission": false
|
||||
},
|
||||
|
||||
... // Listen Fields
|
||||
}
|
||||
```
|
||||
@ -16,3 +29,23 @@
|
||||
### 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.
|
||||
|
@ -1,3 +1,11 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.10.0 中的更改"
|
||||
|
||||
:material-plus: [auto_redirect](#auto_redirect)
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux 和 macOS。
|
||||
@ -9,9 +17,35 @@
|
||||
"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 访问权限被拒绝时,忽略错误。
|
||||
|
@ -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), nil
|
||||
return NewRedirect(ctx, router, logger, options.Tag, options.RedirectOptions)
|
||||
case C.TypeTProxy:
|
||||
return NewTProxy(ctx, router, logger, options.Tag, options.TProxyOptions), nil
|
||||
case C.TypeDirect:
|
||||
|
@ -2,25 +2,36 @@ package inbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"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/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
|
||||
autoRedirect option.AutoRedirectOptions
|
||||
needSu bool
|
||||
suPath string
|
||||
}
|
||||
|
||||
func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RedirectInboundOptions) *Redirect {
|
||||
func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RedirectInboundOptions) (*Redirect, error) {
|
||||
redirect := &Redirect{
|
||||
myInboundAdapter{
|
||||
myInboundAdapter: myInboundAdapter{
|
||||
protocol: C.TypeRedirect,
|
||||
network: []string{N.NetworkTCP},
|
||||
ctx: ctx,
|
||||
@ -29,9 +40,27 @@ func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
tag: tag,
|
||||
listenOptions: options.ListenOptions,
|
||||
},
|
||||
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
|
||||
return redirect, nil
|
||||
}
|
||||
|
||||
func (r *Redirect) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
@ -42,3 +71,72 @@ 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 {
|
||||
myUid := os.Getuid()
|
||||
tcpPort := M.AddrPortFromNet(r.tcpListener.Addr()).Port()
|
||||
interfaceRules := 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 sing-box -p tcp -j RETURN -d " + it.String()
|
||||
})
|
||||
})
|
||||
return r.runAndroidShell(`
|
||||
set -e -o pipefail
|
||||
iptables -t nat -N sing-box
|
||||
` + strings.Join(interfaceRules, "\n") + `
|
||||
iptables -t nat -A sing-box -j RETURN -m owner --uid-owner ` + F.ToString(myUid) + `
|
||||
iptables -t nat -A sing-box -p tcp -j REDIRECT --to-ports ` + F.ToString(tcpPort) + `
|
||||
iptables -t nat -A OUTPUT -p tcp -j sing-box
|
||||
`)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -2,6 +2,12 @@ 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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user