perf/
lib.rs

1#[cfg(feature = "qlog")]
2use std::path::PathBuf;
3use std::{io, net::SocketAddr, num::ParseIntError, str::FromStr, sync::Arc, time::Duration};
4
5use anyhow::{Context, Result};
6use clap::{Parser, ValueEnum};
7use noq::{
8    AckFrequencyConfig, TransportConfig, VarInt,
9    congestion::{self, ControllerFactory},
10    udp::UdpSocketState,
11};
12use rustls::crypto::ring::cipher_suite;
13use socket2::{Domain, Protocol, Socket, Type};
14use tracing::warn;
15
16#[cfg_attr(not(feature = "json-output"), allow(dead_code))]
17pub mod stats;
18
19pub mod noprotection;
20
21pub mod client;
22pub mod server;
23
24// Common options between client and server binary
25#[derive(Parser)]
26pub struct CommonOpt {
27    /// Send buffer size in bytes
28    ///
29    /// This can use SI suffixes for sizes. For example, 1M will request
30    /// 1MiB, 10G will request 10GiB.
31    #[clap(long, default_value = "2M", value_parser = parse_byte_size)]
32    pub send_buffer_size: u64,
33    /// Receive buffer size in bytes
34    ///
35    /// This can use SI suffixes for sizes. For example, 1M will request
36    /// 1MiB, 10G will request 10GiB.
37    #[clap(long, default_value = "2M", value_parser = parse_byte_size)]
38    pub recv_buffer_size: u64,
39    /// Whether to print connection statistics
40    #[clap(long)]
41    pub conn_stats: bool,
42    /// Perform NSS-compatible TLS key logging to the file specified in `SSLKEYLOGFILE`.
43    #[clap(long = "keylog")]
44    pub keylog: bool,
45    /// UDP payload size that the network must be capable of carrying
46    #[clap(long, default_value = "1200")]
47    pub initial_mtu: u16,
48    /// Disable packet encryption/decryption (for debugging purpose)
49    #[clap(long = "no-protection")]
50    pub no_protection: bool,
51    /// The initial round-trip-time (in msecs)
52    #[clap(long, group = "common")]
53    pub initial_rtt: Option<u64>,
54    /// Ack Frequency mode
55    #[clap(long = "ack-frequency")]
56    pub ack_frequency: bool,
57    /// Congestion algorithm to use
58    #[clap(long = "congestion")]
59    pub cong_alg: Option<CongestionAlgorithm>,
60    /// Maximum number of bytes the peer may transmit without acknowledgement on any one stream
61    /// before becoming blocked.
62    ///
63    /// This can use SI suffixes for sizes. For example, 1M will limit to
64    /// 1MiB, 10G will limit to 10GiB.
65    #[clap(long, value_parser = parse_byte_size)]
66    pub stream_receive_window: Option<u64>,
67    /// Maximum number of bytes the peer may transmit across all streams of a connection before
68    /// becoming blocked.
69    ///
70    /// This can use SI suffixes for sizes. For example, 1M will limit to
71    /// 1MiB, 10G will limit to 10GiB.
72    #[clap(long, value_parser = parse_byte_size)]
73    pub receive_window: Option<u64>,
74    /// Maximum number of bytes to transmit to a peer without acknowledgment
75    ///
76    /// This can use SI suffixes for sizes. For example, 1M will limit to
77    /// 1MiB, 10G will limit to 10GiB.
78    #[clap(long, value_parser = parse_byte_size)]
79    pub send_window: Option<u64>,
80    /// Max UDP payload size in bytes
81    #[clap(long, default_value = "1472")]
82    pub max_udp_payload_size: u16,
83    /// qlog output directory
84    ///
85    /// Alternatively you can set the `QLOGDIR` environment variable.
86    #[cfg(feature = "qlog")]
87    #[clap(long = "qlog")]
88    pub qlog_dir: Option<PathBuf>,
89}
90
91impl CommonOpt {
92    pub fn build_transport_config(
93        &self,
94        #[cfg(feature = "qlog")] name: &str,
95    ) -> io::Result<TransportConfig> {
96        let mut transport = TransportConfig::default();
97        transport.initial_mtu(self.initial_mtu);
98
99        if let Some(initial_rtt) = self.initial_rtt {
100            transport.initial_rtt(Duration::from_millis(initial_rtt));
101        }
102
103        if self.ack_frequency {
104            transport.ack_frequency_config(Some(AckFrequencyConfig::default()));
105        }
106
107        if let Some(cong_alg) = self.cong_alg {
108            transport.congestion_controller_factory(cong_alg.build());
109        }
110
111        if let Some(stream_receive_window) = self.stream_receive_window {
112            transport.stream_receive_window(
113                VarInt::from_u64(stream_receive_window).unwrap_or(VarInt::MAX),
114            );
115        }
116
117        if let Some(receive_window) = self.receive_window {
118            transport.receive_window(VarInt::from_u64(receive_window).unwrap_or(VarInt::MAX));
119        }
120
121        if let Some(send_window) = self.send_window {
122            transport.send_window(send_window);
123        }
124
125        #[cfg(feature = "qlog")]
126        if let Some(qlog_dir) = &self.qlog_dir {
127            transport.qlog_from_path(qlog_dir, name);
128        } else {
129            transport.qlog_from_env(name);
130        }
131
132        Ok(transport)
133    }
134
135    pub fn bind_socket(&self, addr: SocketAddr) -> Result<std::net::UdpSocket> {
136        let socket = Socket::new(Domain::for_address(addr), Type::DGRAM, Some(Protocol::UDP))
137            .context("create socket")?;
138
139        if addr.is_ipv6() {
140            socket.set_only_v6(false).context("set_only_v6")?;
141        }
142
143        socket
144            .bind(&socket2::SockAddr::from(addr))
145            .context("binding endpoint")?;
146
147        let socket_state = UdpSocketState::new((&socket).into())?;
148        socket_state
149            .set_send_buffer_size((&socket).into(), self.send_buffer_size as usize)
150            .context("send buffer size")?;
151        socket_state
152            .set_recv_buffer_size((&socket).into(), self.recv_buffer_size as usize)
153            .context("recv buffer size")?;
154
155        let buf_size = socket_state
156            .send_buffer_size((&socket).into())
157            .context("send buffer size")?;
158        if buf_size < self.send_buffer_size as usize {
159            warn!(
160                "Unable to set desired send buffer size. Desired: {}, Actual: {}",
161                self.send_buffer_size, buf_size
162            );
163        }
164
165        let buf_size = socket_state
166            .recv_buffer_size((&socket).into())
167            .context("recv buffer size")?;
168        if buf_size < self.recv_buffer_size as usize {
169            warn!(
170                "Unable to set desired recv buffer size. Desired: {}, Actual: {}",
171                self.recv_buffer_size, buf_size
172            );
173        }
174
175        Ok(socket.into())
176    }
177}
178
179pub fn parse_byte_size(s: &str) -> Result<u64, ParseIntError> {
180    let s = s.trim();
181
182    let multiplier = match s.chars().last() {
183        Some('T') => 1024 * 1024 * 1024 * 1024,
184        Some('G') => 1024 * 1024 * 1024,
185        Some('M') => 1024 * 1024,
186        Some('k') => 1024,
187        _ => 1,
188    };
189
190    let s = match multiplier {
191        1 => s,
192        _ => &s[..s.len() - 1],
193    };
194
195    Ok(u64::from_str(s)? * multiplier)
196}
197
198#[derive(Clone, Copy, ValueEnum)]
199pub enum CongestionAlgorithm {
200    Cubic,
201    Bbr3,
202    NewReno,
203}
204
205impl CongestionAlgorithm {
206    pub fn build(self) -> Arc<dyn ControllerFactory + Send + Sync + 'static> {
207        match self {
208            Self::Cubic => Arc::new(congestion::CubicConfig::default()),
209            Self::Bbr3 => Arc::new(congestion::Bbr3Config::default()),
210            Self::NewReno => Arc::new(congestion::NewRenoConfig::default()),
211        }
212    }
213}
214
215pub static PERF_CIPHER_SUITES: &[rustls::SupportedCipherSuite] = &[
216    cipher_suite::TLS13_AES_128_GCM_SHA256,
217    cipher_suite::TLS13_AES_256_GCM_SHA384,
218    cipher_suite::TLS13_CHACHA20_POLY1305_SHA256,
219];