Skip to content

atunnel: bind actor ingress/egress listeners dual-stack (:port) - #978

Closed
SURAJ KUMAR (krsnaSuraj) wants to merge 1 commit into
agent-substrate:mainfrom
krsnaSuraj:fix/atunnel-dualstack-listeners2
Closed

atunnel: bind actor ingress/egress listeners dual-stack (:port)#978
SURAJ KUMAR (krsnaSuraj) wants to merge 1 commit into
agent-substrate:mainfrom
krsnaSuraj:fix/atunnel-dualstack-listeners2

Conversation

@krsnaSuraj

Copy link
Copy Markdown

atunnel: bind actor ingress/egress listeners dual-stack (:port) instead of 0.0.0.0

Fixes: #943

Problem

The atunnel actor ingress (atunnel-listen-address) and egress
(atunnel-egress-listen-address) listeners defaulted to 0.0.0.0:443 /
0.0.0.0:15001 in both cmd/ateom-gvisor/main.go and cmd/ateom-microvm/main.go.

Binding a specific IPv4 wildcard (0.0.0.0) has two consequences on modern
clusters:

  1. IPv6-only clusters are broken — there is nothing to tunnel to: the mTLS
    ingress listener only exists on the IPv4 loopback/any address, so the router
    cannot reach the actor on an IPv6-only pod network.
  2. Dual-stack clusters only get an IPv4 leg — the listener does not accept
    IPv6 connections, so dual-stack actors lose the IPv6 path.

Since #559 removed the pod-IP:80 DNAT, the mTLS listener is the only actor
ingress path, so this is the sole ingress surface for actors.

Fix

Use Go's dual-stack wildcard form (:port) as the default for both listener
flags in both ateom binaries. net.Listen("tcp", ":443") creates a dual-stack
socket that accepts both IPv6 and IPv4 connections on hosts with IPv6 support,
and falls back to IPv4-only on IPv4-only hosts — the correct default for a
pod-network listener.

Also updated the atecontroller worker-pool deployment apply path, which was
passing the old 0.0.0.0:... values as explicit flag overrides (which would
have silently reverted the new default at runtime):

  • cmd/atecontroller/internal/controllers/workerpool_apply.go
  • cmd/atecontroller/internal/controllers/workerpool_apply_test.go

Files changed

  • cmd/ateom-gvisor/main.go:443 / :15001 defaults
  • cmd/ateom-microvm/main.go:443 / :15001 defaults
  • cmd/atecontroller/internal/controllers/workerpool_apply.go — explicit args updated
  • cmd/atecontroller/internal/controllers/workerpool_apply_test.go — expected args updated

Behavior / compatibility

  • Flag overrides still work: an operator can pass
    --atunnel-listen-address=127.0.0.1:8443 and it is honored unchanged.
  • No change to the egress SO_ORIGINAL_DST lookup path (atunnel: IPv6 support for original destination lookup #686) — that is the
    destination-address resolution, not the listen address.
  • No change to the API-server --grpc-listen-addr (that flag is unrelated).

Verification

  • gofmt -l clean on all changed files.
  • go vet clean on the affected packages.
  • go test ./internal/atunnel/... passes.
  • No remaining 0.0.0.0:443 / 0.0.0.0:15001 references in Go sources.

Default atunnel-listen-address to :443 and atunnel-egress-listen-address
to :15001 in both ateom binaries so listeners accept IPv6 + IPv4 on
dual-stack clusters and exist on IPv6-only clusters. 0.0.0.0 bound only
an IPv4 wildcard, breaking IPv6-only pod networks and dropping the IPv6
leg on dual-stack.

Update atecontroller workerpool apply path that passed the old values
as explicit flag overrides, plus its expected-args test.

Fixes: agent-substrate#943
Yuan Gao (ygao-g) added a commit to ygao-g/substrate that referenced this pull request Aug 18, 2026
@ygao-g

Copy link
Copy Markdown
Collaborator

Thanks for picking this up. Two notes, one procedural and one technical.

I had a branch for this predating the issue and have now opened it as #1042 — the same ingress change, plus a test that pins both listen addresses so the default can't drift back silently. Flagging the overlap so review isn't duplicated; maintainers should take whichever they prefer.

On the egress half, I'd suggest dropping it from this PR. It is inert today — the actor interior veth is IPv4-only and the REDIRECT feeding 15001 is an IPv4 nftables rule, so nothing can reach that listener over v6. It stops being inert the moment your #979 lands: TCPOriginalDestination still has no IP6T_SO_ORIGINAL_DST branch (#686, #753), so a v6 flow would then be accepted and fail the original-destination lookup rather than be refused. The two listeners are ready at different times.

@krsnaSuraj

Copy link
Copy Markdown
Author

Thanks for the thorough review — both points are correct, and #1042 is strictly the better change. I verified the actor interior veth is IPv4-only and the REDIRECT into 15001 is an IPv4 nftables rule, so the dual-stack egress listener in #978 is indeed inert today and would become an accepted-then-failed flow once #979 lands and #686/#753 are still open.

The egress half is out — I'm closing #978 in favor of #1042. It carries the ingress change plus a test that pins both listen addresses, which is exactly the right regression guard (and better than what I had). The egress widening belongs in a separate PR gated on #686/#753, which the TODO comment in #1042 already records.

No unique content left in my branch. Appreciate the heads-up on the overlap.

@krsnaSuraj

Copy link
Copy Markdown
Author

Closing in favor of #1042 — see review reply above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

atunnel: egress listener accepts IPv6 connections it cannot route

2 participants