Prechádzať zdrojové kódy

Add set system proxy support for macOS

世界 3 rokov pred
rodič
commit
f691bd5ce1

+ 3 - 3
adapter/router.go

@@ -7,6 +7,7 @@ import (
 
 	"github.com/sagernet/sing-box/common/geoip"
 	"github.com/sagernet/sing-dns"
+	"github.com/sagernet/sing-tun"
 	"github.com/sagernet/sing/common/control"
 	N "github.com/sagernet/sing/common/network"
 
@@ -33,10 +34,9 @@ type Router interface {
 	InterfaceBindManager() control.BindManager
 	DefaultInterface() string
 	AutoDetectInterface() bool
-	AutoDetectInterfaceName() string
-	AutoDetectInterfaceIndex() int
 	DefaultMark() int
-
+	NetworkMonitor() tun.NetworkUpdateMonitor
+	InterfaceMonitor() tun.DefaultInterfaceMonitor
 	Rules() []Rule
 	SetTrafficController(controller TrafficController)
 }

+ 1 - 0
box.go

@@ -90,6 +90,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
 		logFactory.NewLogger("dns"),
 		common.PtrValueOrDefault(options.Route),
 		common.PtrValueOrDefault(options.DNS),
+		options.Inbounds,
 	)
 	if err != nil {
 		return nil, E.Cause(err, "parse route options")

+ 16 - 16
common/dialer/default.go

@@ -61,27 +61,27 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
 	var listener net.ListenConfig
 	if options.BindInterface != "" {
 		warnBindInterfaceOnUnsupportedPlatform.Check()
-		dialer.Control = control.Append(dialer.Control, control.BindToInterface(router.InterfaceBindManager(), options.BindInterface))
-		listener.Control = control.Append(listener.Control, control.BindToInterface(router.InterfaceBindManager(), options.BindInterface))
+		bindFunc := control.BindToInterface(router.InterfaceBindManager(), options.BindInterface)
+		dialer.Control = control.Append(dialer.Control, bindFunc)
+		listener.Control = control.Append(listener.Control, bindFunc)
 	} else if router.AutoDetectInterface() {
 		if C.IsWindows {
-			dialer.Control = control.Append(dialer.Control, control.BindToInterfaceIndexFunc(func() int {
-				return router.AutoDetectInterfaceIndex()
-			}))
-			listener.Control = control.Append(listener.Control, control.BindToInterfaceIndexFunc(func() int {
-				return router.AutoDetectInterfaceIndex()
-			}))
+			bindFunc := control.BindToInterfaceIndexFunc(func() int {
+				return router.InterfaceMonitor().DefaultInterfaceIndex()
+			})
+			dialer.Control = control.Append(dialer.Control, bindFunc)
+			listener.Control = control.Append(listener.Control, bindFunc)
 		} else {
-			dialer.Control = control.Append(dialer.Control, control.BindToInterfaceFunc(router.InterfaceBindManager(), func() string {
-				return router.AutoDetectInterfaceName()
-			}))
-			listener.Control = control.Append(listener.Control, control.BindToInterfaceFunc(router.InterfaceBindManager(), func() string {
-				return router.AutoDetectInterfaceName()
-			}))
+			bindFunc := control.BindToInterfaceFunc(router.InterfaceBindManager(), func() string {
+				return router.InterfaceMonitor().DefaultInterfaceName()
+			})
+			dialer.Control = control.Append(dialer.Control, bindFunc)
+			listener.Control = control.Append(listener.Control, bindFunc)
 		}
 	} else if router.DefaultInterface() != "" {
-		dialer.Control = control.Append(dialer.Control, control.BindToInterface(router.InterfaceBindManager(), router.DefaultInterface()))
-		listener.Control = control.Append(listener.Control, control.BindToInterface(router.InterfaceBindManager(), router.DefaultInterface()))
+		bindFunc := control.BindToInterface(router.InterfaceBindManager(), router.DefaultInterface())
+		dialer.Control = control.Append(dialer.Control, bindFunc)
+		listener.Control = control.Append(listener.Control, bindFunc)
 	}
 	if options.RoutingMark != 0 {
 		warnRoutingMarkOnUnsupportedPlatform.Check()

+ 6 - 0
common/settings/command.go

@@ -13,3 +13,9 @@ func runCommand(name string, args ...string) error {
 	command.Stderr = os.Stderr
 	return command.Run()
 }
+
+func readCommand(name string, args ...string) ([]byte, error) {
+	command := exec.Command(name, args...)
+	command.Env = os.Environ()
+	return command.CombinedOutput()
+}

+ 9 - 6
common/settings/proxy_android.go

@@ -4,6 +4,7 @@ import (
 	"os"
 	"strings"
 
+	"github.com/sagernet/sing-box/adapter"
 	C "github.com/sagernet/sing-box/constant"
 	F "github.com/sagernet/sing/common/format"
 )
@@ -30,10 +31,12 @@ func runAndroidShell(name string, args ...string) error {
 	}
 }
 
-func ClearSystemProxy() error {
-	return runAndroidShell("settings", "put", "global", "http_proxy", ":0")
-}
-
-func SetSystemProxy(port uint16, mixed bool) error {
-	return runAndroidShell("settings", "put", "global", "http_proxy", F.ToString("127.0.0.1:", port))
+func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
+	err := runAndroidShell("settings", "put", "global", "http_proxy", F.ToString("127.0.0.1:", port))
+	if err != nil {
+		return nil, err
+	}
+	return func() error {
+		return runAndroidShell("settings", "put", "global", "http_proxy", ":0")
+	}, nil
 }

+ 98 - 0
common/settings/proxy_darwin.go

@@ -0,0 +1,98 @@
+package settings
+
+import (
+	"strings"
+
+	"github.com/sagernet/sing-box/adapter"
+	"github.com/sagernet/sing-tun"
+	E "github.com/sagernet/sing/common/exceptions"
+	F "github.com/sagernet/sing/common/format"
+	"github.com/sagernet/sing/common/x/list"
+)
+
+type systemProxy struct {
+	monitor       tun.DefaultInterfaceMonitor
+	interfaceName string
+	element       *list.Element[tun.DefaultInterfaceUpdateCallback]
+	port          uint16
+	isMixed       bool
+}
+
+func (p *systemProxy) update() error {
+	newInterfaceName := p.monitor.DefaultInterfaceName()
+	if p.interfaceName == newInterfaceName {
+		return nil
+	}
+	if p.interfaceName != "" {
+		_ = p.unset()
+	}
+	p.interfaceName = newInterfaceName
+	interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
+	if err != nil {
+		return err
+	}
+	if p.isMixed {
+		err = runCommand("networksetup", "-setsocksfirewallproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port))
+	}
+	if err == nil {
+		err = runCommand("networksetup", "-setwebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port))
+	}
+	if err == nil {
+		err = runCommand("networksetup", "-setsecurewebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port))
+	}
+	return err
+}
+
+func (p *systemProxy) unset() error {
+	interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
+	if err != nil {
+		return err
+	}
+	if p.isMixed {
+		err = runCommand("networksetup", "-setsocksfirewallproxystate", interfaceDisplayName, "off")
+	}
+	if err == nil {
+		err = runCommand("networksetup", "-setwebproxystate", interfaceDisplayName, "off")
+	}
+	if err == nil {
+		err = runCommand("networksetup", "-setsecurewebproxystate", interfaceDisplayName, "off")
+	}
+	return err
+}
+
+func getInterfaceDisplayName(name string) (string, error) {
+	content, err := readCommand("networksetup", "-listallhardwareports")
+	if err != nil {
+		return "", err
+	}
+	for _, deviceSpan := range strings.Split(string(content), "Ethernet Address") {
+		if strings.Contains(deviceSpan, "Device: "+name) {
+			substr := "Hardware Port: "
+			deviceSpan = deviceSpan[strings.Index(deviceSpan, substr)+len(substr):]
+			deviceSpan = deviceSpan[:strings.Index(deviceSpan, "\n")]
+			return deviceSpan, nil
+		}
+	}
+	return "", E.New(name, " not found in networksetup -listallhardwareports")
+}
+
+func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
+	interfaceMonitor := router.InterfaceMonitor()
+	if interfaceMonitor == nil {
+		return nil, E.New("missing interface monitor")
+	}
+	proxy := &systemProxy{
+		monitor: interfaceMonitor,
+		port:    port,
+		isMixed: isMixed,
+	}
+	err := proxy.update()
+	if err != nil {
+		return nil, err
+	}
+	proxy.element = interfaceMonitor.RegisterCallback(proxy.update)
+	return func() error {
+		interfaceMonitor.UnregisterCallback(proxy.element)
+		return proxy.unset()
+	}, nil
+}

+ 25 - 34
common/settings/proxy_linux.go

@@ -7,7 +7,7 @@ import (
 	"os/exec"
 	"strings"
 
-	"github.com/sagernet/sing-box/log"
+	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing/common"
 	E "github.com/sagernet/sing/common/exceptions"
 	F "github.com/sagernet/sing/common/format"
@@ -35,42 +35,33 @@ func runAsUser(name string, args ...string) error {
 	}
 }
 
-func ClearSystemProxy() error {
-	if hasGSettings {
-		return runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "none")
+func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
+	if !hasGSettings {
+		return nil, E.New("unsupported desktop environment")
 	}
-	return nil
-}
-
-func SetSystemProxy(port uint16, mixed bool) error {
-	if hasGSettings {
-		err := runAsUser("gsettings", "set", "org.gnome.system.proxy.http", "enabled", "true")
-		if err != nil {
-			return err
-		}
-		if mixed {
-			err = setGnomeProxy(port, "ftp", "http", "https", "socks")
-			if err != nil {
-				return err
-			}
-		} else {
-			err = setGnomeProxy(port, "http", "https")
-			if err != nil {
-				return err
-			}
-		}
-		err = runAsUser("gsettings", "set", "org.gnome.system.proxy", "use-same-proxy", F.ToString(mixed))
-		if err != nil {
-			return err
-		}
-		err = runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "manual")
-		if err != nil {
-			return err
-		}
+	err := runAsUser("gsettings", "set", "org.gnome.system.proxy.http", "enabled", "true")
+	if err != nil {
+		return nil, err
+	}
+	if isMixed {
+		err = setGnomeProxy(port, "ftp", "http", "https", "socks")
 	} else {
-		log.Warn("set system proxy: unsupported desktop environment")
+		err = setGnomeProxy(port, "http", "https")
 	}
-	return nil
+	if err != nil {
+		return nil, err
+	}
+	err = runAsUser("gsettings", "set", "org.gnome.system.proxy", "use-same-proxy", F.ToString(isMixed))
+	if err != nil {
+		return nil, err
+	}
+	err = runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "manual")
+	if err != nil {
+		return nil, err
+	}
+	return func() error {
+		return runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "none")
+	}, nil
 }
 
 func setGnomeProxy(port uint16, proxyTypes ...string) error {

+ 7 - 8
common/settings/proxy_stub.go

@@ -1,14 +1,13 @@
-//go:build !windows && !linux
+//go:build !(windows || linux || darwin)
 
 package settings
 
-import "github.com/sagernet/sing-box/log"
+import (
+	"os"
 
-func ClearSystemProxy() error {
-	return nil
-}
+	"github.com/sagernet/sing-box/adapter"
+)
 
-func SetSystemProxy(port uint16, mixed bool) error {
-	log.Warn("set system proxy: unsupported operating system")
-	return nil
+func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
+	return nil, os.ErrInvalid
 }

+ 9 - 6
common/settings/proxy_windows.go

@@ -1,14 +1,17 @@
 package settings
 
 import (
+	"github.com/sagernet/sing-box/adapter"
 	F "github.com/sagernet/sing/common/format"
 	"github.com/sagernet/sing/common/wininet"
 )
 
-func ClearSystemProxy() error {
-	return wininet.ClearSystemProxy()
-}
-
-func SetSystemProxy(port uint16, mixed bool) error {
-	return wininet.SetSystemProxy(F.ToString("http://127.0.0.1:", port), "local")
+func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
+	err := wininet.SetSystemProxy(F.ToString("http://127.0.0.1:", port), "local")
+	if err != nil {
+		return nil, err
+	}
+	return func() error {
+		return wininet.ClearSystemProxy()
+	}, nil
 }

+ 1 - 1
docs/configuration/inbound/http.md

@@ -71,7 +71,7 @@ If `sniff_override_destination` is in effect, its value will be taken as a fallb
 
 !!! error ""
 
-    Windows only
+    Only supported on Linux, Android, Windows, and macOS.
 
 Automatically set system proxy configuration when start and clean up when stop.
 

+ 1 - 1
docs/configuration/inbound/mixed.md

@@ -71,7 +71,7 @@ If `sniff_override_destination` is in effect, its value will be taken as a fallb
 
 !!! error ""
 
-    Windows only
+    Only supported on Linux, Android, Windows, and macOS.
 
 Automatically set system proxy configuration when start and clean up when stop.
 

+ 3 - 3
docs/configuration/route/index.md

@@ -30,7 +30,7 @@ Default outbound tag. the first outbound will be used if empty.
 
 !!! error ""
 
-    Linux and Windows only
+    Only supported on Linux and Windows.
 
 Bind outbound connections to the default NIC by default to prevent routing loops under Tun.
 
@@ -40,7 +40,7 @@ Takes no effect if `outbound.bind_interface` is set.
 
 !!! error ""
 
-    Linux and Windows only
+    Only supported on Linux and Windows.
 
 Bind outbound connections to the specified NIC by default to prevent routing loops under Tun.
 
@@ -50,7 +50,7 @@ Takes no effect if `auto_detect_interface` is set.
 
 !!! error ""
 
-    Linux only
+    Only supported on Linux.
 
 Set iptables routing mark by default.
 

+ 2 - 2
go.mod

@@ -16,7 +16,7 @@ require (
 	github.com/sagernet/sing v0.0.0-20220804023557-9c64b40e7050
 	github.com/sagernet/sing-dns v0.0.0-20220803121532-9e1ffb850d91
 	github.com/sagernet/sing-shadowsocks v0.0.0-20220801112336-a91eacdd01e1
-	github.com/sagernet/sing-tun v0.0.0-20220803112223-a8fd6450d4ed
+	github.com/sagernet/sing-tun v0.0.0-20220804154459-7ee0d19103d2
 	github.com/sagernet/sing-vmess v0.0.0-20220804023624-e829b41c84c2
 	github.com/spf13/cobra v1.5.0
 	github.com/stretchr/testify v1.8.0
@@ -24,7 +24,7 @@ require (
 	go.uber.org/atomic v1.9.0
 	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
 	golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b
-	golang.org/x/sys v0.0.0-20220731174439-a90be440212d
+	golang.org/x/sys v0.0.0-20220803195053-6e608f9ce704
 )
 
 require (

+ 4 - 4
go.sum

@@ -155,8 +155,8 @@ github.com/sagernet/sing-dns v0.0.0-20220803121532-9e1ffb850d91 h1:jxt2PYixIkK2i
 github.com/sagernet/sing-dns v0.0.0-20220803121532-9e1ffb850d91/go.mod h1:T77zZdE2Cm6VqnFumrpwsq+kxYsbq+vWDhmjtdSl/oM=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220801112336-a91eacdd01e1 h1:RYvOc69eSNMN0dwVugrDts41Nn7Ar/C/n/fvytvFcp4=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220801112336-a91eacdd01e1/go.mod h1:NqZjiXszgVCMQ4gVDa2V+drhS8NMfGqUqDF86EacEFc=
-github.com/sagernet/sing-tun v0.0.0-20220803112223-a8fd6450d4ed h1:28qeqeuHLZEkzdcZjYwcCn8y4ckyKimaP+L4P25dqUo=
-github.com/sagernet/sing-tun v0.0.0-20220803112223-a8fd6450d4ed/go.mod h1:jNlPidQzZYkpmpQJ+sDN2YGrPsL4QImoqBpuauId9po=
+github.com/sagernet/sing-tun v0.0.0-20220804154459-7ee0d19103d2 h1:har8hmVNhGxp14zLNAoGrgfzgxZQn0KTYJDfJudj0RU=
+github.com/sagernet/sing-tun v0.0.0-20220804154459-7ee0d19103d2/go.mod h1:K1Hfxaa/1zsxZix3ats3k1TJftVwK0l4OoRnUjjhi0g=
 github.com/sagernet/sing-vmess v0.0.0-20220804023624-e829b41c84c2 h1:C8sc2MYiNx0O7uQ0nieJWq5qYeIHj20XHFWPlcgoQeY=
 github.com/sagernet/sing-vmess v0.0.0-20220804023624-e829b41c84c2/go.mod h1:bNXBqSWYaG3ePl6u0xQY5zneE+ZKa3683ZpuE8S1M1w=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
@@ -279,8 +279,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80=
-golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220803195053-6e608f9ce704 h1:Y7NOhdqIOU8kYI7BxsgL38d0ot0raxvcW+EMQU2QrT4=
+golang.org/x/sys v0.0.0-20220803195053-6e608f9ce704/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

+ 8 - 6
inbound/default.go

@@ -40,7 +40,8 @@ type myInboundAdapter struct {
 
 	// http mixed
 
-	setSystemProxy bool
+	setSystemProxy   bool
+	clearSystemProxy func() error
 
 	// internal
 
@@ -60,10 +61,10 @@ func (a *myInboundAdapter) Tag() string {
 }
 
 func (a *myInboundAdapter) Start() error {
+	var err error
 	bindAddr := M.SocksaddrFrom(netip.Addr(a.listenOptions.Listen), a.listenOptions.ListenPort)
 	if common.Contains(a.network, N.NetworkTCP) {
 		var tcpListener *net.TCPListener
-		var err error
 		if !a.listenOptions.TCPFastOpen {
 			tcpListener, err = net.ListenTCP(M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.TCPAddr())
 		} else {
@@ -77,7 +78,8 @@ func (a *myInboundAdapter) Start() error {
 		a.logger.Info("tcp server started at ", tcpListener.Addr())
 	}
 	if common.Contains(a.network, N.NetworkUDP) {
-		udpConn, err := net.ListenUDP(M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.UDPAddr())
+		var udpConn *net.UDPConn
+		udpConn, err = net.ListenUDP(M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.UDPAddr())
 		if err != nil {
 			return err
 		}
@@ -101,7 +103,7 @@ func (a *myInboundAdapter) Start() error {
 		a.logger.Info("udp server started at ", udpConn.LocalAddr())
 	}
 	if a.setSystemProxy {
-		err := settings.SetSystemProxy(M.SocksaddrFromNet(a.tcpListener.Addr()).Port, a.protocol == C.TypeMixed)
+		a.clearSystemProxy, err = settings.SetSystemProxy(a.router, M.SocksaddrFromNet(a.tcpListener.Addr()).Port, a.protocol == C.TypeMixed)
 		if err != nil {
 			return E.Cause(err, "set system proxy")
 		}
@@ -111,8 +113,8 @@ func (a *myInboundAdapter) Start() error {
 
 func (a *myInboundAdapter) Close() error {
 	var err error
-	if a.setSystemProxy {
-		err = settings.ClearSystemProxy()
+	if a.clearSystemProxy != nil {
+		err = a.clearSystemProxy()
 	}
 	return E.Errors(err, common.Close(
 		common.PtrOrNil(a.tcpListener),

+ 0 - 22
route/iface.go

@@ -1,22 +0,0 @@
-package route
-
-import "github.com/sagernet/sing/common/x/list"
-
-type (
-	NetworkUpdateCallback          = func() error
-	DefaultInterfaceUpdateCallback = func()
-)
-
-type NetworkUpdateMonitor interface {
-	Start() error
-	Close() error
-	RegisterCallback(callback NetworkUpdateCallback) *list.Element[NetworkUpdateCallback]
-	UnregisterCallback(element *list.Element[NetworkUpdateCallback])
-}
-
-type DefaultInterfaceMonitor interface {
-	Start() error
-	Close() error
-	DefaultInterfaceName() string
-	DefaultInterfaceIndex() int
-}

+ 0 - 17
route/iface_stub.go

@@ -1,17 +0,0 @@
-//go:build !(linux || windows) || no_gvisor
-
-package route
-
-import (
-	"os"
-
-	E "github.com/sagernet/sing/common/exceptions"
-)
-
-func NewNetworkUpdateMonitor(errorHandler E.Handler) (NetworkUpdateMonitor, error) {
-	return nil, os.ErrInvalid
-}
-
-func NewDefaultInterfaceMonitor(networkMonitor NetworkUpdateMonitor, callback DefaultInterfaceUpdateCallback) (DefaultInterfaceMonitor, error) {
-	return nil, os.ErrInvalid
-}

+ 0 - 16
route/iface_tun.go

@@ -1,16 +0,0 @@
-//go:build (linux || windows) && !no_gvisor
-
-package route
-
-import (
-	"github.com/sagernet/sing-tun"
-	E "github.com/sagernet/sing/common/exceptions"
-)
-
-func NewNetworkUpdateMonitor(errorHandler E.Handler) (NetworkUpdateMonitor, error) {
-	return tun.NewNetworkUpdateMonitor(errorHandler)
-}
-
-func NewDefaultInterfaceMonitor(networkMonitor NetworkUpdateMonitor, callback DefaultInterfaceUpdateCallback) (DefaultInterfaceMonitor, error) {
-	return tun.NewDefaultInterfaceMonitor(networkMonitor, callback)
-}

+ 26 - 23
route/router.go

@@ -25,6 +25,7 @@ import (
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-dns"
+	"github.com/sagernet/sing-tun"
 	"github.com/sagernet/sing/common"
 	"github.com/sagernet/sing/common/buf"
 	"github.com/sagernet/sing/common/bufio"
@@ -83,16 +84,16 @@ type Router struct {
 	transports                         []dns.Transport
 	transportMap                       map[string]dns.Transport
 	interfaceBindManager               control.BindManager
-	networkMonitor                     NetworkUpdateMonitor
 	autoDetectInterface                bool
 	defaultInterface                   string
-	interfaceMonitor                   DefaultInterfaceMonitor
 	defaultMark                        int
+	networkMonitor                     tun.NetworkUpdateMonitor
+	interfaceMonitor                   tun.DefaultInterfaceMonitor
 	trafficController                  adapter.TrafficController
 	processSearcher                    process.Searcher
 }
 
-func NewRouter(ctx context.Context, logger log.ContextLogger, dnsLogger log.ContextLogger, options option.RouteOptions, dnsOptions option.DNSOptions) (*Router, error) {
+func NewRouter(ctx context.Context, logger log.ContextLogger, dnsLogger log.ContextLogger, options option.RouteOptions, dnsOptions option.DNSOptions, inbounds []option.Inbound) (*Router, error) {
 	if options.DefaultInterface != "" {
 		warnDefaultInterfaceOnUnsupportedPlatform.Check()
 	}
@@ -231,8 +232,13 @@ func NewRouter(ctx context.Context, logger log.ContextLogger, dnsLogger log.Cont
 	router.transports = transports
 	router.transportMap = transportMap
 
-	if router.interfaceBindManager != nil || options.AutoDetectInterface {
-		networkMonitor, err := NewNetworkUpdateMonitor(router)
+	needInterfaceMonitor := options.AutoDetectInterface ||
+		C.IsDarwin && common.Any(inbounds, func(inbound option.Inbound) bool {
+			return inbound.HTTPOptions.SetSystemProxy || inbound.MixedOptions.SetSystemProxy
+		})
+
+	if router.interfaceBindManager != nil || needInterfaceMonitor {
+		networkMonitor, err := tun.NewNetworkUpdateMonitor(router)
 		if err == nil {
 			router.networkMonitor = networkMonitor
 			if router.interfaceBindManager != nil {
@@ -241,15 +247,18 @@ func NewRouter(ctx context.Context, logger log.ContextLogger, dnsLogger log.Cont
 		}
 	}
 
-	if router.networkMonitor != nil && options.AutoDetectInterface {
-		interfaceMonitor, err := NewDefaultInterfaceMonitor(router.networkMonitor, func() {
-			router.logger.Info("updated default interface ", router.interfaceMonitor.DefaultInterfaceName(), ", index ", router.interfaceMonitor.DefaultInterfaceIndex())
-		})
+	if router.networkMonitor != nil && needInterfaceMonitor {
+		interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor)
 		if err != nil {
 			return nil, E.New("auto_detect_interface unsupported on current platform")
 		}
+		interfaceMonitor.RegisterCallback(func() error {
+			router.logger.Info("updated default interface ", router.interfaceMonitor.DefaultInterfaceName(), ", index ", router.interfaceMonitor.DefaultInterfaceIndex())
+			return nil
+		})
 		router.interfaceMonitor = interfaceMonitor
 	}
+
 	if hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess {
 		searcher, err := process.NewSearcher(logger)
 		if err != nil {
@@ -648,20 +657,6 @@ func (r *Router) DefaultInterface() string {
 	return r.defaultInterface
 }
 
-func (r *Router) AutoDetectInterfaceName() string {
-	if r.interfaceMonitor == nil {
-		return ""
-	}
-	return r.interfaceMonitor.DefaultInterfaceName()
-}
-
-func (r *Router) AutoDetectInterfaceIndex() int {
-	if r.interfaceMonitor == nil {
-		return -1
-	}
-	return r.interfaceMonitor.DefaultInterfaceIndex()
-}
-
 func (r *Router) DefaultMark() int {
 	return r.defaultMark
 }
@@ -670,6 +665,14 @@ func (r *Router) Rules() []adapter.Rule {
 	return r.rules
 }
 
+func (r *Router) NetworkMonitor() tun.NetworkUpdateMonitor {
+	return r.networkMonitor
+}
+
+func (r *Router) InterfaceMonitor() tun.DefaultInterfaceMonitor {
+	return r.interfaceMonitor
+}
+
 func (r *Router) SetTrafficController(controller adapter.TrafficController) {
 	r.trafficController = controller
 }

+ 2 - 2
test/go.mod

@@ -54,7 +54,7 @@ require (
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805 // indirect
 	github.com/sagernet/sing-dns v0.0.0-20220803121532-9e1ffb850d91 // indirect
-	github.com/sagernet/sing-tun v0.0.0-20220803112223-a8fd6450d4ed // indirect
+	github.com/sagernet/sing-tun v0.0.0-20220804154459-7ee0d19103d2 // indirect
 	github.com/sagernet/sing-vmess v0.0.0-20220804023624-e829b41c84c2 // indirect
 	github.com/sirupsen/logrus v1.8.1 // indirect
 	github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
@@ -62,7 +62,7 @@ require (
 	go.uber.org/atomic v1.9.0 // indirect
 	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
 	golang.org/x/mod v0.5.1 // indirect
-	golang.org/x/sys v0.0.0-20220731174439-a90be440212d // indirect
+	golang.org/x/sys v0.0.0-20220803195053-6e608f9ce704 // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
 	golang.org/x/tools v0.1.9 // indirect

+ 4 - 4
test/go.sum

@@ -180,8 +180,8 @@ github.com/sagernet/sing-dns v0.0.0-20220803121532-9e1ffb850d91 h1:jxt2PYixIkK2i
 github.com/sagernet/sing-dns v0.0.0-20220803121532-9e1ffb850d91/go.mod h1:T77zZdE2Cm6VqnFumrpwsq+kxYsbq+vWDhmjtdSl/oM=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220801112336-a91eacdd01e1 h1:RYvOc69eSNMN0dwVugrDts41Nn7Ar/C/n/fvytvFcp4=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220801112336-a91eacdd01e1/go.mod h1:NqZjiXszgVCMQ4gVDa2V+drhS8NMfGqUqDF86EacEFc=
-github.com/sagernet/sing-tun v0.0.0-20220803112223-a8fd6450d4ed h1:28qeqeuHLZEkzdcZjYwcCn8y4ckyKimaP+L4P25dqUo=
-github.com/sagernet/sing-tun v0.0.0-20220803112223-a8fd6450d4ed/go.mod h1:jNlPidQzZYkpmpQJ+sDN2YGrPsL4QImoqBpuauId9po=
+github.com/sagernet/sing-tun v0.0.0-20220804154459-7ee0d19103d2 h1:har8hmVNhGxp14zLNAoGrgfzgxZQn0KTYJDfJudj0RU=
+github.com/sagernet/sing-tun v0.0.0-20220804154459-7ee0d19103d2/go.mod h1:K1Hfxaa/1zsxZix3ats3k1TJftVwK0l4OoRnUjjhi0g=
 github.com/sagernet/sing-vmess v0.0.0-20220804023624-e829b41c84c2 h1:C8sc2MYiNx0O7uQ0nieJWq5qYeIHj20XHFWPlcgoQeY=
 github.com/sagernet/sing-vmess v0.0.0-20220804023624-e829b41c84c2/go.mod h1:bNXBqSWYaG3ePl6u0xQY5zneE+ZKa3683ZpuE8S1M1w=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
@@ -314,8 +314,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220731174439-a90be440212d h1:Sv5ogFZatcgIMMtBSTTAgMYsicp25MXBubjXNDKwm80=
-golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220803195053-6e608f9ce704 h1:Y7NOhdqIOU8kYI7BxsgL38d0ot0raxvcW+EMQU2QrT4=
+golang.org/x/sys v0.0.0-20220803195053-6e608f9ce704/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=