mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
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.
175 lines
5.2 KiB
Rust
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.
|
|
}
|
|
}
|