Skip to main content

nex_socket/icmp/
sync_impl.rs

1use crate::SocketFamily;
2use crate::icmp::{IcmpConfig, IcmpKind, IcmpSocketType};
3use socket2::{Domain, Protocol, Socket, Type as SockType};
4use std::io;
5use std::net::{SocketAddr, UdpSocket};
6
7/// Synchronous ICMP socket.
8#[derive(Debug)]
9pub struct IcmpSocket {
10    inner: UdpSocket,
11    socket_type: IcmpSocketType,
12    socket_family: SocketFamily,
13}
14
15impl IcmpSocket {
16    /// Create a new synchronous ICMP socket.
17    pub fn new(config: &IcmpConfig) -> io::Result<Self> {
18        config.validate()?;
19
20        let (domain, proto) = match config.socket_family {
21            SocketFamily::IPV4 => (Domain::IPV4, Some(Protocol::ICMPV4)),
22            SocketFamily::IPV6 => (Domain::IPV6, Some(Protocol::ICMPV6)),
23        };
24
25        let socket = match Socket::new(domain, config.sock_type_hint.to_sock_type(), proto) {
26            Ok(s) => s,
27            Err(_) => {
28                let alt_type = if config.sock_type_hint.is_dgram() {
29                    SockType::RAW
30                } else {
31                    SockType::DGRAM
32                };
33                Socket::new(domain, alt_type, proto)?
34            }
35        };
36
37        socket.set_nonblocking(false)?;
38
39        // Set socket options based on configuration
40        if let Some(ttl) = config.ttl {
41            socket.set_ttl(ttl)?;
42        }
43        if let Some(hoplimit) = config.hoplimit {
44            socket.set_unicast_hops_v6(hoplimit)?;
45        }
46        if let Some(timeout) = config.read_timeout {
47            socket.set_read_timeout(Some(timeout))?;
48        }
49        if let Some(timeout) = config.write_timeout {
50            socket.set_write_timeout(Some(timeout))?;
51        }
52        // FreeBSD only: optional FIB support
53        #[cfg(target_os = "freebsd")]
54        if let Some(fib) = config.fib {
55            socket.set_fib(fib)?;
56        }
57        // Linux: optional interface name
58        #[cfg(any(target_os = "linux", target_os = "android", target_os = "fuchsia"))]
59        if let Some(interface) = &config.interface {
60            socket.bind_device(Some(interface.as_bytes()))?;
61        }
62
63        // bind to the specified address if provided
64        if let Some(addr) = &config.bind {
65            socket.bind(&(*addr).into())?;
66        }
67
68        let sock_type = socket.r#type()?;
69
70        // Convert socket2::Socket into std::net::UdpSocket
71        let std_socket: UdpSocket = socket.into();
72
73        Ok(Self {
74            inner: std_socket,
75            socket_type: IcmpSocketType::try_from_sock_type(sock_type)?,
76            socket_family: config.socket_family,
77        })
78    }
79
80    /// Send a packet.
81    pub fn send_to(&self, buf: &[u8], target: SocketAddr) -> io::Result<usize> {
82        self.inner.send_to(buf, target)
83    }
84
85    /// Receive a packet.
86    pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
87        self.inner.recv_from(buf)
88    }
89
90    /// Retrieve the local address.
91    pub fn local_addr(&self) -> io::Result<SocketAddr> {
92        self.inner.local_addr()
93    }
94
95    /// Return the socket type.
96    pub fn socket_type(&self) -> IcmpSocketType {
97        self.socket_type
98    }
99
100    /// Return the socket family.
101    pub fn socket_family(&self) -> SocketFamily {
102        self.socket_family
103    }
104
105    /// Return the ICMP variant.
106    pub fn icmp_kind(&self) -> IcmpKind {
107        match self.socket_family {
108            SocketFamily::IPV4 => IcmpKind::V4,
109            SocketFamily::IPV6 => IcmpKind::V6,
110        }
111    }
112
113    /// Extract the RAW file descriptor for Unix.
114    #[cfg(unix)]
115    pub fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
116        use std::os::fd::AsRawFd;
117        self.inner.as_raw_fd()
118    }
119
120    /// Extract the RAW socket handle for Windows.
121    #[cfg(windows)]
122    pub fn as_raw_socket(&self) -> std::os::windows::io::RawSocket {
123        use std::os::windows::io::AsRawSocket;
124        self.inner.as_raw_socket()
125    }
126}