Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

envd/port-forwarder: IPv6 wildcard (:::PORT) listeners silently ignored; dual-stack port key collision drops one socat

Abierto
#3,586 0 comentarios 0 reacciones 0 asignados Ver en GitHub

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

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 :::PORT on any dual-stack host
  • FastAPI / uvicorn with --host ::, Go net.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

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de e2b-dev/runtime

Todos los issues de e2b-dev/runtime

Issues similares

Más issues de Go

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.