src/lib.rs
use clap::Parser;
use clap_num::maybe_hex;
use regex::Regex;
use std::fs;
use std::os::fd::AsRawFd;
use std::vec::Vec;
use nix::{self, request_code_write};
use nix::libc;
const DATA_MAX: usize = 4096;
const MON_IOC_MAGIC: u8 = 0x92;
const MON_IOCQ_URB_LEN: u8 = 1;
const MON_IOCG_STATS: u8 = 3;
const MON_IOCX_GETX: u8 = 10;
#[derive(Parser, Debug, Default)]
#[command(author = "Trevor Bentley", version, about, long_about = None)]
#[command(help_template = "\
{name} v{version}, by {author-with-newline}
{about-with-newline}
{usage-heading} {usage}
{all-args}{after-help}
")]
pub struct CliArgs {
/// Which USB bus to monitor (optional. Default: all)
#[arg(short, long)]
pub bus: Option<u16>,
/// USB device Vendor ID to monitor (optional. Default: all)
#[arg(short, long, value_parser=maybe_hex::<u16>)]
pub vid: Option<u16>,
/// USB device Product ID to monitor (optional. Default: all)
#[arg(short, long, value_parser=maybe_hex::<u16>)]
pub pid: Option<u16>,
/// Suppress packet output
#[arg(short, long)]
pub quiet: bool,
/// Show field header
#[arg(long)]
pub header: bool,
}
impl CliArgs {
pub fn args() -> CliArgs {
CliArgs::parse()
}
pub fn dev_filter() -> DeviceFilter {
let cli = CliArgs::args();
DeviceFilter {
bus: cli.bus,
vid: cli.vid,
pid: cli.pid,
}
}
}
pub type U8Cmp = fn(x: u8) -> bool;
pub type U16Cmp = fn(x: u16) -> bool;
pub type CIntCmp = fn(x: libc::c_int) -> bool;
pub type CUintCmp = fn(x: libc::c_uint) -> bool;
pub type SliceCmp = fn(x: &[u8]) -> bool;
pub type BoolCmp = fn(x: bool) -> bool;
#[derive(Default, Copy, Clone)]
pub struct DeviceFilter {
pub bus: Option<u16>,
pub vid: Option<u16>,
pub pid: Option<u16>,
}
#[derive(Default, Copy, Clone)]
#[allow(dead_code)]
pub struct UsbmonFilter {
// Capture behavior
pub pkts_after: usize, // packets to output after this packet
// USB packet
pub pkt_type: Option<U8Cmp>,
pub xfer_type: Option<U8Cmp>,
pub epnum: Option<U8Cmp>,
pub devnum: Option<U8Cmp>,
pub busnum: Option<U16Cmp>,
pub flag_setup: Option<U8Cmp>,
pub flag_data: Option<U8Cmp>,
pub status: Option<CIntCmp>,
pub data_len: Option<CUintCmp>,
pub dir_out: Option<BoolCmp>,
// USB data
pub data: Option<SliceCmp>,
// Setup packet
pub setup_request_type: Option<U8Cmp>,
pub setup_request: Option<U8Cmp>,
pub setup_value: Option<U16Cmp>,
pub setup_index: Option<U16Cmp>,
pub setup_length: Option<U16Cmp>,
}
#[derive(Default)]
pub struct UsbmonDevice {
bus: u16,
dev: u8,
vendor_id: u16,
product_id: u16,
manufacturer: String,
product: String,
}
impl UsbmonDevice {
pub fn device_match(&self, bus: u16, devnum: Option<u8>,
vid: Option<u16>, pid: Option<u16>) -> bool {
let mut is_match = true;
is_match &= bus == 0 || self.bus == bus;
is_match &= devnum.map_or(true, |x| x == self.dev);
is_match &= vid.map_or(true, |x| x == self.vendor_id);
is_match &= pid.map_or(true, |x| x == self.product_id);
is_match
}
}
pub fn read_devices() -> Vec<UsbmonDevice> {
let mut cur_dev: UsbmonDevice = UsbmonDevice::default();
let mut all_devs: Vec<UsbmonDevice> = Vec::new();
let mon_devices: &str = "/sys/kernel/debug/usb/devices";
let contents = fs::read_to_string(mon_devices)
.expect("debugfs devices file not found. Is usbmod kernel loaded?");
let t_re = Regex::new(r"^T: Bus=([0-9]*) Lev=([0-9]*) Prnt=([0-9]*) Port=([0-9]*) Cnt=([0-9]*) Dev#=(...) .*$").unwrap();
let p_re = Regex::new(r"^P: Vendor=(....) ProdID=(....).*$").unwrap();
let manu_re = Regex::new(r"^S: Manufacturer=(.*)$").unwrap();
let prod_re = Regex::new(r"^S: Product=(.*)$").unwrap();
for line in contents.lines() {
if let Some(t_caps) = t_re.captures(line) {
cur_dev.bus = t_caps.get(1).map_or("0", |s| s.as_str().trim()).parse::<u16>().unwrap_or(0);
cur_dev.dev = t_caps.get(6).map_or("0", |s| s.as_str().trim()).parse::<u8>().unwrap_or(0);
}
if let Some(p_caps) = p_re.captures(line) {
cur_dev.vendor_id = p_caps.get(1).map_or(0, |s| u16::from_str_radix(s.as_str(), 16).unwrap_or(0));
cur_dev.product_id = p_caps.get(2).map_or(0, |s| u16::from_str_radix(s.as_str(), 16).unwrap_or(0));
}
if let Some(manu_caps) = manu_re.captures(line) {
cur_dev.manufacturer = manu_caps.get(1).map_or("", |s| s.as_str().trim()).to_owned();
}
if let Some(prod_caps) = prod_re.captures(line) {
cur_dev.product = prod_caps.get(1).map_or("", |s| s.as_str().trim()).to_owned();
all_devs.push(cur_dev);
cur_dev = Default::default();
}
}
all_devs.sort_by_key(|x| x.bus);
all_devs
}
pub fn print_devices(all_devs: &Vec<UsbmonDevice>) {
println!("bus dev vid:pid product [manufacturer]");
println!("-----------------------------------------------");
for cur_dev in all_devs {
println!("{:02} {:03} {:04x}:{:04x} {} [{}]",
cur_dev.bus, cur_dev.dev,
cur_dev.vendor_id, cur_dev.product_id,
cur_dev.product, cur_dev.manufacturer);
}
}
//struct usbmon_packet {
// u64 id; /* 0: URB ID - from submission to callback */
// unsigned char type; /* 8: Same as text; extensible. */
// unsigned char xfer_type; /* ISO (0), Intr, Control, Bulk (3) */
// unsigned char epnum; /* Endpoint number and transfer direction */
// unsigned char devnum; /* Device address */
// u16 busnum; /* 12: Bus number */
// char flag_setup; /* 14: Same as text */
// char flag_data; /* 15: Same as text; Binary zero is OK. */
// s64 ts_sec; /* 16: gettimeofday */
// s32 ts_usec; /* 24: gettimeofday */
// int status; /* 28: */
// unsigned int length; /* 32: Length of data (submitted or actual) */
// unsigned int len_cap; /* 36: Delivered length */
// union { /* 40: */
// unsigned char setup[SETUP_LEN]; /* Only for Control S-type */
// struct iso_rec { /* Only for ISO */
// int error_count;
// int numdesc;
// } iso;
// } s;
// int interval; /* 48: Only for Interrupt and ISO */
// int start_frame; /* 52: For ISO */
// unsigned int xfer_flags; /* 56: copy of URB's transfer_flags */
// unsigned int ndesc; /* 60: Actual number of ISO descriptors */
//}; /* 64 total length */
#[derive(Default, Copy, Clone)]
#[repr(C, packed)]
struct IsoRec {
error_count: libc::c_int,
numdesc: libc::c_int,
}
#[derive(Default, Copy, Clone)]
#[allow(non_snake_case)]
#[repr(C, packed)]
struct SetupPacket {
bmRequestType: u8,
bRequest: u8,
wValue: u16,
wIndex: u16,
wLength: u16,
}
impl SetupPacket {
fn request_type(&self) -> u8 {
let x = self.bmRequestType;
x
}
fn request(&self) -> u8 {
let x = self.bRequest;
x
}
fn value(&self) -> u16 {
let x = self.wValue;
x
}
fn index(&self) -> u16 {
let x = self.wIndex;
x
}
fn length(&self) -> u16 {
let x = self.wLength;
x
}
}
#[derive(Copy, Clone)]
#[repr(C, packed)]
union SetupUnion {
setup: SetupPacket,
iso: IsoRec,
}
impl Default for SetupUnion {
fn default() -> Self { SetupUnion { setup: Default::default() } }
}
#[derive(Default, Copy, Clone)]
#[repr(C, packed)]
struct UsbmonPacket {
id: u64,
pkt_type: u8, // "S" (submit), "C" (complete)
xfer_type: u8, // ISO (0), Interrupt (1), Control (2), Bulk (3)
epnum: u8, // direction encoded in highest bit, 0x80 == in
devnum: u8,
busnum: u16,
flag_setup: u8, // 0 or "-" (setup packet present)
flag_data: u8, // 0, ">" (out no data), "=" (out data), or "<" (in)
ts_sec: i64,
ts_usec: i32,
// status always -115 (EINPROGRESS) for submit events
status: libc::c_int, // USB URB errno. -71 == EPROTO, -115 == EINPROGRESS
length: libc::c_uint,
len_cap: libc::c_uint,
s: SetupUnion,
interval: libc::c_int, // IRQ and ISO
start_frame: libc::c_int, // ISO
// from kernel's include/linux/usb.h:
//
// #define URB_SHORT_NOT_OK 0x0001 /* report short reads as errors */
// #define URB_ISO_ASAP 0x0002 /* iso-only; use the first unexpired
// * slot in the schedule */
// #define URB_NO_TRANSFER_DMA_MAP 0x0004 /* urb->transfer_dma valid on submit */
// #define URB_ZERO_PACKET 0x0040 /* Finish bulk OUT with short packet */
// #define URB_NO_INTERRUPT 0x0080 /* HINT: no non-error interrupt
// * needed */
// #define URB_FREE_BUFFER 0x0100 /* Free transfer buffer with the URB */
//
// /* The following flags are used internally by usbcore and HCDs */
// #define URB_DIR_IN 0x0200 /* Transfer from device to host */
// #define URB_DIR_OUT 0
// #define URB_DIR_MASK URB_DIR_IN
//
// #define URB_DMA_MAP_SINGLE 0x00010000 /* Non-scatter-gather mapping */
// #define URB_DMA_MAP_PAGE 0x00020000 /* HCD-unsupported S-G */
// #define URB_DMA_MAP_SG 0x00040000 /* HCD-supported S-G */
// #define URB_MAP_LOCAL 0x00080000 /* HCD-local-memory mapping */
// #define URB_SETUP_MAP_SINGLE 0x00100000 /* Setup packet DMA mapped */
// #define URB_SETUP_MAP_LOCAL 0x00200000 /* HCD-local setup packet */
// #define URB_DMA_SG_COMBINED 0x00400000 /* S-G entries were combined */
// #define URB_ALIGNED_TEMP_BUFFER 0x00800000 /* Temp buffer was alloc'd */
xfer_flags: libc::c_uint, // bit field of above URB flags
ndesc: libc::c_uint, // number of ISO descriptors
}
impl UsbmonPacket {
fn setup_packet(&self) -> Option<&SetupPacket> {
match self.flag_setup {
b'-' => None,
_ => unsafe {
let setup = &self.s.setup;
Some(setup)
},
}
}
fn pkt_type(&self) -> u8 {
let x = self.pkt_type;
x
}
fn xfer_type(&self) -> u8 {
let x = self.xfer_type;
x
}
fn busnum(&self) -> u16 {
let x = self.busnum;
x
}
fn devnum(&self) -> u8 {
let x = self.devnum;
x
}
fn epnum(&self) -> u8 {
let x = self.epnum;
x & 0x7f
}
fn dir_out(&self) -> bool {
let epnum = self.epnum;
match epnum & 0x80 { 0 => true, _ => false }
}
fn dir_chr(&self) -> char {
let dir_out = self.dir_out();
match dir_out { true => '>', _ => '<' }
}
fn flag_setup(&self) -> u8 {
let x = self.flag_setup;
x
}
fn flag_data(&self) -> u8 {
let x = self.flag_data;
x
}
fn status(&self) -> libc::c_int {
let x = self.status;
match self.pkt_type() {
b'S' => 0,
_ => x
}
}
fn status_chr(&self) -> char {
match self.pkt_type() {
b'S' => ' ', // submit packets always have status EINPROGRESS
_ => match self.status {
0 => ' ',
_ => 'e',
},
}
}
fn length(&self) -> libc::c_uint {
let x = self.len_cap;
x
}
fn length_available(&self) -> libc::c_uint {
std::cmp::min(self.length(), DATA_MAX as u32)
}
fn ts_sec(&self) -> i64 {
let x = self.ts_sec;
x
}
fn ts_usec(&self) -> i64 {
let x = self.ts_usec;
x as i64
}
fn usec_since(&self, since: i64) -> i64 {
let ts_usec = (self.ts_sec() * 1000 * 1000) + (self.ts_usec());
ts_usec - since
}
fn is_error(&self) -> bool {
// 'S' packets always return EINPROGRESS
self.pkt_type != b'S' && self.status != 0
}
fn match_filter(&self, f: Option<&UsbmonFilter>, data: &[u8]) -> bool {
let mut matched = true;
if f.is_none() {
return true;
}
let f = f.unwrap();
matched &= f.pkt_type.map(|x| x(self.pkt_type())).unwrap_or(true);
matched &= f.xfer_type.map(|x| x(self.xfer_type())).unwrap_or(true);
matched &= f.epnum.map(|x| x(self.epnum())).unwrap_or(true);
matched &= f.devnum.map(|x| x(self.devnum())).unwrap_or(true);
matched &= f.busnum.map(|x| x(self.busnum())).unwrap_or(true);
matched &= f.dir_out.map(|x| x(self.dir_out())).unwrap_or(true);
matched &= f.flag_setup.map(|x| x(self.flag_setup())).unwrap_or(true);
matched &= f.flag_data.map(|x| x(self.flag_data())).unwrap_or(true);
matched &= f.status.map(|x| x(self.status())).unwrap_or(true);
matched &= f.data_len.map(|x| x(self.length())).unwrap_or(true);
matched &= f.data.map(|x| x(data)).unwrap_or(true);
matched &= f.setup_request_type
.map(|x| x(self.setup_packet()
.map(|s| s.request_type()).unwrap_or(0))).unwrap_or(true);
matched &= f.setup_request
.map(|x| x(self.setup_packet()
.map(|s| s.request()).unwrap_or(0))).unwrap_or(true);
matched &= f.setup_value
.map(|x| x(self.setup_packet()
.map(|s| s.value()).unwrap_or(0))).unwrap_or(true);
matched &= f.setup_index
.map(|x| x(self.setup_packet()
.map(|s| s.index()).unwrap_or(0))).unwrap_or(true);
matched &= f.setup_length
.map(|x| x(self.setup_packet()
.map(|s| s.length()).unwrap_or(0))).unwrap_or(true);
matched
}
fn print_hdr() {
println!(" usec bs:dv:ep dir pkt:xfr [len]: |setup| data");
println!("------------------------------------------------------------------------");
}
fn print(&self, start_time: i64, pkt_data: &[u8; DATA_MAX]) {
print!("{:010} {:02}:{:02}:{:02} {} {}:{:02} {} [{:03}]: ",
self.usec_since(start_time),
self.busnum(),
self.devnum(),
self.epnum(),
self.dir_chr(),
self.pkt_type(),
self.xfer_type(),
self.status_chr(),
self.length());
if let Some(setup_pkt) = self.setup_packet() {
print!("|{:02x} {:02x} {:04x} {:04x} {:02}| ",
setup_pkt.request_type(),
setup_pkt.request(),
setup_pkt.value(),
setup_pkt.index(),
setup_pkt.length());
}
// print data
if self.length() > 0 {
for i in (0..self.length_available()).step_by(16) {
if i != 0 || self.setup_packet().is_some() {
println!();
print!(" ")
}
let max = std::cmp::min(16, self.length_available() - i);
for j in 0..max {
print!("{:02x}", pkt_data[(i + j) as usize]);
}
}
}
println!();
}
}
#[derive(Default, Copy, Clone)]
#[repr(C, packed)]
struct MonStats {
queued: u32,
dropped: u32,
}
#[derive(Copy, Clone)]
#[repr(C, packed(1))]
struct MonGetArg {
hdr: *mut UsbmonPacket,
data: *mut u8,
alloc: libc::size_t,
}
impl Default for MonGetArg {
fn default() -> Self { MonGetArg {
hdr: std::ptr::null::<UsbmonPacket>() as *mut UsbmonPacket,
data: std::ptr::null::<u8>() as *mut u8,
alloc: 0,
} }
}
pub fn monitor(dev_filter: Option<&DeviceFilter>,
start_filter: Option<&UsbmonFilter>,
active_filter: Option<&UsbmonFilter>,
end_filter: Option<&UsbmonFilter>) -> i64 {
let cli_args = CliArgs::args();
let bus: u16 = match dev_filter {
None => 0,
Some(f) => f.bus.unwrap_or(0),
};
let bus_devfile: String = format!("/dev/usbmon{}", bus);
let bus_dev = std::fs::File::open(&bus_devfile)
.expect(&format!("device {} not found. is usbmon module loaded?", &bus_devfile));
let bus_dev_fd = bus_dev.as_raw_fd();
let vid = match dev_filter {
None => None,
Some(f) => f.vid,
};
let pid = match dev_filter {
None => None,
Some(f) => f.pid,
};
nix::ioctl_none!(usbmon_read_len, MON_IOC_MAGIC, MON_IOCQ_URB_LEN);
nix::ioctl_read!(usbmon_read_stats, MON_IOC_MAGIC, MON_IOCG_STATS, MonStats);
// Due to a bug in either the packing or the size_of operator, the
// size of 24 bytes has to be explicitly set here to calculate the
// ioctl because the struct reports as 32 bytes even when packed.
//
//nix::ioctl_write_ptr!(usbmon_read_data, MON_IOC_MAGIC, MON_IOCX_GETX, MonGetArg);
// request_code_write!(MON_IOC_MAGIC, MON_IOCX_GETX, std::mem::size_of::<MonGetArg>())
nix::ioctl_write_ptr_bad!(usbmon_read_data, request_code_write!(MON_IOC_MAGIC, MON_IOCX_GETX, 24), MonGetArg);
let mut _next_len: i32;
let mut mon_stats = Default::default();
let mut pkt_data: [u8; DATA_MAX] = [0; DATA_MAX];
let mut mon_pkt: UsbmonPacket = Default::default();
let mon_arg = MonGetArg {
hdr: &mut mon_pkt,
data: &mut pkt_data as *mut u8,
alloc: DATA_MAX,
};
let mut all_devs: Vec<UsbmonDevice> = read_devices();
let mut start_time: i64 = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default().as_micros() as i64;
let last_time: i64;
let mut capturing: bool = false;
let mut stop_capturing_count: Option<usize> = None;
if cli_args.header {
UsbmonPacket::print_hdr();
}
loop {
unsafe { usbmon_read_stats(bus_dev_fd, &mut mon_stats).expect("stats ioctl failed"); }
let queued = mon_stats.queued;
let _dropped = mon_stats.dropped;
if queued == 0 {
std::thread::sleep(std::time::Duration::from_millis(20));
continue;
}
unsafe {
// unused, just to prove that ioctl is working
_next_len = usbmon_read_len(bus_dev_fd).expect("len ioctl failed");
// get the actual event
usbmon_read_data(bus_dev_fd, &mon_arg).expect("pkt ioctl failed");
let hdr = mon_arg.hdr;
// Check if this packet's bus/dev combo has been enumerated yet. Re-enumerate
// if it hasn't or if the packet indicates a bus error (which can be device
// connects and disconnects).
if !all_devs.iter().any(|d| d.device_match((*hdr).busnum(),
Some((*hdr).devnum()),
None, None)) ||
(*hdr).is_error() {
all_devs = read_devices();
}
// See if we have enumerated a device matching the registered capture filter,
// and if this packet matches it.
let matching: Vec<&UsbmonDevice> = all_devs.iter().filter(
|d| d.device_match(bus, None, vid, pid)).collect();
match matching.len() {
0 => {
// No known device matching the capture filter. Maybe it isn't
// connected yet.
continue;
},
n if n == all_devs.len() => {
// All devices matched means no filter, continue
},
_ => {
// Multiple devices matched
let mut matched = false;
for dev in matching {
// Device is enumerated, did it produce this packet?
if dev.device_match((*hdr).busnum(),
Some((*hdr).devnum()),
dev_filter.map_or(None, |f| f.vid),
dev_filter.map_or(None, |f| f.pid)) {
matched = true;
break;
}
}
if !matched {
// This packet isn't from a matching device.
continue;
}
// Packet matches filter, continue.
}
}
if !capturing && (*hdr).match_filter(start_filter, &pkt_data) {
// If the start filter matches, begin capturing packets
capturing = true;
start_time = (*hdr).usec_since(0);
if !cli_args.quiet {
(*hdr).print(start_time, &pkt_data);
}
}
else if capturing {
if end_filter.is_some() && (*hdr).match_filter(end_filter, &pkt_data) {
if !cli_args.quiet {
(*hdr).print(start_time, &pkt_data);
}
if let Some(f) = end_filter {
stop_capturing_count = Some(f.pkts_after);
}
}
else if (*hdr).match_filter(active_filter, &pkt_data) {
if !cli_args.quiet {
(*hdr).print(start_time, &pkt_data);
}
}
}
if let Some(cnt) = stop_capturing_count {
if cnt == 0 {
last_time = (*hdr).usec_since(start_time);
break;
}
let cnt = cnt - 1;
stop_capturing_count = Some(cnt);
}
}
}
last_time
}