Forwards a permitted connection to its destination (005 T060a9,
contracts/egress.md).
Why this is a module and not three lines in the accept loop
Egress.Pool's relay/2 — since removed, along with the rest of that module's
socket handling and the name itself — was a placeholder that logged and closed. That was
the honest shape while the netns did not exist: it denies something the policy
allows, which fails closed and is visible as the "permitted destination is
reachable" check not passing. It was never a false pass.
But it made two conformance checks unclosable, and separating the forwarding
from the socket accept loop is what makes the forwarding testable. Off-Linux
every connection to the acceptor dies at OriginalDst.read/1, so a test
driving the listener never reaches this code at all — the same vacuity
acceptor_transport_test.exs documents for the allowlist. splice/3 takes two
ordinary sockets, so its behaviour is reachable on any host.
⚠️ The bug direction that matters here
Every other component in this subsystem fails safe when it fails: a broken
decoder refuses, a missing policy denies, an unsupervised pool denies. The
relay is the one place where the natural bug goes the other way. Code that
forwards on an error path — a destination that could not be connected, a
socket in an unexpected state, a recv that returned something unhandled —
is a boundary that stops enforcing exactly when something is wrong with it.
So: this module never opens a socket it was not told to open, and never
continues past an error. decide/3 has already permitted this one
destination; the relay's only job is to carry bytes to it and to stop when
anything at all goes wrong. There is no retry, no fallback destination, and
no path where a failure results in more reachability than a success.
Both halves, and why the pair is torn down together
A half-duplex relay forwards the request and drops the response, which from inside the sandbox reads as a slow destination rather than a broken proxy — and passes every denial check. Both directions are carried, and either side closing tears down both, because a socket left open to a destination that is gone is a descriptor leak whose only symptom is the acceptor failing to accept long after and nowhere near the cause.
Summary
Types
Where a permitted connection is headed.
Types
@type destination() :: {:inet.ip4_address(), :inet.port_number()}
Where a permitted connection is headed.
Functions
@spec splice(:gen_tcp.socket(), destination(), keyword()) :: :ok | {:error, term()}
Connects to destination and carries bytes both ways until either side ends.
Returns :ok once the pair is torn down, and {:error, reason} if the
destination could not be reached — in which case sandbox_socket is closed
before returning, so the sandbox sees a refusal rather than a hang.
⚠️ sandbox_socket is closed on every path out of this function. From
inside the sandbox a closed socket is what a denied or unreachable
destination looks like (FR-011a), and leaving it open on an error path
turns a refusal into an indefinite hang that the conformance probe scores as
a timeout rather than a refusal.
@spec upstream_mark() :: non_neg_integer()
The SO_MARK value splice/3 asks NetnsSocket.socket/2 to set.
Public only so acceptor_mark_wiring_test.exs can assert that the mark
used here is the one the redirect exempts. Asserting on a duplicate literal in
the test would pass while the socket carried something else, which is
precisely the defect being guarded against.
⚠️ upstream_connect/4 below calls this function, not acceptor_mark/0
directly, and the indirection is the whole point rather than a style. It was
briefly inlined, and the test kept passing while proving nothing: it compared
Netns.acceptor_mark() to Netns.acceptor_mark() and would have stayed green
with the relay setting any value at all, or none. A seam nothing reads is a
seam that has stopped seaming.