From e3bb28c5591dbeb5629fc9c2ba3f1bbfb88656ba Mon Sep 17 00:00:00 2001 From: Nils Andreas Svee Date: Thu, 7 May 2026 20:52:21 +0700 Subject: [PATCH] fix(client): validate packet length configuration --- crates/irtt-cli/src/lib.rs | 4 +-- crates/irtt-client/src/client.rs | 10 ++++++- crates/irtt-client/src/client/tests/config.rs | 26 +++++++++++++++++++ crates/irtt-client/src/config.rs | 1 + crates/irtt-client/src/lib.rs | 2 +- 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/crates/irtt-cli/src/lib.rs b/crates/irtt-cli/src/lib.rs index 8308b30..cb49c49 100644 --- a/crates/irtt-cli/src/lib.rs +++ b/crates/irtt-cli/src/lib.rs @@ -13,13 +13,11 @@ use clap::{Parser, ValueEnum}; use irtt_client::{ ClientConfig, ClientEvent, NegotiatedParams, NegotiationPolicy, OneWayDelaySample, PacketMeta, ReceivedStatsSample, RttSample, ServerTiming, SocketConfig, WarningKind, MAX_DSCP_CODEPOINT, - MAX_SERVER_FILL_BYTES, MAX_TTL, + MAX_SERVER_FILL_BYTES, MAX_TTL, MAX_UDP_PAYLOAD_LENGTH, }; use irtt_proto::{Clock, ReceivedStats, StampAt}; const DEFAULT_RECV_TIMEOUT: Duration = Duration::from_millis(20); -const MAX_UDP_PAYLOAD_LENGTH: u32 = 65_507; - #[derive(Debug, Clone, Parser)] #[command(name = "irtt-rs", about = "Minimal IRTT-compatible client")] pub struct CliArgs { diff --git a/crates/irtt-client/src/client.rs b/crates/irtt-client/src/client.rs index 438141b..0398d48 100644 --- a/crates/irtt-client/src/client.rs +++ b/crates/irtt-client/src/client.rs @@ -11,7 +11,10 @@ use irtt_proto::{ }; use crate::{ - config::{ClientConfig, RecvBudget, RunMode, MAX_DSCP_CODEPOINT, MAX_SERVER_FILL_BYTES}, + config::{ + ClientConfig, RecvBudget, RunMode, MAX_DSCP_CODEPOINT, MAX_SERVER_FILL_BYTES, + MAX_UDP_PAYLOAD_LENGTH, + }, error::ClientError, event::{ ClientEvent, OneWayDelaySample, OpenOutcome, ReceivedStatsSample, RttSample, ServerTiming, @@ -782,6 +785,11 @@ fn validate_protocol_config(config: &ClientConfig) -> Result<(), ClientError> { reason: format!("dscp must be <= {MAX_DSCP_CODEPOINT}"), }); } + if config.length > MAX_UDP_PAYLOAD_LENGTH { + return Err(ClientError::InvalidConfig { + reason: format!("packet length must be <= {MAX_UDP_PAYLOAD_LENGTH}"), + }); + } if let Some(fill) = &config.server_fill { let len = fill.len(); diff --git a/crates/irtt-client/src/client/tests/config.rs b/crates/irtt-client/src/client/tests/config.rs index 777f9f6..76f9120 100644 --- a/crates/irtt-client/src/client/tests/config.rs +++ b/crates/irtt-client/src/client/tests/config.rs @@ -50,6 +50,32 @@ fn params_from_config_maps_compatibility_fields() { assert_eq!(config.hmac_key.as_deref(), Some(b"secret".as_slice())); } +#[test] +fn params_from_config_accepts_udp_payload_length_boundary_values() { + for length in [0, 1, 1472, 4096, MAX_UDP_PAYLOAD_LENGTH] { + let config = ClientConfig { + length, + ..ClientConfig::default() + }; + assert_eq!( + params_from_config(&config).unwrap().length, + i64::from(length) + ); + } +} + +#[test] +fn params_from_config_rejects_oversized_udp_payload_length() { + let config = ClientConfig { + length: MAX_UDP_PAYLOAD_LENGTH + 1, + ..ClientConfig::default() + }; + assert!(matches!( + params_from_config(&config), + Err(ClientError::InvalidConfig { reason }) if reason.contains("packet length") + )); +} + #[test] fn params_from_config_encodes_continuous_duration_as_zero() { let config = ClientConfig { diff --git a/crates/irtt-client/src/config.rs b/crates/irtt-client/src/config.rs index 95fdcbd..0db956e 100644 --- a/crates/irtt-client/src/config.rs +++ b/crates/irtt-client/src/config.rs @@ -5,6 +5,7 @@ use irtt_proto::{Clock, ReceivedStats, StampAt}; pub(crate) const DEFAULT_PORT: u16 = 2112; pub const MAX_DSCP_CODEPOINT: u8 = 63; pub const MAX_TTL: u32 = 255; +pub const MAX_UDP_PAYLOAD_LENGTH: u32 = 65_507; pub const MAX_SERVER_FILL_BYTES: usize = 32; pub(crate) const DEFAULT_DURATION: Duration = Duration::from_secs(3); pub(crate) const DEFAULT_INTERVAL: Duration = Duration::from_secs(1); diff --git a/crates/irtt-client/src/lib.rs b/crates/irtt-client/src/lib.rs index d8ae692..65aba6a 100644 --- a/crates/irtt-client/src/lib.rs +++ b/crates/irtt-client/src/lib.rs @@ -20,7 +20,7 @@ mod timing; pub use client::Client; pub use config::{ ClientConfig, NegotiationPolicy, RecvBudget, RunMode, SocketConfig, MAX_DSCP_CODEPOINT, - MAX_SERVER_FILL_BYTES, MAX_TTL, + MAX_SERVER_FILL_BYTES, MAX_TTL, MAX_UDP_PAYLOAD_LENGTH, }; pub use error::{ClientError, EventSubscriptionError}; pub use event::{