chore: dialer will consider the routing of the local interface when auto-detect-interface in tun is enabled

for #1881 #1819
This commit is contained in:
wwqgtxx 2025-03-10 10:45:07 +08:00
parent 00e6466153
commit 4bd3ae52bd
5 changed files with 65 additions and 31 deletions

View File

@ -88,6 +88,15 @@ func ListenPacket(ctx context.Context, network, address string, rAddrPort netip.
if DefaultSocketHook != nil { // ignore interfaceName, routingMark when DefaultSocketHook not null (in CMFA) if DefaultSocketHook != nil { // ignore interfaceName, routingMark when DefaultSocketHook not null (in CMFA)
socketHookToListenConfig(lc) socketHookToListenConfig(lc)
} else { } else {
if cfg.interfaceName == "" {
if finder := DefaultInterfaceFinder.Load(); finder != nil {
cfg.interfaceName = finder.FindInterfaceName(rAddrPort.Addr())
}
}
if rAddrPort.Addr().Unmap().IsLoopback() {
// avoid "The requested address is not valid in its context."
cfg.interfaceName = ""
}
if cfg.interfaceName != "" { if cfg.interfaceName != "" {
bind := bindIfaceToListenConfig bind := bindIfaceToListenConfig
if cfg.fallbackBind { if cfg.fallbackBind {
@ -153,6 +162,11 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
if DefaultSocketHook != nil { // ignore interfaceName, routingMark and tfo when DefaultSocketHook not null (in CMFA) if DefaultSocketHook != nil { // ignore interfaceName, routingMark and tfo when DefaultSocketHook not null (in CMFA)
socketHookToToDialer(dialer) socketHookToToDialer(dialer)
} else { } else {
if opt.interfaceName == "" {
if finder := DefaultInterfaceFinder.Load(); finder != nil {
opt.interfaceName = finder.FindInterfaceName(destination)
}
}
if opt.interfaceName != "" { if opt.interfaceName != "" {
bind := bindIfaceToDialer bind := bindIfaceToDialer
if opt.fallbackBind { if opt.fallbackBind {
@ -373,12 +387,7 @@ func (d Dialer) DialContext(ctx context.Context, network, address string) (net.C
} }
func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) { func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
opt := d.Opt // make a copy return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, rAddrPort, WithOption(d.Opt))
if rAddrPort.Addr().Unmap().IsLoopback() {
// avoid "The requested address is not valid in its context."
WithInterface("")(&opt)
}
return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, rAddrPort, WithOption(opt))
} }
func NewDialer(options ...Option) Dialer { func NewDialer(options ...Option) Dialer {

View File

@ -3,6 +3,7 @@ package dialer
import ( import (
"context" "context"
"net" "net"
"net/netip"
"github.com/metacubex/mihomo/common/atomic" "github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
@ -12,8 +13,14 @@ var (
DefaultOptions []Option DefaultOptions []Option
DefaultInterface = atomic.NewTypedValue[string]("") DefaultInterface = atomic.NewTypedValue[string]("")
DefaultRoutingMark = atomic.NewInt32(0) DefaultRoutingMark = atomic.NewInt32(0)
DefaultInterfaceFinder = atomic.NewTypedValue[InterfaceFinder](nil)
) )
type InterfaceFinder interface {
FindInterfaceName(destination netip.Addr) string
}
type NetDialer interface { type NetDialer interface {
DialContext(ctx context.Context, network, address string) (net.Conn, error) DialContext(ctx context.Context, network, address string) (net.Conn, error)
} }

View File

@ -512,9 +512,6 @@ func ReCreateTun(tunConf LC.Tun, tunnel C.Tunnel) {
}() }()
if tunConf.Equal(LastTunConf) { if tunConf.Equal(LastTunConf) {
if tunLister != nil {
tunLister.FlushDefaultInterface()
}
return return
} }

View File

@ -52,6 +52,8 @@ type Listener struct {
autoRedirect tun.AutoRedirect autoRedirect tun.AutoRedirect
autoRedirectOutputMark int32 autoRedirectOutputMark int32
cDialerInterfaceFinder dialer.InterfaceFinder
ruleUpdateCallbackCloser io.Closer ruleUpdateCallbackCloser io.Closer
ruleUpdateMutex sync.Mutex ruleUpdateMutex sync.Mutex
routeAddressMap map[string]*netipx.IPSet routeAddressMap map[string]*netipx.IPSet
@ -290,13 +292,25 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
} }
l.defaultInterfaceMonitor = defaultInterfaceMonitor l.defaultInterfaceMonitor = defaultInterfaceMonitor
defaultInterfaceMonitor.RegisterCallback(func(event int) { defaultInterfaceMonitor.RegisterCallback(func(event int) {
l.FlushDefaultInterface() iface.FlushCache()
resolver.ResetConnection() // reset resolver's connection after default interface changed
}) })
err = defaultInterfaceMonitor.Start() err = defaultInterfaceMonitor.Start()
if err != nil { if err != nil {
err = E.Cause(err, "start DefaultInterfaceMonitor") err = E.Cause(err, "start DefaultInterfaceMonitor")
return return
} }
if options.AutoDetectInterface {
l.cDialerInterfaceFinder = &cDialerInterfaceFinder{
tunName: tunName,
defaultInterfaceMonitor: defaultInterfaceMonitor,
}
if !dialer.DefaultInterfaceFinder.CompareAndSwap(nil, l.cDialerInterfaceFinder) {
err = E.New("don't allowed two tun listener using auto-detect-interface")
return
}
}
} }
tunOptions := tun.Options{ tunOptions := tun.Options{
@ -503,27 +517,25 @@ func (l *Listener) updateRule(ruleProvider provider.RuleProvider, exclude bool,
} }
} }
func (l *Listener) FlushDefaultInterface() { type cDialerInterfaceFinder struct {
if l.options.AutoDetectInterface && l.defaultInterfaceMonitor != nil { tunName string
for _, destination := range []netip.Addr{netip.IPv4Unspecified(), netip.IPv6Unspecified(), netip.MustParseAddr("1.1.1.1")} { defaultInterfaceMonitor tun.DefaultInterfaceMonitor
autoDetectInterfaceName := l.defaultInterfaceMonitor.DefaultInterfaceName(destination) }
if autoDetectInterfaceName == l.tunName {
log.Warnln("[TUN] Auto detect interface by %s get same name with tun", destination.String()) func (d *cDialerInterfaceFinder) FindInterfaceName(destination netip.Addr) string {
} else if autoDetectInterfaceName == "" || autoDetectInterfaceName == "<nil>" { for _, dest := range []netip.Addr{destination, netip.IPv4Unspecified(), netip.IPv6Unspecified()} {
log.Warnln("[TUN] Auto detect interface by %s get empty name.", destination.String()) autoDetectInterfaceName := d.defaultInterfaceMonitor.DefaultInterfaceName(dest)
} else { if autoDetectInterfaceName == d.tunName {
if old := dialer.DefaultInterface.Swap(autoDetectInterfaceName); old != autoDetectInterfaceName { log.Warnln("[TUN] Auto detect interface for %s get same name with tun", destination.String())
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, autoDetectInterfaceName) } else if autoDetectInterfaceName == "" || autoDetectInterfaceName == "<nil>" {
iface.FlushCache() log.Warnln("[TUN] Auto detect interface for %s get empty name.", destination.String())
resolver.ResetConnection() // reset resolver's connection after default interface changed } else {
} log.Debugln("[TUN] Auto detect interface for %s --> %s", destination, autoDetectInterfaceName)
return return autoDetectInterfaceName
}
}
if dialer.DefaultInterface.CompareAndSwap("", "<invalid>") {
log.Warnln("[TUN] Auto detect interface failed, set '<invalid>' to DefaultInterface to avoid lookback")
} }
} }
log.Warnln("[TUN] Auto detect interface for %s failed, return '<invalid>' to avoid lookback", destination)
return "<invalid>"
} }
func uidToRange(uidList []uint32) []ranges.Range[uint32] { func uidToRange(uidList []uint32) []ranges.Range[uint32] {
@ -564,6 +576,9 @@ func (l *Listener) Close() error {
if l.autoRedirectOutputMark != 0 { if l.autoRedirectOutputMark != 0 {
dialer.DefaultRoutingMark.CompareAndSwap(l.autoRedirectOutputMark, 0) dialer.DefaultRoutingMark.CompareAndSwap(l.autoRedirectOutputMark, 0)
} }
if l.cDialerInterfaceFinder != nil {
dialer.DefaultInterfaceFinder.CompareAndSwap(l.cDialerInterfaceFinder, nil)
}
return common.Close( return common.Close(
l.ruleUpdateCallbackCloser, l.ruleUpdateCallbackCloser,
l.tunStack, l.tunStack,

View File

@ -404,8 +404,14 @@ func Dial(network, address string) (*TCPConn, error) {
var lTcpAddr *net.TCPAddr var lTcpAddr *net.TCPAddr
var lIpAddr *net.IPAddr var lIpAddr *net.IPAddr
if ifaceName := dialer.DefaultInterface.Load(); len(ifaceName) > 0 { rAddrPort := raddr.AddrPort()
rAddrPort := raddr.AddrPort() ifaceName := dialer.DefaultInterface.Load()
if ifaceName == "" {
if finder := dialer.DefaultInterfaceFinder.Load(); finder != nil {
ifaceName = finder.FindInterfaceName(rAddrPort.Addr())
}
}
if len(ifaceName) > 0 {
addr, err := dialer.LookupLocalAddrFromIfaceName(ifaceName, network, rAddrPort.Addr(), int(rAddrPort.Port())) addr, err := dialer.LookupLocalAddrFromIfaceName(ifaceName, network, rAddrPort.Addr(), int(rAddrPort.Port()))
if err != nil { if err != nil {
return nil, err return nil, err