ant_quic/config/
port.rs

1// Copyright 2024 Saorsa Labs Ltd.
2//
3// This Saorsa Network Software is licensed under the General Public License (GPL), version 3.
4// Please see the file LICENSE-GPL, or visit <http://www.gnu.org/licenses/> for the full text.
5//
6// Full details available at https://saorsalabs.com/licenses
7
8//! Port configuration for QUIC endpoints
9//!
10//! This module provides flexible port binding strategies, dual-stack IPv4/IPv6 support,
11//! and port discovery APIs to enable OS-assigned ports and avoid port conflicts.
12
13use std::net::SocketAddr;
14use thiserror::Error;
15
16/// Port binding strategy for QUIC endpoints
17#[derive(Debug, Clone, PartialEq, Eq, Default)]
18pub enum PortBinding {
19    /// Let OS assign random available port (port 0)
20    ///
21    /// This is the recommended default as it avoids conflicts and allows multiple
22    /// instances to run on the same machine.
23    ///
24    /// # Example
25    /// ```
26    /// use ant_quic::config::PortBinding;
27    ///
28    /// let port = PortBinding::OsAssigned;
29    /// ```
30    #[default]
31    OsAssigned,
32
33    /// Bind to specific port
34    ///
35    /// # Example
36    /// ```
37    /// use ant_quic::config::PortBinding;
38    ///
39    /// let port = PortBinding::Explicit(9000);
40    /// ```
41    Explicit(u16),
42
43    /// Try ports in range, use first available
44    ///
45    /// # Example
46    /// ```
47    /// use ant_quic::config::PortBinding;
48    ///
49    /// let port = PortBinding::Range(9000, 9010);
50    /// ```
51    Range(u16, u16),
52}
53
54/// IP stack configuration for endpoint binding
55#[derive(Debug, Clone, PartialEq, Eq, Default)]
56pub enum IpMode {
57    /// IPv4 only (bind to 0.0.0.0:port)
58    ///
59    /// This is the safest default as it:
60    /// - Works on all platforms
61    /// - Avoids dual-stack conflicts
62    /// - Simplifies configuration
63    #[default]
64    IPv4Only,
65
66    /// IPv6 only (bind to `[::]`:port)
67    IPv6Only,
68
69    /// Both IPv4 and IPv6 on same port
70    ///
71    /// Note: May fail on some platforms due to dual-stack binding conflicts.
72    /// Use `DualStackSeparate` if this fails.
73    DualStack,
74
75    /// IPv4 and IPv6 on different ports
76    ///
77    /// This avoids dual-stack binding conflicts by using separate ports.
78    DualStackSeparate {
79        /// Port binding for IPv4
80        ipv4_port: PortBinding,
81        /// Port binding for IPv6
82        ipv6_port: PortBinding,
83    },
84}
85
86/// Socket-level options for endpoint binding
87#[derive(Debug, Clone, PartialEq, Eq, Default)]
88pub struct SocketOptions {
89    /// Send buffer size in bytes
90    pub send_buffer_size: Option<usize>,
91    /// Receive buffer size in bytes
92    pub recv_buffer_size: Option<usize>,
93    /// Enable SO_REUSEADDR
94    pub reuse_address: bool,
95    /// Enable SO_REUSEPORT (Linux/BSD only)
96    pub reuse_port: bool,
97}
98
99impl SocketOptions {
100    /// Create SocketOptions with platform-appropriate defaults
101    ///
102    /// This uses optimal buffer sizes for the current platform to ensure
103    /// reliable QUIC connections, especially on Windows where defaults
104    /// may be too small.
105    #[must_use]
106    pub fn with_platform_defaults() -> Self {
107        Self {
108            send_buffer_size: Some(buffer_defaults::PLATFORM_DEFAULT),
109            recv_buffer_size: Some(buffer_defaults::PLATFORM_DEFAULT),
110            reuse_address: false,
111            reuse_port: false,
112        }
113    }
114
115    /// Create SocketOptions optimized for PQC handshakes
116    ///
117    /// Post-Quantum Cryptography requires larger buffer sizes due to
118    /// the increased key sizes in ML-KEM and ML-DSA.
119    #[must_use]
120    pub fn with_pqc_defaults() -> Self {
121        Self {
122            send_buffer_size: Some(buffer_defaults::PQC_BUFFER_SIZE),
123            recv_buffer_size: Some(buffer_defaults::PQC_BUFFER_SIZE),
124            reuse_address: false,
125            reuse_port: false,
126        }
127    }
128
129    /// Create SocketOptions with custom buffer sizes
130    #[must_use]
131    pub fn with_buffer_sizes(send: usize, recv: usize) -> Self {
132        Self {
133            send_buffer_size: Some(send),
134            recv_buffer_size: Some(recv),
135            reuse_address: false,
136            reuse_port: false,
137        }
138    }
139}
140
141/// Retry behavior on port binding failures
142#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
143pub enum PortRetryBehavior {
144    /// Fail immediately if port unavailable
145    #[default]
146    FailFast,
147    /// Fall back to OS-assigned port on conflict
148    FallbackToOsAssigned,
149    /// Try next port in range (only for Range binding)
150    TryNext,
151}
152
153/// Configuration for endpoint port binding
154///
155/// This configuration allows flexible port binding strategies, dual-stack support,
156/// and automatic port discovery.
157///
158/// # Examples
159///
160/// ## OS-assigned port (recommended)
161/// ```
162/// use ant_quic::config::EndpointPortConfig;
163///
164/// let config = EndpointPortConfig::default();
165/// ```
166///
167/// ## Explicit port
168/// ```
169/// use ant_quic::config::{EndpointPortConfig, PortBinding};
170///
171/// let config = EndpointPortConfig {
172///     port: PortBinding::Explicit(9000),
173///     ..Default::default()
174/// };
175/// ```
176///
177/// ## Dual-stack with separate ports
178/// ```
179/// use ant_quic::config::{EndpointPortConfig, IpMode, PortBinding};
180///
181/// let config = EndpointPortConfig {
182///     ip_mode: IpMode::DualStackSeparate {
183///         ipv4_port: PortBinding::Explicit(9000),
184///         ipv6_port: PortBinding::Explicit(9001),
185///     },
186///     ..Default::default()
187/// };
188/// ```
189#[derive(Debug, Clone)]
190pub struct EndpointPortConfig {
191    /// Port binding configuration
192    pub port: PortBinding,
193    /// IP stack mode
194    pub ip_mode: IpMode,
195    /// Socket options
196    pub socket_options: SocketOptions,
197    /// Retry behavior on port conflicts
198    pub retry_behavior: PortRetryBehavior,
199}
200
201impl Default for EndpointPortConfig {
202    fn default() -> Self {
203        Self {
204            // Use OS-assigned port to avoid conflicts
205            port: PortBinding::OsAssigned,
206            // Use IPv4-only to avoid dual-stack conflicts
207            ip_mode: IpMode::IPv4Only,
208            socket_options: SocketOptions::default(),
209            retry_behavior: PortRetryBehavior::FailFast,
210        }
211    }
212}
213
214/// Errors related to endpoint port configuration and binding
215#[derive(Debug, Error, Clone, PartialEq, Eq)]
216pub enum EndpointConfigError {
217    /// Port is already in use
218    #[error("Port {0} is already in use. Try using PortBinding::OsAssigned to let the OS choose.")]
219    PortInUse(u16),
220
221    /// Invalid port number
222    #[error("Invalid port number: {0}. Port must be in range 0-65535.")]
223    InvalidPort(u32),
224
225    /// Cannot bind to privileged port
226    #[error(
227        "Cannot bind to privileged port {0}. Use port 1024 or higher, or run with appropriate permissions."
228    )]
229    PermissionDenied(u16),
230
231    /// No available port in range
232    #[error(
233        "No available port in range {0}-{1}. Try a wider range or use PortBinding::OsAssigned."
234    )]
235    NoPortInRange(u16, u16),
236
237    /// Dual-stack not supported on this platform
238    #[error("Dual-stack not supported on this platform. Use IpMode::IPv4Only or IpMode::IPv6Only.")]
239    DualStackNotSupported,
240
241    /// IPv6 not available on this system
242    #[error("IPv6 not available on this system. Use IpMode::IPv4Only.")]
243    Ipv6NotAvailable,
244
245    /// Failed to bind socket
246    #[error("Failed to bind socket: {0}")]
247    BindFailed(String),
248
249    /// Invalid configuration
250    #[error("Invalid configuration: {0}")]
251    InvalidConfig(String),
252
253    /// IO error during socket operations
254    #[error("IO error: {0}")]
255    IoError(String),
256}
257
258impl From<std::io::Error> for EndpointConfigError {
259    fn from(err: std::io::Error) -> Self {
260        use std::io::ErrorKind;
261        match err.kind() {
262            ErrorKind::AddrInUse => {
263                // Try to extract port from error message
264                Self::BindFailed(err.to_string())
265            }
266            ErrorKind::PermissionDenied => Self::BindFailed(err.to_string()),
267            ErrorKind::AddrNotAvailable => Self::Ipv6NotAvailable,
268            _ => Self::IoError(err.to_string()),
269        }
270    }
271}
272
273/// Result type for port configuration operations
274pub type PortConfigResult<T> = Result<T, EndpointConfigError>;
275
276/// Platform-specific UDP buffer size defaults
277///
278/// These constants help ensure reliable QUIC connections, especially with
279/// Post-Quantum Cryptography (PQC) which requires larger handshake packets.
280pub mod buffer_defaults {
281    /// Minimum buffer size for QUIC (covers standard handshakes)
282    pub const MIN_BUFFER_SIZE: usize = 64 * 1024; // 64KB
283
284    /// Default buffer size for classical crypto
285    pub const CLASSICAL_BUFFER_SIZE: usize = 256 * 1024; // 256KB
286
287    /// Buffer size for PQC (larger due to ML-KEM/ML-DSA key sizes)
288    /// PQC handshakes can be 5-8KB vs classical 2-3KB
289    pub const PQC_BUFFER_SIZE: usize = 4 * 1024 * 1024; // 4MB
290
291    /// Platform-recommended default buffer size
292    #[cfg(target_os = "windows")]
293    pub const PLATFORM_DEFAULT: usize = 256 * 1024; // 256KB - Windows needs explicit sizing
294
295    /// Platform-recommended default buffer size
296    #[cfg(target_os = "linux")]
297    pub const PLATFORM_DEFAULT: usize = 2 * 1024 * 1024; // 2MB - Linux usually allows large buffers
298
299    /// Platform-recommended default buffer size
300    #[cfg(target_os = "macos")]
301    pub const PLATFORM_DEFAULT: usize = 512 * 1024; // 512KB - macOS middle ground
302
303    /// Platform-recommended default buffer size
304    #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
305    pub const PLATFORM_DEFAULT: usize = 256 * 1024; // 256KB fallback
306
307    /// Get recommended buffer size based on crypto mode
308    ///
309    /// Returns larger buffer sizes when PQC is enabled to accommodate
310    /// the larger key exchange messages.
311    #[must_use]
312    pub fn recommended_buffer_size(pqc_enabled: bool) -> usize {
313        if pqc_enabled {
314            PQC_BUFFER_SIZE
315        } else {
316            PLATFORM_DEFAULT.max(CLASSICAL_BUFFER_SIZE)
317        }
318    }
319}
320
321/// Bound socket information after successful binding
322#[derive(Debug, Clone)]
323pub struct BoundSocket {
324    /// Socket addresses that were successfully bound
325    pub addrs: Vec<SocketAddr>,
326    /// The configuration that was used
327    pub config: EndpointPortConfig,
328}
329
330impl BoundSocket {
331    /// Get the primary bound address (first in the list)
332    pub fn primary_addr(&self) -> Option<SocketAddr> {
333        self.addrs.first().copied()
334    }
335
336    /// Get all bound addresses
337    pub fn all_addrs(&self) -> &[SocketAddr] {
338        &self.addrs
339    }
340}
341
342#[cfg(test)]
343mod tests {
344    use super::*;
345
346    #[test]
347    fn test_port_binding_default() {
348        let port = PortBinding::default();
349        assert_eq!(port, PortBinding::OsAssigned);
350    }
351
352    #[test]
353    fn test_port_binding_explicit() {
354        let port = PortBinding::Explicit(9000);
355        match port {
356            PortBinding::Explicit(9000) => {}
357            _ => panic!("Expected Explicit(9000)"),
358        }
359    }
360
361    #[test]
362    fn test_port_binding_range() {
363        let port = PortBinding::Range(9000, 9010);
364        match port {
365            PortBinding::Range(9000, 9010) => {}
366            _ => panic!("Expected Range(9000, 9010)"),
367        }
368    }
369
370    #[test]
371    fn test_ip_mode_default() {
372        let mode = IpMode::default();
373        assert_eq!(mode, IpMode::IPv4Only);
374    }
375
376    #[test]
377    fn test_ip_mode_ipv4_only() {
378        let mode = IpMode::IPv4Only;
379        match mode {
380            IpMode::IPv4Only => {}
381            _ => panic!("Expected IPv4Only"),
382        }
383    }
384
385    #[test]
386    fn test_ip_mode_dual_stack_separate() {
387        let mode = IpMode::DualStackSeparate {
388            ipv4_port: PortBinding::Explicit(9000),
389            ipv6_port: PortBinding::Explicit(9001),
390        };
391        match mode {
392            IpMode::DualStackSeparate {
393                ipv4_port,
394                ipv6_port,
395            } => {
396                assert_eq!(ipv4_port, PortBinding::Explicit(9000));
397                assert_eq!(ipv6_port, PortBinding::Explicit(9001));
398            }
399            _ => panic!("Expected DualStackSeparate"),
400        }
401    }
402
403    #[test]
404    fn test_socket_options_default() {
405        let opts = SocketOptions::default();
406        assert_eq!(opts.send_buffer_size, None);
407        assert_eq!(opts.recv_buffer_size, None);
408        assert!(!opts.reuse_address);
409        assert!(!opts.reuse_port);
410    }
411
412    #[test]
413    fn test_retry_behavior_default() {
414        let behavior = PortRetryBehavior::default();
415        assert_eq!(behavior, PortRetryBehavior::FailFast);
416    }
417
418    #[test]
419    fn test_endpoint_port_config_default() {
420        let config = EndpointPortConfig::default();
421        assert_eq!(config.port, PortBinding::OsAssigned);
422        assert_eq!(config.ip_mode, IpMode::IPv4Only);
423        assert_eq!(config.retry_behavior, PortRetryBehavior::FailFast);
424    }
425
426    #[test]
427    fn test_endpoint_config_error_display() {
428        let err = EndpointConfigError::PortInUse(9000);
429        assert!(err.to_string().contains("Port 9000 is already in use"));
430
431        let err = EndpointConfigError::InvalidPort(70000);
432        assert!(err.to_string().contains("Invalid port number"));
433
434        let err = EndpointConfigError::PermissionDenied(80);
435        assert!(err.to_string().contains("privileged port"));
436    }
437
438    #[test]
439    fn test_bound_socket() {
440        let config = EndpointPortConfig::default();
441        let addrs = vec![
442            "127.0.0.1:9000".parse().expect("valid address"),
443            "127.0.0.1:9001".parse().expect("valid address"),
444        ];
445        let bound = BoundSocket {
446            addrs: addrs.clone(),
447            config,
448        };
449
450        assert_eq!(bound.primary_addr(), Some(addrs[0]));
451        assert_eq!(bound.all_addrs(), &addrs[..]);
452    }
453}