Skip to content

e2e: assert the router ingress works on every IP family - #1083

Draft
Yuan Gao (ygao-g) wants to merge 7 commits into
agent-substrate:mainfrom
ygao-g:e2e-ingress-family
Draft

e2e: assert the router ingress works on every IP family#1083
Yuan Gao (ygao-g) wants to merge 7 commits into
agent-substrate:mainfrom
ygao-g:e2e-ingress-family

Conversation

@ygao-g

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

Copy link
Copy Markdown
Collaborator

Stacked on #1082 and #911, whose commits it contains; the last commit is the reviewable change. Part of #246. Draft until #911 lands.

Nothing checks that the router's dataplane listeners bind more than an IPv4 socket, and no test reaches an actor over the router's IPv6 ClusterIP. Every path a test has into the router — a port-forward, the pods/proxy and services/proxy subresources — is mediated by the API server, which picks the family, so a listener that lost its IPv6 socket would go unnoticed.

Verified on a dual-stack kind cluster: both new assertions pass, and reverting #911 turns them red and skipped respectively. Note a dual-stack lane also needs #938 — without it TestActorDNSAAAA stops skipping and fails.

🤖 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.
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.
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.

Both rcode assertions fail on a tree without the first commit here and
pass with it, measured on a single-stack IPv4 kind cluster. Part of
agent-substrate#246.
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.
@ygao-g
Yuan Gao (ygao-g) marked this pull request as draft August 21, 2026 20:09
The router and egress manifests bind the IPv4 wildcard on every Envoy
socket, and neither gateway's Service asks for a second IP family. On a
dual-stack cluster the router answers on its IPv4 ClusterIP and on
nothing at all for IPv6; on an IPv6-primary cluster the kubelet cannot
probe the pod on its only address, so atenet-egress crashloops while
Envoy itself starts fine.

Both gateways now bind `::` as well and ask for PreferDualStack. That
makes them accept IPv6, not reach it: the egress dns_lookup_family, the
DNS AAAA path, and atunnel's original-destination lookup stay IPv4. The
experimental sdsmint egress variant is untouched.
The router's dataplane health check and the drain sequence dial the
Envoy admin interface on the IPv4 loopback, which now depends on the
admin socket keeping ipv4_compat set alongside its `::` bind. Losing
that fails silently: the drain reads the refused dial as "Envoy already
exited" and reports a drain it never performed.

Both callers now dial localhost, which resolves to either loopback, so
the drain no longer depends on how the socket is spelled. ipv4_compat
stays for the egress kubelet probe, where a regression turns any IPv4
run red on the spot.
Nothing checked that the router's dataplane listeners bind more than an
IPv4 socket, and nothing reached an actor over the router's IPv6
ClusterIP. Every other path a test has into the router -- a
port-forward, the pods/proxy and services/proxy subresources -- is
mediated by the API server, which picks the family, so no existing test
could have caught a listener that lost its IPv6 socket. Reads the bound
addresses from Envoy's own admin /listeners, and drives an in-cluster
probe pod at the router over each ClusterIP in turn.

Red until agent-substrate#911 binds those sockets, so this stays a draft until then.
The per-family probe skips on a single-stack cluster. Part of agent-substrate#246.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DO NOT MERGE This PR must not be merged yet.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant