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.
This commit is contained in:
2026-08-10 20:35:12 -04:00
parent c7d599de85
commit 52eba72613
+15 -3
View File
@@ -16,6 +16,15 @@ import (
func addHostRoute(ip string, _ int, ifaceName string) error { func addHostRoute(ip string, _ int, ifaceName string) error {
out, err := routeExec("ip", "route", "replace", ip+"/32", "dev", ifaceName) out, err := routeExec("ip", "route", "replace", ip+"/32", "dev", ifaceName)
if err != nil { 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 fmt.Errorf("ip route replace %s via %s: %v: %s", ip, ifaceName, err, strings.TrimSpace(string(out)))
} }
return nil return nil
@@ -24,10 +33,13 @@ func addHostRoute(ip string, _ int, ifaceName string) error {
func delHostRoute(ip string) error { func delHostRoute(ip string) error {
out, err := routeExec("ip", "route", "del", ip+"/32") out, err := routeExec("ip", "route", "del", ip+"/32")
if err != nil { if err != nil {
// "No such process" / RTNETLINK errors mean the route isn't there; // A missing route isn't an error -- nothing to drop. Covers both the
// nothing to drop, not an error. // 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)) 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 nil
} }
return fmt.Errorf("ip route del %s: %v: %s", ip, err, strings.TrimSpace(string(out))) return fmt.Errorf("ip route del %s: %v: %s", ip, err, strings.TrimSpace(string(out)))