use quick_xml::Reader;
use crate::{
packet::Packet,
xml::{ParserResult, RTSharkParser},
};
use std::io::{BufRead, BufReader, Result};
#[cfg(target_family = "unix")]
use std::os::unix::process::ExitStatusExt;
use std::process::{Child, ChildStderr, ChildStdout};
pub struct RTShark {
process: Option<Child>,
parser: quick_xml::Reader<BufReader<ChildStdout>>,
stderr: BufReader<ChildStderr>,
blacklist: Vec<String>,
whitelist: Vec<String>,
}
impl RTShark {
pub(crate) fn new(mut process: Child, blacklist: Vec<String>, whitelist: Vec<String>) -> Self {
let buf_reader = BufReader::new(process.stdout.take().unwrap());
let stderr = BufReader::new(process.stderr.take().unwrap());
let parser = quick_xml::Reader::from_reader(buf_reader);
RTShark {
process: Some(process),
parser,
stderr,
blacklist,
whitelist,
}
}
pub fn read(&mut self) -> Result<Option<Packet>> {
let xml_reader = &mut self.parser;
let msg = RTShark::parse(xml_reader, &self.blacklist, &self.whitelist);
if let Ok(ref msg) = msg {
let done = match msg {
None => {
match self.process {
Some(ref mut process) => RTShark::try_wait_has_exited(process),
_ => true,
}
}
_ => false,
};
if done {
self.process = None;
let mut line = String::new();
let size = self.stderr.read_line(&mut line)?;
if size != 0 {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, line));
}
}
}
msg
}
pub(crate) fn parse<B: BufRead>(
reader: &mut Reader<B>,
blacklist: &[String],
whitelist: &[String],
) -> std::io::Result<Option<Packet>> {
let mut parser = RTSharkParser::new();
let mut buf = vec![];
loop {
let event = reader.read_event_into(&mut buf).map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("cant parse xml: {e}"),
)
})?;
match parser.parse(event, blacklist, whitelist)? {
ParserResult::Continue => (),
ParserResult::Packet(packet) => return Ok(Some(packet)),
ParserResult::Eof => return Ok(None),
}
}
}
pub fn kill(&mut self) {
if let Some(ref mut process) = self.process {
let done = match process.try_wait() {
Ok(maybe) => match maybe {
None => false,
Some(_exitcode) => true,
},
Err(e) => {
eprintln!("Error while killing rtshark: wait: {e}");
false
}
};
if !done {
match process.kill() {
Ok(()) => (),
Err(e) => eprintln!("Error while killing rtshark: kill: {e}"),
}
if let Err(e) = process.wait() {
eprintln!("Error while killing rtshark: wait: {e}");
}
}
self.process = None;
}
}
pub fn pid(&self) -> Option<u32> {
self.process.as_ref().map(|p| p.id())
}
fn try_wait_has_exited(child: &mut Child) -> bool {
let mut count = 3;
while count != 0 {
#[cfg(target_family = "unix")]
if let Ok(Some(s)) = child.try_wait() {
return s.code().is_some() || s.signal().is_some();
}
#[cfg(target_family = "windows")]
if let Ok(Some(s)) = child.try_wait() {
return s.code().is_some();
}
std::thread::sleep(std::time::Duration::from_millis(100));
count -= 1;
}
false
}
}
impl Drop for RTShark {
fn drop(&mut self) {
self.kill()
}
}
#[cfg(test)]
mod tests {
use std::io::Write;
use crate::RTSharkBuilder;
use serial_test::serial;
#[test]
fn test_rtshark_input_pcap() {
let pcap = include_bytes!("../assets/test.pcap");
let tmp_dir = tempdir::TempDir::new("test_pcap").unwrap();
let pcap_path = tmp_dir.path().join("file.pcap");
let mut output = std::fs::File::create(&pcap_path).expect("unable to open file");
output.write_all(pcap).expect("unable to write pcap");
output.flush().expect("unable to flush");
let builder = RTSharkBuilder::builder().input_path(pcap_path.to_str().unwrap());
let mut rtshark = builder.spawn().unwrap();
match rtshark.read().unwrap() {
Some(p) => assert!(p.layer_name("udp").is_some()),
_ => panic!("invalid Output type"),
}
loop {
match rtshark.read().unwrap() {
None => break,
Some(_) => todo!(),
}
}
rtshark.kill();
assert!(rtshark.pid().is_none());
tmp_dir.close().expect("Error deleting fifo dir");
}
#[test]
fn test_rtshark_input_pcap_decode_as() {
let pcap = include_bytes!("../assets/rtp.pcap");
let tmp_dir = tempdir::TempDir::new("test_pcap").unwrap();
let pcap_path = tmp_dir.path().join("rtp.pcap");
let mut output = std::fs::File::create(&pcap_path).expect("unable to open file");
output.write_all(pcap).expect("unable to write pcap");
output.flush().expect("unable to flush");
let builder = RTSharkBuilder::builder().input_path(pcap_path.to_str().unwrap());
let mut rtshark = builder.spawn().unwrap();
match rtshark.read().unwrap() {
Some(p) => assert!(p.layer_name("rtp").is_none()),
_ => panic!("invalid Output type"),
}
rtshark.kill();
assert!(rtshark.pid().is_none());
let builder = RTSharkBuilder::builder()
.input_path(pcap_path.to_str().unwrap())
.decode_as("udp.port==6000,rtp");
let mut rtshark = builder.spawn().unwrap();
match rtshark.read().unwrap() {
Some(p) => assert!(p.layer_name("rtp").is_some()),
_ => panic!("invalid Output type"),
}
rtshark.kill();
assert!(rtshark.pid().is_none());
tmp_dir.close().expect("Error deleting fifo dir");
}
#[test]
fn test_rtshark_input_pcap_display_filter() {
let pcap = include_bytes!("../assets/test.pcap");
let tmp_dir = tempdir::TempDir::new("test_pcap").unwrap();
let pcap_path = tmp_dir.path().join("file.pcap");
let mut output = std::fs::File::create(&pcap_path).expect("unable to open file");
output.write_all(pcap).expect("unable to write pcap");
output.flush().expect("unable to flush");
let builder = RTSharkBuilder::builder()
.input_path(pcap_path.to_str().unwrap())
.display_filter("udp.port == 53");
let mut rtshark = builder.spawn().unwrap();
match rtshark.read().unwrap() {
Some(p) => assert!(p.layer_name("udp").is_some()),
_ => panic!("invalid Output type"),
}
rtshark.kill();
let builder = RTSharkBuilder::builder()
.input_path(pcap_path.to_str().unwrap())
.display_filter("tcp.port == 80");
let mut rtshark = builder.spawn().unwrap();
match rtshark.read().unwrap() {
None => (),
_ => panic!("invalid Output type"),
}
rtshark.kill();
tmp_dir.close().expect("Error deleting fifo dir");
}
#[test]
fn test_rtshark_input_pcap_blacklist() {
let pcap = include_bytes!("../assets/test.pcap");
let tmp_dir = tempdir::TempDir::new("test_pcap").unwrap();
let pcap_path = tmp_dir.path().join("file.pcap");
let mut output = std::fs::File::create(&pcap_path).expect("unable to open file");
output.write_all(pcap).expect("unable to write pcap");
output.flush().expect("unable to flush");
let builder = RTSharkBuilder::builder()
.input_path(pcap_path.to_str().unwrap())
.metadata_blacklist("ip.src");
let mut rtshark = builder.spawn().unwrap();
let pkt = match rtshark.read().unwrap() {
Some(p) => p,
_ => panic!("invalid Output type"),
};
let ip = pkt.layer_name("ip").unwrap();
assert!(ip.metadata("ip.src").is_none());
assert!(ip.metadata("ip.dst").unwrap().value().eq("127.0.0.1"));
rtshark.kill();
tmp_dir.close().expect("Error deleting fifo dir");
}
#[test]
fn test_rtshark_input_pcap_whitelist() {
let pcap = include_bytes!("../assets/test.pcap");
let tmp_dir = tempdir::TempDir::new("test_pcap").unwrap();
let pcap_path = tmp_dir.path().join("file.pcap");
let mut output = std::fs::File::create(&pcap_path).expect("unable to open file");
output.write_all(pcap).expect("unable to write pcap");
output.flush().expect("unable to flush");
let builder = RTSharkBuilder::builder()
.input_path(pcap_path.to_str().unwrap())
.metadata_whitelist("ip.dst");
let mut rtshark = builder.spawn().unwrap();
let pkt = match rtshark.read().unwrap() {
Some(p) => p,
_ => panic!("invalid Output type"),
};
let ip = pkt.layer_name("ip").unwrap();
assert!(ip.metadata("ip.src").is_none());
assert!(ip.metadata("ip.dst").unwrap().value().eq("127.0.0.1"));
rtshark.kill();
tmp_dir.close().expect("Error deleting fifo dir");
}
#[test]
fn test_rtshark_input_pcap_multiple_whitelist() {
let pcap = include_bytes!("../assets/test.pcap");
let tmp_dir = tempdir::TempDir::new("test_pcap").unwrap();
let pcap_path = tmp_dir.path().join("file.pcap");
let mut output = std::fs::File::create(&pcap_path).expect("unable to open file");
output.write_all(pcap).expect("unable to write pcap");
output.flush().expect("unable to flush");
let builder = RTSharkBuilder::builder()
.input_path(pcap_path.to_str().unwrap())
.metadata_whitelist("ip.src")
.metadata_whitelist("ip.dst");
let mut rtshark = builder.spawn().unwrap();
let pkt = match rtshark.read().unwrap() {
Some(p) => p,
_ => panic!("invalid Output type"),
};
let ip = pkt.layer_name("ip").unwrap();
assert!(ip.metadata("ip.src").unwrap().value().eq("127.0.0.1"));
assert!(ip.metadata("ip.dst").unwrap().value().eq("127.0.0.1"));
rtshark.kill();
tmp_dir.close().expect("Error deleting fifo dir");
}
#[test]
fn test_rtshark_input_pcap_whitelist_multiple_layer() {
let pcap = include_bytes!("../assets/test.pcap");
let tmp_dir = tempdir::TempDir::new("test_pcap").unwrap();
let pcap_path = tmp_dir.path().join("file.pcap");
let mut output = std::fs::File::create(&pcap_path).expect("unable to open file");
output.write_all(pcap).expect("unable to write pcap");
output.flush().expect("unable to flush");
let builder = RTSharkBuilder::builder()
.input_path(pcap_path.to_str().unwrap())
.metadata_whitelist("ip.src")
.metadata_whitelist("udp.dstport");
let mut rtshark = builder.spawn().unwrap();
let pkt = match rtshark.read().unwrap() {
Some(p) => p,
_ => panic!("invalid Output type"),
};
let ip = pkt.layer_name("ip").unwrap();
assert!(ip.metadata("ip.src").unwrap().value().eq("127.0.0.1"));
let ip = pkt.layer_name("udp").unwrap();
assert!(ip.metadata("udp.dstport").unwrap().value().eq("53"));
rtshark.kill();
tmp_dir.close().expect("Error deleting fifo dir");
}
#[test]
fn test_rtshark_input_pcap_whitelist_missing_attr() {
let pcap = include_bytes!("../assets/test.pcap");
let tmp_dir = tempdir::TempDir::new("test_pcap").unwrap();
let pcap_path = tmp_dir.path().join("file.pcap");
let mut output = std::fs::File::create(&pcap_path).expect("unable to open file");
output.write_all(pcap).expect("unable to write pcap");
output.flush().expect("unable to flush");
let builder = RTSharkBuilder::builder()
.input_path(pcap_path.to_str().unwrap())
.metadata_whitelist("nosuchproto.nosuchmetadata");
let mut rtshark = builder.spawn().unwrap();
let ret = rtshark.read();
assert!(ret.is_err());
rtshark.kill();
tmp_dir.close().expect("Error deleting fifo dir");
}
#[cfg(target_family = "unix")]
#[test]
fn test_rtshark_input_fifo() {
let pcap = include_bytes!("../assets/test.pcap");
let tmp_dir = tempdir::TempDir::new("test_fifo").unwrap();
let fifo_path = tmp_dir.path().join("pcap.pipe");
nix::unistd::mkfifo(&fifo_path, nix::sys::stat::Mode::S_IRWXU)
.expect("Error creating fifo");
let builder = RTSharkBuilder::builder()
.input_path(fifo_path.to_str().unwrap())
.live_capture();
let mut rtshark = builder.spawn().unwrap();
let mut output = std::fs::OpenOptions::new()
.write(true)
.open(&fifo_path)
.expect("unable to open fifo");
output.write_all(pcap).expect("unable to write in fifo");
match rtshark.read().unwrap() {
Some(p) => assert!(p.layer_name("udp").is_some()),
_ => panic!("invalid Output type"),
}
rtshark.kill();
assert!(rtshark.pid().is_none());
tmp_dir.close().expect("Error deleting fifo dir");
}
#[cfg(target_family = "unix")]
#[test]
fn test_rtshark_input_multiple_fifo() {
let pcap = include_bytes!("../assets/test.pcap");
let tmp_dir = tempdir::TempDir::new("test_fifo").unwrap();
let fifo_path1 = tmp_dir.path().join("pcap1.pipe");
let fifo_path2 = tmp_dir.path().join("pcap2.pipe");
nix::unistd::mkfifo(&fifo_path1, nix::sys::stat::Mode::S_IRWXU)
.expect("Error creating fifo");
nix::unistd::mkfifo(&fifo_path2, nix::sys::stat::Mode::S_IRWXU)
.expect("Error creating fifo");
let builder = RTSharkBuilder::builder()
.input_path(fifo_path1.to_str().unwrap())
.input_path(fifo_path2.to_str().unwrap())
.live_capture();
let mut rtshark = builder.spawn().unwrap();
let mut output = std::fs::OpenOptions::new()
.write(true)
.open(&fifo_path1)
.expect("unable to open fifo");
output.write_all(pcap).expect("unable to write in fifo");
let mut output = std::fs::OpenOptions::new()
.write(true)
.open(&fifo_path2)
.expect("unable to open fifo");
output.write_all(pcap).expect("unable to write in fifo");
match rtshark.read().unwrap() {
Some(p) => assert!(p.layer_name("udp").is_some()),
_ => panic!("invalid Output type"),
}
match rtshark.read().unwrap() {
Some(p) => assert!(p.layer_name("udp").is_some()),
_ => panic!("invalid Output type"),
}
rtshark.kill();
assert!(rtshark.pid().is_none());
tmp_dir.close().expect("Error deleting fifo dir");
}
#[cfg(target_family = "unix")]
#[test]
fn test_rtshark_input_pcap_filter_pcap() {
let pcap = include_bytes!("../assets/test.pcap");
let tmp_dir = tempdir::TempDir::new("test_fifo").unwrap();
let fifo_path = tmp_dir.path().join("pcap.pipe");
nix::unistd::mkfifo(&fifo_path, nix::sys::stat::Mode::S_IRWXU)
.expect("Error creating fifo");
let builder = RTSharkBuilder::builder()
.input_path(fifo_path.to_str().unwrap())
.live_capture()
.capture_filter("port 53");
let mut rtshark = builder.spawn().unwrap();
let mut output = std::fs::OpenOptions::new()
.write(true)
.open(&fifo_path)
.expect("unable to open fifo");
output.write_all(pcap).expect("unable to write in fifo");
match rtshark.read().unwrap() {
Some(p) => assert!(p.layer_name("udp").is_some()),
_ => panic!("invalid Output type"),
}
rtshark.kill();
assert!(rtshark.pid().is_none());
tmp_dir.close().expect("Error deleting fifo dir");
}
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
#[test]
fn test_rtshark_drop() {
let tmp_dir = tempdir::TempDir::new("test_fifo").unwrap();
let fifo_path = tmp_dir.path().join("pcap.pipe");
nix::unistd::mkfifo(&fifo_path, nix::sys::stat::Mode::S_IRWXU)
.expect("Error creating fifo");
let builder = RTSharkBuilder::builder()
.input_path(fifo_path.to_str().unwrap())
.live_capture();
let pid = {
let rtshark = builder.spawn().unwrap();
let pid = rtshark.pid().unwrap();
assert!(std::path::Path::new(&format!("/proc/{pid}")).exists());
pid
};
assert!(!std::path::Path::new(&format!("/proc/{pid}")).exists());
tmp_dir.close().expect("Error deleting fifo dir");
}
#[cfg(target_family = "unix")]
#[test]
fn test_rtshark_killed() {
let tmp_dir = tempdir::TempDir::new("test_fifo").unwrap();
let fifo_path = tmp_dir.path().join("pcap.pipe");
nix::unistd::mkfifo(&fifo_path, nix::sys::stat::Mode::S_IRWXU)
.expect("Error creating fifo");
let builder = RTSharkBuilder::builder()
.input_path(fifo_path.to_str().unwrap())
.live_capture();
let mut rtshark = builder.spawn().unwrap();
nix::sys::signal::kill(
nix::unistd::Pid::from_raw(rtshark.pid().unwrap() as libc::pid_t),
nix::sys::signal::Signal::SIGKILL,
)
.unwrap();
match rtshark.read().unwrap() {
None => (),
_ => panic!("invalid Output type"),
}
tmp_dir.close().expect("Error deleting fifo dir");
}
#[cfg(target_family = "unix")]
#[test]
fn test_rtshark_fifo_lost() {
let tmp_dir = tempdir::TempDir::new("test_fifo").unwrap();
let fifo_path = tmp_dir.path().join("pcap.pipe");
nix::unistd::mkfifo(&fifo_path, nix::sys::stat::Mode::S_IRWXU)
.expect("Error creating fifo");
let builder = RTSharkBuilder::builder()
.input_path(fifo_path.to_str().unwrap())
.live_capture();
let mut rtshark = builder.spawn().unwrap();
tmp_dir.close().expect("Error deleting fifo dir");
loop {
match rtshark.read() {
Ok(e) if e.is_some() => panic!("invalid Output type"),
Ok(e) if e.is_none() => break,
_ => (),
}
}
}
#[cfg(target_family = "unix")]
#[test]
fn test_rtshark_fifo_opened_then_closed() {
let pcap = include_bytes!("../assets/test.pcap");
let tmp_dir = tempdir::TempDir::new("test_fifo").unwrap();
let fifo_path = tmp_dir.path().join("pcap.pipe");
nix::unistd::mkfifo(&fifo_path, nix::sys::stat::Mode::S_IRWXU)
.expect("Error creating fifo");
let builder = RTSharkBuilder::builder()
.input_path(fifo_path.to_str().unwrap())
.live_capture();
let mut rtshark = builder.spawn().unwrap();
{
let mut output = std::fs::OpenOptions::new()
.write(true)
.open(&fifo_path)
.expect("unable to open fifo");
output.write_all(pcap).expect("unable to write in fifo");
}
match rtshark.read().unwrap() {
Some(p) => assert!(p.layer_name("udp").is_some()),
_ => panic!("invalid Output type"),
}
rtshark.kill();
tmp_dir.close().expect("Error deleting fifo dir");
}
#[test]
fn test_rtshark_file_missing() {
let builder = RTSharkBuilder::builder().input_path("/missing/rtshark/fifo");
let ret = builder.spawn();
match ret {
Ok(_) => panic!("We can't start if file is missing"),
Err(e) => eprintln!("{e}"),
}
}
#[cfg(target_family = "unix")]
#[test]
fn test_rtshark_set_options() {
let pcap = include_bytes!("../assets/tcp_fragmentation.pcap");
let tmp_dir = tempdir::TempDir::new("test_pcap").unwrap();
let pcap_path = tmp_dir.path().join("file.pcap");
let mut output = std::fs::File::create(&pcap_path).expect("unable to open file");
output.write_all(pcap).expect("unable to write pcap");
output.flush().expect("unable to flush");
let builder = RTSharkBuilder::builder()
.input_path(pcap_path.to_str().unwrap())
.option("tcp.relative_sequence_numbers:true");
let mut rtshark = builder.spawn().unwrap();
match rtshark.read().unwrap() {
Some(p) => {
let tcp = p.layer_name("tcp").expect("tcp layer");
if !tcp.iter().any(|md| {
if let Some(display) = md.display() {
display.contains("relative sequence number")
} else {
false
}
}) {
panic!("expected relative sequence number")
}
}
e => panic!("invalid Output type: {e:?}"),
}
rtshark.kill();
let builder = RTSharkBuilder::builder()
.input_path(pcap_path.to_str().unwrap())
.option("tcp.relative_sequence_numbers:false");
let mut rtshark = builder.spawn().unwrap();
match rtshark.read().unwrap() {
Some(p) => {
let tcp = p.layer_name("tcp").expect("tcp layer");
if tcp.iter().any(|md| {
if let Some(display) = md.display() {
display.contains("relative sequence number")
} else {
false
}
}) {
panic!("expected no relative sequence numbers")
}
}
e => panic!("invalid Output type: {e:?}"),
}
rtshark.kill();
tmp_dir.close().expect("Error deleting fifo dir");
}
#[cfg(target_family = "unix")]
#[test]
fn test_rtshark_set_disabled_protocols() {
let pcap = include_bytes!("../assets/tcp_fragmentation.pcap");
let tmp_dir = tempdir::TempDir::new("test_pcap").unwrap();
let pcap_path = tmp_dir.path().join("file.pcap");
let mut output = std::fs::File::create(&pcap_path).expect("unable to open file");
output.write_all(pcap).expect("unable to write pcap");
output.flush().expect("unable to flush");
let builder = RTSharkBuilder::builder()
.input_path(pcap_path.to_str().unwrap())
.disable_protocol("tcp")
.disable_protocol("sip");
let mut rtshark = builder.spawn().unwrap();
match rtshark.read().unwrap() {
Some(p) => {
assert!(p.layer_name("tcp").is_none());
assert!(p.layer_name("sip").is_none());
}
e => panic!("invalid Output type: {e:?}"),
}
rtshark.kill();
tmp_dir.close().expect("Error deleting fifo dir");
}
#[cfg(target_family = "unix")]
#[test]
fn test_rtshark_set_enabled_protocols() {
let pcap = include_bytes!("../assets/tcp_fragmentation.pcap");
let tmp_dir = tempdir::TempDir::new("test_pcap").unwrap();
let pcap_path = tmp_dir.path().join("file.pcap");
let mut output = std::fs::File::create(&pcap_path).expect("unable to open file");
output.write_all(pcap).expect("unable to write pcap");
output.flush().expect("unable to flush");
let builder = RTSharkBuilder::builder()
.input_path(pcap_path.to_str().unwrap())
.disable_protocol("ALL")
.enable_protocol("eth")
.enable_protocol("ip");
let mut rtshark = builder.spawn().unwrap();
match rtshark.read().unwrap() {
Some(p) => {
assert!(p.layer_name("tcp").is_none());
assert!(p.layer_name("sip").is_none());
assert!(p.layer_name("ip").is_some());
}
e => panic!("invalid Output type: {e:?}"),
}
rtshark.kill();
tmp_dir.close().expect("Error deleting fifo dir");
}
#[test]
#[serial] fn test_rtshark_tshark_missing() {
let path = match std::env::var("PATH") {
Ok(v) => {
std::env::remove_var("PATH");
Some(v)
}
Err(_) => None,
};
let builder = RTSharkBuilder::builder()
.input_path("/missing/rtshark/fifo")
.live_capture()
.env_path("/invalid/path");
let ret = builder.spawn();
if let Some(v) = path {
std::env::set_var("PATH", v);
}
match ret {
Ok(_) => panic!("We can't start if tshark is missing"),
Err(e) => eprintln!("{e}"),
}
}
#[test]
fn test_rtshark_input_pcap_output_pcap() {
let pcap = include_bytes!("../assets/test.pcap");
let tmp_dir = tempdir::TempDir::new("test_pcap").unwrap();
let in_path = tmp_dir.path().join("in.pcap");
let mut output = std::fs::File::create(&in_path).expect("unable to open file");
output.write_all(pcap).expect("unable to write pcap");
output.flush().expect("unable to flush");
let out_path = tmp_dir.path().join("out.pcap");
let builder = RTSharkBuilder::builder()
.input_path(in_path.to_str().unwrap())
.output_path(out_path.to_str().unwrap());
let mut rtshark = builder.spawn().unwrap();
match rtshark.read().unwrap() {
Some(p) => assert!(p.layer_name("udp").is_some()),
_ => panic!("invalid Output type"),
}
loop {
match rtshark.read().unwrap() {
None => break,
Some(_) => todo!(),
}
}
rtshark.kill();
assert!(rtshark.pid().is_none());
let mut rtshark = RTSharkBuilder::builder()
.input_path(out_path.to_str().unwrap())
.spawn()
.unwrap();
match rtshark.read().unwrap() {
Some(p) => assert!(p.layer_name("udp").is_some()),
_ => panic!("invalid Output type"),
}
rtshark.kill();
tmp_dir.close().expect("Error deleting fifo dir");
}
#[cfg(target_family = "unix")]
#[test]
fn test_rtshark_input_fifo_output_pcap() {
let pcap = include_bytes!("../assets/test.pcap");
let tmp_dir = tempdir::TempDir::new("test_fifo").unwrap();
let fifo_path = tmp_dir.path().join("pcap.pipe");
nix::unistd::mkfifo(&fifo_path, nix::sys::stat::Mode::S_IRWXU)
.expect("Error creating fifo");
let out_path = tmp_dir.path().join("out.pcap");
let builder = RTSharkBuilder::builder()
.input_path(fifo_path.to_str().unwrap())
.output_path(out_path.to_str().unwrap())
.live_capture();
let mut rtshark = builder.spawn().unwrap();
let mut output = std::fs::OpenOptions::new()
.write(true)
.open(&fifo_path)
.expect("unable to open fifo");
output.write_all(pcap).expect("unable to write in fifo");
match rtshark.read().unwrap() {
Some(p) => assert!(p.layer_name("udp").is_some()),
_ => panic!("invalid Output type"),
}
rtshark.kill();
assert!(rtshark.pid().is_none());
let mut rtshark = RTSharkBuilder::builder()
.input_path(out_path.to_str().unwrap())
.spawn()
.unwrap();
match rtshark.read().unwrap() {
Some(p) => assert!(p.layer_name("udp").is_some()),
_ => panic!("invalid Output type"),
}
rtshark.kill();
tmp_dir.close().expect("Error deleting fifo dir");
}
#[test]
#[serial] fn test_rtshark_multiple_spawn_pcap() {
let pcap = include_bytes!("../assets/test.pcap");
let tmp_dir = tempdir::TempDir::new("test_pcap").unwrap();
let in_path = tmp_dir.path().join("in.pcap");
let mut output = std::fs::File::create(&in_path).expect("unable to open file");
output.write_all(pcap).expect("unable to write pcap");
output.flush().expect("unable to flush");
let out_path = tmp_dir.path().join("out.pcap");
let builder = RTSharkBuilder::builder()
.input_path(in_path.to_str().unwrap())
.output_path(out_path.to_str().unwrap());
let mut rtshark = builder.spawn().unwrap();
match rtshark.read().unwrap() {
Some(p) => assert!(p.layer_name("udp").is_some()),
_ => panic!("invalid Output type"),
}
rtshark.kill();
let mut rtshark = builder.spawn().unwrap();
match rtshark.read().unwrap() {
Some(p) => assert!(p.layer_name("udp").is_some()),
_ => panic!("invalid Output type"),
}
rtshark.kill();
tmp_dir.close().expect("Error deleting fifo dir");
}
#[test]
fn test_rtshark_timestamp_micros() {
let pcap = include_bytes!("../assets/test.pcap");
let tmp_dir = tempdir::TempDir::new("test_pcap").unwrap();
let in_path = tmp_dir.path().join("in.pcap");
let mut output = std::fs::File::create(&in_path).expect("unable to open file");
output.write_all(pcap).expect("unable to write pcap");
output.flush().expect("unable to flush");
let out_path = tmp_dir.path().join("out.pcap");
let builder = RTSharkBuilder::builder()
.input_path(in_path.to_str().unwrap())
.output_path(out_path.to_str().unwrap());
let mut rtshark = builder.spawn().unwrap();
match rtshark.read().unwrap() {
Some(p) => assert_eq!(p.timestamp_micros(), Some(1652011560275852)),
_ => panic!("invalid Output type"),
}
rtshark.kill();
tmp_dir.close().expect("Error deleting fifo dir");
}
#[test]
fn test_rtshark_tls_keylogfile_pcap() {
let pcap = include_bytes!("../assets/test_tls.pcap");
let keylog = include_bytes!("../assets/test_tlskeylogfile.txt");
let tmp_dir = tempdir::TempDir::new("test_pcap").unwrap();
let pcap_path = tmp_dir.path().join("file.pcap");
let mut output = std::fs::File::create(&pcap_path).expect("unable to open file");
output.write_all(pcap).expect("unable to write pcap");
output.flush().expect("unable to flush");
let builder = RTSharkBuilder::builder().input_path(pcap_path.to_str().unwrap());
let mut rtshark = builder.spawn().unwrap();
loop {
match rtshark.read().unwrap() {
None => break,
Some(p) => {
assert!(p.layer_name("tcp").is_some());
assert!(p.layer_name("http2").is_none())
}
}
}
rtshark.kill();
let keylog_path = tmp_dir.path().join("keylogfile.txt");
let mut output = std::fs::File::create(&keylog_path).expect("unable to open file");
output.write_all(keylog).expect("unable to write pcap");
output.flush().expect("unable to flush");
let builder = RTSharkBuilder::builder()
.input_path(pcap_path.to_str().unwrap())
.keylog_file(keylog_path.as_os_str().to_str().unwrap());
let mut rtshark = builder.spawn().unwrap();
let mut http2_found = false;
loop {
match rtshark.read().unwrap() {
None => break,
Some(p) => {
assert!(p.layer_name("tcp").is_some());
if p.layer_name("http2").is_some() {
http2_found = true;
}
}
}
}
assert!(http2_found);
rtshark.kill();
assert!(rtshark.pid().is_none());
tmp_dir.close().expect("Error deleting fifo dir");
}
#[test]
fn test_reassembled_tcp() {
let pcap = include_bytes!("../assets/tcp_fragmentation.pcap");
let tmp_dir = tempdir::TempDir::new("test_pcap").unwrap();
let pcap_path = tmp_dir.path().join("file.pcap");
let mut output = std::fs::File::create(&pcap_path).expect("unable to open file");
output.write_all(pcap).expect("unable to write pcap");
output.flush().expect("unable to flush");
let builder = RTSharkBuilder::builder()
.input_path(pcap_path.to_str().unwrap())
.display_filter("tls.handshake.type == 1");
let mut rtshark = builder.spawn().unwrap();
loop {
match rtshark.read().unwrap() {
None => break,
Some(p) => {
let tcp = p.layer_name("tcp").expect("Missing tcp layer");
tcp.metadata("tcp.reassembled.data")
.expect("Missing metadata");
}
}
}
rtshark.kill();
assert!(rtshark.pid().is_none());
tmp_dir.close().expect("Error deleting fifo dir");
}
#[test]
fn test_tshark_version() {
let builder = RTSharkBuilder::builder();
builder.version().expect("Error getting tshark version");
}
#[test]
fn test_batch() {
let pcap = include_bytes!("../assets/test.pcap");
let tmp_dir = tempdir::TempDir::new("test_pcap").unwrap();
let original = tmp_dir.path().join("original.pcap");
std::fs::write(&original, pcap).unwrap();
let normalized = tmp_dir.path().join("normalized.pcap");
RTSharkBuilder::builder()
.input_path(original.to_str().unwrap())
.output_path(normalized.to_str().unwrap())
.batch()
.unwrap();
assert!(
!std::fs::read(&normalized).unwrap().is_empty(),
"assumed normalization to produce some output, but it did not"
);
let output = tmp_dir.path().join("output.pcap");
RTSharkBuilder::builder()
.input_path(normalized.to_str().unwrap())
.output_path(output.to_str().unwrap())
.batch()
.unwrap();
let normalized = std::fs::read(normalized).unwrap();
let output = std::fs::read(output).unwrap();
assert_eq!(normalized, output);
}
#[test]
fn test_tls_record_grouping() {
let pcap = include_bytes!("../assets/test_tls.pcap");
let tmp_dir = tempdir::TempDir::new("test_pcap").unwrap();
let pcap_path = tmp_dir.path().join("test_tls.pcap");
let mut output = std::fs::File::create(&pcap_path).expect("unable to open file");
output.write_all(pcap).expect("unable to write pcap");
output.flush().expect("unable to flush");
let builder = RTSharkBuilder::builder()
.input_path(pcap_path.to_str().unwrap())
.display_filter("frame.number == 6")
.metadata_whitelist("tls.record")
.metadata_whitelist("tls.record.content_type")
.metadata_whitelist("tls.record.length");
let mut rtshark = builder.spawn().unwrap();
let pkt = rtshark.read().unwrap().unwrap();
let tls = pkt.layer_name("tls").expect("expected a tls layer");
let records = tls.groups("tls.record");
assert_eq!(records.len(), 2, "expected two TLS records in this packet");
let field = |group: &crate::layer::MetadataGroup<'_>, name: &str| -> String {
group
.fields
.iter()
.find(|m| m.name() == name)
.map(|m| m.value().to_string())
.unwrap_or_else(|| "?".to_string())
};
assert_eq!(
field(&records[0], "tls.record.content_type"),
"22",
"first record: expected content_type 22 (Handshake)"
);
assert_eq!(
field(&records[0], "tls.record.length"),
"122",
"first record: expected length 122"
);
assert_eq!(
field(&records[1], "tls.record.content_type"),
"20",
"second record: expected content_type 20 (Change Cipher Spec)"
);
assert_eq!(
field(&records[1], "tls.record.length"),
"1",
"second record: expected length 1"
);
rtshark.kill();
tmp_dir.close().expect("error deleting temp dir");
}
}