fix(envd/port): detect :::PORT wildcard listeners and fix dual-stack key collision - #3588
Open
AdaAibaby wants to merge 4 commits into
Open
fix(envd/port): detect :::PORT wildcard listeners and fix dual-stack key collision#3588AdaAibaby wants to merge 4 commits into
AdaAibaby wants to merge 4 commits into
Conversation
…key collision Fixes e2b-dev#3586. Three bugs caused services binding to IPv6 wildcard addresses (:::PORT) to be silently unreachable from outside the sandbox. Bug A — "::" wildcard not in scan filter The ScannerFilter for the port forwarder listed only "127.0.0.1", "localhost", and "::1" as accepted local addresses. On a dual-stack kernel, gRPC and other frameworks default to listening on "::" (IPv6 wildcard), which gopsutil reports as Laddr.IP = "::". Because "::" was absent from the filter the port was never detected and no socat was started. Fix: add "::" to the filter IPs. A wildcard "::" listener is then normalized to family=4 so socat connects via 127.0.0.1 rather than [::1], avoiding /etc/hosts AAAA-lookup failures on Alpine and other minimal images where "::1 localhost" is absent. Bug B — port key collision drops one socat for dual-stack services The map key was fmt.Sprintf("%d-%d", pid, port), so two connections sharing the same PID and port (one AF_INET on 127.0.0.1, one AF_INET6 on ::1) produced the same key. The second entry silently overwrote the first in the ports map; only one socat was started, for whichever family /proc/net/tcp vs /proc/net/tcp6 happened to return first. Fix: include the local IP in the key → fmt.Sprintf("%d-%d-%s", pid, port, ip). Bug C — TCP6:localhost fails on minimal images startPortForwarding constructed the socat backend address as TCP%d:localhost:PORT. For family=6 this is TCP6:localhost:PORT, which requires /etc/hosts to contain "::1 localhost". Alpine and many stripped base images only have "127.0.0.1 localhost", so the resolution returns no AAAA record and socat fails. Fix: use literal addresses (127.0.0.1 / [::1]) instead of "localhost". Note: fixing e2b-dev#3585 (ipv6.disable=1) eliminates the dual-stack binding condition that triggers Bugs A and B, but Bugs B and C are independently correct to fix regardless of the IPv6 kernel setting. Bump envd version 0.6.13 → 0.6.14 (behavioral change per CLAUDE.md).
AdaAibaby
requested review from
ValentaTomas,
dobrac and
jakubno
as code owners
August 18, 2026 06:46
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b162b72c92
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
When an old envd binary exports port-forwards the Key field uses "<pid>-<port>" (one dash). The new scan loop already writes "<pid>-<port>-<ip>" so the imported entry is never matched, gets marked DELETE on the first scan cycle, and the restored socat is killed — dropping all forwarded connections across a live upgrade. Introduce normalizeForwardKey() in ImportForwards: a single-dash key has its IP reconstructed from the Family field (4→127.0.0.1, 6→::1), matching exactly what the scan loop will produce for that socket. New-format keys (two dashes) pass through unchanged. Update tests to use the new key format throughout and add TestForwarder_ImportForwards_NormalizesOldKey covering the upgrade path.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3586.
What changed
Three bugs in
packages/envd/internal/port/forward.gocaused services that bind to an IPv6 wildcard address (:::PORT) to be silently unreachable from outside the sandbox.Bug A —
::wildcard not in scan filter (forward.go:79)The
ScannerFilterlisted only127.0.0.1,localhost, and::1. On a dual-stack kernel, gRPC and other frameworks default to:::PORT; gopsutil reportsLaddr.IP = "::"for those sockets."::"was not in the filter → port never detected → no socat → service inaccessible.Fix: add
"::"toIPs. Wildcard::listeners are then normalized tofamily=4so socat connects via127.0.0.1rather than[::1](see Bug C).Bug B — port key collision drops one socat (
forward.go:127)Key was
fmt.Sprintf("%d-%d", pid, port). Two connections with the same PID and port (oneAF_INET/127.0.0.1, oneAF_INET6/::1) produced the same key; the second overwrote the first. Only one socat started — whichever family/proc/net/tcpvs/proc/net/tcp6returned first. The other address was silently unreachable.Fix:
fmt.Sprintf("%d-%d-%s", pid, port, ip)— include IP in the key.Bug C —
TCP6:localhostfails on minimal images (forward.go:175)Backend address was
TCP%d:localhost:PORT. Forfamily=6this isTCP6:localhost:PORT, which requires/etc/hoststo contain::1 localhost. Alpine and many stripped images only have127.0.0.1 localhost, so socat's name resolution returns no AAAA record and the connection fails.Fix: literal addresses (
127.0.0.1/[::1]) instead of"localhost".Tests
scanfilter_test.go(new): table-drivenScannerFilter.Matchtests, including"::"wildcard and wrong-state cases.forward_test.go: two new testsTestStartForwarding_WildcardIPv6_NormalizedToFamilyFour— pushes a::connection through the scan loop, assertsfamily=4in the ports map.TestStartForwarding_DualStackKey_TwoEntries— pushes same-PID, same-port127.0.0.1+::1connections, asserts two distinct map entries.Envd version bump
0.6.13 → 0.6.14