How I proved a campus firewall was filtering by hostname

A command that hung instead of failing, a test that showed the campus firewall was reading the TLS handshake, and what I learned about diagnosing network problems layer by layer.

The problem

I look after a server on campus. It has no public IP, and I wanted to reach it over SSH from anywhere.

The campus VPN looked like the obvious answer. It connects, but it does not route the internal subnets, so the server was still out of reach.

What I tried

The server runs Ubuntu 23.10, which is end of life. apt returned 404 errors because the package archives for that release had moved. Before installing anything I had to sort out the sources.

I tried Tailscale because it is the least work when it behaves. The install went fine. Then this hung:

sudo tailscale up

How I worked out what was blocking it

Plain failure would have been easier. The command did not fail, it waited.

What I could see:

A silent drop after the TCP handshake, on a port that is otherwise allowed, points at inspection of the handshake itself. The firewall appeared to be reading the SNI, the server name a client sends at the start of TLS, and dropping connections to names it did not like. A rule that blocks by IP or port would have refused the TCP connection.

The test that settled it: TLS on port 443 to my own VPS worked, but TLS on port 443 to Tailscale's control server connected over TCP and then silently dropped. Same port, same protocol, different hostname. The only thing that differed was the hostname in the TLS handshake (SNI), so the filter was looking at that.

Gotchas

An end-of-life Ubuntu release makes every step slower, because even installing a tool means fixing the package sources first.

A hang is not a failure. Check which layer stalls (DNS, TCP, TLS) before you decide what to try next.

What I learned

Diagnose a network problem one layer at a time: DNS, then TCP, then TLS, then the application. Each layer either works or it does not, and the first one that stalls tells you where to look.

Here the TCP connection opened and the stall came at TLS, which is where the hostname is sent. Changing only the hostname and keeping the port and the protocol the same is what turned a guess into a proof.