P2P Proxy Inbound Requests
Crate: kinetic-daemon Stage: 9 Reading time: 15 minutes Depends on: 15_proxy_http.md (or HTTP proxy concepts), kinetic-network (Stage 8)
What Is This?
This file implements the inbound side of the Kinetic proxy system.
When another node in the P2P network wants to access a service hosted on your local machine, it sends a ProxyRequest through the libp2p network. This module is responsible for listening to those incoming requests.
Once a request is received, it acts as a gatekeeper and a translator. It validates the request to ensure it isn’t malicious—blocking things like path traversal attacks or excessively large payloads that could crash the daemon. After the request passes these stringent security checks, the module translates the P2P message into a standard HTTP request.
It then acts as an HTTP client, forwarding that request to a standard HTTP server running on your local machine. This local backend could be anything: a local web application, a REST API, or a media server.
Finally, it receives the HTTP response from your local backend, packages it back into a ProxyResponse, and sends it over the P2P network to the remote peer.
In short: it is the secure bridge that exposes your local web services to the Kinetic P2P network, ensuring that the network remains isolated from arbitrary filesystem or system access. It makes sure that interacting with the decentralized network feels exactly like interacting with standard web infrastructure for the local application.
Why Kinetic Needs This
For the Kinetic network to function as a truly distributed, decentralized application platform, nodes must have a robust mechanism to serve content, applications, or APIs to each other. They need to do this without relying on public IP addresses, centralized DNS, or traditional web hosting infrastructure. This is the core promise of a P2P network: direct, peer-to-peer interaction.
However, providing remote peers with raw socket access to our local machine is not an option. That would introduce massive security vulnerabilities, allowing any node on the network to probe our local ports, exploit internal services, or worse. Kinetic requires a secure, controlled choke-point between the untrusted P2P network and the trusted local execution environment.
This module provides exactly that secure choke-point. It ensures several vital protections:
- Path traversal attacks are thwarted:
Requests attempting to use
../(like../../../etc/passwd) are blocked before they ever touch the local filesystem or the backend web server. - Memory exhaustion is prevented: The daemon enforces strict payload size limits on both incoming requests and outgoing responses. This protects the Kinetic node from Out-Of-Memory (OOM) crashes caused by malicious or broken peers sending gigantic payloads.
- Protocol integrity is maintained: Arbitrary or malformed HTTP methods are rejected, ensuring the local backend only sees well-formed requests.
- Server spoofing is mitigated:
The
Hostheader provided by the remote peer is deliberately ignored and rewritten. This ensures the local server accurately sees the request as coming from the proxy interface, preventing virtual-host confusion attacks.
Without this module, safely exposing a local service to the P2P network would be impossible, and the entire peer-to-peer proxy architecture would collapse under the weight of security risks.
How It Works
The core of this module is built around a single asynchronous loop that listens for incoming ProxyRequest messages from the libp2p network and handles them concurrently.
Here is the step-by-step lifecycle of an inbound P2P proxy request as it flows through this system:
-
Listening for Incoming P2P Requests: The
handle_incoming_proxy_requestsfunction is the entry point. It takes atokio::sync::mpsc::Receiver. This receiver channel is fed by the underlyingkinetic-networklayer whenever a peer sends a proxy request over the libp2p request-response protocol. The function loops indefinitely, waiting for messages to arrive on this channel. -> See:kinetic-daemon/src/proxy/p2p.rs— Lines 21 to 22 -
Spawning Concurrent Tasks: For every incoming request, a new asynchronous task is spawned using
tokio::spawn. This is a crucial architectural decision. It ensures that if a local server is slow to respond, it does not block the daemon from processing requests from other peers. Each request is handled in its own isolated execution context. -> See:kinetic-daemon/src/proxy/p2p.rs— Lines 26 to 27 -
Security Validation — Path: Before anything is forwarded, the path is rigorously checked. It must not contain
..(which indicates a path traversal attempt) and it must start with a/. If it fails these checks, the peer receives a 400 Bad Request immediately, and processing stops. -> See:kinetic-daemon/src/proxy/p2p.rs— Lines 28 to 44 -
Security Validation — Payload Size: To prevent memory exhaustion attacks, the daemon checks the size of the incoming request body against a hardcoded limit. This limit is defined by
LIMITS_PROXY_MAX_BODY_BYTES(typically 5MB). Oversized requests are immediately rejected with a 413 Payload Too Large response. -> See:kinetic-daemon/src/proxy/p2p.rs— Lines 45 to 63 -
Security Validation — HTTP Method: The HTTP method string is parsed to ensure it represents a valid HTTP verb (GET, POST, etc.). If the method string is garbage data, it returns a 400 Bad Request. -> See:
kinetic-daemon/src/proxy/p2p.rs— Lines 66 to 82 -
Constructing the Local HTTP Request: If the request is deemed safe, a standard HTTP client (
reqwest) builds a new request directed at the local backend address (e.g.,127.0.0.1:8080). All headers from the peer are copied over, except theHostheader. TheHostheader is overwritten to match the local binding, ensuring the backend server behaves correctly. -> See:kinetic-daemon/src/proxy/p2p.rs— Lines 84 to 94 -
Forwarding and Streaming the Backend Response: The constructed local request is sent to the backend. The response body is read chunk by chunk using an asynchronous stream (
res.bytes_stream()). Crucially, the payload size limit is enforced again on this response body. This prevents a misconfigured or malicious local server from flooding the daemon with a massive file. -> See:kinetic-daemon/src/proxy/p2p.rs— Lines 104 to 118 -
Returning the Final Response to the Peer: Finally, the HTTP response status, headers, and buffered body are bundled back into a
ProxyResponsestruct. This struct is sent back to the original peer via the libp2pResponseChannelprovided by the network layer. -> See:kinetic-daemon/src/proxy/p2p.rs— Lines 140 to 142
Key Pieces
-
handle_incoming_proxy_requestsThis is the primary driver of the inbound proxy system. It loops endlessly, pulling requests from the MPSC channel and delegating them to spawned worker tasks to ensure high concurrency and non-blocking I/O operations. -> Lives at:kinetic-daemon/src/proxy/p2p.rs— Lines 6 to 14 -
tokio::sync::mpsc::ReceiverThe asynchronous channel through whichkinetic-networkdelivers requests. It effectively decouples the raw P2P swarm implementation from the higher-level HTTP processing and validation logic found in the daemon. -> Lives at:kinetic-daemon/src/proxy/p2p.rs— Lines 8 to 11 -
Path Traversal Protection Logic A mandatory security boundary that blocks
..sequences in URL paths. This prevents remote peers from tricking the local web server into navigating outside its designated root directory and serving sensitive files from the host operating system. -> Lives at:kinetic-daemon/src/proxy/p2p.rs— Lines 28 to 44 -
reqwest::ClientThe asynchronous HTTP client used to make the actual local request. It is instantiated once and cloned for each request. Because it uses connection pooling under the hood, cloning it is very efficient and prevents exhausting local sockets. -> Lives at:kinetic-daemon/src/proxy/p2p.rs— Lines 15 and 84 -
Streaming Body Reader (
res.bytes_stream()) Usesfutures_util::StreamExtto read the local server’s response incrementally. By processing chunks, the daemon can keep a running total of the bytes received and abort early if the payload exceeds the strict 5MB safety limit, protecting daemon memory. -> Lives at:kinetic-daemon/src/proxy/p2p.rs— Lines 104 to 118
How This Connects to the Rest of Kinetic
-
CROSS-CRATE:
kinetic-networkThis module relies on the network crate to handle the complex, low-level libp2p request-response protocol. TheNetworkClientandResponseChanneltypes are passed in directly from that crate, treating the network as an abstract transport layer. -
CROSS-CRATE:
kinetic-coreThe maximum payload size (LIMITS_PROXY_MAX_BODY_BYTES) is pulled directly from the core constants crate. This ensures that size limits are unified and consistent across the entire platform, preventing fragmented rules. -
Internal Integration: This file is the exact mirror image of
kinetic-daemon/src/proxy/http.rs. While the HTTP proxy converts local browser requests into P2P messages, this P2P proxy takes those P2P messages and converts them back into HTTP requests for a local server. Together, they form the complete proxy circuit.
Quick Reference
- Core Function:
handle_incoming_proxy_requests - Trigger Condition: A peer sends a libp2p
ProxyRequesttargeted at this node. - Security Check 1: Path cannot contain
... - Security Check 2: Path must begin with
/. - Security Check 3: The HTTP Method must parse correctly.
- Security Check 4: The inbound request body must be <= 5MB.
- Security Check 5: The outbound response body must be <= 5MB.
- Forwarding Target:
http://<bind_ip>:<local_port><path>. - Header Modification:
Hostheader from peer is stripped and rewritten to match the local binding address.
Open Questions / Things to Revisit
-
Streaming vs. Buffering in Memory: Currently, the entire response body from the local server is buffered into a memory vector (
Vec::new()) before being sent back over the P2P network. While the 5MB limit prevents OOM crashes, buffering still wastes memory under heavy load. We should investigate streaming the response directly into the libp2p response channel if the protocol allows it. -
Timeout Handling for reqwest: There does not appear to be an explicit timeout configured on the
reqwestclient when calling the local backend server. If the local server hangs indefinitely, the spawnedtokio::spawntask will live forever, leading to task leaks over time. We should add a strict timeout configuration to thereqwest::ClientBuilderto ensure task completion. -
Status Code Ambiguity (413 vs 502): When the local server returns a response larger than 5MB, the code currently overrides the status to
502 Bad Gateway(with a comment// Or 413). This semantic distinction might confuse remote clients; returning 502 implies the backend is dead or invalid, while 413 implies a specific size violation occurred. We should standardize on the most accurate HTTP status code to improve client debugging.