192.0.2.64 Kept Freezing After Login — the Trail Ended at a Port Collision on 16GB
The trail ended at a port collision on a 16GB Mac mini · 2026-08-02
#macos #ssh #mdnsresponder #wifi-powersave #apple-mlx #thunderbolt #distributed-inference #root-cause
The Symptom
Login, then nothing for a few seconds
Two Mac minis on the LAN, both slated to run Apple MLX distributed inference across a pair of machines. Passwordless SSH to both. And on both, the same complaint: the terminal goes unresponsive right after login. Same symptom, reported for both boxes — which turned out to be the first false lead. They didn’t share a cause.
Host A192.0.2.5Mac mini M4 · 16GB · 3× TB4
Host B192.0.2.64Mac mini M2 · 8GB · 2× TB4
SymptomFrozenevery login, both hosts, a few seconds
The trap: one symptom, described identically for two machines, invites one diagnosis. Treating it as a single bug would have meant fixing whichever cause was found first and declaring victory — while the other host kept freezing for a completely unrelated reason.
Why it mattered beyond SSH comfort
These two minis weren’t just admin boxes — they were about to become the two nodes of an MLX distributed ring for running larger models than either machine could hold alone. A network that stalls for a few seconds on an interactive login is an annoyance. The same network stalling mid-tensor-sync is a broken training run. That’s the thread this investigation eventually pulled.
The Hunt
Two hosts, in parallel, before touching anything
Rather than SSH into one box and start guessing, the first pass ran identical layered diagnostics against both hosts simultaneously — connection timing, PTY-allocated timing, verbose handshake logs, shell rc files, and who/w timing. The point wasn’t speed, it was isolating which layer each host was actually failing at before proposing anything.
192.0.2.5 — connects fine, then a black hole
$ time ssh dk@.5 'who; w'
(no output at all)
real 0m10.005s # killed by my own timeout
Connection was instant. PTY allocation was instant. But who and w — anything that has to resolve a client IP — returned zero output before hitting a 10-second timeout.
192.0.2.64 — the first attempt just doesn’t answer
$ for i in 1 2 3 4 5; do time ssh dk@.64 echo ok; done
attempt 1: Connection timed out real 0m5.024s
attempt 2: ok real 0m0.464s
attempt 3: ok real 0m0.461s
No shell-level hang at all. The very first TCP connection attempt got no response whatsoever; every attempt after it was instant.
Two completely different failure signatures at two completely different layers: .5 hangs after authentication, inside a resolver call. .64 never even completes the TCP handshake on a cold connection. Confirmed early: this needed two separate root causes, not one.
RCA A · DNS Stall
Root Cause A — a reverse DNS lookup with nowhere fast to go
who, w, and sshd’s own login banner all resolve the client’s IP to a hostname. On macOS that resolution runs through mDNSResponder, not a plain /etc/resolv.conf lookup — and .5’s resolver chain had nothing local to answer with.
Where the 8 seconds wentconfirmed
The confirming test: bypass macOS’s resolver entirely
$ dig -x 192.0.2.10 +time=5 +tries=1 # straight to the nameserver
;; status: NXDOMAIN
;; Query time: 15 msec
A direct query got a fast, correct NXDOMAIN in 15 milliseconds. The network path was never the problem — it was specifically macOS’s resolver chain, which tries the (unreachable) mDNS path first and only falls back to the public IPv6 resolvers after the mDNS timeout, and those public resolvers have no idea what 192.0.2.0/24 is either.
Honest caveat: this stall is cache-sensitive. Re-running the same lookup moments later returns instantly because mDNSResponder caches the miss — which is exactly why it reads as “sometimes it freezes” rather than “always.” The fix (route LAN reverse lookups to the gateway, 192.0.2.254, ahead of the public resolvers) was handed off as a networksetup -setdnsservers command rather than applied directly.
Revised, after the deeper pass in Tab 5: this DNS stall on .5 was real and reproducible, but it turns out not to be the dominant explanation. A later round of packet-level and kernel-level testing found the same host also carries Apple’s MAGICWAKE Wi-Fi power-save assertion and produces a near-identical decaying-latency signature on plain ICMP and raw TCP connects that never touch a resolver at all. The mDNS reverse-lookup stall looks like a real, secondary/compounding factor — the resolver genuinely is slow when it has nowhere to answer from — but the common root cause across both hosts turned out to be Wi-Fi client power-save, not DNS. See Tab 5 · Latency Deep-Dive for the evidence.
RCA B · WiFi Wake
Root Cause B — and a fix that only half worked
.64 had no DNS stall at all — every attempt after the first was instant. The problem was earlier: cold connections it just didn’t answer. Along the way, the user mentioned limactl, socket_vmnet, and tailscale had been installed on both machines back in February and “probably” removed — worth checking before chasing anything else.
Still runningsocket_vmnetroot daemon, active on .5
Half-removedtailscale pliston .64, wrong path guessed first
UntouchedNetworkExtensionTailscale.app remnant on .5
All three were cleaned up and both boxes rebooted. Neither the leftover VM networking tools nor the reboot changed the connection-timeout pattern on .64 — a useful negative result that ruled out “stray virtual interface” as the cause.
Catching it live, from the real client
All prior tests had run from the same box the user’s own Ubuntu laptop uses — 192.0.2.10 — but in tight back-to-back loops that kept everything warm. Waiting for genuine idle time and firing one real, timestamped ssh -v reproduced it outright:
07:47:12.271 Connecting to 192.0.2.64 [192.0.2.64] port 22.
07:47:18.272 connect to address 192.0.2.64 port 22: Connection timed out # 6.0s later, hard failure
Immediate retry: fail, then instant, then instant — the exact pattern from the first pass, reproduced live from the real client this time.
Cold attempt vs. warm attempt, same command
ssh command, seconds apart in the same retry loop
The kernel-level evidence: MAGICWAKE
pmset -g log and pmset -g assertions on .64 told a specific story: display sleep fires after 10 idle minutes, and only once an SSH session actually connects does powerd create a NetworkClientActive assertion that holds the interface awake. Before that assertion exists, both Wi-Fi interfaces carry a live kernel assertion:
Kernel Assertions: 0x100=MAGICWAKE
id=538 ... description=en0 owner=en0
id=541 ... description=en1 owner=IOSkywalkNetworkBSDClient # en1 = WiFi, confirmed via networksetup
Wake-on-Wireless standby, needing a multi-second radio wake before the first packet after idle gets answered — matching the “fails once, then works” pattern exactly.
Updated: the root cause is now fully proven, not just plausible. sudo pmset -a displaysleep 0; sudo pmset -a powernap 0 was applied and verified — pmset -g confirmed both settings took. But re-checking pmset -g assertions afterward showed the exact same MAGICWAKE assertions on en0/en1, unchanged, and the freeze persisted. At the time this read as “confirmed root cause, incomplete fix, left open.” A much deeper pass since (Tab 5) settled it: .5 was independently found to already have displaysleep 0, powernap 0, and even SleepDisabled 1 set, and still produced a clean, measured 1.4-second wake stall with the same MAGICWAKE assertion — direct proof that pmset display/system-sleep settings never controlled this behavior at all. It lives entirely in the Wi-Fi driver’s 802.11 client power-save state (MAGICWAKE/Wake-on-Wireless), independent of macOS system power management, and it’s the same mechanism on both hosts. No software fix exists on this macOS build, but a mitigation (a keepalive ping every 30s) is now deployed on both machines. Full evidence in Tab 5 · Latency Deep-Dive.
Latency Deep-Dive
The real, common root cause: Wi-Fi client power-save
Both prior root causes were real, but neither was the whole story. A deeper pass — extended ICMP bursts, DNS-free raw TCP connects, PHY/signal captures, and kernel assertion dumps, run against both hosts and the gateway at the same time — found one mechanism common to both machines: Apple’s MAGICWAKE Wake-on-Wireless / 802.11 client power-save state. This tab is the evidence trail and the mitigation that’s now actually deployed.
Gateway .2541.86msavg · max 16.9ms · 0 outliers >20ms
.644.6msavg · max 193ms · 1 isolated blip
.539.0msavg · max 1377ms · 7-packet decay
150-ping burst, three destinations, same client, same moment
All three targets — the gateway (192.0.2.254), .64, and .5 — sit on the same 5GHz/80MHz AP, channel 60. 150 pings at 0.2s intervals, 0% packet loss on all three. The gateway’s clean trace rules out the AP/channel itself; the shape of the other two traces is the tell.
Same client, same moment, three very different tracesconfirmed
The decay, packet by packet
Seven packets caught behind a waking interface, draining in send order once the radio was up. The step size — roughly 205ms per packet — lands almost exactly on the 200ms send interval used. That’s the signature of a queue draining, not RF interference and not random jitter.
| Seq | RTT | Δ vs prior |
|---|---|---|
| 1 | 1377 ms | — |
| 2 | 1176 ms | −201 ms |
| 3 | 975 ms | −201 ms |
| 4 | 766 ms | −209 ms |
| 5 | 559 ms | −207 ms |
| 6 | 350 ms | −209 ms |
| 7 | 142 ms | −208 ms |
| 8–150 | <20 ms | clean — 143 packets |
DNS-free proof: raw TCP connects by IP, no hostname involved
20 raw /dev/tcp connects straight to port 22 by IP — no resolver call at all — reproduce the exact same signature on .5, which rules out DNS as the mechanism for this specific delay.
$ for i in $(seq 1 20); do time (exec 3<>/dev/tcp/192.0.2.5/22) 2>&1; done
.5 attempt 1: 1.3792s
.5 attempts 2-20: 0.0069s – 0.0116s # all fast, no resolver in the path
$ ...same loop against 192.0.2.64
.64 attempts 1-20: 0.0061s – 0.0141s # uniform, already warm this run
PHY / signal — both clean, rules out RF
| Metric | .5 | .64 |
|---|---|---|
| RSSI | −51 to −52 dBm | −55 to −56 dBm |
| Noise | −94 dBm | −94 dBm |
| SNR | ~43 dB | ~39 dB |
| Tx Rate | 866 Mbps (MCS9) | 650–702 Mbps (MCS7) |
| CCA busy | 27% | 22% |
| Ierrs/Oerrs/Coll (en1) | 0 / 0 / 0 | 0 / 0 / 0 |
Strong signal, same AP/channel on both, zero driver-level errors. CCA 22–27% is moderate contention — microsecond-to-millisecond scale, nowhere near enough to explain a 1.3-second stall.
Kernel proof: identical mechanism on both hosts
$ pmset -g assertions # en1 on each host
.5: Kernel Assertions: 0x104=USB,MAGICWAKE
... description=en1 owner=IOSkywalkNetworkBSDClient
.64: Kernel Assertions: 0x100=MAGICWAKE
... description=en1 owner=IOSkywalkNetworkBSDClient
Both Wi-Fi interfaces carry the same Wake-on-Wireless kernel assertion — not a coincidence, the same mechanism on both machines.
The pmset falsification. .5 was checked for the first time in this pass and turns out to already have displaysleep 0, powernap 0, and even SleepDisabled 1 (system sleep permanently disabled) — the exact settings applied to .64 earlier as an attempted fix (Tab 4). Yet .5 still carries the MAGICWAKE assertion and still produced the measured 1.4s wake stall above. Clean proof that pmset display/system-sleep settings never controlled this behavior at all — it lives in the Wi-Fi driver’s 802.11 client power-save state, entirely independent of macOS system power management.
wdutil info — no fix found, and a useful reframe
Run by the user directly, on both hosts (sudo, so not run by the assistant — strict no-sudo policy). Neither host’s output contains a “Power Save” field at all — this macOS build (26.5.1) doesn’t expose a controllable PSM toggle through this tool. sudo wdutil dump produced no stdout on either host and wasn’t pursued further.
Both hosts report WIFI FAULTS LAST HOUR: None, RECOVERIES: None, LINK TESTS: None — despite .5 having just produced a real, measured 1.3s wake stall inside that same hour. macOS’s own Wi-Fi diagnostics don’t classify this as a fault. It’s standard, expected 802.11 client power-save behavior, working as designed from Apple’s perspective — not a hidden bug with an undiscovered off-switch, just standard behavior two always-on compute nodes don’t want. The old airport CLI that used to allow disabling this was removed by Apple on Apple Silicon; wdutil needs sudo and doesn’t expose the control anyway.
Checked and ruled out: leftover limactl / socket_vmnet / tailscale config
The user’s own hypothesis, investigated directly: could a stray bridge or route left over from the removed VM networking tools be contributing?
- The original
bridge100reject route fromsocket_vmnetis completely gone fromnetstat -rnon both hosts post-cleanup+reboot — one clean default route viaen1remains on each. - No orphaned vmnet/bridge/lima interfaces in
ifconfig -aon either host. NetworkInterfaces.plist(read directly, world-readable) shows only genuine hardware ports — zero orphaned virtual interface entries.net.inet.ip.forwardingis0on both — never left enabled.scutil --proxyshows only default macOS proxy exceptions, nothing injected by lima.
One genuine unresolved leftover: com.apple.vmnet.plist — Apple’s own Vmnet framework state file, not socket_vmnet’s own file, so brew uninstall socket_vmnet never touches it — still exists, root-owned, on both hosts (13KB on .5, 18KB on .64). Couldn’t be read without sudo, so its content and relevance are unverified. There’s no established mechanism connecting VM NAT/DHCP lease bookkeeping to physical Wi-Fi radio power management, so this is reported as an honest gap, not a claimed cause.
Verdict: not supported by available evidence. MAGICWAKE/802.11 client power-save is standard, built-in Wi-Fi driver behavior on every Mac’s en1 — not something an app installs — and almost certainly predates the vmnet/lima/tailscale tools. The more likely (but unprovable — no historical logs reach back to February) explanation for “it used to work fine” is a change in usage pattern: these minis used to be touched more continuously, so the Wi-Fi interface rarely idled long enough to re-arm power-save. Becoming sparsely-touched headless compute nodes made the idle windows long enough for always-present behavior to start being noticed.
The mitigation actually deployed — not a fix, a workaround
A non-sudo, user-level LaunchAgent on both hosts, pinging the gateway every 30 seconds so the interface never idles long enough to re-arm the wake penalty. Confirmed armed via launchctl print showing run interval = 30 seconds.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>Label</key><string>com.dk.wifi-keepalive</string>
<key>ProgramArguments</key>
<array><string>/sbin/ping</string><string>-c</string><string>1</string><string>192.0.2.254</string></array>
<key>StartInterval</key><integer>30</integer>
<key>StandardOutPath</key><string>/dev/null</string>
<key>StandardErrorPath</key><string>/dev/null</string>
</dict></plist>
# deployed on both hosts:
$ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.dk.wifi-keepalive.plist
Two levers not yet pursued: shortening the AP’s DTIM interval would reduce (not eliminate) the wake penalty at the source, if the user ever gets admin access to the router. Wired Ethernet isn’t available for these specific machines right now — though both already have Ethernet as network-service priority #1, so it would take over automatically with zero config if ever connected.
Final verdict. One common root cause across both hosts, proven with packet-level and kernel-level evidence: macOS Wi-Fi client power-save (MAGICWAKE). Not two unrelated bugs. No confirmed software fix exists on this macOS build — the old airport toggle is gone on Apple Silicon and wdutil doesn’t expose one. A keepalive mitigation is deployed and running on both machines today. The DNS stall on .5 (Tab 3) was real but secondary; the leftover-vmnet-config hypothesis was checked and ruled out, bar one unread, unverified plist.
Thunderbolt Detour
A detour: can Thunderbolt do RDMA for MLX?
Mid-investigation, the actual point of these two machines came up: run Apple MLX distributed inference and training across both. Both minis carry Thunderbolt/USB4 ports — worth checking before cabling anything.
| 192.0.2.5 | 192.0.2.64 | |
|---|---|---|
| Model | Mac mini M4 (Mac16,10) | Mac mini M2 (Mac14,3) |
| Cores | 10 (4P + 6E) | 8 (4P + 4E) |
| Unified memory | 16 GB | 8 GB |
| Thunderbolt/USB4 | 3× ports, 40 Gb/s each | 2× ports, 40 Gb/s each |
No, Thunderbolt Bridge is not RDMA. macOS exposes it to the OS as a plain virtual Ethernet adapter — regular TCP/IP sockets ride on top, just at 40 Gb/s instead of shared WiFi bandwidth. There’s no RoCE, no InfiniBand verbs, no kernel-bypass DMA exposed to userspace on macOS. MLX’s ring distributed backend — built specifically for multi-Mac setups — uses exactly this: fast IP over a direct Thunderbolt link, not RDMA semantics.
Where MLX ring traffic runs, before and after
Setup is a static IP on each side, no coordination with any network team needed — the bridge is a private point-to-point link invisible outside these two machines:
# on .5
sudo networksetup -setmanual "Thunderbolt Bridge" 192.0.2.201 255.255.255.0
# on .64
sudo networksetup -setmanual "Thunderbolt Bridge" 192.0.2.202 255.255.255.0
The Real Culprit
The actual MLX run, and the error that changed everything
With the WiFi theory still open, the user ran the real thing: mlx.launch --backend ring across both minis, serving Llama-3.1-8B-Instruct-8bit. It connected, it fetched model metadata from HuggingFace on both nodes — and then broke, mid-session.
[ring] Rank 1 connecting to 0
[ring] Attempt 0 waiting 1000 ms (error: 61)
[ring] Rank 0 accepting # normal startup race, resolved by retry
...
2026-08-02 08:15:19 - HTTP Request: GET huggingface.co/.../revision/main "200 OK" # both nodes, model loaded
...
[ring] Receiving from socket 10 failed with errno 54
[ring] Receiving from socket 10 failed with errno 54
[ring] Too many send/recv errors. Aborting...
[ring] Sending to socket 4 failed with errno 32
[ring] Too many send/recv errors. Aborting...
errno 61Refusedstartup race — harmless, self-resolves
errno 54Resetlive socket killed mid-session — the real fault
errno 32Broken Pipefallout from the peer’s reset, not a separate cause
“It works and then stops and then works” was the description. Then, checking what else was already running on .5 before this new job even started:
$ launchctl list | grep -i mlx
1462 0 com.hetu.vllm-mlx-embedding
1892 -6 com.hetu.vllm-mlx
# both already serving:
vllm-mlx serve embeddinggemma-300m-6bit --host 0.0.0.0 --port 8080
vllm-mlx serve Qwen3.5-4B-MLX-8bit --host 0.0.0.0 --port 8081 --continuous-batching
Port collision. The new mlx_lm.server was launched with --port 8080 — the exact port a pre-existing, launchd-managed vllm-mlx embedding server was already LISTENing on, confirmed live via lsof. Two servers on the same port means the kernel arbitrates which one answers each incoming connection — a direct mechanism for “works, then a request goes to the wrong server, then works again.”
16GB, three models, one port fought over twiceopen before fix
Combined raw model weights — embeddinggemma (≈0.3GB) + Qwen3.5-4B-8bit (≈4GB) + Llama-3.1-8B-8bit (≈8GB) — land around 77% of .5’s 16GB unified memory, before any KV-cache growth or Metal/runtime overhead. On a machine where GPU and system RAM share one pool, that’s enough pressure on its own to stall an active socket long enough to blow past the ring backend’s timeout — independent of, and probably compounding, the still-open WiFi issue.
The Fix
Cleared the port, moved the plists, re-routed the ring
Two forgotten background services, stopped and archived rather than deleted — in case they were serving something else the user cared about.
$ launchctl bootout gui/$(id -u)/com.hetu.vllm-mlx-embedding
$ launchctl bootout gui/$(id -u)/com.hetu.vllm-mlx
$ mv ~/Library/LaunchAgents/com.hetu.vllm-mlx*.plist ~/Documents/
$ launchctl list | grep -i mlx (empty)
$ lsof -iTCP -sTCP:LISTEN | grep -E "8080|8081" (empty)
| Check | Before | After |
|---|---|---|
| launchctl list | 2 hetu.vllm-mlx entries | none |
| Port 8080 | held by embedding server | free |
| Port 8081 | held by Qwen3.5 4B | free |
| Plist location | ~/Library/LaunchAgents (auto-loads) | ~/Documents (archived, inert) |
Recommended next step, not yet applied: point the MLX device.json hostfile at the Thunderbolt Bridge addresses (192.0.2.201 / .2) instead of the WiFi IPs. That takes ring traffic off the still-unresolved flaky radio entirely, regardless of how the WiFi wake-latency bug eventually gets fixed.
Lessons
Lessons & open items
1. One reported symptom can be two unrelated bugs
Running identical diagnostics on both hosts in parallel, before touching either, is what separated a DNS-resolver stall from a WiFi wake-latency bug. Fixing whichever was found first would have left the other host silently still broken.
2. A reverse DNS lookup can freeze a macOS login
who, w, and sshd’s own banner all resolve the client IP. With no LAN-aware DNS server configured, that resolution burns through a 5-second mDNS timeout before falling through to public resolvers that were never going to answer for a private address.
3. Not every root cause gets a clean fix on the first try
displaysleep 0 was correctly applied and verified, and the symptom persisted anyway. Reporting “confirmed root cause, incomplete fix” honestly is more useful than declaring victory on a setting that didn’t actually change the kernel-level MAGICWAKE state.
4. The most convincing lead isn’t always the real one
Hours went into WiFi power management before a single launchctl list | grep -i mlx surfaced two forgotten inference servers sitting on the exact port, and a meaningful share of the RAM, the new job needed. Check what’s already running before debugging what’s new.
5. Don’t fight WiFi power-save for latency-sensitive traffic — route around it
MLX’s ring backend needs a stable, low-latency link for tensor sync. Rather than keep chasing an unresolved kernel power-management bug, moving that specific traffic to a dedicated Thunderbolt Bridge sidesteps the problem entirely — 40 Gb/s, no access point, no radio to sleep.
6. Two different-looking symptoms can still share one root cause
A resolver stall on .5 and a dropped cold connection on .64 read as two unrelated bugs, and Tab 2’s parallel-diagnostics discipline correctly kept them separate at first. But a deeper cross-host pass (Tab 5) found the same MAGICWAKE kernel assertion and the same decaying-latency signature on both machines. Don’t stop at the first plausible per-host explanation when a common-cause explanation fits the evidence better across hosts — run the same deep test against every affected host before closing the investigation.
Current state: mitigated, not fixed. The WiFi wake-latency root cause is now fully proven on both hosts — packet-level and kernel-level evidence, and .5 independently falsifies the earlier pmset-based explanation (Tab 5). No confirmed software toggle exists on this macOS build to disable 802.11 client power-save; the old airport CLI is gone on Apple Silicon and wdutil doesn’t expose one. A non-sudo keepalive LaunchAgent, pinging the gateway every 30 seconds, is deployed and running on both machines as a workaround. Wired Ethernet isn’t an option for these specific machines right now. The MLX path has its own way around it via Thunderbolt Bridge (Tab 6), independent of whether the underlying Wi-Fi issue ever gets a real fix.
Enjoyed this post?
Get the next one in your inbox — only when I ship something worth reading.
Newsletter form not configured.
Or follow on Substack for the newsletter.
Comments via GitHub Discussions
Comments not configured. Set GISCUS env vars to enable.