mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-04-27 07:59:59 -07:00
148 lines
5.5 KiB
Rust
148 lines
5.5 KiB
Rust
use std::{collections::HashMap, future, path::PathBuf, pin::pin};
|
|
use log::{info, warn};
|
|
use rayhunter::{analysis::analyzer::{EventType, Harness}, diag::DataType, gsmtap_parser, pcap::GsmtapPcapWriter, qmdl::QmdlReader};
|
|
use tokio::fs::{metadata, read_dir, File};
|
|
use clap::Parser;
|
|
use futures::TryStreamExt;
|
|
|
|
mod dummy_analyzer;
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[command(version, about)]
|
|
struct Args {
|
|
#[arg(short = 'p', long)]
|
|
qmdl_path: PathBuf,
|
|
|
|
#[arg(short, long)]
|
|
pcapify: bool,
|
|
|
|
#[arg(long)]
|
|
show_skipped: bool,
|
|
|
|
#[arg(long)]
|
|
enable_dummy_analyzer: bool,
|
|
|
|
#[arg(short, long)]
|
|
quiet: bool,
|
|
}
|
|
|
|
async fn analyze_file(harness: &mut Harness, qmdl_path: &str, show_skipped: bool) {
|
|
let qmdl_file = &mut File::open(&qmdl_path).await.expect("failed to open file");
|
|
let file_size = qmdl_file.metadata().await.expect("failed to get QMDL file metadata").len();
|
|
let mut qmdl_reader = QmdlReader::new(qmdl_file, Some(file_size as usize));
|
|
let mut qmdl_stream = pin!(qmdl_reader.as_stream()
|
|
.try_filter(|container| future::ready(container.data_type == DataType::UserSpace)));
|
|
let mut skipped_reasons: HashMap<String, i32> = HashMap::new();
|
|
let mut total_messages = 0;
|
|
let mut warnings = 0;
|
|
let mut skipped = 0;
|
|
while let Some(container) = qmdl_stream.try_next().await.expect("failed getting QMDL container") {
|
|
let row = harness.analyze_qmdl_messages(container);
|
|
total_messages += 1;
|
|
for reason in row.skipped_message_reasons {
|
|
*skipped_reasons.entry(reason).or_insert(0) += 1;
|
|
skipped += 1;
|
|
}
|
|
for analysis in row.analysis {
|
|
for maybe_event in analysis.events {
|
|
let Some(event) = maybe_event else { continue };
|
|
match event.event_type {
|
|
EventType::Informational => {
|
|
info!(
|
|
"{}: INFO - {} {}",
|
|
qmdl_path,
|
|
analysis.timestamp,
|
|
event.message,
|
|
);
|
|
}
|
|
EventType::QualitativeWarning { severity } => {
|
|
warn!(
|
|
"{}: WARNING (Severity: {:?}) - {} {}",
|
|
qmdl_path,
|
|
severity,
|
|
analysis.timestamp,
|
|
event.message,
|
|
);
|
|
warnings += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if show_skipped && skipped > 0 {
|
|
info!("{}: messages skipped:", qmdl_path);
|
|
for (reason, count) in skipped_reasons.iter() {
|
|
info!(" - {}: \"{}\"", count, reason);
|
|
}
|
|
}
|
|
info!("{}: {} messages analyzed, {} warnings, {} messages skipped", qmdl_path, total_messages, warnings, skipped);
|
|
}
|
|
|
|
async fn pcapify(qmdl_path: &PathBuf) {
|
|
let qmdl_file = &mut File::open(&qmdl_path).await.expect("failed to open qmdl file");
|
|
let qmdl_file_size = qmdl_file.metadata().await.unwrap().len();
|
|
let mut qmdl_reader = QmdlReader::new(qmdl_file, Some(qmdl_file_size as usize));
|
|
let mut pcap_path = qmdl_path.clone();
|
|
pcap_path.set_extension("pcap");
|
|
let pcap_file = &mut File::create(&pcap_path).await.expect("failed to open pcap file");
|
|
let mut pcap_writer = GsmtapPcapWriter::new(pcap_file).await.unwrap();
|
|
pcap_writer.write_iface_header().await.unwrap();
|
|
while let Some(container) = qmdl_reader.get_next_messages_container().await.expect("failed to get container") {
|
|
for maybe_msg in container.into_messages() {
|
|
if let Ok(msg) = maybe_msg {
|
|
if let Ok(Some((timestamp, parsed))) = gsmtap_parser::parse(msg) {
|
|
pcap_writer.write_gsmtap_message(parsed, timestamp).await.expect("failed to write");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
info!("wrote pcap to {:?}", &pcap_path);
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let args = Args::parse();
|
|
let level = if args.quiet {
|
|
log::LevelFilter::Warn
|
|
} else {
|
|
log::LevelFilter::Trace
|
|
};
|
|
simple_logger::SimpleLogger::new()
|
|
.with_colors(true)
|
|
.without_timestamps()
|
|
.with_level(level)
|
|
.init().unwrap();
|
|
|
|
let mut harness = Harness::new_with_all_analyzers();
|
|
if args.enable_dummy_analyzer {
|
|
harness.add_analyzer(Box::new(dummy_analyzer::TestAnalyzer { count: 0 }));
|
|
}
|
|
info!("Analyzers:");
|
|
for analyzer in harness.get_metadata().analyzers {
|
|
info!(" - {}: {}", analyzer.name, analyzer.description);
|
|
}
|
|
|
|
let metadata = metadata(&args.qmdl_path).await.expect("failed to get metadata");
|
|
if metadata.is_dir() {
|
|
let mut dir = read_dir(&args.qmdl_path).await.expect("failed to read dir");
|
|
while let Some(entry) = dir.next_entry().await.expect("failed to get entry") {
|
|
let name = entry.file_name();
|
|
let name_str = name.to_str().unwrap();
|
|
if name_str.ends_with(".qmdl") {
|
|
let path = entry.path();
|
|
let path_str = path.to_str().unwrap();
|
|
analyze_file(&mut harness, path_str, args.show_skipped).await;
|
|
if args.pcapify {
|
|
pcapify(&path).await;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
let path = args.qmdl_path.to_str().unwrap();
|
|
analyze_file(&mut harness, path, args.show_skipped).await;
|
|
if args.pcapify {
|
|
pcapify(&args.qmdl_path).await;
|
|
}
|
|
}
|
|
}
|