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