Skip to main content

Module egress

Module egress 

Source
Expand description

The outbound HTTP policy (kernel spec §7.1; gateway spec §4, §5.7): every request hennery itself sends to the network — the gateway’s proxy, its OAuth calls, Web Push — goes through an EgressClient.

  • Built once, cloned freely. Egress::new builds its reqwest clients once: one per Allowance, and a second, internal-only one for plain http under Allowance::InternalNetwork. Egress and EgressClient are cheap to clone (reqwest::Client is an Arc inside) and share connection pools with their clones. The two allowances never share a pool: a pooled connection skips the resolver, so a public-only request must never ride one an internal-network request opened.
  • No way around the check. The only way to send is EgressClient::send or EgressClient::send_streaming, which run check_url on the request’s URL first. The reqwest::Client inside is never handed out. A caller holding an http::Request (for example, rmcp’s OAuthHttpClient) converts it with Request::try_from and sends it here.
  • The URL check (check_url): https, or plain http to loopback (a loopback literal or localhost); under Allowance::InternalNetwork also plain http to an internal address or a name (plan 8b-ii, below). No credentials in the URL; an IP-literal host must be public under Allowance::PublicOnly. reqwest never asks the resolver about a literal, so this check is the only one a literal gets. url::Url has already normalised the odd IPv4 spellings (2130706433, 0x7f.1, 0177.0.0.1, 127.1) to the address they name.
  • Names are resolved by hennery (the system resolver) and, under PublicOnly, refused when any address is not public — not filtered — so one record pointing inward refuses the name. The connection then goes only to the addresses that were checked. localhost is never looked up: it is 127.0.0.1 and ::1 (RFC 6761), so plain http to it stays on this machine whatever the resolver or hosts file says.
  • Public (is_public): IPv4 outside the special-purpose ranges (this network and 0.0.0.0, RFC 1918, CGNAT, loopback, link-local including 169.254.169.254, IETF protocol assignments, the documentation and benchmarking ranges, 6to4 relay anycast, multicast, reserved and broadcast); IPv6 inside global unicast 2000::/3 and outside its special ranges (2001::/23 with Teredo and benchmarking, documentation 2001:db8::/32 and 3fff::/20, 6to4 2002::/16). So ::, ::1, IPv4-mapped ::ffff:0:0/96, NAT64 64:ff9b::/96 and 64:ff9b:1::/48, unique-local fc00::/7, link-local fe80::/10 and multicast ff00::/8 are never public.
  • Plain http inside the internal network (plan 8b-ii, the operator’s decision of 2026-10-02): only under Allowance::InternalNetwork, and only to internal addresses — RFC 1918, loopback and unique-local IPv6, except AWS’s metadata address fd00:ec2::254. A public address stays https only even then, and so do the addresses that are neither: link-local and that metadata address (cloud metadata services answer plain http there), CGNAT (it may be the carrier’s network), documentation, multicast, 0.0.0.0, IPv4-mapped, NAT64, 6to4 and Teredo (they may lead to a public host). A literal is checked by the URL check; a name goes through a third client, used for nothing but plain http under InternalNetwork, whose resolver refuses the name if any address is not internal.
  • Never a proxy from the environment (HTTP_PROXY, HTTPS_PROXY, ALL_PROXY): it would take the checked connection elsewhere and hand it the request. Never a followed redirect: a 3xx is the caller’s answer.
  • Timeouts (Timeouts): a connect timeout on every connection. EgressClient::send bounds the whole exchange, body included, by the request’s own timeout or else Timeouts::request. EgressClient::send_streaming bounds only the wait for the response head the same way; the caller reads the body for as long as it wants and decides for itself when a stream has been idle too long. A body that fails while it is read (send’s deadline included) fails with a reqwest::Error from Response, not an EgressError: strip its URL with without_url() before logging it.
  • Errors (EgressError) never carry the URL (its path or query can hold a secret), so they are safe to log.
  • Per-caller limits (Limiter): a counter of permits per caller key (the gateway’s is the connection), refusing past its cap with Busy instead of queueing. A caller keeps one for concurrent requests and one for open streams; the gateway answers Busy with 503.

Modules§

header
What a caller needs to build a request and read its answer, so it depends on the kernel, not on reqwest. HTTP header types

Structs§

Busy
A caller is over its cap.
Egress
The policy’s clients: one per Allowance, and the internal-network one with a second, for plain http.
EgressClient
Sends requests under one Allowance; see the module docs.
Limiter
At most cap permits held at once per caller key. Clones share the counts. Only keys holding a permit are tracked, so the table is bounded by what is in flight.
Method
What a caller needs to build a request and read its answer, so it depends on the kernel, not on reqwest. The Request Method (VERB)
Permit
One of a Limiter’s permits; dropping it releases it.
Request
What a caller needs to build a request and read its answer, so it depends on the kernel, not on reqwest. A request which can be executed with Client::execute().
Response
What a caller needs to build a request and read its answer, so it depends on the kernel, not on reqwest. A Response to a submitted Request.
StatusCode
What a caller needs to build a request and read its answer, so it depends on the kernel, not on reqwest. An HTTP status code (status-code in RFC 9110 et al.).
Timeouts
The policy’s timeouts.
Url
What a caller needs to build a request and read its answer, so it depends on the kernel, not on reqwest. A parsed URL record.

Enums§

Allowance
Whether a request may reach non-public addresses.
BodyError
Why read_capped did not return a body.
EgressError
A request that did not get a response.
Refused
Why the policy refused a request.

Functions§

check_url
The URL check every request passes before it is sent; see the module docs. A name is checked later, by the resolver, once it has addresses.
is_https_or_loopback
https, or plain http to loopback: the scheme rule for OAuth’s URLs (gateway spec §4, §5.7), which stays this even for a connection marked “internal network”, whose client check_url lets send plain http to internal addresses (plan 8b-ii). The OAuth caller checks it itself before sending (plan 8f).
is_public
Whether addr is a public address; see the module docs.
read_capped
response’s body, refused past max bytes: a declared length over it is refused before anything is read, and the read stops at the first chunk past it. EgressClient::send bounds a request’s time, not its size; this is the size (the egress plan’s O3), for every caller that reads a whole body (OAuth’s documents and token responses, plan 8f).