Context
PR #156 added a post-launch kill -0 $PROXY_PID check to enable_dns_mode's privileged shell script to detect early proxy death (the silent-failure path from #155). After 1 second, if kill -0 succeeds, the script proceeds to networksetup -setdnsservers <iface> 127.0.0.1.
Problem: kill -0 proves the process exists, not that it's listening on UDP 53. The proxy could be:
- Alive but in pre-
UdpSocket::bind initialization code
- Hung at bind (port already in use; bind blocks on certain error paths in
tokio::net::UdpSocket::bind)
- Mid-panic during startup before taking the bind
- Delayed by a slow filesystem / extension-loading step
In any of these, kill -0 returns 0, the script clears the readiness check, and networksetup flips system DNS to 127.0.0.1. The DNS client queries 127.0.0.1:53, gets no response, and we're back to the same black-hole port the PR claims to eliminate — just with a smaller window than before.
The exact "binary missing" path is already caught by the [ ! -x ] early check, so kill -0 only adds value if we also verify bind completion.
Residual risk today
On normal macOS systems the window is sub-second and worst-case is one query timeout. On systems where bind is slow (full disk, encrypted FS, process spawn contention), the window grows. There's no upper bound guarantee from kill -0 alone.
Fix
Add a proxy-emitted readiness signal:
-
Proxy side (crates/mhost-dns/src/bin/dns_proxy.rs or proxy.rs::run): after the successful UdpSocket::bind(listen_addr) and before entering the main recv loop, write a small marker file (e.g. runtime_dir()/mhost-dns-proxy-ready.signal) atomically with content "ready\n" and sync_all. Sync matters here: the script's cat / [ -f ] will hit the inode but readers should see the data.
-
Script side: poll both kill -0 and [ -f "$ready_marker" ] with a bounded timeout (e.g. 5s with 100ms intervals). Either failure aborts the script via the existing error path.
-
Cleanup side: disable_dns_mode already cleans runtime_dir() on success; ensure the ready marker is removed alongside. If the proxy dies abnormally, the marker becomes stale — cleanup_stale_proxy should remove it during the next start-up scan.
UDP probe as alternative: connect a UDP socket to 127.0.0.1:53 + send a no-op. Not reliable for unconnected UDP (kernel queues the datagram without confirming a listener). Marker is preferred.
Linked
Context
PR #156 added a post-launch
kill -0 $PROXY_PIDcheck toenable_dns_mode's privileged shell script to detect early proxy death (the silent-failure path from #155). After 1 second, ifkill -0succeeds, the script proceeds tonetworksetup -setdnsservers <iface> 127.0.0.1.Problem:
kill -0proves the process exists, not that it's listening on UDP 53. The proxy could be:UdpSocket::bindinitialization codetokio::net::UdpSocket::bind)In any of these,
kill -0returns 0, the script clears the readiness check, andnetworksetupflips system DNS to127.0.0.1. The DNS client queries127.0.0.1:53, gets no response, and we're back to the same black-hole port the PR claims to eliminate — just with a smaller window than before.The exact "binary missing" path is already caught by the
[ ! -x ]early check, sokill -0only adds value if we also verify bind completion.Residual risk today
On normal macOS systems the window is sub-second and worst-case is one query timeout. On systems where bind is slow (full disk, encrypted FS, process spawn contention), the window grows. There's no upper bound guarantee from
kill -0alone.Fix
Add a proxy-emitted readiness signal:
Proxy side (
crates/mhost-dns/src/bin/dns_proxy.rsorproxy.rs::run): after the successfulUdpSocket::bind(listen_addr)and before entering the main recv loop, write a small marker file (e.g.runtime_dir()/mhost-dns-proxy-ready.signal) atomically with content"ready\n"andsync_all. Sync matters here: the script'scat/[ -f ]will hit the inode but readers should see the data.Script side: poll both
kill -0and[ -f "$ready_marker" ]with a bounded timeout (e.g. 5s with 100ms intervals). Either failure aborts the script via the existing error path.Cleanup side:
disable_dns_modealready cleansruntime_dir()on success; ensure the ready marker is removed alongside. If the proxy dies abnormally, the marker becomes stale —cleanup_stale_proxyshould remove it during the next start-up scan.UDP probe as alternative: connect a UDP socket to 127.0.0.1:53 + send a no-op. Not reliable for unconnected UDP (kernel queues the datagram without confirming a listener). Marker is preferred.
Linked