mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-06-13 06:48:16 +03:00
Add simple auto redirect for Android
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user