summaryrefslogtreecommitdiffhomepage
path: root/talpid-core/src
diff options
context:
space:
mode:
authorEmīls <emils@mullvad.net>2021-05-06 10:46:04 +0100
committerEmīls <emils@mullvad.net>2021-05-12 13:57:35 +0100
commitd53c2926fdab33637f8f290acb07983cfe71e87c (patch)
treef512d9eebc944514e018b327d14acc47210c9375 /talpid-core/src
parent057e5966e24104be2f033abebe66729ab21f251d (diff)
downloadmullvadvpn-d53c2926fdab33637f8f290acb07983cfe71e87c.tar.xz
mullvadvpn-d53c2926fdab33637f8f290acb07983cfe71e87c.zip
Use ICMP socket on Linux
Diffstat (limited to 'talpid-core/src')
-rw-r--r--talpid-core/src/lib.rs2
-rw-r--r--talpid-core/src/ping_monitor/icmp.rs239
-rw-r--r--talpid-core/src/ping_monitor/mod.rs8
-rw-r--r--talpid-core/src/ping_monitor/win.rs120
4 files changed, 245 insertions, 124 deletions
diff --git a/talpid-core/src/lib.rs b/talpid-core/src/lib.rs
index fbf06c8815..e4ac42857b 100644
--- a/talpid-core/src/lib.rs
+++ b/talpid-core/src/lib.rs
@@ -60,4 +60,4 @@ mod mktemp;
mod linux;
/// A pair of functions to monitor and establish connectivity with ICMP
-mod ping_monitor;
+pub mod ping_monitor;
diff --git a/talpid-core/src/ping_monitor/icmp.rs b/talpid-core/src/ping_monitor/icmp.rs
new file mode 100644
index 0000000000..2965231219
--- /dev/null
+++ b/talpid-core/src/ping_monitor/icmp.rs
@@ -0,0 +1,239 @@
+use byteorder::{NetworkEndian, WriteBytesExt};
+use rand::Rng;
+use socket2::{Domain, Protocol, Socket, Type};
+#[cfg(target_os = "linux")]
+use std::ffi::CString;
+use std::{
+ io::{self, Write},
+ net::{Ipv4Addr, SocketAddr},
+ thread,
+ time::Duration,
+};
+
+const SEND_RETRY_ATTEMPTS: u32 = 10;
+
+/// Pinger errors
+#[derive(err_derive::Error, Debug)]
+#[error(no_from)]
+pub enum Error {
+ /// Failed to open raw socket
+ #[error(display = "Failed to open ICMP socket")]
+ OpenError(#[error(source)] io::Error),
+
+ /// Failed to read from raw socket
+ #[error(display = "Failed to read ICMP socket")]
+ ReadError(#[error(source)] io::Error),
+
+ /// Failed to set socket options
+ #[error(display = "Failed to set socket options")]
+ SocketOptError(#[error(source)] io::Error),
+
+ /// Failed to write to raw socket
+ #[error(display = "Failed to write to socket")]
+ WriteError(#[error(source)] io::Error),
+
+ /// ICMP buffer too small
+ #[error(display = "ICMP message buffer too small")]
+ BufferTooSmall,
+
+ /// Interface name contains null bytes
+ #[error(display = "Interface name contains a null byte")]
+ InterfaceNameContainsNull,
+}
+
+type Result<T> = std::result::Result<T, Error>;
+
+pub struct Pinger {
+ sock: Socket,
+ addr: SocketAddr,
+ id: u16,
+ seq: u16,
+}
+
+impl Pinger {
+ #[cfg(target_os = "windows")]
+ pub fn new(addr: Ipv4Addr, _interface_name: String) -> Result<Self> {
+ let addr = SocketAddr::new(addr.into(), 0);
+ 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,
+ })
+ }
+
+ #[cfg(target_os = "linux")]
+ pub fn new(addr: Ipv4Addr, interface_name: String) -> Result<Self> {
+ let addr = SocketAddr::new(addr.into(), 0);
+ let sock = Socket::new(Domain::ipv4(), Type::raw(), Some(Protocol::icmpv4()))
+ .map_err(Error::OpenError)?;
+ sock.set_nonblocking(true).map_err(Error::OpenError)?;
+
+ let cname = CString::new(interface_name.as_bytes().to_vec())
+ .map_err(|_| Error::InterfaceNameContainsNull)?;
+ sock.bind_device(Some(&cname))
+ .map_err(Error::SocketOptError)?;
+
+ Ok(Self {
+ addr,
+ sock,
+ seq: 0,
+ id: rand::random(),
+ })
+ }
+
+ fn send_ping_request(&mut self, message: &[u8], destination: SocketAddr) -> Result<()> {
+ let mut tries = 0;
+ let mut result = Ok(());
+ while tries < SEND_RETRY_ATTEMPTS {
+ match self.sock.send_to(message, &destination.into()) {
+ Ok(_) => {
+ return Ok(());
+ }
+ Err(err) => {
+ if Some(10065) != err.raw_os_error() {
+ return Err(Error::WriteError(err));
+ }
+ result = Err(Error::WriteError(err));
+ }
+ }
+ thread::sleep(Duration::from_secs(1));
+ tries += 1;
+ }
+ result
+ }
+
+ fn construct_icmpv4_packet(&mut self, buffer: &mut [u8]) -> Result<()> {
+ if !construct_icmpv4_packet_inner(buffer, self) {
+ return Err(Error::BufferTooSmall);
+ }
+ Ok(())
+ }
+}
+
+impl super::Pinger for Pinger {
+ fn send_icmp(&mut self) -> Result<()> {
+ let mut message = [0u8; 50];
+ self.construct_icmpv4_packet(&mut message)?;
+ self.send_ping_request(&message, self.addr)
+ }
+}
+
+trait PayloadWriter {
+ fn packet_id(&mut self) -> u16;
+ fn sequence_num(&mut self) -> u16;
+ fn write_payload(&mut self, buffer: &mut [u8]);
+}
+
+impl PayloadWriter for Pinger {
+ fn packet_id(&mut self) -> u16 {
+ self.id
+ }
+
+ fn sequence_num(&mut self) -> u16 {
+ let seq = self.seq;
+ self.seq += 1;
+ seq
+ }
+
+ fn write_payload(&mut self, buffer: &mut [u8]) {
+ rand::thread_rng().fill(buffer);
+ }
+}
+
+fn construct_icmpv4_packet_inner(
+ buffer: &mut [u8],
+ packet_writer: &mut impl PayloadWriter,
+) -> bool {
+ const ICMP_CHECKSUM_OFFSET: usize = 2;
+ if buffer.len() < 14 {
+ return false;
+ }
+
+ let mut writer = &mut buffer[..];
+ // ICMP type - Echo (ping) request
+ writer.write_u8(0x08).unwrap();
+ // Code - 0
+ writer.write_u8(0x00).unwrap();
+ // Checksum -filled in later
+ writer.write_u16::<NetworkEndian>(0x000).unwrap();
+ // packet ID
+ writer
+ .write_u16::<NetworkEndian>(packet_writer.packet_id())
+ .unwrap();
+ // packet sequence number
+ writer
+ .write_u16::<NetworkEndian>(packet_writer.sequence_num())
+ .unwrap();
+ // payload
+ packet_writer.write_payload(writer);
+
+ let checksum = internet_checksum::checksum(buffer);
+ (&mut buffer[ICMP_CHECKSUM_OFFSET..])
+ .write(&checksum)
+ .unwrap();
+
+ true
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ struct TestPayload {}
+
+ impl PayloadWriter for TestPayload {
+ fn packet_id(&mut self) -> u16 {
+ 0x1dcd
+ }
+
+ fn sequence_num(&mut self) -> u16 {
+ 0x0001
+ }
+
+ fn write_payload(&mut self, mut buffer: &mut [u8]) {
+ let _ = buffer.write(&[
+ 0xb6, 0xe0, 0x87, 0x60, 0x00, 0x00, 0x00, 0x00, 0x97, 0xad, 0x09, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
+ 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ ]);
+ }
+ }
+
+ #[test]
+ fn test_icmpv4_packet() {
+ // captured from a plain `ping -4 127.1`
+ let expected_packet = [
+ // ICMP type - echo request
+ 0x08, // Code 0
+ 0x00, // checksum
+ 0x3c, 0x70, // packet ID
+ 0x1d, 0xcd, // sequence number
+ 0x00, 0x01, // payload
+ 0xb6, 0xe0, 0x87, 0x60, 0x00, 0x00, 0x00, 0x00, 0x97, 0xad, 0x09, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
+ 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+ ];
+
+ let mut buffer = [0u8; 64];
+ assert!(construct_icmpv4_packet_inner(
+ &mut buffer[..],
+ &mut TestPayload {}
+ ));
+ assert_eq!(buffer, expected_packet);
+ }
+
+ #[test]
+ fn test_icmpv4_packet_too_short() {
+ assert!(!construct_icmpv4_packet_inner(
+ &mut [0u8; 13],
+ &mut TestPayload {}
+ ));
+ }
+}
diff --git a/talpid-core/src/ping_monitor/mod.rs b/talpid-core/src/ping_monitor/mod.rs
index c5e2a256e5..2a6f5afbee 100644
--- a/talpid-core/src/ping_monitor/mod.rs
+++ b/talpid-core/src/ping_monitor/mod.rs
@@ -1,14 +1,15 @@
-#[cfg(any(target_os = "android", target_os = "macos", target_os = "linux"))]
+#[cfg(any(target_os = "android", target_os = "macos"))]
#[path = "unix.rs"]
mod imp;
-#[cfg(target_os = "windows")]
-#[path = "win.rs"]
+#[cfg(any(target_os = "windows", target_os = "linux"))]
+#[path = "icmp.rs"]
mod imp;
pub use imp::Error;
+/// Trait for sending ICMP requests to get some traffic from a remote server
pub trait Pinger: Send {
/// Sends an ICMP packet
fn send_icmp(&mut self) -> Result<(), Error>;
@@ -16,6 +17,7 @@ pub trait Pinger: Send {
fn reset(&mut self) {}
}
+/// Create a new pinger
pub fn new_pinger(
addr: std::net::Ipv4Addr,
interface_name: String,
diff --git a/talpid-core/src/ping_monitor/win.rs b/talpid-core/src/ping_monitor/win.rs
deleted file mode 100644
index e2564b3943..0000000000
--- a/talpid-core/src/ping_monitor/win.rs
+++ /dev/null
@@ -1,120 +0,0 @@
-use pnet_packet::{
- icmp::{
- self,
- echo_request::{EchoRequestPacket, MutableEchoRequestPacket},
- IcmpCode, IcmpPacket, IcmpType,
- },
- Packet,
-};
-use socket2::{Domain, Protocol, Socket, Type};
-use std::{
- io,
- net::{IpAddr, Ipv4Addr, SocketAddr},
- thread,
- time::Duration,
-};
-
-const SEND_RETRY_ATTEMPTS: u32 = 10;
-
-#[derive(err_derive::Error, Debug)]
-#[error(no_from)]
-pub enum Error {
- /// Failed to open raw socket
- #[error(display = "Failed to open raw socket")]
- OpenError(#[error(source)] io::Error),
-
- /// Failed to read from raw socket
- #[error(display = "Failed to read from socket")]
- ReadError(#[error(source)] io::Error),
-
- /// Failed to write to raw socket
- #[error(display = "Failed to write to socket")]
- WriteError(#[error(source)] io::Error),
-
- #[error(display = "Timed out")]
- TimeoutError,
-}
-
-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, _interface_name: String) -> 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,
- })
- }
-
- fn send_ping_request(
- &mut self,
- request: &EchoRequestPacket<'static>,
- destination: SocketAddr,
- ) -> Result<()> {
- let mut tries = 0;
- let mut result = Ok(());
- while tries < SEND_RETRY_ATTEMPTS {
- match self.sock.send_to(request.packet(), &destination.into()) {
- Ok(_) => {
- return Ok(());
- }
- Err(err) => {
- if Some(10065) != err.raw_os_error() {
- return Err(Error::WriteError(err));
- }
- result = Err(Error::WriteError(err));
- }
- }
- thread::sleep(Duration::from_secs(1));
- tries += 1;
- }
- result
- }
-
- /// returns the next ping packet
- fn next_ping_request(&mut self) -> EchoRequestPacket<'static> {
- use rand::Rng;
- const ICMP_HEADER_LENGTH: usize = 8;
- const ICMP_PAYLOAD_LENGTH: usize = 150;
- const ICMP_PACKET_LENGTH: usize = ICMP_HEADER_LENGTH + ICMP_PAYLOAD_LENGTH;
- let mut payload = [0u8; ICMP_PAYLOAD_LENGTH];
- rand::thread_rng().fill(&mut payload[..]);
- 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
- }
-}
-
-impl super::Pinger for Pinger {
- fn send_icmp(&mut self) -> Result<()> {
- let dest = SocketAddr::new(IpAddr::from(self.addr), 0);
- let request = self.next_ping_request();
- self.send_ping_request(&request, dest)
- }
-}