macosmlxdebuggingnetworking

192.0.2.64 Kept Freezing After Login — the Trail Ended at a Port Collision on 16GB

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

SSH Login who / w / banner mDNSResponder local resolver reach: Not Reachable tried anyway, 5s timeout Public Resolvers Cloudflare + ISP, IPv6-only no 192.0.2.0/24 PTR zone dscacheutil 8.006s, no answer Terminal reads as frozen reverse lookup of the client IP, in flight same dead end, every fresh lookup

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

Attempt 1 · cold 6.009s → hard timeout zero response, no SYN-ACK wait time Attempt 2 · warm 0.46s → instant interface already awake wait time same 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

Gateway · .254 shared channel 60 AP 150/150 received, 0% loss avg 1.86ms · max 16.9ms 0 outliers >20ms — AP is healthy .64 already warm going in 193ms 150/150 received, 0% loss avg 4.6ms · max 193ms 1 mid-stream blip · seq 116 .5 cold at test start 1377ms 150/150 received, 0% loss avg 39.0ms · max 1377ms 7-pkt decay, then clean

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.

SeqRTTΔ vs prior
11377 ms
21176 ms−201 ms
3975 ms−201 ms
4766 ms−209 ms
5559 ms−207 ms
6350 ms−209 ms
7142 ms−208 ms
8–150<20 msclean — 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 Rate866 Mbps (MCS9)650–702 Mbps (MCS7)
CCA busy27%22%
Ierrs/Oerrs/Coll (en1)0 / 0 / 00 / 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 bridge100 reject route from socket_vmnet is completely gone from netstat -rn on both hosts post-cleanup+reboot — one clean default route via en1 remains on each.
  • No orphaned vmnet/bridge/lima interfaces in ifconfig -a on either host.
  • NetworkInterfaces.plist (read directly, world-readable) shows only genuine hardware ports — zero orphaned virtual interface entries.
  • net.inet.ip.forwarding is 0 on both — never left enabled.
  • scutil --proxy shows 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.5192.0.2.64
ModelMac mini M4 (Mac16,10)Mac mini M2 (Mac14,3)
Cores10 (4P + 6E)8 (4P + 4E)
Unified memory16 GB8 GB
Thunderbolt/USB43× ports, 40 Gb/s each2× 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

Today · Shared WiFi WiFi Access Point shared with rest of LAN .5 · M4 16GB .64 · M2 8GB ring traffic shares the same radio that just froze Proposed · Direct Thunderbolt .5 · M4 192.0.2.201 .64 · M2 192.0.2.202 TB4 · 40 Gb/s no AP, no shared radio, no power-save wake

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

Host .5 — 16GB Unified Memory 16GB 77% committed to raw model weights alone — before KV-cache and runtime overhead Embedding Gemma 300M launchd · already running port 8080 Qwen3.5 4B launchd · already running port 8081 Llama 3.1 8B Instruct mlx.launch · just started port 8080 SAME PORT · 8080

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)
CheckBeforeAfter
launchctl list2 hetu.vllm-mlx entriesnone
Port 8080held by embedding serverfree
Port 8081held by Qwen3.5 4Bfree
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.