Add auto-redirect
This commit is contained in:
parent
ad763519ff
commit
67a5b408ef
9 changed files with 944 additions and 10 deletions
185
redirect_linux.go
Normal file
185
redirect_linux.go
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/sagernet/nftables"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type autoRedirect struct {
|
||||
tunOptions *Options
|
||||
ctx context.Context
|
||||
handler Handler
|
||||
logger logger.Logger
|
||||
tableName string
|
||||
customRedirectPortFunc func() int
|
||||
customRedirectPort int
|
||||
redirectServer *redirectServer
|
||||
enableIPv4 bool
|
||||
enableIPv6 bool
|
||||
iptablesPath string
|
||||
ip6tablesPath string
|
||||
useNFTables bool
|
||||
androidSu bool
|
||||
suPath string
|
||||
}
|
||||
|
||||
func NewAutoRedirect(options AutoRedirectOptions) (AutoRedirect, error) {
|
||||
r := &autoRedirect{
|
||||
tunOptions: options.TunOptions,
|
||||
ctx: options.Context,
|
||||
handler: options.Handler,
|
||||
logger: options.Logger,
|
||||
useNFTables: runtime.GOOS != "android" && !options.DisableNFTables,
|
||||
customRedirectPortFunc: options.CustomRedirectPort,
|
||||
}
|
||||
var err error
|
||||
if runtime.GOOS == "android" {
|
||||
r.enableIPv4 = true
|
||||
r.iptablesPath = "/system/bin/iptables"
|
||||
userId := os.Getuid()
|
||||
if userId != 0 {
|
||||
r.androidSu = true
|
||||
for _, suPath := range []string{
|
||||
"su",
|
||||
"/system/bin/su",
|
||||
} {
|
||||
r.suPath, err = exec.LookPath(suPath)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, E.Extend(E.Cause(err, "root permission is required for auto redirect"), os.Getenv("PATH"))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if r.useNFTables {
|
||||
err = r.initializeNFTables()
|
||||
if err != nil && err != os.ErrInvalid {
|
||||
r.logger.Debug("device has no nftables support: ", err)
|
||||
}
|
||||
}
|
||||
if len(r.tunOptions.Inet4Address) > 0 {
|
||||
r.enableIPv4 = true
|
||||
if !r.useNFTables {
|
||||
r.iptablesPath, err = exec.LookPath("iptables")
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "iptables is required")
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(r.tunOptions.Inet6Address) > 0 {
|
||||
r.enableIPv6 = true
|
||||
if !r.useNFTables {
|
||||
r.ip6tablesPath, err = exec.LookPath("ip6tables")
|
||||
if err != nil {
|
||||
if !r.enableIPv4 {
|
||||
return nil, E.Cause(err, "ip6tables is required")
|
||||
} else {
|
||||
r.enableIPv6 = false
|
||||
r.logger.Error("device has no ip6tables nat support: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *autoRedirect) Start() error {
|
||||
if r.customRedirectPortFunc != nil {
|
||||
r.customRedirectPort = r.customRedirectPortFunc()
|
||||
}
|
||||
if r.customRedirectPort == 0 {
|
||||
var listenAddr netip.Addr
|
||||
if runtime.GOOS == "android" {
|
||||
listenAddr = netip.AddrFrom4([4]byte{127, 0, 0, 1})
|
||||
} else if r.enableIPv6 {
|
||||
listenAddr = netip.IPv6Unspecified()
|
||||
} else {
|
||||
listenAddr = netip.IPv4Unspecified()
|
||||
}
|
||||
server := newRedirectServer(r.ctx, r.handler, r.logger, listenAddr)
|
||||
err := server.Start()
|
||||
if err != nil {
|
||||
return E.Cause(err, "start redirect server")
|
||||
}
|
||||
r.redirectServer = server
|
||||
}
|
||||
return r.setupTables()
|
||||
}
|
||||
|
||||
func (r *autoRedirect) Close() error {
|
||||
r.cleanupTables()
|
||||
return common.Close(
|
||||
common.PtrOrNil(r.redirectServer),
|
||||
)
|
||||
}
|
||||
|
||||
func (r *autoRedirect) initializeNFTables() error {
|
||||
nft, err := nftables.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer nft.CloseLasting()
|
||||
_, err = nft.ListTablesOfFamily(unix.AF_INET)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.useNFTables = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *autoRedirect) redirectPort() uint16 {
|
||||
if r.customRedirectPort > 0 {
|
||||
return uint16(r.customRedirectPort)
|
||||
}
|
||||
return M.AddrPortFromNet(r.redirectServer.listener.Addr()).Port()
|
||||
}
|
||||
|
||||
func (r *autoRedirect) setupTables() error {
|
||||
var setupTables func(int) error
|
||||
if r.useNFTables {
|
||||
setupTables = r.setupNFTables
|
||||
} else {
|
||||
setupTables = r.setupIPTables
|
||||
}
|
||||
if r.enableIPv4 {
|
||||
err := setupTables(unix.AF_INET)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if r.enableIPv6 {
|
||||
err := setupTables(unix.AF_INET6)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *autoRedirect) cleanupTables() {
|
||||
var cleanupTables func(int)
|
||||
if r.useNFTables {
|
||||
cleanupTables = r.cleanupNFTables
|
||||
} else {
|
||||
cleanupTables = r.cleanupIPTables
|
||||
}
|
||||
if r.enableIPv4 {
|
||||
cleanupTables(unix.AF_INET)
|
||||
}
|
||||
if r.enableIPv6 {
|
||||
cleanupTables(unix.AF_INET6)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue