nex_socket/icmp/
config.rs1use socket2::Type as SockType;
2use std::{io, net::SocketAddr, time::Duration};
3
4use crate::SocketFamily;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[non_exhaustive]
9pub enum IcmpKind {
10 V4,
11 V6,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16#[non_exhaustive]
17pub enum IcmpSocketType {
18 Dgram,
19 Raw,
20}
21
22impl IcmpSocketType {
23 pub fn is_dgram(&self) -> bool {
25 matches!(self, IcmpSocketType::Dgram)
26 }
27
28 pub fn is_raw(&self) -> bool {
30 matches!(self, IcmpSocketType::Raw)
31 }
32
33 pub(crate) fn try_from_sock_type(sock_type: SockType) -> io::Result<Self> {
35 match sock_type {
36 SockType::DGRAM => Ok(IcmpSocketType::Dgram),
37 SockType::RAW => Ok(IcmpSocketType::Raw),
38 _ => Err(io::Error::new(
39 io::ErrorKind::InvalidInput,
40 "invalid ICMP socket type",
41 )),
42 }
43 }
44
45 pub(crate) fn to_sock_type(self) -> SockType {
47 match self {
48 IcmpSocketType::Dgram => SockType::DGRAM,
49 IcmpSocketType::Raw => SockType::RAW,
50 }
51 }
52}
53
54#[derive(Debug, Clone)]
56#[non_exhaustive]
57pub struct IcmpConfig {
58 pub socket_family: SocketFamily,
60 pub bind: Option<SocketAddr>,
62 pub ttl: Option<u32>,
64 pub hoplimit: Option<u32>,
66 pub read_timeout: Option<Duration>,
68 pub write_timeout: Option<Duration>,
70 pub interface: Option<String>,
72 pub sock_type_hint: IcmpSocketType,
74 pub fib: Option<u32>,
76}
77
78impl IcmpConfig {
79 pub fn new(kind: IcmpKind) -> Self {
81 Self {
82 socket_family: match kind {
83 IcmpKind::V4 => SocketFamily::IPV4,
84 IcmpKind::V6 => SocketFamily::IPV6,
85 },
86 bind: None,
87 ttl: None,
88 hoplimit: None,
89 read_timeout: None,
90 write_timeout: None,
91 interface: None,
92 sock_type_hint: IcmpSocketType::Dgram,
93 fib: None,
94 }
95 }
96
97 pub fn from_family(socket_family: SocketFamily) -> Self {
99 Self {
100 socket_family,
101 ..Self::new(match socket_family {
102 SocketFamily::IPV4 => IcmpKind::V4,
103 SocketFamily::IPV6 => IcmpKind::V6,
104 })
105 }
106 }
107
108 pub fn with_bind(mut self, addr: SocketAddr) -> Self {
110 self.bind = Some(addr);
111 self
112 }
113
114 pub fn with_ttl(mut self, ttl: u32) -> Self {
116 self.ttl = Some(ttl);
117 self
118 }
119
120 pub fn with_hoplimit(mut self, hops: u32) -> Self {
122 self.hoplimit = Some(hops);
123 self
124 }
125
126 pub fn with_hop_limit(self, hops: u32) -> Self {
128 self.with_hoplimit(hops)
129 }
130
131 pub fn with_read_timeout(mut self, timeout: Duration) -> Self {
133 self.read_timeout = Some(timeout);
134 self
135 }
136
137 pub fn with_write_timeout(mut self, timeout: Duration) -> Self {
139 self.write_timeout = Some(timeout);
140 self
141 }
142
143 pub fn with_interface(mut self, iface: impl Into<String>) -> Self {
145 self.interface = Some(iface.into());
146 self
147 }
148
149 pub fn with_sock_type(mut self, ty: IcmpSocketType) -> Self {
151 self.sock_type_hint = ty;
152 self
153 }
154
155 pub fn with_fib(mut self, fib: u32) -> Self {
157 self.fib = Some(fib);
158 self
159 }
160
161 pub fn validate(&self) -> io::Result<()> {
163 if let Some(addr) = self.bind {
164 let addr_family = crate::SocketFamily::from_socket_addr(&addr);
165 if addr_family != self.socket_family {
166 return Err(io::Error::new(
167 io::ErrorKind::InvalidInput,
168 "bind address family does not match socket_family",
169 ));
170 }
171 }
172
173 if self.socket_family.is_v4() && self.hoplimit.is_some() {
174 return Err(io::Error::new(
175 io::ErrorKind::InvalidInput,
176 "hoplimit is only supported for IPv6 ICMP sockets",
177 ));
178 }
179
180 if self.socket_family.is_v6() && self.ttl.is_some() {
181 return Err(io::Error::new(
182 io::ErrorKind::InvalidInput,
183 "ttl is only supported for IPv4 ICMP sockets",
184 ));
185 }
186
187 if matches!(self.read_timeout, Some(timeout) if timeout.is_zero()) {
188 return Err(io::Error::new(
189 io::ErrorKind::InvalidInput,
190 "read_timeout must be greater than zero",
191 ));
192 }
193
194 if matches!(self.write_timeout, Some(timeout) if timeout.is_zero()) {
195 return Err(io::Error::new(
196 io::ErrorKind::InvalidInput,
197 "write_timeout must be greater than zero",
198 ));
199 }
200
201 if matches!(self.interface.as_deref(), Some("")) {
202 return Err(io::Error::new(
203 io::ErrorKind::InvalidInput,
204 "interface must not be empty",
205 ));
206 }
207
208 Ok(())
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn icmp_config_builders() {
218 let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
219 let cfg = IcmpConfig::new(IcmpKind::V4)
220 .with_bind(addr)
221 .with_ttl(4)
222 .with_interface("eth0")
223 .with_sock_type(IcmpSocketType::Raw);
224 assert_eq!(cfg.socket_family, SocketFamily::IPV4);
225 assert_eq!(cfg.bind, Some(addr));
226 assert_eq!(cfg.ttl, Some(4));
227 assert_eq!(cfg.interface.as_deref(), Some("eth0"));
228 assert_eq!(cfg.sock_type_hint, IcmpSocketType::Raw);
229 }
230
231 #[test]
232 fn from_family_sets_expected_kind() {
233 let v4 = IcmpConfig::from_family(SocketFamily::IPV4);
234 let v6 = IcmpConfig::from_family(SocketFamily::IPV6);
235 assert_eq!(v4.socket_family, SocketFamily::IPV4);
236 assert_eq!(v6.socket_family, SocketFamily::IPV6);
237 }
238
239 #[test]
240 fn icmp_config_validate_rejects_family_mismatch() {
241 let cfg = IcmpConfig::new(IcmpKind::V4).with_bind("[::1]:0".parse().unwrap());
242 assert!(cfg.validate().is_err());
243 }
244}