Skip to main content

nex_socket/icmp/
config.rs

1use socket2::Type as SockType;
2use std::{io, net::SocketAddr, time::Duration};
3
4use crate::SocketFamily;
5
6/// ICMP protocol version.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum IcmpKind {
9    V4,
10    V6,
11}
12
13/// ICMP socket type, either DGRAM or RAW.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum IcmpSocketType {
16    Dgram,
17    Raw,
18}
19
20impl IcmpSocketType {
21    /// Returns true if the socket type is DGRAM.
22    pub fn is_dgram(&self) -> bool {
23        matches!(self, IcmpSocketType::Dgram)
24    }
25
26    /// Returns true if the socket type is RAW.
27    pub fn is_raw(&self) -> bool {
28        matches!(self, IcmpSocketType::Raw)
29    }
30
31    /// Converts the ICMP socket type from a `socket2::Type`.
32    pub(crate) fn try_from_sock_type(sock_type: SockType) -> io::Result<Self> {
33        match sock_type {
34            SockType::DGRAM => Ok(IcmpSocketType::Dgram),
35            SockType::RAW => Ok(IcmpSocketType::Raw),
36            _ => Err(io::Error::new(
37                io::ErrorKind::InvalidInput,
38                "invalid ICMP socket type",
39            )),
40        }
41    }
42
43    /// Converts the ICMP socket type to a `socket2::Type`.
44    pub(crate) fn to_sock_type(&self) -> SockType {
45        match self {
46            IcmpSocketType::Dgram => SockType::DGRAM,
47            IcmpSocketType::Raw => SockType::RAW,
48        }
49    }
50}
51
52/// Configuration for an ICMP socket.
53#[derive(Debug, Clone)]
54pub struct IcmpConfig {
55    /// The socket family.
56    pub socket_family: SocketFamily,
57    /// Optional bind address for the socket.
58    pub bind: Option<SocketAddr>,
59    /// Time-to-live for IPv4 packets.
60    pub ttl: Option<u32>,
61    /// Hop limit for IPv6 packets.
62    pub hoplimit: Option<u32>,
63    /// Read timeout for the socket.
64    pub read_timeout: Option<Duration>,
65    /// Write timeout for the socket.
66    pub write_timeout: Option<Duration>,
67    /// Network interface to use for the socket.
68    pub interface: Option<String>,
69    /// Socket type hint, DGRAM preferred on Linux, RAW fallback on macOS/Windows.
70    pub sock_type_hint: IcmpSocketType,
71    /// FreeBSD only: optional FIB (Forwarding Information Base) support.
72    pub fib: Option<u32>,
73}
74
75impl IcmpConfig {
76    /// Creates a new ICMP configuration with the specified kind.
77    pub fn new(kind: IcmpKind) -> Self {
78        Self {
79            socket_family: match kind {
80                IcmpKind::V4 => SocketFamily::IPV4,
81                IcmpKind::V6 => SocketFamily::IPV6,
82            },
83            bind: None,
84            ttl: None,
85            hoplimit: None,
86            read_timeout: None,
87            write_timeout: None,
88            interface: None,
89            sock_type_hint: IcmpSocketType::Dgram,
90            fib: None,
91        }
92    }
93
94    /// Creates a new ICMP configuration from a socket family.
95    pub fn from_family(socket_family: SocketFamily) -> Self {
96        Self {
97            socket_family,
98            ..Self::new(match socket_family {
99                SocketFamily::IPV4 => IcmpKind::V4,
100                SocketFamily::IPV6 => IcmpKind::V6,
101            })
102        }
103    }
104
105    /// Set bind address for the socket.
106    pub fn with_bind(mut self, addr: SocketAddr) -> Self {
107        self.bind = Some(addr);
108        self
109    }
110
111    /// Set the time-to-live for IPv4 packets.
112    pub fn with_ttl(mut self, ttl: u32) -> Self {
113        self.ttl = Some(ttl);
114        self
115    }
116
117    /// Set the hop limit for IPv6 packets.
118    pub fn with_hoplimit(mut self, hops: u32) -> Self {
119        self.hoplimit = Some(hops);
120        self
121    }
122
123    /// Set the hop limit for IPv6 packets.
124    pub fn with_hop_limit(self, hops: u32) -> Self {
125        self.with_hoplimit(hops)
126    }
127
128    /// Set the read timeout for the socket.
129    pub fn with_read_timeout(mut self, timeout: Duration) -> Self {
130        self.read_timeout = Some(timeout);
131        self
132    }
133
134    /// Set the write timeout for the socket.
135    pub fn with_write_timeout(mut self, timeout: Duration) -> Self {
136        self.write_timeout = Some(timeout);
137        self
138    }
139
140    /// Set the network interface to use for the socket.
141    pub fn with_interface(mut self, iface: impl Into<String>) -> Self {
142        self.interface = Some(iface.into());
143        self
144    }
145
146    /// Set the socket type hint. (DGRAM or RAW)
147    pub fn with_sock_type(mut self, ty: IcmpSocketType) -> Self {
148        self.sock_type_hint = ty;
149        self
150    }
151
152    /// Set the FIB (Forwarding Information Base) for FreeBSD.
153    pub fn with_fib(mut self, fib: u32) -> Self {
154        self.fib = Some(fib);
155        self
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    #[test]
164    fn icmp_config_builders() {
165        let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
166        let cfg = IcmpConfig::new(IcmpKind::V4)
167            .with_bind(addr)
168            .with_ttl(4)
169            .with_interface("eth0")
170            .with_sock_type(IcmpSocketType::Raw);
171        assert_eq!(cfg.socket_family, SocketFamily::IPV4);
172        assert_eq!(cfg.bind, Some(addr));
173        assert_eq!(cfg.ttl, Some(4));
174        assert_eq!(cfg.interface.as_deref(), Some("eth0"));
175        assert_eq!(cfg.sock_type_hint, IcmpSocketType::Raw);
176    }
177
178    #[test]
179    fn from_family_sets_expected_kind() {
180        let v4 = IcmpConfig::from_family(SocketFamily::IPV4);
181        let v6 = IcmpConfig::from_family(SocketFamily::IPV6);
182        assert_eq!(v4.socket_family, SocketFamily::IPV4);
183        assert_eq!(v6.socket_family, SocketFamily::IPV6);
184    }
185}