tun_watch: remove bypass routes on Stop(), catch SIGHUP

SocketWatcher.addRoute() installs a /32 bypass route through the
physical gateway for every remote IP the process talks to directly,
so the transport's own sockets stay off the tunnel. Stop() never
removed them, so after the client exited the routes stayed in the
table pinned to whatever gateway was current at that moment. Once the
network changed (different Wi-Fi, another VPN, a hotspot), traffic to
that specific IP kept following the stale route instead of the new
default route - breaking reachability to that one host while
everything else worked fine.

Stop() now removes every route it added. Also notify on SIGHUP, since
that's what a closed terminal window sends the foreground process
(not SIGINT/SIGTERM) - previously that path skipped cleanup entirely.
This commit is contained in:
damnurmum
2026-09-17 22:00:41 +03:00
parent 24dc2f509e
commit 032d6af0a1
2 changed files with 30 additions and 1 deletions
+4 -1
View File
@@ -9,5 +9,8 @@ import (
)
func notifySignals(ch chan os.Signal) {
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
// SIGHUP is what a closed terminal window sends the foreground process
// (not SIGINT/SIGTERM) - without catching it, closing the window instead
// of Ctrl+C skips the bypass-route cleanup in SocketWatcher.Stop() entirely.
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
}
+26
View File
@@ -69,6 +69,21 @@ func (w *SocketWatcher) Stop() {
}
close(w.stop)
w.stopped.Wait()
// The bypass routes added by addRoute() are only meaningful while this
// process's tunnel is up; leaving them in place after we stop watching
// silently strands a host route through whatever gateway happened to be
// current at the time, which breaks reachability to that IP once the
// network changes (Wi-Fi <-> hotspot <-> another VPN, etc).
w.mu.Lock()
known := w.known
w.known = make(map[string]bool)
w.mu.Unlock()
for ip := range known {
if err := w.removeRoute(ip); err != nil {
utils.Debugf("[WATCH] remove bypass route %s failed: %v", ip, err)
}
}
}
func (w *SocketWatcher) snapshot() {
@@ -151,3 +166,14 @@ func (w *SocketWatcher) addRoute(ip string) error {
}
return nil
}
func (w *SocketWatcher) removeRoute(ip string) error {
out, err := exec.Command("sudo", "route", "delete", "-host", ip).CombinedOutput()
if err != nil {
if strings.Contains(string(out), "not in table") {
return nil
}
return fmt.Errorf("%v: %s", err, strings.TrimSpace(string(out)))
}
return nil
}