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("Cannot bind to privileged port {0}. Use port 1024 or higher, or run with appropriate permissions.")]
200    PermissionDenied(u16),
201
202    /// No available port in range
203    #[error("No available port in range {0}-{1}. Try a wider range or use PortBinding::OsAssigned.")]
204    NoPortInRange(u16, u16),
205
206    /// Dual-stack not supported on this platform
207    #[error("Dual-stack not supported on this platform. Use IpMode::IPv4Only or IpMode::IPv6Only.")]
208    DualStackNotSupported,
209
210    /// IPv6 not available on this system
211    #[error("IPv6 not available on this system. Use IpMode::IPv4Only.")]
212    Ipv6NotAvailable,
213
214    /// Failed to bind socket
215    #[error("Failed to bind socket: {0}")]
216    BindFailed(String),
217
218    /// Invalid configuration
219    #[error("Invalid configuration: {0}")]
220    InvalidConfig(String),
221
222    /// IO error during socket operations
223    #[error("IO error: {0}")]
224    IoError(String),
225}
226
227impl From<std::io::Error> for EndpointConfigError {
228    fn from(err: std::io::Error) -> Self {
229        use std::io::ErrorKind;
230        match err.kind() {
231            ErrorKind::AddrInUse => {
232                // Try to extract port from error message
233                Self::BindFailed(err.to_string())
234            }
235            ErrorKind::PermissionDenied => Self::BindFailed(err.to_string()),
236            ErrorKind::AddrNotAvailable => Self::Ipv6NotAvailable,
237            _ => Self::IoError(err.to_string()),
238        }
239    }
240}
241
242/// Result type for port configuration operations
243pub type PortConfigResult<T> = Result<T, EndpointConfigError>;
244
245/// Bound socket information after successful binding
246#[derive(Debug, Clone)]
247pub struct BoundSocket {
248    /// Socket addresses that were successfully bound
249    pub addrs: Vec<SocketAddr>,
250    /// The configuration that was used
251    pub config: EndpointPortConfig,
252}
253
254impl BoundSocket {
255    /// Get the primary bound address (first in the list)
256    pub fn primary_addr(&self) -> Option<SocketAddr> {
257        self.addrs.first().copied()
258    }
259
260    /// Get all bound addresses
261    pub fn all_addrs(&self) -> &[SocketAddr] {
262        &self.addrs
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    use super::*;
269
270    #[test]
271    fn test_port_binding_default() {
272        let port = PortBinding::default();
273        assert_eq!(port, PortBinding::OsAssigned);
274    }
275
276    #[test]
277    fn test_port_binding_explicit() {
278        let port = PortBinding::Explicit(9000);
279        match port {
280            PortBinding::Explicit(9000) => {}
281            _ => panic!("Expected Explicit(9000)"),
282        }
283    }
284
285    #[test]
286    fn test_port_binding_range() {
287        let port = PortBinding::Range(9000, 9010);
288        match port {
289            PortBinding::Range(9000, 9010) => {}
290            _ => panic!("Expected Range(9000, 9010)"),
291        }
292    }
293
294    #[test]
295    fn test_ip_mode_default() {
296        let mode = IpMode::default();
297        assert_eq!(mode, IpMode::IPv4Only);
298    }
299
300    #[test]
301    fn test_ip_mode_ipv4_only() {
302        let mode = IpMode::IPv4Only;
303        match mode {
304            IpMode::IPv4Only => {}
305            _ => panic!("Expected IPv4Only"),
306        }
307    }
308
309    #[test]
310    fn test_ip_mode_dual_stack_separate() {
311        let mode = IpMode::DualStackSeparate {
312            ipv4_port: PortBinding::Explicit(9000),
313            ipv6_port: PortBinding::Explicit(9001),
314        };
315        match mode {
316            IpMode::DualStackSeparate { ipv4_port, ipv6_port } => {
317                assert_eq!(ipv4_port, PortBinding::Explicit(9000));
318                assert_eq!(ipv6_port, PortBinding::Explicit(9001));
319            }
320            _ => panic!("Expected DualStackSeparate"),
321        }
322    }
323
324    #[test]
325    fn test_socket_options_default() {
326        let opts = SocketOptions::default();
327        assert_eq!(opts.send_buffer_size, None);
328        assert_eq!(opts.recv_buffer_size, None);
329        assert!(!opts.reuse_address);
330        assert!(!opts.reuse_port);
331    }
332
333    #[test]
334    fn test_retry_behavior_default() {
335        let behavior = PortRetryBehavior::default();
336        assert_eq!(behavior, PortRetryBehavior::FailFast);
337    }
338
339    #[test]
340    fn test_endpoint_port_config_default() {
341        let config = EndpointPortConfig::default();
342        assert_eq!(config.port, PortBinding::OsAssigned);
343        assert_eq!(config.ip_mode, IpMode::IPv4Only);
344        assert_eq!(config.retry_behavior, PortRetryBehavior::FailFast);
345    }
346
347    #[test]
348    fn test_endpoint_config_error_display() {
349        let err = EndpointConfigError::PortInUse(9000);
350        assert!(err
351            .to_string()
352            .contains("Port 9000 is already in use"));
353
354        let err = EndpointConfigError::InvalidPort(70000);
355        assert!(err.to_string().contains("Invalid port number"));
356
357        let err = EndpointConfigError::PermissionDenied(80);
358        assert!(err.to_string().contains("privileged port"));
359    }
360
361    #[test]
362    fn test_bound_socket() {
363        let config = EndpointPortConfig::default();
364        let addrs = vec![
365            "127.0.0.1:9000".parse().expect("valid address"),
366            "127.0.0.1:9001".parse().expect("valid address"),
367        ];
368        let bound = BoundSocket {
369            addrs: addrs.clone(),
370            config,
371        };
372
373        assert_eq!(bound.primary_addr(), Some(addrs[0]));
374        assert_eq!(bound.all_addrs(), &addrs[..]);
375    }
376}