feat(discovery): Windows local-discovery + local route pinning + prompt reconnect
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.
This commit is contained in:
@@ -42,6 +42,7 @@ func StartLocalDiscovery(cm *ConfigManager) {
|
||||
|
||||
log.Printf("[local-discovery] enabled, watching for a local announcement fronting %s", targetHost)
|
||||
currentlyOverridden := false
|
||||
lastIP := ""
|
||||
|
||||
for {
|
||||
ip := findLocalAnnouncement(targetHost)
|
||||
@@ -50,21 +51,48 @@ func StartLocalDiscovery(cm *ConfigManager) {
|
||||
if err := applyHostsOverride(map[string]string{targetHost: ip}); err != nil {
|
||||
log.Printf("[local-discovery] found %s locally at %s but failed to apply hosts override: %v", targetHost, ip, err)
|
||||
} else {
|
||||
// Pin the packet path too: the hosts override only fixes name
|
||||
// resolution, the route table decides where the packets go.
|
||||
// If the WireGuard mesh tunnel is up with AllowedIPs covering
|
||||
// this LAN subnet, it would swallow the direct connection.
|
||||
if err := applyLocalRoute(ip); err != nil {
|
||||
log.Printf("[local-discovery] found %s locally at %s but failed to pin a direct host route (a WireGuard tunnel may override it): %v", targetHost, ip, err)
|
||||
}
|
||||
log.Printf("[local-discovery] %s announced locally at %s -- routing directly, skipping the relay/WAN path", targetHost, ip)
|
||||
lastIP = ip
|
||||
currentlyOverridden = true
|
||||
notifyDiscoveryChange()
|
||||
}
|
||||
case ip == "" && currentlyOverridden:
|
||||
if err := applyHostsOverride(map[string]string{}); err != nil {
|
||||
log.Printf("[local-discovery] lost local announcement for %s but failed to clear hosts override: %v", targetHost, err)
|
||||
} else {
|
||||
if lastIP != "" {
|
||||
removeLocalRoute(lastIP)
|
||||
}
|
||||
log.Printf("[local-discovery] %s no longer announced locally -- reverting to normal resolution", targetHost)
|
||||
currentlyOverridden = false
|
||||
lastIP = ""
|
||||
notifyDiscoveryChange()
|
||||
}
|
||||
}
|
||||
time.Sleep(mdnsPollInterval)
|
||||
}
|
||||
}
|
||||
|
||||
// discoveryChangedCh is signaled (non-blocking) whenever a local-discovery
|
||||
// apply/revert changes name resolution or routing, so the WebSocket loop can
|
||||
// reconnect promptly and pick up the new path instead of waiting out its
|
||||
// reconnect backoff.
|
||||
var discoveryChangedCh = make(chan struct{}, 1)
|
||||
|
||||
func notifyDiscoveryChange() {
|
||||
select {
|
||||
case discoveryChangedCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func hostFromURL(raw string) string {
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil || u.Hostname() == "" {
|
||||
|
||||
Reference in New Issue
Block a user