Tracked deferral from up --auto-forward (spec 015-auto-forward-ports, task T055; shipped in #188). A throughput optimization for the relay.
Goal / acceptance
Relay no longer pays a docker exec per connection; same external behavior.
Background — what v1 does
crates/core/src/port_forward/relay.rs spawns a fresh docker exec -i <container> <nc|socat|bash /dev/tcp> per accepted host connection. Correct and dependency-light, but every connection pays docker-CLI fork + daemon round-trip + setns() + relay-program fork (~tens of ms + real CPU). Bad under high connection churn (many short HTTP requests, etc.).
Proposed model
One long-lived docker exec carrying many logical streams over its single stdin/stdout, with a tiny framing protocol:
daemon ⇄ ONE `docker exec -i <container> <relay-agent>` (started once per container)
OPEN <stream_id> <port> | DATA <stream_id> <bytes> | CLOSE <stream_id>
Host side allocates a stream_id per connection and demuxes replies; the in-container agent dials 127.0.0.1:<port> per OPEN and frames bytes back.
Hidden issues / gotchas
- Getting the agent into the container is the real blocker. v1 relies on tools already present (
socat/nc/bash). A multiplexer needs a program in the container that speaks the protocol:
- Embedded static binary (musl, few KB,
docker cp'd in) — works on alpine/distroless, but means building, arch-matching (amd64/arm64), versioning, and embedding a second artifact in the deacon distribution + a cp/exec/integrity-check dance. (research.md Decision 3 named this the primary strategy and left the exact form an open sub-decision.)
- Shell-based agent — no new artifact, but fragile/slow and absent on
distroless.
- You now own a protocol: stream IDs, flow control / backpressure (one slow stream must not head-of-line-block others on the shared pipe), half-close semantics, agent crash/restart, clean teardown.
- Must preserve the no-silent-fallback guarantee: if the agent can't be installed, fall back to today's per-connection relay (still works on any image).
Recommended path forward
- Phase 1 (verifiable here): implement the host-side framing/demux + a shell-based in-container agent; route
start_forward through the multiplexer when available, else fall back to per-connection. Add Docker integration tests (concurrent streams, slow-stream isolation, agent death → fallback).
- Phase 2: add the embedded static musl agent (cross-compiled, arch-matched, version-checked,
docker cp'd) so it works on minimal images; wire it into the release build. This is the part that can't be fully exercised in the current sandbox.
- Keep per-connection relay as the permanent fallback.
Notes
Largest of the auto-forward deferrals (a protocol + a shipped artifact, not a contained feature). Relates to #189-style "ship an in-container agent" concerns shared with event-driven detection.
Tracked deferral from
up --auto-forward(spec015-auto-forward-ports, task T055; shipped in #188). A throughput optimization for the relay.Goal / acceptance
Relay no longer pays a
docker execper connection; same external behavior.Background — what v1 does
crates/core/src/port_forward/relay.rsspawns a freshdocker exec -i <container> <nc|socat|bash /dev/tcp>per accepted host connection. Correct and dependency-light, but every connection pays docker-CLI fork + daemon round-trip +setns()+ relay-program fork (~tens of ms + real CPU). Bad under high connection churn (many short HTTP requests, etc.).Proposed model
One long-lived
docker execcarrying many logical streams over its single stdin/stdout, with a tiny framing protocol:Host side allocates a
stream_idper connection and demuxes replies; the in-container agent dials127.0.0.1:<port>perOPENand frames bytes back.Hidden issues / gotchas
socat/nc/bash). A multiplexer needs a program in the container that speaks the protocol:docker cp'd in) — works onalpine/distroless, but means building, arch-matching (amd64/arm64), versioning, and embedding a second artifact in the deacon distribution + a cp/exec/integrity-check dance. (research.mdDecision 3 named this the primary strategy and left the exact form an open sub-decision.)distroless.Recommended path forward
start_forwardthrough the multiplexer when available, else fall back to per-connection. Add Docker integration tests (concurrent streams, slow-stream isolation, agent death → fallback).docker cp'd) so it works on minimal images; wire it into the release build. This is the part that can't be fully exercised in the current sandbox.Notes
Largest of the auto-forward deferrals (a protocol + a shipped artifact, not a contained feature). Relates to #189-style "ship an in-container agent" concerns shared with event-driven detection.