channel CLI: pure-Go resolver bypasses Tailscale MagicDNS on Linux #12

Closed
opened 2026-06-03 08:51:02 +00:00 by coilysiren · 1 comment
Owner

Ported from closed coilyco-bridge/coily#101. Home moved to otel-a2a-relay-cli (coily channel removed in coily v2.51.0). Applies to the o2r-cli binary's HTTP client and any Go binary built the same way.

Symptom

channel read <id> fails from kai-server with:

channel: GET /agent-channel/VHGC: Get "http://api/agent-channel/VHGC": dial tcp: lookup api on 127.0.0.53:53: server misbehaving

curl http://api from the same shell on the same host works fine and resolves api to a tailnet IP (100.126.181.22 at the time of writing).

Cause

The binary is built with Go's default pure-Go resolver. The pure-Go resolver reads /etc/resolv.conf directly and sends queries to whatever nameserver is listed there. On Ubuntu kai-server that is 127.0.0.53 (systemd-resolved), which does not know Tailscale MagicDNS names. The pure-Go resolver does not consult /etc/nsswitch.conf, so it bypasses the Tailscale resolver entry that libc-based clients (curl, getent hosts, ssh, anything that calls getaddrinfo) honor.

Known Go-on-Linux footgun. Reference: golang/go#57757, Tailscale MagicDNS doc (systemd-resolved + Go interaction).

Fix options, ranked

  1. Build with cgo resolver - add -tags netcgo, or bake GODEBUG=netdns=cgo+1. Routes Go's net package through libc getaddrinfo, picking up nsswitch and the Tailscale entry. Lowest blast radius, no runtime config, fixes every verb that opens an HTTP connection. Cost: cgo enabled = need a C toolchain at build time.
  2. Runtime env in the systemd unit / shell rc - set GODEBUG=netdns=cgo. Works, but every host has to be configured, so it drifts. Unblocks today (GODEBUG=netdns=cgo ... channel read <id> works, verified).
  3. Resolve via the Tailscale local API - tailscale.com/client/tailscale.LocalClient. More invasive, tailnet-only, breaks where tailscaled isn't local. Not the right call.

Option 1 is the right move.

Acceptance

  • channel read <id> against a MagicDNS hostname succeeds from kai-server with no per-shell env tweak.
  • A regression test that resolves a tailnet hostname (or a mocked equivalent exercising the same code path) fails before the fix.

Provenance

Surfaced in agentic-os-kai#292 while teaching an agent about the o2r agent-channel surface.

_Ported from closed coilyco-bridge/coily#101. Home moved to otel-a2a-relay-cli (coily channel removed in coily v2.51.0). Applies to the o2r-cli binary's HTTP client and any Go binary built the same way._ **Symptom** `channel read <id>` fails from kai-server with: ``` channel: GET /agent-channel/VHGC: Get "http://api/agent-channel/VHGC": dial tcp: lookup api on 127.0.0.53:53: server misbehaving ``` `curl http://api` from the same shell on the same host works fine and resolves `api` to a tailnet IP (`100.126.181.22` at the time of writing). **Cause** The binary is built with Go's default pure-Go resolver. The pure-Go resolver reads `/etc/resolv.conf` directly and sends queries to whatever nameserver is listed there. On Ubuntu kai-server that is `127.0.0.53` (systemd-resolved), which does not know Tailscale MagicDNS names. The pure-Go resolver does **not** consult `/etc/nsswitch.conf`, so it bypasses the Tailscale resolver entry that libc-based clients (`curl`, `getent hosts`, `ssh`, anything that calls `getaddrinfo`) honor. Known Go-on-Linux footgun. Reference: golang/go#57757, Tailscale MagicDNS doc (systemd-resolved + Go interaction). **Fix options, ranked** 1. **Build with cgo resolver** - add `-tags netcgo`, or bake `GODEBUG=netdns=cgo+1`. Routes Go's net package through libc `getaddrinfo`, picking up nsswitch and the Tailscale entry. Lowest blast radius, no runtime config, fixes every verb that opens an HTTP connection. Cost: cgo enabled = need a C toolchain at build time. 2. **Runtime env in the systemd unit / shell rc** - set `GODEBUG=netdns=cgo`. Works, but every host has to be configured, so it drifts. Unblocks today (`GODEBUG=netdns=cgo ... channel read <id>` works, verified). 3. **Resolve via the Tailscale local API** - `tailscale.com/client/tailscale.LocalClient`. More invasive, tailnet-only, breaks where tailscaled isn't local. Not the right call. Option 1 is the right move. **Acceptance** - `channel read <id>` against a MagicDNS hostname succeeds from kai-server with no per-shell env tweak. - A regression test that resolves a tailnet hostname (or a mocked equivalent exercising the same code path) fails before the fix. **Provenance** Surfaced in agentic-os-kai#292 while teaching an agent about the o2r agent-channel surface.
coilysiren added
P3
and removed
P2
labels 2026-06-17 08:40:03 +00:00
Author
Owner

Closing. o2r is archived in the June 2026 surface reduction - an optional agent channel, unused autonomously. Handover doctrine moves to human-mediated.

Closing. o2r is archived in the June 2026 surface reduction - an optional agent channel, unused autonomously. Handover doctrine moves to human-mediated.
Commenting is not possible because the repository is archived.
No description provided.