Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

P2P Reverse Proxy

File: kinetic-host/src/proxy.rs Crate: kinetic-host | Stage: 13


What Is This?

The reverse proxy is what makes the host actually serve content. When a Kinetic user’s daemon makes a P2P ProxyRequest to saif.kin, that request travels over libp2p to this host’s running process. proxy.rs receives it, translates it into a standard HTTP request to the local web server, and routes the HTTP response back over P2P.

This is the “server side” of the proxy pair — kinetic-daemon/src/proxy/ is the “client side” that sends requests.


How It Works

handle_incoming_proxy_requests — the main loop

-> See: kinetic-host/src/proxy.rs — Lines 83–149

Receives (ProxyRequest, ResponseChannel<ProxyResponse>) tuples from the incoming_rx mpsc channel. For each request, spawns a new Tokio task so multiple requests are handled concurrently (not sequentially).

Path validation (before any HTTP call):

#![allow(unused)]
fn main() {
let decoded_path = percent_encoding::percent_decode_str(&req.path)
    .decode_utf8()
    .unwrap_or(...);
if decoded_path.contains("..") || !decoded_path.starts_with('/') {
    // Return 400 Bad Request
}
}

Warning

Percent-decodes the path first (to catch %2E%2E as ..), then checks for path traversal attempts. Paths not starting with / are also rejected.

Body size check:

#![allow(unused)]
fn main() {
if req.body.len() > LIMITS_PROXY_MAX_BODY_BYTES {
    // Return 413 Payload Too Large
}
}

Important

Checked before calling forward_request. Prevents the local backend from receiving oversized P2P payloads.


forward_request — HTTP translation

-> See: kinetic-host/src/proxy.rs — Lines 10–76

Constructs a reqwest HTTP request from the ProxyRequest:

  • Parses req.method as an HTTP method, defaults to GET if unrecognized.
  • Copies all headers from the P2P request except Host (stripped and replaced with the local backend’s host:port). Without stripping, the backend would see Host: saif.kin and reject the request.
  • Sets req.body as the HTTP body.

Response body streaming with size cap:

#![allow(unused)]
fn main() {
let mut stream = res.bytes_stream();
while let Some(chunk_res) = stream.next().await {
    body.extend_from_slice(&chunk);
    if body.len() > LIMITS_PROXY_MAX_BODY_BYTES {
        // Truncate, set status = 502
        break;
    }
}
}

The backend response is streamed in chunks. If the total exceeds LIMITS_PROXY_MAX_BODY_BYTES (5MB), the body is discarded and a 502 is returned. This prevents the host from relaying gigabyte responses over P2P.

Error → 502: If reqwest fails to connect to the backend at all (backend not running), returns 502 Bad Gateway with an error message including the failed port number.


reqwest::Client configuration

#![allow(unused)]
fn main() {
reqwest::Client::builder().no_proxy().build().unwrap_or_default()
}

Note

.no_proxy() prevents the HTTP client from accidentally routing through the system proxy (which on a host machine might be Kinetic’s own PAC). Without this, you’d get infinite loops.


Tests

proxy_handles_chaotic_requests_gracefully (proptest):

-> See: kinetic-host/src/proxy.rs — Lines 157–179

Generates random HTTP methods, paths, and bodies and calls forward_request() pointing at port 65534 (guaranteed dead). Asserts:

  • Never panics.
  • Always returns 502 (since the backend is dead).
  • Response body contains "Bad Gateway".

Full integration tests are in proxy_tests.rs (a separate file, 427 lines) — those spin up a real Axum backend server, send real P2P-format requests through forward_request, and assert the correct HTTP response is mirrored back.


Quick Reference

CheckWhereResponse on Fail
Path traversal (..)Before HTTP call400 Bad Request
Path doesn’t start with /Before HTTP call400 Bad Request
Request body too largeBefore HTTP call413 Payload Too Large
Backend unreachableIn forward_request502 Bad Gateway
Backend response too largeDuring streaming502 Payload Too Large
Host header from P2PIn forward_requestStripped + replaced