Files
figcraft/crates/collab-net/src/limits.rs
T
bflatastic e0435d4eb4 M9: close the remaining open-hosting DoS paths
Second security review:
- a broadcast counts once against the queued-bytes budget; each block of
  addresses gets at most an eighth; when the budget runs out the biggest
  consumer is shed, not the receiver
- forced compactions at most once a minute per file
- plugin downloads check the per-address share before the global budget
  and before reading
- install.sh never replaces a Caddyfile with other sites (one import line,
  once; install-test.sh in packaging lint); the built binary runs only as
  the server's account
- bytes being read count against per-block and global budgets, the
  biggest readers dropped first
- block multipliers x2 (/56) and x4 (/48), connection caps at most a
  sixteenth/eighth of all; global publish and download budgets eight times
  a /48's share
- problems capped at 200; writes refused below max(1 GiB, two files) free;
  the private-server file cap documented
2026-10-12 11:40:03 +07:00

86 lines
3.8 KiB
Rust

//! Transport limits, shared by the native transport and the browser client.
use std::net::IpAddr;
use std::time::Duration;
/// Transport limits (the collaboration core has its own: [`figcraft_collab::Limits`]).
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct NetLimits {
/// Sockets a host keeps open at once, joined or not.
pub max_connections: usize,
/// Largest message a client may send once it has joined (a batch can carry images), and
/// before: its hello.
pub max_client_message: usize,
pub max_hello: usize,
/// Largest message a client accepts from the host (a welcome carries the whole document).
pub max_server_message: usize,
/// Messages a client may send per second on average, and in a burst; beyond that it is
/// disconnected.
pub rate: f64,
pub burst: f64,
/// A new connection must finish the WebSocket handshake and say hello within this.
pub hello_timeout: Duration,
/// A connection with no traffic for this long is dead.
pub idle_timeout: Duration,
/// A quiet client pings this often (keeps it under `idle_timeout`).
pub keepalive: Duration,
/// Messages queued for one client before it counts as stuck and is disconnected.
pub queue: usize,
/// How long a write may block.
pub write_timeout: Duration,
/// Most connections from one address at once (an IPv6 address counts by its /64; 0: no cap).
pub max_per_address: usize,
/// The reverse proxy in front of a host, whose `X-Forwarded-For` names the client: only
/// connections from this address are believed (the header is anyone's to forge).
pub trusted_proxy: Option<IpAddr>,
/// Bytes queued for one client before it counts as stuck (one message always fits an empty
/// queue), for one block of addresses (an IPv4 address, an IPv6 /48), and for every client
/// together (a broadcast counted once; past it the biggest consumer is dropped).
pub max_queued_bytes: usize,
pub max_queued_block: usize,
pub max_queued_total: usize,
/// Bytes being read into messages (not whole yet) for one block, and for every connection:
/// past them the biggest readers are dropped.
pub max_reading_block: usize,
pub max_reading_total: usize,
/// Bytes of messages handed to the hub and not handled yet, all connections together: past it
/// a connection is dropped as busy instead of piling more on.
pub max_in_flight: usize,
/// Largest message on a connection that only asks small things (browsing a plugin directory).
pub max_request: usize,
/// Connections only sessions (`Hello`) and the operator may take: one-shot requests leave them.
pub reserve_for_sessions: usize,
/// A one-shot request connection (hosting, a library, a plugin directory) with no traffic for
/// this long is dropped.
pub request_idle: Duration,
}
impl Default for NetLimits {
fn default() -> Self {
Self {
max_connections: 64,
max_client_message: figcraft_collab::Limits::default().max_message_bytes,
max_hello: 64 << 10,
max_server_message: figcraft_collab::Limits::default().max_message_bytes,
rate: 100.0,
burst: 400.0,
hello_timeout: Duration::from_secs(10),
idle_timeout: Duration::from_secs(45),
keepalive: Duration::from_secs(15),
queue: 1024,
write_timeout: Duration::from_secs(10),
max_per_address: 0,
trusted_proxy: None,
max_queued_bytes: 160 << 20,
max_queued_block: usize::MAX,
max_queued_total: usize::MAX,
max_reading_block: usize::MAX,
max_reading_total: usize::MAX,
max_in_flight: usize::MAX,
max_request: 64 << 10,
reserve_for_sessions: 0,
request_idle: Duration::from_secs(45),
}
}
}