diff --git a/go.mod b/go.mod index ad3de92a..e629e777 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/oschwald/geoip2-golang v1.7.0 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.1 + github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86 github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 go.etcd.io/bbolt v1.3.6 go.uber.org/atomic v1.9.0 @@ -48,6 +49,7 @@ require ( github.com/oschwald/maxminddb-golang v1.9.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect + github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect golang.org/x/tools v0.1.10 // indirect diff --git a/go.sum b/go.sum index 0585c508..bb38823c 100644 --- a/go.sum +++ b/go.sum @@ -202,6 +202,10 @@ github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 h1:hl6sK6aFgTLISijk6xIz github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86 h1:7SWt9pGCMaw+N1ZhRsaLKaYNviFhxambdoaoYlDqz1w= +github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= +github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 h1:4mkzGhKqt3JO1BWYjtD3iRFyAx4ow67hmSqOcGjuxqQ= github.com/xtls/go v0.0.0-20210920065950-d4af136d3672/go.mod h1:YGGVbz9cOxyKFUmhW7LGaLZaMA0cPlHJinvAmVxEMSU= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -289,6 +293,7 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/listener/tun/ipstack/commons/auto_linux.go b/listener/tun/ipstack/commons/auto_linux.go new file mode 100644 index 00000000..36be5a66 --- /dev/null +++ b/listener/tun/ipstack/commons/auto_linux.go @@ -0,0 +1,90 @@ +package commons + +import ( + "github.com/Dreamacro/clash/component/dialer" + "github.com/Dreamacro/clash/component/iface" + "github.com/Dreamacro/clash/log" + "github.com/vishvananda/netlink" + "go.uber.org/atomic" + "time" +) + +var ( + monitorStarted = atomic.NewBool(false) + monitorStop = make(chan struct{}, 2) +) + +func StartDefaultInterfaceChangeMonitor() { + go func() { + if monitorStarted.Load() { + return + } + monitorStarted.Store(true) + + done := make(chan struct{}) + ch := make(chan netlink.RouteUpdate, 2) + err := netlink.RouteSubscribe(ch, done) + if err != nil { + log.Warnln("[TUN] auto detect interface fail: %s", err) + return + } + log.Debugln("[TUN] start auto detect interface monitor") + defer func() { + close(done) + monitorStarted.Store(false) + log.Debugln("[TUN] stop auto detect interface monitor") + }() + + select { + case <-monitorStop: + default: + } + + for { + select { + case <-monitorStop: + return + case <-ch: + } + + interfaceName, err := GetAutoDetectInterface() + if err != nil { + t := time.NewTicker(2 * time.Second) + for { + select { + case ch <- <-ch: + break + case <-t.C: + interfaceName, err = GetAutoDetectInterface() + if err != nil { + continue + } + } + break + } + t.Stop() + } + + if err != nil { + log.Debugln("[TUN] detect interface: %s", err) + continue + } + + old := dialer.DefaultInterface.Load() + if interfaceName == old { + continue + } + + dialer.DefaultInterface.Store(interfaceName) + iface.FlushCache() + + log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName) + } + }() +} + +func StopDefaultInterfaceChangeMonitor() { + if monitorStarted.Load() { + monitorStop <- struct{}{} + } +} diff --git a/listener/tun/ipstack/commons/auto_others.go b/listener/tun/ipstack/commons/auto_others.go new file mode 100644 index 00000000..23772c5e --- /dev/null +++ b/listener/tun/ipstack/commons/auto_others.go @@ -0,0 +1,67 @@ +//go:build !linux + +package commons + +import ( + "github.com/Dreamacro/clash/component/dialer" + "github.com/Dreamacro/clash/component/iface" + "github.com/Dreamacro/clash/log" + "go.uber.org/atomic" + "time" +) + +var ( + monitorDuration = 3 * time.Second + monitorStarted = atomic.NewBool(false) + monitorStop = make(chan struct{}, 2) +) + +func StartDefaultInterfaceChangeMonitor() { + go func() { + if monitorStarted.Load() { + return + } + monitorStarted.Store(true) + t := time.NewTicker(monitorDuration) + log.Debugln("[TUN] start auto detect interface monitor") + defer func() { + monitorStarted.Store(false) + t.Stop() + log.Debugln("[TUN] stop auto detect interface monitor") + }() + + select { + case <-monitorStop: + default: + } + + for { + select { + case <-t.C: + interfaceName, err := GetAutoDetectInterface() + if err != nil { + log.Warnln("[TUN] default interface monitor err: %v", err) + continue + } + + old := dialer.DefaultInterface.Load() + if interfaceName == old { + continue + } + + dialer.DefaultInterface.Store(interfaceName) + iface.FlushCache() + + log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName) + case <-monitorStop: + break + } + } + }() +} + +func StopDefaultInterfaceChangeMonitor() { + if monitorStarted.Load() { + monitorStop <- struct{}{} + } +} diff --git a/listener/tun/ipstack/commons/router.go b/listener/tun/ipstack/commons/router.go index e4f66755..57c99502 100644 --- a/listener/tun/ipstack/commons/router.go +++ b/listener/tun/ipstack/commons/router.go @@ -2,21 +2,13 @@ package commons import ( "fmt" - "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/iface" "github.com/Dreamacro/clash/log" "net" - "sync" "time" ) 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"} - - monitorDuration = 3 * time.Second - monitorStarted = false - monitorStop = make(chan struct{}, 2) - monitorMux sync.Mutex ) func ipv4MaskString(bits int) string { @@ -28,59 +20,6 @@ func ipv4MaskString(bits int) string { return fmt.Sprintf("%d.%d.%d.%d", m[0], m[1], m[2], m[3]) } -func StartDefaultInterfaceChangeMonitor() { - go func() { - monitorMux.Lock() - if monitorStarted { - monitorMux.Unlock() - return - } - monitorStarted = true - monitorMux.Unlock() - - select { - case <-monitorStop: - default: - } - - t := time.NewTicker(monitorDuration) - defer t.Stop() - - for { - select { - case <-t.C: - interfaceName, err := GetAutoDetectInterface() - if err != nil { - log.Warnln("[TUN] default interface monitor err: %v", err) - continue - } - - old := dialer.DefaultInterface.Load() - if interfaceName == old { - continue - } - - dialer.DefaultInterface.Store(interfaceName) - iface.FlushCache() - - log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName) - case <-monitorStop: - break - } - } - }() -} - -func StopDefaultInterfaceChangeMonitor() { - monitorMux.Lock() - defer monitorMux.Unlock() - - if monitorStarted { - monitorStop <- struct{}{} - monitorStarted = false - } -} - func WaitForTunClose(deviceName string) { t := time.NewTicker(600 * time.Millisecond) defer t.Stop() diff --git a/listener/tun/ipstack/commons/router_linux.go b/listener/tun/ipstack/commons/router_linux.go index 8c5b7165..b34dd5a4 100644 --- a/listener/tun/ipstack/commons/router_linux.go +++ b/listener/tun/ipstack/commons/router_linux.go @@ -10,7 +10,11 @@ import ( ) func GetAutoDetectInterface() (string, error) { - return cmd.ExecCmd("bash -c ip route show | grep 'default via' | awk -F ' ' 'NR==1{print $5}' | xargs echo -n") + execCmd, err := cmd.ExecCmd("bash -c ip route show | grep 'default via' | awk -F ' ' 'NR==1{print $5}' | xargs echo -n") + if execCmd == "" { + return "", fmt.Errorf("interface not found") + } + return execCmd, err } func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {