From 412701d4c505bd0a22065f6f673aba2d36067f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 11 Nov 2024 16:23:45 +0800 Subject: [PATCH] refactor: Platform Interfaces --- adapter/network.go | 19 ++ adapter/router.go | 9 - common/settings/proxy_darwin.go | 7 +- constant/network.go | 8 + constant/os.go | 2 +- experimental/libbox/config.go | 18 +- experimental/libbox/link_flags_stub.go | 2 +- ...link_flags_linux.go => link_flags_unix.go} | 2 + experimental/libbox/monitor.go | 163 ++++------------ experimental/libbox/platform.go | 16 +- experimental/libbox/platform/interface.go | 5 +- experimental/libbox/service.go | 55 ++++-- go.mod | 6 +- go.sum | 13 +- protocol/direct/loopback_detect.go | 6 +- route/network.go | 179 +++++++++++++----- transport/dhcp/server.go | 21 +- 17 files changed, 271 insertions(+), 260 deletions(-) create mode 100644 constant/network.go rename experimental/libbox/{link_flags_linux.go => link_flags_unix.go} (97%) diff --git a/adapter/network.go b/adapter/network.go index 533bfced..6c09a0a3 100644 --- a/adapter/network.go +++ b/adapter/network.go @@ -9,6 +9,8 @@ type NetworkManager interface { Lifecycle InterfaceFinder() control.InterfaceFinder UpdateInterfaces() error + DefaultNetworkInterface() *NetworkInterface + NetworkInterfaces() []NetworkInterface DefaultInterface() string AutoDetectInterface() bool AutoDetectInterfaceFunc() control.Func @@ -21,3 +23,20 @@ type NetworkManager interface { WIFIState() WIFIState ResetNetwork() } + +type InterfaceUpdateListener interface { + InterfaceUpdated() +} + +type WIFIState struct { + SSID string + BSSID string +} + +type NetworkInterface struct { + control.Interface + Type string + DNSServers []string + Expensive bool + Constrained bool +} diff --git a/adapter/router.go b/adapter/router.go index 6dd39357..a637e506 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -119,12 +119,3 @@ func (c *HTTPStartContext) Close() { client.CloseIdleConnections() } } - -type InterfaceUpdateListener interface { - InterfaceUpdated() -} - -type WIFIState struct { - SSID string - BSSID string -} diff --git a/common/settings/proxy_darwin.go b/common/settings/proxy_darwin.go index 17e82cf5..3c06a853 100644 --- a/common/settings/proxy_darwin.go +++ b/common/settings/proxy_darwin.go @@ -2,7 +2,6 @@ package settings import ( "context" - "net/netip" "strconv" "strings" @@ -77,14 +76,14 @@ func (p *DarwinSystemProxy) update(event int) { } func (p *DarwinSystemProxy) update0() error { - newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified()) - if p.interfaceName == newInterfaceName { + newInterface := p.monitor.DefaultInterface() + if p.interfaceName == newInterface.Name { return nil } if p.interfaceName != "" { _ = p.Disable() } - p.interfaceName = newInterfaceName + p.interfaceName = newInterface.Name interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName) if err != nil { return err diff --git a/constant/network.go b/constant/network.go new file mode 100644 index 00000000..f5ac2a4e --- /dev/null +++ b/constant/network.go @@ -0,0 +1,8 @@ +package constant + +const ( + InterfaceTypeWIFI = "wifi" + InterfaceTypeCellular = "cellular" + InterfaceTypeEthernet = "ethernet" + InterfaceTypeOther = "other" +) diff --git a/constant/os.go b/constant/os.go index 63c6c41c..6142767c 100644 --- a/constant/os.go +++ b/constant/os.go @@ -6,7 +6,7 @@ import ( const IsAndroid = goos.IsAndroid == 1 -const IsDarwin = goos.IsDarwin == 1 +const IsDarwin = goos.IsDarwin == 1 || goos.IsIos == 1 const IsDragonfly = goos.IsDragonfly == 1 diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index a102a3d8..cce45c60 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -74,11 +74,7 @@ func (s *platformInterfaceStub) CreateDefaultInterfaceMonitor(logger logger.Logg return (*interfaceMonitorStub)(nil) } -func (s *platformInterfaceStub) UsePlatformInterfaceGetter() bool { - return true -} - -func (s *platformInterfaceStub) Interfaces() ([]control.Interface, error) { +func (s *platformInterfaceStub) Interfaces() ([]adapter.NetworkInterface, error) { return nil, os.ErrInvalid } @@ -111,16 +107,8 @@ func (s *interfaceMonitorStub) Close() error { return os.ErrInvalid } -func (s *interfaceMonitorStub) DefaultInterfaceName(destination netip.Addr) string { - return "" -} - -func (s *interfaceMonitorStub) DefaultInterfaceIndex(destination netip.Addr) int { - return -1 -} - -func (s *interfaceMonitorStub) DefaultInterface(destination netip.Addr) (string, int) { - return "", -1 +func (s *interfaceMonitorStub) DefaultInterface() *control.Interface { + return nil } func (s *interfaceMonitorStub) OverrideAndroidVPN() bool { diff --git a/experimental/libbox/link_flags_stub.go b/experimental/libbox/link_flags_stub.go index 306754f3..ce3d1eb8 100644 --- a/experimental/libbox/link_flags_stub.go +++ b/experimental/libbox/link_flags_stub.go @@ -1,4 +1,4 @@ -//go:build !linux +//go:build !unix package libbox diff --git a/experimental/libbox/link_flags_linux.go b/experimental/libbox/link_flags_unix.go similarity index 97% rename from experimental/libbox/link_flags_linux.go rename to experimental/libbox/link_flags_unix.go index aa9ad46d..04f41d64 100644 --- a/experimental/libbox/link_flags_linux.go +++ b/experimental/libbox/link_flags_unix.go @@ -1,3 +1,5 @@ +//go:build unix + package libbox import ( diff --git a/experimental/libbox/monitor.go b/experimental/libbox/monitor.go index 18c8b009..8f401971 100644 --- a/experimental/libbox/monitor.go +++ b/experimental/libbox/monitor.go @@ -1,15 +1,10 @@ package libbox import ( - "net" - "net/netip" - "sync" - "github.com/sagernet/sing-tun" - "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" - M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/x/list" ) @@ -20,19 +15,9 @@ var ( type platformDefaultInterfaceMonitor struct { *platformInterfaceWrapper - networkAddresses []networkAddress - defaultInterfaceName string - defaultInterfaceIndex int - element *list.Element[tun.NetworkUpdateCallback] - access sync.Mutex - callbacks list.List[tun.DefaultInterfaceUpdateCallback] - logger logger.Logger -} - -type networkAddress struct { - interfaceName string - interfaceIndex int - addresses []netip.Prefix + element *list.Element[tun.NetworkUpdateCallback] + callbacks list.List[tun.DefaultInterfaceUpdateCallback] + logger logger.Logger } func (m *platformDefaultInterfaceMonitor) Start() error { @@ -43,37 +28,10 @@ func (m *platformDefaultInterfaceMonitor) Close() error { return m.iif.CloseDefaultInterfaceMonitor(m) } -func (m *platformDefaultInterfaceMonitor) DefaultInterfaceName(destination netip.Addr) string { - for _, address := range m.networkAddresses { - for _, prefix := range address.addresses { - if prefix.Contains(destination) { - return address.interfaceName - } - } - } - return m.defaultInterfaceName -} - -func (m *platformDefaultInterfaceMonitor) DefaultInterfaceIndex(destination netip.Addr) int { - for _, address := range m.networkAddresses { - for _, prefix := range address.addresses { - if prefix.Contains(destination) { - return address.interfaceIndex - } - } - } - return m.defaultInterfaceIndex -} - -func (m *platformDefaultInterfaceMonitor) DefaultInterface(destination netip.Addr) (string, int) { - for _, address := range m.networkAddresses { - for _, prefix := range address.addresses { - if prefix.Contains(destination) { - return address.interfaceName, address.interfaceIndex - } - } - } - return m.defaultInterfaceName, m.defaultInterfaceIndex +func (m *platformDefaultInterfaceMonitor) DefaultInterface() *control.Interface { + m.defaultInterfaceAccess.Lock() + defer m.defaultInterfaceAccess.Unlock() + return m.defaultInterface } func (m *platformDefaultInterfaceMonitor) OverrideAndroidVPN() bool { @@ -85,96 +43,49 @@ func (m *platformDefaultInterfaceMonitor) AndroidVPNEnabled() bool { } func (m *platformDefaultInterfaceMonitor) RegisterCallback(callback tun.DefaultInterfaceUpdateCallback) *list.Element[tun.DefaultInterfaceUpdateCallback] { - m.access.Lock() - defer m.access.Unlock() + m.defaultInterfaceAccess.Lock() + defer m.defaultInterfaceAccess.Unlock() return m.callbacks.PushBack(callback) } func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) { - m.access.Lock() - defer m.access.Unlock() + m.defaultInterfaceAccess.Lock() + defer m.defaultInterfaceAccess.Unlock() m.callbacks.Remove(element) } -func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32) { - if interfaceName == "" || interfaceIndex32 == -1 { - m.defaultInterfaceName = "" - m.defaultInterfaceIndex = -1 - m.access.Lock() - callbacks := m.callbacks.Array() - m.access.Unlock() - for _, callback := range callbacks { - callback(tun.EventNoRoute) - } - return - } - var err error - if m.iif.UsePlatformInterfaceGetter() { - err = m.updateInterfacesPlatform() - } else { - err = m.updateInterfaces() - } - if err == nil { - err = m.networkManager.UpdateInterfaces() - } +func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) { + m.isExpensive = isExpensive + m.isConstrained = isConstrained + err := m.networkManager.UpdateInterfaces() if err != nil { m.logger.Error(E.Cause(err, "update interfaces")) } - interfaceIndex := int(interfaceIndex32) - if m.defaultInterfaceName == interfaceName && m.defaultInterfaceIndex == interfaceIndex { + m.defaultInterfaceAccess.Lock() + if interfaceIndex32 == -1 { + m.defaultInterface = nil + callbacks := m.callbacks.Array() + m.defaultInterfaceAccess.Unlock() + for _, callback := range callbacks { + callback(tun.EventInterfaceUpdate) + } + return + } + oldInterface := m.defaultInterface + newInterface, err := m.networkManager.InterfaceFinder().ByIndex(int(interfaceIndex32)) + if err != nil { + m.defaultInterfaceAccess.Unlock() + m.logger.Error(E.Cause(err, "find updated interface: ", interfaceName)) + return + } + m.defaultInterface = newInterface + if oldInterface != nil && oldInterface.Name == m.defaultInterface.Name && oldInterface.Index == m.defaultInterface.Index { + m.defaultInterfaceAccess.Unlock() return } - m.defaultInterfaceName = interfaceName - m.defaultInterfaceIndex = interfaceIndex - m.access.Lock() callbacks := m.callbacks.Array() - m.access.Unlock() + m.defaultInterfaceAccess.Unlock() for _, callback := range callbacks { callback(tun.EventInterfaceUpdate) } } - -func (m *platformDefaultInterfaceMonitor) updateInterfaces() error { - interfaces, err := net.Interfaces() - if err != nil { - return err - } - var addresses []networkAddress - for _, iif := range interfaces { - var netAddresses []net.Addr - netAddresses, err = iif.Addrs() - if err != nil { - return err - } - var address networkAddress - address.interfaceName = iif.Name - address.interfaceIndex = iif.Index - address.addresses = common.Map(common.FilterIsInstance(netAddresses, func(it net.Addr) (*net.IPNet, bool) { - value, loaded := it.(*net.IPNet) - return value, loaded - }), func(it *net.IPNet) netip.Prefix { - bits, _ := it.Mask.Size() - return netip.PrefixFrom(M.AddrFromIP(it.IP), bits) - }) - addresses = append(addresses, address) - } - m.networkAddresses = addresses - return nil -} - -func (m *platformDefaultInterfaceMonitor) updateInterfacesPlatform() error { - interfaces, err := m.Interfaces() - if err != nil { - return err - } - var addresses []networkAddress - for _, iif := range interfaces { - var address networkAddress - address.interfaceName = iif.Name - address.interfaceIndex = iif.Index - // address.addresses = common.Map(iif.Addresses, netip.MustParsePrefix) - addresses = append(addresses, address) - } - m.networkAddresses = addresses - return nil -} diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index 1976ad70..a6124345 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -1,6 +1,7 @@ package libbox import ( + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" ) @@ -13,10 +14,8 @@ type PlatformInterface interface { FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error) PackageNameByUid(uid int32) (string, error) UIDByPackageName(packageName string) (int32, error) - UsePlatformDefaultInterfaceMonitor() bool StartDefaultInterfaceMonitor(listener InterfaceUpdateListener) error CloseDefaultInterfaceMonitor(listener InterfaceUpdateListener) error - UsePlatformInterfaceGetter() bool GetInterfaces() (NetworkInterfaceIterator, error) UnderNetworkExtension() bool IncludeAllNetworks() bool @@ -31,15 +30,26 @@ type TunInterface interface { } type InterfaceUpdateListener interface { - UpdateDefaultInterface(interfaceName string, interfaceIndex int32) + UpdateDefaultInterface(interfaceName string, interfaceIndex int32, isExpensive bool, isConstrained bool) } +const ( + InterfaceTypeWIFI = C.InterfaceTypeWIFI + InterfaceTypeCellular = C.InterfaceTypeCellular + InterfaceTypeEthernet = C.InterfaceTypeEthernet + InterfaceTypeOther = C.InterfaceTypeOther +) + type NetworkInterface struct { Index int32 MTU int32 Name string Addresses StringIterator Flags int32 + + Type string + DNSServer StringIterator + Metered bool } type WIFIState struct { diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index 16eab5ab..ef37daea 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -5,7 +5,6 @@ import ( "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" - "github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/logger" ) @@ -14,10 +13,8 @@ type Interface interface { UsePlatformAutoDetectInterfaceControl() bool AutoDetectInterfaceControl(fd int) error OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) - UsePlatformDefaultInterfaceMonitor() bool CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor - UsePlatformInterfaceGetter() bool - Interfaces() ([]control.Interface, error) + Interfaces() ([]adapter.NetworkInterface, error) UnderNetworkExtension() bool IncludeAllNetworks() bool ClearDNSCache() diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 26bbbec5..93274b1f 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -6,6 +6,7 @@ import ( "os" "runtime" runtimeDebug "runtime/debug" + "sync" "syscall" "time" @@ -54,7 +55,10 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box ctx, cancel := context.WithCancel(ctx) urlTestHistoryStorage := urltest.NewHistoryStorage() ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) - platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()} + platformWrapper := &platformInterfaceWrapper{ + iif: platformInterface, + useProcFS: platformInterface.UseProcFS(), + } service.MustRegister[platform.Interface](ctx, platformWrapper) instance, err := box.New(box.Options{ Context: ctx, @@ -106,9 +110,14 @@ var ( ) type platformInterfaceWrapper struct { - iif PlatformInterface - useProcFS bool - networkManager adapter.NetworkManager + iif PlatformInterface + useProcFS bool + networkManager adapter.NetworkManager + myTunName string + defaultInterfaceAccess sync.Mutex + defaultInterface *control.Interface + isExpensive bool + isConstrained bool } func (w *platformInterfaceWrapper) Initialize(networkManager adapter.NetworkManager) error { @@ -148,38 +157,42 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions return nil, E.Cause(err, "dup tun file descriptor") } options.FileDescriptor = dupFd + w.myTunName = options.Name return tun.New(*options) } -func (w *platformInterfaceWrapper) UsePlatformDefaultInterfaceMonitor() bool { - return w.iif.UsePlatformDefaultInterfaceMonitor() -} - func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor { return &platformDefaultInterfaceMonitor{ platformInterfaceWrapper: w, - defaultInterfaceIndex: -1, logger: logger, } } -func (w *platformInterfaceWrapper) UsePlatformInterfaceGetter() bool { - return w.iif.UsePlatformInterfaceGetter() -} - -func (w *platformInterfaceWrapper) Interfaces() ([]control.Interface, error) { +func (w *platformInterfaceWrapper) Interfaces() ([]adapter.NetworkInterface, error) { interfaceIterator, err := w.iif.GetInterfaces() if err != nil { return nil, err } - var interfaces []control.Interface + var interfaces []adapter.NetworkInterface for _, netInterface := range iteratorToArray[*NetworkInterface](interfaceIterator) { - interfaces = append(interfaces, control.Interface{ - Index: int(netInterface.Index), - MTU: int(netInterface.MTU), - Name: netInterface.Name, - Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix), - Flags: linkFlags(uint32(netInterface.Flags)), + if netInterface.Name == w.myTunName { + continue + } + w.defaultInterfaceAccess.Lock() + isDefault := w.defaultInterface != nil && int(netInterface.Index) == w.defaultInterface.Index + w.defaultInterfaceAccess.Unlock() + interfaces = append(interfaces, adapter.NetworkInterface{ + Interface: control.Interface{ + Index: int(netInterface.Index), + MTU: int(netInterface.MTU), + Name: netInterface.Name, + Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix), + Flags: linkFlags(uint32(netInterface.Flags)), + }, + Type: netInterface.Type, + DNSServers: iteratorToArray[string](netInterface.DNSServer), + Expensive: netInterface.Metered || isDefault && w.isExpensive, + Constrained: isDefault && w.isConstrained, }) } return interfaces, nil diff --git a/go.mod b/go.mod index 0a2f7b0c..aaa1b064 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/sing-tun v0.6.0-alpha.4 + github.com/sagernet/sing-tun v0.6.0-alpha.5 github.com/sagernet/sing-vmess v0.1.12 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/utls v1.6.7 @@ -53,10 +53,6 @@ require ( howett.net/plist v1.0.1 ) -replace github.com/sagernet/sing => ../sing - -replace github.com/sagernet/sing-tun => ../sing-tun - require ( github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.0.6 // indirect diff --git a/go.sum b/go.sum index 63709752..44f61a99 100644 --- a/go.sum +++ b/go.sum @@ -109,6 +109,9 @@ github.com/sagernet/quic-go v0.48.1-beta.1 h1:ElPaV5yzlXIKZpqFMAcUGax6vddi3zt4AE github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= +github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= +github.com/sagernet/sing v0.6.0-alpha.4 h1:h9oshzhaY0ESPC9HERcXtT9MhK7Oyo/IWXVu1uIiw3Y= +github.com/sagernet/sing v0.6.0-alpha.4/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing-dns v0.4.0-alpha.1 h1:2KlP8DeqtGkULFiZtvG2r7SuoJP6orANFzJwC5vDKvg= github.com/sagernet/sing-dns v0.4.0-alpha.1/go.mod h1:vgHATsm4wdymwpvBZPei8RY+546iGXS6hlWv2x6YKcM= github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= @@ -121,6 +124,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= +github.com/sagernet/sing-tun v0.6.0-alpha.5 h1:N2m+tzpVgeDxsQzc3tq3Bge2uSRhJ72KXy4Zr03U+Cg= +github.com/sagernet/sing-tun v0.6.0-alpha.5/go.mod h1:5FKJNou4ZfW3HhLoSpRRUc8RT+nsdFTvhJc+4MlBrOo= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= @@ -136,14 +141,8 @@ github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3k github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= @@ -182,7 +181,7 @@ golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/protocol/direct/loopback_detect.go b/protocol/direct/loopback_detect.go index 4bc1be3b..7a62164e 100644 --- a/protocol/direct/loopback_detect.go +++ b/protocol/direct/loopback_detect.go @@ -33,7 +33,7 @@ func (l *loopBackDetector) NewConn(conn net.Conn) net.Conn { } if udpConn, isUDPConn := conn.(abstractUDPConn); isUDPConn { if !source.Addr().IsLoopback() { - _, err := l.networkManager.InterfaceFinder().InterfaceByAddr(source.Addr()) + _, err := l.networkManager.InterfaceFinder().ByAddr(source.Addr()) if err != nil { return conn } @@ -59,7 +59,7 @@ func (l *loopBackDetector) NewPacketConn(conn N.NetPacketConn, destination M.Soc return conn } if !source.Addr().IsLoopback() { - _, err := l.networkManager.InterfaceFinder().InterfaceByAddr(source.Addr()) + _, err := l.networkManager.InterfaceFinder().ByAddr(source.Addr()) if err != nil { return conn } @@ -82,7 +82,7 @@ func (l *loopBackDetector) CheckPacketConn(source netip.AddrPort, local netip.Ad return false } if !source.Addr().IsLoopback() { - _, err := l.networkManager.InterfaceFinder().InterfaceByAddr(source.Addr()) + _, err := l.networkManager.InterfaceFinder().ByAddr(source.Addr()) if err != nil { return false } diff --git a/route/network.go b/route/network.go index b0caa24c..c6253b91 100644 --- a/route/network.go +++ b/route/network.go @@ -3,9 +3,10 @@ package route import ( "context" "errors" - "net/netip" + "net" "os" "runtime" + "strings" "syscall" "github.com/sagernet/sing-box/adapter" @@ -15,33 +16,41 @@ import ( "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/winpowrprof" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" + + "golang.org/x/exp/slices" ) var _ adapter.NetworkManager = (*NetworkManager)(nil) type NetworkManager struct { - logger logger.ContextLogger - interfaceFinder *control.DefaultInterfaceFinder + logger logger.ContextLogger + interfaceFinder *control.DefaultInterfaceFinder + networkInterfaces atomic.TypedValue[[]adapter.NetworkInterface] + autoDetectInterface bool defaultInterface string defaultMark uint32 autoRedirectOutputMark uint32 - networkMonitor tun.NetworkUpdateMonitor - interfaceMonitor tun.DefaultInterfaceMonitor - packageManager tun.PackageManager - powerListener winpowrprof.EventListener - pauseManager pause.Manager - platformInterface platform.Interface - outboundManager adapter.OutboundManager - wifiState adapter.WIFIState - started bool + + networkMonitor tun.NetworkUpdateMonitor + interfaceMonitor tun.DefaultInterfaceMonitor + packageManager tun.PackageManager + powerListener winpowrprof.EventListener + pauseManager pause.Manager + platformInterface platform.Interface + outboundManager adapter.OutboundManager + wifiState adapter.WIFIState + started bool } func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions) (*NetworkManager, error) { @@ -55,7 +64,7 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp platformInterface: service.FromContext[platform.Interface](ctx), outboundManager: service.FromContext[adapter.OutboundManager](ctx), } - usePlatformDefaultInterfaceMonitor := nm.platformInterface != nil && nm.platformInterface.UsePlatformDefaultInterfaceMonitor() + usePlatformDefaultInterfaceMonitor := nm.platformInterface != nil enforceInterfaceMonitor := routeOptions.AutoDetectInterface if !usePlatformDefaultInterfaceMonitor { networkMonitor, err := tun.NewNetworkUpdateMonitor(logger) @@ -90,17 +99,17 @@ func (r *NetworkManager) Start(stage adapter.StartStage) error { monitor := taskmonitor.New(r.logger, C.StartTimeout) switch stage { case adapter.StartStateInitialize: - if r.interfaceMonitor != nil { - monitor.Start("initialize interface monitor") - err := r.interfaceMonitor.Start() + if r.networkMonitor != nil { + monitor.Start("initialize network monitor") + err := r.networkMonitor.Start() monitor.Finish() if err != nil { return err } } - if r.networkMonitor != nil { - monitor.Start("initialize network monitor") - err := r.networkMonitor.Start() + if r.interfaceMonitor != nil { + monitor.Start("initialize interface monitor") + err := r.interfaceMonitor.Start() monitor.Finish() if err != nil { return err @@ -151,20 +160,6 @@ func (r *NetworkManager) Start(stage adapter.StartStage) error { func (r *NetworkManager) Close() error { monitor := taskmonitor.New(r.logger, C.StopTimeout) var err error - if r.interfaceMonitor != nil { - monitor.Start("close interface monitor") - err = E.Append(err, r.interfaceMonitor.Close(), func(err error) error { - return E.Cause(err, "close interface monitor") - }) - monitor.Finish() - } - if r.networkMonitor != nil { - monitor.Start("close network monitor") - err = E.Append(err, r.networkMonitor.Close(), func(err error) error { - return E.Cause(err, "close network monitor") - }) - monitor.Finish() - } if r.packageManager != nil { monitor.Start("close package manager") err = E.Append(err, r.packageManager.Close(), func(err error) error { @@ -179,6 +174,20 @@ func (r *NetworkManager) Close() error { }) monitor.Finish() } + if r.interfaceMonitor != nil { + monitor.Start("close interface monitor") + err = E.Append(err, r.interfaceMonitor.Close(), func(err error) error { + return E.Cause(err, "close interface monitor") + }) + monitor.Finish() + } + if r.networkMonitor != nil { + monitor.Start("close network monitor") + err = E.Append(err, r.networkMonitor.Close(), func(err error) error { + return E.Cause(err, "close network monitor") + }) + monitor.Finish() + } return nil } @@ -187,18 +196,75 @@ func (r *NetworkManager) InterfaceFinder() control.InterfaceFinder { } func (r *NetworkManager) UpdateInterfaces() error { - if r.platformInterface == nil || !r.platformInterface.UsePlatformInterfaceGetter() { + if r.platformInterface == nil { return r.interfaceFinder.Update() } else { interfaces, err := r.platformInterface.Interfaces() if err != nil { return err } - r.interfaceFinder.UpdateInterfaces(interfaces) + if C.IsDarwin { + err = r.interfaceFinder.Update() + if err != nil { + return err + } + // NEInterface only provides name,index and type + interfaces = common.Map(interfaces, func(it adapter.NetworkInterface) adapter.NetworkInterface { + iif, _ := r.interfaceFinder.ByIndex(it.Index) + if iif != nil { + it.Interface = *iif + } + return it + }) + } else { + r.interfaceFinder.UpdateInterfaces(common.Map(interfaces, func(it adapter.NetworkInterface) control.Interface { return it.Interface })) + } + oldInterfaces := r.networkInterfaces.Load() + newInterfaces := common.Filter(interfaces, func(it adapter.NetworkInterface) bool { + return it.Flags&net.FlagUp != 0 + }) + r.networkInterfaces.Store(newInterfaces) + if !slices.EqualFunc(oldInterfaces, newInterfaces, func(oldInterface adapter.NetworkInterface, newInterface adapter.NetworkInterface) bool { + return oldInterface.Interface.Index == newInterface.Interface.Index && + oldInterface.Interface.Name == newInterface.Interface.Name && + oldInterface.Interface.Flags == newInterface.Interface.Flags && + oldInterface.Type == newInterface.Type && + oldInterface.Expensive == newInterface.Expensive && + oldInterface.Constrained == newInterface.Constrained + }) { + r.logger.Info("updated available networks: ", strings.Join(common.Map(newInterfaces, func(it adapter.NetworkInterface) string { + var options []string + options = append(options, F.ToString(it.Type)) + if it.Expensive { + options = append(options, "expensive") + } + if it.Constrained { + options = append(options, "constrained") + } + return F.ToString(it.Name, " (", strings.Join(options, ", "), ")") + }), ", ")) + } return nil } } +func (r *NetworkManager) DefaultNetworkInterface() *adapter.NetworkInterface { + iif := r.interfaceMonitor.DefaultInterface() + if iif == nil { + return nil + } + for _, it := range r.networkInterfaces.Load() { + if it.Interface.Index == iif.Index { + return &it + } + } + return &adapter.NetworkInterface{Interface: *iif} +} + +func (r *NetworkManager) NetworkInterfaces() []adapter.NetworkInterface { + return r.networkInterfaces.Load() +} + func (r *NetworkManager) DefaultInterface() string { return r.defaultInterface } @@ -220,18 +286,17 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func { } return control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) { remoteAddr := M.ParseSocksaddr(address).Addr - if C.IsLinux { - interfaceName, interfaceIndex = r.interfaceMonitor.DefaultInterface(remoteAddr) - if interfaceIndex == -1 { - err = tun.ErrNoRoute - } - } else { - interfaceIndex = r.interfaceMonitor.DefaultInterfaceIndex(remoteAddr) - if interfaceIndex == -1 { - err = tun.ErrNoRoute + if remoteAddr.IsValid() { + iif, err := r.interfaceFinder.ByAddr(remoteAddr) + if err == nil { + return iif.Name, iif.Index, nil } } - return + defaultInterface := r.interfaceMonitor.DefaultInterface() + if defaultInterface == nil { + return "", -1, tun.ErrNoRoute + } + return defaultInterface.Name, defaultInterface.Index, nil }) } } @@ -285,6 +350,12 @@ func (r *NetworkManager) notifyNetworkUpdate(event int) { r.logger.Error("missing default interface") } else { r.pauseManager.NetworkWake() + defaultInterface := r.DefaultNetworkInterface() + if defaultInterface == nil { + panic("invalid interface context") + } + var options []string + options = append(options, F.ToString("index ", defaultInterface.Index)) if C.IsAndroid && r.platformInterface == nil { var vpnStatus string if r.interfaceMonitor.AndroidVPNEnabled() { @@ -292,17 +363,24 @@ func (r *NetworkManager) notifyNetworkUpdate(event int) { } else { vpnStatus = "disabled" } - r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()), ", vpn ", vpnStatus) + options = append(options, "vpn "+vpnStatus) } else { - r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified())) + if defaultInterface.Type != "" { + options = append(options, F.ToString("type ", defaultInterface.Type)) + } + if defaultInterface.Expensive { + options = append(options, "expensive") + } + if defaultInterface.Constrained { + options = append(options, "constrained") + } } + r.logger.Info("updated default interface ", defaultInterface.Name, ", ", strings.Join(options, ", ")) if r.platformInterface != nil { state := r.platformInterface.ReadWIFIState() if state != r.wifiState { r.wifiState = state - if state.SSID == "" && state.BSSID == "" { - r.logger.Info("updated WIFI state: disconnected") - } else { + if state.SSID != "" { r.logger.Info("updated WIFI state: SSID=", state.SSID, ", BSSID=", state.BSSID) } } @@ -312,7 +390,6 @@ func (r *NetworkManager) notifyNetworkUpdate(event int) { if !r.started { return } - r.ResetNetwork() } diff --git a/transport/dhcp/server.go b/transport/dhcp/server.go index dfe33d86..9a06ac17 100644 --- a/transport/dhcp/server.go +++ b/transport/dhcp/server.go @@ -119,18 +119,19 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, return nil, err } -func (t *Transport) fetchInterface() (*net.Interface, error) { - interfaceName := t.interfaceName +func (t *Transport) fetchInterface() (*control.Interface, error) { if t.autoInterface { if t.networkManager.InterfaceMonitor() == nil { return nil, E.New("missing monitor for auto DHCP, set route.auto_detect_interface") } - interfaceName = t.networkManager.InterfaceMonitor().DefaultInterfaceName(netip.Addr{}) + defaultInterface := t.networkManager.InterfaceMonitor().DefaultInterface() + if defaultInterface == nil { + return nil, E.New("missing default interface") + } + return defaultInterface, nil + } else { + return t.networkManager.InterfaceFinder().ByName(t.interfaceName) } - if interfaceName == "" { - return nil, E.New("missing default interface") - } - return net.InterfaceByName(interfaceName) } func (t *Transport) fetchServers() error { @@ -172,7 +173,7 @@ func (t *Transport) interfaceUpdated(int) { } } -func (t *Transport) fetchServers0(ctx context.Context, iface *net.Interface) error { +func (t *Transport) fetchServers0(ctx context.Context, iface *control.Interface) error { var listener net.ListenConfig listener.Control = control.Append(listener.Control, control.BindToInterface(t.networkManager.InterfaceFinder(), iface.Name, iface.Index)) listener.Control = control.Append(listener.Control, control.ReuseAddr()) @@ -206,7 +207,7 @@ func (t *Transport) fetchServers0(ctx context.Context, iface *net.Interface) err return group.Run(ctx) } -func (t *Transport) fetchServersResponse(iface *net.Interface, packetConn net.PacketConn, transactionID dhcpv4.TransactionID) error { +func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn net.PacketConn, transactionID dhcpv4.TransactionID) error { buffer := buf.NewSize(dhcpv4.MaxMessageSize) defer buffer.Release() @@ -246,7 +247,7 @@ func (t *Transport) fetchServersResponse(iface *net.Interface, packetConn net.Pa } } -func (t *Transport) recreateServers(iface *net.Interface, serverAddrs []netip.Addr) error { +func (t *Transport) recreateServers(iface *control.Interface, serverAddrs []netip.Addr) error { if len(serverAddrs) > 0 { t.options.Logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, func(it netip.Addr) string { return it.String()