summaryrefslogtreecommitdiffhomepage
path: root/talpid-wireguard/src/connectivity/pinger/android.rs
blob: 00ad4d8fd37931b97459052fe69887ae040cfe38 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
use std::{io, net::Ipv4Addr};

/// Pinger errors
#[derive(thiserror::Error, Debug)]
pub enum Error {
    /// Failed to run `ping` process
    #[error("Failed to run ping command")]
    PingError(#[from] io::Error),

    /// ICMP timed out
    #[error("Ping timed out")]
    TimeoutError,
}

/// A pinger that sends ICMP requests without waiting for responses
pub struct Pinger {
    addr: Ipv4Addr,
    processes: Vec<duct::Handle>,
}

impl Pinger {
    /// Creates a new pinger that will send ICMP requests only through the specified interface
    pub fn new(addr: Ipv4Addr) -> Result<Self, Error> {
        Ok(Self {
            processes: vec![],
            addr,
        })
    }

    fn try_deplete_process_list(&mut self) {
        self.processes.retain(|child| {
            match child.try_wait() {
                // child has terminated, doesn't have to be retained
                Ok(Some(_)) => false,
                _ => true,
            }
        });
    }
}

impl super::Pinger for Pinger {
    // Send an ICMP packet without waiting for a reply
    fn send_icmp(&mut self) -> Result<(), Error> {
        self.try_deplete_process_list();

        let cmd = ping_cmd(self.addr, 1);
        let handle = cmd.start().map_err(Error::PingError)?;
        self.processes.push(handle);
        Ok(())
    }

    fn reset(&mut self) {
        let processes = std::mem::take(&mut self.processes);
        for proc in processes {
            if proc
                .try_wait()
                .map(|maybe_stopped| maybe_stopped.is_none())
                .unwrap_or(false)
            {
                if let Err(err) = proc.kill() {
                    log::error!("Failed to kill ping process: {}", err);
                }
            }
        }
    }
}

impl Drop for Pinger {
    fn drop(&mut self) {
        for child in self.processes.iter_mut() {
            if let Err(e) = child.kill() {
                log::error!("Failed to kill ping process: {}", e);
            }
        }
    }
}

fn ping_cmd(ip: Ipv4Addr, timeout_secs: u16) -> duct::Expression {
    let timeout_secs = timeout_secs.to_string();
    let ip = ip.to_string();
    let args = ["-n", "-i", "1", "-w", &timeout_secs, &ip];

    duct::cmd("ping", args)
        .stdin_null()
        .stdout_null()
        .unchecked()
}