Refactor multi networks strategy

This commit is contained in:
世界 2024-11-13 19:05:28 +08:00
parent 26c7f86121
commit f13956b9d4
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
31 changed files with 509 additions and 344 deletions

View file

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

View file

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

View file

@ -26,7 +26,7 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a
var err error
if len(metadata.DestinationAddresses) > 0 {
if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer {
outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.FallbackDelay)
outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
} else {
outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses)
}
@ -56,7 +56,7 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
if metadata.UDPConnect {
if len(metadata.DestinationAddresses) > 0 {
if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer {
outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.FallbackDelay)
outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
} else {
outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses)
}
@ -74,7 +74,7 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
} else {
if len(metadata.DestinationAddresses) > 0 {
if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer {
outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, parallelDialer, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.FallbackDelay)
outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, parallelDialer, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
} else {
outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses)
}

View file

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

View file

@ -7,14 +7,14 @@ import (
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
N "github.com/sagernet/sing/common/network"
)
func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, bool, error) {
primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy)
func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, bool, error) {
primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
return nil, false, E.New("no available network interface")
}
@ -84,8 +84,8 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di
}
}
func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, fallbackDelay time.Duration, resetFastFallback func(time.Time)) (net.Conn, bool, error) {
primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy)
func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration, resetFastFallback func(time.Time)) (net.Conn, bool, error) {
primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
return nil, false, E.New("no available network interface")
}
@ -144,8 +144,8 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
}
}
func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, error) {
primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy)
func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
return nil, E.New("no available network interface")
}
@ -174,12 +174,12 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
return nil, E.Errors(errors...)
}
func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) {
func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) {
interfaces := networkManager.NetworkInterfaces()
switch strategy {
case C.NetworkStrategyFallback:
case C.NetworkStrategyDefault:
if len(interfaceType) == 0 {
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
if defaultIf != nil {
for _, iif := range interfaces {
if iif.Index == defaultIf.Index {
primaryInterfaces = append(primaryInterfaces, iif)
@ -188,54 +188,36 @@ func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkS
}
}
} else {
primaryInterfaces = interfaces
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(interfaceType, iif.Type)
})
}
case C.NetworkStrategyHybrid:
if len(interfaceType) == 0 {
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 {
if iif.Type == C.InterfaceTypeWIFI {
if iif.Index == defaultIf.Index {
primaryInterfaces = append(primaryInterfaces, iif)
} else {
fallbackInterfaces = append(fallbackInterfaces, iif)
}
}
case C.NetworkStrategyCellular:
for _, iif := range interfaces {
if iif.Type == C.InterfaceTypeCellular {
primaryInterfaces = append(primaryInterfaces, iif)
} else {
fallbackInterfaces = append(fallbackInterfaces, iif)
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(interfaceType, iif.Type)
})
}
}
case C.NetworkStrategyEthernet:
for _, iif := range interfaces {
if iif.Type == C.InterfaceTypeEthernet {
primaryInterfaces = append(primaryInterfaces, iif)
} else {
fallbackInterfaces = append(fallbackInterfaces, iif)
}
}
case C.NetworkStrategyWIFIOnly:
for _, iif := range interfaces {
if iif.Type == C.InterfaceTypeWIFI {
primaryInterfaces = append(primaryInterfaces, iif)
}
}
case C.NetworkStrategyCellularOnly:
for _, iif := range interfaces {
if iif.Type == C.InterfaceTypeCellular {
primaryInterfaces = append(primaryInterfaces, iif)
}
}
case C.NetworkStrategyEthernetOnly:
for _, iif := range interfaces {
if iif.Type == C.InterfaceTypeEthernet {
primaryInterfaces = append(primaryInterfaces, iif)
}
}
default:
panic(F.ToString("unknown network strategy: ", strategy))
fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(fallbackInterfaceType, iif.Type)
})
}
return primaryInterfaces, fallbackInterfaces
}

View file

@ -13,13 +13,13 @@ import (
N "github.com/sagernet/sing/common/network"
)
func DialSerialNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) {
func DialSerialNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, fallbackDelay)
return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
}
var errors []error
for _, address := range destinationAddresses {
conn, err := dialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, fallbackDelay)
conn, err := dialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
if err == nil {
return conn, nil
}
@ -28,7 +28,7 @@ func DialSerialNetwork(ctx context.Context, dialer ParallelInterfaceDialer, netw
return nil, E.Errors(errors...)
}
func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) {
func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if fallbackDelay == 0 {
fallbackDelay = N.DefaultFallbackDelay
}
@ -43,7 +43,7 @@ func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, ne
return address.Is6() && !address.Is4In6()
})
if len(addresses4) == 0 || len(addresses6) == 0 {
return DialSerialNetwork(ctx, dialer, network, destination, destinationAddresses, strategy, fallbackDelay)
return DialSerialNetwork(ctx, dialer, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
}
var primaries, fallbacks []netip.Addr
if preferIPv6 {
@ -65,7 +65,7 @@ func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, ne
if !primary {
ras = fallbacks
}
c, err := DialSerialNetwork(ctx, dialer, network, destination, ras, strategy, fallbackDelay)
c, err := DialSerialNetwork(ctx, dialer, network, destination, ras, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
select {
case results <- dialResult{Conn: c, error: err, primary: primary, done: true}:
case <-returned:
@ -106,13 +106,13 @@ func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, ne
}
}
func ListenSerialNetworkPacket(ctx context.Context, dialer ParallelInterfaceDialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
func ListenSerialNetworkPacket(ctx context.Context, dialer ParallelInterfaceDialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, fallbackDelay)
return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
}
var errors []error
for _, address := range destinationAddresses {
conn, err := dialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, fallbackDelay)
conn, err := dialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
if err == nil {
return conn, address, nil
}

View file

@ -77,11 +77,11 @@ func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInter
type ParallelInterfaceDialer interface {
N.Dialer
DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error)
ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, error)
DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error)
}
type ParallelNetworkDialer interface {
DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error)
ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error)
DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error)
}

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
}
func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) {
func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if !destination.IsFqdn() {
return d.dialer.DialContext(ctx, network, destination)
}
@ -128,13 +128,13 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context
fallbackDelay = d.fallbackDelay
}
if d.parallel {
return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, strategy, fallbackDelay)
return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
} else {
return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, fallbackDelay)
return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
}
}
func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, error) {
func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
if !destination.IsFqdn() {
return d.dialer.ListenPacket(ctx, destination)
}
@ -152,7 +152,7 @@ func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.C
if err != nil {
return nil, err
}
conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, fallbackDelay)
conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
if err != nil {
return nil, err
}

View file

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

View file

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

View file

@ -7,6 +7,8 @@ icon: material/new-box
!!! quote "Changes in sing-box 1.11.0"
:material-plus: [default_network_strategy](#default_network_strategy)
:material-plus: [default_network_type](#default_network_type)
:material-plus: [default_fallback_network_type](#default_fallback_network_type)
:material-alert: [default_fallback_delay](#default_fallback_delay)
!!! quote "Changes in sing-box 1.8.0"
@ -30,17 +32,18 @@ icon: material/new-box
"default_interface": "",
"default_mark": 0,
"default_network_strategy": "",
"default_network_type": [],
"default_fallback_network_type": [],
"default_fallback_delay": ""
}
}
```
### Fields
!!! note ""
| Key | Format |
|-----------|-----------------------|
| `geoip` | [GeoIP](./geoip/) |
| `geosite` | [Geosite](./geosite/) |
You can ignore the JSON Array [] tag when the content is only one item
### Fields
#### rules
@ -96,11 +99,9 @@ Takes no effect if `outbound.routing_mark` is set.
#### default_network_strategy
!!! quote ""
!!! question "Since sing-box 1.11.0"
Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled.
Strategy for selecting network interfaces.
See [Dial Fields](/configuration/shared/dial/#network_strategy) for details.
Takes no effect if `outbound.bind_interface`, `outbound.inet4_bind_address` or `outbound.inet6_bind_address` is set.
@ -108,12 +109,20 @@ Can be overrides by `outbound.network_strategy`.
Conflicts with `default_interface`.
See [Dial Fields](/configuration/shared/dial/#network_strategy) for available values.
#### default_network_type
!!! question "Since sing-box 1.11.0"
See [Dial Fields](/configuration/shared/dial/#network_type) for details.
#### default_fallback_network_type
!!! question "Since sing-box 1.11.0"
See [Dial Fields](/configuration/shared/dial/#fallback_network_type) for details.
#### default_fallback_delay
!!! quote ""
Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled and `network_strategy` set.
!!! question "Since sing-box 1.11.0"
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 中的更改"
:material-plus: [rule_set](#rule_set)
@ -26,6 +37,10 @@
}
```
!!! note ""
当内容只有一项时,可以忽略 JSON 数组 [] 标签
### 字段
| 键 | 格式 |
@ -87,11 +102,9 @@
#### network_strategy
!!! quote ""
!!! question "自 sing-box 1.11.0 起"
仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface`
选择网络接口的策略。
详情参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。
`outbound.bind_interface`, `outbound.inet4_bind_address``outbound.inet6_bind_address` 已设置时不生效。
@ -99,12 +112,20 @@
`default_interface` 冲突。
可用值请参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。
#### default_network_type
#### fallback_delay
!!! question "自 sing-box 1.11.0 起"
!!! quote ""
详情参阅 [拨号字段](/configuration/shared/dial/#default_network_type)。
仅在 Android 与 Apple 平台图形客户端中支持,并且需要 `auto_detect_interface``network_strategy` 已设置。
#### default_fallback_network_type
详情请参阅 [拨号字段](/configuration/shared/dial/#fallback_delay)。
!!! question "自 sing-box 1.11.0 起"
详情参阅 [拨号字段](/configuration/shared/dial/#default_fallback_network_type)。
#### default_fallback_delay
!!! question "自 sing-box 1.11.0 起"
详情参阅 [拨号字段](/configuration/shared/dial/#fallback_delay)。

View file

@ -11,12 +11,18 @@ icon: material/new-box
"action": "route", // default
"outbound": "",
"network_strategy": "",
"network_type": [],
"fallback_network_type": [],
"fallback_delay": "",
"udp_disable_domain_unmapping": false,
"udp_connect": false
}
```
!!! note ""
You can ignore the JSON Array [] tag when the content is only one item
`route` inherits the classic rule behavior of routing connection to the specified outbound.
#### outbound
@ -27,23 +33,21 @@ Tag of target outbound.
#### network_strategy
!!! quote ""
Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled.
Strategy for selecting network interfaces.
See [Dial Fields](/configuration/shared/dial/#network_strategy) for details.
Only take effect if outbound is direct without `outbound.bind_interface`,
`outbound.inet4_bind_address` and `outbound.inet6_bind_address` set.
See [Dial Fields](/configuration/shared/dial/#network_strategy) for available values.
#### network_type
See [Dial Fields](/configuration/shared/dial/#network_type) for details.
#### fallback_network_type
See [Dial Fields](/configuration/shared/dial/#fallback_network_type) for details.
#### fallback_delay
!!! quote ""
Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled and `network_strategy` set.
See [Dial Fields](/configuration/shared/dial/#fallback_delay) for details.
#### udp_disable_domain_unmapping

View file

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

View file

@ -6,6 +6,8 @@ icon: material/new-box
:material-plus: [network_strategy](#network_strategy)
:material-alert: [fallback_delay](#fallback_delay)
:material-alert: [network_type](#network_type)
:material-alert: [fallback_network_type](#fallback_network_type)
### Structure
@ -23,10 +25,16 @@ icon: material/new-box
"udp_fragment": false,
"domain_strategy": "prefer_ipv6",
"network_strategy": "default",
"network_type": [],
"fallback_network_type": [],
"fallback_delay": "300ms"
}
```
!!! note ""
You can ignore the JSON Array [] tag when the content is only one item
### Fields
#### detour
@ -101,30 +109,57 @@ If set, the requested domain name will be resolved to IP before connect.
!!! quote ""
Only supported in graphical clients on Android and iOS with `auto_detect_interface` enabled.
Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled.
Strategy for selecting network interfaces.
Available values:
- `default` (default): Connect to the default interface.
- `fallback`: Try all other interfaces when timeout.
- `hybrid`: Connect to all interfaces concurrently and choose the fastest one.
- `wifi`: Prioritize WIFI, but try all other interfaces when unavailable or timeout.
- `cellular`: Prioritize Cellular, but try all other interfaces when unavailable or timeout.
- `ethernet`: Prioritize Ethernet, but try all other interfaces when unavailable or timeout.
- `wifi_only`: Connect to WIFI only.
- `cellular_only`: Connect to Cellular only.
- `ethernet_only`: Connect to Ethernet only.
- `default` (default): Connect to default network or networks specified in `network_type` sequentially.
- `hybrid`: Connect to all networks or networks specified in `network_type` concurrently.
- `fallback`: Connect to default network or preferred networks specified in `network_type` concurrently, and try fallback networks when unavailable or timeout.
For fallback strategies, when preferred interfaces fails or times out,
it will enter a 15s fast fallback state (upgraded to `hybrid`),
and exit immediately if recovers.
For fallback, when preferred interfaces fails or times out,
it will enter a 15s fast fallback state (Connect to all preferred and fallback networks concurrently),
and exit immediately if preferred networks recover.
Conflicts with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`.
#### network_type
!!! question "Since sing-box 1.11.0"
!!! quote ""
Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled.
Network types to use when using `default` or `hybrid` network strategy or
preferred network types to use when using `fallback` network strategy.
Available values: `wifi`, `cellular`, `ethernet`, `other`.
Device's default network is used by default.
#### fallback_network_type
!!! question "Since sing-box 1.11.0"
!!! quote ""
Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled.
Fallback network types when preferred networks are unavailable or timeout when using `fallback` network strategy.
All other networks expect preferred are used by default.
#### fallback_delay
!!! question "Since sing-box 1.11.0"
!!! quote ""
Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled.
The length of time to wait before spawning a RFC 6555 Fast Fallback connection.
For `domain_strategy`, is the amount of time to wait for connection to succeed before assuming

View file

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

View file

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

View file

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

View file

@ -70,7 +70,7 @@ type DialerOptions struct {
Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"`
Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"`
ProtectPath string `json:"protect_path,omitempty"`
RoutingMark uint32 `json:"routing_mark,omitempty"`
RoutingMark FwMark `json:"routing_mark,omitempty"`
ReuseAddr bool `json:"reuse_addr,omitempty"`
ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"`
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
@ -79,6 +79,8 @@ type DialerOptions struct {
UDPFragmentDefault bool `json:"-"`
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
NetworkStrategy NetworkStrategy `json:"network_strategy,omitempty"`
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
FallbackNetworkType badoption.Listable[InterfaceType] `json:"fallback_network_type,omitempty"`
FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"`
NetworkFallbackDelay badoption.Duration `json:"network_fallback_delay,omitempty"`
IsWireGuardListener bool `json:"-"`

View file

@ -12,8 +12,10 @@ type RouteOptions struct {
AutoDetectInterface bool `json:"auto_detect_interface,omitempty"`
OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"`
DefaultInterface string `json:"default_interface,omitempty"`
DefaultMark uint32 `json:"default_mark,omitempty"`
DefaultMark FwMark `json:"default_mark,omitempty"`
DefaultNetworkStrategy NetworkStrategy `json:"default_network_strategy,omitempty"`
DefaultNetworkType badoption.Listable[InterfaceType] `json:"default_network_type,omitempty"`
DefaultFallbackNetworkType badoption.Listable[InterfaceType] `json:"default_fallback_network_type,omitempty"`
DefaultFallbackDelay badoption.Duration `json:"default_fallback_delay,omitempty"`
}

View file

@ -95,7 +95,7 @@ type RawDefaultRule struct {
User badoption.Listable[string] `json:"user,omitempty"`
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
ClashMode string `json:"clash_mode,omitempty"`
NetworkType badoption.Listable[string] `json:"network_type,omitempty"`
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`

View file

@ -97,7 +97,7 @@ type RawDefaultDNSRule struct {
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
Outbound badoption.Listable[string] `json:"outbound,omitempty"`
ClashMode string `json:"clash_mode,omitempty"`
NetworkType badoption.Listable[string] `json:"network_type,omitempty"`
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`

View file

@ -162,7 +162,7 @@ type DefaultHeadlessRule struct {
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
NetworkType badoption.Listable[string] `json:"network_type,omitempty"`
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`

View file

@ -171,3 +171,27 @@ func (n *NetworkStrategy) UnmarshalJSON(content []byte) error {
*n = NetworkStrategy(strategy)
return nil
}
type InterfaceType C.InterfaceType
func (t InterfaceType) Build() C.InterfaceType {
return C.InterfaceType(t)
}
func (t InterfaceType) MarshalJSON() ([]byte, error) {
return json.Marshal(C.InterfaceType(t).String())
}
func (t *InterfaceType) UnmarshalJSON(content []byte) error {
var value string
err := json.Unmarshal(content, &value)
if err != nil {
return err
}
interfaceType, loaded := C.StringToInterfaceType[value]
if !loaded {
return E.New("unknown interface type: ", value)
}
*t = InterfaceType(interfaceType)
return nil
}

View file

@ -12,7 +12,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
dns "github.com/sagernet/sing-dns"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
@ -37,6 +37,8 @@ type Outbound struct {
domainStrategy dns.DomainStrategy
fallbackDelay time.Duration
networkStrategy C.NetworkStrategy
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration
overrideOption int
overrideDestination M.Socksaddr
@ -55,6 +57,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
domainStrategy: dns.DomainStrategy(options.DomainStrategy),
fallbackDelay: time.Duration(options.FallbackDelay),
networkStrategy: C.NetworkStrategy(options.NetworkStrategy),
networkType: common.Map(options.NetworkType, option.InterfaceType.Build),
fallbackNetworkType: common.Map(options.FallbackNetworkType, option.InterfaceType.Build),
networkFallbackDelay: time.Duration(options.NetworkFallbackDelay),
dialer: outboundDialer,
// loopBack: newLoopBackDetector(router),
@ -171,10 +175,10 @@ func (h *Outbound) DialParallel(ctx context.Context, network string, destination
return nil, E.New("no IPv6 address available for ", destination)
}
}
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, h.networkStrategy, h.fallbackDelay)
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, h.networkStrategy, h.networkType, h.fallbackNetworkType, h.fallbackDelay)
}
func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, fallbackDelay time.Duration) (net.Conn, error) {
func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag()
metadata.Destination = destination
@ -210,10 +214,10 @@ func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, dest
return nil, E.New("no IPv6 address available for ", destination)
}
}
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, networkStrategy, fallbackDelay)
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, networkStrategy, networkType, fallbackNetworkType, fallbackDelay)
}
func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag()
metadata.Destination = destination
@ -232,7 +236,7 @@ func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.
} else {
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
}
conn, newDestination, err := dialer.ListenSerialNetworkPacket(ctx, h.dialer, destination, destinationAddresses, networkStrategy, fallbackDelay)
conn, newDestination, err := dialer.ListenSerialNetworkPacket(ctx, h.dialer, destination, destinationAddresses, networkStrategy, networkType, fallbackNetworkType, fallbackDelay)
if err != nil {
return nil, netip.Addr{}, err
}

View file

@ -59,10 +59,12 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp
interfaceFinder: control.NewDefaultInterfaceFinder(),
autoDetectInterface: routeOptions.AutoDetectInterface,
defaultOptions: adapter.NetworkOptions{
DefaultInterface: routeOptions.DefaultInterface,
DefaultMark: routeOptions.DefaultMark,
DefaultNetworkStrategy: C.NetworkStrategy(routeOptions.DefaultNetworkStrategy),
DefaultFallbackDelay: time.Duration(routeOptions.DefaultFallbackDelay),
BindInterface: routeOptions.DefaultInterface,
RoutingMark: uint32(routeOptions.DefaultMark),
NetworkStrategy: C.NetworkStrategy(routeOptions.DefaultNetworkStrategy),
NetworkType: common.Map(routeOptions.DefaultNetworkType, option.InterfaceType.Build),
FallbackNetworkType: common.Map(routeOptions.DefaultFallbackNetworkType, option.InterfaceType.Build),
FallbackDelay: time.Duration(routeOptions.DefaultFallbackDelay),
},
pauseManager: service.FromContext[pause.Manager](ctx),
platformInterface: service.FromContext[platform.Interface](ctx),
@ -385,7 +387,7 @@ func (r *NetworkManager) notifyInterfaceUpdate(defaultInterface *control.Interfa
networkInterface := common.Find(r.networkInterfaces.Load(), func(it adapter.NetworkInterface) bool {
return it.Interface.Index == defaultInterface.Index
})
if networkInterface.Type == "" {
if networkInterface.Name == "" {
// race
return
}

View file

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

View file

@ -8,6 +8,7 @@ import (
"github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/service"
)
@ -224,7 +225,7 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio
rule.allItems = append(rule.allItems, item)
}
if len(options.NetworkType) > 0 {
item := NewNetworkTypeItem(networkManager, options.NetworkType)
item := NewNetworkTypeItem(networkManager, common.Map(options.NetworkType, option.InterfaceType.Build))
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}

View file

@ -221,7 +221,7 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op
rule.allItems = append(rule.allItems, item)
}
if len(options.NetworkType) > 0 {
item := NewNetworkTypeItem(networkManager, options.NetworkType)
item := NewNetworkTypeItem(networkManager, common.Map(options.NetworkType, option.InterfaceType.Build))
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}

View file

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

View file

@ -4,6 +4,7 @@ import (
"strings"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
F "github.com/sagernet/sing/common/format"
)
@ -12,10 +13,10 @@ var _ RuleItem = (*NetworkTypeItem)(nil)
type NetworkTypeItem struct {
networkManager adapter.NetworkManager
networkType []string
networkType []C.InterfaceType
}
func NewNetworkTypeItem(networkManager adapter.NetworkManager, networkType []string) *NetworkTypeItem {
func NewNetworkTypeItem(networkManager adapter.NetworkManager, networkType []C.InterfaceType) *NetworkTypeItem {
return &NetworkTypeItem{
networkManager: networkManager,
networkType: networkType,