Created
September 19, 2018 15:36
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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