Refactor multi networks strategy

This commit is contained in:
世界 2024-11-13 19:05:28 +08:00
parent 0ce2259562
commit fb37d7c4dd
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
31 changed files with 495 additions and 336 deletions

View file

@ -69,6 +69,8 @@ type InboundContext struct {
UDPDisableDomainUnmapping bool UDPDisableDomainUnmapping bool
UDPConnect bool UDPConnect bool
NetworkStrategy C.NetworkStrategy NetworkStrategy C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration FallbackDelay time.Duration
DNSServer string DNSServer string

View file

@ -28,10 +28,12 @@ type NetworkManager interface {
} }
type NetworkOptions struct { type NetworkOptions struct {
DefaultNetworkStrategy C.NetworkStrategy NetworkStrategy C.NetworkStrategy
DefaultFallbackDelay time.Duration NetworkType []C.InterfaceType
DefaultInterface string FallbackNetworkType []C.InterfaceType
DefaultMark uint32 FallbackDelay time.Duration
BindInterface string
RoutingMark uint32
} }
type InterfaceUpdateListener interface { type InterfaceUpdateListener interface {
@ -45,7 +47,7 @@ type WIFIState struct {
type NetworkInterface struct { type NetworkInterface struct {
control.Interface control.Interface
Type string Type C.InterfaceType
DNSServers []string DNSServers []string
Expensive bool Expensive bool
Constrained bool Constrained bool

View file

@ -26,7 +26,7 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a
var err error var err error
if len(metadata.DestinationAddresses) > 0 { if len(metadata.DestinationAddresses) > 0 {
if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { 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 { } else {
outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses) 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 metadata.UDPConnect {
if len(metadata.DestinationAddresses) > 0 { if len(metadata.DestinationAddresses) > 0 {
if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { 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 { } else {
outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses) 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 { } else {
if len(metadata.DestinationAddresses) > 0 { if len(metadata.DestinationAddresses) > 0 {
if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer { 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 { } else {
outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses) outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses)
} }

View file

@ -10,6 +10,7 @@ import (
"github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/common/conntrack"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic" "github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@ -33,6 +34,8 @@ type DefaultDialer struct {
isWireGuardListener bool isWireGuardListener bool
networkManager adapter.NetworkManager networkManager adapter.NetworkManager
networkStrategy C.NetworkStrategy networkStrategy C.NetworkStrategy
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration networkFallbackDelay time.Duration
networkLastFallback atomic.TypedValue[time.Time] networkLastFallback atomic.TypedValue[time.Time]
} }
@ -43,6 +46,8 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti
listener net.ListenConfig listener net.ListenConfig
interfaceFinder control.InterfaceFinder interfaceFinder control.InterfaceFinder
networkStrategy C.NetworkStrategy networkStrategy C.NetworkStrategy
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration networkFallbackDelay time.Duration
) )
if networkManager != nil { if networkManager != nil {
@ -56,8 +61,8 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti
listener.Control = control.Append(listener.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc)
} }
if options.RoutingMark > 0 { if options.RoutingMark > 0 {
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark)) dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(options.RoutingMark)))
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark)) listener.Control = control.Append(listener.Control, control.RoutingMark(uint32(options.RoutingMark)))
} }
if networkManager != nil { if networkManager != nil {
autoRedirectOutputMark := networkManager.AutoRedirectOutputMark() 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`") return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`")
} }
networkStrategy = C.NetworkStrategy(options.NetworkStrategy) 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) networkFallbackDelay = time.Duration(options.NetworkFallbackDelay)
if networkManager == nil || !networkManager.AutoDetectInterface() { if networkManager == nil || !networkManager.AutoDetectInterface() {
return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`") 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 { if networkManager != nil && options.BindInterface == "" && options.Inet4BindAddress == nil && options.Inet6BindAddress == nil {
defaultOptions := networkManager.DefaultOptions() defaultOptions := networkManager.DefaultOptions()
if defaultOptions.DefaultInterface != "" { if defaultOptions.BindInterface != "" {
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.DefaultInterface, -1) bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc) dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc)
} else if networkManager.AutoDetectInterface() { } else if networkManager.AutoDetectInterface() {
if defaultOptions.DefaultNetworkStrategy != C.NetworkStrategyDefault && C.NetworkStrategy(options.NetworkStrategy) == C.NetworkStrategyDefault { if defaultOptions.NetworkStrategy != C.NetworkStrategyDefault && C.NetworkStrategy(options.NetworkStrategy) == C.NetworkStrategyDefault {
networkStrategy = defaultOptions.DefaultNetworkStrategy networkStrategy = defaultOptions.NetworkStrategy
networkFallbackDelay = defaultOptions.DefaultFallbackDelay networkType = defaultOptions.NetworkType
fallbackNetworkType = defaultOptions.FallbackNetworkType
networkFallbackDelay = defaultOptions.FallbackDelay
bindFunc := networkManager.ProtectFunc() bindFunc := networkManager.ProtectFunc()
dialer.Control = control.Append(dialer.Control, bindFunc) dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc)
@ -179,6 +188,8 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti
isWireGuardListener: options.IsWireGuardListener, isWireGuardListener: options.IsWireGuardListener,
networkManager: networkManager, networkManager: networkManager,
networkStrategy: networkStrategy, networkStrategy: networkStrategy,
networkType: networkType,
fallbackNetworkType: fallbackNetworkType,
networkFallbackDelay: networkFallbackDelay, networkFallbackDelay: networkFallbackDelay,
}, nil }, nil
} }
@ -202,11 +213,11 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address)) return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
} }
} else { } 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 { if strategy == C.NetworkStrategyDefault {
return d.DialContext(ctx, network, address) return d.DialContext(ctx, network, address)
} }
@ -226,9 +237,9 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
err error err error
) )
if !fastFallback { 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 { } 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 { if err != nil {
return nil, err 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)) return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
} }
} else { } 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 { if strategy == C.NetworkStrategyDefault {
return d.ListenPacket(ctx, destination) return d.ListenPacket(ctx, destination)
} }
@ -264,11 +275,11 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
if destination.IsIPv4() && !destination.Addr.IsUnspecified() { if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
network += "4" 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) { 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) { func trackConn(conn net.Conn, err error) (net.Conn, error) {

View file

@ -7,14 +7,14 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
N "github.com/sagernet/sing/common/network" 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) { 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) primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
return nil, false, E.New("no available network interface") 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) { 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) primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
return nil, false, E.New("no available network interface") 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) { 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) primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 { if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
return nil, E.New("no available network interface") 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...) 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() interfaces := networkManager.NetworkInterfaces()
switch strategy { switch strategy {
case C.NetworkStrategyFallback: case C.NetworkStrategyDefault:
defaultIf := networkManager.InterfaceMonitor().DefaultInterface() if len(interfaceType) == 0 {
if defaultIf != nil { defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
for _, iif := range interfaces { for _, iif := range interfaces {
if iif.Index == defaultIf.Index { if iif.Index == defaultIf.Index {
primaryInterfaces = append(primaryInterfaces, iif) primaryInterfaces = append(primaryInterfaces, iif)
@ -188,54 +188,36 @@ func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkS
} }
} }
} else { } else {
primaryInterfaces = interfaces primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(interfaceType, iif.Type)
})
} }
case C.NetworkStrategyHybrid: case C.NetworkStrategyHybrid:
primaryInterfaces = interfaces if len(interfaceType) == 0 {
case C.NetworkStrategyWIFI: primaryInterfaces = interfaces
for _, iif := range interfaces { } else {
if iif.Type == C.InterfaceTypeWIFI { primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
primaryInterfaces = append(primaryInterfaces, iif) return common.Contains(interfaceType, iif.Type)
} else { })
fallbackInterfaces = append(fallbackInterfaces, iif)
}
} }
case C.NetworkStrategyCellular: case C.NetworkStrategyFallback:
for _, iif := range interfaces { if len(interfaceType) == 0 {
if iif.Type == C.InterfaceTypeCellular { defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
primaryInterfaces = append(primaryInterfaces, iif) for _, iif := range interfaces {
} else { if iif.Index == defaultIf.Index {
fallbackInterfaces = append(fallbackInterfaces, iif) 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: fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
for _, iif := range interfaces { return common.Contains(fallbackInterfaceType, iif.Type)
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))
} }
return primaryInterfaces, fallbackInterfaces return primaryInterfaces, fallbackInterfaces
} }

View file

@ -13,13 +13,13 @@ import (
N "github.com/sagernet/sing/common/network" 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 { 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 var errors []error
for _, address := range destinationAddresses { 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 { if err == nil {
return conn, nil return conn, nil
} }
@ -28,7 +28,7 @@ func DialSerialNetwork(ctx context.Context, dialer ParallelInterfaceDialer, netw
return nil, E.Errors(errors...) 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 { if fallbackDelay == 0 {
fallbackDelay = N.DefaultFallbackDelay fallbackDelay = N.DefaultFallbackDelay
} }
@ -43,7 +43,7 @@ func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, ne
return address.Is6() && !address.Is4In6() return address.Is6() && !address.Is4In6()
}) })
if len(addresses4) == 0 || len(addresses6) == 0 { 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 var primaries, fallbacks []netip.Addr
if preferIPv6 { if preferIPv6 {
@ -65,7 +65,7 @@ func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, ne
if !primary { if !primary {
ras = fallbacks 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 { select {
case results <- dialResult{Conn: c, error: err, primary: primary, done: true}: case results <- dialResult{Conn: c, error: err, primary: primary, done: true}:
case <-returned: 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 { 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 var errors []error
for _, address := range destinationAddresses { 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 { if err == nil {
return conn, address, nil return conn, address, nil
} }

View file

@ -77,11 +77,11 @@ func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInter
type ParallelInterfaceDialer interface { type ParallelInterfaceDialer interface {
N.Dialer N.Dialer
DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, 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, fallbackDelay time.Duration) (net.PacketConn, 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 { type ParallelNetworkDialer interface {
DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, 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, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, 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)
} }

View file

@ -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 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() { if !destination.IsFqdn() {
return d.dialer.DialContext(ctx, network, destination) return d.dialer.DialContext(ctx, network, destination)
} }
@ -128,13 +128,13 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context
fallbackDelay = d.fallbackDelay fallbackDelay = d.fallbackDelay
} }
if d.parallel { 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 { } 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() { if !destination.IsFqdn() {
return d.dialer.ListenPacket(ctx, destination) return d.dialer.ListenPacket(ctx, destination)
} }
@ -152,7 +152,7 @@ func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.C
if err != nil { if err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -226,7 +226,7 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
} }
rule.AdGuardDomainMatcher = matcher rule.AdGuardDomainMatcher = matcher
case ruleItemNetworkType: case ruleItemNetworkType:
rule.NetworkType, err = readRuleItemString(reader) rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
case ruleItemNetworkIsExpensive: case ruleItemNetworkIsExpensive:
rule.NetworkIsExpensive = true rule.NetworkIsExpensive = true
case ruleItemNetworkIsConstrained: case ruleItemNetworkIsConstrained:
@ -349,7 +349,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
if generateVersion < C.RuleSetVersion3 { if generateVersion < C.RuleSetVersion3 {
return E.New("network_type rule item is only supported in version 3 or later") 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 { if err != nil {
return err return err
} }
@ -414,6 +414,18 @@ func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) e
return varbin.Write(writer, binary.BigEndian, value) 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) { func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) {
return varbin.ReadValue[[]uint16](reader, binary.BigEndian) return varbin.ReadValue[[]uint16](reader, binary.BigEndian)
} }

View file

@ -5,44 +5,52 @@ import (
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
) )
type InterfaceType uint8
const ( const (
InterfaceTypeWIFI = "wifi" InterfaceTypeWIFI InterfaceType = iota
InterfaceTypeCellular = "cellular" InterfaceTypeCellular
InterfaceTypeEthernet = "ethernet" InterfaceTypeEthernet
InterfaceTypeOther = "other" 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 ( const (
NetworkStrategyDefault NetworkStrategy = iota NetworkStrategyDefault NetworkStrategy = iota
NetworkStrategyFallback NetworkStrategyFallback
NetworkStrategyHybrid NetworkStrategyHybrid
NetworkStrategyWIFI
NetworkStrategyCellular
NetworkStrategyEthernet
NetworkStrategyWIFIOnly
NetworkStrategyCellularOnly
NetworkStrategyEthernetOnly
) )
var ( var (
NetworkStrategyToString = map[NetworkStrategy]string{ networkStrategyToString = map[NetworkStrategy]string{
NetworkStrategyDefault: "default", NetworkStrategyDefault: "default",
NetworkStrategyFallback: "fallback", NetworkStrategyFallback: "fallback",
NetworkStrategyHybrid: "hybrid", NetworkStrategyHybrid: "hybrid",
NetworkStrategyWIFI: "wifi",
NetworkStrategyCellular: "cellular",
NetworkStrategyEthernet: "ethernet",
NetworkStrategyWIFIOnly: "wifi_only",
NetworkStrategyCellularOnly: "cellular_only",
NetworkStrategyEthernetOnly: "ethernet_only",
} }
StringToNetworkStrategy = common.ReverseMap(NetworkStrategyToString) StringToNetworkStrategy = common.ReverseMap(networkStrategyToString)
) )
func (s NetworkStrategy) String() string { func (s NetworkStrategy) String() string {
name, loaded := NetworkStrategyToString[s] name, loaded := networkStrategyToString[s]
if !loaded { if !loaded {
return F.ToString(int(s)) return F.ToString(int(s))
} }

View file

@ -7,6 +7,8 @@ icon: material/new-box
!!! quote "Changes in sing-box 1.11.0" !!! quote "Changes in sing-box 1.11.0"
:material-plus: [default_network_strategy](#default_network_strategy) :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) :material-alert: [default_fallback_delay](#default_fallback_delay)
!!! quote "Changes in sing-box 1.8.0" !!! quote "Changes in sing-box 1.8.0"
@ -30,17 +32,18 @@ icon: material/new-box
"default_interface": "", "default_interface": "",
"default_mark": 0, "default_mark": 0,
"default_network_strategy": "", "default_network_strategy": "",
"default_network_type": [],
"default_fallback_network_type": [],
"default_fallback_delay": "" "default_fallback_delay": ""
} }
} }
``` ```
### Fields !!! note ""
| Key | Format | You can ignore the JSON Array [] tag when the content is only one item
|-----------|-----------------------|
| `geoip` | [GeoIP](./geoip/) | ### Fields
| `geosite` | [Geosite](./geosite/) |
#### rules #### rules
@ -96,11 +99,9 @@ Takes no effect if `outbound.routing_mark` is set.
#### default_network_strategy #### 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. See [Dial Fields](/configuration/shared/dial/#network_strategy) for details.
Strategy for selecting network interfaces.
Takes no effect if `outbound.bind_interface`, `outbound.inet4_bind_address` or `outbound.inet6_bind_address` is set. 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`. 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 #### 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. See [Dial Fields](/configuration/shared/dial/#fallback_delay) for details.

View file

@ -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 中的更改" !!! quote "sing-box 1.8.0 中的更改"
:material-plus: [rule_set](#rule_set) :material-plus: [rule_set](#rule_set)
@ -26,6 +37,10 @@
} }
``` ```
!!! note ""
当内容只有一项时,可以忽略 JSON 数组 [] 标签
### 字段 ### 字段
| 键 | 格式 | | 键 | 格式 |
@ -87,11 +102,9 @@
#### network_strategy #### 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` 已设置时不生效。 `outbound.bind_interface`, `outbound.inet4_bind_address``outbound.inet6_bind_address` 已设置时不生效。
@ -99,12 +112,20 @@
`default_interface` 冲突。 `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)。

View file

@ -11,12 +11,18 @@ icon: material/new-box
"action": "route", // default "action": "route", // default
"outbound": "", "outbound": "",
"network_strategy": "", "network_strategy": "",
"network_type": [],
"fallback_network_type": [],
"fallback_delay": "", "fallback_delay": "",
"udp_disable_domain_unmapping": false, "udp_disable_domain_unmapping": false,
"udp_connect": 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. `route` inherits the classic rule behavior of routing connection to the specified outbound.
#### outbound #### outbound
@ -27,23 +33,21 @@ Tag of target outbound.
#### network_strategy #### network_strategy
!!! quote "" See [Dial Fields](/configuration/shared/dial/#network_strategy) for details.
Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled.
Strategy for selecting network interfaces.
Only take effect if outbound is direct without `outbound.bind_interface`, Only take effect if outbound is direct without `outbound.bind_interface`,
`outbound.inet4_bind_address` and `outbound.inet6_bind_address` set. `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 #### 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. See [Dial Fields](/configuration/shared/dial/#fallback_delay) for details.
#### udp_disable_domain_unmapping #### udp_disable_domain_unmapping

View file

@ -12,6 +12,8 @@ icon: material/new-box
"outbound": "", "outbound": "",
"network_strategy": "", "network_strategy": "",
"fallback_delay": "", "fallback_delay": "",
"network_type": [],
"fallback_network_type": [],
"udp_disable_domain_unmapping": false, "udp_disable_domain_unmapping": false,
"udp_connect": false "udp_connect": false
} }
@ -27,23 +29,21 @@ icon: material/new-box
#### network_strategy #### network_strategy
!!! quote "" 详情参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。
仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface`
选择网络接口的策略。
仅当出站为 `direct``outbound.bind_interface`, `outbound.inet4_bind_address` 仅当出站为 `direct``outbound.bind_interface`, `outbound.inet4_bind_address`
`outbound.inet6_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 #### fallback_delay
!!! quote ""
仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface``network_strategy` 已设置。
详情参阅 [拨号字段](/configuration/shared/dial/#fallback_delay)。 详情参阅 [拨号字段](/configuration/shared/dial/#fallback_delay)。
#### udp_disable_domain_unmapping #### udp_disable_domain_unmapping
@ -68,6 +68,10 @@ icon: material/new-box
} }
``` ```
!!! note ""
当内容只有一项时,可以忽略 JSON 数组 [] 标签
`route-options` 为路由设置选项。 `route-options` 为路由设置选项。
### reject ### reject

View file

@ -6,6 +6,8 @@ icon: material/new-box
:material-plus: [network_strategy](#network_strategy) :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 ### Structure
@ -23,10 +25,16 @@ icon: material/new-box
"udp_fragment": false, "udp_fragment": false,
"domain_strategy": "prefer_ipv6", "domain_strategy": "prefer_ipv6",
"network_strategy": "default", "network_strategy": "default",
"network_type": [],
"fallback_network_type": [],
"fallback_delay": "300ms" "fallback_delay": "300ms"
} }
``` ```
!!! note ""
You can ignore the JSON Array [] tag when the content is only one item
### Fields ### Fields
#### detour #### detour
@ -101,30 +109,57 @@ If set, the requested domain name will be resolved to IP before connect.
!!! quote "" !!! 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. Strategy for selecting network interfaces.
Available values: Available values:
- `default` (default): Connect to the default interface. - `default` (default): Connect to default network or networks specified in `network_type` sequentially.
- `fallback`: Try all other interfaces when timeout. - `hybrid`: Connect to all networks or networks specified in `network_type` concurrently.
- `hybrid`: Connect to all interfaces concurrently and choose the fastest one. - `fallback`: Connect to default network or preferred networks specified in `network_type` concurrently, and try fallback networks when unavailable or timeout.
- `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.
For fallback strategies, when preferred interfaces fails or times out, For fallback, when preferred interfaces fails or times out,
it will enter a 15s fast fallback state (upgraded to `hybrid`), it will enter a 15s fast fallback state (Connect to all preferred and fallback networks concurrently),
and exit immediately if recovers. and exit immediately if preferred networks recover.
Conflicts with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`. 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 #### 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. 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 For `domain_strategy`, is the amount of time to wait for connection to succeed before assuming

View file

@ -6,6 +6,8 @@ icon: material/new-box
:material-plus: [network_strategy](#network_strategy) :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, "udp_fragment": false,
"domain_strategy": "prefer_ipv6", "domain_strategy": "prefer_ipv6",
"network_strategy": "", "network_strategy": "",
"network_type": [],
"fallback_network_type": [],
"fallback_delay": "300ms" "fallback_delay": "300ms"
} }
``` ```
!!! note ""
当内容只有一项时,可以忽略 JSON 数组 [] 标签
### 字段 ### 字段
#### detour #### detour
@ -99,26 +107,48 @@ icon: material/new-box
!!! quote "" !!! quote ""
仅在 Android 与 iOS 平台图形客户端中支持。 仅在 Android 与 iOS 平台图形客户端中支持,并且需要 `route.auto_detect_interface`
用于选择网络接口的策略。 用于选择网络接口的策略。
可用值: 可用值:
- `default` (默认): 连接到默认接口, - `default`(默认值):按顺序连接默认网络或 `network_type` 中指定的网络。
- `fallback`: 如果超时,尝试所有剩余接口。 - `hybrid`:同时连接所有网络或 `network_type` 中指定的网络。
- `hybrid`: 同时尝试所有接口,选择最快的一个。 - `fallback`:同时连接默认网络或 `network_type` 中指定的首选网络,当不可用或超时时尝试回退网络。
- `wifi`: 优先使用 WIFI但在不可用或超时时尝试所有其他接口。
- `cellular`: 优先使用蜂窝数据,但在不可用或超时时尝试所有其他接口。
- `ethernet`: 优先使用以太网,但在不可用或超时时尝试所有其他接口。
- `wifi_only`: 仅连接到 WIFI。
- `cellular_only`: 仅连接到蜂窝数据。
- `ethernet_only`: 仅连接到以太网。
对于回退策略, 当优先使用的接口发生故障或超时时, 将进入 15 秒的快速回退状态(升级为 `hybrid` 且恢复后立即退出。 对于回退模式,当首选接口失败或超时时,
将进入15秒的快速回退状态同时连接所有首选和回退网络
如果首选网络恢复,则立即退出。
`bind_interface`, `bind_inet4_address``bind_inet6_address` 冲突。 `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 #### fallback_delay
在生成 RFC 6555 快速回退连接之前等待的时间长度。 在生成 RFC 6555 快速回退连接之前等待的时间长度。

View file

@ -34,10 +34,10 @@ type InterfaceUpdateListener interface {
} }
const ( const (
InterfaceTypeWIFI = C.InterfaceTypeWIFI InterfaceTypeWIFI = int32(C.InterfaceTypeWIFI)
InterfaceTypeCellular = C.InterfaceTypeCellular InterfaceTypeCellular = int32(C.InterfaceTypeCellular)
InterfaceTypeEthernet = C.InterfaceTypeEthernet InterfaceTypeEthernet = int32(C.InterfaceTypeEthernet)
InterfaceTypeOther = C.InterfaceTypeOther InterfaceTypeOther = int32(C.InterfaceTypeOther)
) )
type NetworkInterface struct { type NetworkInterface struct {
@ -47,7 +47,7 @@ type NetworkInterface struct {
Addresses StringIterator Addresses StringIterator
Flags int32 Flags int32
Type string Type int32
DNSServer StringIterator DNSServer StringIterator
Metered bool Metered bool
} }

View file

@ -189,7 +189,7 @@ func (w *platformInterfaceWrapper) Interfaces() ([]adapter.NetworkInterface, err
Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix), Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix),
Flags: linkFlags(uint32(netInterface.Flags)), Flags: linkFlags(uint32(netInterface.Flags)),
}, },
Type: netInterface.Type, Type: C.InterfaceType(netInterface.Type),
DNSServers: iteratorToArray[string](netInterface.DNSServer), DNSServers: iteratorToArray[string](netInterface.DNSServer),
Expensive: netInterface.Metered || isDefault && w.isExpensive, Expensive: netInterface.Metered || isDefault && w.isExpensive,
Constrained: isDefault && w.isConstrained, Constrained: isDefault && w.isConstrained,

View file

@ -65,23 +65,25 @@ type DialerOptionsWrapper interface {
} }
type DialerOptions struct { type DialerOptions struct {
Detour string `json:"detour,omitempty"` Detour string `json:"detour,omitempty"`
BindInterface string `json:"bind_interface,omitempty"` BindInterface string `json:"bind_interface,omitempty"`
Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"` Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"`
Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"` Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"`
ProtectPath string `json:"protect_path,omitempty"` ProtectPath string `json:"protect_path,omitempty"`
RoutingMark uint32 `json:"routing_mark,omitempty"` RoutingMark FwMark `json:"routing_mark,omitempty"`
ReuseAddr bool `json:"reuse_addr,omitempty"` ReuseAddr bool `json:"reuse_addr,omitempty"`
ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"` ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"`
TCPFastOpen bool `json:"tcp_fast_open,omitempty"` TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
TCPMultiPath bool `json:"tcp_multi_path,omitempty"` TCPMultiPath bool `json:"tcp_multi_path,omitempty"`
UDPFragment *bool `json:"udp_fragment,omitempty"` UDPFragment *bool `json:"udp_fragment,omitempty"`
UDPFragmentDefault bool `json:"-"` UDPFragmentDefault bool `json:"-"`
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"` NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"`
FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"` NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
NetworkFallbackDelay badoption.Duration `json:"network_fallback_delay,omitempty"` FallbackNetworkType badoption.Listable[InterfaceType] `json:"fallback_network_type,omitempty"`
IsWireGuardListener bool `json:"-"` FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"`
NetworkFallbackDelay badoption.Duration `json:"network_fallback_delay,omitempty"`
IsWireGuardListener bool `json:"-"`
} }
func (o *DialerOptions) TakeDialerOptions() DialerOptions { func (o *DialerOptions) TakeDialerOptions() DialerOptions {

View file

@ -3,18 +3,20 @@ package option
import "github.com/sagernet/sing/common/json/badoption" import "github.com/sagernet/sing/common/json/badoption"
type RouteOptions struct { type RouteOptions struct {
GeoIP *GeoIPOptions `json:"geoip,omitempty"` GeoIP *GeoIPOptions `json:"geoip,omitempty"`
Geosite *GeositeOptions `json:"geosite,omitempty"` Geosite *GeositeOptions `json:"geosite,omitempty"`
Rules []Rule `json:"rules,omitempty"` Rules []Rule `json:"rules,omitempty"`
RuleSet []RuleSet `json:"rule_set,omitempty"` RuleSet []RuleSet `json:"rule_set,omitempty"`
Final string `json:"final,omitempty"` Final string `json:"final,omitempty"`
FindProcess bool `json:"find_process,omitempty"` FindProcess bool `json:"find_process,omitempty"`
AutoDetectInterface bool `json:"auto_detect_interface,omitempty"` AutoDetectInterface bool `json:"auto_detect_interface,omitempty"`
OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"` OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"`
DefaultInterface string `json:"default_interface,omitempty"` DefaultInterface string `json:"default_interface,omitempty"`
DefaultMark uint32 `json:"default_mark,omitempty"` DefaultMark uint32 `json:"default_mark,omitempty"`
DefaultNetworkStrategy NetworkStrategy `json:"default_network_strategy,omitempty"` DefaultNetworkStrategy NetworkStrategy `json:"default_network_strategy,omitempty"`
DefaultFallbackDelay badoption.Duration `json:"default_fallback_delay,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 { type GeoIPOptions struct {

View file

@ -67,42 +67,42 @@ func (r Rule) IsValid() bool {
} }
type RawDefaultRule struct { type RawDefaultRule struct {
Inbound badoption.Listable[string] `json:"inbound,omitempty"` Inbound badoption.Listable[string] `json:"inbound,omitempty"`
IPVersion int `json:"ip_version,omitempty"` IPVersion int `json:"ip_version,omitempty"`
Network badoption.Listable[string] `json:"network,omitempty"` Network badoption.Listable[string] `json:"network,omitempty"`
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
Protocol badoption.Listable[string] `json:"protocol,omitempty"` Protocol badoption.Listable[string] `json:"protocol,omitempty"`
Client badoption.Listable[string] `json:"client,omitempty"` Client badoption.Listable[string] `json:"client,omitempty"`
Domain badoption.Listable[string] `json:"domain,omitempty"` Domain badoption.Listable[string] `json:"domain,omitempty"`
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
Geosite badoption.Listable[string] `json:"geosite,omitempty"` Geosite badoption.Listable[string] `json:"geosite,omitempty"`
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
GeoIP badoption.Listable[string] `json:"geoip,omitempty"` GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
IPIsPrivate bool `json:"ip_is_private,omitempty"` IPIsPrivate bool `json:"ip_is_private,omitempty"`
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
Port badoption.Listable[uint16] `json:"port,omitempty"` Port badoption.Listable[uint16] `json:"port,omitempty"`
PortRange badoption.Listable[string] `json:"port_range,omitempty"` PortRange badoption.Listable[string] `json:"port_range,omitempty"`
ProcessName badoption.Listable[string] `json:"process_name,omitempty"` ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
PackageName badoption.Listable[string] `json:"package_name,omitempty"` PackageName badoption.Listable[string] `json:"package_name,omitempty"`
User badoption.Listable[string] `json:"user,omitempty"` User badoption.Listable[string] `json:"user,omitempty"`
UserID badoption.Listable[int32] `json:"user_id,omitempty"` UserID badoption.Listable[int32] `json:"user_id,omitempty"`
ClashMode string `json:"clash_mode,omitempty"` ClashMode string `json:"clash_mode,omitempty"`
NetworkType badoption.Listable[string] `json:"network_type,omitempty"` NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
Invert bool `json:"invert,omitempty"` Invert bool `json:"invert,omitempty"`
// Deprecated: renamed to rule_set_ip_cidr_match_source // Deprecated: renamed to rule_set_ip_cidr_match_source
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`

View file

@ -68,44 +68,44 @@ func (r DNSRule) IsValid() bool {
} }
type RawDefaultDNSRule struct { type RawDefaultDNSRule struct {
Inbound badoption.Listable[string] `json:"inbound,omitempty"` Inbound badoption.Listable[string] `json:"inbound,omitempty"`
IPVersion int `json:"ip_version,omitempty"` IPVersion int `json:"ip_version,omitempty"`
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
Network badoption.Listable[string] `json:"network,omitempty"` Network badoption.Listable[string] `json:"network,omitempty"`
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"` AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
Protocol badoption.Listable[string] `json:"protocol,omitempty"` Protocol badoption.Listable[string] `json:"protocol,omitempty"`
Domain badoption.Listable[string] `json:"domain,omitempty"` Domain badoption.Listable[string] `json:"domain,omitempty"`
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
Geosite badoption.Listable[string] `json:"geosite,omitempty"` Geosite badoption.Listable[string] `json:"geosite,omitempty"`
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"` SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
GeoIP badoption.Listable[string] `json:"geoip,omitempty"` GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
IPIsPrivate bool `json:"ip_is_private,omitempty"` IPIsPrivate bool `json:"ip_is_private,omitempty"`
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
Port badoption.Listable[uint16] `json:"port,omitempty"` Port badoption.Listable[uint16] `json:"port,omitempty"`
PortRange badoption.Listable[string] `json:"port_range,omitempty"` PortRange badoption.Listable[string] `json:"port_range,omitempty"`
ProcessName badoption.Listable[string] `json:"process_name,omitempty"` ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
PackageName badoption.Listable[string] `json:"package_name,omitempty"` PackageName badoption.Listable[string] `json:"package_name,omitempty"`
User badoption.Listable[string] `json:"user,omitempty"` User badoption.Listable[string] `json:"user,omitempty"`
UserID badoption.Listable[int32] `json:"user_id,omitempty"` UserID badoption.Listable[int32] `json:"user_id,omitempty"`
Outbound badoption.Listable[string] `json:"outbound,omitempty"` Outbound badoption.Listable[string] `json:"outbound,omitempty"`
ClashMode string `json:"clash_mode,omitempty"` ClashMode string `json:"clash_mode,omitempty"`
NetworkType badoption.Listable[string] `json:"network_type,omitempty"` NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"` RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"` RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"`
Invert bool `json:"invert,omitempty"` Invert bool `json:"invert,omitempty"`
// Deprecated: renamed to rule_set_ip_cidr_match_source // Deprecated: renamed to rule_set_ip_cidr_match_source
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"` Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`

View file

@ -146,28 +146,28 @@ func (r HeadlessRule) IsValid() bool {
} }
type DefaultHeadlessRule struct { type DefaultHeadlessRule struct {
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
Network badoption.Listable[string] `json:"network,omitempty"` Network badoption.Listable[string] `json:"network,omitempty"`
Domain badoption.Listable[string] `json:"domain,omitempty"` Domain badoption.Listable[string] `json:"domain,omitempty"`
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"` DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"` DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"` DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"` SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"` IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"` SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"` SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
Port badoption.Listable[uint16] `json:"port,omitempty"` Port badoption.Listable[uint16] `json:"port,omitempty"`
PortRange badoption.Listable[string] `json:"port_range,omitempty"` PortRange badoption.Listable[string] `json:"port_range,omitempty"`
ProcessName badoption.Listable[string] `json:"process_name,omitempty"` ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"` ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"` ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
PackageName badoption.Listable[string] `json:"package_name,omitempty"` PackageName badoption.Listable[string] `json:"package_name,omitempty"`
NetworkType badoption.Listable[string] `json:"network_type,omitempty"` NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"` NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"` NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"` WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"` WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
Invert bool `json:"invert,omitempty"` Invert bool `json:"invert,omitempty"`
DomainMatcher *domain.Matcher `json:"-"` DomainMatcher *domain.Matcher `json:"-"`
SourceIPSet *netipx.IPSet `json:"-"` SourceIPSet *netipx.IPSet `json:"-"`

View file

@ -171,3 +171,27 @@ func (n *NetworkStrategy) UnmarshalJSON(content []byte) error {
*n = NetworkStrategy(strategy) *n = NetworkStrategy(strategy)
return nil 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
}

View file

@ -12,7 +12,7 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "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"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@ -37,6 +37,8 @@ type Outbound struct {
domainStrategy dns.DomainStrategy domainStrategy dns.DomainStrategy
fallbackDelay time.Duration fallbackDelay time.Duration
networkStrategy C.NetworkStrategy networkStrategy C.NetworkStrategy
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration networkFallbackDelay time.Duration
overrideOption int overrideOption int
overrideDestination M.Socksaddr overrideDestination M.Socksaddr
@ -55,6 +57,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
domainStrategy: dns.DomainStrategy(options.DomainStrategy), domainStrategy: dns.DomainStrategy(options.DomainStrategy),
fallbackDelay: time.Duration(options.FallbackDelay), fallbackDelay: time.Duration(options.FallbackDelay),
networkStrategy: C.NetworkStrategy(options.NetworkStrategy), 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), networkFallbackDelay: time.Duration(options.NetworkFallbackDelay),
dialer: outboundDialer, dialer: outboundDialer,
// loopBack: newLoopBackDetector(router), // 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 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) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag() metadata.Outbound = h.Tag()
metadata.Destination = destination 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 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) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag() metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
@ -232,7 +236,7 @@ func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.
} else { } else {
h.logger.InfoContext(ctx, "outbound packet connection to ", destination) 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 { if err != nil {
return nil, netip.Addr{}, err return nil, netip.Addr{}, err
} }

View file

@ -59,10 +59,12 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp
interfaceFinder: control.NewDefaultInterfaceFinder(), interfaceFinder: control.NewDefaultInterfaceFinder(),
autoDetectInterface: routeOptions.AutoDetectInterface, autoDetectInterface: routeOptions.AutoDetectInterface,
defaultOptions: adapter.NetworkOptions{ defaultOptions: adapter.NetworkOptions{
DefaultInterface: routeOptions.DefaultInterface, BindInterface: routeOptions.DefaultInterface,
DefaultMark: routeOptions.DefaultMark, RoutingMark: routeOptions.DefaultMark,
DefaultNetworkStrategy: C.NetworkStrategy(routeOptions.DefaultNetworkStrategy), NetworkStrategy: C.NetworkStrategy(routeOptions.DefaultNetworkStrategy),
DefaultFallbackDelay: time.Duration(routeOptions.DefaultFallbackDelay), 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), pauseManager: service.FromContext[pause.Manager](ctx),
platformInterface: service.FromContext[platform.Interface](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 { networkInterface := common.Find(r.networkInterfaces.Load(), func(it adapter.NetworkInterface) bool {
return it.Interface.Index == defaultInterface.Index return it.Interface.Index == defaultInterface.Index
}) })
if networkInterface.Type == "" { if networkInterface.Name == "" {
// race // race
return return
} }

View file

@ -140,6 +140,8 @@ func (r *RuleActionRoute) String() string {
type RuleActionRouteOptions struct { type RuleActionRouteOptions struct {
NetworkStrategy C.NetworkStrategy NetworkStrategy C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration FallbackDelay time.Duration
UDPDisableDomainUnmapping bool UDPDisableDomainUnmapping bool
UDPConnect bool UDPConnect bool

View file

@ -8,6 +8,7 @@ import (
"github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/service" "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) rule.allItems = append(rule.allItems, item)
} }
if len(options.NetworkType) > 0 { 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.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
} }

View file

@ -221,7 +221,7 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
} }
if len(options.NetworkType) > 0 { 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.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
} }

View file

@ -6,6 +6,7 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
) )
@ -142,7 +143,7 @@ func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessR
} }
if networkManager != nil { if networkManager != nil {
if len(options.NetworkType) > 0 { 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.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item) rule.allItems = append(rule.allItems, item)
} }

View file

@ -4,6 +4,7 @@ import (
"strings" "strings"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
) )
@ -12,10 +13,10 @@ var _ RuleItem = (*NetworkTypeItem)(nil)
type NetworkTypeItem struct { type NetworkTypeItem struct {
networkManager adapter.NetworkManager 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{ return &NetworkTypeItem{
networkManager: networkManager, networkManager: networkManager,
networkType: networkType, networkType: networkType,