I have spent a lot of this year moving data around my own house without ever knowing how fast the house actually is. Every speed test I'd run measured the internet, which is the one part of the path I can't change. What I wanted was the inside number: PC to server, phone to server, laptop to server, with no ISP anywhere in it. So today started as a measurement day. It ended as a day about the one device I couldn't measure at all.
A service with an eleven-minute lifespan
The first attempt was OpenSpeedTest, the browser-based version of the idea: a container on the server, a gauge in a tab, press start. One fact came off the box before I'd written a line of the plan, and it reset everything. The server's active NIC links at 2.5 GbE, not gigabit, so the ceiling for anything wired is around 2.35 Gbps rather than the 940 Mbit/s I'd been assuming.
Then I asked whether the thing could be driven from a terminal, because a gauge in a browser is not a number I can log. It could: the container's nginx serves a 30 MiB blob at one path and takes uploads at another, both plain HTTP, so it got wrapped in a curl driver that pushed 22.6 Gbps over loopback. At which point I looked at what I'd built, realised I'd reinvented a worse iperf3, and said take it down. Container, image and directory all deleted. Eleven minutes of visible life, about an hour counting the research. I had built a browser front end for something that has had a perfectly good command-line front end for twenty years.
iperf3 replaced it in minutes, on host networking rather than bridged, since Docker's NAT layer shaves throughput and accuracy was the entire point.
Two numbers that looked like faults and weren't
The wired test, eight parallel streams, PC to server: 2.38 Gbits/sec sending and 2.37 Gbits/sec receiving. Theoretical TCP maximum on 2.5GbE is about 2.4 Gbit/s, so that's within a couple of percent of physics, and there's no gigabit hop hiding anywhere between my desk and the server. Then the next two devices both came back slow, and both were fine.
The iPhone first, where I confused myself thoroughly. The first run gave 1.05 Mbit/s, which is not a fault, it's iperf3's default UDP pacing of exactly 1 Mbit/s doing what it's told. The second gave 182 Mbit/s and I announced that one was on TCP. It wasn't; the app's own label still said UDP, and I'd read what I expected rather than what was written. The controller had the phone on 6 GHz to the lounge AP at −64 dBm, negotiating 1441 Mbit/s down and 817 up. Weak direction, single stream, pointed the wrong way through a wall.
The MacBook was the better lesson. 844 Mbit/s felt like a gigabit wall and set off every alarm I have about a slow hop. But it was on the office in-wall AP at −38 dBm, about as good as a wireless signal gets, on 80 MHz WiFi 6 with a link rate of 1201 both ways, and 844 out of 1201 is 70% — roughly the textbook ceiling for real WiFi after overhead. It's also an M1 MacBook Pro, whose radio is hard-capped at 80 MHz and two spatial streams. About 900 Mbit/s isn't a fault on that machine, it's the permanent roof.
Two numbers that looked like problems, two answers that were "this is correct behaviour". Worth naming, because the instinct both times was to go hunting for a fault that didn't exist.
The handheld that would only talk in one direction
Then the afternoon, and the part I still don't have an answer for.
I'd bought an MSI Claw 8 AI+ and was mid-unboxing. On WiFi it worked perfectly, on the Home network. Plugged into a 2.5G USB ethernet dongle it picked up an address on the Management network, showed a link, and had no internet whatsoever. Three hours later I had four diagnoses, each consistent with the evidence available at the time, and each killed by a single line of reply.
One: it's my own firewall. The controller showed the Claw linked at 2.5G with a healthy lease, nothing blocked. But my Management network has an allow rule for whitelisted admin devices followed immediately by a block-everything-else rule, and the dongle's MAC wasn't on the whitelist. That would cut it off from the gateway, which is also its DHCP-assigned DNS server, which is exactly what Windows reports as "no internet". WiFi worked because the Home network has its own resolver. Clean, complete, satisfying. I added the MAC. Three minutes later: "Still nothing."
Two: stale mapping. Those rules resolve MAC to current IP when the gateway provisions them, and this dongle has history on the network going back to June. Force-provision, wait 30 seconds, retest. I couldn't paste the adapter config, because it's a handheld games console with no clipboard bridge to here, so I read four values off the screen: address, mask, gateway, DNS. All textbook correct. The retest returned Destination host unreachable, a different failure entirely, and one that lives below the firewall layer. Firewalls don't touch ARP.
Three: the ethernet has been working the whole time. I pulled traffic flows for the dongle's MAC over three hours and got 284 of them, HTTPS and HTTP with return traffic, starting well before I'd touched the firewall. So TCP is flowing and only UDP and ICMP to the gateway are dead, which fits, because Windows decides "no internet" from a DNS probe. That split is the signature of a Realtek USB NIC checksum-offload bug, where bad checksums get silently discarded. It even explains the working DHCP lease, since DHCP servers read raw sockets and famously ignore bad UDP checksums, and ARP has no checksum at all. The test was to load a bare IP address in the browser: pure TCP, no DNS. Reply: "just comes up with 'server not found'".
Which killed it. Firefox never got a TCP connection at all, so those 284 flows the gateway logged were traffic it forwarded and counted and that never arrived: a real record of real packets that didn't land, and the most misleading evidence of the day.
Four: the return path is dying, and it's the cable. That port had only ever run at 1G before, feeding a Flex Mini, so this was the first time anything had asked it to sustain 2.5GBASE-T, and marginal cable can genuinely pass one direction and mangle the other. I pulled the port error counters and suggested forcing the dongle down to 1.0 Gbps full duplex as the equivalent of swapping the cable. Reply: "i dont know how, but it got through for a few seconds and has now been locked out again."
A few seconds after a link event, then dead again, transmitting the whole time. That reads like the receive path dropping into low-power idle and never waking: Realtek dongle, Energy-Efficient Ethernet, a battery-optimised handheld with aggressive USB power management. So the day ended with a kill-list — disable EEE, Green Ethernet, Gigabit Lite, selective suspend, untick "allow the computer to turn off this device to save power" — and no confirmation any of it worked, because none of it was tried.
The Claw is on WiFi. That is not a fix, it's a workaround, and I'm writing it down as one. Along the way I ruled out a duplicate IP claimant, ACLs, content filters and blocked flows in either direction, and I put a MAC on an admin whitelist for a problem that had nothing to do with the firewall. That entry is still there.
Housekeeping
By nine that evening I'd given up on the wire and set the Claw up over WiFi instead. SSH needed no firewall change, because the existing Home-to-server allow rule already sits above the Management block, though I did nearly destroy the wrong key by running ssh-keygen inside the SSH session, on the server, where it offered to overwrite the server's own identity. Then I said I thought it had worked and the log disagreed, because the three most recent lines still read Accepted password and the last of those was the key-copy itself. One more login and there it was, Accepted publickey ... ED25519 at 21:26:13. Believing the log over the vibe is a habit worth keeping.
The NAS needed an actual rule, since it sits behind that same block: one allow policy scoped to the Claw's WiFi MAC and the NAS address only. The controller filed it at index 10010, below the block at 10007, which looked wrong, and two attempts to move it failed. Then the ping went through anyway, 4 of 4 at 3 to 14 ms. So those index numbers don't reflect actual evaluation order between zones, and the right response to a misplaced rule that works is to leave it alone.
The morning had also produced a whole plan for moving the sim rig off the Flex Mini and straight into the in-wall AP: nothing blocked it, the Flex Mini was serving exactly one device and became redundant, and the rig would have gone from about 940 Mbit/s to about 2.3 Gbit/s. I'd already signed the session off when the correction landed: that motherboard only has a gigabit port. The whole thing evaporated on one line of hardware trivia I should have checked first.
And the last thing today, at 21:41 and again at 21:54, was the same mistake twice on two machines. I mapped a drive using the name I assumed the server's share had, which is the folder's name and not the share's name, and got System error 67, the network name cannot be found. Corrected it on the Claw, then five minutes later corrected it again on the PC, having learned nothing in between.
I spent today building the ability to measure my network precisely, and proved the wired path runs within two percent of its physical limit. The device that beat me was the one that never produced a number at all.