fastn_net/protocol.rs
1//! Protocol multiplexing over P2P connections.
2//!
3//! This module implements a custom protocol multiplexing system over Iroh P2P
4//! connections, deliberately deviating from Iroh's recommended ALPN-per-protocol
5//! approach.
6//!
7//! # Why Not Use Iroh's Built-in ALPN Feature?
8//!
9//! Iroh [recommends using different ALPNs](https://docs.rs/iroh/latest/iroh/endpoint/struct.Builder.html#method.alpns)
10//! for different protocols. However, this approach has a significant limitation:
11//! **each protocol requires a separate connection**.
12//!
13//! ## The Problem with Multiple Connections
14//!
15//! Consider a typical P2P session where an entity might:
16//! - Send periodic pings to check connection health
17//! - Proxy HTTP requests through another entity
18//! - Tunnel TCP connections simultaneously
19//! - Stream real-time data (e.g., during a call while browsing shared files)
20//!
21//! With Iroh's approach, each protocol would need its own connection, requiring
22//! a full TLS handshake for each. ALPN is negotiated during the TLS handshake:
23//!
24//! ```text
25//! Client Hello Message Structure:
26//! ┌─────────────────────────────────────┐
27//! │ Handshake Type: Client Hello (1)    │
28//! │ Version: TLS 1.2 (0x0303)          │
29//! │ Random: dd67b5943e5efd07...        │
30//! │ Cipher Suites: [...]                │
31//! │ Extensions:                         │
32//! │   ALPN Extension:                   │
33//! │     - h2                           │
34//! │     - http/1.1                     │
35//! └─────────────────────────────────────┘
36//! ```
37//!
38//! Creating additional connections means additional:
39//! - TLS handshakes (expensive cryptographic operations)
40//! - Network round trips
41//! - Memory overhead for connection state
42//! - Complexity in connection management
43//!
44//! ## Our Solution: Application-Layer Multiplexing
45//!
46//! We use a single ALPN (`/fastn/entity/0.1`) and multiplex different protocols
47//! over [bidirectional streams](https://docs.rs/iroh/latest/iroh/endpoint/struct.Connection.html#method.open_bi)
48//! within that connection:
49//!
50//! ```text
51//! Single Connection between Entities
52//!     ├── Stream 1: HTTP Proxy
53//!     ├── Stream 2: Ping
54//!     ├── Stream 3: TCP Tunnel
55//!     └── Stream N: ...
56//! ```
57//!
58//! Each stream starts with a JSON protocol header identifying its type.
59//!
60//! # The Protocol "Protocol"
61//!
62//! ## Stream Lifecycle
63//!
64//! 1. **Client entity** opens a bidirectional stream
65//! 2. **Client** sends a JSON protocol header (newline-terminated)
66//! 3. **Server entity** sends ACK to confirm protocol support
67//! 4. Protocol-specific communication begins
68//!
69//! ## Protocol Header
70//!
71//! The first message on each stream is a JSON-encoded [`ProtocolHeader`] containing:
72//! - The [`Protocol`] type (Ping, Http, Tcp, etc.)
73//! - Optional protocol-specific metadata
74//!
75//! This allows protocol handlers to receive all necessary information upfront
76//! without additional negotiation rounds.
77//!
78//! # Future Considerations
79//!
80//! This multiplexing approach may not be optimal for all use cases. Real-time
81//! protocols (RTP/RTCP for audio/video) might benefit from dedicated connections
82//! to avoid head-of-line blocking. This design decision will be re-evaluated
83//! based on performance requirements.
84#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq)]
85pub enum Protocol {
86    /// client can send this message to check if the connection is open / healthy.
87    Ping,
88    /// client may not be using NTP, or may only have p2p access and no other internet access, in
89    /// which case it can ask for the time from the peers and try to create a consensus.
90    WhatTimeIsIt,
91    /// client wants to make an HTTP request to a device whose ID is specified. note that the exact
92    /// ip:port is not known to peers, they only the "device id" for the service. server will figure
93    /// out the ip:port from the device id.
94    Http,
95    HttpProxy,
96    /// if the client wants their traffic to route via this server, they can send this. for this to
97    /// work, the person owning the device must have created a SOCKS5 device, and allowed this peer
98    /// to access it.
99    Socks5,
100    Tcp,
101    // TODO: RTP/"RTCP" for audio video streaming
102}
103
104/// Single ALPN protocol identifier for all fastn entity connections.
105///
106/// Each fastn instance is called an "entity" in the P2P network. Unlike Iroh's
107/// recommended approach of using different ALPNs for different protocols, we use
108/// a single ALPN and multiplex protocols at the application layer. This avoids
109/// the overhead of multiple TLS handshakes when entities need to use multiple
110/// protocols (e.g., HTTP proxy + TCP tunnel + ping).
111///
112/// See module documentation for detailed rationale.
113pub const APNS_IDENTITY: &[u8] = b"/fastn/entity/0.1";
114
115/// Protocol header with optional metadata.
116///
117/// Sent at the beginning of each bidirectional stream to identify
118/// the protocol and provide any protocol-specific metadata.
119#[derive(Debug)]
120pub struct ProtocolHeader {
121    pub protocol: Protocol,
122    pub extra: Option<String>,
123}
124
125impl From<Protocol> for ProtocolHeader {
126    fn from(protocol: Protocol) -> Self {
127        Self {
128            protocol,
129            extra: None,
130        }
131    }
132}