diff --git a/src/bin/wavehunter.rs b/src/bin/wavehunter.rs index c0e8b66..ed923a8 100644 --- a/src/bin/wavehunter.rs +++ b/src/bin/wavehunter.rs @@ -1,21 +1,28 @@ use wavehunter::diag_device::{DiagDevice, DiagResult}; use wavehunter::diag_reader::DiagReader; +use wavehunter::gsmtap_parser::GsmtapParser; +use wavehunter::pcap::PcapFile; + +use log::debug; fn main() -> DiagResult<()> { - // this should eventually be removed for prod env_logger::init(); - let file = std::fs::File::options() - .read(true) - .write(true) - .open("/dev/diag")?; - let mut dev = DiagDevice::new(&file)?; + let mut dev = DiagDevice::new()?; dev.enable_debug_mode("/data/wavehunter-debug")?; dev.config_logs()?; + let mut gsmtap_parser = GsmtapParser::new(); + let mut pcap_file = PcapFile::new("/data/wavehunter.pcap").unwrap(); + pcap_file.write_iface_header().unwrap(); + loop { for msg in dev.read_response()? { - println!("msg: {:?}", msg); + debug!("msg: {:?}", msg); + if let Some((timestamp, gsmtap_msg)) = gsmtap_parser.recv_message(msg).unwrap() { + debug!("gsmtap_msg: {:?}", gsmtap_msg); + pcap_file.write_gsmtap_message(gsmtap_msg, timestamp).unwrap(); + } } } } diff --git a/src/bin/wavehunter_reader.rs b/src/bin/wavehunter_reader.rs index cbb99c1..3be7350 100644 --- a/src/bin/wavehunter_reader.rs +++ b/src/bin/wavehunter_reader.rs @@ -1,20 +1,32 @@ use wavehunter::debug_file::DebugFileReader; use wavehunter::diag_reader::DiagReader; use wavehunter::diag_device::DiagResult; +use wavehunter::gsmtap_parser::GsmtapParser; +use wavehunter::pcap::PcapFile; + +use log::{debug, error}; fn main() -> DiagResult<()> { - // this should eventually be removed for prod env_logger::init(); + let args: Vec = std::env::args().collect(); if args.len() != 2 { - println!("Usage: {} /path/to/debug/file", args[0]); + error!("Usage: {} /path/to/debug/file", args[0]); std::process::exit(1); } let mut debug_reader = DebugFileReader::new(&args[1])?; + let mut gsmtap_parser = GsmtapParser::new(); + let mut pcap_file = PcapFile::new("./wavehunter.pcap").unwrap(); + pcap_file.write_iface_header().unwrap(); + loop { for msg in debug_reader.read_response()? { - println!("msg: {:?}", msg); + debug!("msg: {:?}", msg); + if let Some((timestamp, gsmtap_msg)) = gsmtap_parser.recv_message(msg).unwrap() { + debug!("gsmtap_msg: {:?}", gsmtap_msg); + pcap_file.write_gsmtap_message(gsmtap_msg, timestamp).unwrap(); + } } } } diff --git a/src/debug_file.rs b/src/debug_file.rs index 3dd2c2a..89284d7 100644 --- a/src/debug_file.rs +++ b/src/debug_file.rs @@ -5,6 +5,7 @@ use crate::diag::*; use deku::prelude::*; use std::fs::File; use std::io::Read; +use log::{warn, info}; #[derive(Debug, DekuRead, DekuWrite)] #[deku(endian = "little")] @@ -27,13 +28,12 @@ impl DebugFileReader { } } - impl DiagReader for DebugFileReader { fn get_next_messages_container(&mut self) -> DiagResult { let mut bytes_read_buf = [0; 4]; if let Err(e) = self.file.read_exact(&mut bytes_read_buf) { if e.kind() == std::io::ErrorKind::UnexpectedEof { - println!("reached end of debug file, exiting..."); + info!("reached end of debug file, exiting..."); std::process::exit(0); } return Err(e.into()); @@ -43,7 +43,7 @@ impl DiagReader for DebugFileReader { self.file.read_exact(&mut data)?; let ((leftover_bytes, _), container) = MessagesContainer::from_bytes((&data, 0))?; if leftover_bytes.len() > 0 { - println!("warning: {} leftover bytes when parsing MessagesContainer", leftover_bytes.len()); + warn!("warning: {} leftover bytes when parsing MessagesContainer", leftover_bytes.len()); } Ok(container) } diff --git a/src/diag.rs b/src/diag.rs index fd8136b..8d8ebfa 100644 --- a/src/diag.rs +++ b/src/diag.rs @@ -68,7 +68,6 @@ pub enum Message { inner_length: u16, log_type: u16, timestamp: Timestamp, - //#[deku(count = "inner_length - 12")] #[deku(ctx = "*log_type, *inner_length - 12")] body: LogBody, }, @@ -126,11 +125,13 @@ pub enum LogBody { rrc_rel: u8, rrc_version_minor: u8, rrc_version_major: u8, - #[deku(count = "hdr_len - 4")] // is this right?? + // is this right?? based on https://github.com/fgsect/scat/blob/97442580e628de414c9f7c2a185f4e28d0ee7523/src/scat/parsers/qualcomm/diagltelogparser.py#L1327 + #[deku(count = "hdr_len - 4")] msg: Vec, }, #[deku(id = "0x11eb")] IpTraffic { + // is this right?? based on https://github.com/P1sec/QCSuper/blob/81dbaeee15ec7747e899daa8e3495e27cdcc1264/src/modules/pcap_dump.py#L378 #[deku(count = "hdr_len - 8")] // is this right??? msg: Vec, }, @@ -219,12 +220,39 @@ impl LteRrcOtaPacket { LteRrcOtaPacket::V25 { sfn_subfn, .. } => *sfn_subfn, } } - pub fn get_sfn(&self) -> u16 { - self.get_sfn_subfn() >> 4 + pub fn get_sfn(&self) -> u32 { + self.get_sfn_subfn() as u32 >> 4 } - pub fn get_subfn(&self) -> u16 { - self.get_sfn_subfn() & 0xf + pub fn get_subfn(&self) -> u8 { + (self.get_sfn_subfn() & 0xf) as u8 + } + + pub fn get_pdu_num(&self) -> u8 { + match self { + LteRrcOtaPacket::V0 { pdu_num, .. } => *pdu_num, + LteRrcOtaPacket::V5 { pdu_num, .. } => *pdu_num, + LteRrcOtaPacket::V8 { pdu_num, .. } => *pdu_num, + LteRrcOtaPacket::V25 { pdu_num, .. } => *pdu_num, + } + } + + pub fn get_earfcn(&self) -> u32 { + match self { + LteRrcOtaPacket::V0 { earfcn, .. } => *earfcn as u32, + LteRrcOtaPacket::V5 { earfcn, .. } => *earfcn as u32, + LteRrcOtaPacket::V8 { earfcn, .. } => *earfcn, + LteRrcOtaPacket::V25 { earfcn, .. } => *earfcn, + } + } + + pub fn take_payload(self) -> Vec { + match self { + LteRrcOtaPacket::V0 { packet, .. } => packet, + LteRrcOtaPacket::V5 { packet, .. } => packet, + LteRrcOtaPacket::V8 { packet, .. } => packet, + LteRrcOtaPacket::V25 { packet, .. } => packet, + } } } diff --git a/src/diag_device.rs b/src/diag_device.rs index 9886abf..ff42b8f 100644 --- a/src/diag_device.rs +++ b/src/diag_device.rs @@ -8,6 +8,7 @@ use std::fs::File; use std::io::{Read, Write}; use std::os::fd::AsRawFd; use thiserror::Error; +use log::{info, warn, error}; use deku::prelude::*; pub type DiagResult = Result; @@ -56,16 +57,14 @@ const MEMORY_DEVICE_MODE: i32 = 2; const DIAG_IOCTL_REMOTE_DEV: u32 = 32; const DIAG_IOCTL_SWITCH_LOGGING: u32 = 7; -pub struct DiagDevice<'a> { - file: &'a File, +pub struct DiagDevice { + file: File, debug_file: Option, read_buf: Vec, use_mdm: i32, } - - -impl<'a> DiagReader for DiagDevice<'a> { +impl DiagReader for DiagDevice { fn get_next_messages_container(&mut self) -> DiagResult { let mut bytes_read; loop { @@ -81,19 +80,23 @@ impl<'a> DiagReader for DiagDevice<'a> { size: bytes_read as u32, data: &self.read_buf[0..bytes_read], }; - let debug_block_bytes = debug_block.to_bytes().unwrap(); - debug_file.write_all(&debug_block_bytes).unwrap(); + let debug_block_bytes = debug_block.to_bytes()?; + debug_file.write_all(&debug_block_bytes)?; } let ((leftover_bytes, _), container) = MessagesContainer::from_bytes((&self.read_buf[0..bytes_read], 0))?; if leftover_bytes.len() > 0 { - println!("warning: {} leftover bytes when parsing MessagesContainer", leftover_bytes.len()); + warn!("warning: {} leftover bytes when parsing MessagesContainer", leftover_bytes.len()); } Ok(container) } } -impl<'a> DiagDevice<'a> { - pub fn new(file: &'a File) -> DiagResult { +impl DiagDevice { + pub fn new() -> DiagResult { + let file = std::fs::File::options() + .read(true) + .write(true) + .open("/dev/diag")?; let fd = file.as_raw_fd(); enable_frame_readwrite(fd, MEMORY_DEVICE_MODE)?; @@ -114,7 +117,7 @@ impl<'a> DiagDevice<'a> { .create(true) .write(true) .open(path)?; - println!("enabling debug mode, writing debug output to {:?}", debug_file); + info!("enabling debug mode, writing debug output to {:?}", debug_file); self.debug_file = Some(debug_file); Ok(()) } @@ -124,8 +127,8 @@ impl<'a> DiagDevice<'a> { data_type: DataType::UserSpace, use_mdm: self.use_mdm > 0, mdm_field: -1, - hdlc_encapsulated_request: hdlc_encapsulate(&req.to_bytes().unwrap(), &CRC_CCITT), - }.to_bytes().unwrap(); + hdlc_encapsulated_request: hdlc_encapsulate(&req.to_bytes()?, &CRC_CCITT), + }.to_bytes()?; unsafe { let fd = self.file.as_raw_fd(); let buf_ptr = buf.as_ptr() as *const libc::c_void; @@ -144,7 +147,7 @@ impl<'a> DiagDevice<'a> { for msg in self.read_response()? { match msg { - Message::Log { .. } => println!("skipping log response..."), + Message::Log { .. } => info!("skipping log response..."), Message::Response { payload, status, .. } => match payload { ResponsePayload::LogConfig(LogConfigResponse::RetrieveIdRanges { log_mask_sizes }) => { if status != 0 { @@ -152,7 +155,7 @@ impl<'a> DiagDevice<'a> { } return Ok(log_mask_sizes); }, - _ => println!("skipping non-LogConfigResponse response..."), + _ => info!("skipping non-LogConfigResponse response..."), }, } } @@ -166,7 +169,7 @@ impl<'a> DiagDevice<'a> { for msg in self.read_response()? { match msg { - Message::Log { .. } => println!("skipping log response..."), + Message::Log { .. } => info!("skipping log response..."), Message::Response { payload, status, .. } => { if let ResponsePayload::LogConfig(LogConfigResponse::SetMask) = payload { if status != 0 { @@ -182,13 +185,13 @@ impl<'a> DiagDevice<'a> { } pub fn config_logs(&mut self) -> DiagResult<()> { - println!("retrieving diag logging capabilities..."); + info!("retrieving diag logging capabilities..."); let log_mask_sizes = self.retrieve_id_ranges()?; for (log_type, &log_mask_bitsize) in log_mask_sizes.iter().enumerate() { if log_mask_bitsize > 0 { self.set_log_mask(log_type as u32, log_mask_bitsize)?; - println!("enabled logging for log type {}", log_type); + info!("enabled logging for log type {}", log_type); } } diff --git a/src/diag_reader.rs b/src/diag_reader.rs index 00ded53..b8d2d85 100644 --- a/src/diag_reader.rs +++ b/src/diag_reader.rs @@ -3,6 +3,7 @@ use crate::diag_device::DiagResult; use crc::{Crc, Algorithm}; use deku::prelude::*; +use log::{debug, info, warn, error}; // this is sorta based on the params qcsuper uses, plus what seems to be used in // https://github.com/fgsect/scat/blob/f1538b397721df3ab8ba12acd26716abcf21f78b/util.py#L47 @@ -27,7 +28,7 @@ pub trait DiagReader { if container.data_type == DataType::UserSpace { return self.parse_response_container(container); } else { - println!("skipping non-userspace message...") + info!("skipping non-userspace message...") } } } @@ -40,18 +41,18 @@ pub trait DiagReader { Ok(data) => match Message::from_bytes((&data, 0)) { Ok(((leftover_bytes, _), res)) => { if leftover_bytes.len() > 0 { - println!("warning: {} leftover bytes when parsing Message", leftover_bytes.len()); + warn!("warning: {} leftover bytes when parsing Message", leftover_bytes.len()); } result.push(res); }, Err(e) => { - println!("error parsing response: {:?}", e); - println!("{:?}", data); + error!("error parsing response: {:?}", e); + debug!("{:?}", data); }, }, Err(err) => { - println!("error decapsulating response: {:?}", err); - println!("{:?}", &sub_msg); + error!("error decapsulating response: {:?}", err); + debug!("{:?}", &sub_msg); } } } diff --git a/src/gsmtap.rs b/src/gsmtap.rs new file mode 100644 index 0000000..cf61ec0 --- /dev/null +++ b/src/gsmtap.rs @@ -0,0 +1,232 @@ +//! The spec for GSMTAP is here: https://github.com/osmocom/libosmocore/blob/master/include/osmocom/core/gsmtap.h + +use deku::prelude::*; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum GsmtapType { + Um(UmSubtype), + Abis, + UmBurst, /* raw burst bits */ + SIM, /* ISO 7816 smart card interface */ + TetraI1, /* tetra air interface */ + TetraI1Burst, /* tetra air interface */ + WmxBurst, /* WiMAX burst */ + GbLlc, /* GPRS Gb interface: LLC */ + GbSndcp, /* GPRS Gb interface: SNDCP */ + Gmr1Um, /* GMR-1 L2 packets */ + UmtsRlcMac, + UmtsRrc(UmtsRrcSubtype), + LteRrc(LteRrcSubtype), /* LTE interface */ + LteMac, /* LTE MAC interface */ + LteMacFramed, /* LTE MAC with context hdr */ + OsmocoreLog, /* libosmocore logging */ + QcDiag, /* Qualcomm DIAG frame */ + LteNas, /* LTE Non-Access Stratum */ + E1T1, /* E1/T1 Lines */ + GsmRlp, /* GSM RLP frames as per 3GPP TS 24.022 */ +} + +#[repr(u8)] +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum UmSubtype { + Unknown = 0x00, + Bcch = 0x01, + Ccch = 0x02, + Rach = 0x03, + Agch = 0x04, + Pch = 0x05, + Sdcch = 0x06, + Sdcch4 = 0x07, + Sdcch8 = 0x08, + TchF = 0x09, + TchH = 0x0a, + Pacch = 0x0b, + Cbch52 = 0x0c, + Pdch = 0x0d, + Ptcch = 0x0e, + Cbch51 = 0x0f, +} + +#[repr(u8)] +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum UmtsRrcSubtype { + DlDcch = 0, + UlDcch = 1, + DlCcch = 2, + UlCcch = 3, + Pcch = 4, + DlShcch = 5, + UlShcch = 6, + BcchFach = 7, + BcchBch = 8, + Mcch = 9, + Msch = 10, + HandoverToUTRANCommand = 11, + InterRATHandoverInfo = 12, + SystemInformationBCH = 13, + SystemInformationContainer = 14, + UERadioAccessCapabilityInfo = 15, + MasterInformationBlock = 16, + SysInfoType1 = 17, + SysInfoType2 = 18, + SysInfoType3 = 19, + SysInfoType4 = 20, + SysInfoType5 = 21, + SysInfoType5bis = 22, + SysInfoType6 = 23, + SysInfoType7 = 24, + SysInfoType8 = 25, + SysInfoType9 = 26, + SysInfoType10 = 27, + SysInfoType11 = 28, + SysInfoType11bis = 29, + SysInfoType12 = 30, + SysInfoType13 = 31, + SysInfoType13_1 = 32, + SysInfoType13_2 = 33, + SysInfoType13_3 = 34, + SysInfoType13_4 = 35, + SysInfoType14 = 36, + SysInfoType15 = 37, + SysInfoType15bis = 38, + SysInfoType15_1 = 39, + SysInfoType15_1bis = 40, + SysInfoType15_2 = 41, + SysInfoType15_2bis = 42, + SysInfoType15_2ter = 43, + SysInfoType15_3 = 44, + SysInfoType15_3bis = 45, + SysInfoType15_4 = 46, + SysInfoType15_5 = 47, + SysInfoType15_6 = 48, + SysInfoType15_7 = 49, + SysInfoType15_8 = 50, + SysInfoType16 = 51, + SysInfoType17 = 52, + SysInfoType18 = 53, + SysInfoType19 = 54, + SysInfoType20 = 55, + SysInfoType21 = 56, + SysInfoType22 = 57, + SysInfoTypeSB1 = 58, + SysInfoTypeSB2 = 59, + ToTargetRNCContainer = 60, + TargetRNCToSourceRNCContainer = 61 +} + +#[repr(u8)] +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum LteRrcSubtype { + DlCcch = 0, + DlDcch = 1, + UlCcch = 2, + UlDcch = 3, + BcchBch = 4, + BcchDlSch = 5, + PCCH = 6, + MCCH = 7, + BcchBchMbms = 8, + BcchDlSchBr = 9, + BcchDlSchMbms = 10, + ScMcch = 11, + SbcchSlBch = 12, + SbcchSlBchV2x = 13, + DlCcchNb = 14, + DlDcchNb = 15, + UlCcchNb = 16, + UlDcchNb = 17, + BcchBchNb = 18, + BcchBchTddNb = 19, + BcchDlSchNb = 20, + PcchNb = 21, + ScMcchNb = 22, +} + +impl GsmtapType { + pub fn get_type(&self) -> u8 { + match self { + GsmtapType::Um(_) => 0x01, + GsmtapType::Abis => 0x02, + GsmtapType::UmBurst => 0x03, + GsmtapType::SIM => 0x04, + GsmtapType::TetraI1 => 0x05, + GsmtapType::TetraI1Burst => 0x06, + GsmtapType::WmxBurst => 0x07, + GsmtapType::GbLlc => 0x08, + GsmtapType::GbSndcp => 0x09, + GsmtapType::Gmr1Um => 0x0a, + GsmtapType::UmtsRlcMac => 0x0b, + GsmtapType::UmtsRrc(_) => 0x0c, + GsmtapType::LteRrc(_) => 0x0d, + GsmtapType::LteMac => 0x0e, + GsmtapType::LteMacFramed => 0x0f, + GsmtapType::OsmocoreLog => 0x10, + GsmtapType::QcDiag => 0x11, + GsmtapType::LteNas => 0x12, + GsmtapType::E1T1 => 0x13, + GsmtapType::GsmRlp => 0x14, + } + } + + pub fn get_subtype(&self) -> u8 { + match self { + GsmtapType::Um(subtype) => *subtype as u8, + GsmtapType::UmtsRrc(subtype) => *subtype as u8, + GsmtapType::LteRrc(subtype) => *subtype as u8, + _ => 0, + } + } +} + +#[derive(Debug, Clone, PartialEq, DekuWrite)] +#[deku(endian = "big")] +pub struct GsmtapHeader { + #[deku(skip)] + pub gsmtap_type: GsmtapType, + + #[deku(assert_eq = "2")] + pub version: u8, + #[deku(assert_eq = "4")] + pub header_len: u8, // length in 4-byte words + #[deku(update = "self.gsmtap_type.get_type()")] + packet_type: u8, + pub timeslot: u8, + pub arfcn: u16, + pub signal_dbm: i8, + pub signal_noise_ratio_db: u8, + pub frame_number: u32, + #[deku(update = "self.gsmtap_type.get_subtype()")] + subtype: u8, + pub antenna_number: u8, + pub subslot: u8, + #[deku(assert_eq = "0")] + pub reserved: u8, +} + +impl GsmtapHeader { + pub fn new( + gsmtap_type: GsmtapType, + ) -> Self { + GsmtapHeader { + gsmtap_type, + version: 2, + header_len: 4, + packet_type: gsmtap_type.get_type(), + timeslot: 0, + arfcn: 0, + signal_dbm: 0, + signal_noise_ratio_db: 0, + frame_number: 0, + subtype: gsmtap_type.get_subtype(), + antenna_number: 0, + subslot: 0, + reserved: 0, + } + } +} + +#[derive(Debug, PartialEq, Clone, DekuWrite)] +pub struct GsmtapMessage { + pub header: GsmtapHeader, + pub payload: Vec, +} diff --git a/src/gsmtap_parser.rs b/src/gsmtap_parser.rs new file mode 100644 index 0000000..e87e13c --- /dev/null +++ b/src/gsmtap_parser.rs @@ -0,0 +1,125 @@ +use crate::diag::*; +use crate::gsmtap::*; + +use log::error; +use thiserror::Error; + +pub struct GsmtapParser { +} + +#[derive(Debug, Error)] +pub enum GsmtapParserError { + #[error("Invalid LteRrcOtaMessage ext header version {0}")] + InvalidLteRrcOtaExtHeaderVersion(u8), + #[error("Invalid LteRrcOtaMessage header/PDU number combination: {0}/{1}")] + InvalidLteRrcOtaHeaderPduNum(u8, u8), +} + +impl GsmtapParser { + pub fn new() -> Self { + GsmtapParser {} + } + + pub fn recv_message(&mut self, msg: Message) -> Result, GsmtapParserError> { + if let Message::Log { timestamp, body, .. } = msg { + match self.log_to_gsmtap(body)? { + Some(msg) => Ok(Some((timestamp, msg))), + None => Ok(None), + } + } else { + Ok(None) + } + } + + fn log_to_gsmtap(&self, value: LogBody) -> Result, GsmtapParserError> { + match value { + LogBody::LteRrcOtaMessage { ext_header_version, packet } => { + let gsmtap_type = match ext_header_version { + 0x02 | 0x03 | 0x04 | 0x06 | 0x07 | 0x08 | 0x0d | 0x16 => match packet.get_pdu_num() { + 1 => GsmtapType::LteRrc(LteRrcSubtype::BcchBch), + 2 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSch), + 3 => GsmtapType::LteRrc(LteRrcSubtype::MCCH), + 4 => GsmtapType::LteRrc(LteRrcSubtype::PCCH), + 5 => GsmtapType::LteRrc(LteRrcSubtype::DlCcch), + 6 => GsmtapType::LteRrc(LteRrcSubtype::DlDcch), + 7 => GsmtapType::LteRrc(LteRrcSubtype::UlCcch), + 8 => GsmtapType::LteRrc(LteRrcSubtype::UlDcch), + pdu => return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(ext_header_version, pdu)), + }, + 0x09 | 0x0c => match packet.get_pdu_num() { + 8 => GsmtapType::LteRrc(LteRrcSubtype::BcchBch), + 9 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSch), + 10 => GsmtapType::LteRrc(LteRrcSubtype::MCCH), + 11 => GsmtapType::LteRrc(LteRrcSubtype::PCCH), + 12 => GsmtapType::LteRrc(LteRrcSubtype::DlCcch), + 13 => GsmtapType::LteRrc(LteRrcSubtype::DlDcch), + 14 => GsmtapType::LteRrc(LteRrcSubtype::UlCcch), + 15 => GsmtapType::LteRrc(LteRrcSubtype::UlDcch), + pdu => return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(ext_header_version, pdu)), + }, + 0x0e | 0x0f | 0x10 => match packet.get_pdu_num() { + 1 => GsmtapType::LteRrc(LteRrcSubtype::BcchBch), + 2 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSch), + 4 => GsmtapType::LteRrc(LteRrcSubtype::MCCH), + 5 => GsmtapType::LteRrc(LteRrcSubtype::PCCH), + 6 => GsmtapType::LteRrc(LteRrcSubtype::DlCcch), + 7 => GsmtapType::LteRrc(LteRrcSubtype::DlDcch), + 8 => GsmtapType::LteRrc(LteRrcSubtype::UlCcch), + 9 => GsmtapType::LteRrc(LteRrcSubtype::UlDcch), + pdu => return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(ext_header_version, pdu)), + }, + 0x13 | 0x1a | 0x1b => match packet.get_pdu_num() { + 1 => GsmtapType::LteRrc(LteRrcSubtype::BcchBch), + 3 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSch), + 6 => GsmtapType::LteRrc(LteRrcSubtype::MCCH), + 7 => GsmtapType::LteRrc(LteRrcSubtype::PCCH), + 8 => GsmtapType::LteRrc(LteRrcSubtype::DlCcch), + 9 => GsmtapType::LteRrc(LteRrcSubtype::DlDcch), + 10 => GsmtapType::LteRrc(LteRrcSubtype::UlCcch), + 11 => GsmtapType::LteRrc(LteRrcSubtype::UlDcch), + 45 => GsmtapType::LteRrc(LteRrcSubtype::BcchBchNb), + 46 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSchNb), + 47 => GsmtapType::LteRrc(LteRrcSubtype::PcchNb), + 48 => GsmtapType::LteRrc(LteRrcSubtype::DlCcchNb), + 49 => GsmtapType::LteRrc(LteRrcSubtype::DlDcchNb), + 50 => GsmtapType::LteRrc(LteRrcSubtype::UlCcchNb), + 52 => GsmtapType::LteRrc(LteRrcSubtype::UlDcchNb), + pdu => return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(ext_header_version, pdu)), + } + 0x14 | 0x18 | 0x19 => match packet.get_pdu_num() { + 1 => GsmtapType::LteRrc(LteRrcSubtype::BcchBch), + 2 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSch), + 4 => GsmtapType::LteRrc(LteRrcSubtype::MCCH), + 5 => GsmtapType::LteRrc(LteRrcSubtype::PCCH), + 6 => GsmtapType::LteRrc(LteRrcSubtype::DlCcch), + 7 => GsmtapType::LteRrc(LteRrcSubtype::DlDcch), + 8 => GsmtapType::LteRrc(LteRrcSubtype::UlCcch), + 9 => GsmtapType::LteRrc(LteRrcSubtype::UlDcch), + 54 => GsmtapType::LteRrc(LteRrcSubtype::BcchBchNb), + 55 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSchNb), + 56 => GsmtapType::LteRrc(LteRrcSubtype::PcchNb), + 57 => GsmtapType::LteRrc(LteRrcSubtype::DlCcchNb), + 58 => GsmtapType::LteRrc(LteRrcSubtype::DlDcchNb), + 59 => GsmtapType::LteRrc(LteRrcSubtype::UlCcchNb), + 61 => GsmtapType::LteRrc(LteRrcSubtype::UlDcchNb), + pdu => return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(ext_header_version, pdu)), + }, + _ => return Err(GsmtapParserError::InvalidLteRrcOtaExtHeaderVersion(ext_header_version)), + }; + let mut header = GsmtapHeader::new(gsmtap_type); + // Wireshark GSMTAP only accepts 14 bits of ARFCN + header.arfcn = packet.get_earfcn().try_into().unwrap_or(0); + header.frame_number = packet.get_sfn(); + header.subslot = packet.get_subfn(); + Ok(Some(GsmtapMessage { + header, + payload: packet.take_payload(), + })) + }, + _ => { + error!("gsmtap_sink: ignoring unhandled log type: {:?}", value); + Ok(None) + }, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index e288db4..3a9f757 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,3 +4,6 @@ pub mod diag_device; pub mod diag_reader; pub mod debug_file; pub mod log_codes; +pub mod gsmtap; +pub mod gsmtap_parser; +pub mod pcap; diff --git a/src/pcap.rs b/src/pcap.rs new file mode 100644 index 0000000..66cf7ba --- /dev/null +++ b/src/pcap.rs @@ -0,0 +1,120 @@ +use crate::gsmtap::GsmtapMessage; +use crate::diag::Timestamp; + +use std::fs::File; +use std::borrow::Cow; +use std::path::Path; +use chrono::prelude::*; +use deku::prelude::*; +use pcap_file::pcapng::blocks::enhanced_packet::EnhancedPacketBlock; +use pcap_file::pcapng::blocks::interface_description::InterfaceDescriptionBlock; +use pcap_file::pcapng::PcapNgWriter; +use pcap_file::PcapError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum PcapFileError { + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + #[error("Pcap error: {0}")] + Pcap(#[from] PcapError), + #[error("Deku error: {0}")] + Deku(#[from] DekuError), +} + +pub struct PcapFile { + writer: PcapNgWriter, + ip_id: u16, +} + +const IP_HEADER_LEN: u16 = 20; +#[derive(DekuWrite)] +#[deku(endian = "big")] +struct IpHeader { + version_and_ihl: u8, + dscp: u8, + total_len: u16, + identification: u16, + flags_and_frag_offset: u8, + idk: u8, + ttl: u8, + protocol: u8, + checksum: u16, + src_addr: u32, + dst_addr: u32, +} + +const UDP_HEADER_LEN: u16 = 8; +const GSMTAP_PORT: u16 = 4729; +#[derive(DekuWrite)] +#[deku(endian = "big")] +struct UdpHeader { + src_port: u16, + dst_port: u16, + length: u16, + checksum: u16, +} + +impl PcapFile { + pub fn new

(path: P) -> Result where P: AsRef { + let file = std::fs::File::options() + .create(true) + .write(true) + .open(path)?; + let writer = PcapNgWriter::new(file)?; + Ok(PcapFile { writer, ip_id: 0 }) + } + + pub fn write_iface_header(&mut self) -> Result<(), PcapFileError> { + let interface = InterfaceDescriptionBlock { + linktype: pcap_file::DataLink::IPV4, + snaplen: 0xffff, + options: vec![], + }; + self.writer.write_pcapng_block(interface)?; + Ok(()) + } + + pub fn write_gsmtap_message(&mut self, msg: GsmtapMessage, timestamp: Timestamp) -> Result<(), PcapFileError> { + let time_since_epoch = timestamp.to_datetime().signed_duration_since(DateTime::UNIX_EPOCH); + let secs_since_epoch = time_since_epoch.num_seconds() as u64; + let nsecs_since_epoch = time_since_epoch.num_nanoseconds().unwrap_or(0) as u32; + // FIXME: although the duration value is correct here, when it shows up in + // the pcap it's WAY off, like in the year 55920 + let duration = std::time::Duration::new(secs_since_epoch, nsecs_since_epoch); + let msg_bytes = msg.to_bytes()?; + let ip_header = IpHeader { + version_and_ihl: 0x45, + dscp: 0, + total_len: msg_bytes.len() as u16 + IP_HEADER_LEN + UDP_HEADER_LEN, + identification: self.ip_id, + flags_and_frag_offset: 0x40, + idk: 0, + ttl: 64, + protocol: 0x11, // UDP + checksum: 0xffff, + src_addr: 0x7f000001, + dst_addr: 0x7f000001, // TODO increment by radio_id + }; + let udp_header = UdpHeader { + src_port: 13337, + dst_port: GSMTAP_PORT, + length: msg_bytes.len() as u16 + UDP_HEADER_LEN, + checksum: 0xffff, + }; + let mut data: Vec = Vec::new(); + data.extend(&ip_header.to_bytes()?); + data.extend(&udp_header.to_bytes()?); + data.extend(&msg_bytes); + let packet = EnhancedPacketBlock { + interface_id: 0, + timestamp: duration, + original_len: data.len() as u32, + data: Cow::Owned(data), + options: vec![], + }; + self.writer.write_pcapng_block(packet)?; + self.ip_id = self.ip_id.wrapping_add(1); + Ok(()) + } +}