From 7b7abf69738d70a82fae2e5977c78924e4249646 Mon Sep 17 00:00:00 2001 From: yaling888 <73897884+yaling888@users.noreply.github.com> Date: Fri, 18 Mar 2022 17:03:50 +0800 Subject: [PATCH] Feature: auto detect interface if switch network --- listener/tun/ipstack/commons/router.go | 36 +++++++++++++++++-- listener/tun/ipstack/commons/router_darwin.go | 8 +++-- listener/tun/ipstack/commons/router_linux.go | 5 ++- .../tun/ipstack/commons/router_windows.go | 14 ++++++-- 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/listener/tun/ipstack/commons/router.go b/listener/tun/ipstack/commons/router.go index efcd540c..1cebfa98 100644 --- a/listener/tun/ipstack/commons/router.go +++ b/listener/tun/ipstack/commons/router.go @@ -3,11 +3,19 @@ package commons import ( "fmt" "net" + "time" + + "github.com/Dreamacro/clash/component/dialer" + "github.com/Dreamacro/clash/log" ) -var Routes = []string{"1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1"} +var ( + defaultRoutes = []string{"1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1"} -func IPv4MaskString(bits int) string { + defaultInterfaceMonitorDuration = 20 * time.Second +) + +func ipv4MaskString(bits int) string { m := net.CIDRMask(bits, 32) if len(m) != 4 { panic("ipv4Mask: len must be 4 bytes") @@ -15,3 +23,27 @@ func IPv4MaskString(bits int) string { return fmt.Sprintf("%d.%d.%d.%d", m[0], m[1], m[2], m[3]) } + +func defaultInterfaceChangeMonitor() { + t := time.NewTicker(defaultInterfaceMonitorDuration) + defer t.Stop() + + for { + <-t.C + + interfaceName, err := GetAutoDetectInterface() + if err != nil { + log.Warnln("[TUN] default interface monitor exited, cause: %v", err) + break + } + + old := dialer.DefaultInterface.Load() + if interfaceName == old { + continue + } + + dialer.DefaultInterface.Store(interfaceName) + + log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName) + } +} diff --git a/listener/tun/ipstack/commons/router_darwin.go b/listener/tun/ipstack/commons/router_darwin.go index 3a61ac9a..4185a48a 100644 --- a/listener/tun/ipstack/commons/router_darwin.go +++ b/listener/tun/ipstack/commons/router_darwin.go @@ -9,7 +9,7 @@ import ( ) func GetAutoDetectInterface() (string, error) { - return cmd.ExecCmd("bash -c netstat -rnf inet | grep 'default' | awk -F ' ' 'NR==1{print $6}' | xargs echo -n") + return cmd.ExecCmd("bash -c route -n get default | grep 'interface:' | awk -F ' ' 'NR==1{print $2}' | xargs echo -n") } func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error { @@ -21,7 +21,7 @@ func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, interfaceName = dev.Name() ip = addr.Masked().Addr().Next() gw = ip.Next() - netmask = IPv4MaskString(addr.Bits()) + netmask = ipv4MaskString(addr.Bits()) ) cmdStr := fmt.Sprintf("ifconfig %s inet %s netmask %s %s", interfaceName, ip, netmask, gw) @@ -44,7 +44,7 @@ func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, func configInterfaceRouting(interfaceName string, addr netip.Prefix) error { var ( - routes = append(Routes, addr.String()) + routes = append(defaultRoutes, addr.String()) gateway = addr.Masked().Addr().Next() ) @@ -54,6 +54,8 @@ func configInterfaceRouting(interfaceName string, addr netip.Prefix) error { } } + go defaultInterfaceChangeMonitor() + return execRouterCmd("add", "-inet6", "2000::/3", interfaceName) } diff --git a/listener/tun/ipstack/commons/router_linux.go b/listener/tun/ipstack/commons/router_linux.go index e27cdcf6..63479bae 100644 --- a/listener/tun/ipstack/commons/router_linux.go +++ b/listener/tun/ipstack/commons/router_linux.go @@ -36,11 +36,14 @@ func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, func configInterfaceRouting(interfaceName string, addr netip.Prefix) error { linkIP := addr.Masked().Addr().Next() - for _, route := range Routes { + for _, route := range defaultRoutes { if err := execRouterCmd("add", route, interfaceName, linkIP.String()); err != nil { return err } } + + go defaultInterfaceChangeMonitor() + return nil } diff --git a/listener/tun/ipstack/commons/router_windows.go b/listener/tun/ipstack/commons/router_windows.go index 05ac7fb1..5ddcf709 100644 --- a/listener/tun/ipstack/commons/router_windows.go +++ b/listener/tun/ipstack/commons/router_windows.go @@ -15,6 +15,8 @@ import ( "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" ) +var wintunInterfaceName string + func GetAutoDetectInterface() (string, error) { ifname, err := getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET)) if err == nil { @@ -30,7 +32,7 @@ func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, var err error startOver: if tryTimes > 0 { - log.Infoln("Retrying interface configuration after failure because system just booted (T+%v): %v", windows.DurationSinceBoot(), err) + log.Infoln("[TUN] retrying interface configuration after failure because system just booted (T+%v): %v", windows.DurationSinceBoot(), err) time.Sleep(time.Second) retryOnFailure = retryOnFailure && tryTimes < 15 } @@ -199,6 +201,10 @@ startOver: return fmt.Errorf("unable to set DNS %s %s: %w", "198.18.0.2", "nil", err) } + wintunInterfaceName = dev.Name() + + go defaultInterfaceChangeMonitor() + return nil } @@ -221,7 +227,7 @@ func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, add for address := iface.FirstUnicastAddress; address != nil; address = address.Next { if ip, _ := netip.AddrFromSlice(address.Address.IP()); addrHash[ip] { prefix := netip.PrefixFrom(ip, int(address.OnLinkPrefixLength)) - log.Infoln("Cleaning up stale address %s from interface ā€˜%sā€™", prefix.String(), iface.FriendlyName()) + log.Infoln("[TUN] cleaning up stale address %s from interface ā€˜%sā€™", prefix.String(), iface.FriendlyName()) _ = iface.LUID.DeleteIPAddress(prefix) } } @@ -248,6 +254,10 @@ func getAutoDetectInterfaceByFamily(family winipcfg.AddressFamily) (string, erro ifname := iface.FriendlyName() + if wintunInterfaceName == ifname { + continue + } + for gatewayAddress := iface.FirstGatewayAddress; gatewayAddress != nil; gatewayAddress = gatewayAddress.Next { nextHop, _ := netip.AddrFromSlice(gatewayAddress.Address.IP())