Skip to content

atenet/dns: publish the router's IPv6 ClusterIP as an AAAA - #938

Open
Yuan Gao (ygao-g) wants to merge 4 commits into
agent-substrate:mainfrom
ygao-g:atenet-dns-aaaa
Open

atenet/dns: publish the router's IPv6 ClusterIP as an AAAA#938
Yuan Gao (ygao-g) wants to merge 4 commits into
agent-substrate:mainfrom
ygao-g:atenet-dns-aaaa

Conversation

@ygao-g

@ygao-g Yuan Gao (ygao-g) commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Depends on #874, whose commit appears in this diff until it merges.

Actor names have no IPv6 address. Every actor name resolves to the same
place — the atenet-router ClusterIP, with per-actor demux at Envoy on the
Host header — so the zone now publishes an address record per family the
router has a ClusterIP in. That also fixes a hard failure on IPv6-only
clusters, where the router's only ClusterIP is a v6 address published as an
IN A: every A query for an actor name SERVFAILs there today, for every
client, not just musl.

Ordering against #911. Prefer landing after it, but the dependency is
soft — without #911 the router Service is SingleStack, so no dual-stack
cluster has a v6 ClusterIP to publish and the rendered zone is unchanged. The
case to avoid is #911 landing split: a dual-stack Service without the ::
listener publishes an AAAA nothing is bound to, and Happy Eyeballs takes the
working IPv4 path down with it.

Part of #246.

🤖 Generated with Claude Code

Before, the actor zone answered A queries and failed everything else --
AAAA for a valid actor, and any name in the zone that is not an actor.
A failure reads as a temporary error rather than an answer, so clients
retry it and then give up on the name; Alpine actors could not resolve
each other at all, even on an IPv4-only cluster. After, those queries
return a correct empty answer, and one that resolvers can cache.

A unit test pins the whole rendered zone as a literal, so editing the
name pattern or the suffix fails there rather than passing silently.
Splits a Service's cluster IPs into its IPv4 and IPv6 entries, returning
"" for a family the Service has no address in. No behavior change on its
own -- nothing calls it until the AAAA change later in this series. It
is shared rather than package-local because a Service with no
ipFamilyPolicy is SingleStack, so one empty family is the steady state
on every cluster, not an error, and each caller would otherwise have to
decide that for itself.

Unit tests cover single- and dual-stack Services and the unallocated and
malformed cases.
No behavior change -- buildTemplate() already ran once, from init(). The
next commit renders the Corefile on every call instead, where a stamp
taken inline would differ each time: reconcile compares the render
against the file on disk, so it would rewrite and reload CoreDNS every
tick.
Before, an actor name never resolved over IPv6: the zone published the
router's primary cluster IP, always as an A record whatever family it
was. On a dual-stack cluster the v6 address went unpublished; on an
IPv6-only cluster the record was malformed, so every A query for an
actor name failed. After, the zone publishes an address record per
family the router has an address in, and answers empty for a family it
has none in.

Unit tests pin the rendered zone for each family combination, verified
against the pinned coredns/coredns:1.11.1.
@ygao-g Yuan Gao (ygao-g) added the DO NOT MERGE This PR must not be merged yet. label Aug 19, 2026
Yuan Gao (ygao-g) added a commit to ygao-g/substrate that referenced this pull request Aug 20, 2026
Nothing in the e2e harness could query the actor DNS zone. Suites reach
actors by port-forwarding atenet-router and passing the actor name as a
Host header, so the zone CoreDNS actually serves went unasserted, and a
suite that wanted to check it had no way to distinguish an empty answer
from a server failure. Adds a DNS client that port-forwards the atenet
DNS Service and reports the rcode class alongside the addresses, plus a
helper for the router's ClusterIP in each family.

First of two commits; the tests that use these follow. clusterIPsByFamily
here is a stopgap that agent-substrate#938 replaces with internal/ipfamily.
Yuan Gao (ygao-g) added a commit to ygao-g/substrate that referenced this pull request Aug 20, 2026
The zone answered A queries and failed everything else -- AAAA for a
valid actor, and any name in the zone that is not an actor -- and no
test caught it, because Go's resolver masks a SERVFAIL that musl treats
as fatal. These assert the rcode class rather than the record: a non-A
qtype and a name that misses the actor regex must come back NODATA or
NXDOMAIN, and an A query must carry the router's ClusterIP. A separate
test covers the AAAA record, skipped where the router has no v6 address.

Second of two commits. The assertions are red until agent-substrate#874 and agent-substrate#938 land,
so this stays a draft until then. Part of agent-substrate#246.
Yuan Gao (ygao-g) added a commit to ygao-g/substrate that referenced this pull request Aug 21, 2026
Nothing in the e2e harness could query the actor DNS zone. Suites reach
actors by port-forwarding atenet-router and passing the actor name as a
Host header, so the zone CoreDNS actually serves went unasserted, and a
suite that wanted to check it had no way to distinguish an empty answer
from a server failure. Adds a DNS client that port-forwards the atenet
DNS Service and reports the rcode class alongside the addresses, plus a
helper for the router's ClusterIP in each family.

First of two commits; the tests that use these follow. clusterIPsByFamily
here is a stopgap that agent-substrate#938 replaces with internal/ipfamily.
Yuan Gao (ygao-g) added a commit to ygao-g/substrate that referenced this pull request Aug 21, 2026
The zone answered A queries and failed everything else -- AAAA for a
valid actor, and any name in the zone that is not an actor -- and no
test caught it, because Go's resolver masks a SERVFAIL that musl treats
as fatal. These assert the rcode class rather than the record: a non-A
qtype and a name that misses the actor regex must come back NODATA or
NXDOMAIN, and an A query must carry the router's ClusterIP. A separate
test covers the AAAA record, skipped where the router has no v6 address.

Second of two commits. The assertions are red until agent-substrate#874 and agent-substrate#938 land,
so this stays a draft until then. Part of agent-substrate#246.
Yuan Gao (ygao-g) added a commit to ygao-g/substrate that referenced this pull request Aug 21, 2026
Nothing in the e2e harness could query the actor DNS zone. Suites reach
actors by port-forwarding atenet-router and passing the actor name as a
Host header, so the zone CoreDNS actually serves went unasserted, and a
suite that wanted to check it had no way to distinguish an empty answer
from a server failure. Adds a DNS client that port-forwards the atenet
DNS Service and reports the rcode class alongside the addresses, plus a
helper for the router's ClusterIP in each family.

The tests that use these follow. clusterIPsByFamily here is a stopgap
that agent-substrate#938 replaces with internal/ipfamily.
Yuan Gao (ygao-g) added a commit to ygao-g/substrate that referenced this pull request Aug 21, 2026
The rcode assertions in the previous commit prove the zone stops failing
an AAAA query, not that it ever answers one. Nothing checks that the
record the zone does publish is the router's IPv6 ClusterIP, so agent-substrate#938
could regress to an empty answer and every existing test would still be
green.

Kept separate from TestActorDNSZone because it is the only assertion
here whose expected result changes with the cluster: it skips wherever
atenet-router has a single ClusterIP, which is every cluster until agent-substrate#911
gives the Service a dual-stack policy. Part of agent-substrate#246.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/network DO NOT MERGE This PR must not be merged yet.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants