Skip to content

Instantly share code, notes, and snippets.

@usefulcat
Created September 19, 2018 15:36
Show Gist options
  • Save usefulcat/56f334bc58c97edb073b457b683a9f93 to your computer and use it in GitHub Desktop.
Save usefulcat/56f334bc58c97edb073b457b683a9f93 to your computer and use it in GitHub Desktop.
Rust program (well, most of it) that reads pcap data from stdin and writes same to one or more files and/or to stdout.
use std::io::{self, BufReader, BufWriter, Read, Write};
use std::slice;
use std::env;
use std::fs;
extern crate etherparse;
use etherparse::*;
mod pcap;
// Read a struct from a reader
// https://stackoverflow.com/questions/25410028/how-to-read-a-struct-from-a-file-in-rust#
//#[inline]
fn read_struct<T, R: Read>(reader: &mut R) -> ::std::io::Result<T> {
let num_bytes = ::std::mem::size_of::<T>();
unsafe {
let mut s = ::std::mem::uninitialized();
let buffer = slice::from_raw_parts_mut(&mut s as *mut T as *mut u8, num_bytes);
match reader.read_exact(buffer) {
Ok(()) => Ok(s),
Err(e) => {
// prevent s dtor from running, since s is uninitialized
::std::mem::forget(s);
Err(e)
}
}
}
}
// convert a struct to '&[u8]' (a u8 slice)
#[inline]
unsafe fn any_as_u8_slice<T: Sized>(p: &T) -> &[u8] {
::std::slice::from_raw_parts((p as *const T) as *const u8, ::std::mem::size_of::<T>())
}
fn parse_vlan_id(s: &str) -> u16 {
let id: u64 = s.parse().unwrap();
// Note: 0 is not a valid vlan id, but it's useful value to use
// for indicating 'all packets'.
if id < 0xFFF {
id as u16
} else {
panic!("Invalid vlan id {}", id);
}
}
//#[inline]
fn get_vlan(packet: &[u8]) -> u16 {
use etherparse::VlanSlice::SingleVlan;
use etherparse::VlanSlice::DoubleVlan;
match SlicedPacket::from_ethernet(&packet) {
Err(_) => 0 as u16, // todo: questionable; should return error?
// value is SlicedPacket
Ok(value) => match value.vlan {
Some(x) => match x {
SingleVlan(single) => single.vlan_identifier(),
DoubleVlan(_) => 0 as u16, // todo: should return error?
},
None => 0 as u16
}
}
}
fn main() -> io::Result<()> {
// Will panic if we ever see a packet larger than this:
const MAX_PACKET_SIZE: usize = 2048;
let in_bufsize = 512 * 1024;
let out_bufsize = 512 * 1024;
// Lock stdout manually and use the resulting handle to avoid lock/unlock on every write
// operation.
let stdout = ::std::io::stdout();
let mut locked_stdout = stdout.lock();
// outputs[n], vlan_ids[n] and filenames[n] correspond to each other
let mut vlan_ids = Vec::new();
let mut outputs = Vec::new();
// Each arg must be of the form "vlan_id" or "vlan_id:filename"
// vlan_id is required, filename is optional. vlan_id may be 0 to indicate
// interest in all packets.
for arg in env::args().skip(1) { // skip(1): skip the first arg, the exe name
match arg.find(':') {
Some(n) => {
let mut vlan = arg.clone();
let fname = vlan.split_off(n + 1); // skip ':'
vlan.truncate(n); // remove trailing ':'
let vlan_id = parse_vlan_id(&vlan);
vlan_ids.push(vlan_id);
let output = Some(BufWriter::with_capacity(out_bufsize, fs::File::create(&fname)?));
outputs.push(output);
eprintln!("vlan {} => {}", vlan_id, fname);
},
None => {
let vlan_id = parse_vlan_id(&arg);
vlan_ids.push(vlan_id);
outputs.push(None);
eprintln!("vlan {} => stdout", vlan_id);
},
}
}
let mut reader = BufReader::with_capacity(in_bufsize, io::stdin());
let file_header =
read_struct::<pcap::FileHeader, _>(&mut reader).expect("failed to read pcap file header");
// Check for valid magic value in header:
match file_header.magic() {
pcap::TCPDUMP_MAGIC => (),
pcap::NSEC_TCPDUMP_MAGIC => (),
_ => panic!("Invalid magic value: 0x{:X}", file_header.magic()),
}
{
let header_bytes = unsafe { any_as_u8_slice(&file_header) };
let mut writing_to_stdout = false;
// Write file header to all outputs
for mut out in outputs.iter_mut() {
match out {
Some(writer) => writer.write_all(header_bytes)?,
None => writing_to_stdout = true,
}
}
if writing_to_stdout {
locked_stdout.write_all(header_bytes)?;
}
}
// Buffer for reading packet data
let mut buf: [u8; MAX_PACKET_SIZE] = [0; MAX_PACKET_SIZE];
loop {
// Read packet header, stop on EOF.
let packet_header = match read_struct::<pcap::PacketHeader, _>(&mut reader) {
Ok(x) => x,
Err(err) => match err.kind() {
// EOF == finished reading; not an error
::std::io::ErrorKind::UnexpectedEof => return Ok(()),
_ => panic!("Unexpected error: {}", err),
},
};
// Read packet data.
let packet_data = &mut buf[0..packet_header.caplen as usize];
reader.read_exact(packet_data)?;
// Extract vlan id from packet data (0 if no vlan id).
// 0 is returned if the packet doesn't have a vlan tag.
let vlan = get_vlan(&packet_data);
let header_bytes = unsafe { any_as_u8_slice(&packet_header) };
// Send this packet to every output for the above vlan id
for i in 0..outputs.len() {
let output_vlan = vlan_ids[i];
// 0 is used to indicate unconditional interest in all packets
if 0 == output_vlan || vlan == output_vlan {
match &mut outputs[i] {
Some(writer) => {
writer.write_all(header_bytes)?;
writer.write_all(packet_data)?
},
None => {
locked_stdout.write_all(header_bytes)?;
locked_stdout.write_all(packet_data)?
},
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment