diff --git a/bin/src/daemon.rs b/bin/src/daemon.rs index bec950b..d87ed20 100644 --- a/bin/src/daemon.rs +++ b/bin/src/daemon.rs @@ -23,7 +23,7 @@ use rayhunter::diag_device::DiagDevice; use axum::routing::{get, post}; use axum::Router; use stats::get_qmdl_manifest; -use tokio::sync::mpsc::{self, Sender}; +use tokio::sync::mpsc::{self, Sender, Receiver}; use tokio::sync::oneshot::error::TryRecvError; use tokio::task::JoinHandle; use tokio_util::task::TaskTracker; @@ -43,11 +43,13 @@ async fn run_server( config: &config::Config, qmdl_store_lock: Arc>, server_shutdown_rx: oneshot::Receiver<()>, + ui_update_tx: Sender, diag_device_sender: Sender ) -> JoinHandle<()> { let state = Arc::new(ServerState { qmdl_store_lock, diag_device_ctrl_sender: diag_device_sender, + ui_update_sender: ui_update_tx, readonly_mode: config.readonly_mode }); @@ -123,13 +125,15 @@ fn run_ctrl_c_thread( }) } -async fn update_ui(task_tracker: &TaskTracker, config: &config::Config, mut ui_shutdown_rx: oneshot::Receiver<()>){ +async fn update_ui(task_tracker: &TaskTracker, config: &config::Config, mut ui_shutdown_rx: oneshot::Receiver<()>, mut ui_update_rx: Receiver){ static IMAGE_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/static/images/"); let display_level = config.ui_level; if display_level == 0 { info!("Invisible mode, not spawning UI."); } + let mut display_color = framebuffer::Color565::Green; + task_tracker.spawn_blocking(move || { let mut fb: Framebuffer = Framebuffer::new(); // this feels wrong, is there a more rusty way to do this? @@ -149,6 +153,14 @@ async fn update_ui(task_tracker: &TaskTracker, config: &config::Config, mut ui_ Err(e) => panic!("error receiving shutdown message: {e}") } + match ui_update_rx.try_recv() { + Ok(color) => { + display_color = color; + }, + Err(tokio::sync::mpsc::error::TryRecvError::Empty) => {}, + Err(e) => panic!("error receiving framebuffer update message: {e}") + } + match display_level { 2 => { fb.draw_gif(img.unwrap()); @@ -164,7 +176,7 @@ async fn update_ui(task_tracker: &TaskTracker, config: &config::Config, mut ui_ fb.draw_line(framebuffer::Color565::Cyan, 25); }, 1 | _ => { - fb.draw_line(framebuffer::Color565::Green, 2); + fb.draw_line(display_color, 2); }, }; sleep(Duration::from_millis(100)); @@ -195,10 +207,11 @@ async fn main() -> Result<(), RayhunterError> { run_diag_read_thread(&task_tracker, dev, rx, qmdl_store_lock.clone()); } let (ui_shutdown_tx, ui_shutdown_rx) = oneshot::channel(); + let (ui_update_tx, ui_update_rx) = mpsc::channel::(1); let (server_shutdown_tx, server_shutdown_rx) = oneshot::channel::<()>(); run_ctrl_c_thread(&task_tracker, tx.clone(), server_shutdown_tx, ui_shutdown_tx, qmdl_store_lock.clone()); - run_server(&task_tracker, &config, qmdl_store_lock.clone(), server_shutdown_rx, tx).await; - update_ui(&task_tracker, &config, ui_shutdown_rx).await; + run_server(&task_tracker, &config, qmdl_store_lock.clone(), server_shutdown_rx, ui_update_tx, tx).await; + update_ui(&task_tracker, &config, ui_shutdown_rx, ui_update_rx).await; task_tracker.close(); task_tracker.wait().await; diff --git a/bin/src/diag.rs b/bin/src/diag.rs index b7bf850..eb7c8ce 100644 --- a/bin/src/diag.rs +++ b/bin/src/diag.rs @@ -20,6 +20,7 @@ use tokio_util::io::ReaderStream; use tokio_util::task::TaskTracker; use futures::{StreamExt, TryStreamExt}; +use crate::framebuffer; use crate::qmdl_store::RecordingStore; use crate::server::ServerState; @@ -33,6 +34,7 @@ struct AnalysisWriter { writer: BufWriter, harness: Harness, bytes_written: usize, + has_warning: bool, } // We write our analysis results to a file immediately to minimize the amount of @@ -47,6 +49,7 @@ impl AnalysisWriter { writer: BufWriter::new(file), harness: Harness::new_with_all_analyzers(), bytes_written: 0, + has_warning: false, }; let metadata = result.harness.get_metadata(); result.write(&metadata).await?; @@ -59,6 +62,7 @@ impl AnalysisWriter { let row = self.harness.analyze_qmdl_messages(container); if !row.is_empty() { self.write(&row).await?; + self.has_warning = ! &row.analysis.is_empty() } Ok(self.bytes_written) } @@ -149,6 +153,8 @@ pub fn run_diag_read_thread( let index = qmdl_store.current_entry.expect("DiagDevice had qmdl_writer, but QmdlStore didn't have current entry???"); qmdl_store.update_entry_analysis_size(index, analysis_file_len as usize).await .expect("failed to update analysis file size"); + qmdl_store.update_entry_has_warning(index, analysis_writer.has_warning).await + .expect("failed to update analysis file has warning"); } }, Err(err) => { @@ -172,6 +178,8 @@ pub async fn start_recording(State(state): State>) -> Result<(S let qmdl_writer = QmdlWriter::new(qmdl_file); state.diag_device_ctrl_sender.send(DiagDeviceCtrlMessage::StartRecording((qmdl_writer, analysis_file))).await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send stop recording message: {}", e)))?; + state.ui_update_sender.send(framebuffer::Color565::Green).await + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send ui update message: {}", e)))?; Ok((StatusCode::ACCEPTED, "ok".to_string())) } @@ -184,6 +192,8 @@ pub async fn stop_recording(State(state): State>) -> Result<(St .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't close current qmdl entry: {}", e)))?; state.diag_device_ctrl_sender.send(DiagDeviceCtrlMessage::StopRecording).await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send stop recording message: {}", e)))?; + state.ui_update_sender.send(framebuffer::Color565::White).await + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send ui update message: {}", e)))?; Ok((StatusCode::ACCEPTED, "ok".to_string())) } diff --git a/bin/src/framebuffer.rs b/bin/src/framebuffer.rs index 13fd614..48066a6 100644 --- a/bin/src/framebuffer.rs +++ b/bin/src/framebuffer.rs @@ -11,6 +11,7 @@ struct Dimensions { } #[allow(dead_code)] +#[derive(Copy, Clone)] pub enum Color565 { Red = 0b1111100000000000, Green = 0b0000011111100000, diff --git a/bin/src/qmdl_store.rs b/bin/src/qmdl_store.rs index accd7ce..61498b7 100644 --- a/bin/src/qmdl_store.rs +++ b/bin/src/qmdl_store.rs @@ -40,6 +40,7 @@ pub struct ManifestEntry { pub last_message_time: Option>, pub qmdl_size_bytes: usize, pub analysis_size_bytes: usize, + pub has_warning: bool, } impl ManifestEntry { @@ -51,6 +52,7 @@ impl ManifestEntry { last_message_time: None, qmdl_size_bytes: 0, analysis_size_bytes: 0, + has_warning: false, } } @@ -169,6 +171,11 @@ impl RecordingStore { self.manifest.entries[entry_index].last_message_time = Some(Local::now()); self.write_manifest().await } + + pub async fn update_entry_has_warning(&mut self, entry_index: usize, has_warning: bool) -> Result<(), RecordingStoreError> { + self.manifest.entries[entry_index].has_warning = has_warning; + self.write_manifest().await + } // Sets the given entry's analysis file size pub async fn update_entry_analysis_size(&mut self, entry_index: usize, size_bytes: usize) -> Result<(), RecordingStoreError> { diff --git a/bin/src/server.rs b/bin/src/server.rs index bef6311..6f91edd 100644 --- a/bin/src/server.rs +++ b/bin/src/server.rs @@ -11,12 +11,13 @@ use tokio::sync::RwLock; use tokio_util::io::ReaderStream; use include_dir::{include_dir, Dir}; -use crate::DiagDeviceCtrlMessage; +use crate::{framebuffer, DiagDeviceCtrlMessage}; use crate::qmdl_store::RecordingStore; pub struct ServerState { pub qmdl_store_lock: Arc>, pub diag_device_ctrl_sender: Sender, + pub ui_update_sender: Sender, pub readonly_mode: bool } diff --git a/bin/src/stats.rs b/bin/src/stats.rs index 28d5a81..96d165b 100644 --- a/bin/src/stats.rs +++ b/bin/src/stats.rs @@ -1,12 +1,12 @@ use std::sync::Arc; -use crate::qmdl_store::ManifestEntry; +use crate::{framebuffer, qmdl_store::ManifestEntry}; use crate::server::ServerState; use axum::Json; use axum::extract::State; use axum::http::StatusCode; -use log::error; +use log::{error, info}; use serde::Serialize; use tokio::process::Command; @@ -121,6 +121,11 @@ pub async fn get_qmdl_manifest(State(state): State>) -> Result< let qmdl_store = state.qmdl_store_lock.read().await; let mut entries = qmdl_store.manifest.entries.clone(); let current_entry = qmdl_store.current_entry.map(|index| entries.remove(index)); + if current_entry.clone().unwrap().has_warning { + info!("a heuristic triggered on this run!"); + state.ui_update_sender.send(framebuffer::Color565::Red).await + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send ui update message: {}", e)))?; + } Ok(Json(ManifestStats { entries, current_entry, diff --git a/bin/static/css/style.css b/bin/static/css/style.css index de89e27..4ef7e54 100644 --- a/bin/static/css/style.css +++ b/bin/static/css/style.css @@ -22,6 +22,11 @@ th[scope='row'] { } tr.current { + background-color: #53fe7b; + font-weight: bold; +} + +tr.warning { background-color: #fe537b; font-weight: bold; } diff --git a/bin/static/index.html b/bin/static/index.html index 878f79d..5ee0555 100644 --- a/bin/static/index.html +++ b/bin/static/index.html @@ -25,6 +25,7 @@ Date Started Date of Last Message Size (bytes) + Has Warning PCAP QMDL diff --git a/bin/static/js/main.js b/bin/static/js/main.js index fd78bee..9b46eeb 100644 --- a/bin/static/js/main.js +++ b/bin/static/js/main.js @@ -33,7 +33,7 @@ function createEntryRow(entry) { name.scope = 'row'; name.innerText = entry.name; row.appendChild(name); - for (const key of ['start_time', 'last_message_time', 'qmdl_size_bytes']) { + for (const key of ['start_time', 'last_message_time', 'qmdl_size_bytes', 'has_warning']) { const td = document.createElement('td'); td.innerText = entry[key]; row.appendChild(td); @@ -50,6 +50,9 @@ function createEntryRow(entry) { qmdl_link.innerText = 'qmdl'; qmdl_td.appendChild(qmdl_link); row.appendChild(qmdl_td); + if(entry["has_warning"]){ + row.classList.add('warning'); + } return row; } diff --git a/lib/src/analysis/analyzer.rs b/lib/src/analysis/analyzer.rs index fbd65ef..e3d69b0 100644 --- a/lib/src/analysis/analyzer.rs +++ b/lib/src/analysis/analyzer.rs @@ -4,7 +4,7 @@ use serde::Serialize; use crate::{diag::MessagesContainer, gsmtap_parser}; -use super::{imsi_provided::ImsiProvidedAnalyzer, information_element::InformationElement, lte_downgrade::LteSib6And7DowngradeAnalyzer, null_cipher::NullCipherAnalyzer}; +use super::{imsi_provided::ImsiProvidedAnalyzer, information_element::InformationElement, lte_downgrade::LteSib6And7DowngradeAnalyzer, null_cipher::NullCipherAnalyzer, example_analyzer::ExampleAnalyzer}; /// Qualitative measure of how severe a Warning event type is. /// The levels should break down like this: @@ -102,6 +102,7 @@ impl Harness { harness.add_analyzer(Box::new(LteSib6And7DowngradeAnalyzer{})); harness.add_analyzer(Box::new(ImsiProvidedAnalyzer{})); harness.add_analyzer(Box::new(NullCipherAnalyzer{})); + harness.add_analyzer(Box::new(ExampleAnalyzer{count:0})); harness } diff --git a/lib/src/analysis/example_analyzer.rs b/lib/src/analysis/example_analyzer.rs new file mode 100644 index 0000000..24a41ea --- /dev/null +++ b/lib/src/analysis/example_analyzer.rs @@ -0,0 +1,45 @@ +use std::borrow::Cow; + +use telcom_parser::lte_rrc::{PCCH_MessageType, PCCH_MessageType_c1, PagingUE_Identity}; + +use super::analyzer::{Analyzer, Event, EventType, Severity}; +use super::information_element::{InformationElement, LteInformationElement}; + +pub struct ExampleAnalyzer{ + pub count: i32, +} + +impl Analyzer for ExampleAnalyzer{ + fn get_name(&self) -> Cow { + Cow::from("Example Analyzer") + } + + fn get_description(&self) -> Cow { + Cow::from("Always returns true, if you are seeing this you are either a developer or you are about to have problems.") + } + + fn analyze_information_element(&mut self, ie: &InformationElement) -> Option { + self.count += 1; + if self.count % 100 == 0 { + return Some(Event { + event_type: EventType::Informational , + message: "multiple of 100 events processed".to_string(), + }) + } + let InformationElement::LTE(LteInformationElement::PCCH(pcch_msg)) = ie else { + return None; + }; + let PCCH_MessageType::C1(PCCH_MessageType_c1::Paging(paging)) = &pcch_msg.message else { + return None; + }; + for record in &paging.paging_record_list.as_ref()?.0 { + if let PagingUE_Identity::S_TMSI(_) = record.ue_identity { + return Some(Event { + event_type: EventType::QualitativeWarning { severity: Severity::Low }, + message: "TMSI was provided to cell".to_string(), + }) + } + } + None + } +} diff --git a/lib/src/analysis/mod.rs b/lib/src/analysis/mod.rs index aa0b490..7a19435 100644 --- a/lib/src/analysis/mod.rs +++ b/lib/src/analysis/mod.rs @@ -3,3 +3,4 @@ pub mod information_element; pub mod lte_downgrade; pub mod imsi_provided; pub mod null_cipher; +pub mod example_analyzer;