diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/ethertype.rs | 20 | ||||
-rw-r--r-- | src/ippacket.rs | 99 | ||||
-rw-r--r-- | src/layer3.rs | 65 | ||||
-rw-r--r-- | src/main.rs | 186 |
4 files changed, 370 insertions, 0 deletions
diff --git a/src/ethertype.rs b/src/ethertype.rs new file mode 100644 index 0000000..0d728f3 --- /dev/null +++ b/src/ethertype.rs @@ -0,0 +1,20 @@ +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum EtherType { + /// The frame type is IEEE 802.3 and this is it's length + Length(u16), + IPv4, + IPv6, + Unknown(u16), +} + +impl EtherType { + //TODO: check ethertype is correct + pub fn new(n: u16) -> Self { + match n { + n if n <= 1500 => Self::Length(n), + 0x0800 => Self::IPv4, + 0x86DD => Self::IPv6, + n => Self::Unknown(n), + } + } +} diff --git a/src/ippacket.rs b/src/ippacket.rs new file mode 100644 index 0000000..60858d2 --- /dev/null +++ b/src/ippacket.rs @@ -0,0 +1,99 @@ +use std::net::Ipv4Addr; + +pub struct Ipv4Packet<'p> { + /// The computed size of the IP packet + header_length: usize, + /// The total length of the IP packet when all fragments are combined + total_length: u16, + more_fragments: bool, + fragment_offset: usize, + next_header: IpNextHeader, + pub src: Ipv4Addr, + pub dst: Ipv4Addr, + payload: &'p [u8], +} + +impl<'p> Ipv4Packet<'p> { + pub fn new(buffer: &'p [u8]) -> Self { + let ihl = buffer[0].to_be() & 0b0000_1111; + let header_length = ihl as usize * 4; + + let total_length = u16::from_be_bytes([buffer[2], buffer[3]]); + + // Fragmentation + let more_fragments = (buffer[6] & 0b0010_0000) > 0; + let fragment_offset = u16::from_be_bytes([buffer[6] & 0b0001_1111, buffer[7]]); + // Fragments are in units of 8 bytes + let true_frag_offset = fragment_offset as usize * 8; + + let next_header = IpNextHeader::new(buffer[9]); + + let src = Ipv4Addr::new(buffer[12], buffer[13], buffer[14], buffer[15]); + let dst = Ipv4Addr::new(buffer[16], buffer[17], buffer[18], buffer[19]); + + Self { + header_length, + total_length, + more_fragments, + fragment_offset: true_frag_offset, + next_header, + src, + dst, + payload: &buffer[header_length..], + } + } + + pub fn get_source(&self) -> Ipv4Addr { + self.src + } + + pub fn get_destination(&self) -> Ipv4Addr { + self.dst + } + + pub fn has_more_fragments(&self) -> bool { + self.more_fragments + } + + pub fn get_fragment_offset(&self) -> usize { + self.fragment_offset + } + + pub fn get_next_header(&self) -> IpNextHeader { + self.next_header + } + + pub fn get_payload(&self) -> &[u8] { + &self.payload + } + + pub fn get_packet_len(&self) -> usize { + self.total_length as usize + } + + pub fn get_payload_len(&self) -> usize { + // An IPv4 header is always 20 bytes long + self.total_length as usize - 20 + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum IpNextHeader { + Icmp, + Igmp, + Tcp, + Udp, + Unknown(u8), +} + +impl IpNextHeader { + pub fn new(n: u8) -> Self { + match n { + 1 => Self::Icmp, + 2 => Self::Igmp, + 6 => Self::Tcp, + 17 => Self::Udp, + n => Self::Unknown(n), + } + } +} diff --git a/src/layer3.rs b/src/layer3.rs new file mode 100644 index 0000000..7521c0d --- /dev/null +++ b/src/layer3.rs @@ -0,0 +1,65 @@ +pub struct Tcp<'p> { + src: u16, + dst: u16, + payload: &'p [u8], +} + +impl<'p> Tcp<'p> { + pub fn new(buffer: &'p [u8]) -> Self { + let src = u16::from_be_bytes([buffer[0], buffer[1]]); + let dst = u16::from_be_bytes([buffer[2], buffer[3]]); + let data_offset = (buffer[12].to_be() & 0b1111_0000) >> 4; + // Offset is number of 32-bit words + let true_offset = data_offset as usize * 4; + + Self { + src, + dst, + payload: &buffer[true_offset..], + } + } + + pub fn source_port(&self) -> u16 { + self.src + } + + pub fn destination_port(&self) -> u16 { + self.dst + } + + pub fn payload(&self) -> &[u8] { + self.payload + } +} + +pub struct Udp<'p> { + src: u16, + dst: u16, + payload: &'p [u8], +} + +impl<'p> Udp<'p> { + pub fn new(buffer: &'p [u8]) -> Self { + let src = u16::from_be_bytes([buffer[0], buffer[1]]); + let dst = u16::from_be_bytes([buffer[2], buffer[3]]); + let _len = u16::from_be_bytes([buffer[4], buffer[5]]); + + Self { + src, + dst, + payload: &buffer[8..], + } + } + + pub fn source_port(&self) -> u16 { + self.src + } + + pub fn destination_port(&self) -> u16 { + self.dst + } + + pub fn payload(&self) -> &[u8] { + self.payload + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..defd70b --- /dev/null +++ b/src/main.rs @@ -0,0 +1,186 @@ +use std::{ + error::Error, + net::{Ipv4Addr, Shutdown}, + sync::mpsc::{self, Receiver, Sender, channel}, + thread::JoinHandle, + time::{Duration, Instant}, +}; + +use ethertype::EtherType; +use ippacket::{IpNextHeader, Ipv4Packet}; +use layer3::{Tcp, Udp}; +use pnet_datalink::{Channel, Config}; +use scurvy::{Argument, Scurvy}; + +mod ethertype; +mod ippacket; +mod layer3; + +fn main() -> Result<(), Box<dyn Error>> { + let args = vec![ + Argument::new(&["interface", "iface"]).arg("dev"), + Argument::new("ip").arg("addr"), + ]; + let scurvy = Scurvy::make(args); + + let interface_name = scurvy.get("interface").unwrap(); + let interface = match pnet_datalink::interfaces().iter().find(|i| i.name == interface_name) { + None => { + eprintln!("No interface found named '{interface_name}'"); + return Ok(()); + } + Some(i) => i.clone(), + }; + + let ip_want: Ipv4Addr = scurvy.get("ip").unwrap().parse()?; + + let mut channel = match pnet_datalink::channel(&interface, Config::default())? { + Channel::Ethernet(_tx, rx) => rx, + _ => unimplemented!(), + }; + + let stat = stat_thread(); + + let tx_clone = stat.tx.clone(); + std::thread::spawn(move || { + std::thread::sleep(Duration::from_secs(30)); + tx_clone.send(Meow::Show).unwrap(); + }); + + loop { + let pkt = channel.next()?; + + let ethertype = EtherType::new(u16::from_be_bytes([pkt[12], pkt[13]])); + let eth_payload = &pkt[14..]; + + match ethertype { + EtherType::IPv4 => { + let ip = Ipv4Packet::new(eth_payload); + let ip_payload = ip.get_payload(); + + // 6 byte per MAC (x2), 2 byte ethertype, 2 byte crc + let total_l2_len = ip.get_packet_len() + 18; + + match ip.get_next_header() { + IpNextHeader::Tcp => { + let tcp = Tcp::new(ip_payload); + + let tcp_tx = if ip.src == ip_want { + true + } else if ip.dst == ip_want { + false + } else { + continue; + }; + + stat.tx.send(Meow::Tcp { + tx: tcp_tx, + src: tcp.source_port(), + dst: tcp.destination_port(), + len: total_l2_len, + })?; + } + IpNextHeader::Udp => { + let udp = Udp::new(ip_payload); + + let udp_tx = if ip.src == ip_want { + true + } else if ip.dst == ip_want { + false + } else { + continue; + }; + + stat.tx.send(Meow::Udp { + tx: udp_tx, + src: udp.source_port(), + dst: udp.destination_port(), + len: total_l2_len, + })?; + } + _ => (), + } + } + _ => (), + } + } +} + +struct StatHandle { + hwnd: JoinHandle<()>, + tx: Sender<Meow>, +} + +enum Meow { + Tcp { + tx: bool, + src: u16, + dst: u16, + len: usize, + }, + Udp { + tx: bool, + src: u16, + dst: u16, + len: usize, + }, + Show, + Shutdown, +} + +fn stat_thread() -> StatHandle { + let (tx, rx) = channel(); + + let hwnd = std::thread::spawn(|| stat(rx)); + + StatHandle { hwnd, tx } +} + +fn stat(rx: Receiver<Meow>) { + let mut tcp_tx = vec![0; u16::MAX as usize]; + let mut tcp_rx = vec![0; u16::MAX as usize]; + + let mut last_print = Instant::now(); + loop { + if last_print.elapsed() > Duration::from_secs(10) { + let http_s = tcp_rx[80] + tcp_rx[443]; + let http_s_kb = http_s / 1000; + + let http_s_tx = tcp_tx[80] + tcp_tx[443]; + let http_s_tx_kb = http_s_tx / 1000; + + println!("HTTP(S) rx {}kB // tx {}kB", http_s_kb, http_s_tx_kb); + + last_print = Instant::now(); + } + + match rx.recv() { + Err(_e) => { + eprintln!("error receiving! breaking from loop"); + break; + } + Ok(Meow::Show) => { + if last_print.elapsed() > Duration::from_secs(30) { + println!("Activated by Meow::Show"); + } + } + Ok(Meow::Shutdown) => { + eprintln!("got shutdown! breaking from loop"); + break; + } + Ok(Meow::Tcp { + tx: tcp_tx_flag, + src, + dst, + len, + }) => { + if tcp_tx_flag { + tcp_tx[src as usize] += len; + } else { + tcp_rx[dst as usize] += len; + } + } + _ => (), + } + } +} |