Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 66 additions & 2 deletions daemon/src/battery/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
use std::path::Path;
use std::{path::Path, time::Duration};

use log::{error, info};
use rayhunter::Device;
use serde::Serialize;
use tokio::select;
use tokio_util::{sync::CancellationToken, task::TaskTracker};

use crate::error::RayhunterError;
use crate::{
error::RayhunterError,
notifications::{Notification, NotificationType},
};

pub mod orbic;
pub mod tmobile;
pub mod wingtech;

const LOW_BATTERY_LEVEL: u8 = 10;

#[derive(Clone, Copy, PartialEq, Debug, Serialize)]
pub struct BatteryState {
level: u8,
Expand Down Expand Up @@ -45,3 +53,59 @@ pub async fn get_battery_status(device: &Device) -> Result<BatteryState, Rayhunt
_ => return Err(RayhunterError::FunctionNotSupportedForDeviceError),
})
}

pub fn run_battery_notification_worker(
task_tracker: &TaskTracker,
device: Device,
notification_channel: tokio::sync::mpsc::Sender<Notification>,
shutdown_token: CancellationToken,
) {
task_tracker.spawn(async move {
// Don't send a notification initially if the device starts at a low battery level.
let mut triggered = match get_battery_status(&device).await {
Err(RayhunterError::FunctionNotSupportedForDeviceError) => {
info!("Battery level function not supported for device");
false
}
Err(e) => {
error!("Failed to get battery status: {e}");
true
}
Ok(status) => status.level < LOW_BATTERY_LEVEL,
};

loop {
select! {
_ = shutdown_token.cancelled() => break,
_ = tokio::time::sleep(Duration::from_secs(15)) => {}
}

let status = match get_battery_status(&device).await {
Err(e) => {
error!("Failed to get battery status: {e}");
continue;
}
Ok(status) => status,
};

// To avoid flapping, if the notification has already been triggered
// wait until the device has been plugged in and the battery level
// is high enough to re-enable notifications.
if triggered && status.is_plugged_in && status.level > LOW_BATTERY_LEVEL {
triggered = false;
continue;
}
if !triggered && !status.is_plugged_in && status.level <= LOW_BATTERY_LEVEL {
notification_channel
.send(Notification::new(
NotificationType::LowBattery,
"Rayhunter's battery is low".to_string(),
None,
))
.await
.expect("Failed to send to notification channel");
triggered = true;
}
}
});
}
3 changes: 3 additions & 0 deletions daemon/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use rayhunter::Device;
use rayhunter::analysis::analyzer::AnalyzerConfig;

use crate::error::RayhunterError;
use crate::notifications::NotificationType;

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(default)]
Expand All @@ -17,6 +18,7 @@ pub struct Config {
pub colorblind_mode: bool,
pub key_input_mode: u8,
pub ntfy_url: Option<String>,
pub enabled_notifications: Vec<NotificationType>,
pub analyzers: AnalyzerConfig,
}

Expand All @@ -32,6 +34,7 @@ impl Default for Config {
key_input_mode: 0,
analyzers: AnalyzerConfig::default(),
ntfy_url: None,
enabled_notifications: vec![NotificationType::Warning, NotificationType::LowBattery],
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions daemon/src/diag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use rayhunter::qmdl::QmdlWriter;

use crate::analysis::{AnalysisCtrlMessage, AnalysisWriter};
use crate::display;
use crate::notifications::Notification;
use crate::notifications::{Notification, NotificationType};
use crate::qmdl_store::{RecordingStore, RecordingStoreError};
use crate::server::ServerState;

Expand Down Expand Up @@ -207,7 +207,7 @@ impl DiagTask {
info!("a heuristic triggered on this run!");
self.notification_channel
.send(Notification::new(
"heuristic-warning".to_string(),
NotificationType::Warning,
format!("Rayhunter has detected a {:?} severity event", max_type),
Some(Duration::from_secs(60 * 5)),
))
Expand Down
16 changes: 15 additions & 1 deletion daemon/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod stats;
use std::net::SocketAddr;
use std::sync::Arc;

use crate::battery::run_battery_notification_worker;
use crate::config::{parse_args, parse_config};
use crate::diag::run_diag_read_thread;
use crate::error::RayhunterError;
Expand Down Expand Up @@ -260,7 +261,20 @@ async fn run_with_config(
qmdl_store_lock.clone(),
analysis_tx.clone(),
);
run_notification_worker(&task_tracker, notification_service);

run_battery_notification_worker(
&task_tracker,
config.device.clone(),
notification_service.new_handler(),
shutdown_token.clone(),
);

run_notification_worker(
&task_tracker,
notification_service,
config.enabled_notifications.clone(),
);

let state = Arc::new(ServerState {
config_path: args.config_path.clone(),
config,
Expand Down
24 changes: 20 additions & 4 deletions daemon/src/notifications.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,30 @@ use std::{
};

use log::error;
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc::{self, error::TryRecvError};
use tokio_util::task::TaskTracker;

#[derive(Hash, Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
pub enum NotificationType {
Warning,
LowBattery,
}

pub struct Notification {
message_type: String,
notification_type: NotificationType,
message: String,
debounce: Option<Duration>,
}

impl Notification {
pub fn new(message_type: String, message: String, debounce: Option<Duration>) -> Self {
pub fn new(
notification_type: NotificationType,
message: String,
debounce: Option<Duration>,
) -> Self {
Notification {
message_type,
notification_type,
message,
debounce,
}
Expand Down Expand Up @@ -52,6 +63,7 @@ impl NotificationService {
pub fn run_notification_worker(
task_tracker: &TaskTracker,
mut notification_service: NotificationService,
enabled_notifications: Vec<NotificationType>,
) {
task_tracker.spawn(async move {
if let Some(url) = notification_service.url
Expand All @@ -65,8 +77,12 @@ pub fn run_notification_worker(
loop {
match notification_service.rx.try_recv() {
Ok(notification) => {
if !enabled_notifications.contains(&notification.notification_type) {
continue;
}

let status = notification_statuses
.entry(notification.message_type)
.entry(notification.notification_type)
.or_insert_with(|| NotificationStatus {
message: "".to_string(),
needs_sending: true,
Expand Down
62 changes: 50 additions & 12 deletions daemon/web/src/lib/components/ConfigForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -111,18 +111,6 @@
</select>
</div>

<div>
<label for="ntfy_url" class="block text-sm font-medium text-gray-700 mb-1">
ntfy URL for Sending Notifications
</label>
<input
id="ntfy_url"
type="url"
bind:value={config.ntfy_url}
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-rayhunter-blue"
/>
</div>

<div class="space-y-3">
<div class="flex items-center">
<input
Expand All @@ -137,6 +125,56 @@
</div>
</div>

<div class="border-t pt-4 mt-6 space-y-3">
<h3 class="text-lg font-semibold text-gray-800 mb-4">Notification Settings</h3>
<div>
<label for="ntfy_url" class="block text-sm font-medium text-gray-700 mb-1">
ntfy URL for Sending Notifications (if unset you will not receive
notifications)
</label>
<input
id="ntfy_url"
type="url"
bind:value={config.ntfy_url}
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-rayhunter-blue"
/>
</div>

<div class="space-y-2">
<div class="block text-sm font-medium text-gray-700 mb-1">
Enabled Notification Types
</div>
<div class="flex items-center">
<input
type="checkbox"
id="enable_warning_notifications"
value="Warning"
bind:group={config.enabled_notifications}
/>
<label
for="enable_warning_notifications"
class="ml-2 block text-sm text-gray-700"
>
Warnings
</label>
</div>
<div class="flex items-center">
<input
type="checkbox"
id="enable_lowbattery_notifications"
value="LowBattery"
bind:group={config.enabled_notifications}
/>
<label
for="enable_lowbattery_notifications"
class="ml-2 block text-sm text-gray-700"
>
Low Battery
</label>
</div>
</div>
</div>

<div class="border-t pt-4 mt-6">
<h3 class="text-lg font-semibold text-gray-800 mb-4">
Analyzer Heuristic Settings
Expand Down
6 changes: 6 additions & 0 deletions daemon/web/src/lib/utils.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,17 @@ export interface AnalyzerConfig {
test_analyzer: boolean;
}

export enum enabled_notifications {
Warning = 'Warning',
LowBattery = 'LowBattery',
}

export interface Config {
ui_level: number;
colorblind_mode: boolean;
key_input_mode: number;
ntfy_url: string;
enabled_notifications: enabled_notifications[];
analyzers: AnalyzerConfig;
}

Expand Down
6 changes: 4 additions & 2 deletions dist/config.toml.in
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ ui_level = 1
key_input_mode = 0

# If set, attempts to send a notification to the url when a new warning is triggered
# ntfy_url =
ntfy_url = ""
# What notification types to enable. Does nothing if the above ntfy_url is not set.
enabled_notifications = ["Warning", "LowBattery"]

# Analyzer Configuration
# Enable/disable specific IMSI catcher detection heuristics
Expand All @@ -35,4 +37,4 @@ lte_sib6_and_7_downgrade = true
null_cipher = true
nas_null_cipher = true
incomplete_sib = true
test_analyzer = false
test_analyzer = false
5 changes: 4 additions & 1 deletion doc/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ Through web UI you can set:
- **Device Input Mode**, which defines behaviour of built-in power button of the device. *Device Input Mode* could be:
- *Disable button control*: built-in power button of the device is not used by Rayhunter;
- *Double-tap power button to start/stop recording*: double clicking on a built-in power button of the device stops and immediately restarts the recording. This could be useful if Rayhunter's heuristichs is triggered and you get the red line, and you want to "reset" the past warnings. Normally you can do that through web UI, but sometimes it is easier to double tap on power button.
- **ntfy URL for Sending Notifications**, which allows setting a [ntfy](https://ntfy.sh/) URL to which notifications of new detections will be sent. The topic should be unique to your device, e.g., `https://ntfy.sh/rayhunter_notifications_ba9di7ie` or `https://myserver.example.com/rayhunter_notifications_ba9di7ie`. The ntfy Android and iOS apps can then be used to receive notifications. More information can be found in the [ntfy docs](https://docs.ntfy.sh/).
- **Colorblind Mode** enables color blind mode (blue line is shown instead of green line, red line remains red). Please note that this does not cover all types of color blindness, but switching green to blue should be about enough to differentiate the color change for most types of color blindness.
- **ntfy URL**, which allows setting a [ntfy](https://ntfy.sh/) URL to which notifications of new detections will be sent. The topic should be unique to your device, e.g., `https://ntfy.sh/rayhunter_notifications_ba9di7ie` or `https://myserver.example.com/rayhunter_notifications_ba9di7ie`. The ntfy Android and iOS apps can then be used to receive notifications. More information can be found in the [ntfy docs](https://docs.ntfy.sh/).
- **Enabled Notification Types** allows enabling or disabling the following types of notifications:
- *Warnings*, which will alert when a heuristic is triggered. Alerts will be sent at most once every five minutes.
- *Low Battery*, which will alert when the device's battery is low. Notifications may not be supported for all devices—you can check if your device is supported by looking at whether the battery level indicator is functioning on the System Information section of the Rayhunter UI.
- With **Analyzer Heuristic Settings** you can switch on or off built-in [Rayhunter heuristics](heuristics.md). Some heuristics are experimental or can trigger a lot of false positive warnings in some networks (our tests have shown that some heuristics have different behaviour in US or European networks). In that case you can decide whether you would like to have the heuristics that trigger a lot of false positives on or off. Please note that we are constantly improving and adding new heuristics, so new release may reduce false positives in existing heuristics as well.

If you prefer editing `config.toml` file, you need to obtain a shell on your [Orbic](./orbic.md#obtaining-a-shell) or [TP-Link](./tplink-m7350.md#obtaining-a-shell) device and edit the file manually. You can view the [default configuration file on a GitHub](https://github.com/EFForg/rayhunter/blob/main/dist/config.toml.in).
Binary file modified doc/rayhunter_config.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading