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)]
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    OsAssigned,
31
32    /// Bind to specific port
33    ///
34    /// # Example
35    /// ```
36    /// use ant_quic::config::PortBinding;
37    ///
38    /// let port = PortBinding::Explicit(9000);
39    /// ```
40    Explicit(u16),
41
42    /// Try ports in range, use first available
43    ///
44    /// # Example
45    /// ```
46    /// use ant_quic::config::PortBinding;
47    ///
48    /// let port = PortBinding::Range(9000, 9010);
49    /// ```
50    Range(u16, u16),
51}
52
53impl Default for PortBinding {
54    fn default() -> Self {
55        Self::OsAssigned
56    }
57}
58
59/// IP stack configuration for endpoint binding
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub enum IpMode {
62    /// IPv4 only (bind to 0.0.0.0:port)
63    ///
64    /// This is the safest default as it:
65    /// - Works on all platforms
66    /// - Avoids dual-stack conflicts
67    /// - Simplifies configuration
68    IPv4Only,
69
70    /// IPv6 only (bind to [::]:port)
71    IPv6Only,
72
73    /// Both IPv4 and IPv6 on same port
74    ///
75    /// Note: May fail on some platforms due to dual-stack binding conflicts.
76    /// Use `DualStackSeparate` if this fails.
77    DualStack,
78
79    /// IPv4 and IPv6 on different ports
80    ///
81    /// This avoids dual-stack binding conflicts by using separate ports.
82    DualStackSeparate {
83        /// Port binding for IPv4
84        ipv4_port: PortBinding,
85        /// Port binding for IPv6
86        ipv6_port: PortBinding,
87    },
88}
89
90impl Default for IpMode {
91    fn default() -> Self {
92        Self::IPv4Only
93    }
94}
95
96/// Socket-level options for endpoint binding
97#[derive(Debug, Clone, PartialEq, Eq, Default)]
98pub struct SocketOptions {
99    /// Send buffer size in bytes
100    pub send_buffer_size: Option<usize>,
101    /// Receive buffer size in bytes
102    pub recv_buffer_size: Option<usize>,
103    /// Enable SO_REUSEADDR
104    pub reuse_address: bool,
105    /// Enable SO_REUSEPORT (Linux/BSD only)
106    pub reuse_port: bool,
107}
108
109/// Retry behavior on port binding failures
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum PortRetryBehavior {
112    /// Fail immediately if port unavailable
113    FailFast,
114    /// Fall back to OS-assigned port on conflict
115    FallbackToOsAssigned,
116    /// Try next port in range (only for Range binding)
117    TryNext,
118}
119
120impl Default for PortRetryBehavior {
121    fn default() -> Self {
122        Self::FailFast
123    }
124}
125
126/// Configuration for endpoint port binding
127///
128/// This configuration allows flexible port binding strategies, dual-stack support,
129/// and automatic port discovery.
130///
131/// # Examples
132///
133/// ## OS-assigned port (recommended)
134/// ```
135/// use ant_quic::config::EndpointPortConfig;
136///
137/// let config = EndpointPortConfig::default();
138/// ```
139///
140/// ## Explicit port
141/// ```
142/// use ant_quic::config::{EndpointPortConfig, PortBinding};
143///
144/// let config = EndpointPortConfig {
145///     port: PortBinding::Explicit(9000),
146///     ..Default::default()
147/// };
148/// ```
149///
150/// ## Dual-stack with separate ports
151/// ```
152/// use ant_quic::config::{EndpointPortConfig, IpMode, PortBinding};
153///
154/// let config = EndpointPortConfig {
155///     ip_mode: IpMode::DualStackSeparate {
156///         ipv4_port: PortBinding::Explicit(9000),
157///         ipv6_port: PortBinding::Explicit(9001),
158///     },
159///     ..Default::default()
160/// };
161/// ```
162#[derive(Debug, Clone)]
163pub struct EndpointPortConfig {
164    /// Port binding configuration
165    pub port: PortBinding,
166    /// IP stack mode
167    pub ip_mode: IpMode,
168    /// Socket options
169    pub socket_options: SocketOptions,
170    /// Retry behavior on port conflicts
171    pub retry_behavior: PortRetryBehavior,
172}
173
174impl Default for EndpointPortConfig {
175    fn default() -> Self {
176        Self {
177            // Use OS-assigned port to avoid conflicts
178            port: PortBinding::OsAssigned,
179            // Use IPv4-only to avoid dual-stack conflicts
180            ip_mode: IpMode::IPv4Only,
181            socket_options: SocketOptions::default(),
182            retry_behavior: PortRetryBehavior::FailFast,
183        }
184    }
185}
186
187/// Errors related to endpoint port configuration and binding
188#[derive(Debug, Error, Clone, PartialEq, Eq)]
189pub enum EndpointConfigError {
190    /// Port is already in use
191    #[error("Port {0} is already in use. Try using PortBinding::OsAssigned to let the OS choose.")]
192    PortInUse(u16),
193
194    /// Invalid port number
195    #[error("Invalid port number: {0}. Port must be in range 0-65535.")]
196    InvalidPort(u32),
197
198    /// Cannot bind to privileged port
199    #[error(
200        "Cannot bind to privileged port {0}. Use port 1024 or higher, or run with appropriate permissions."
201    )]
202    PermissionDenied(u16),
203
204    /// No available port in range
205    #[error(
206        "No available port in range {0}-{1}. Try a wider range or use PortBinding::OsAssigned."
207    )]
208    NoPortInRange(u16, u16),
209
210    /// Dual-stack not supported on this platform
211    #[error("Dual-stack not supported on this platform. Use IpMode::IPv4Only or IpMode::IPv6Only.")]
212    DualStackNotSupported,
213
214    /// IPv6 not available on this system
215    #[error("IPv6 not available on this system. Use IpMode::IPv4Only.")]
216    Ipv6NotAvailable,
217
218    /// Failed to bind socket
219    #[error("Failed to bind socket: {0}")]
220    BindFailed(String),
221
222    /// Invalid configuration
223    #[error("Invalid configuration: {0}")]
224    InvalidConfig(String),
225
226    /// IO error during socket operations
227    #[error("IO error: {0}")]
228    IoError(String),
229}
230
231impl From<std::io::Error> for EndpointConfigError {
232    fn from(err: std::io::Error) -> Self {
233        use std::io::ErrorKind;
234        match err.kind() {
235            ErrorKind::AddrInUse => {
236                // Try to extract port from error message
237                Self::BindFailed(err.to_string())
238            }
239            ErrorKind::PermissionDenied => Self::BindFailed(err.to_string()),
240            ErrorKind::AddrNotAvailable => Self::Ipv6NotAvailable,
241            _ => Self::IoError(err.to_string()),
242        }
243    }
244}
245
246/// Result type for port configuration operations
247pub type PortConfigResult<T> = Result<T, EndpointConfigError>;
248
249/// Bound socket information after successful binding
250#[derive(Debug, Clone)]
251pub struct BoundSocket {
252    /// Socket addresses that were successfully bound
253    pub addrs: Vec<SocketAddr>,
254    /// The configuration that was used
255    pub config: EndpointPortConfig,
256}
257
258impl BoundSocket {
259    /// Get the primary bound address (first in the list)
260    pub fn primary_addr(&self) -> Option<SocketAddr> {
261        self.addrs.first().copied()
262    }
263
264    /// Get all bound addresses
265    pub fn all_addrs(&self) -> &[SocketAddr] {
266        &self.addrs
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273
274    #[test]
275    fn test_port_binding_default() {
276        let port = PortBinding::default();
277        assert_eq!(port, PortBinding::OsAssigned);
278    }
279
280    #[test]
281    fn test_port_binding_explicit() {
282        let port = PortBinding::Explicit(9000);
283        match port {
284            PortBinding::Explicit(9000) => {}
285            _ => panic!("Expected Explicit(9000)"),
286        }
287    }
288
289    #[test]
290    fn test_port_binding_range() {
291        let port = PortBinding::Range(9000, 9010);
292        match port {
293            PortBinding::Range(9000, 9010) => {}
294            _ => panic!("Expected Range(9000, 9010)"),
295        }
296    }
297
298    #[test]
299    fn test_ip_mode_default() {
300        let mode = IpMode::default();
301        assert_eq!(mode, IpMode::IPv4Only);
302    }
303
304    #[test]
305    fn test_ip_mode_ipv4_only() {
306        let mode = IpMode::IPv4Only;
307        match mode {
308            IpMode::IPv4Only => {}
309            _ => panic!("Expected IPv4Only"),
310        }
311    }
312
313    #[test]
314    fn test_ip_mode_dual_stack_separate() {
315        let mode = IpMode::DualStackSeparate {
316            ipv4_port: PortBinding::Explicit(9000),
317            ipv6_port: PortBinding::Explicit(9001),
318        };
319        match mode {
320            IpMode::DualStackSeparate {
321                ipv4_port,
322                ipv6_port,
323            } => {
324                assert_eq!(ipv4_port, PortBinding::Explicit(9000));
325                assert_eq!(ipv6_port, PortBinding::Explicit(9001));
326            }
327            _ => panic!("Expected DualStackSeparate"),
328        }
329    }
330
331    #[test]
332    fn test_socket_options_default() {
333        let opts = SocketOptions::default();
334        assert_eq!(opts.send_buffer_size, None);
335        assert_eq!(opts.recv_buffer_size, None);
336        assert!(!opts.reuse_address);
337        assert!(!opts.reuse_port);
338    }
339
340    #[test]
341    fn test_retry_behavior_default() {
342        let behavior = PortRetryBehavior::default();
343        assert_eq!(behavior, PortRetryBehavior::FailFast);
344    }
345
346    #[test]
347    fn test_endpoint_port_config_default() {
348        let config = EndpointPortConfig::default();
349        assert_eq!(config.port, PortBinding::OsAssigned);
350        assert_eq!(config.ip_mode, IpMode::IPv4Only);
351        assert_eq!(config.retry_behavior, PortRetryBehavior::FailFast);
352    }
353
354    #[test]
355    fn test_endpoint_config_error_display() {
356        let err = EndpointConfigError::PortInUse(9000);
357        assert!(err.to_string().contains("Port 9000 is already in use"));
358
359        let err = EndpointConfigError::InvalidPort(70000);
360        assert!(err.to_string().contains("Invalid port number"));
361
362        let err = EndpointConfigError::PermissionDenied(80);
363        assert!(err.to_string().contains("privileged port"));
364    }
365
366    #[test]
367    fn test_bound_socket() {
368        let config = EndpointPortConfig::default();
369        let addrs = vec![
370            "127.0.0.1:9000".parse().expect("valid address"),
371            "127.0.0.1:9001".parse().expect("valid address"),
372        ];
373        let bound = BoundSocket {
374            addrs: addrs.clone(),
375            config,
376        };
377
378        assert_eq!(bound.primary_addr(), Some(addrs[0]));
379        assert_eq!(bound.all_addrs(), &addrs[..]);
380    }
381}