diff --git a/adapter/inbound.go b/adapter/inbound.go index 33b1b4d1..1093bac4 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -69,6 +69,8 @@ type InboundContext struct { UDPDisableDomainUnmapping bool UDPConnect bool NetworkStrategy C.NetworkStrategy + NetworkType []C.InterfaceType + FallbackNetworkType []C.InterfaceType FallbackDelay time.Duration DNSServer string diff --git a/adapter/network.go b/adapter/network.go index dd924c33..08fc00fa 100644 --- a/adapter/network.go +++ b/adapter/network.go @@ -28,10 +28,12 @@ type NetworkManager interface { } type NetworkOptions struct { - DefaultNetworkStrategy C.NetworkStrategy - DefaultFallbackDelay time.Duration - DefaultInterface string - DefaultMark uint32 + NetworkStrategy C.NetworkStrategy + NetworkType []C.InterfaceType + FallbackNetworkType []C.InterfaceType + FallbackDelay time.Duration + BindInterface string + RoutingMark uint32 } type InterfaceUpdateListener interface { @@ -45,7 +47,7 @@ type WIFIState struct { type NetworkInterface struct { control.Interface - Type string + Type C.InterfaceType DNSServers []string Expensive bool Constrained bool diff --git a/adapter/outbound/default.go b/adapter/outbound/default.go index 84be8aba..27cafb9d 100644 --- a/adapter/outbound/default.go +++ b/adapter/outbound/default.go @@ -26,7 +26,7 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a var err error if len(metadata.DestinationAddresses) > 0 { if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { - outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.FallbackDelay) + outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) } else { outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses) } @@ -56,7 +56,7 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, if metadata.UDPConnect { if len(metadata.DestinationAddresses) > 0 { if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { - outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.FallbackDelay) + outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) } else { outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses) } @@ -74,7 +74,7 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, } else { if len(metadata.DestinationAddresses) > 0 { if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { - outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, parallelDialer, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.FallbackDelay) + outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, parallelDialer, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay) } else { outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) } diff --git a/common/dialer/default.go b/common/dialer/default.go index 3b2ffd76..9c4c865b 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -10,6 +10,7 @@ import ( "github.com/sagernet/sing-box/common/conntrack" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" @@ -33,6 +34,8 @@ type DefaultDialer struct { isWireGuardListener bool networkManager adapter.NetworkManager networkStrategy C.NetworkStrategy + networkType []C.InterfaceType + fallbackNetworkType []C.InterfaceType networkFallbackDelay time.Duration networkLastFallback atomic.TypedValue[time.Time] } @@ -43,6 +46,8 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti listener net.ListenConfig interfaceFinder control.InterfaceFinder networkStrategy C.NetworkStrategy + networkType []C.InterfaceType + fallbackNetworkType []C.InterfaceType networkFallbackDelay time.Duration ) if networkManager != nil { @@ -56,8 +61,8 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti listener.Control = control.Append(listener.Control, bindFunc) } if options.RoutingMark > 0 { - dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark)) - listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark)) + dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(options.RoutingMark))) + listener.Control = control.Append(listener.Control, control.RoutingMark(uint32(options.RoutingMark))) } if networkManager != nil { autoRedirectOutputMark := networkManager.AutoRedirectOutputMark() @@ -74,6 +79,8 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`") } networkStrategy = C.NetworkStrategy(options.NetworkStrategy) + networkType = common.Map(options.NetworkType, option.InterfaceType.Build) + fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build) networkFallbackDelay = time.Duration(options.NetworkFallbackDelay) if networkManager == nil || !networkManager.AutoDetectInterface() { return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`") @@ -81,14 +88,16 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti } if networkManager != nil && options.BindInterface == "" && options.Inet4BindAddress == nil && options.Inet6BindAddress == nil { defaultOptions := networkManager.DefaultOptions() - if defaultOptions.DefaultInterface != "" { - bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.DefaultInterface, -1) + if defaultOptions.BindInterface != "" { + bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1) dialer.Control = control.Append(dialer.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc) } else if networkManager.AutoDetectInterface() { - if defaultOptions.DefaultNetworkStrategy != C.NetworkStrategyDefault && C.NetworkStrategy(options.NetworkStrategy) == C.NetworkStrategyDefault { - networkStrategy = defaultOptions.DefaultNetworkStrategy - networkFallbackDelay = defaultOptions.DefaultFallbackDelay + if defaultOptions.NetworkStrategy != C.NetworkStrategyDefault && C.NetworkStrategy(options.NetworkStrategy) == C.NetworkStrategyDefault { + networkStrategy = defaultOptions.NetworkStrategy + networkType = defaultOptions.NetworkType + fallbackNetworkType = defaultOptions.FallbackNetworkType + networkFallbackDelay = defaultOptions.FallbackDelay bindFunc := networkManager.ProtectFunc() dialer.Control = control.Append(dialer.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc) @@ -179,6 +188,8 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti isWireGuardListener: options.IsWireGuardListener, networkManager: networkManager, networkStrategy: networkStrategy, + networkType: networkType, + fallbackNetworkType: fallbackNetworkType, networkFallbackDelay: networkFallbackDelay, }, nil } @@ -202,11 +213,11 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address return trackConn(DialSlowContext(&d.dialer6, ctx, network, address)) } } else { - return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkFallbackDelay) + return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay) } } -func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { +func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { if strategy == C.NetworkStrategyDefault { return d.DialContext(ctx, network, address) } @@ -226,9 +237,9 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin err error ) if !fastFallback { - conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), strategy, fallbackDelay) + conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } else { - conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), strategy, fallbackDelay, d.networkLastFallback.Store) + conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store) } if err != nil { return nil, err @@ -249,11 +260,11 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)) } } else { - return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkFallbackDelay) + return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay) } } -func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, error) { +func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) { if strategy == C.NetworkStrategyDefault { return d.ListenPacket(ctx, destination) } @@ -264,11 +275,11 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina if destination.IsIPv4() && !destination.Addr.IsUnspecified() { network += "4" } - return trackPacketConn(d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", strategy, fallbackDelay)) + return trackPacketConn(d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", strategy, interfaceType, fallbackInterfaceType, fallbackDelay)) } func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) { - return trackPacketConn(d.listenSerialInterfacePacket(context.Background(), d.udpListener, network, address, d.networkStrategy, d.networkFallbackDelay)) + return trackPacketConn(d.listenSerialInterfacePacket(context.Background(), d.udpListener, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)) } func trackConn(conn net.Conn, err error) (net.Conn, error) { diff --git a/common/dialer/default_parallel_interface.go b/common/dialer/default_parallel_interface.go index baf0349e..71d9814b 100644 --- a/common/dialer/default_parallel_interface.go +++ b/common/dialer/default_parallel_interface.go @@ -7,14 +7,14 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" + "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" N "github.com/sagernet/sing/common/network" ) -func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, bool, error) { - primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy) +func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, bool, error) { + primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType) if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { return nil, false, E.New("no available network interface") } @@ -84,8 +84,8 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di } } -func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, fallbackDelay time.Duration, resetFastFallback func(time.Time)) (net.Conn, bool, error) { - primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy) +func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration, resetFastFallback func(time.Time)) (net.Conn, bool, error) { + primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType) if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { return nil, false, E.New("no available network interface") } @@ -144,8 +144,8 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d } } -func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, error) { - primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy) +func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) { + primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType) if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { return nil, E.New("no available network interface") } @@ -174,12 +174,12 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene return nil, E.Errors(errors...) } -func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) { +func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) { interfaces := networkManager.NetworkInterfaces() switch strategy { - case C.NetworkStrategyFallback: - defaultIf := networkManager.InterfaceMonitor().DefaultInterface() - if defaultIf != nil { + case C.NetworkStrategyDefault: + if len(interfaceType) == 0 { + defaultIf := networkManager.InterfaceMonitor().DefaultInterface() for _, iif := range interfaces { if iif.Index == defaultIf.Index { primaryInterfaces = append(primaryInterfaces, iif) @@ -188,54 +188,36 @@ func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkS } } } else { - primaryInterfaces = interfaces + primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool { + return common.Contains(interfaceType, iif.Type) + }) } case C.NetworkStrategyHybrid: - primaryInterfaces = interfaces - case C.NetworkStrategyWIFI: - for _, iif := range interfaces { - if iif.Type == C.InterfaceTypeWIFI { - primaryInterfaces = append(primaryInterfaces, iif) - } else { - fallbackInterfaces = append(fallbackInterfaces, iif) - } + if len(interfaceType) == 0 { + primaryInterfaces = interfaces + } else { + primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool { + return common.Contains(interfaceType, iif.Type) + }) } - case C.NetworkStrategyCellular: - for _, iif := range interfaces { - if iif.Type == C.InterfaceTypeCellular { - primaryInterfaces = append(primaryInterfaces, iif) - } else { - fallbackInterfaces = append(fallbackInterfaces, iif) + case C.NetworkStrategyFallback: + if len(interfaceType) == 0 { + defaultIf := networkManager.InterfaceMonitor().DefaultInterface() + for _, iif := range interfaces { + if iif.Index == defaultIf.Index { + primaryInterfaces = append(primaryInterfaces, iif) + } else { + fallbackInterfaces = append(fallbackInterfaces, iif) + } } + } else { + primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool { + return common.Contains(interfaceType, iif.Type) + }) } - case C.NetworkStrategyEthernet: - for _, iif := range interfaces { - if iif.Type == C.InterfaceTypeEthernet { - primaryInterfaces = append(primaryInterfaces, iif) - } else { - fallbackInterfaces = append(fallbackInterfaces, iif) - } - } - case C.NetworkStrategyWIFIOnly: - for _, iif := range interfaces { - if iif.Type == C.InterfaceTypeWIFI { - primaryInterfaces = append(primaryInterfaces, iif) - } - } - case C.NetworkStrategyCellularOnly: - for _, iif := range interfaces { - if iif.Type == C.InterfaceTypeCellular { - primaryInterfaces = append(primaryInterfaces, iif) - } - } - case C.NetworkStrategyEthernetOnly: - for _, iif := range interfaces { - if iif.Type == C.InterfaceTypeEthernet { - primaryInterfaces = append(primaryInterfaces, iif) - } - } - default: - panic(F.ToString("unknown network strategy: ", strategy)) + fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool { + return common.Contains(fallbackInterfaceType, iif.Type) + }) } return primaryInterfaces, fallbackInterfaces } diff --git a/common/dialer/default_parallel_network.go b/common/dialer/default_parallel_network.go index f42d9330..ea043dfd 100644 --- a/common/dialer/default_parallel_network.go +++ b/common/dialer/default_parallel_network.go @@ -13,13 +13,13 @@ import ( N "github.com/sagernet/sing/common/network" ) -func DialSerialNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { +func DialSerialNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel { - return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, fallbackDelay) + return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } var errors []error for _, address := range destinationAddresses { - conn, err := dialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, fallbackDelay) + conn, err := dialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) if err == nil { return conn, nil } @@ -28,7 +28,7 @@ func DialSerialNetwork(ctx context.Context, dialer ParallelInterfaceDialer, netw return nil, E.Errors(errors...) } -func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { +func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { if fallbackDelay == 0 { fallbackDelay = N.DefaultFallbackDelay } @@ -43,7 +43,7 @@ func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, ne return address.Is6() && !address.Is4In6() }) if len(addresses4) == 0 || len(addresses6) == 0 { - return DialSerialNetwork(ctx, dialer, network, destination, destinationAddresses, strategy, fallbackDelay) + return DialSerialNetwork(ctx, dialer, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } var primaries, fallbacks []netip.Addr if preferIPv6 { @@ -65,7 +65,7 @@ func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, ne if !primary { ras = fallbacks } - c, err := DialSerialNetwork(ctx, dialer, network, destination, ras, strategy, fallbackDelay) + c, err := DialSerialNetwork(ctx, dialer, network, destination, ras, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) select { case results <- dialResult{Conn: c, error: err, primary: primary, done: true}: case <-returned: @@ -106,13 +106,13 @@ func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, ne } } -func ListenSerialNetworkPacket(ctx context.Context, dialer ParallelInterfaceDialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { +func ListenSerialNetworkPacket(ctx context.Context, dialer ParallelInterfaceDialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel { - return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, fallbackDelay) + return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } var errors []error for _, address := range destinationAddresses { - conn, err := dialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, fallbackDelay) + conn, err := dialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay) if err == nil { return conn, address, nil } diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index b3305d73..b307a330 100644 --- a/common/dialer/dialer.go +++ b/common/dialer/dialer.go @@ -77,11 +77,11 @@ func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInter type ParallelInterfaceDialer interface { N.Dialer - DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) - ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, error) + DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) + ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) } type ParallelNetworkDialer interface { - DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) - ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) + DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) + ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) } diff --git a/common/dialer/resolve.go b/common/dialer/resolve.go index ce17923c..b5d922b3 100644 --- a/common/dialer/resolve.go +++ b/common/dialer/resolve.go @@ -106,7 +106,7 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil } -func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { +func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { if !destination.IsFqdn() { return d.dialer.DialContext(ctx, network, destination) } @@ -128,13 +128,13 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context fallbackDelay = d.fallbackDelay } if d.parallel { - return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, strategy, fallbackDelay) + return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } else { - return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, fallbackDelay) + return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) } } -func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, error) { +func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) { if !destination.IsFqdn() { return d.dialer.ListenPacket(ctx, destination) } @@ -152,7 +152,7 @@ func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.C if err != nil { return nil, err } - conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, fallbackDelay) + conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay) if err != nil { return nil, err } diff --git a/common/srs/binary.go b/common/srs/binary.go index fbed78ad..42b4460d 100644 --- a/common/srs/binary.go +++ b/common/srs/binary.go @@ -226,7 +226,7 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea } rule.AdGuardDomainMatcher = matcher case ruleItemNetworkType: - rule.NetworkType, err = readRuleItemString(reader) + rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader) case ruleItemNetworkIsExpensive: rule.NetworkIsExpensive = true case ruleItemNetworkIsConstrained: @@ -349,7 +349,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen if generateVersion < C.RuleSetVersion3 { return E.New("network_type rule item is only supported in version 3 or later") } - err = writeRuleItemString(writer, ruleItemNetworkType, rule.NetworkType) + err = writeRuleItemUint8(writer, ruleItemNetworkType, rule.NetworkType) if err != nil { return err } @@ -414,6 +414,18 @@ func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) e return varbin.Write(writer, binary.BigEndian, value) } +func readRuleItemUint8[E ~uint8](reader varbin.Reader) ([]E, error) { + return varbin.ReadValue[[]E](reader, binary.BigEndian) +} + +func writeRuleItemUint8[E ~uint8](writer varbin.Writer, itemType uint8, value []E) error { + err := writer.WriteByte(itemType) + if err != nil { + return err + } + return varbin.Write(writer, binary.BigEndian, value) +} + func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) { return varbin.ReadValue[[]uint16](reader, binary.BigEndian) } diff --git a/constant/network.go b/constant/network.go index c026b7b1..88a1dd81 100644 --- a/constant/network.go +++ b/constant/network.go @@ -5,44 +5,52 @@ import ( F "github.com/sagernet/sing/common/format" ) +type InterfaceType uint8 + const ( - InterfaceTypeWIFI = "wifi" - InterfaceTypeCellular = "cellular" - InterfaceTypeEthernet = "ethernet" - InterfaceTypeOther = "other" + InterfaceTypeWIFI InterfaceType = iota + InterfaceTypeCellular + InterfaceTypeEthernet + InterfaceTypeOther ) -type NetworkStrategy int +var ( + interfaceTypeToString = map[InterfaceType]string{ + InterfaceTypeWIFI: "wifi", + InterfaceTypeCellular: "cellular", + InterfaceTypeEthernet: "ethernet", + InterfaceTypeOther: "other", + } + StringToInterfaceType = common.ReverseMap(interfaceTypeToString) +) + +func (t InterfaceType) String() string { + name, loaded := interfaceTypeToString[t] + if !loaded { + return F.ToString(int(t)) + } + return name +} + +type NetworkStrategy uint8 const ( NetworkStrategyDefault NetworkStrategy = iota NetworkStrategyFallback NetworkStrategyHybrid - NetworkStrategyWIFI - NetworkStrategyCellular - NetworkStrategyEthernet - NetworkStrategyWIFIOnly - NetworkStrategyCellularOnly - NetworkStrategyEthernetOnly ) var ( - NetworkStrategyToString = map[NetworkStrategy]string{ - NetworkStrategyDefault: "default", - NetworkStrategyFallback: "fallback", - NetworkStrategyHybrid: "hybrid", - NetworkStrategyWIFI: "wifi", - NetworkStrategyCellular: "cellular", - NetworkStrategyEthernet: "ethernet", - NetworkStrategyWIFIOnly: "wifi_only", - NetworkStrategyCellularOnly: "cellular_only", - NetworkStrategyEthernetOnly: "ethernet_only", + networkStrategyToString = map[NetworkStrategy]string{ + NetworkStrategyDefault: "default", + NetworkStrategyFallback: "fallback", + NetworkStrategyHybrid: "hybrid", } - StringToNetworkStrategy = common.ReverseMap(NetworkStrategyToString) + StringToNetworkStrategy = common.ReverseMap(networkStrategyToString) ) func (s NetworkStrategy) String() string { - name, loaded := NetworkStrategyToString[s] + name, loaded := networkStrategyToString[s] if !loaded { return F.ToString(int(s)) } diff --git a/docs/configuration/route/index.md b/docs/configuration/route/index.md index 2b035eb4..58f73352 100644 --- a/docs/configuration/route/index.md +++ b/docs/configuration/route/index.md @@ -7,6 +7,8 @@ icon: material/new-box !!! quote "Changes in sing-box 1.11.0" :material-plus: [default_network_strategy](#default_network_strategy) + :material-plus: [default_network_type](#default_network_type) + :material-plus: [default_fallback_network_type](#default_fallback_network_type) :material-alert: [default_fallback_delay](#default_fallback_delay) !!! quote "Changes in sing-box 1.8.0" @@ -30,17 +32,18 @@ icon: material/new-box "default_interface": "", "default_mark": 0, "default_network_strategy": "", + "default_network_type": [], + "default_fallback_network_type": [], "default_fallback_delay": "" } } ``` -### Fields +!!! note "" -| Key | Format | -|-----------|-----------------------| -| `geoip` | [GeoIP](./geoip/) | -| `geosite` | [Geosite](./geosite/) | + You can ignore the JSON Array [] tag when the content is only one item + +### Fields #### rules @@ -96,11 +99,9 @@ Takes no effect if `outbound.routing_mark` is set. #### default_network_strategy -!!! quote "" +!!! question "Since sing-box 1.11.0" - Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled. - -Strategy for selecting network interfaces. +See [Dial Fields](/configuration/shared/dial/#network_strategy) for details. Takes no effect if `outbound.bind_interface`, `outbound.inet4_bind_address` or `outbound.inet6_bind_address` is set. @@ -108,12 +109,20 @@ Can be overrides by `outbound.network_strategy`. Conflicts with `default_interface`. -See [Dial Fields](/configuration/shared/dial/#network_strategy) for available values. +#### default_network_type + +!!! question "Since sing-box 1.11.0" + +See [Dial Fields](/configuration/shared/dial/#network_type) for details. + +#### default_fallback_network_type + +!!! question "Since sing-box 1.11.0" + +See [Dial Fields](/configuration/shared/dial/#fallback_network_type) for details. #### default_fallback_delay -!!! quote "" +!!! question "Since sing-box 1.11.0" - Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled and `network_strategy` set. - -See [Dial Fields](/configuration/shared/dial/#fallback_delay) for details. \ No newline at end of file +See [Dial Fields](/configuration/shared/dial/#fallback_delay) for details. diff --git a/docs/configuration/route/index.zh.md b/docs/configuration/route/index.zh.md index b00237c4..9550b4ac 100644 --- a/docs/configuration/route/index.zh.md +++ b/docs/configuration/route/index.zh.md @@ -1,5 +1,16 @@ +--- +icon: material/new-box +--- + # 路由 +!!! quote "sing-box 1.11.0 中的更改" + + :material-plus: [network_strategy](#network_strategy) + :material-plus: [default_network_type](#default_network_type) + :material-plus: [default_fallback_network_type](#default_fallback_network_type) + :material-alert: [default_fallback_delay](#default_fallback_delay) + !!! quote "sing-box 1.8.0 中的更改" :material-plus: [rule_set](#rule_set) @@ -26,6 +37,10 @@ } ``` +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 + ### 字段 | 键 | 格式 | @@ -87,11 +102,9 @@ #### network_strategy -!!! quote "" +!!! question "自 sing-box 1.11.0 起" - 仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface`。 - -选择网络接口的策略。 +详情参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。 当 `outbound.bind_interface`, `outbound.inet4_bind_address` 或 `outbound.inet6_bind_address` 已设置时不生效。 @@ -99,12 +112,20 @@ 与 `default_interface` 冲突。 -可用值请参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。 +#### default_network_type -#### fallback_delay +!!! question "自 sing-box 1.11.0 起" -!!! quote "" +详情参阅 [拨号字段](/configuration/shared/dial/#default_network_type)。 - 仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface` 且 `network_strategy` 已设置。 +#### default_fallback_network_type -详情请参阅 [拨号字段](/configuration/shared/dial/#fallback_delay)。 +!!! question "自 sing-box 1.11.0 起" + +详情参阅 [拨号字段](/configuration/shared/dial/#default_fallback_network_type)。 + +#### default_fallback_delay + +!!! question "自 sing-box 1.11.0 起" + +详情参阅 [拨号字段](/configuration/shared/dial/#fallback_delay)。 diff --git a/docs/configuration/route/rule_action.md b/docs/configuration/route/rule_action.md index 5e9cfbc2..6462c848 100644 --- a/docs/configuration/route/rule_action.md +++ b/docs/configuration/route/rule_action.md @@ -11,12 +11,18 @@ icon: material/new-box "action": "route", // default "outbound": "", "network_strategy": "", + "network_type": [], + "fallback_network_type": [], "fallback_delay": "", "udp_disable_domain_unmapping": false, "udp_connect": false } ``` +!!! note "" + + You can ignore the JSON Array [] tag when the content is only one item + `route` inherits the classic rule behavior of routing connection to the specified outbound. #### outbound @@ -27,23 +33,21 @@ Tag of target outbound. #### network_strategy -!!! quote "" - - Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled. - -Strategy for selecting network interfaces. +See [Dial Fields](/configuration/shared/dial/#network_strategy) for details. Only take effect if outbound is direct without `outbound.bind_interface`, `outbound.inet4_bind_address` and `outbound.inet6_bind_address` set. -See [Dial Fields](/configuration/shared/dial/#network_strategy) for available values. +#### network_type + +See [Dial Fields](/configuration/shared/dial/#network_type) for details. + +#### fallback_network_type + +See [Dial Fields](/configuration/shared/dial/#fallback_network_type) for details. #### fallback_delay -!!! quote "" - - Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled and `network_strategy` set. - See [Dial Fields](/configuration/shared/dial/#fallback_delay) for details. #### udp_disable_domain_unmapping diff --git a/docs/configuration/route/rule_action.zh.md b/docs/configuration/route/rule_action.zh.md index 52965fd2..acadb66d 100644 --- a/docs/configuration/route/rule_action.zh.md +++ b/docs/configuration/route/rule_action.zh.md @@ -12,6 +12,8 @@ icon: material/new-box "outbound": "", "network_strategy": "", "fallback_delay": "", + "network_type": [], + "fallback_network_type": [], "udp_disable_domain_unmapping": false, "udp_connect": false } @@ -27,23 +29,21 @@ icon: material/new-box #### network_strategy -!!! quote "" - - 仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface`。 - -选择网络接口的策略。 +详情参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。 仅当出站为 `direct` 且 `outbound.bind_interface`, `outbound.inet4_bind_address` 且 `outbound.inet6_bind_address` 未设置时生效。 -可用值参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。 +#### network_type + +详情参阅 [拨号字段](/configuration/shared/dial/#network_type)。 + +#### fallback_network_type + +详情参阅 [拨号字段](/configuration/shared/dial/#fallback_network_type)。 #### fallback_delay -!!! quote "" - - 仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface` 且 `network_strategy` 已设置。 - 详情参阅 [拨号字段](/configuration/shared/dial/#fallback_delay)。 #### udp_disable_domain_unmapping @@ -68,6 +68,10 @@ icon: material/new-box } ``` +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 + `route-options` 为路由设置选项。 ### reject diff --git a/docs/configuration/shared/dial.md b/docs/configuration/shared/dial.md index e67bf616..5f654ae2 100644 --- a/docs/configuration/shared/dial.md +++ b/docs/configuration/shared/dial.md @@ -5,7 +5,9 @@ icon: material/new-box !!! quote "Changes in sing-box 1.11.0" :material-plus: [network_strategy](#network_strategy) - :material-alert: [fallback_delay](#fallback_delay) + :material-alert: [fallback_delay](#fallback_delay) + :material-alert: [network_type](#network_type) + :material-alert: [fallback_network_type](#fallback_network_type) ### Structure @@ -23,10 +25,16 @@ icon: material/new-box "udp_fragment": false, "domain_strategy": "prefer_ipv6", "network_strategy": "default", + "network_type": [], + "fallback_network_type": [], "fallback_delay": "300ms" } ``` +!!! note "" + + You can ignore the JSON Array [] tag when the content is only one item + ### Fields #### detour @@ -101,30 +109,57 @@ If set, the requested domain name will be resolved to IP before connect. !!! quote "" - Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled. + Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled. Strategy for selecting network interfaces. Available values: -- `default` (default): Connect to the default interface. -- `fallback`: Try all other interfaces when timeout. -- `hybrid`: Connect to all interfaces concurrently and choose the fastest one. -- `wifi`: Prioritize WIFI, but try all other interfaces when unavailable or timeout. -- `cellular`: Prioritize Cellular, but try all other interfaces when unavailable or timeout. -- `ethernet`: Prioritize Ethernet, but try all other interfaces when unavailable or timeout. -- `wifi_only`: Connect to WIFI only. -- `cellular_only`: Connect to Cellular only. -- `ethernet_only`: Connect to Ethernet only. +- `default` (default): Connect to default network or networks specified in `network_type` sequentially. +- `hybrid`: Connect to all networks or networks specified in `network_type` concurrently. +- `fallback`: Connect to default network or preferred networks specified in `network_type` concurrently, and try fallback networks when unavailable or timeout. -For fallback strategies, when preferred interfaces fails or times out, -it will enter a 15s fast fallback state (upgraded to `hybrid`), -and exit immediately if recovers. +For fallback, when preferred interfaces fails or times out, +it will enter a 15s fast fallback state (Connect to all preferred and fallback networks concurrently), +and exit immediately if preferred networks recover. Conflicts with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`. +#### network_type + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled. + +Network types to use when using `default` or `hybrid` network strategy or +preferred network types to use when using `fallback` network strategy. + +Available values: `wifi`, `cellular`, `ethernet`, `other`. + +Device's default network is used by default. + +#### fallback_network_type + +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled. + +Fallback network types when preferred networks are unavailable or timeout when using `fallback` network strategy. + +All other networks expect preferred are used by default. + #### fallback_delay +!!! question "Since sing-box 1.11.0" + +!!! quote "" + + Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled. + The length of time to wait before spawning a RFC 6555 Fast Fallback connection. For `domain_strategy`, is the amount of time to wait for connection to succeed before assuming diff --git a/docs/configuration/shared/dial.zh.md b/docs/configuration/shared/dial.zh.md index 4e20f106..ab83c44c 100644 --- a/docs/configuration/shared/dial.zh.md +++ b/docs/configuration/shared/dial.zh.md @@ -5,7 +5,9 @@ icon: material/new-box !!! quote "sing-box 1.11.0 中的更改" :material-plus: [network_strategy](#network_strategy) - :material-alert: [fallback_delay](#fallback_delay) + :material-alert: [fallback_delay](#fallback_delay) + :material-alert: [network_type](#network_type) + :material-alert: [fallback_network_type](#fallback_network_type) ### 结构 @@ -23,10 +25,16 @@ icon: material/new-box "udp_fragment": false, "domain_strategy": "prefer_ipv6", "network_strategy": "", + "network_type": [], + "fallback_network_type": [], "fallback_delay": "300ms" } ``` +!!! note "" + + 当内容只有一项时,可以忽略 JSON 数组 [] 标签 + ### 字段 #### detour @@ -99,26 +107,48 @@ icon: material/new-box !!! quote "" - 仅在 Android 与 iOS 平台图形客户端中支持。 + 仅在 Android 与 iOS 平台图形客户端中支持,并且需要 `route.auto_detect_interface`。 用于选择网络接口的策略。 可用值: -- `default` (默认): 连接到默认接口, -- `fallback`: 如果超时,尝试所有剩余接口。 -- `hybrid`: 同时尝试所有接口,选择最快的一个。 -- `wifi`: 优先使用 WIFI,但在不可用或超时时尝试所有其他接口。 -- `cellular`: 优先使用蜂窝数据,但在不可用或超时时尝试所有其他接口。 -- `ethernet`: 优先使用以太网,但在不可用或超时时尝试所有其他接口。 -- `wifi_only`: 仅连接到 WIFI。 -- `cellular_only`: 仅连接到蜂窝数据。 -- `ethernet_only`: 仅连接到以太网。 +- `default`(默认值):按顺序连接默认网络或 `network_type` 中指定的网络。 +- `hybrid`:同时连接所有网络或 `network_type` 中指定的网络。 +- `fallback`:同时连接默认网络或 `network_type` 中指定的首选网络,当不可用或超时时尝试回退网络。 -对于回退策略, 当优先使用的接口发生故障或超时时, 将进入 15 秒的快速回退状态(升级为 `hybrid`), 且恢复后立即退出。 +对于回退模式,当首选接口失败或超时时, +将进入15秒的快速回退状态(同时连接所有首选和回退网络), +如果首选网络恢复,则立即退出。 与 `bind_interface`, `bind_inet4_address` 和 `bind_inet6_address` 冲突。 +#### network_type + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 iOS 平台图形客户端中支持,并且需要 `route.auto_detect_interface`。 + +当使用 `default` 或 `hybrid` 网络策略时要使用的网络类型,或当使用 `fallback` 网络策略时要使用的首选网络类型。 + +可用值:`wifi`, `cellular`, `ethernet`, `other`。 + +默认使用设备默认网络。 + +#### fallback_network_type + +!!! question "自 sing-box 1.11.0 起" + +!!! quote "" + + 仅在 Android 与 iOS 平台图形客户端中支持,并且需要 `route.auto_detect_interface`。 + +当使用 `fallback` 网络策略时,在首选网络不可用或超时的情况下要使用的回退网络类型。 + +默认使用除首选网络外的所有其他网络。 + #### fallback_delay 在生成 RFC 6555 快速回退连接之前等待的时间长度。 diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index a6124345..d5951cd3 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -34,10 +34,10 @@ type InterfaceUpdateListener interface { } const ( - InterfaceTypeWIFI = C.InterfaceTypeWIFI - InterfaceTypeCellular = C.InterfaceTypeCellular - InterfaceTypeEthernet = C.InterfaceTypeEthernet - InterfaceTypeOther = C.InterfaceTypeOther + InterfaceTypeWIFI = int32(C.InterfaceTypeWIFI) + InterfaceTypeCellular = int32(C.InterfaceTypeCellular) + InterfaceTypeEthernet = int32(C.InterfaceTypeEthernet) + InterfaceTypeOther = int32(C.InterfaceTypeOther) ) type NetworkInterface struct { @@ -47,7 +47,7 @@ type NetworkInterface struct { Addresses StringIterator Flags int32 - Type string + Type int32 DNSServer StringIterator Metered bool } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 93274b1f..0fdf721a 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -189,7 +189,7 @@ func (w *platformInterfaceWrapper) Interfaces() ([]adapter.NetworkInterface, err Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix), Flags: linkFlags(uint32(netInterface.Flags)), }, - Type: netInterface.Type, + Type: C.InterfaceType(netInterface.Type), DNSServers: iteratorToArray[string](netInterface.DNSServer), Expensive: netInterface.Metered || isDefault && w.isExpensive, Constrained: isDefault && w.isConstrained, diff --git a/option/outbound.go b/option/outbound.go index 5791802c..34ef904a 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -65,23 +65,25 @@ type DialerOptionsWrapper interface { } type DialerOptions struct { - Detour string `json:"detour,omitempty"` - BindInterface string `json:"bind_interface,omitempty"` - Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"` - Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"` - ProtectPath string `json:"protect_path,omitempty"` - RoutingMark uint32 `json:"routing_mark,omitempty"` - ReuseAddr bool `json:"reuse_addr,omitempty"` - ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"` - TCPFastOpen bool `json:"tcp_fast_open,omitempty"` - TCPMultiPath bool `json:"tcp_multi_path,omitempty"` - UDPFragment *bool `json:"udp_fragment,omitempty"` - UDPFragmentDefault bool `json:"-"` - DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` - NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` - FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"` - NetworkFallbackDelay badoption.Duration `json:"network_fallback_delay,omitempty"` - IsWireGuardListener bool `json:"-"` + Detour string `json:"detour,omitempty"` + BindInterface string `json:"bind_interface,omitempty"` + Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"` + Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"` + ProtectPath string `json:"protect_path,omitempty"` + RoutingMark FwMark `json:"routing_mark,omitempty"` + ReuseAddr bool `json:"reuse_addr,omitempty"` + ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"` + TCPFastOpen bool `json:"tcp_fast_open,omitempty"` + TCPMultiPath bool `json:"tcp_multi_path,omitempty"` + UDPFragment *bool `json:"udp_fragment,omitempty"` + UDPFragmentDefault bool `json:"-"` + DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` + NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` + NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` + FallbackNetworkType badoption.Listable[InterfaceType] `json:"fallback_network_type,omitempty"` + FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"` + NetworkFallbackDelay badoption.Duration `json:"network_fallback_delay,omitempty"` + IsWireGuardListener bool `json:"-"` } func (o *DialerOptions) TakeDialerOptions() DialerOptions { diff --git a/option/route.go b/option/route.go index 236e56f7..b05fb399 100644 --- a/option/route.go +++ b/option/route.go @@ -3,18 +3,20 @@ package option import "github.com/sagernet/sing/common/json/badoption" type RouteOptions struct { - GeoIP *GeoIPOptions `json:"geoip,omitempty"` - Geosite *GeositeOptions `json:"geosite,omitempty"` - Rules []Rule `json:"rules,omitempty"` - RuleSet []RuleSet `json:"rule_set,omitempty"` - Final string `json:"final,omitempty"` - FindProcess bool `json:"find_process,omitempty"` - AutoDetectInterface bool `json:"auto_detect_interface,omitempty"` - OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"` - DefaultInterface string `json:"default_interface,omitempty"` - DefaultMark uint32 `json:"default_mark,omitempty"` - DefaultNetworkStrategy NetworkStrategy `json:"default_network_strategy,omitempty"` - DefaultFallbackDelay badoption.Duration `json:"default_fallback_delay,omitempty"` + GeoIP *GeoIPOptions `json:"geoip,omitempty"` + Geosite *GeositeOptions `json:"geosite,omitempty"` + Rules []Rule `json:"rules,omitempty"` + RuleSet []RuleSet `json:"rule_set,omitempty"` + Final string `json:"final,omitempty"` + FindProcess bool `json:"find_process,omitempty"` + AutoDetectInterface bool `json:"auto_detect_interface,omitempty"` + OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"` + DefaultInterface string `json:"default_interface,omitempty"` + DefaultMark uint32 `json:"default_mark,omitempty"` + DefaultNetworkStrategy NetworkStrategy `json:"default_network_strategy,omitempty"` + DefaultNetworkType badoption.Listable[InterfaceType] `json:"default_network_type,omitempty"` + DefaultFallbackNetworkType badoption.Listable[InterfaceType] `json:"default_fallback_network_type,omitempty"` + DefaultFallbackDelay badoption.Duration `json:"default_fallback_delay,omitempty"` } type GeoIPOptions struct { diff --git a/option/rule.go b/option/rule.go index 82a53f75..b769dab8 100644 --- a/option/rule.go +++ b/option/rule.go @@ -67,42 +67,42 @@ func (r Rule) IsValid() bool { } type RawDefaultRule struct { - Inbound badoption.Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - Network badoption.Listable[string] `json:"network,omitempty"` - AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` - Protocol badoption.Listable[string] `json:"protocol,omitempty"` - Client badoption.Listable[string] `json:"client,omitempty"` - Domain badoption.Listable[string] `json:"domain,omitempty"` - DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` - Geosite badoption.Listable[string] `json:"geosite,omitempty"` - SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` - GeoIP badoption.Listable[string] `json:"geoip,omitempty"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` - SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` - IPIsPrivate bool `json:"ip_is_private,omitempty"` - SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` - Port badoption.Listable[uint16] `json:"port,omitempty"` - PortRange badoption.Listable[string] `json:"port_range,omitempty"` - ProcessName badoption.Listable[string] `json:"process_name,omitempty"` - ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` - PackageName badoption.Listable[string] `json:"package_name,omitempty"` - User badoption.Listable[string] `json:"user,omitempty"` - UserID badoption.Listable[int32] `json:"user_id,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - NetworkType badoption.Listable[string] `json:"network_type,omitempty"` - NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` - NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` - WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` - RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` - RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` - Invert bool `json:"invert,omitempty"` + Inbound badoption.Listable[string] `json:"inbound,omitempty"` + IPVersion int `json:"ip_version,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` + Protocol badoption.Listable[string] `json:"protocol,omitempty"` + Client badoption.Listable[string] `json:"client,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + Geosite badoption.Listable[string] `json:"geosite,omitempty"` + SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` + GeoIP badoption.Listable[string] `json:"geoip,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + IPIsPrivate bool `json:"ip_is_private,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + User badoption.Listable[string] `json:"user,omitempty"` + UserID badoption.Listable[int32] `json:"user_id,omitempty"` + ClashMode string `json:"clash_mode,omitempty"` + NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` + RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` + Invert bool `json:"invert,omitempty"` // Deprecated: renamed to rule_set_ip_cidr_match_source Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` diff --git a/option/rule_dns.go b/option/rule_dns.go index 33dc816c..b437eb54 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -68,44 +68,44 @@ func (r DNSRule) IsValid() bool { } type RawDefaultDNSRule struct { - Inbound badoption.Listable[string] `json:"inbound,omitempty"` - IPVersion int `json:"ip_version,omitempty"` - QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` - Network badoption.Listable[string] `json:"network,omitempty"` - AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` - Protocol badoption.Listable[string] `json:"protocol,omitempty"` - Domain badoption.Listable[string] `json:"domain,omitempty"` - DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` - Geosite badoption.Listable[string] `json:"geosite,omitempty"` - SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` - GeoIP badoption.Listable[string] `json:"geoip,omitempty"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` - IPIsPrivate bool `json:"ip_is_private,omitempty"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` - SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` - SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` - Port badoption.Listable[uint16] `json:"port,omitempty"` - PortRange badoption.Listable[string] `json:"port_range,omitempty"` - ProcessName badoption.Listable[string] `json:"process_name,omitempty"` - ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` - PackageName badoption.Listable[string] `json:"package_name,omitempty"` - User badoption.Listable[string] `json:"user,omitempty"` - UserID badoption.Listable[int32] `json:"user_id,omitempty"` - Outbound badoption.Listable[string] `json:"outbound,omitempty"` - ClashMode string `json:"clash_mode,omitempty"` - NetworkType badoption.Listable[string] `json:"network_type,omitempty"` - NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` - NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` - WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` - RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` - RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` - RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"` - Invert bool `json:"invert,omitempty"` + Inbound badoption.Listable[string] `json:"inbound,omitempty"` + IPVersion int `json:"ip_version,omitempty"` + QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` + Protocol badoption.Listable[string] `json:"protocol,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + Geosite badoption.Listable[string] `json:"geosite,omitempty"` + SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` + GeoIP badoption.Listable[string] `json:"geoip,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + IPIsPrivate bool `json:"ip_is_private,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + User badoption.Listable[string] `json:"user,omitempty"` + UserID badoption.Listable[int32] `json:"user_id,omitempty"` + Outbound badoption.Listable[string] `json:"outbound,omitempty"` + ClashMode string `json:"clash_mode,omitempty"` + NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` + RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` + RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"` + Invert bool `json:"invert,omitempty"` // Deprecated: renamed to rule_set_ip_cidr_match_source Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` diff --git a/option/rule_set.go b/option/rule_set.go index 9ccca475..f7a5f334 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -146,28 +146,28 @@ func (r HeadlessRule) IsValid() bool { } type DefaultHeadlessRule struct { - QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` - Network badoption.Listable[string] `json:"network,omitempty"` - Domain badoption.Listable[string] `json:"domain,omitempty"` - DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` - DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` - DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` - SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` - IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` - SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` - SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` - Port badoption.Listable[uint16] `json:"port,omitempty"` - PortRange badoption.Listable[string] `json:"port_range,omitempty"` - ProcessName badoption.Listable[string] `json:"process_name,omitempty"` - ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` - ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` - PackageName badoption.Listable[string] `json:"package_name,omitempty"` - NetworkType badoption.Listable[string] `json:"network_type,omitempty"` - NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` - NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` - WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` - WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` - Invert bool `json:"invert,omitempty"` + QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` + Network badoption.Listable[string] `json:"network,omitempty"` + Domain badoption.Listable[string] `json:"domain,omitempty"` + DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` + DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` + DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` + SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` + IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` + SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` + SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` + Port badoption.Listable[uint16] `json:"port,omitempty"` + PortRange badoption.Listable[string] `json:"port_range,omitempty"` + ProcessName badoption.Listable[string] `json:"process_name,omitempty"` + ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` + ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` + PackageName badoption.Listable[string] `json:"package_name,omitempty"` + NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"` + NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` + NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` + WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` + WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` + Invert bool `json:"invert,omitempty"` DomainMatcher *domain.Matcher `json:"-"` SourceIPSet *netipx.IPSet `json:"-"` diff --git a/option/types.go b/option/types.go index 8ed06250..66f58ef8 100644 --- a/option/types.go +++ b/option/types.go @@ -171,3 +171,27 @@ func (n *NetworkStrategy) UnmarshalJSON(content []byte) error { *n = NetworkStrategy(strategy) return nil } + +type InterfaceType C.InterfaceType + +func (t InterfaceType) Build() C.InterfaceType { + return C.InterfaceType(t) +} + +func (t InterfaceType) MarshalJSON() ([]byte, error) { + return json.Marshal(C.InterfaceType(t).String()) +} + +func (t *InterfaceType) UnmarshalJSON(content []byte) error { + var value string + err := json.Unmarshal(content, &value) + if err != nil { + return err + } + interfaceType, loaded := C.StringToInterfaceType[value] + if !loaded { + return E.New("unknown interface type: ", value) + } + *t = InterfaceType(interfaceType) + return nil +} diff --git a/protocol/direct/outbound.go b/protocol/direct/outbound.go index 4251c366..5ae0dac6 100644 --- a/protocol/direct/outbound.go +++ b/protocol/direct/outbound.go @@ -12,7 +12,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - dns "github.com/sagernet/sing-dns" + "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" @@ -37,6 +37,8 @@ type Outbound struct { domainStrategy dns.DomainStrategy fallbackDelay time.Duration networkStrategy C.NetworkStrategy + networkType []C.InterfaceType + fallbackNetworkType []C.InterfaceType networkFallbackDelay time.Duration overrideOption int overrideDestination M.Socksaddr @@ -55,6 +57,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL domainStrategy: dns.DomainStrategy(options.DomainStrategy), fallbackDelay: time.Duration(options.FallbackDelay), networkStrategy: C.NetworkStrategy(options.NetworkStrategy), + networkType: common.Map(options.NetworkType, option.InterfaceType.Build), + fallbackNetworkType: common.Map(options.FallbackNetworkType, option.InterfaceType.Build), networkFallbackDelay: time.Duration(options.NetworkFallbackDelay), dialer: outboundDialer, // loopBack: newLoopBackDetector(router), @@ -171,10 +175,10 @@ func (h *Outbound) DialParallel(ctx context.Context, network string, destination return nil, E.New("no IPv6 address available for ", destination) } } - return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, h.networkStrategy, h.fallbackDelay) + return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, h.networkStrategy, h.networkType, h.fallbackNetworkType, h.fallbackDelay) } -func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) { +func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() metadata.Destination = destination @@ -210,10 +214,10 @@ func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, dest return nil, E.New("no IPv6 address available for ", destination) } } - return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, networkStrategy, fallbackDelay) + return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, networkStrategy, networkType, fallbackNetworkType, fallbackDelay) } -func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { +func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() metadata.Destination = destination @@ -232,7 +236,7 @@ func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M. } else { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) } - conn, newDestination, err := dialer.ListenSerialNetworkPacket(ctx, h.dialer, destination, destinationAddresses, networkStrategy, fallbackDelay) + conn, newDestination, err := dialer.ListenSerialNetworkPacket(ctx, h.dialer, destination, destinationAddresses, networkStrategy, networkType, fallbackNetworkType, fallbackDelay) if err != nil { return nil, netip.Addr{}, err } diff --git a/route/network.go b/route/network.go index 51fbdf05..85fd2210 100644 --- a/route/network.go +++ b/route/network.go @@ -59,10 +59,12 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp interfaceFinder: control.NewDefaultInterfaceFinder(), autoDetectInterface: routeOptions.AutoDetectInterface, defaultOptions: adapter.NetworkOptions{ - DefaultInterface: routeOptions.DefaultInterface, - DefaultMark: routeOptions.DefaultMark, - DefaultNetworkStrategy: C.NetworkStrategy(routeOptions.DefaultNetworkStrategy), - DefaultFallbackDelay: time.Duration(routeOptions.DefaultFallbackDelay), + BindInterface: routeOptions.DefaultInterface, + RoutingMark: routeOptions.DefaultMark, + NetworkStrategy: C.NetworkStrategy(routeOptions.DefaultNetworkStrategy), + NetworkType: common.Map(routeOptions.DefaultNetworkType, option.InterfaceType.Build), + FallbackNetworkType: common.Map(routeOptions.DefaultFallbackNetworkType, option.InterfaceType.Build), + FallbackDelay: time.Duration(routeOptions.DefaultFallbackDelay), }, pauseManager: service.FromContext[pause.Manager](ctx), platformInterface: service.FromContext[platform.Interface](ctx), @@ -385,7 +387,7 @@ func (r *NetworkManager) notifyInterfaceUpdate(defaultInterface *control.Interfa networkInterface := common.Find(r.networkInterfaces.Load(), func(it adapter.NetworkInterface) bool { return it.Interface.Index == defaultInterface.Index }) - if networkInterface.Type == "" { + if networkInterface.Name == "" { // race return } diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go index f9b2e641..52e95207 100644 --- a/route/rule/rule_action.go +++ b/route/rule/rule_action.go @@ -140,6 +140,8 @@ func (r *RuleActionRoute) String() string { type RuleActionRouteOptions struct { NetworkStrategy C.NetworkStrategy + NetworkType []C.InterfaceType + FallbackNetworkType []C.InterfaceType FallbackDelay time.Duration UDPDisableDomainUnmapping bool UDPConnect bool diff --git a/route/rule/rule_default.go b/route/rule/rule_default.go index a5d47b44..2794c287 100644 --- a/route/rule/rule_default.go +++ b/route/rule/rule_default.go @@ -8,6 +8,7 @@ import ( "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/service" ) @@ -224,7 +225,7 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio rule.allItems = append(rule.allItems, item) } if len(options.NetworkType) > 0 { - item := NewNetworkTypeItem(networkManager, options.NetworkType) + item := NewNetworkTypeItem(networkManager, common.Map(options.NetworkType, option.InterfaceType.Build)) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } diff --git a/route/rule/rule_dns.go b/route/rule/rule_dns.go index 4ac08a5a..fb8c6b78 100644 --- a/route/rule/rule_dns.go +++ b/route/rule/rule_dns.go @@ -221,7 +221,7 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op rule.allItems = append(rule.allItems, item) } if len(options.NetworkType) > 0 { - item := NewNetworkTypeItem(networkManager, options.NetworkType) + item := NewNetworkTypeItem(networkManager, common.Map(options.NetworkType, option.InterfaceType.Build)) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } diff --git a/route/rule/rule_headless.go b/route/rule/rule_headless.go index 7f2dc5fc..619856a5 100644 --- a/route/rule/rule_headless.go +++ b/route/rule/rule_headless.go @@ -6,6 +6,7 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/service" ) @@ -142,7 +143,7 @@ func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessR } if networkManager != nil { if len(options.NetworkType) > 0 { - item := NewNetworkTypeItem(networkManager, options.NetworkType) + item := NewNetworkTypeItem(networkManager, common.Map(options.NetworkType, option.InterfaceType.Build)) rule.items = append(rule.items, item) rule.allItems = append(rule.allItems, item) } diff --git a/route/rule/rule_item_network_type.go b/route/rule/rule_item_network_type.go index 8ebdb25e..31856e70 100644 --- a/route/rule/rule_item_network_type.go +++ b/route/rule/rule_item_network_type.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing/common" F "github.com/sagernet/sing/common/format" ) @@ -12,10 +13,10 @@ var _ RuleItem = (*NetworkTypeItem)(nil) type NetworkTypeItem struct { networkManager adapter.NetworkManager - networkType []string + networkType []C.InterfaceType } -func NewNetworkTypeItem(networkManager adapter.NetworkManager, networkType []string) *NetworkTypeItem { +func NewNetworkTypeItem(networkManager adapter.NetworkManager, networkType []C.InterfaceType) *NetworkTypeItem { return &NetworkTypeItem{ networkManager: networkManager, networkType: networkType,