Trim obvious comments, add architecture.md stub
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
# IpMixStream Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
`IpMixStream` tunnels IP packets through the Nym mixnet to an IP Packet Router
|
||||
(IPR) exit gateway. It provides a high-level API over a single `MixnetStream`,
|
||||
which handles LP Stream framing and Sphinx packet transport automatically.
|
||||
|
||||
## Data Flow
|
||||
|
||||
```text
|
||||
Client IPR (exit gateway)
|
||||
------ ------------------
|
||||
IpMixStream.send_ip_packet(bytes)
|
||||
IpPacketRequest.to_bytes()
|
||||
MixnetStream.write()
|
||||
LP Stream frame
|
||||
Sphinx packets
|
||||
mixnet ──────────────────> on_reconstructed_message()
|
||||
detect LpFrameKind::Stream
|
||||
strip LP header
|
||||
parse IpPacketRequest
|
||||
write IP packet to TUN
|
||||
──> internet
|
||||
|
||||
internet response arrives on TUN
|
||||
ConnectedClientHandler
|
||||
wrap in IpPacketResponse
|
||||
wrap in LP Stream frame
|
||||
mixnet <────────────────── send via Sphinx/SURBs
|
||||
|
||||
stream router dispatches
|
||||
by stream_id
|
||||
MixnetStream.read()
|
||||
IprListener parses response
|
||||
IpMixStream.handle_incoming()
|
||||
returns Vec<ip_packet_bytes>
|
||||
```
|
||||
|
||||
## Layer Stack
|
||||
|
||||
```text
|
||||
IpMixStream IPR protocol (connect, data, disconnect)
|
||||
MixnetStream AsyncRead + AsyncWrite, LP Stream framing, seq numbers
|
||||
Stream Router Dispatches inbound messages by stream_id
|
||||
MixnetClient Sphinx packet encryption, SURB management
|
||||
Mixnet Entry GW -> Mix1 -> Mix2 -> Mix3 -> Exit GW
|
||||
```
|
||||
|
||||
## LP Stream Framing
|
||||
|
||||
All messages between client and IPR are wrapped in LP Stream frames:
|
||||
|
||||
- **Client -> IPR**: `MixnetStream.write()` wraps each write in an LP Stream
|
||||
Data frame (stream_id, sequence number, payload). The IPR detects
|
||||
`LpFrameKind::Stream` and strips the header before processing.
|
||||
|
||||
- **IPR -> Client**: Both inline responses (connect handshake, pong) and async
|
||||
TUN responses are wrapped in LP Stream frames with the same stream_id. The
|
||||
client's stream router dispatches by stream_id to the correct `MixnetStream`.
|
||||
|
||||
## Connection Lifecycle
|
||||
|
||||
1. `IpMixStream::new(env)` -- discover IPR, connect MixnetClient, open MixnetStream
|
||||
2. `connect_tunnel()` -- send connect request, receive allocated IPs
|
||||
3. `send_ip_packet()` / `handle_incoming()` -- steady-state data transfer
|
||||
4. `disconnect_stream()` -- tear down MixnetClient
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
- **MixnetStream over MixnetClient**: One stream per IPR tunnel. LP framing is
|
||||
handled by MixnetStream internally, no manual frame construction needed.
|
||||
|
||||
- **Multiplexing at IP layer**: Different remote hosts are addressed by IP
|
||||
packet destination headers, not by opening multiple streams.
|
||||
|
||||
- **stream_id threading**: The IPR stores stream_id in each client's
|
||||
`ConnectedClientHandler` so async TUN responses are wrapped in matching LP
|
||||
Stream frames for correct dispatch at the client.
|
||||
@@ -32,10 +32,6 @@ const IPR_CONNECT_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
/// provides ample headroom.
|
||||
const READ_BUF_SIZE: usize = 64 * 1024;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Gateway discovery helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct IprWithPerformance {
|
||||
pub(crate) address: Recipient,
|
||||
@@ -125,10 +121,6 @@ async fn get_random_ipr(client: nym_http_api_client::Client) -> Result<Recipient
|
||||
Ok(ipr_address)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// IpMixStream
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// A bidirectional tunnel for sending and receiving IP packets through the mixnet.
|
||||
///
|
||||
/// Wraps a [`MixnetStream`] (opened to an IPR exit gateway) and provides a
|
||||
@@ -156,7 +148,6 @@ pub struct IpMixStream {
|
||||
client: MixnetClient,
|
||||
/// Parses incoming IPR protocol responses.
|
||||
listener: IprListener,
|
||||
/// Reusable read buffer to avoid allocating per `handle_incoming()` call.
|
||||
read_buf: Vec<u8>,
|
||||
allocated_ips: Option<IpPair>,
|
||||
connection_state: ConnectionState,
|
||||
@@ -226,8 +217,6 @@ impl IpMixStream {
|
||||
let (request, request_id) = IpPacketRequest::new_connect_request(None);
|
||||
debug!("Sending connect request with ID: {}", request_id);
|
||||
|
||||
// Write the connect request — MixnetStream wraps it in an LP Stream
|
||||
// Data frame automatically.
|
||||
let request_bytes = request.to_bytes()?;
|
||||
self.stream
|
||||
.write_all(&request_bytes)
|
||||
@@ -353,12 +342,10 @@ impl IpMixStream {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the allocated IP addresses for this tunnel.
|
||||
pub fn allocated_ips(&self) -> Option<&IpPair> {
|
||||
self.allocated_ips.as_ref()
|
||||
}
|
||||
|
||||
/// Check if the tunnel is currently connected.
|
||||
pub fn is_connected(&self) -> bool {
|
||||
self.connection_state == ConnectionState::Connected
|
||||
}
|
||||
|
||||
@@ -230,7 +230,6 @@ impl MixnetMessageSinkTranslator for ToIprDataResponse {
|
||||
&self,
|
||||
bundled_ip_packets: &[u8],
|
||||
) -> std::result::Result<InputMessage, nym_sdk::Error> {
|
||||
// Create an IPR packet response that the recipient can understand
|
||||
let response_packet = create_ip_packet_response(bundled_ip_packets, self.client_version)?;
|
||||
|
||||
// Optionally wrap in LP Stream frame for stream-mode clients
|
||||
@@ -249,7 +248,6 @@ impl MixnetMessageSinkTranslator for ToIprDataResponse {
|
||||
response_packet
|
||||
};
|
||||
|
||||
// Wrap in a mixnet input message
|
||||
let input_message =
|
||||
crate::util::create_message::create_input_message(&self.send_to, final_packet)
|
||||
.with_max_retransmissions(0);
|
||||
|
||||
@@ -466,13 +466,8 @@ impl MixnetListener {
|
||||
|
||||
/// Handle LP Stream-framed messages.
|
||||
///
|
||||
/// LP Stream frames carry IPR requests in the frame content. We parse the
|
||||
/// stream attributes, process the inner payload, and handle responses inline
|
||||
/// (wrapped in LP Stream frames) — the same pattern used by the KCP handler.
|
||||
///
|
||||
/// The `current_stream_id` field is set during processing so that connect
|
||||
/// handlers can pass it to `ConnectedClientHandler`, which wraps async TUN
|
||||
/// responses in LP Stream frames too.
|
||||
/// Parses stream attributes, processes the inner IPR payload, and handles
|
||||
/// responses inline (wrapped in LP Stream frames) — same pattern as KCP.
|
||||
async fn on_stream_frame(
|
||||
&mut self,
|
||||
reconstructed: ReconstructedMessage,
|
||||
@@ -482,7 +477,6 @@ impl MixnetListener {
|
||||
reconstructed.message.len()
|
||||
);
|
||||
|
||||
// Parse stream attributes from the LP header
|
||||
let header = LpFrameHeader::parse(&reconstructed.message)
|
||||
.map_err(|e| IpPacketRouterError::Other(format!("Invalid LP frame header: {e}")))?;
|
||||
let attrs = StreamFrameAttributes::parse(&header.frame_attributes).map_err(|e| {
|
||||
@@ -508,7 +502,6 @@ impl MixnetListener {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
// Strip LP header, process inner payload as IPR message
|
||||
let inner_reconstructed = ReconstructedMessage {
|
||||
message: payload.to_vec(),
|
||||
sender_tag: reconstructed.sender_tag,
|
||||
|
||||
Reference in New Issue
Block a user