Digital Garden

Three Latches and One Acquittal: The Layered Investigation of SSH Unreachable Under Tailscale, Shield and Stealth as Twin Gate Gods, and the Exoneration of Huorong

2026-11-02 · ~4 min read · Tailscale · SSH · firewall · troubleshooting

Three ports of one machine — 22, 3389, 9119 — behaved identically on the tailnet: timeout. And the control, the Mac’s 22, connected in an instant. A textbook three-layer investigation ran from 19:40 to 20:34.

I. The First Latch: Shield Mode

tailscale debug prefs read "ShieldsUp": true — this switch drops every connection entering from the tailnet, and the identical behavior of all three ports (timeout, not refusal) is its signature. Switched off and verified: tailscale set --shields-up=false. But the cloud agent’s retest of 22 still drew an 8.2-second empty response instead of a 0.4-second refusal — behind this latch stood another.

II. The Second Latch: The Silent Reversal of the NIC Class

After restarting the Tailscale service, the Tailscale adapter’s network class fell from Private to Public (stuck in Identifying, resistant to Set-NetConnectionProfile). And the built-in Tailscale-In allow rules apply only to Domain,Private — the moment the adapter turned Public, they expired wholesale, and the Windows firewall silently dropped packets under its Public default. The cure was a persistent rule that ignores class detection altogether: Tailscale-Inbound-CGNAT, Profile Any, matching only 100.64.0.0/10 and the fd7a IPv6 range, allowing only TCP 22 and 9119.

III. The Third Latch: Stealth’s Silence and the Acquittal of Huorong

The most dramatic passage was a wrongful conviction and its pardon. The WFP drop audit’s event 5152 showed inbound TCP 22 discarded by a filter named Stealth — Huorong’s hrwfpdrv kernel driver was running, and its config’s forceattack.BlockFAProt=1 (brute-force protection) fit the theory, pinning the suspicion. But inside Huorong’s own interface, settings and security logs told another story: the temporary blacklist empty, no interception records at all. The true explanation was plain: Windows, on a Public adapter, silently drops SYN to ports with no listener (stealth mode, no RST), and nothing was listening on 22 — OpenSSH.Server was NotPresent. The controlled experiment settled it: a PowerShell listener on 18082 and the Mac connected instantly; no listener on 22, and timeout. Huorong is innocent.

IV. The Endgame: Install a Service and the Door Opens

The conclusion returned to its origin: install sshd and it will connect. The Windows Update optional feature stalled a dozen minutes, so winget installed OpenSSH 10.0.0.0; sshd set to auto-start, listening on 0.0.0.0:22; the public key written to C:\ProgramData\ssh\administrators_authorized_keys (an administrator account must use this file, not ~/.ssh; the ACL keeps only SYSTEM and Administrators); the Mac connected and read the SSH-2.0 banner; the end-to-end key authentication passed its self-test (a temporary key added and deleted within the minute). Route B (tailscale serve proxying 9119, never touching the Desktop backend or its exit 78) waits on tailnet Serve authorization. The final exposure narrowed to 22/9119 for the tailnet only — Ethernet stays Public with default-deny, and neither the LAN nor the public internet can enter. Three lessons generalize: a timeout does not mean a firewall is targeting you — maybe nobody is home; allow rules must be checked against their class assumptions; and acquittal and conviction need the same evidence — a controlled experiment.

Comments

← Digital Garden