mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
Add per app proxy support for auto redirect
This commit is contained in:
parent
116492cb46
commit
c0341118ec
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/process"
|
"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-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
@ -97,6 +98,14 @@ func (s *platformInterfaceStub) FindProcessInfo(ctx context.Context, network str
|
|||||||
return nil, os.ErrInvalid
|
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{}
|
type interfaceMonitorStub struct{}
|
||||||
|
|
||||||
func (s *interfaceMonitorStub) Start() error {
|
func (s *interfaceMonitorStub) Start() error {
|
||||||
|
@ -22,6 +22,8 @@ type PlatformInterface interface {
|
|||||||
IncludeAllNetworks() bool
|
IncludeAllNetworks() bool
|
||||||
ReadWIFIState() *WIFIState
|
ReadWIFIState() *WIFIState
|
||||||
ClearDNSCache()
|
ClearDNSCache()
|
||||||
|
PerAppProxyList() (IntegerIterator, error)
|
||||||
|
PerAppProxyMode() int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type TunInterface interface {
|
type TunInterface interface {
|
||||||
@ -54,6 +56,11 @@ type NetworkInterfaceIterator interface {
|
|||||||
HasNext() bool
|
HasNext() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IntegerIterator interface {
|
||||||
|
Next() int32
|
||||||
|
HasNext() bool
|
||||||
|
}
|
||||||
|
|
||||||
type OnDemandRule interface {
|
type OnDemandRule interface {
|
||||||
Target() int32
|
Target() int32
|
||||||
DNSSearchDomainMatch() StringIterator
|
DNSSearchDomainMatch() StringIterator
|
||||||
|
@ -11,6 +11,12 @@ import (
|
|||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PerAppProxyModeDisabled int32 = iota
|
||||||
|
PerAppProxyModeExclude
|
||||||
|
PerAppProxyModeInclude
|
||||||
|
)
|
||||||
|
|
||||||
type Interface interface {
|
type Interface interface {
|
||||||
Initialize(ctx context.Context, router adapter.Router) error
|
Initialize(ctx context.Context, router adapter.Router) error
|
||||||
UsePlatformAutoDetectInterfaceControl() bool
|
UsePlatformAutoDetectInterfaceControl() bool
|
||||||
@ -24,5 +30,7 @@ type Interface interface {
|
|||||||
IncludeAllNetworks() bool
|
IncludeAllNetworks() bool
|
||||||
ClearDNSCache()
|
ClearDNSCache()
|
||||||
ReadWIFIState() adapter.WIFIState
|
ReadWIFIState() adapter.WIFIState
|
||||||
|
PerAppProxyList() ([]uint32, error)
|
||||||
|
PerAppProxyMode() int32
|
||||||
process.Searcher
|
process.Searcher
|
||||||
}
|
}
|
||||||
|
@ -229,6 +229,18 @@ func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState {
|
|||||||
return (adapter.WIFIState)(*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 {
|
func (w *platformInterfaceWrapper) DisableColors() bool {
|
||||||
return runtime.GOOS != "android"
|
return runtime.GOOS != "android"
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
|
|||||||
case C.TypeTun:
|
case C.TypeTun:
|
||||||
return NewTun(ctx, router, logger, options.Tag, options.TunOptions, platformInterface)
|
return NewTun(ctx, router, logger, options.Tag, options.TunOptions, platformInterface)
|
||||||
case C.TypeRedirect:
|
case C.TypeRedirect:
|
||||||
return NewRedirect(ctx, router, logger, options.Tag, options.RedirectOptions)
|
return NewRedirect(ctx, router, logger, options.Tag, options.RedirectOptions, platformInterface)
|
||||||
case C.TypeTProxy:
|
case C.TypeTProxy:
|
||||||
return NewTProxy(ctx, router, logger, options.Tag, options.TProxyOptions), nil
|
return NewTProxy(ctx, router, logger, options.Tag, options.TProxyOptions), nil
|
||||||
case C.TypeDirect:
|
case C.TypeDirect:
|
||||||
|
@ -7,11 +7,13 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/redir"
|
"github.com/sagernet/sing-box/common/redir"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
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/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
@ -24,12 +26,13 @@ import (
|
|||||||
|
|
||||||
type Redirect struct {
|
type Redirect struct {
|
||||||
myInboundAdapter
|
myInboundAdapter
|
||||||
|
platformInterface platform.Interface
|
||||||
autoRedirect option.AutoRedirectOptions
|
autoRedirect option.AutoRedirectOptions
|
||||||
needSu bool
|
needSu bool
|
||||||
suPath string
|
suPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RedirectInboundOptions) (*Redirect, error) {
|
func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RedirectInboundOptions, platformInterface platform.Interface) (*Redirect, error) {
|
||||||
redirect := &Redirect{
|
redirect := &Redirect{
|
||||||
myInboundAdapter: myInboundAdapter{
|
myInboundAdapter: myInboundAdapter{
|
||||||
protocol: C.TypeRedirect,
|
protocol: C.TypeRedirect,
|
||||||
@ -40,6 +43,7 @@ func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
tag: tag,
|
tag: tag,
|
||||||
listenOptions: options.ListenOptions,
|
listenOptions: options.ListenOptions,
|
||||||
},
|
},
|
||||||
|
platformInterface: platformInterface,
|
||||||
autoRedirect: common.PtrValueOrDefault(options.AutoRedirect),
|
autoRedirect: common.PtrValueOrDefault(options.AutoRedirect),
|
||||||
}
|
}
|
||||||
if redirect.autoRedirect.Enabled {
|
if redirect.autoRedirect.Enabled {
|
||||||
@ -101,21 +105,73 @@ func (r *Redirect) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Redirect) setupRedirect() error {
|
func (r *Redirect) setupRedirect() error {
|
||||||
myUid := os.Getuid()
|
tableName := "sing-box"
|
||||||
tcpPort := M.AddrPortFromNet(r.tcpListener.Addr()).Port()
|
rules := `
|
||||||
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
|
set -e -o pipefail
|
||||||
iptables -t nat -N sing-box
|
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) + `
|
rules += strings.Join(common.FlatMap(r.router.(adapter.Router).InterfaceFinder().Interfaces(), func(it control.Interface) []string {
|
||||||
iptables -t nat -A sing-box -p tcp -j REDIRECT --to-ports ` + F.ToString(tcpPort) + `
|
return common.Map(common.Filter(it.Addresses, func(it netip.Prefix) bool { return it.Addr().Is4() }), func(it netip.Prefix) string {
|
||||||
iptables -t nat -A OUTPUT -p tcp -j sing-box
|
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() {
|
func (r *Redirect) cleanupRedirect() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user