diff --git a/Cargo.lock b/Cargo.lock index 799914f..4a753c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,30 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -24,10 +42,14 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" name = "cake-autorate-rust" version = "0.1.0" dependencies = [ + "bincode", "byteorder", "libc", "neli", "nix", + "pnet", + "pnet_transport", + "serde", ] [[package]] @@ -42,6 +64,21 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "ipnetwork" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f84f1612606f3753f205a4e9a2efd6fe5b4c573a6269b2cc6c3003d44a0d127" +dependencies = [ + "serde", +] + [[package]] name = "libc" version = "0.2.132" @@ -57,6 +94,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + [[package]] name = "memoffset" version = "0.6.5" @@ -103,12 +146,107 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "no-std-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" + [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pnet" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0caaf5b11fd907ff15cf14a4477bfabca4b37ab9e447a4f8dead969a59cdafad" +dependencies = [ + "pnet_base", + "pnet_datalink", + "pnet_packet", + "pnet_transport", +] + +[[package]] +name = "pnet_base" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d3a993d49e5fd5d4d854d6999d4addca1f72d86c65adf224a36757161c02b6" +dependencies = [ + "no-std-net", +] + +[[package]] +name = "pnet_datalink" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e466faf03a98ad27f6e15cd27a2b7cc89e73e640a43527742977bc503c37f8aa" +dependencies = [ + "ipnetwork", + "libc", + "pnet_base", + "pnet_sys", + "winapi", +] + +[[package]] +name = "pnet_macros" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd52a5211fac27e7acb14cfc9f30ae16ae0e956b7b779c8214c74559cef4c3" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "pnet_macros_support" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89de095dc7739349559913aed1ef6a11e73ceade4897dadc77c5e09de6740750" +dependencies = [ + "pnet_base", +] + +[[package]] +name = "pnet_packet" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc3b5111e697c39c8b9795b9fdccbc301ab696699e88b9ea5a4e4628978f495f" +dependencies = [ + "glob", + "pnet_base", + "pnet_macros", + "pnet_macros_support", +] + +[[package]] +name = "pnet_sys" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328e231f0add6d247d82421bf3790b4b33b39c8930637f428eef24c4c6a90805" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "pnet_transport" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff597185e6f1f5671b3122e4dba892a1c73e17c17e723d7669bd9299cbe7f124" +dependencies = [ + "libc", + "pnet_base", + "pnet_packet", + "pnet_sys", +] + [[package]] name = "proc-macro2" version = "1.0.43" @@ -128,19 +266,36 @@ dependencies = [ ] [[package]] -name = "serde" -version = "1.0.143" +name = "regex" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "serde" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.143" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" dependencies = [ "proc-macro2", "quote", @@ -163,3 +318,25 @@ name = "unicode-ident" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index b27ec7a..1438e57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,10 +13,14 @@ strip = "symbols" [dependencies] +bincode = "1.3.3" byteorder = "1.4.3" libc = "0.2.132" neli = "0.6.2" nix = { version = "0.25.0", features = ["net", "socket", "time"] } +pnet = "0.31.0" +pnet_transport = "0.31.0" +serde = { version = "1.0.144", features = ["derive"] } [patch.crates-io] nix = { path = "../nix" } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 6b3d1a3..2723c3a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,14 +4,18 @@ mod cake; mod config; mod netlink; mod pinger; +mod pinger_icmp; +mod pinger_icmp_ts; use std::net::IpAddr; use std::str::FromStr; -use std::{thread, time}; +use std::{process, thread, time}; use crate::config::Config; -use crate::netlink::{find_interface, find_qdisc, set_qdisc_rate}; -use crate::pinger::{Pinger, PingerICMPEcho}; +use crate::netlink::{find_interface, find_qdisc, get_interface_stats, set_qdisc_rate}; +use crate::pinger::Pinger; +use crate::pinger_icmp::PingerICMPEcho; +use crate::pinger_icmp_ts::PingerICMPTimestamps; #[macro_export] macro_rules! error { @@ -30,7 +34,7 @@ fn main() { println!("starting up"); let config = Config::new(); - test(config); + test(&config); let mut reflectors: Vec = Vec::new(); @@ -39,18 +43,30 @@ fn main() { reflectors.push(IpAddr::from_str("9.9.9.9").unwrap()); reflectors.push(IpAddr::from_str("9.9.9.10").unwrap()); - let pinger = PingerICMPEcho::new(reflectors); - let pinger_receiver = pinger.clone(); - let pinger_sender = pinger.clone(); + get_interface_stats(config.upload_interface.as_str()).unwrap(); - let receiver_handle = thread::spawn(move || pinger_receiver.receive_loop()); - let sender_handle = thread::spawn(move || pinger_sender.sender_loop()); + let mut pinger_receiver = + PingerICMPTimestamps::new((process::id() & 0xFFFF) as u16, reflectors.clone()); + let mut pinger_sender = + PingerICMPTimestamps::new((process::id() & 0xFFFF) as u16, reflectors.clone()); - receiver_handle.join().unwrap(); - sender_handle.join().unwrap(); + let receiver_handle = thread::Builder::new() + .name("receiver".to_string()) + .spawn(move || pinger_receiver.receive_loop()) + .unwrap(); + let sender_handle = thread::Builder::new() + .name("sender".to_string()) + .spawn(move || pinger_sender.sender_loop()) + .unwrap(); + + let threads = vec![receiver_handle, sender_handle]; + + for thread in threads { + thread.join().unwrap(); + } } -fn test(config: Config) { +fn test(config: &Config) { if true { return; } diff --git a/src/netlink.rs b/src/netlink.rs index b69d552..1c991e4 100644 --- a/src/netlink.rs +++ b/src/netlink.rs @@ -8,9 +8,11 @@ use neli::nl::{NlPayload, Nlmsghdr}; use neli::rtnl::{Ifinfomsg, Rtattr, Tcmsg}; use neli::socket::NlSocketHandle; use neli::types::{Buffer, RtBuffer}; +use serde::Deserialize; use std::error::Error; use std::fmt::Display; +use bincode::deserialize; use std::fmt; #[derive(Default, Debug)] @@ -30,6 +32,34 @@ impl Display for NoQdiscFoundError { } } +#[derive(Deserialize, Copy, Clone, Default, Debug)] +pub struct RtnlLinkStats64 { + pub rx_packets: u64, + pub tx_packets: u64, + pub rx_bytes: u64, + pub tx_bytes: u64, + pub rx_errors: u64, + pub tx_errors: u64, + pub rx_dropped: u64, + pub tx_dropped: u64, + pub multicast: u64, + pub collisions: u64, + pub rx_length_errors: u64, + pub rx_over_errors: u64, + pub rx_crc_errors: u64, + pub rx_frame_errors: u64, + pub rx_fifo_errors: u64, + pub rx_missed_errors: u64, + pub tx_aborted_errors: u64, + pub tx_carrier_errors: u64, + pub tx_fifo_errors: u64, + pub tx_heartbeat_errors: u64, + pub tx_window_errors: u64, + pub rx_compressed: u64, + pub tx_compressed: u64, + pub rx_nohandler: u64, +} + #[allow(dead_code)] pub struct Qdisc { ifindex: i32, @@ -39,8 +69,7 @@ pub struct Qdisc { parent: u32, } -pub fn find_interface(ifname: &str) -> Result> { - let mut socket = NlSocketHandle::connect(NlFamily::Route, None, &[]).unwrap(); +fn nl_interface_get(socket: &mut NlSocketHandle, ifname: &str) -> Result<(), Box> { let mut attrs = RtBuffer::new(); const RTEXT_FILTER_VF: i32 = 1 << 0; @@ -70,6 +99,14 @@ pub fn find_interface(ifname: &str) -> Result> { socket.send(nlhdr)?; + Ok(()) +} + +pub fn find_interface(ifname: &str) -> Result> { + let mut socket = NlSocketHandle::connect(NlFamily::Route, None, &[]).unwrap(); + + nl_interface_get(&mut socket, ifname).expect("Wrong when sending intf"); + for response in socket.iter(false) { let header: Nlmsghdr = response?; @@ -86,6 +123,40 @@ pub fn find_interface(ifname: &str) -> Result> { Ok(-1) } +pub fn get_interface_stats(ifname: &str) -> Result> { + let mut socket = NlSocketHandle::connect(NlFamily::Route, None, &[]).unwrap(); + + nl_interface_get(&mut socket, ifname).expect("Wrong when sending intf"); + + for response in socket.iter(false) { + let header: Nlmsghdr = response?; + + if header.nl_type != Rtm::Newlink { + return Err(Box::new(NlError::msg("Netlink error retrieving link"))); + } + + if let NlPayload::Payload(p) = header.nl_payload { + for attr in p.rtattrs.iter() { + //println!("{:?}", attr.rta_type); + + if attr.rta_type == Ifla::Stats64 { + println!("{:?}", attr.rta_payload.as_ref()); + let buf = attr.rta_payload.as_ref(); + + let stats: RtnlLinkStats64 = deserialize(buf).unwrap(); + + println!("stats rx: {}", stats.rx_bytes); + println!("stats tx: {}", stats.tx_bytes); + + return Ok(stats); + } + } + } + } + + Err(Box::new(NlError::msg("Error getting stats"))) +} + pub fn find_qdisc(ifindex: i32) -> Result> { let mut socket = NlSocketHandle::connect(NlFamily::Route, None, &[]).unwrap(); let tc_msg = Tcmsg::new( diff --git a/src/pinger.rs b/src/pinger.rs index 77ce175..d062a75 100644 --- a/src/pinger.rs +++ b/src/pinger.rs @@ -1,215 +1,9 @@ -use byteorder::{BigEndian, ReadBytesExt}; -use nix::sys::socket::{ - recvfrom, sendto, AddressFamily, MsgFlags, SockFlag, SockProtocol, SockType, SockaddrIn, - SockaddrIn6, SockaddrLike, -}; -use nix::sys::time::TimeValLike; -use nix::time::{clock_gettime, ClockId}; use std::error::Error; -use std::mem::size_of; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6}; -use std::os::unix::io::RawFd; -use std::str::FromStr; -use std::{thread, time}; - -struct ICMP { - icmp_type: u8, - code: u8, - checksum: u16, - identifier: u16, - sequence: u16, - payload: u64, -} - -impl ICMP { - fn to_bytes(&self) -> Box<[u8; 16]> { - let checksum = self.checksum.to_be_bytes(); - let identifier = self.identifier.to_be_bytes(); - let sequence = self.sequence.to_be_bytes(); - let payload = self.payload.to_be_bytes(); - - let buf = [ - self.icmp_type, - self.code, - checksum[0], - checksum[1], - identifier[0], - identifier[1], - sequence[0], - sequence[1], - payload[0], - payload[1], - payload[2], - payload[3], - payload[4], - payload[5], - payload[6], - payload[7], - ]; - - return Box::new(buf); - } -} - -impl From<&[u8]> for ICMP { - fn from(buf: &[u8]) -> Self { - ICMP { - icmp_type: buf[0], - code: buf[1], - checksum: (&buf[2..3]).read_u16::().unwrap(), - identifier: (&buf[4..5]).read_u16::().unwrap(), - sequence: (&buf[6..7]).read_u16::().unwrap(), - payload: (&buf[8..16]).read_u64::().unwrap(), - } - } -} - -impl From<&mut [u8]> for ICMP { - fn from(buf: &mut [u8]) -> Self { - ICMP { - icmp_type: buf[0], - code: buf[1], - checksum: (&buf[2..3]).read_u16::().unwrap(), - identifier: (&buf[4..5]).read_u16::().unwrap(), - sequence: (&buf[6..7]).read_u16::().unwrap(), - payload: (&buf[8..16]).read_u64::().unwrap(), - } - } -} +use std::net::IpAddr; pub trait Pinger { - fn new(reflectors: Vec) -> Self; - fn receive_loop(&self); - fn receive_ping(&self) -> Result<(), Box>; - fn sender_loop(&self); - fn send_ping(&self, reflector: &IpAddr, seq: u16) -> Result<(), Box>; -} - -#[derive(Clone)] -pub struct PingerICMPEcho { - reflectors: Vec, - socket: RawFd, -} - -impl PingerICMPEcho { - fn calculate_checksum(buffer: &mut [u8]) -> u16 { - let mut sum = 0u32; - for word in buffer.chunks(2) { - let mut part = u16::from(word[0]) << 8; - if word.len() > 1 { - part += u16::from(word[1]); - } - sum = sum.wrapping_add(u32::from(part)); - } - - while (sum >> 16) > 0 { - sum = (sum & 0xffff) + (sum >> 16); - } - - !sum as u16 - } -} - -impl Pinger for PingerICMPEcho { - fn new(reflectors: Vec) -> Self { - let socket = nix::sys::socket::socket( - AddressFamily::Inet, - SockType::Raw, - SockFlag::empty(), - SockProtocol::ICMP, - ) - .expect("Couldn't open a socket"); - - PingerICMPEcho { reflectors, socket } - } - - fn receive_loop(&self) { - loop { - let mut v: Vec = vec![0; size_of::()]; - let buf = v.as_mut_slice(); - - println!("buf len: {}", buf.len()); - - let res = recvfrom::(self.socket, buf).unwrap(); - let remote = res.1.unwrap(); - - let ip = IpAddr::from(remote.ip().to_ne_bytes()); - - if self.reflectors.contains(&ip) { - println!("ip: {}", ip.to_string()); - println!("usize: {}", res.0); - println!("{:?}", buf); - - let hdr: ICMP = buf.into(); - println!("Type: {}", hdr.icmp_type); - } - } - } - - fn receive_ping(&self) -> Result<(), Box> { - // Process ping RTT - /*let ping_rtt = SystemTime::now() - .duration_since(ping_start_time) - .unwrap_or(Duration::from_secs(0));*/ - todo!() - } - - fn sender_loop(&self) { - let mut seq: u16 = 0; - let tick_duration_ms: u16 = 500; - let sleep_duration = time::Duration::from_millis(tick_duration_ms as u64); - - loop { - for reflector in self.reflectors.iter() { - self.send_ping(reflector, seq).expect("TODO: panic message"); - } - - thread::sleep(sleep_duration); - - seq += 1; - - if seq > std::u16::MAX { - seq = 0; - } - } - } - - fn send_ping(&self, reflector: &IpAddr, seq: u16) -> Result<(), Box> { - let time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap(); - - let mut hdr = ICMP { - icmp_type: 8, - code: 0, - checksum: 0, - identifier: 0xBABE, - sequence: seq, - payload: time.num_milliseconds() as u64, - }; - - let mut bytes = hdr.to_bytes(); - let buf: &mut [u8] = bytes.as_mut_slice(); - - hdr.checksum = PingerICMPEcho::calculate_checksum(buf); - let bytes = hdr.to_bytes(); - let buf = bytes.as_slice(); - - let addr: Box; - - match reflector.is_ipv4() { - true => { - let ip4 = Ipv4Addr::from_str(&*reflector.to_string()).unwrap(); - let sock4 = SocketAddrV4::new(ip4, 0); - addr = Box::new(SockaddrIn::from(sock4)); - } - false => { - let ip6 = Ipv6Addr::from_str(&*reflector.to_string()).unwrap(); - let sock6 = SocketAddrV6::new(ip6, 0, 0, 0); - addr = Box::new(SockaddrIn6::from(sock6)); - } - } - - sendto(self.socket, buf, addr.as_ref(), MsgFlags::empty()).expect("TODO: F message"); - - Ok(()) - } + fn new(id: u16, reflectors: Vec) -> Self; + fn receive_loop(&mut self); + fn sender_loop(&mut self); + fn send_ping(&mut self, reflector: &IpAddr, id: u16, seq: u16) -> Result<(), Box>; } diff --git a/src/pinger_icmp.rs b/src/pinger_icmp.rs new file mode 100644 index 0000000..0335f74 --- /dev/null +++ b/src/pinger_icmp.rs @@ -0,0 +1,124 @@ +use std::error::Error; +use std::net::{IpAddr, Ipv6Addr, SocketAddrV4, SocketAddrV6}; +use std::{process, thread, time}; + +use crate::Pinger; +use byteorder::*; +use nix::sys::time::TimeValLike; +use nix::time::{clock_gettime, ClockId}; +use pnet::packet::icmp::IcmpType; +use pnet::packet::icmp::IcmpTypes::EchoReply; +use pnet::packet::ip::IpNextHeaderProtocols; +use pnet::packet::*; +use pnet_transport::TransportChannelType::Layer4; +use pnet_transport::TransportProtocol::Ipv4; +use pnet_transport::*; + +pub struct PingerICMPEcho { + id: u16, + reflectors: Vec, + rx: TransportReceiver, + tx: TransportSender, +} + +impl Pinger for PingerICMPEcho { + fn new(id: u16, reflectors: Vec) -> Self { + let protocol = Layer4(Ipv4(IpNextHeaderProtocols::Icmp)); + + // Create a new transport channel, dealing with layer 4 packets on a test protocol + // It has a receive buffer of 4096 bytes. + let (tx, rx) = match transport_channel(4096, protocol) { + Ok((tx, rx)) => (tx, rx), + Err(e) => panic!( + "An error occurred when creating the transport channel: {}", + e + ), + }; + + PingerICMPEcho { + id, + reflectors, + rx, + tx, + } + } + + fn receive_loop(&mut self) { + let mut iter = icmp_packet_iter(&mut self.rx); + loop { + let (packet, sender) = match iter.next() { + Ok(res) => res, + Err(_) => continue, + }; + + let curr_time: u64 = clock_gettime(ClockId::CLOCK_MONOTONIC) + .unwrap() + .num_milliseconds() as u64; + + if packet.get_icmp_type() != EchoReply { + continue; + } + + let icmp_response = icmp::echo_reply::EchoReplyPacket::new(packet.packet()).unwrap(); + + if icmp_response.get_identifier() != self.id { + continue; + } + + let sent_time: u64 = icmp_response.payload().read_u64::().unwrap(); + let rtt = curr_time - sent_time; + + println!("Type: {:4} | Reflector IP: {:>15} | Seq: {:5} | Current time: {:8} | Sent time: {:8} | RTT: {:8}", "ICMP", sender.to_string(), icmp_response.get_sequence_number(), curr_time, sent_time, rtt); + } + } + + fn sender_loop(&mut self) { + println!("My ID is: {}", self.id); + + let mut seq: u16 = 0; + let tick_duration_ms: u16 = 500; + let sleep_duration = time::Duration::from_millis(tick_duration_ms as u64); + + loop { + let reflectors = self.reflectors.clone(); + + for reflector in reflectors.iter() { + self.send_ping(reflector, self.id, seq) + .expect("TODO: panic message"); + } + + thread::sleep(sleep_duration); + + seq += 1; + + if seq > u16::MAX { + seq = 0; + } + } + } + + fn send_ping(&mut self, reflector: &IpAddr, id: u16, seq: u16) -> Result<(), Box> { + let time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap(); + + let mut buf = [0u8; 8 + 56]; + + let mut packet = icmp::echo_request::MutableEchoRequestPacket::new(&mut buf).unwrap(); + + packet.populate(&icmp::echo_request::EchoRequest { + icmp_type: icmp::IcmpTypes::EchoRequest, + icmp_code: icmp::IcmpCode::new(0), + checksum: 0, + identifier: id, + sequence_number: seq, + payload: Vec::from(time.num_milliseconds().to_ne_bytes()), + }); + + packet.set_checksum(icmp::checksum( + &icmp::IcmpPacket::new(&packet.packet()).unwrap(), + )); + + self.tx.send_to(packet, *reflector).expect("Error sending"); + + Ok(()) + } +} diff --git a/src/pinger_icmp_ts.rs b/src/pinger_icmp_ts.rs new file mode 100644 index 0000000..5296ded --- /dev/null +++ b/src/pinger_icmp_ts.rs @@ -0,0 +1,153 @@ +use bincode::{deserialize, serialize}; +use std::error::Error; +use std::net::{IpAddr, Ipv6Addr, SocketAddrV4, SocketAddrV6}; +use std::{process, thread, time}; + +use crate::Pinger; +use byteorder::*; +use nix::sys::time::TimeValLike; +use nix::time::{clock_gettime, ClockId}; +use pnet::packet::icmp::IcmpTypes::{Timestamp, TimestampReply}; +use pnet::packet::ip::IpNextHeaderProtocols; +use pnet::packet::*; +use pnet_transport::TransportChannelType::Layer4; +use pnet_transport::TransportProtocol::Ipv4; +use pnet_transport::*; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize, Copy, Clone, Default, Debug)] +pub struct ICMPTimestamp { + type_: u8, + code: u8, + checksum: u16, + identifier: u16, + sequence: u16, + originate_time: u32, + receive_time: u32, + transmit_time: u32, +} + +pub struct PingerICMPTimestamps { + id: u16, + reflectors: Vec, + rx: TransportReceiver, + tx: TransportSender, +} + +impl Pinger for PingerICMPTimestamps { + fn new(id: u16, reflectors: Vec) -> Self { + let protocol = Layer4(Ipv4(IpNextHeaderProtocols::Icmp)); + + // Create a new transport channel, dealing with layer 4 packets on a test protocol + // It has a receive buffer of 4096 bytes. + let (tx, rx) = match transport_channel(4096, protocol) { + Ok((tx, rx)) => (tx, rx), + Err(e) => panic!( + "An error occurred when creating the transport channel: {}", + e + ), + }; + + PingerICMPTimestamps { + id, + reflectors, + rx, + tx, + } + } + + fn receive_loop(&mut self) { + let mut iter = icmp_packet_iter(&mut self.rx); + loop { + let (packet, sender) = match iter.next() { + Ok(res) => res, + Err(_) => continue, + }; + + let time = clock_gettime(ClockId::CLOCK_REALTIME).unwrap(); + let time_since_midnight: i64 = + ((time.tv_sec() % 86400 * 1000) + (time.tv_nsec() / 1000000)); + + if packet.get_icmp_type() != TimestampReply { + continue; + } + + let hdr: ICMPTimestamp = deserialize(packet.packet()).unwrap(); + + if hdr.identifier != self.id { + println!("wrong id: {} != {}", hdr.identifier, self.id); + continue; + } + + let rtt: i64 = time_since_midnight - hdr.originate_time as i64; + let dl_time: i64 = time_since_midnight - hdr.transmit_time as i64; + let ul_time: i64 = hdr.receive_time as i64 - hdr.originate_time as i64; + + println!("Type: {:4} | Reflector IP: {:>15} | Seq: {:5} | Current time: {:8} | Originate: {:8} | Received time: {:8} | Transmit time : {:8} | RTT: {:8} | UL time: {:5} | DL time: {:5}", "ICMP", sender.to_string(), hdr.sequence, time_since_midnight, hdr.originate_time, hdr.receive_time, hdr.transmit_time, rtt, ul_time, dl_time); + } + } + + fn sender_loop(&mut self) { + println!("My ID is: {}", self.id); + + let mut seq: u16 = 0; + let tick_duration_ms: u16 = 500; + let sleep_duration = time::Duration::from_millis(tick_duration_ms as u64); + + loop { + let reflectors = self.reflectors.clone(); + + for reflector in reflectors.iter() { + self.send_ping(reflector, self.id, seq) + .expect("TODO: panic message"); + } + + thread::sleep(sleep_duration); + + seq += 1; + + if seq > u16::MAX { + seq = 0; + } + } + } + + fn send_ping(&mut self, reflector: &IpAddr, id: u16, seq: u16) -> Result<(), Box> { + let mut buf = [0u8; 8 + 56]; + + let mut packet = pnet::packet::icmp::MutableIcmpPacket::new(&mut buf).unwrap(); + + let time = clock_gettime(ClockId::CLOCK_REALTIME).unwrap(); + let time_since_midnight: u32 = + ((time.tv_sec() % 86400 * 1000) + (time.tv_nsec() / 1000000)) as u32; + + let mut icmp = ICMPTimestamp { + type_: 255, + code: 255, + checksum: 65535, + identifier: id, + sequence: seq, + originate_time: time_since_midnight, + receive_time: 0, + transmit_time: 0, + }; + + let buf_v: Vec = serialize(&icmp).unwrap(); + let payload = &buf_v[4..]; + + packet.populate(&icmp::Icmp { + icmp_type: Timestamp, + icmp_code: icmp::IcmpCode::new(0), + checksum: 0, + payload: Vec::from(payload), + }); + + packet.set_checksum(icmp::checksum( + &icmp::IcmpPacket::new(&packet.packet()).unwrap(), + )); + + self.tx.send_to(packet, *reflector).expect("Error sending"); + + Ok(()) + } +}