use std::convert::From;
use std::fmt;
use std::io::{Read, Write};
use std::mem::transmute;
use std::net::Ipv4Addr;
use std::time::{Duration, SystemTime};
use bitflags::bitflags;
use thiserror::Error;
const PCAP_MAGIC: u32 = 0xA1B2_3C4D;
const PCAP_BAD_ENDIANNESS: u32 = 0x4D3C_B2A1;
#[derive(Debug, Error)]
pub enum IpParseErr {
#[error("Found version {0}, expected 4")]
Version(u8),
#[error("Found protocol {0}")]
Proto(u8),
#[error("IPv4 header is 20 bytes, only passed a buffer of size {0}")]
Size(usize),
#[error("Failed to parse the TCP payload")]
Tcp(#[from] TcpParseErr),
#[error("Error occurred while parsing UDP payload")]
Udp(#[from] UdpParseErr),
#[error("This library doesn't support ip options")]
Options,
#[error("Size field in packet is larger than data passed in ({0})")]
InvalidSize(usize),
#[error("IHL field was outside valid range of [6, 15] ({0})")]
InvalidIHL(usize),
#[error("Encountered IO error")]
IOError(#[from] std::io::Error),
#[error("PCAP file endianness does not match")]
EndianError,
#[error("Pcap file had bad magic: {0:0X}")]
BadMagic(u32),
#[error("Pcap file is invalid")]
PcapInvalid,
#[error("Pcap file too small")]
PcapFileSize,
}
#[non_exhaustive]
#[derive(Debug)]
pub enum DataType {
TCP(TcpPacket),
UDP(UdpDatagram),
}
impl DataType {
pub fn pcap_write<W: Write>(&self, f: &mut W) -> std::io::Result<()> {
match self {
Self::TCP(p) => p.pcap_write(f),
Self::UDP(p) => p.pcap_write(f),
}
}
pub fn get_proto_num(&self) -> u8 {
match self {
Self::TCP(_) => 6,
Self::UDP(_) => 17,
}
}
pub fn get_payload(&self) -> &Vec<u8> {
match self {
Self::TCP(p) => &p.data,
Self::UDP(p) => &p.payload,
}
}
}
impl From<TcpPacket> for DataType {
fn from(packet: TcpPacket) -> Self {
Self::TCP(packet)
}
}
impl From<UdpDatagram> for DataType {
fn from(packet: UdpDatagram) -> Self {
Self::UDP(packet)
}
}
#[derive(Debug)]
pub struct IpPacket {
pub dscp: u8,
pub ecn: u8,
pub len: usize,
pub id: u16,
pub flags: u8,
pub frag_off: u16,
pub ttl: u8,
pub checksum: u16,
pub source: Ipv4Addr,
pub dest: Ipv4Addr,
pub options: Option<Vec<u8>>,
pub payload: DataType,
pub recv_time: SystemTime,
}
impl IpPacket {
pub fn parse_from_bytes(
data: &dyn AsRef<[u8]>,
time: Option<SystemTime>,
) -> Result<Self, IpParseErr> {
let recv_time = match time {
None => SystemTime::now(),
Some(t) => t,
};
let data = data.as_ref();
if data.len() < 20 {
return Err(IpParseErr::Size(data.len()));
}
let version = data[0] >> 4;
if version != 4 {
return Err(IpParseErr::Version(version));
}
let ihl = data[0] & 0xf;
if !(5..=15).contains(&ihl) {
return Err(IpParseErr::InvalidIHL(ihl as usize));
}
if ihl as usize * 4 > data.len() {
return Err(IpParseErr::InvalidIHL(ihl as usize));
}
let dscp = (data[1] & 0xfc) >> 2;
let ecn = data[1] & 0x3;
let len = u16::from_be_bytes(data[2..4].try_into().unwrap()) as usize;
if len > data.len() {
return Err(IpParseErr::InvalidSize(len));
}
let id = u16::from_be_bytes(data[4..6].try_into().unwrap());
let flags = data[6] >> 5;
let frag_off: u16 = (u16::from(data[6] & 0x1f) << 8) + u16::from(data[7]);
let ttl = data[8];
let proto = data[9];
let checksum = u16::from_be_bytes(data[10..12].try_into().unwrap());
let source = Ipv4Addr::new(data[12], data[13], data[14], data[15]);
let dest = Ipv4Addr::new(data[16], data[17], data[18], data[19]);
let end: usize;
let options = if ihl == 5 {
end = 20;
None
} else {
end = ihl as usize * 4;
Some(data[20..end].to_vec())
};
if len < end {
return Err(IpParseErr::Size(len));
}
let payload: DataType = match proto {
6 => TcpPacket::parse_from_bytes(&&data[end..len])?.into(),
17 => UdpDatagram::parse_from_bytes(&&data[end..len])?.into(),
p => return Err(IpParseErr::Proto(p)),
};
Ok(IpPacket {
dscp,
ecn,
len,
id,
flags,
frag_off,
ttl,
checksum,
source,
dest,
options,
payload,
recv_time,
})
}
pub fn pcap_write<W: Write>(&self, file: &mut W) -> std::io::Result<()> {
let diff = self
.recv_time
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap();
let secs = diff.as_secs() as u32;
let nanos = diff.as_nanos() as u32;
file.write_all(&secs.to_ne_bytes())?;
file.write_all(&nanos.to_ne_bytes())?;
let size = self.len as u32;
file.write_all(&size.to_ne_bytes())?;
file.write_all(&size.to_ne_bytes())?;
let ihl = self.options.clone().map_or(0, |v| v.len() >> 2) as u8;
let version_ihl = 0x40 | (ihl + 5);
file.write_all(&version_ihl.to_be_bytes())?;
let dscp_ecn = (self.dscp << 2) | self.ecn;
file.write_all(&dscp_ecn.to_be_bytes())?;
let total_len = self.len as u16;
file.write_all(&total_len.to_be_bytes())?;
file.write_all(&self.id.to_be_bytes())?;
let frag_off = ((self.flags as u16) << 13) | self.frag_off;
file.write_all(&frag_off.to_be_bytes())?;
file.write_all(&self.ttl.to_be_bytes())?;
file.write_all(&self.payload.get_proto_num().to_be_bytes())?;
file.write_all(&self.checksum.to_be_bytes())?;
file.write_all(&self.source.octets())?;
file.write_all(&self.dest.octets())?;
if let Some(ref o) = self.options {
file.write_all(o)?;
}
self.payload.pcap_write(file)?;
Ok(())
}
}
impl fmt::Display for IpPacket {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("IPv4 Header")
.field("Total length", &self.len)
.field("Source addr", &self.source)
.field("Dest addr", &self.dest)
.field("Payload", &self.payload)
.finish_non_exhaustive()
}
}
bitflags! {
#[repr(transparent)]
pub struct TcpFlags: u16 {
const NS = 0b1_0000_0000;
const CWR = 0b1000_0000;
const ECE = 0b100_0000;
const URG = 0b10_0000;
const ACK = 0b10000;
const PSH = 0b1000;
const RST = 0b100;
const SYN = 0b10;
const FIN = 0b1;
}
}
#[derive(Clone)]
pub struct TcpPacket {
pub source: u16,
pub dest: u16,
pub seq: u32,
pub ack: u32,
pub flags: TcpFlags,
pub window: u16,
pub checksum: u16,
pub urg: u16,
pub option_data: Option<Vec<u8>>,
pub data: Vec<u8>,
}
impl fmt::Debug for TcpPacket {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TCP Packet")
.field("Source Addr", &self.source)
.field("Dest Addr", &self.source)
.field("Data Len", &self.data)
.finish_non_exhaustive()
}
}
#[derive(Error, Debug)]
pub enum TcpParseErr {
#[error("Data is too small to be a TCP packet {0}")]
Size(usize),
#[error("Header has invalid size field {0}")]
InvalidSize(usize),
}
impl TcpPacket {
pub fn parse_from_bytes(data: &dyn AsRef<[u8]>) -> Result<Self, TcpParseErr> {
let data = data.as_ref();
if data.len() < 20 {
return Err(TcpParseErr::Size(data.len()));
}
let source = u16::from_be_bytes(data[0..2].try_into().unwrap());
let dest = u16::from_be_bytes(data[2..4].try_into().unwrap());
let seq = u32::from_be_bytes(data[4..8].try_into().unwrap());
let ack = u32::from_be_bytes(data[8..12].try_into().unwrap());
let data_off = data[12] >> 4;
if data_off as usize * 4 > data.len() {
return Err(TcpParseErr::InvalidSize(data_off as usize));
}
let mut flag_bits: u16 = u16::from(data[12] & 1) << 8;
flag_bits += u16::from(data[13]);
let flags: TcpFlags = unsafe { transmute(flag_bits) };
let window = u16::from_be_bytes(data[14..16].try_into().unwrap());
let checksum = u16::from_be_bytes(data[16..18].try_into().unwrap());
let urg = u16::from_be_bytes(data[18..20].try_into().unwrap());
let option_data: Option<Vec<u8>> = if data_off > 5 {
Some(data[20..(data_off as usize) * 4].to_vec())
} else {
None
};
let data = data[(data_off as usize) * 4..].to_vec();
Ok(TcpPacket {
source,
dest,
seq,
ack,
flags,
window,
checksum,
urg,
option_data,
data,
})
}
pub fn pcap_write<W: Write>(&self, file: &mut W) -> std::io::Result<()> {
file.write_all(&self.source.to_be_bytes())?;
file.write_all(&self.dest.to_be_bytes())?;
file.write_all(&self.seq.to_be_bytes())?;
file.write_all(&self.ack.to_be_bytes())?;
let mut data_off = self.option_data.clone().map_or(0_usize, |v| v.len() >> 2);
data_off += 5;
data_off <<= 4;
data_off |= (self.flags.bits >> 8) as usize;
let data_off_ns = data_off as u8;
file.write_all(&data_off_ns.to_be_bytes())?;
let options = (self.flags.bits & 0xFF) as u8;
file.write_all(&options.to_be_bytes())?;
file.write_all(&self.window.to_be_bytes())?;
file.write_all(&self.checksum.to_be_bytes())?;
file.write_all(&self.urg.to_be_bytes())?;
if let Some(ref v) = self.option_data {
file.write_all(v)?;
}
file.write_all(&self.data)?;
Ok(())
}
}
#[derive(Debug, Error)]
pub enum UdpParseErr {
#[error("Size in header did not match size of data")]
SizeMismatch,
#[error("Passed in data is not large enough for UDP header")]
MissingHeader,
}
pub struct UdpDatagram {
pub source: u16,
pub dest: u16,
pub checksum: u16,
pub payload: Vec<u8>,
}
impl fmt::Debug for UdpDatagram {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UDP Packet")
.field("Source Port", &self.source)
.field("Dest Port", &self.dest)
.field("Data len", &self.payload.len())
.finish_non_exhaustive()
}
}
impl UdpDatagram {
pub fn parse_from_bytes(data: &dyn AsRef<[u8]>) -> Result<Self, UdpParseErr> {
let data = data.as_ref();
if data.len() < 8 {
return Err(UdpParseErr::MissingHeader);
}
let source = u16::from_be_bytes(data[0..2].try_into().unwrap());
let dest = u16::from_be_bytes(data[2..4].try_into().unwrap());
let length = u16::from_be_bytes(data[4..6].try_into().unwrap()) as usize;
let checksum = u16::from_be_bytes(data[6..8].try_into().unwrap());
if !(8..data.len()).contains(&length) {
return Err(UdpParseErr::SizeMismatch);
}
let payload = data[8..length].to_vec();
Ok(Self {
source,
dest,
checksum,
payload,
})
}
pub fn pcap_write<W: Write>(&self, f: &mut W) -> std::io::Result<()> {
f.write_all(&self.source.to_be_bytes())?;
f.write_all(&self.dest.to_be_bytes())?;
f.write_all(&self.payload.len().to_be_bytes())?;
f.write_all(&self.checksum.to_be_bytes())?;
f.write_all(&self.payload)?;
Ok(())
}
}
pub fn write_pcap_file<W: Write>(
packets: &Vec<IpPacket>,
pcap_file: &mut W,
) -> std::io::Result<()> {
pcap_file.write_all(&PCAP_MAGIC.to_ne_bytes())?;
pcap_file.write_all(&2_u16.to_ne_bytes())?;
pcap_file.write_all(&4_u16.to_ne_bytes())?;
pcap_file.write_all(&0_u32.to_ne_bytes())?;
pcap_file.write_all(&0_u32.to_ne_bytes())?;
pcap_file.write_all(&0xFFFF_u32.to_ne_bytes())?;
pcap_file.write_all(&101_u32.to_ne_bytes())?;
for packet in packets {
packet.pcap_write(pcap_file)?;
}
pcap_file.flush()?;
Ok(())
}
pub fn read_pcap_file<R: Read>(pcap_file: &mut R) -> Result<Vec<IpPacket>, IpParseErr> {
let mut pcap_data = Vec::new();
let data_size = pcap_file.read_to_end(&mut pcap_data)?;
if data_size < 24 {
return Err(IpParseErr::PcapFileSize);
}
let magic = u32::from_ne_bytes(pcap_data[0..4].try_into().unwrap());
if magic == PCAP_BAD_ENDIANNESS {
return Err(IpParseErr::EndianError);
}
if magic != PCAP_MAGIC {
return Err(IpParseErr::BadMagic(magic));
}
if u16::from_ne_bytes(pcap_data[4..6].try_into().unwrap()) != 2 {
return Err(IpParseErr::PcapInvalid);
}
if u16::from_ne_bytes(pcap_data[6..8].try_into().unwrap()) != 4 {
return Err(IpParseErr::PcapInvalid);
}
if u32::from_ne_bytes(pcap_data[20..24].try_into().unwrap()) != 101 {
return Err(IpParseErr::PcapInvalid);
}
let mut packets = Vec::new();
let mut index: usize = 24;
while index + 16 < data_size {
let seconds = u32::from_ne_bytes(pcap_data[index..index + 4].try_into().unwrap()) as u64;
let nanos = u32::from_ne_bytes(pcap_data[index + 4..index + 8].try_into().unwrap());
let time = Some(
SystemTime::UNIX_EPOCH
.checked_add(Duration::new(seconds, nanos))
.unwrap(),
);
let packet_size =
u32::from_ne_bytes(pcap_data[index + 8..index + 12].try_into().unwrap()) as usize;
if index + 16 + packet_size > data_size {
break;
}
let packet = IpPacket::parse_from_bytes(&&pcap_data[index + 16..][..packet_size], time);
if let Ok(p) = packet {
packets.push(p);
}
index += 16;
index += packet_size;
}
Ok(packets)
}
#[cfg(test)]
mod ip_testing {
use super::*;
use std::io::Cursor;
const EMPTY_PACKET_BYTES: &[u8] = &[
0x45, 0x00, 0x00, 0x28, 0xde, 0xad, 0x00, 0x00, 0x00, 0x06, 0xbe, 0xef, 0x12, 0x34, 0x56,
0x78, 0xab, 0xcd, 0xef, 0x12, 0x43, 0x21, 0xfe, 0xdc, 0xde, 0xad, 0xbe, 0xef, 0x69, 0x69,
0x69, 0x69, 0x50, 0x00, 0x10, 0x00, 0x99, 0x99, 0x00, 0x00,
];
#[test]
fn empty_packet() {
let packet = IpPacket::parse_from_bytes(&EMPTY_PACKET_BYTES, None).unwrap();
assert_eq!(packet.id, 0xdead);
assert_eq!(packet.checksum, 0xbeef);
assert_eq!(packet.len, 40);
}
#[test]
fn test_pcap_write() {
let packet = IpPacket::parse_from_bytes(&EMPTY_PACKET_BYTES, None).unwrap();
let mut file = Cursor::new(Vec::new());
packet.pcap_write(&mut file).unwrap();
let buffer = file.into_inner();
assert_eq!(buffer[8], 40);
assert_eq!(buffer[9], 0);
assert_eq!(buffer[10], 0);
assert_eq!(buffer[11], 0);
assert_eq!(buffer[12], 40);
assert_eq!(buffer[13], 0);
assert_eq!(buffer[14], 0);
assert_eq!(buffer[15], 0);
assert_eq!(&buffer[16..], EMPTY_PACKET_BYTES);
}
#[test]
fn test_read_pcap() {
use std::fs::File;
let mut file = File::open("./test/test.pcap").unwrap();
let packets = read_pcap_file(&mut file).unwrap();
assert!(packets.len() > 0);
}
#[test]
fn test_fuzz_crash_pcap_read() {
use std::fs::File;
let mut file =
File::open("test/test.pcap")
.unwrap();
let _data = read_pcap_file(&mut file);
}
}