about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ethertype.rs20
-rw-r--r--src/ippacket.rs99
-rw-r--r--src/layer3.rs65
-rw-r--r--src/main.rs186
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;
+				}
+			}
+			_ => (),
+		}
+	}
+}