Refactor multi networks strategy

This commit is contained in:
世界 2024-11-13 19:05:28 +08:00
parent 2be7482e32
commit 55b2e114f6
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
27 changed files with 326 additions and 270 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:
if len(interfaceType) == 0 {
defaultIf := networkManager.InterfaceMonitor().DefaultInterface() defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
if defaultIf != nil {
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:
if len(interfaceType) == 0 {
primaryInterfaces = interfaces primaryInterfaces = interfaces
case C.NetworkStrategyWIFI: } else {
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(interfaceType, iif.Type)
})
}
case C.NetworkStrategyFallback:
if len(interfaceType) == 0 {
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
for _, iif := range interfaces { for _, iif := range interfaces {
if iif.Type == C.InterfaceTypeWIFI { if iif.Index == defaultIf.Index {
primaryInterfaces = append(primaryInterfaces, iif) primaryInterfaces = append(primaryInterfaces, iif)
} else { } else {
fallbackInterfaces = append(fallbackInterfaces, iif) fallbackInterfaces = append(fallbackInterfaces, iif)
} }
} }
case C.NetworkStrategyCellular:
for _, iif := range interfaces {
if iif.Type == C.InterfaceTypeCellular {
primaryInterfaces = append(primaryInterfaces, iif)
} else { } else {
fallbackInterfaces = append(fallbackInterfaces, iif) primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(interfaceType, iif.Type)
})
} }
} fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
case C.NetworkStrategyEthernet: return common.Contains(fallbackInterfaceType, iif.Type)
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))
} }
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

@ -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,

2
go.mod
View File

@ -25,7 +25,7 @@ require (
github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3 github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3
github.com/sagernet/quic-go v0.48.1-beta.1 github.com/sagernet/quic-go v0.48.1-beta.1
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.6.0-alpha.8 github.com/sagernet/sing v0.6.0-alpha.9
github.com/sagernet/sing-dns v0.4.0-alpha.1 github.com/sagernet/sing-dns v0.4.0-alpha.1
github.com/sagernet/sing-mux v0.3.0-alpha.1 github.com/sagernet/sing-mux v0.3.0-alpha.1
github.com/sagernet/sing-quic v0.3.0-rc.2 github.com/sagernet/sing-quic v0.3.0-rc.2

4
go.sum
View File

@ -110,8 +110,8 @@ github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.6.0-alpha.8 h1:j0ghX5QAXH/ozUjsTidMyHclkLw/mpRzHlQagByRsJI= github.com/sagernet/sing v0.6.0-alpha.9 h1:tOeHdRECQwe9R/1edVHbckF/IBoJoGzqhHRnHsNAQb8=
github.com/sagernet/sing v0.6.0-alpha.8/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.0-alpha.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.4.0-alpha.1 h1:2KlP8DeqtGkULFiZtvG2r7SuoJP6orANFzJwC5vDKvg= github.com/sagernet/sing-dns v0.4.0-alpha.1 h1:2KlP8DeqtGkULFiZtvG2r7SuoJP6orANFzJwC5vDKvg=
github.com/sagernet/sing-dns v0.4.0-alpha.1/go.mod h1:vgHATsm4wdymwpvBZPei8RY+546iGXS6hlWv2x6YKcM= github.com/sagernet/sing-dns v0.4.0-alpha.1/go.mod h1:vgHATsm4wdymwpvBZPei8RY+546iGXS6hlWv2x6YKcM=
github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg=

View File

@ -70,7 +70,7 @@ type DialerOptions struct {
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"`
@ -79,6 +79,8 @@ type DialerOptions struct {
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"`
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
FallbackNetworkType badoption.Listable[InterfaceType] `json:"fallback_network_type,omitempty"`
FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"` FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"`
NetworkFallbackDelay badoption.Duration `json:"network_fallback_delay,omitempty"` NetworkFallbackDelay badoption.Duration `json:"network_fallback_delay,omitempty"`
IsWireGuardListener bool `json:"-"` IsWireGuardListener bool `json:"-"`

View File

@ -14,6 +14,8 @@ type RouteOptions struct {
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"`
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"` DefaultFallbackDelay badoption.Duration `json:"default_fallback_delay,omitempty"`
} }

View File

@ -95,7 +95,7 @@ type RawDefaultRule struct {
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"`

View File

@ -97,7 +97,7 @@ type RawDefaultDNSRule struct {
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"`

View File

@ -162,7 +162,7 @@ type DefaultHeadlessRule struct {
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"`

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,