# flare.http.proxy_protocol

> A module of flare_mlake's API.

PROXY protocol v1 + v2 parser (HAProxy spec).

The PROXY protocol is the de-facto wire shape for forwarding the
*original* client TCP endpoint through a load balancer (HAProxy,
AWS NLB, GCP TCP LB, Cloudflare Spectrum, ...) to the origin
server. Without it the origin only sees the LB's IP — every access
log, every per-IP rate limiter, every geo decision is wrong.

The protocol lives in front of the application data: the LB writes
a small header right after the TCP accept on the origin's listener,
then forwards the raw TLS / HTTP bytes unchanged. The origin sniffs
the first few bytes, parses the header, populates ``Request.peer``
with the original client endpoint, and then proceeds with its
normal handshake / parse on the bytes that follow the header.

flare ships strict, fuzz-clean parsers for both wire shapes:

- **v1 (text)** — ``"PROXY TCP4 src dst sport dport
"`` (or
  ``TCP6`` / ``UNKNOWN``). Variable length, terminated by CRLF,
  spec-mandated maximum 107 bytes.
- **v2 (binary)** — 12-byte signature ``

 
QUIT
``
  followed by 1-byte version+command, 1-byte family+protocol,
  2-byte big-endian payload length, then a fixed-shape address
  block plus optional TLVs. Total length is always
  ``16 + payload_length`` bytes.

Public surface:

- :class:`ProxyHeader` — the parsed header (source ``SocketAddr`` +
  destination ``SocketAddr`` + ``consumed: Int`` byte count to
  skip before reading application data).
- :class:`ProxyParseError` — typed error raised on malformed input.
  Carries ``version`` (1, 2, or 0 for the version-detecting
  wrapper), ``position`` (byte offset where the error was
  detected; -1 if not applicable), and ``what`` (a stable
  human-readable phrase for greppable logs). Defensive: a hostile
  peer that thinks it's behind a PROXY-protocol-aware LB but
  actually isn't must not be able to inject log entries or spoof
  source IPs, so the parser refuses any non-conforming input
  rather than guessing.
- :func:`parse_proxy_v1` / :func:`parse_proxy_v2` — strict parsers
  that fail closed on any spec violation.
- :func:`parse_proxy_protocol` — version-detecting wrapper. Sniffs
  the first 12 bytes (or fewer) and dispatches to the right
  parser. Returns ``None`` on a buffer that's too short to decide
  yet (keep reading).

The reactor integration (``ServerConfig.trust_proxy_protocol``)
opt-in is a follow-up; this module ships the parsers
+ tests + fuzz harness independently so the wire shapes can be
audited and reused.

References:
- HAProxy 2.8 PROXY protocol spec:
  https://www.haproxy.org/download/2.8/doc/proxy-protocol.txt

## Structs

### `ProxyParseError`

```mojo
struct ProxyParseError
```

Typed parse error raised by :func:`parse_proxy_v1` / :func:`parse_proxy_v2` / :func:`parse_proxy_protocol`.

Fields:

- ``version`` — wire-shape version this error applies to:
  ``1`` (v1 text), ``2`` (v2 binary), or ``0`` (version-
  detecting wrapper rejected the buffer before committing
  to either shape).
- ``position`` — byte offset in the input buffer where the
  violation was detected, or ``-1`` if not byte-positional
  (e.g. token-level v1 errors).
- ``what`` — a stable human-readable phrase that callers
  can grep on. Stable across patch versions so log
  regressions are obvious.

Implements: `Copyable`, `Movable`, `Writable`

Fields:

- `version: Int`
- `position: Int`
- `what: String`

#### `write_to`

```mojo
def write_to[W: Writer](self, mut writer: W)
```

### `ProxyHeader`

```mojo
struct ProxyHeader
```

A parsed PROXY protocol header.

Fields:
    source: The original client endpoint (the IP + port the LB
        saw on its public-facing socket).
    destination: The endpoint the client *thought* it was
        connecting to (the LB's listener address). Useful for
        origin servers that multiplex on the LB's port.
    consumed: Byte count to skip past in the inbound stream
        before resuming application-protocol read. Always > 0
        when this header was successfully parsed.
    is_local: ``True`` for v2 PROXY-LOCAL frames (LB health
        checks); the ``source`` / ``destination`` fields are
        zero-initialised in this case and the application
        should fall back to ``getpeername(2)`` for the peer
        address. Always ``False`` for v1.

Implements: `Copyable`, `Movable`

Fields:

- `source: SocketAddr`
- `destination: SocketAddr`
- `consumed: Int`
- `is_local: Bool`

## Functions

### `parse_proxy_v1`

```mojo
def parse_proxy_v1(buf: Span[UInt8]) -> Optional[ProxyHeader]
```

Parse a HAProxy PROXY protocol v1 (text) header from ``buf``.

Returns ``None`` if the buffer doesn't yet contain the
terminating CRLF (caller should keep reading and retry).

Returns the parsed :class:`ProxyHeader` on success.

Raises :class:`ProxyParseError` (``version=1``) on:

- Missing ``"PROXY "`` prefix (wrong wire shape; v2 sniff
  should have caught this earlier).
- Header > 107 bytes without CRLF (HAProxy §2.1 cap).
- Unknown protocol token (must be ``TCP4``, ``TCP6``, or
  ``UNKNOWN``).
- Malformed IP / port tokens for ``TCP4`` / ``TCP6``.
- IPv4 address in a ``TCP6`` header (or vice versa).
- Trailing whitespace or a missing CR before the LF.

### `parse_proxy_v2`

```mojo
def parse_proxy_v2(buf: Span[UInt8]) -> Optional[ProxyHeader]
```

Parse a HAProxy PROXY protocol v2 (binary) header from ``buf``.

Returns ``None`` if the buffer is shorter than ``16 +
payload_length`` (caller should keep reading).

Raises :class:`ProxyParseError` (``version=2``) on:

- Missing 12-byte signature.
- Version field ``!= 2`` (HAProxy §2.2).
- Command not in ``{LOCAL=0, PROXY=1}``.
- Unsupported family.
- Family + payload-length mismatch
  (``INET`` requires >= 12, ``INET6`` requires >= 36).

### `parse_proxy_protocol`

```mojo
def parse_proxy_protocol(buf: Span[UInt8]) -> Optional[ProxyHeader]
```

Sniff the wire shape and dispatch to v1 or v2.

Returns ``None`` when the buffer is too short to decide.
Raises :class:`ProxyParseError` (``version=0`` for the
"no signature found" case; otherwise ``version=1`` or
``version=2`` from the underlying parser) on a buffer that
is long enough to commit to a wire shape but fails strict
parsing of that shape.
