envd/port-forwarder: IPv6 wildcard (:::PORT) listeners silently ignored; dual-stack port key collision drops one socat
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 68/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Tranquilo
- Stack tecnológico
- go, linux
- Área
- infrastructure, networking
Línea de trabajo
Comienza con packages/envd/internal/port/forward.go y scanfilter.go, y después lee el manejo de conexiones descrito en scan.go. Reproduce listeners en ::, 127.0.0.1 y ::1, y rastrea la detección, la generación de claves y la selección del backend de socat. La tarea está terminada cuando se detectan los servicios wildcard de IPv6, las entradas de doble pila no se fusionan y el reenvío de loopback de IPv6 funciona sin resolución de nombres de host.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Symptom
Case A — a user starts a gRPC server (or any modern framework that defaults to IPv6 wildcard binding) inside a sandbox. The service is running, but the port is never accessible from outside:
# inside sandbox
python -m grpc_tools ... &
ss -tlnp | grep 50051
# LISTEN 0 4096 *:50051 *:* ← kernel reports dual-stack :::50051
# from outside — never connects
grpcurl -plaintext <sandbox-host>:50051 list
# timeout / connection refused
Case B — a service listening on both 127.0.0.1:PORT and ::1:PORT gets only one socat. Which address socat connects to depends on the nondeterministic order that /proc/net/tcp and /proc/net/tcp6 entries are returned. If socat picks the wrong family the port becomes inaccessible with no error.
Root cause
Bug A — :::PORT wildcard not in the scan filter
packages/envd/internal/port/forward.go:79:
&ScannerFilter{
IPs: []string{"127.0.0.1", "localhost", "::1"},
State: "LISTEN",
},
packages/envd/internal/port/scanfilter.go:20:
ipMatch := slices.Contains(sf.IPs, proc.Laddr.IP)
net.Connections("tcp") reads both /proc/net/tcp and /proc/net/tcp6 (confirmed by the comment in scan.go:42). For an IPv6 wildcard socket, the kernel reports Laddr.IP = "::". "::" is not in the filter list → the port is never detected → no socat is started → service is inaccessible.
With IPv6 enabled in the guest (ipv6.disable=0, see #3585), frameworks that auto-select the bind address on a dual-stack kernel produce :::PORT rather than 0.0.0.0:PORT:
| Framework / runtime | Default bind | Laddr.IP reported |
Detected? |
|---|---|---|---|
| gRPC-Go, gRPC-Python | :::PORT |
"::" |
❌ |
Node.js net.createServer().listen(PORT) on dual-stack |
:::PORT |
"::" |
❌ |
Python socket.AF_INET6 with IPV6_V6ONLY=0 |
:::PORT |
"::" |
❌ |
Python uvicorn --host 0.0.0.0 |
0.0.0.0:PORT |
(IPv4 only) | ❌ (expected) |
127.0.0.1:PORT |
127.0.0.1:PORT |
"127.0.0.1" |
✅ |
::1:PORT |
::1:PORT |
"::1" |
✅ |
Bug B — port key collision drops one socat for dual-stack services
packages/envd/internal/port/forward.go:127:
key := fmt.Sprintf("%d-%d", p.Pid, p.Laddr.Port)
When a service listens on both 127.0.0.1:PORT (AF_INET) and ::1:PORT (AF_INET6), both ConnectionStat entries share the same PID and port number. Both pass the filter. The scan loop processes them sequentially:
Iteration 1: key="PID-PORT" not in map → create PortToForward{family=4}, start socat TCP4:localhost:PORT
Iteration 2: key="PID-PORT" already in map → just mark state=FORWARD, skip startPortForwarding
Only one socat is started, for whichever family /proc/net/tcp vs /proc/net/tcp6 returns first. If the app only responds on the skipped family the port silently breaks. The entry order is not guaranteed.
Bug C (secondary) — TCP6:localhost resolution fails on minimal images
packages/envd/internal/port/forward.go:175:
fmt.Sprintf("TCP%d:localhost:%v", p.family, p.port)
For family=6 socat uses TCP6:localhost:PORT. This requires /etc/hosts to contain ::1 localhost. Alpine Linux and many stripped base images ship only 127.0.0.1 localhost, so socat's name resolution returns no AAAA entry → connection fails → ::1 listeners are inaccessible even though the filter correctly detected them.
Why it matters
- gRPC is the most common affected framework — its default listener is
:::PORTon any dual-stack host - FastAPI / uvicorn with
--host ::, Gonet.Listen("tcp", ":PORT")on dual-stack kernels, all produce:::PORT - These services start and appear healthy inside the sandbox, but are completely unreachable from outside — no error, just a silent port forwarding gap
Proposed fix
Fix A — add "::" to the filter and normalize wildcard connections to the IPv4 path:
// forward.go:79
IPs: []string{"127.0.0.1", "localhost", "::1", "::"},
// forward.go:148 — wildcard binds accept both families; connect via IPv4 localhost
family: func() uint32 {
if p.Laddr.IP == "::" {
return 4
}
return familyToIPVersion(p.Family)
}(),
Fix B — include the IP in the port key to avoid collision:
// Before
key := fmt.Sprintf("%d-%d", p.Pid, p.Laddr.Port)
// After
key := fmt.Sprintf("%d-%d-%s", p.Pid, p.Laddr.Port, p.Laddr.IP)
Fix C — use literal addresses instead of hostname resolution:
// Before
fmt.Sprintf("TCP%d:localhost:%v", p.family, p.port)
// After
backendAddr := "127.0.0.1"
if p.family == 6 {
backendAddr = "[::1]"
}
fmt.Sprintf("TCP%d:%s:%v", p.family, backendAddr, p.port)
Note: fixing #3585 (ipv6.disable=1) eliminates the condition that causes dual-stack :::PORT binding in the first place — on an IPv4-only kernel the same bind("::") call falls back to 0.0.0.0. However Bugs B and C are independently correctness issues that should be fixed regardless of the IPv6 kernel setting.
- Lenguaje dominante
- Go
- Estrellas
- 1.6k
- Forks
- 438
- Métricas de merge de PR
- Sin PR fusionados en 30 d
Guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de e2b-dev/runtime
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
-
sandbox cache: StartRemoving state transition not broadcast, all allocations see stale Running state Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 86/100
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 86/100
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 86/100
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
Todos los issues de e2b-dev/runtime
Issues similares
-
agentic-workflows
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
-
agentic-workflows
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
microsoft/agent-framework-go#1179 ·
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
-
[Bug]: OLLAMA_KEEP_ALIVE="5m" / "24h" crashes Ollama embedding and vision models with ValueError Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
infiniflow/ragflow#20223 · 1 reacción ·
-
bug needs triage pkg/translator/faro
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
open-telemetry/opentelemetry-collector-contrib#51484 · 1 comentario ·