Freenet Transport Protocol (FrTP)

Note: This document is a work in progress and is subject to change, it is currently out-of-sync with the codebase and should be updated to reflect the current state of the codebase once it has stabilized.

Introduction

The Freenet Transport Protocol (FrTP) is a UDP-based system designed to ensure reliable and encrypted message transmission. This document outlines the key elements of FrTP, including connection establishment, message handling, and rate limiting.

Overview

  • Firewall Traversal: FrTP allows peers behind firewalls to establish direct connections.
  • Security: All messages are encrypted using AES128GCM, with RSA public key exchange for connection establishment, should effectively thwart man-in-the-middle attacks.
  • Streaming: Large messages can be streamed, meaning that a peer can start forwarding data before the entire message is received.
  • Covert: FrTP can run on any UDP port and FrTP packets look like random data, although more sophisticated analysis of packet timing and size could be used to identify FrTP traffic. FrTP can't be port-scanned as it won't respond to packets unless encrypted with the peer's public key.
  • Efficient: FrTP is designed to minimize bandwidth usage, with rate limiting and confirmation message batching.

Connection Establishment

Scenario 1: Both Peers Behind NAT

This describes how to establish one side of a two-way connection, allowing Bob to send messages to Alice. The process is symmetric in the other direction.

Actors

  • Alice and Bob are both peers behind firewalls.

Terminology

  • Bob_public_key: Bob's RSA public key.
  • Bob_private_key: Bob's RSA private key.
  • Alice_inbound_symmetric_key: AES128GCM symmetric key generated by Alice, used for decrypting inbound messages from Bob.
  • hello_message(A->B): Message sent from Alice to Bob, containing Alice_inbound_symmetric_key and a u16 protocol version number, encrypted using B_public_key.
  • hello_ack(B->A): Message sent from Bob to Alice acknowledging hello_message(A->B), encrypted using Alice_inbound_symmetric_key.

Steps

  1. Key Generation: Alice generates a random AES128GCM symmetric key, called Alice_inbound_symmetric_key.

  2. Outbound Hello Message: Alice encrypts Alice_inbound_symmetric_key with Bob_public_key and a u16 protocol version number with Peer B's public key, to create hello_message(A->B).

  3. Sending Outbound Hello: Alice repeatedly sends hello_message(A->B) every 200ms until a hello_ack(B->A) from Bob is received or a 5-second timeout occurs, indicating connection failure.

  4. Receiving Inbound Hello: Bob receives hello_message(A->B) and decrypts it using Bob_private_key. If the protocol version is not supported, then Bob sends a hello_ack(B->A) with an error code and terminates the connection.

  5. Hello Acknowledgement: Upon receiving hello_ack(B->A), Alice stops sending hello_message(A->B) and the inbound side of the connection is established.

  6. Unexpected Hello Messages: If Bob receives a hello_message(A->B) from Alice after it has already sent a hello_ack(B->A), then it should resend the hello_ack(B->A) and otherwise ignore the message (this may occur if the initial hello_ack(B->A) is lost).

Scenario 2: Peer behind NAT connects to Gateway peer

Actors

  • Alice is a peer behind a firewall, Gateway isn't behind a firewall and is configured to act as a gateway peer for new peers to assimiate into the network.

Terminology

  • Gateway_public_key: Gateway's RSA public key.
  • Gateway_private_key: Gateway's RSA private key.
  • Alice_bidirectional_symmetric_key: AES128GCM symmetric key generated by Alice, used for encrypting and decrypting messages to/from Gateway.

Steps

  1. Key Generation: Alice generates a random AES128GCM symmetric key, called Alice_bidrectional_symmetric_key.

  2. Outbound Hello Message: Alice encrypts Alice_bidrectional_symmetric_key with Gateway_public_key and a u16 protocol version number with Gateway's public key, to create hello_message(A->G).

  3. Sending Outbound Hello: Alice repeatedly sends hello_message(A->B) every 200ms until a hello_ack(G->A) from Gateway is received or a 5-second timeout occurs, indicating connection failure.

  4. Receiving Inbound Hello: Gateway receives hello_message(A->G) and decrypts it using Gateway_private_key. If the protocol version is not supported, then Gateway sends a hello_ack(G->A) with an error code and terminates the connection, otherwise it sends a hello_ack(G->A) to Alice.

  5. Hello Acknowledgement: Upon receiving hello_ack(G->A), Alice stops sending hello_message(A->G), and the the connection is established, Alice should use Alice_bidirectional_symmetric_key for both encryption and decryption of packets sent to and received from Gateway.

Keep-Alive Protocol

To maintain an open connection, keep_alive messages are exchanged every 30 seconds. A connection is terminated if a peer fails to receive any message within 120 seconds.

Symmetric Message Schema

#![allow(unused)]
fn main() {
pub(super) struct SymmetricMessage {
    pub packet_id: PacketId,
    pub confirm_receipt: Vec<PacketId>,
    pub payload: SymmetricMessagePayload,
}

pub(super) enum SymmetricMessagePayload {
    AckConnection {
        // if we successfully connected to a remote we attempt to connect to initially
        // then we return our TransportPublicKey so they can enroute other peers to us
        result: Result<(), Cow<'static, str>>,
    },
    GatewayConnection {
        // a gateway acknowledges a connection and returns the private key to use
        // for communication
        key: [u8; 16],
    },
    ShortMessage {
        payload: MessagePayload,
    },
    StreamFragment {
        stream_id: StreamId,
        total_length_bytes: u64, // we shouldn't allow messages larger than u32, that's already crazy big
        fragment_number: u32,
        payload: MessagePayload,
    },
    NoOp,
}

pub enum HelloError {
    UnsupportedProtocolVersion {
      min_supported: u16,
      max_supported: u16,
      your_version: u16
    },
}
}

Message Handling

Dropped and Out-of-Order Messages

  • Duplicate Detection: Messages are checked for duplicate message_id. Duplicates trigger an immediate NoOperation message with a reconfirmation in confirm_receipt.
  • Acknowledgement Timeout: Messages are resent if not acknowledged within 2 seconds (MESSAGE_CONFIRMATION_TIMEOUT).

Confirmation Batching

  • Batching Strategy: Receipts can be delayed up to 500ms (MAX_CONFIRMATION_DELAY) to enable batch confirmation.
  • Queue Management: Receipt queues exceeding 20 messages prompt immediate confirmation to prevent overflow.

Message Types

  • Short Messages: Contained within a single UDP packet (up to 1kb).
  • Long Messages: Split into fragments for larger payloads, enabling efficient data forwarding.

Rate Limiting

  • Initial Setup: Upstream bandwidth set 50% above desired usage to allow for traffic bursts.
  • Dynamic Adjustment: Future adaptations may use isotonic regression for optimizing bandwidth and packet loss balance.
  • Implementation: Bandwidth monitoring over 10-second windows (BANDWIDTH_MEASUREMENT_WINDOW). Exceeding limits triggers a 10ms sleep (BANDWIDTH_CONTROL_SLEEP_DURATION), with periodic reassessment.

Implementation Notes

Serialization

  • Try to avoid unnecessary copies of data, especially for large messages.
  • Ensure serialization format is robust against untrustedf data.
  • Note that there will be nested layers of serialization, both internal to FrTP and by the FrTP user.

Consider:

Conclusion

The Freenet Transport Protocol provides a robust framework for secure and efficient data transmission. Its design considers NAT challenges, message integrity, and bandwidth management, ensuring reliable communication in various network conditions.