package route import ( "context" "errors" "net" "net/netip" "os" "runtime" "strings" "sync" "syscall" "time" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/settings" "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" "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" "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 { ctx context.Context logger logger.ContextLogger router adapter.Router interfaceFinder *control.DefaultInterfaceFinder networkInterfaces common.TypedValue[[]adapter.NetworkInterface] autoDetectInterface bool defaultOptions adapter.NetworkOptions autoRedirectOutputMark uint32 networkMonitor tun.NetworkUpdateMonitor interfaceMonitor tun.DefaultInterfaceMonitor packageManager tun.PackageManager powerListener winpowrprof.EventListener pauseManager pause.Manager platformInterface adapter.PlatformInterface connectionManager adapter.ConnectionManager endpoint adapter.EndpointManager inbound adapter.InboundManager outbound adapter.OutboundManager needWIFIState bool wifiMonitor settings.WIFIMonitor wifiState adapter.WIFIState networkEnvironment uint64 stateAccess sync.RWMutex environmentUpdateAccess sync.Mutex environmentUpdateTimer *time.Timer startedCtx context.Context interfaceUpdateAccess sync.Mutex interfaceUpdateCancel context.CancelFunc networkResetPending bool resetRunAccess sync.Mutex powerUpdateAccess sync.Mutex powerUpdateCancel context.CancelFunc } func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, options option.RouteOptions, dnsOptions option.DNSOptions) (*NetworkManager, error) { defaultDomainResolver := common.PtrValueOrDefault(options.DefaultDomainResolver) if options.AutoDetectInterface && !(C.IsLinux || C.IsDarwin || C.IsWindows) { return nil, E.New("`auto_detect_interface` is only supported on Linux, Windows and macOS") } else if options.OverrideAndroidVPN && !C.IsAndroid { return nil, E.New("`override_android_vpn` is only supported on Android") } else if options.DefaultInterface != "" && !(C.IsLinux || C.IsDarwin || C.IsWindows) { return nil, E.New("`default_interface` is only supported on Linux, Windows and macOS") } else if options.DefaultMark != 0 && !C.IsLinux { return nil, E.New("`default_mark` is only supported on linux") } nm := &NetworkManager{ ctx: ctx, logger: logger, interfaceFinder: control.NewDefaultInterfaceFinder(), autoDetectInterface: options.AutoDetectInterface, defaultOptions: adapter.NetworkOptions{ BindInterface: options.DefaultInterface, RoutingMark: uint32(options.DefaultMark), DomainResolver: defaultDomainResolver.Server, DomainResolveOptions: adapter.DNSQueryOptions{ Strategy: C.DomainStrategy(defaultDomainResolver.Strategy), Timeout: time.Duration(defaultDomainResolver.Timeout), DisableCache: defaultDomainResolver.DisableCache, DisableOptimisticCache: defaultDomainResolver.DisableOptimisticCache, RewriteTTL: defaultDomainResolver.RewriteTTL, ClientSubnet: defaultDomainResolver.ClientSubnet.Build(netip.Prefix{}), }, NetworkStrategy: (*C.NetworkStrategy)(options.DefaultNetworkStrategy), NetworkType: common.Map(options.DefaultNetworkType, option.InterfaceType.Build), FallbackNetworkType: common.Map(options.DefaultFallbackNetworkType, option.InterfaceType.Build), FallbackDelay: time.Duration(options.DefaultFallbackDelay), }, pauseManager: service.FromContext[pause.Manager](ctx), platformInterface: service.FromContext[adapter.PlatformInterface](ctx), connectionManager: service.FromContext[adapter.ConnectionManager](ctx), endpoint: service.FromContext[adapter.EndpointManager](ctx), inbound: service.FromContext[adapter.InboundManager](ctx), outbound: service.FromContext[adapter.OutboundManager](ctx), needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule), } if options.DefaultNetworkStrategy != nil { if options.DefaultInterface != "" { return nil, E.New("`default_network_strategy` is conflict with `default_interface`") } if !options.AutoDetectInterface { return nil, E.New("`auto_detect_interface` is required by `default_network_strategy`") } } usePlatformDefaultInterfaceMonitor := nm.platformInterface != nil && nm.platformInterface.UsePlatformDefaultInterfaceMonitor() enforceInterfaceMonitor := options.AutoDetectInterface if !usePlatformDefaultInterfaceMonitor { networkMonitor, err := tun.NewNetworkUpdateMonitor(logger) if !((err != nil && !enforceInterfaceMonitor) || errors.Is(err, os.ErrInvalid)) { if err != nil { return nil, E.Cause(err, "create network monitor") } nm.networkMonitor = networkMonitor networkMonitor.RegisterCallback(nm.postUpdateNetworkEnvironment) interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(nm.networkMonitor, logger, tun.DefaultInterfaceMonitorOptions{ InterfaceFinder: nm.interfaceFinder, OverrideAndroidVPN: options.OverrideAndroidVPN, UnderNetworkExtension: nm.platformInterface != nil && nm.platformInterface.UnderNetworkExtension(), }) if err != nil { return nil, E.New("auto_detect_interface unsupported on current platform") } nm.interfaceMonitor = interfaceMonitor } } else { nm.interfaceMonitor = nm.platformInterface.CreateDefaultInterfaceMonitor(logger) } return nm, nil } func (r *NetworkManager) Start(stage adapter.StartStage, scope *adapter.Scope) error { monitor := taskmonitor.New(r.logger, C.StartTimeout) switch stage { case adapter.StartStateInitialize: r.router = service.FromContext[adapter.Router](r.ctx) scope.Add(func() error { r.environmentUpdateAccess.Lock() if r.environmentUpdateTimer != nil { r.environmentUpdateTimer.Stop() } r.environmentUpdateAccess.Unlock() return nil }) if r.networkMonitor != nil { monitor.Start("initialize network monitor") err := r.networkMonitor.Start() monitor.Finish() if err != nil { return err } scope.Add(r.networkMonitor.Close) } if r.interfaceMonitor != nil { monitor.Start("initialize interface monitor") err := r.interfaceMonitor.Start() monitor.Finish() if err != nil { return err } scope.Add(r.interfaceMonitor.Close) interfaceUpdateElement := r.interfaceMonitor.RegisterCallback(r.notifyInterfaceUpdate) scope.Add(func() error { r.interfaceMonitor.UnregisterCallback(interfaceUpdateElement) return nil }) } case adapter.StartStateStart: if C.IsAndroid && r.platformInterface == nil { monitor.Start("initialize package manager") packageManager, err := tun.NewPackageManager(tun.PackageManagerOptions{ Callback: r, Logger: r.logger, }) monitor.Finish() if err != nil { return E.Cause(err, "create package manager") } monitor.Start("start package manager") err = packageManager.Start() monitor.Finish() if err != nil { r.logger.Warn("initialize package manager: ", err) } else { r.packageManager = packageManager scope.Add(packageManager.Close) } } case adapter.StartStatePostStart: if r.needWIFIState && !(r.platformInterface != nil && r.platformInterface.UsePlatformWIFIMonitor()) { wifiMonitor, err := settings.NewWIFIMonitor(r.onWIFIStateChanged) if err != nil { if err != os.ErrInvalid { r.logger.Warn(E.Cause(err, "create WIFI monitor")) } } else { r.wifiMonitor = wifiMonitor scope.Add(wifiMonitor.Close) err = r.wifiMonitor.Start() if err != nil { r.logger.Warn(E.Cause(err, "start WIFI monitor")) } } } scope.Add(func() error { r.resetRunAccess.Lock() defer r.resetRunAccess.Unlock() return nil }) r.interfaceUpdateAccess.Lock() r.startedCtx = scope.Context() if r.interfaceMonitor != nil { r.dispatchInterfaceUpdateLocked() } r.interfaceUpdateAccess.Unlock() if runtime.GOOS == "windows" { powerListener, err := winpowrprof.NewEventListener(r.notifyWindowsPowerEvent) if err == nil { r.powerListener = powerListener } else { r.logger.Warn("initialize power listener: ", err) } } if r.powerListener != nil { monitor.Start("start power listener") err := r.powerListener.Start() monitor.Finish() if err != nil { return E.Cause(err, "start power listener") } scope.Add(r.powerListener.Close) } } return nil } func (r *NetworkManager) Initialize(ruleSets []adapter.RuleSet) { for _, ruleSet := range ruleSets { metadata := ruleSet.Metadata() if metadata.ContainsWIFIRule { r.needWIFIState = true break } } } func (r *NetworkManager) InterfaceFinder() control.InterfaceFinder { return r.interfaceFinder } func (r *NetworkManager) UpdateInterfaces() error { defer r.postUpdateNetworkEnvironment() if r.platformInterface == nil || !r.platformInterface.UsePlatformNetworkInterfaces() { return r.interfaceFinder.Update() } else { interfaces, err := r.platformInterface.NetworkInterfaces() if err != nil { return err } 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 len(newInterfaces) > 0 && !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.Notice("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) AutoDetectInterface() bool { return r.autoDetectInterface } func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func { if r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() { return func(network, address string, conn syscall.RawConn) error { return control.Raw(conn, func(fd uintptr) error { return r.platformInterface.AutoDetectInterfaceControl(int(fd)) }) } } else { if r.interfaceMonitor == nil { return nil } return control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) { remoteAddr := M.ParseSocksaddr(address).Addr if remoteAddr.IsValid() { iif, err := r.interfaceFinder.ByAddr(remoteAddr) if err == nil { return iif.Name, iif.Index, nil } } defaultInterface := r.interfaceMonitor.DefaultInterface() if defaultInterface == nil { return "", -1, tun.ErrNoRoute } return defaultInterface.Name, defaultInterface.Index, nil }) } } func (r *NetworkManager) ProtectFunc() control.Func { if r.platformInterface != nil && r.platformInterface.UsePlatformAutoDetectInterfaceControl() { return func(network, address string, conn syscall.RawConn) error { return control.Raw(conn, func(fd uintptr) error { return r.platformInterface.AutoDetectInterfaceControl(int(fd)) }) } } return nil } func (r *NetworkManager) DefaultOptions() adapter.NetworkOptions { return r.defaultOptions } func (r *NetworkManager) RegisterAutoRedirectOutputMark(mark uint32) error { if r.autoRedirectOutputMark > 0 { return E.New("only one auto-redirect can be configured") } r.autoRedirectOutputMark = mark return nil } func (r *NetworkManager) AutoRedirectOutputMark() uint32 { return r.autoRedirectOutputMark } func (r *NetworkManager) AutoRedirectOutputMarkFunc() control.Func { return func(network, address string, conn syscall.RawConn) error { if r.autoRedirectOutputMark == 0 { return nil } return control.RoutingMark(r.autoRedirectOutputMark)(network, address, conn) } } func (r *NetworkManager) NetworkMonitor() tun.NetworkUpdateMonitor { return r.networkMonitor } func (r *NetworkManager) InterfaceMonitor() tun.DefaultInterfaceMonitor { return r.interfaceMonitor } func (r *NetworkManager) PackageManager() tun.PackageManager { return r.packageManager } func (r *NetworkManager) NeedWIFIState() bool { return r.needWIFIState } func (r *NetworkManager) WIFIState() adapter.WIFIState { r.stateAccess.RLock() defer r.stateAccess.RUnlock() return r.wifiState } func (r *NetworkManager) onWIFIStateChanged(state adapter.WIFIState) { state.BSSID = adapter.NormalizeWIFIBSSID(state.BSSID) r.stateAccess.Lock() if state != r.wifiState { r.wifiState = state r.stateAccess.Unlock() r.postUpdateNetworkEnvironment() if state.SSID != "" { r.logger.Notice("WIFI state changed: SSID=", state.SSID, ", BSSID=", state.BSSID) } else { r.logger.Notice("WIFI disconnected") } } else { r.stateAccess.Unlock() } } func (r *NetworkManager) UpdateWIFIState(ctx context.Context) { var state adapter.WIFIState if r.wifiMonitor != nil { state = r.wifiMonitor.ReadWIFIState(ctx) } else if r.platformInterface != nil && r.platformInterface.UsePlatformWIFIMonitor() { state = r.platformInterface.ReadWIFIState(ctx) } else { return } r.onWIFIStateChanged(state) } func (r *NetworkManager) ResetNetwork(ctx context.Context) { if r.connectionManager != nil { r.connectionManager.CloseAll() } for _, endpoint := range r.endpoint.Endpoints() { listener, isListener := endpoint.(adapter.InterfaceUpdateListener) if isListener { listener.InterfaceUpdated(ctx) } } for _, inbound := range r.inbound.Inbounds() { listener, isListener := inbound.(adapter.InterfaceUpdateListener) if isListener { listener.InterfaceUpdated(ctx) } } for _, outbound := range r.outbound.Outbounds() { listener, isListener := outbound.(adapter.InterfaceUpdateListener) if isListener { listener.InterfaceUpdated(ctx) } } r.router.ResetNetwork() } func (r *NetworkManager) notifyInterfaceUpdate(_ *control.Interface, _ int) { r.interfaceUpdateAccess.Lock() defer r.interfaceUpdateAccess.Unlock() r.networkResetPending = true if r.startedCtx != nil { r.dispatchInterfaceUpdateLocked() } } func (r *NetworkManager) dispatchInterfaceUpdateLocked() { defaultInterface := r.interfaceMonitor.DefaultInterface() if defaultInterface == nil { r.pauseManager.NetworkPause() r.logger.Error("missing default interface") return } r.pauseManager.NetworkWake() if r.interfaceUpdateCancel != nil { r.interfaceUpdateCancel() } updateContext, updateCancel := context.WithCancel(r.startedCtx) r.interfaceUpdateCancel = updateCancel go r.updateInterface(updateContext, defaultInterface) } func (r *NetworkManager) updateInterface(ctx context.Context, defaultInterface *control.Interface) { r.resetRunAccess.Lock() defer r.resetRunAccess.Unlock() if ctx.Err() != nil { return } var options []string options = append(options, F.ToString("index ", defaultInterface.Index)) if C.IsAndroid && r.platformInterface == nil { var vpnStatus string if r.interfaceMonitor.AndroidVPNEnabled() { vpnStatus = "enabled" } else { vpnStatus = "disabled" } options = append(options, "vpn "+vpnStatus) } else if r.platformInterface != nil && r.platformInterface.UsePlatformNetworkInterfaces() { networkInterface := common.Find(r.networkInterfaces.Load(), func(it adapter.NetworkInterface) bool { return it.Interface.Index == defaultInterface.Index }) if networkInterface.Name == "" { // race return } options = append(options, F.ToString("type ", networkInterface.Type)) if networkInterface.Expensive { options = append(options, "expensive") } if networkInterface.Constrained { options = append(options, "constrained") } } r.logger.Notice("updated default interface ", defaultInterface.Name, ", ", strings.Join(options, ", ")) r.UpdateWIFIState(ctx) if ctx.Err() != nil { return } r.updateNetworkEnvironment() r.interfaceUpdateAccess.Lock() resetNetwork := ctx.Err() == nil && r.networkResetPending if resetNetwork { r.networkResetPending = false } r.interfaceUpdateAccess.Unlock() if resetNetwork { r.ResetNetwork(ctx) } } func (r *NetworkManager) notifyWindowsPowerEvent(event int) { switch event { case winpowrprof.EVENT_SUSPEND: r.pauseManager.DevicePause() r.cancelPowerUpdate() r.ResetNetwork(r.startedCtx) case winpowrprof.EVENT_RESUME: if !r.pauseManager.IsDevicePaused() { return } fallthrough case winpowrprof.EVENT_RESUME_AUTOMATIC: r.pauseManager.DeviceWake() updateContext, updateCancel := context.WithCancel(r.startedCtx) r.powerUpdateAccess.Lock() previousCancel := r.powerUpdateCancel r.powerUpdateCancel = updateCancel r.powerUpdateAccess.Unlock() if previousCancel != nil { previousCancel() } go func() { r.resetRunAccess.Lock() defer r.resetRunAccess.Unlock() if updateContext.Err() != nil { return } r.ResetNetwork(updateContext) }() } } func (r *NetworkManager) cancelPowerUpdate() { r.powerUpdateAccess.Lock() previousCancel := r.powerUpdateCancel r.powerUpdateCancel = nil r.powerUpdateAccess.Unlock() if previousCancel != nil { previousCancel() } } func (r *NetworkManager) OnPackagesUpdated(packages int, sharedUsers int) { r.logger.Notice("updated packages list: ", packages, " packages, ", sharedUsers, " shared users") }