nex_socket/icmp/
async_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 as StdUdpSocket};
6use tokio::net::UdpSocket;
7
8/// Asynchronous ICMP socket built on Tokio.
9#[derive(Debug)]
10pub struct AsyncIcmpSocket {
11    inner: UdpSocket,
12    socket_type: IcmpSocketType,
13    socket_family: SocketFamily,
14}
15
16impl AsyncIcmpSocket {
17    /// Create a new asynchronous ICMP socket.
18    pub async fn new(config: &IcmpConfig) -> io::Result<Self> {
19        let (domain, proto) = match config.socket_family {
20            SocketFamily::IPV4 => (Domain::IPV4, Some(Protocol::ICMPV4)),
21            SocketFamily::IPV6 => (Domain::IPV6, Some(Protocol::ICMPV6)),
22        };
23
24        // Build the socket with DGRAM preferred and RAW as a fallback
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(true)?;
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        #[cfg(windows)]
72        let std_socket = unsafe {
73            use std::os::windows::io::{FromRawSocket, IntoRawSocket};
74
75            StdUdpSocket::from_raw_socket(socket.into_raw_socket())
76        };
77        #[cfg(unix)]
78        let std_socket = unsafe {
79            use std::os::fd::{FromRawFd, IntoRawFd};
80
81            StdUdpSocket::from_raw_fd(socket.into_raw_fd())
82        };
83
84        // std -> tokio::net::UdpSocket
85        let inner = UdpSocket::from_std(std_socket)?;
86
87        Ok(Self {
88            inner,
89            socket_type: IcmpSocketType::from_sock_type(sock_type),
90            socket_family: config.socket_family,
91        })
92    }
93
94    /// Send a packet asynchronously.
95    pub async fn send_to(&self, buf: &[u8], target: SocketAddr) -> io::Result<usize> {
96        self.inner.send_to(buf, target).await
97    }
98
99    /// Receive a packet asynchronously.
100    pub async fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
101        self.inner.recv_from(buf).await
102    }
103
104    /// Retrieve the local address.
105    pub fn local_addr(&self) -> io::Result<SocketAddr> {
106        self.inner.local_addr()
107    }
108
109    /// Return the socket type (DGRAM or RAW).
110    pub fn socket_type(&self) -> IcmpSocketType {
111        self.socket_type
112    }
113
114    /// Return the socket family.
115    pub fn socket_family(&self) -> SocketFamily {
116        self.socket_family
117    }
118
119    /// Return the ICMP kind.
120    pub fn icmp_kind(&self) -> IcmpKind {
121        match self.socket_family {
122            SocketFamily::IPV4 => IcmpKind::V4,
123            SocketFamily::IPV6 => IcmpKind::V6,
124        }
125    }
126
127    /// Extract the RAW file descriptor for Unix.
128    #[cfg(unix)]
129    pub fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
130        use std::os::fd::AsRawFd;
131        self.inner.as_raw_fd()
132    }
133
134    /// Extract the RAW socket handle for Windows.
135    #[cfg(windows)]
136    pub fn as_raw_socket(&self) -> std::os::windows::io::RawSocket {
137        use std::os::windows::io::AsRawSocket;
138        self.inner.as_raw_socket()
139    }
140}