b2ad8f4844
Completes the mDNS local-discovery feature on Windows (was Linux-only since v2.1.2). Three parts: 1. Windows hosts override (hosts_override_windows.go): %SystemRoot%...\\hosts, CRLF-aware read/write, ipconfig /flushdns after each change. The agent runs as a SYSTEM service so elevation is a non-issue. hosts_override.go split into shared rewrite logic + platform files; the hosts tests now run the real Windows write path on CI instead of skipping. 2. Local route pinning (local_route*.go): the hosts override only fixes name resolution -- the packet path is the routing table's job. If the WG mesh tunnel is up with AllowedIPs covering the LAN (or full-tunnel 0.0.0.0/0) it swallows the direct connection. Discovery now pins a /32 host route via the owning local interface (route.exe metric 1 on Windows, ip route replace on Linux) and drops it on revert. Closes a gap in the shipped Linux path too. 3. Prompt reconnect: apply/revert signals the WS loop so it reconnects immediately instead of waiting out the 5s backoff. Route/hosts code is injectable + unit tested; go test passes natively on Windows (this machine), and linux/amd64 + windows/arm64 cross-builds are clean.
37 lines
1.2 KiB
Go
37 lines
1.2 KiB
Go
//go:build !windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// Unix host-route pinning via `ip route`. The discovered IP is on-link, so a
|
|
// /32 route straight out the owning interface is enough; `ip route replace`
|
|
// is idempotent (re-adds instead of failing when the route is already there).
|
|
// The /32 wins by longest-prefix-match over any broader tunnel route, even a
|
|
// full-tunnel 0.0.0.0/0 -- no metric games needed on Linux.
|
|
|
|
func addHostRoute(ip string, _ int, ifaceName string) error {
|
|
out, err := routeExec("ip", "route", "replace", ip+"/32", "dev", ifaceName)
|
|
if err != nil {
|
|
return fmt.Errorf("ip route replace %s via %s: %v: %s", ip, ifaceName, err, strings.TrimSpace(string(out)))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
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.
|
|
lower := strings.ToLower(string(out))
|
|
if strings.Contains(lower, "no such process") || strings.Contains(lower, "cannot find") {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("ip route del %s: %v: %s", ip, err, strings.TrimSpace(string(out)))
|
|
}
|
|
return nil
|
|
}
|