ping_fox/
ping_fox.rs

1use crate::details;
2use crate::PingReceive;
3use std::net::Ipv4Addr;
4use std::sync::Arc;
5use std::time::Duration;
6
7/// The ping-fox configuration structure.
8#[allow(clippy::module_name_repetitions)]
9pub struct PingFoxConfig {
10    /// The type of socket used for network communication.
11    pub socket_type: SocketType,
12    /// Timeout for `receive` calls on a socket.
13    pub timeout: Duration,
14    /// Size of the communiation channel used between a [`PingSender`] and a [`PingReceiver`].
15    pub channel_size: usize,
16}
17
18/// Type of socket used for network communication.
19///
20/// The socket type also determines whether or not the resulting code needs to be executed with
21/// elevated privileges.
22#[derive(Clone, Copy)]
23pub enum SocketType {
24    /// Datagram socket.
25    ///
26    /// Datagram sockets can be used without elevated privileges.
27    DGRAM,
28    /// Raw socket.
29    ///
30    /// Raw sockets need elevated privileges.
31    RAW,
32}
33
34/// A `PingSentToken` represents an evidence that a ping message has been sent.
35// The attribute non_exhaustive prevents construction outside of this crate.
36#[non_exhaustive]
37pub struct PingSentToken {}
38
39/// Structure used for sending ping echo messages.
40pub struct PingSender(details::PingSender<details::icmp::v4::Socket>);
41impl PingSender {
42    /// Sends a ping echo message and returns a [`PingSentToken`].
43    ///
44    /// # Arguments
45    ///
46    /// * `ip` - The address to send the ping to.
47    pub fn send_to(&mut self, ip: Ipv4Addr) -> details::PingResult<PingSentToken> {
48        self.0.send_to(ip)
49    }
50}
51
52/// Structure used for receiving ping echo reply messages.
53pub struct PingReceiver(details::PingReceiver<details::icmp::v4::Socket>);
54impl PingReceiver {
55    /// Blocks and waits for an echo reply message.
56    /// Returns the data from the received echo reply message in [`PingReceive::Data`] or a
57    /// [`PingReceive::Timeout`] representing a timeout.
58    ///
59    /// # Arguments
60    ///
61    /// * `token` - A [`PingSentToken`] obtained from a previous call to `PingSender::send_to`.
62    pub fn receive(&mut self, token: PingSentToken) -> details::PingResult<PingReceive> {
63        self.0.receive(token)
64    }
65}
66
67/// Principal function in ping-fox. It creates a [`PingSender`] and a [`PingReceiver`].
68pub fn create(config: &PingFoxConfig) -> details::PingResult<(PingSender, PingReceiver)> {
69    let socket = details::icmp::v4::Socket::new(config.socket_type, config.timeout)?;
70    let (sender, receiver) = create_with_socket::<details::icmp::v4::Socket>(socket, config.channel_size);
71    Ok((PingSender(sender), PingReceiver(receiver)))
72}
73
74fn create_with_socket<S>(socket: S, channel_size: usize) -> (details::PingSender<S>, details::PingReceiver<S>)
75where
76    S: details::icmp::v4::TSocket + 'static,
77{
78    let icmpv4 = Arc::new(details::icmp::v4::IcmpV4::new(socket));
79    let (send_record_tx, send_record_rx) = details::records::ping_send_record_channel(channel_size);
80    let ping_data_buffer = details::PingDataBuffer::new(send_record_rx);
81    (
82        details::PingSender::new(icmpv4.clone(), send_record_tx),
83        details::PingReceiver::new(icmpv4, ping_data_buffer),
84    )
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use details::icmp::v4::tests::SocketMock;
91
92    #[test]
93    fn ping_localhost_succeeds() {
94        let ip = Ipv4Addr::new(127, 0, 0, 1);
95        let channel_size = 4;
96        let socket = SocketMock::new_default();
97
98        let (mut ping_sender, mut ping_receiver) = super::create_with_socket(socket, channel_size);
99        let token = ping_sender.send_to(ip).unwrap();
100        let ping_response = ping_receiver.receive(token);
101
102        assert!(ping_response.is_ok());
103
104        let token = ping_sender.send_to(ip).unwrap();
105        let ping_response = ping_receiver.receive(token);
106
107        assert!(ping_response.is_ok());
108    }
109}