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)]
8pub enum IcmpKind {
9 V4,
10 V6,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum IcmpSocketType {
16 Dgram,
17 Raw,
18}
19
20impl IcmpSocketType {
21 pub fn is_dgram(&self) -> bool {
23 matches!(self, IcmpSocketType::Dgram)
24 }
25
26 pub fn is_raw(&self) -> bool {
28 matches!(self, IcmpSocketType::Raw)
29 }
30
31 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 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#[derive(Debug, Clone)]
54pub struct IcmpConfig {
55 pub socket_family: SocketFamily,
57 pub bind: Option<SocketAddr>,
59 pub ttl: Option<u32>,
61 pub hoplimit: Option<u32>,
63 pub read_timeout: Option<Duration>,
65 pub write_timeout: Option<Duration>,
67 pub interface: Option<String>,
69 pub sock_type_hint: IcmpSocketType,
71 pub fib: Option<u32>,
73}
74
75impl IcmpConfig {
76 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 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 pub fn with_bind(mut self, addr: SocketAddr) -> Self {
107 self.bind = Some(addr);
108 self
109 }
110
111 pub fn with_ttl(mut self, ttl: u32) -> Self {
113 self.ttl = Some(ttl);
114 self
115 }
116
117 pub fn with_hoplimit(mut self, hops: u32) -> Self {
119 self.hoplimit = Some(hops);
120 self
121 }
122
123 pub fn with_hop_limit(self, hops: u32) -> Self {
125 self.with_hoplimit(hops)
126 }
127
128 pub fn with_read_timeout(mut self, timeout: Duration) -> Self {
130 self.read_timeout = Some(timeout);
131 self
132 }
133
134 pub fn with_write_timeout(mut self, timeout: Duration) -> Self {
136 self.write_timeout = Some(timeout);
137 self
138 }
139
140 pub fn with_interface(mut self, iface: impl Into<String>) -> Self {
142 self.interface = Some(iface.into());
143 self
144 }
145
146 pub fn with_sock_type(mut self, ty: IcmpSocketType) -> Self {
148 self.sock_type_hint = ty;
149 self
150 }
151
152 pub fn with_fib(mut self, fib: u32) -> Self {
154 self.fib = Some(fib);
155 self
156 }
157
158 pub fn validate(&self) -> io::Result<()> {
160 if let Some(addr) = self.bind {
161 let addr_family = crate::SocketFamily::from_socket_addr(&addr);
162 if addr_family != self.socket_family {
163 return Err(io::Error::new(
164 io::ErrorKind::InvalidInput,
165 "bind address family does not match socket_family",
166 ));
167 }
168 }
169
170 if self.socket_family.is_v4() && self.hoplimit.is_some() {
171 return Err(io::Error::new(
172 io::ErrorKind::InvalidInput,
173 "hoplimit is only supported for IPv6 ICMP sockets",
174 ));
175 }
176
177 if self.socket_family.is_v6() && self.ttl.is_some() {
178 return Err(io::Error::new(
179 io::ErrorKind::InvalidInput,
180 "ttl is only supported for IPv4 ICMP sockets",
181 ));
182 }
183
184 if matches!(self.read_timeout, Some(timeout) if timeout.is_zero()) {
185 return Err(io::Error::new(
186 io::ErrorKind::InvalidInput,
187 "read_timeout must be greater than zero",
188 ));
189 }
190
191 if matches!(self.write_timeout, Some(timeout) if timeout.is_zero()) {
192 return Err(io::Error::new(
193 io::ErrorKind::InvalidInput,
194 "write_timeout must be greater than zero",
195 ));
196 }
197
198 if matches!(self.interface.as_deref(), Some("")) {
199 return Err(io::Error::new(
200 io::ErrorKind::InvalidInput,
201 "interface must not be empty",
202 ));
203 }
204
205 Ok(())
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn icmp_config_builders() {
215 let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
216 let cfg = IcmpConfig::new(IcmpKind::V4)
217 .with_bind(addr)
218 .with_ttl(4)
219 .with_interface("eth0")
220 .with_sock_type(IcmpSocketType::Raw);
221 assert_eq!(cfg.socket_family, SocketFamily::IPV4);
222 assert_eq!(cfg.bind, Some(addr));
223 assert_eq!(cfg.ttl, Some(4));
224 assert_eq!(cfg.interface.as_deref(), Some("eth0"));
225 assert_eq!(cfg.sock_type_hint, IcmpSocketType::Raw);
226 }
227
228 #[test]
229 fn from_family_sets_expected_kind() {
230 let v4 = IcmpConfig::from_family(SocketFamily::IPV4);
231 let v6 = IcmpConfig::from_family(SocketFamily::IPV6);
232 assert_eq!(v4.socket_family, SocketFamily::IPV4);
233 assert_eq!(v6.socket_family, SocketFamily::IPV6);
234 }
235
236 #[test]
237 fn icmp_config_validate_rejects_family_mismatch() {
238 let cfg = IcmpConfig::new(IcmpKind::V4).with_bind("[::1]:0".parse().unwrap());
239 assert!(cfg.validate().is_err());
240 }
241}