Fix dialing IPv6 link-local addresses without zone

This commit is contained in:
世界 committed 2026-11-09 14:37:27 +00:00
1 parent 586b48065c
commit 758708747a
2 files changed
+51 -28

No files matched your search

+25 -2
View File
@@ -6,6 +6,7 @@ import (
"net"
"net/netip"
"slices"
"strconv"
"syscall"
"time"
@@ -39,6 +40,7 @@ type DefaultDialer struct {
udpAddr4 string
udpAddr6 string
netns string
bindInterface string
autoDetectBindFunc control.Func
connectionManager adapter.ConnectionManager
networkManager adapter.NetworkManager
@@ -62,6 +64,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
dialer net.Dialer
listener net.ListenConfig
interfaceFinder control.InterfaceFinder
bindInterface string
networkStrategy *C.NetworkStrategy
defaultNetworkStrategy bool
networkType []C.InterfaceType
@@ -86,6 +89,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
}
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
bindInterface = options.BindInterface
}
if options.RoutingMark > 0 {
if !C.IsLinux {
@@ -107,6 +111,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
bindInterface = defaultOptions.BindInterface
} else if networkManager.AutoDetectInterface() && !disableDefaultBind && options.NetNs == "" {
if platformInterface != nil && platformInterface.UsePlatformNetworkInterfaces() {
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
@@ -242,6 +247,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
udpAddr4: udpAddr4,
udpAddr6: udpAddr6,
netns: options.NetNs,
bindInterface: bindInterface,
autoDetectBindFunc: autoDetectBindFunc,
connectionManager: connectionManager,
networkManager: networkManager,
@@ -279,6 +285,19 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
return nil, E.New("domain not resolved")
}
if d.networkStrategy == nil {
if d.netns == "" && address.Addr.Zone() == "" && isLinkScoped(address.Addr) {
if d.bindInterface != "" {
address.Addr = address.Addr.WithZone(d.bindInterface)
} else if d.autoDetectBindFunc != nil || d.defaultNetworkStrategy {
iif, err := d.networkManager.InterfaceFinder().ByAddr(address.Addr)
if err != nil {
iif = d.networkManager.InterfaceMonitor().DefaultInterface()
}
if iif != nil {
address.Addr = address.Addr.WithZone(strconv.Itoa(iif.Index))
}
}
}
conn, err := listener.ListenNetworkNamespace[net.Conn](ctx, d.netns, func() (net.Conn, error) {
switch N.NetworkName(network) {
case N.NetworkUDP:
@@ -300,6 +319,10 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
}
}
func isLinkScoped(addr netip.Addr) bool {
return addr.Is6() && (addr.IsLinkLocalUnicast() || addr.IsLinkLocalMulticast() || addr.IsInterfaceLocalMulticast())
}
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 == nil {
strategy = d.networkStrategy
@@ -329,9 +352,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, interfaceType, fallbackInterfaceType, fallbackDelay)
conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address, *strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
} else {
conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store)
conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address, *strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store)
}
if err != nil {
// bind interface failed on legacy xiaomi systems
+26 -26
View File
@@ -3,6 +3,7 @@ package dialer
import (
"context"
"net"
"strconv"
"syscall"
"time"
@@ -11,10 +12,11 @@ import (
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
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) {
func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Dialer, network string, destination M.Socksaddr, 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")
@@ -31,13 +33,9 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di
} else {
iif = fallbackInterfaces[0]
}
perNetDialer := dialer
if defaultInterface == nil || iif.Index != defaultInterface.Index {
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
}
conn, err := perNetDialer.DialContext(ctx, network, addr)
conn, err := dialInterface(ctx, dialer, network, destination, iif, defaultInterface)
if err != nil {
return nil, false, E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")")
return nil, false, err
}
return conn, primary, nil
}
@@ -53,14 +51,10 @@ func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Di
}
results := make(chan dialResult) // unbuffered
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
perNetDialer := dialer
if defaultInterface == nil || iif.Index != defaultInterface.Index {
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
}
conn, err := perNetDialer.DialContext(ctx, network, addr)
conn, err := dialInterface(ctx, dialer, network, destination, iif, defaultInterface)
if err != nil {
select {
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")"), primary: primary}:
case results <- dialResult{error: err, primary: primary}:
case <-returned:
}
} else {
@@ -109,7 +103,7 @@ 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, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration, resetFastFallback func(time.Time)) (net.Conn, bool, error) {
func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, dialer net.Dialer, network string, destination M.Socksaddr, 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")
@@ -126,13 +120,9 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
} else {
iif = fallbackInterfaces[0]
}
perNetDialer := dialer
if defaultInterface == nil || iif.Index != defaultInterface.Index {
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
}
conn, err := perNetDialer.DialContext(ctx, network, addr)
conn, err := dialInterface(ctx, dialer, network, destination, iif, defaultInterface)
if err != nil {
return nil, false, E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")")
return nil, false, err
}
return conn, primary, nil
}
@@ -149,14 +139,10 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
startAt := time.Now()
results := make(chan dialResult) // unbuffered
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
perNetDialer := dialer
if defaultInterface == nil || iif.Index != defaultInterface.Index {
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
}
conn, err := perNetDialer.DialContext(ctx, network, addr)
conn, err := dialInterface(ctx, dialer, network, destination, iif, defaultInterface)
if err != nil {
select {
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")"), primary: primary}:
case results <- dialResult{error: err, primary: primary}:
case <-returned:
}
} else {
@@ -191,6 +177,20 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
return nil, false, E.Errors(errors...)
}
func dialInterface(ctx context.Context, dialer net.Dialer, network string, destination M.Socksaddr, iif adapter.NetworkInterface, defaultInterface *control.Interface) (net.Conn, error) {
if defaultInterface == nil || iif.Index != defaultInterface.Index {
dialer.Control = control.Append(dialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
}
if destination.Addr.Zone() == "" && isLinkScoped(destination.Addr) {
destination.Addr = destination.Addr.WithZone(strconv.Itoa(iif.Index))
}
conn, err := dialer.DialContext(ctx, network, destination.String())
if err != nil {
return nil, E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")")
}
return conn, nil
}
func (d *DefaultDialer) serialInterfaceControl(strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) control.Func {
return func(network, address string, conn syscall.RawConn) error {
primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)