From 9de839066c69ef13cf314642f83079b7e9abca88 Mon Sep 17 00:00:00 2001 From: Marcus Summers Date: Thu, 30 Apr 2026 09:57:40 -0400 Subject: [PATCH] =?UTF-8?q?wiki:=20add=20troubleshooting=20article=20?= =?UTF-8?q?=E2=80=94=20iOS=20Tailscale=20clients=20report=20HostName=3D"lo?= =?UTF-8?q?calhost"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documents the non-obvious failure mode where /etc/hosts generator scripts using `tailscale status --json | jq '.HostName'` get poisoned by iOS peers, which always report HostName as the literal string "localhost" because iOS doesn't expose the device name to apps. Includes the buggy and fixed jq filter (use .DNSName first label instead), a real-world Postfix outage example, and a verification checklist. Linked from troubleshooting index and SUMMARY. Discovered while diagnosing a 24h Postfix outage on majordiscord. Co-Authored-By: Claude Opus 4.7 (1M context) --- 05-troubleshooting/index.md | 3 +- ...cale-status-json-hostname-localhost-ios.md | 116 ++++++++++++++++++ SUMMARY.md | 3 +- 3 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 05-troubleshooting/networking/tailscale-status-json-hostname-localhost-ios.md diff --git a/05-troubleshooting/index.md b/05-troubleshooting/index.md index 764b5cf..9bd55ef 100644 --- a/05-troubleshooting/index.md +++ b/05-troubleshooting/index.md @@ -1,6 +1,6 @@ --- created: 2026-03-15T06:37 -updated: 2026-04-29T22:45 +updated: 2026-04-29T23:55 --- # 🔧 General Troubleshooting @@ -14,6 +14,7 @@ Practical fixes for common Linux, networking, and application problems. - [Mail Client Stops Receiving: Fail2ban IMAP Self-Ban](networking/fail2ban-imap-self-ban-mail-client.md) - [firewalld: Mail Ports Wiped After Reload](networking/firewalld-mail-ports-reset.md) - [Tailscale SSH: Unexpected Re-Authentication Prompt](networking/tailscale-ssh-reauth-prompt.md) +- [iOS Tailscale Clients Report HostName="localhost" — Breaks /etc/hosts Generators](networking/tailscale-status-json-hostname-localhost-ios.md) - [rsync over Tailscale: Hung in TCP Teardown After Transfer Completes](networking/rsync-tailscale-teardown-stall.md) - [Windows OpenSSH: WSL Default Shell Breaks Remote Commands](networking/windows-openssh-wsl-default-shell-breaks-remote-commands.md) - [Pi-hole AI Blocklist Blocks Claude Desktop (ERR_CONNECTION_REFUSED)](networking/pihole-blocks-claude-desktop.md) diff --git a/05-troubleshooting/networking/tailscale-status-json-hostname-localhost-ios.md b/05-troubleshooting/networking/tailscale-status-json-hostname-localhost-ios.md new file mode 100644 index 0000000..9e0606e --- /dev/null +++ b/05-troubleshooting/networking/tailscale-status-json-hostname-localhost-ios.md @@ -0,0 +1,116 @@ +--- +title: iOS Tailscale Clients Report HostName="localhost" — Breaks /etc/hosts Generators +domain: troubleshooting +category: networking +tags: + - tailscale + - ios + - postfix + - etc-hosts + - jq +status: published +created: 2026-04-29 +updated: 2026-04-29 +--- + +# iOS Tailscale Clients Report HostName="localhost" — Breaks /etc/hosts Generators + +## Problem + +A homegrown script that builds an `/etc/hosts` block from `tailscale status --json` silently corrupted the file the moment any iOS device joined the tailnet. After the next run, services bound to `localhost` started failing. + +On the affected host (`majordiscord`), Postfix refused to start with: + +``` +postfix: fatal: parameter inet_interfaces: no local interface found for 100.127.114.10 +``` + +`/etc/hosts` looked fine at the top — `127.0.0.1 localhost` was still present — but inside the Tailscale-managed block: + +``` +# TAILSCALE_START +100.84.42.102 tttpod +100.110.197.17 majortoot +100.95.55.40 localhost <-- WRONG (this is an iPhone) +100.84.165.52 majormail +... +100.127.114.10 localhost <-- WRONG (this is an iPad) +# TAILSCALE_END +``` + +When Postfix resolved `localhost` (because `inet_interfaces = localhost` in `main.cf`), the **last matching entry** in `/etc/hosts` won — a Tailscale IP that doesn't exist on this host — and the daemon died on bind. + +## Root Cause + +The script used `.HostName` from the Tailscale JSON: + +```bash +tailscale status --json \ + | jq -r '.Peer[] | "\(.TailscaleIPs[0]) \(.HostName)"' \ + >> "$TEMP_HOSTS" +``` + +iOS Tailscale clients (iPhone, iPad) **always report `HostName: "localhost"`** in the JSON. iOS doesn't expose the real device name to apps the way macOS/Linux/Windows do, so the Tailscale client falls back to the literal string `localhost`. + +Inspect it directly: + +```bash +$ tailscale status --json | jq '.Peer[] | select(.OS == "iOS") | {DNSName, HostName, OS}' +{ + "DNSName": "iphone171.tail7f2d9.ts.net.", + "HostName": "localhost", + "OS": "iOS" +} +{ + "DNSName": "ipad166.tail7f2d9.ts.net.", + "HostName": "localhost", + "OS": "iOS" +} +``` + +Every iOS device contributes a line ` localhost` to `/etc/hosts`, hijacking the `localhost` lookup. + +## Fix + +Use `.DNSName` (the unique tailnet DNS name) and take the first dotted component instead of `.HostName`: + +```bash +tailscale status --json \ + | jq -r '.Peer[] | "\(.TailscaleIPs[0]) \(.DNSName | rtrimstr(".") | split(".")[0])"' \ + >> "$TEMP_HOSTS" +``` + +`DNSName` is always set, always unique, and produces clean labels like `iphone171`, `ipad166`, `majorlab`, etc. + +After patching the script and re-running it: + +```bash +$ bash /root/update_tailscale_hosts.sh +$ systemctl restart postfix +$ systemctl is-active postfix +active +``` + +## Why It's Hard to Spot + +- The corruption only triggers when an iOS device is in the tailnet — so the script "worked" for months. +- `/etc/hosts` files are commonly skimmed top-down. The bogus `localhost` line is buried in the Tailscale block, well below the legitimate `127.0.0.1 localhost` line, and looks superficially like a normal Tailscale entry. +- Postfix's error message names the IP, not `localhost`, so the connection to `/etc/hosts` isn't obvious. +- `getent hosts localhost` shows the *first* match (`127.0.0.1`), not the one Postfix's resolver actually picks for `inet_interfaces` lookup. + +## Verification Checklist + +If you suspect this on any host using a similar generator script: + +```bash +# Any non-loopback "localhost" entries are bugs +grep -nE '^[0-9]+\..* localhost\s*$' /etc/hosts + +# Look at iOS peers' HostName field +tailscale status --json | jq '.Peer[] | select(.OS == "iOS") | .HostName' +``` + +## Related + +- [[majordiscord]] — affected host (incident logged 2026-04-29) +- [[Network Overview]] — Tailscale fleet topology diff --git a/SUMMARY.md b/SUMMARY.md index 5096c39..45a8d18 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -1,6 +1,6 @@ --- created: 2026-04-02T16:03 -updated: 2026-04-29T22:45 +updated: 2026-04-29T23:55 --- * [Home](index.md) * [Linux & Sysadmin](01-linux/index.md) @@ -91,6 +91,7 @@ updated: 2026-04-29T22:45 * [Ollama Drops Off Tailscale When Mac Sleeps](05-troubleshooting/ollama-macos-sleep-tailscale-disconnect.md) * [Ollama: `ollama run` with Piped Stdin Bypasses Chat Template + SYSTEM Prompt](05-troubleshooting/ollama-chat-template-pipe-stdin-bypass.md) * [rsync over Tailscale: Hung in TCP Teardown After Transfer Completes](05-troubleshooting/networking/rsync-tailscale-teardown-stall.md) + * [iOS Tailscale Clients Report HostName="localhost" — Breaks /etc/hosts Generators](05-troubleshooting/networking/tailscale-status-json-hostname-localhost-ios.md) * [macOS: Repeating Alert Tone from Mirrored iPhone Notification](05-troubleshooting/macos-mirrored-notification-alert-loop.md) * [ClamAV CPU Spike: Safe Scheduling with nice/ionice](05-troubleshooting/security/clamscan-cpu-spike-nice-ionice.md) * [Ansible: Vault Password File Not Found](05-troubleshooting/ansible-vault-password-file-missing.md)