summaryrefslogtreecommitdiffhomepage
path: root/talpid-core/src
diff options
context:
space:
mode:
authorEmīls Piņķis <emils@mullvad.net>2019-09-30 12:26:23 +0100
committerEmīls Piņķis <emils@mullvad.net>2019-09-30 14:50:37 +0100
commita28b7f1a8720219a5e9416d77c3e8ea780fb2189 (patch)
tree1bd71b894c513d75de0d8b7cecf500f038e4783d /talpid-core/src
parent3164047b1eae8ce7fb4e10f89f86c4a39158576b (diff)
downloadmullvadvpn-a28b7f1a8720219a5e9416d77c3e8ea780fb2189.tar.xz
mullvadvpn-a28b7f1a8720219a5e9416d77c3e8ea780fb2189.zip
Add Windows implementation for ping_monitor
Diffstat (limited to 'talpid-core/src')
-rw-r--r--talpid-core/src/lib.rs1
-rw-r--r--talpid-core/src/ping_monitor/mod.rs2
-rw-r--r--talpid-core/src/ping_monitor/win.rs157
3 files changed, 160 insertions, 0 deletions
diff --git a/talpid-core/src/lib.rs b/talpid-core/src/lib.rs
index cd88277d9d..5fdbd3030d 100644
--- a/talpid-core/src/lib.rs
+++ b/talpid-core/src/lib.rs
@@ -23,6 +23,7 @@ mod winnet;
#[cfg(any(target_os = "linux", target_os = "macos"))]
/// Working with IP interface devices
pub mod network_interface;
+#[cfg(not(windows))]
/// Abstraction over operating system routing table.
pub mod routing;
diff --git a/talpid-core/src/ping_monitor/mod.rs b/talpid-core/src/ping_monitor/mod.rs
index 25b33be0bb..8cdc766ad4 100644
--- a/talpid-core/src/ping_monitor/mod.rs
+++ b/talpid-core/src/ping_monitor/mod.rs
@@ -2,6 +2,8 @@
#[path = "unix.rs"]
mod imp;
+
+#[cfg(target_os = "windows")]
#[path = "win.rs"]
mod imp;
diff --git a/talpid-core/src/ping_monitor/win.rs b/talpid-core/src/ping_monitor/win.rs
new file mode 100644
index 0000000000..e98161fae6
--- /dev/null
+++ b/talpid-core/src/ping_monitor/win.rs
@@ -0,0 +1,157 @@
+#![allow(dead_code)]
+// TODO: Remove lint exemption once ping monitor is used on Windows
+use pnet_packet::{
+ icmp::{
+ self,
+ echo_reply::EchoReplyPacket,
+ echo_request::{EchoRequestPacket, MutableEchoRequestPacket},
+ IcmpCode, IcmpPacket, IcmpType,
+ },
+ Packet,
+};
+use socket2::{Domain, Protocol, Socket, Type};
+use std::{
+ io,
+ net::{IpAddr, Ipv4Addr, SocketAddr},
+ sync::mpsc,
+ thread,
+ time::{Duration, Instant},
+};
+
+#[derive(err_derive::Error, Debug)]
+pub enum Error {
+ /// Failed to open raw socket
+ #[error(display = "Failed to open raw socket")]
+ OpenError(#[error(cause)] io::Error),
+
+ /// Failed to read from raw socket
+ #[error(display = "Failed to read from socket")]
+ ReadError(#[error(cause)] io::Error),
+
+ /// Failed to write to raw socket
+ #[error(display = "Failed to write to socket")]
+ WriteError(#[error(cause)] io::Error),
+
+ #[error(display = "Timed out")]
+ TimeoutError,
+}
+
+pub fn monitor_ping(
+ ip: Ipv4Addr,
+ timeout_secs: u16,
+ _interface: &str,
+ close_receiver: mpsc::Receiver<()>,
+) -> Result<()> {
+ let mut pinger = Pinger::new(ip)?;
+ while let Err(mpsc::TryRecvError::Empty) = close_receiver.try_recv() {
+ let start = Instant::now();
+ pinger.send_ping(Duration::from_secs(timeout_secs.into()))?;
+ if let Some(remaining) =
+ Duration::from_secs(timeout_secs.into()).checked_sub(start.elapsed())
+ {
+ thread::sleep(remaining);
+ }
+ }
+
+ Ok(())
+}
+
+pub fn ping(ip: Ipv4Addr, timeout_secs: u16, _interface: &str) -> Result<()> {
+ Pinger::new(ip)?.send_ping(Duration::from_secs(timeout_secs.into()))
+}
+
+type Result<T> = std::result::Result<T, Error>;
+
+pub struct Pinger {
+ sock: Socket,
+ addr: Ipv4Addr,
+ id: u16,
+ seq: u16,
+}
+
+impl Pinger {
+ pub fn new(addr: Ipv4Addr) -> Result<Self> {
+ let sock = Socket::new(Domain::ipv4(), Type::raw(), Some(Protocol::icmpv4()))
+ .map_err(Error::OpenError)?;
+ sock.set_nonblocking(true).map_err(Error::OpenError)?;
+ Ok(Self {
+ sock,
+ id: rand::random(),
+ addr,
+ seq: 0,
+ })
+ }
+
+ /// Sends an ICMP echo request
+ pub fn send_ping(&mut self, timeout: Duration) -> Result<()> {
+ let dest = SocketAddr::new(IpAddr::from(self.addr), 0);
+ let request = self.next_ping_request();
+ self.sock
+ .send_to(request.packet(), &dest.into())
+ .map_err(Error::WriteError)?;
+ self.wait_for_response(Instant::now() + timeout, &request)
+ }
+
+ /// returns the next ping packet
+ fn next_ping_request(&mut self) -> EchoRequestPacket<'static> {
+ const ICMP_HEADER_LENGTH: usize = 8;
+ const ICMP_PAYLOAD_LENGTH: usize = 24;
+ const ICMP_PACKET_LENGTH: usize = ICMP_HEADER_LENGTH + ICMP_PAYLOAD_LENGTH;
+ let payload: [u8; ICMP_PAYLOAD_LENGTH] = rand::random();
+ let mut packet = MutableEchoRequestPacket::owned(vec![0u8; ICMP_PACKET_LENGTH])
+ .expect("Failed to construct an empty packet");
+ packet.set_icmp_type(IcmpType::new(8));
+ packet.set_icmp_code(IcmpCode::new(0));
+ packet.set_sequence_number(self.next_seq());
+ packet.set_identifier(self.id);
+ packet.set_payload(&payload);
+ packet.set_checksum(icmp::checksum(&IcmpPacket::new(&packet.packet()).unwrap()));
+ packet.consume_to_immutable()
+ }
+
+ fn next_seq(&mut self) -> u16 {
+ let seq = self.seq;
+ self.seq += 1;
+ seq
+ }
+
+
+ fn wait_for_response(&mut self, deadline: Instant, req: &EchoRequestPacket<'_>) -> Result<()> {
+ let mut recv_buffer = [0u8; 4096];
+ while Instant::now() < deadline {
+ match self.sock.recv(&mut recv_buffer) {
+ Ok(recv_len) => {
+ if recv_len > 20 {
+ // have to slice off first 20 bytes for the IP header.
+ if let Some(reply) = Self::parse_response(&recv_buffer[20..recv_len]) {
+ if reply.get_identifier() == req.get_identifier()
+ && reply.get_sequence_number() == req.get_sequence_number()
+ && req.payload() == reply.payload()
+ {
+ return Ok(());
+ }
+ }
+ }
+ }
+ Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
+ std::thread::sleep(Duration::from_millis(100));
+ continue;
+ }
+ Err(e) => {
+ return Err(Error::ReadError(e));
+ }
+ }
+ }
+ Err(Error::TimeoutError)
+ }
+
+ fn parse_response<'a>(buffer: &'a [u8]) -> Option<EchoReplyPacket<'a>> {
+ let icmp_checksum = icmp::checksum(&IcmpPacket::new(buffer)?);
+ let reply = EchoReplyPacket::new(buffer)?;
+ if reply.get_checksum() == icmp_checksum {
+ Some(reply)
+ } else {
+ None
+ }
+ }
+}