Files
fips/src/config/transport.rs
T
Johnathan Corgan ac82e61c77 Set UDP socket buffer sizes to prevent receive overflow
Under high-throughput forwarding, the kernel default 212KB receive
buffer overflows, silently dropping ~11% of UDP datagrams at transit
nodes. Add configurable recv_buf_size and send_buf_size to UdpConfig
(default 2MB each) using socket2 for pre-bind buffer configuration.
Startup log now reports actual buffer sizes granted by the kernel.

Requires net.core.rmem_max >= 2097152 on the host for the full 2MB
to take effect; otherwise the kernel silently clamps.
2026-02-19 05:51:36 +00:00

175 lines
5.2 KiB
Rust

//! Transport configuration types.
//!
//! Generic transport instance handling (single vs. named) and
//! transport-specific configuration structs.
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
/// Default UDP bind address.
const DEFAULT_UDP_BIND_ADDR: &str = "0.0.0.0:4000";
/// Default UDP MTU (IPv6 minimum).
const DEFAULT_UDP_MTU: u16 = 1280;
/// Default UDP receive buffer size (2 MB).
const DEFAULT_UDP_RECV_BUF: usize = 2 * 1024 * 1024;
/// Default UDP send buffer size (2 MB).
const DEFAULT_UDP_SEND_BUF: usize = 2 * 1024 * 1024;
/// UDP transport instance configuration.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UdpConfig {
/// Bind address (`bind_addr`). Defaults to "0.0.0.0:4000".
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bind_addr: Option<String>,
/// UDP MTU (`mtu`). Defaults to 1280 (IPv6 minimum).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mtu: Option<u16>,
/// UDP receive buffer size in bytes (`recv_buf_size`). Defaults to 2 MB.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub recv_buf_size: Option<usize>,
/// UDP send buffer size in bytes (`send_buf_size`). Defaults to 2 MB.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub send_buf_size: Option<usize>,
}
impl UdpConfig {
/// Get the bind address, using default if not configured.
pub fn bind_addr(&self) -> &str {
self.bind_addr.as_deref().unwrap_or(DEFAULT_UDP_BIND_ADDR)
}
/// Get the UDP MTU, using default if not configured.
pub fn mtu(&self) -> u16 {
self.mtu.unwrap_or(DEFAULT_UDP_MTU)
}
/// Get the receive buffer size, using default if not configured.
pub fn recv_buf_size(&self) -> usize {
self.recv_buf_size.unwrap_or(DEFAULT_UDP_RECV_BUF)
}
/// Get the send buffer size, using default if not configured.
pub fn send_buf_size(&self) -> usize {
self.send_buf_size.unwrap_or(DEFAULT_UDP_SEND_BUF)
}
}
/// Transport instances - either a single config or named instances.
///
/// Allows both simple single-instance config:
/// ```yaml
/// transports:
/// udp:
/// bind_addr: "0.0.0.0:4000"
/// ```
///
/// And multiple named instances:
/// ```yaml
/// transports:
/// udp:
/// main:
/// bind_addr: "0.0.0.0:4000"
/// backup:
/// bind_addr: "192.168.1.100:4001"
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum TransportInstances<T> {
/// Single unnamed instance (config fields directly under transport type).
Single(T),
/// Multiple named instances.
Named(HashMap<String, T>),
}
impl<T> TransportInstances<T> {
/// Get the number of instances.
pub fn len(&self) -> usize {
match self {
TransportInstances::Single(_) => 1,
TransportInstances::Named(map) => map.len(),
}
}
/// Check if there are no instances.
pub fn is_empty(&self) -> bool {
match self {
TransportInstances::Single(_) => false,
TransportInstances::Named(map) => map.is_empty(),
}
}
/// Iterate over all instances as (name, config) pairs.
///
/// Single instances have `None` as the name.
/// Named instances have `Some(name)`.
pub fn iter(&self) -> impl Iterator<Item = (Option<&str>, &T)> {
match self {
TransportInstances::Single(config) => {
vec![(None, config)].into_iter()
}
TransportInstances::Named(map) => {
map.iter()
.map(|(k, v)| (Some(k.as_str()), v))
.collect::<Vec<_>>()
.into_iter()
}
}
}
}
impl<T> Default for TransportInstances<T> {
fn default() -> Self {
TransportInstances::Named(HashMap::new())
}
}
/// Transports configuration section.
///
/// Each transport type can have either a single instance (config directly
/// under the type name) or multiple named instances.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TransportsConfig {
/// UDP transport instances.
#[serde(default, skip_serializing_if = "is_transport_empty")]
pub udp: TransportInstances<UdpConfig>,
// Future transport types:
// #[serde(default, skip_serializing_if = "is_transport_empty")]
// pub tcp: TransportInstances<TcpConfig>,
//
// #[serde(default, skip_serializing_if = "is_transport_empty")]
// pub tor: TransportInstances<TorConfig>,
}
/// Helper for skip_serializing_if on TransportInstances.
fn is_transport_empty<T>(instances: &TransportInstances<T>) -> bool {
instances.is_empty()
}
impl TransportsConfig {
/// Check if any transports are configured.
pub fn is_empty(&self) -> bool {
self.udp.is_empty()
// && self.tcp.is_empty()
// && self.tor.is_empty()
}
/// Merge another TransportsConfig into this one.
///
/// Non-empty transport sections from `other` replace those in `self`.
pub fn merge(&mut self, other: TransportsConfig) {
if !other.udp.is_empty() {
self.udp = other.udp;
}
// Future: same for tcp, tor, etc.
}
}