diff options
| author | Emīls <emils@mullvad.net> | 2021-05-06 10:46:04 +0100 |
|---|---|---|
| committer | Emīls <emils@mullvad.net> | 2021-05-12 13:57:35 +0100 |
| commit | d53c2926fdab33637f8f290acb07983cfe71e87c (patch) | |
| tree | f512d9eebc944514e018b327d14acc47210c9375 /talpid-core/src | |
| parent | 057e5966e24104be2f033abebe66729ab21f251d (diff) | |
| download | mullvadvpn-d53c2926fdab33637f8f290acb07983cfe71e87c.tar.xz mullvadvpn-d53c2926fdab33637f8f290acb07983cfe71e87c.zip | |
Use ICMP socket on Linux
Diffstat (limited to 'talpid-core/src')
| -rw-r--r-- | talpid-core/src/lib.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/ping_monitor/icmp.rs | 239 | ||||
| -rw-r--r-- | talpid-core/src/ping_monitor/mod.rs | 8 | ||||
| -rw-r--r-- | talpid-core/src/ping_monitor/win.rs | 120 |
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) - } -} |
