From 52eba72613d9118c90dda4d446bf59872acc5c7e Mon Sep 17 00:00:00 2001 From: William Mantly Date: Mon, 10 Aug 2026 20:35:12 -0400 Subject: [PATCH] fix(local-route): Unix addHostRoute/delHostRoute didn't tolerate benign errors Caught by running the actual test suite on Linux (go test ./...) after pulling v2.2.0 -- local_route_test.go is a shared cross-platform suite exercising the same "already exists" / "not found" tolerance for both platforms' addHostRoute/delHostRoute, but local_route_windows.go had the tolerance checks and local_route_unix.go didn't: - addHostRoute (unix): had no "already exists" check at all. `ip route replace` is idempotent in real usage so this rarely bites in practice, but the implementation should match its own test coverage rather than relying on that. - delHostRoute (unix): checked "no such process"/"cannot find" but not "route not found", the fixture text the shared test uses (and a real message some `ip route del` failures produce). Both now mirror local_route_windows.go's tolerance logic. go test ./... passes on Linux again. --- local_route_unix.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/local_route_unix.go b/local_route_unix.go index 30c391d..0c8e7e2 100644 --- a/local_route_unix.go +++ b/local_route_unix.go @@ -16,6 +16,15 @@ import ( func addHostRoute(ip string, _ int, ifaceName string) error { out, err := routeExec("ip", "route", "replace", ip+"/32", "dev", ifaceName) if err != nil { + // `ip route replace` is idempotent in real usage -- it re-adds + // rather than erroring when the route already exists -- but tolerate + // an "already exists" error anyway (defensive, and matches + // local_route_windows.go's addHostRoute, which route.exe genuinely + // does return for a duplicate `route add`; the shared test suite + // exercises both platforms' tolerance for the same fixture text). + if strings.Contains(strings.ToLower(string(out)), "already exists") { + return nil + } return fmt.Errorf("ip route replace %s via %s: %v: %s", ip, ifaceName, err, strings.TrimSpace(string(out))) } return nil @@ -24,10 +33,13 @@ func addHostRoute(ip string, _ int, ifaceName string) error { func delHostRoute(ip string) error { out, err := routeExec("ip", "route", "del", ip+"/32") if err != nil { - // "No such process" / RTNETLINK errors mean the route isn't there; - // nothing to drop, not an error. + // A missing route isn't an error -- nothing to drop. Covers both the + // RTNETLINK/iproute2 phrasing ("No such process", "Cannot find + // device") and "route not found", which the shared cross-platform + // test suite (local_route_test.go) also exercises against + // local_route_windows.go's delHostRoute. lower := strings.ToLower(string(out)) - if strings.Contains(lower, "no such process") || strings.Contains(lower, "cannot find") { + if strings.Contains(lower, "no such process") || strings.Contains(lower, "cannot find") || strings.Contains(lower, "route not found") { return nil } return fmt.Errorf("ip route del %s: %v: %s", ip, err, strings.TrimSpace(string(out)))