1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
#+TITLE: Captive-portal login — learnings + baking it into the net panel
#+DATE: 2026-06-30
#+SOURCE: the 2026-06-30 Hyatt wifi saga (velox)
* Why this exists
On a locked-down-DNS laptop, captive portals never show their login page, even
though phones get on fine. We spent hours on a Hyatt portal before finding the
mechanism; this captures it so the fix becomes a panel feature instead of a
one-off script.
* The mechanism (what actually blocks the login)
A redirect portal works by *DNS hijack*: you query a name, the hotel's resolver
hands back the portal, you get the login page. Two things on velox stop that:
- *System resolver forces DNS-over-TLS.* =/etc/systemd/resolved.conf.d/dns-over-tls.conf=
hardcodes =DNS=1.1.1.1#... 9.9.9.9#...= with =DNSOverTLS=yes=. The system never
queries the hotel's resolver at all. The hotel blocks 853 (DoT) and external
53, so system DNS is simply dead on the portal — only 443 (DoH) gets out.
- *Browser DoH.* Chrome "secure DNS" on bypasses the hotel DNS too, so the
browser never gets redirected either.
A phone works because it uses *plain DNS* from the hotel plus a built-in
captive-portal popper. The laptop has neither.
Confirmed facts from the saga:
- Front desk: it's a normal redirect-to-login portal. Phone: connects fine.
- No DHCP option 114 (RFC 8910) — the portal doesn't advertise its URL. But the
URL is recoverable from the HTTP 302 once you're on plain DNS.
- The walled garden whitelists OS captive-detection endpoints
(=captive.apple.com= returns "Success") — a *misleading* signal, not real
internet. Don't trust it.
- 443/DoH egress works broadly on the portal; only port-53 DNS is held. So
"system DNS fails" never means "no internet" here.
* The working fix (=~/.local/bin/hotel-wifi=, to be folded in)
Temporarily disable DoT → plain hotel DNS → discover the portal URL from the
redirect → open it in a clean browser profile (no DoH, no stale HSTS/cookies) →
click the button → restore DoT. Reversible; tested to restore cleanly.
#+begin_src sh
#!/bin/sh
# hotel-wifi disable DoT -> find the portal login URL -> open it
# hotel-wifi off restore normal encrypted DNS (run once online)
conf=/etc/systemd/resolved.conf.d/dns-over-tls.conf
if [ "${1:-on}" = "off" ]; then
[ -f "$conf.captive-disabled" ] && sudo mv "$conf.captive-disabled" "$conf"
sudo systemctl restart systemd-resolved
echo "Encrypted DNS (DoT) restored."; exit 0
fi
[ -f "$conf" ] && sudo mv "$conf" "$conf.captive-disabled"
sudo systemctl restart systemd-resolved; sleep 1
resolvectl flush-caches 2>/dev/null || true
portal=""
for t in http://captive.apple.com/hotspot-detect.html http://neverssl.com \
http://detectportal.firefox.com/canonical.html; do
loc=$(curl -sS -m 6 -o /dev/null -w '%{redirect_url}' "$t" 2>/dev/null)
[ -n "$loc" ] && { portal="$loc"; break; }
url=$(curl -sS -m 6 "$t" 2>/dev/null | grep -ioE 'https?://[^"'"'"' >]+' \
| grep -ivE 'apple\.com|neverssl|firefox|w3\.org|gstatic' | head -1)
[ -n "$url" ] && { portal="$url"; break; }
done
prof=$(mktemp -d)
setsid -f google-chrome-stable --user-data-dir="$prof" "${portal:-http://neverssl.com}" >/dev/null 2>&1
echo "Click the login button. When online: hotel-wifi off"
#+end_src
* Baking it into the net panel (the task)
- The net engine already diagnoses captive / no-internet. When it sees a held
portal, the panel should offer a first-class *"Log in to this network"*
action that runs the plain-DNS + clean-browser flow above, reversibly, and
auto-restores DoT when connectivity returns (or on a timeout).
- Reconcile with the existing =net portal= command and the =captive= helper —
they assumed a DNS-hijack-to-gateway model that did NOT match this portal
(gateway served no web; DNS was held, not hijacked-to-portal). The plain-DNS
approach is the one that worked; make it the engine's portal path.
- The DoT toggle must be safe and reversible (the =off= step). Consider a
per-connection or time-boxed DoT-off that can't strand encrypted DNS.
- Surface the misleading-"Success" lesson: a whitelisted captive-check passing
is not "online" — gate on a real, non-whitelisted fetch.
* Related fix that unblocked the panel (already shipped)
The panel could never switch networks because =net up= placed =--wait= after the
nmcli subcommand (it's a global option). Fixed in dotfiles 2432311; fake-nmcli
now rejects the misplaced flag so it can't regress.
|