mirror of
https://github.com/rzuasti/oott.git
synced 2026-07-08 19:21:54 +02:00
Moved backend code into its own subfolder
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
pub mod devices;
|
||||
|
||||
use include_dir::{Dir, include_dir};
|
||||
use log::{debug, error};
|
||||
use rusqlite::Connection;
|
||||
use rusqlite_migration::Migrations;
|
||||
use std::result::Result;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use crate::settings;
|
||||
|
||||
static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/database_migrations");
|
||||
|
||||
// Define migrations. These are applied atomically.
|
||||
static MIGRATIONS: LazyLock<Migrations<'static>> =
|
||||
LazyLock::new(|| Migrations::from_directory(&MIGRATIONS_DIR).unwrap());
|
||||
|
||||
pub fn init_db() -> Result<Connection, String> {
|
||||
if settings::CONFIG.database.path.is_empty() {
|
||||
error!("Database path not set. Make sure to define database.path in your config file.");
|
||||
Err(format!(
|
||||
"Database path not set. Make sure to define database.path in your config file."
|
||||
))
|
||||
} else {
|
||||
debug!("Opening database at {}.", settings::CONFIG.database.path);
|
||||
|
||||
let database_path = settings::CONFIG.database.path.clone();
|
||||
|
||||
let mut conn = match Connection::open(database_path) {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
error!("Error opening database (oott.db): {error}");
|
||||
return Err(format!("Error opening database (oott.db): {error}"));
|
||||
}
|
||||
};
|
||||
|
||||
debug!("Database open, executing migrations if needed.");
|
||||
// Update the database schema, atomically
|
||||
match MIGRATIONS.to_latest(&mut conn) {
|
||||
Ok(_) => {
|
||||
debug!("Database up to date.");
|
||||
Ok(conn)
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Error updating database: {error}");
|
||||
Err(format!("Error updating database: {error}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
use log::{debug, error};
|
||||
use rusqlite::{Connection, params};
|
||||
use std::sync::MutexGuard;
|
||||
|
||||
use crate::device_finders::Device;
|
||||
|
||||
// Read device from its MAC address
|
||||
pub fn read(conn: MutexGuard<Connection>, mac_address: String) -> Option<Device> {
|
||||
let result: Result<Device, rusqlite::Error> = conn.query_one(
|
||||
"SELECT mac_address, ipv4_address, vendor, last_seen FROM devices WHERE mac_address=?1",
|
||||
params![mac_address],
|
||||
|row| {
|
||||
Ok(Device {
|
||||
mac_address: row.get(0)?,
|
||||
ipv4_address: row.get(1)?,
|
||||
vendor: row.get(2)?,
|
||||
last_seen: row.get(3)?,
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(value) => Some(value),
|
||||
Err(error) => {
|
||||
match error {
|
||||
rusqlite::Error::QueryReturnedNoRows => {
|
||||
debug!("No device found for MAC address {mac_address}.")
|
||||
}
|
||||
_ => error!("Error reading device from database: {error}"),
|
||||
};
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(conn: MutexGuard<Connection>, device: Device) -> Result<(), String> {
|
||||
match conn.execute(
|
||||
"INSERT INTO devices (mac_address, ipv4_address, vendor, last_seen) VALUES (?1, ?2, ?3, ?4)",
|
||||
params![device.mac_address, device.ipv4_address, device.vendor, device.last_seen]) {
|
||||
Ok(_) => {
|
||||
debug!("Device inserted into database: {}", device);
|
||||
Ok(())
|
||||
},
|
||||
Err(error) => {
|
||||
error!("Error inserting device ({device}) into database: {error}");
|
||||
Err(format!("Error inserting device ({device}) into database: {error}").to_string())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(conn: MutexGuard<Connection>, device: Device) -> Result<(), String> {
|
||||
match conn.execute(
|
||||
"UPDATE devices SET ipv4_address=?1, vendor=?2, last_seen=?3 WHERE mac_address=?4",
|
||||
params![
|
||||
device.ipv4_address,
|
||||
device.vendor,
|
||||
device.last_seen,
|
||||
device.mac_address
|
||||
],
|
||||
) {
|
||||
Ok(_) => {
|
||||
debug!("Device updated in database: {}", device);
|
||||
Ok(())
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Error updating device ({device}) in database: {error}");
|
||||
Err(format!("Error updating device ({device}) in database: {error}").to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
pub mod arp;
|
||||
|
||||
use chrono::NaiveDateTime;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Device {
|
||||
pub mac_address: String,
|
||||
pub ipv4_address: String,
|
||||
pub vendor: String,
|
||||
pub last_seen: NaiveDateTime,
|
||||
}
|
||||
|
||||
impl fmt::Display for Device {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"mac={}, ip={}, vendor={}, last_seen={}",
|
||||
self.mac_address, self.ipv4_address, self.vendor, self.last_seen
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
mod packet_send_receive;
|
||||
|
||||
use crate::{device_finders::Device, settings::CONFIG};
|
||||
use duration_string::DurationString;
|
||||
use log::{debug, error, info, warn};
|
||||
use packet_send_receive::{listen_for_packets, send_packet};
|
||||
use pnet::{
|
||||
datalink::{self, Channel, NetworkInterface},
|
||||
ipnetwork::IpNetwork,
|
||||
};
|
||||
use tokio::time::{Duration, timeout};
|
||||
|
||||
pub async fn find(interface: String) -> Result<Vec<Device>, String> {
|
||||
debug!("Looking up devices via ARP using interface {}", interface);
|
||||
|
||||
// Get the network device to use
|
||||
let network_interface: NetworkInterface = match datalink::interfaces()
|
||||
.iter()
|
||||
.filter(|el| el.is_up())
|
||||
.filter(|el| el.name == interface)
|
||||
.next()
|
||||
{
|
||||
Some(value) => value.clone(),
|
||||
None => {
|
||||
error!("Interface ({interface}) not found or not active.");
|
||||
return Err(format!("Interface ({interface}) not found or not active.").to_string());
|
||||
}
|
||||
};
|
||||
|
||||
// Get the IPV4 network to use
|
||||
let ipv4_net = match network_interface
|
||||
.ips
|
||||
.iter()
|
||||
.filter_map(|el| match el {
|
||||
IpNetwork::V4(v4) => Some(*v4),
|
||||
_ => None,
|
||||
})
|
||||
.next()
|
||||
{
|
||||
Some(value) => value,
|
||||
None => {
|
||||
error!("No IP address found for selected interface ({interface}).");
|
||||
return Err(
|
||||
format!("No IP address found for selected interface ({interface}).").to_string(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Get the local MAC address
|
||||
let mac = match network_interface.mac {
|
||||
Some(mac) => mac,
|
||||
None => {
|
||||
error!("Could not get MAC address for selected interface ({interface}).");
|
||||
return Err(
|
||||
format!("Could not get MAC address for selected interface ({interface}).")
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
info!("Local MAC: {}", mac);
|
||||
info!("Local IP: {}", ipv4_net);
|
||||
|
||||
// Data channel
|
||||
debug!("Creating data channel");
|
||||
let tunnel: Channel = match datalink::channel(&network_interface, datalink::Config::default()) {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
error!("Could not create data channel: {error}");
|
||||
return Err(format!("Could not create data channel: {error}").to_string());
|
||||
}
|
||||
};
|
||||
|
||||
let (sender, receiver) = match tunnel {
|
||||
Channel::Ethernet(tx, rx) => (tx, rx),
|
||||
_ => {
|
||||
error!("Unsupported data channel type");
|
||||
return Err("Unsupported data channel type".to_string());
|
||||
}
|
||||
};
|
||||
|
||||
let send_interface = network_interface.clone();
|
||||
|
||||
// Get timeouts
|
||||
let sender_timeout: Duration = CONFIG.timings.arp_sender_timeout.into();
|
||||
info!(
|
||||
"Sender timeout set to {}",
|
||||
CONFIG.timings.arp_sender_timeout
|
||||
);
|
||||
let scan_duration: Duration = CONFIG.timings.arp_scan_duration.into();
|
||||
let receiver_timeout: Duration = scan_duration * 2;
|
||||
info!("Scan duration set to {}", CONFIG.timings.arp_scan_duration);
|
||||
info!(
|
||||
"Receiver timeout set to {}",
|
||||
String::from(DurationString::from(receiver_timeout))
|
||||
);
|
||||
|
||||
let result_send = timeout(
|
||||
sender_timeout,
|
||||
send_packet(sender, send_interface, ipv4_net, mac),
|
||||
)
|
||||
.await;
|
||||
|
||||
match result_send {
|
||||
Ok(_) => info!("ARP sender done."),
|
||||
Err(_) => warn!("ARP sender timed out. Consider increasing its duration."),
|
||||
};
|
||||
|
||||
let result_receive = timeout(
|
||||
receiver_timeout,
|
||||
listen_for_packets(receiver, ipv4_net, scan_duration),
|
||||
)
|
||||
.await;
|
||||
|
||||
match result_receive {
|
||||
Ok(_) => info!("ARP receiver done."),
|
||||
Err(_) => info!("ARP receiver timed out."),
|
||||
};
|
||||
|
||||
Ok(result_receive.unwrap_or(Vec::new()))
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
use crate::device_finders::Device;
|
||||
use crate::mac_vendor_finder;
|
||||
use chrono::Local;
|
||||
use duration_string::DurationString;
|
||||
use log::{debug, info, trace};
|
||||
use pnet::datalink::{DataLinkReceiver, DataLinkSender, NetworkInterface};
|
||||
use pnet::ipnetwork::Ipv4Network;
|
||||
use pnet::packet::arp::{ArpHardwareTypes, ArpOperations, ArpPacket, MutableArpPacket};
|
||||
use pnet::packet::ethernet::{EtherTypes, EthernetPacket, MutableEthernetPacket};
|
||||
use pnet::packet::{MutablePacket, Packet};
|
||||
use pnet::util::MacAddr;
|
||||
use tokio::time::{Duration, Instant, sleep};
|
||||
|
||||
pub async fn send_packet(
|
||||
mut tx: Box<dyn DataLinkSender>,
|
||||
interface: NetworkInterface,
|
||||
sender_ip: Ipv4Network,
|
||||
sender_macaddr: MacAddr,
|
||||
) {
|
||||
info!("Starting ARP sender");
|
||||
let sender_start = Instant::now();
|
||||
|
||||
let mut count = 0;
|
||||
for target_ip in sender_ip.iter() {
|
||||
if target_ip == sender_ip.ip() {
|
||||
continue;
|
||||
}
|
||||
trace!("Sending ARP packet to {}", target_ip);
|
||||
for _ in 0..1 {
|
||||
//arp packet
|
||||
let mut arp_buf = [0u8; 28];
|
||||
let mut arp_packet = MutableArpPacket::new(&mut arp_buf).unwrap();
|
||||
|
||||
arp_packet.set_hardware_type(ArpHardwareTypes::Ethernet);
|
||||
arp_packet.set_protocol_type(EtherTypes::Ipv4);
|
||||
arp_packet.set_hw_addr_len(6);
|
||||
arp_packet.set_operation(ArpOperations::Request);
|
||||
arp_packet.set_proto_addr_len(4);
|
||||
arp_packet.set_sender_hw_addr(sender_macaddr);
|
||||
arp_packet.set_sender_proto_addr(sender_ip.ip());
|
||||
arp_packet.set_target_hw_addr(MacAddr::zero());
|
||||
arp_packet.set_target_proto_addr(target_ip);
|
||||
|
||||
//ethernet packet
|
||||
let mut ethernet_buf = [0u8; 42];
|
||||
let mut ethernet_packet = MutableEthernetPacket::new(&mut ethernet_buf).unwrap();
|
||||
|
||||
ethernet_packet.set_destination(MacAddr::broadcast());
|
||||
ethernet_packet.set_source(sender_macaddr);
|
||||
ethernet_packet.set_ethertype(EtherTypes::Arp);
|
||||
ethernet_packet.set_payload(arp_packet.packet_mut());
|
||||
|
||||
tx.send_to(
|
||||
ðernet_packet.to_immutable().packet(),
|
||||
Some(interface.clone()),
|
||||
);
|
||||
}
|
||||
count += 1;
|
||||
// Sleep 1 millisecond every 255 packets
|
||||
if (count % 255) == 0 {
|
||||
debug!("Sent {count} ARP packets so far.");
|
||||
sleep(Duration::from_millis(1)).await;
|
||||
}
|
||||
}
|
||||
info!(
|
||||
"ARP sender took {} secs",
|
||||
(Instant::now() - sender_start).as_secs()
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn listen_for_packets(
|
||||
mut rx: Box<dyn DataLinkReceiver>,
|
||||
ipv4_net: Ipv4Network,
|
||||
run_for: Duration,
|
||||
) -> Vec<Device> {
|
||||
info!(
|
||||
"Starting ARP receiver for {}",
|
||||
String::from(DurationString::from(run_for))
|
||||
);
|
||||
let start_time = Instant::now();
|
||||
|
||||
let mut devices = Vec::new();
|
||||
|
||||
let mut count = 0;
|
||||
// Run while still under the time window
|
||||
while start_time.elapsed() <= run_for {
|
||||
let arp_buffer = match rx.next() {
|
||||
Ok(buffer) => buffer,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let ethernet_packet = EthernetPacket::new(arp_buffer).unwrap();
|
||||
|
||||
if ethernet_packet.get_ethertype() == EtherTypes::Arp {
|
||||
debug!("ARP packet received");
|
||||
let arp_packet = ArpPacket::new(ethernet_packet.payload()).unwrap();
|
||||
if arp_packet.get_operation() == ArpOperations::Reply {
|
||||
debug!("It is an ARP reply packet");
|
||||
if arp_packet.get_target_proto_addr() == ipv4_net.ip() {
|
||||
let packet_mac_address = arp_packet.get_sender_hw_addr().to_string();
|
||||
let packet_ip_address = arp_packet.get_sender_proto_addr().to_string();
|
||||
let packet_vendor = mac_vendor_finder::find(
|
||||
packet_mac_address.get(0..8).unwrap_or("").to_string(),
|
||||
);
|
||||
|
||||
debug!(
|
||||
"Found online device - IP addr={} - MAC addr={} - vendor={}",
|
||||
packet_ip_address, packet_mac_address, packet_vendor
|
||||
);
|
||||
devices.push(Device {
|
||||
mac_address: packet_mac_address,
|
||||
ipv4_address: packet_ip_address,
|
||||
vendor: packet_vendor,
|
||||
last_seen: Local::now().naive_local(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
count += 1;
|
||||
if (count % 10) == 0 {
|
||||
// Sleep 1 millisecond every 10 packets
|
||||
sleep(Duration::from_millis(1)).await;
|
||||
}
|
||||
}
|
||||
info!(
|
||||
"ARP receiver ran for {}",
|
||||
String::from(DurationString::from(Duration::from_secs(
|
||||
start_time.elapsed().as_secs()
|
||||
)))
|
||||
);
|
||||
|
||||
devices
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
mod pushover;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{device_finders::Device, settings::CONFIG};
|
||||
use chrono::Local;
|
||||
use duration_string::DurationString;
|
||||
use log::{debug, info, warn};
|
||||
|
||||
// Private helper function to deliver messages
|
||||
fn send_message(body: String) -> Result<(), String> {
|
||||
debug!("About to send notification ({body}).");
|
||||
|
||||
match CONFIG.notifications.method.as_str() {
|
||||
"pushover" => {
|
||||
pushover::send_message(body)?;
|
||||
}
|
||||
other => {
|
||||
warn!("Notification method set to '{other}'. Set logs to 'info' to see notifications.");
|
||||
info!("Notification: {body}");
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn trigger_new_device(device: Device) -> Result<(), String> {
|
||||
send_message(format!("New device found in your network: {device}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn trigger_existing_device(existing_device: Device, new_device: Device) -> Result<(), String> {
|
||||
// Notify if the device comes back online after not being seen for the configured period
|
||||
let elapsed_since_last_seen: Duration = (Local::now().naive_local()
|
||||
- existing_device.last_seen)
|
||||
.to_std()
|
||||
.unwrap_or(Duration::from_secs(0));
|
||||
|
||||
if elapsed_since_last_seen >= Duration::from(CONFIG.notifications.notify_when_not_seen_for) {
|
||||
send_message(format!(
|
||||
"Device MAC {} - IP {} - Vendor {} came back online after {}.",
|
||||
new_device.mac_address,
|
||||
new_device.ipv4_address,
|
||||
new_device.vendor,
|
||||
String::from(DurationString::from(Duration::from_secs(
|
||||
elapsed_since_last_seen.as_secs()
|
||||
)))
|
||||
))?;
|
||||
}
|
||||
|
||||
// Notify if the devices vendor and/or IP changed
|
||||
if (existing_device.ipv4_address != new_device.ipv4_address)
|
||||
&& (existing_device.vendor != new_device.vendor)
|
||||
{
|
||||
send_message(format!(
|
||||
"Device with MAC {} changed IP address from {} to {} and vendor from {} to {}.",
|
||||
existing_device.mac_address,
|
||||
existing_device.ipv4_address,
|
||||
new_device.ipv4_address,
|
||||
existing_device.vendor,
|
||||
new_device.vendor
|
||||
))?;
|
||||
} else if existing_device.ipv4_address != new_device.ipv4_address {
|
||||
send_message(format!(
|
||||
"Device with MAC {} ({}) changed IP address from {} to {}.",
|
||||
existing_device.mac_address,
|
||||
new_device.vendor,
|
||||
existing_device.ipv4_address,
|
||||
new_device.ipv4_address
|
||||
))?;
|
||||
} else if existing_device.vendor != new_device.vendor {
|
||||
send_message(format!(
|
||||
"Device with MAC {} ({}) changed vendor from {} to {}.",
|
||||
existing_device.mac_address,
|
||||
existing_device.ipv4_address,
|
||||
existing_device.vendor,
|
||||
new_device.vendor
|
||||
))?;
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use log::{debug, error};
|
||||
use pushover::API;
|
||||
use pushover::requests::message::SendMessage;
|
||||
|
||||
use crate::settings::CONFIG;
|
||||
|
||||
pub fn send_message(body: String) -> Result<(), String> {
|
||||
debug!("About to send message via pushover ({body})");
|
||||
let api = API::new();
|
||||
|
||||
let msg = SendMessage::new(
|
||||
CONFIG.notifications.pushover.token.as_str(),
|
||||
CONFIG.notifications.pushover.user_key.as_str(),
|
||||
body,
|
||||
);
|
||||
|
||||
match api.send(&msg) {
|
||||
Ok(_) => {
|
||||
debug!("Message sent successfully");
|
||||
Ok(())
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Error sending message via pushover: {error}");
|
||||
Err(format!("Error sending message via pushover: {error}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
use lazy_static::lazy_static;
|
||||
use log::{debug, error, info};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct MacRecord {
|
||||
pub mac_prefix: String,
|
||||
pub vendor_name: String,
|
||||
// private: bool,
|
||||
// block_type: String,
|
||||
// last_update: String,
|
||||
}
|
||||
|
||||
// Initialize mac vendors database as a static lazy loaded unit
|
||||
lazy_static! {
|
||||
static ref MAC_VENDORS_DATABASE: HashMap<String, String> = {
|
||||
info!("Loading MAC vendor database into memory");
|
||||
|
||||
let mut database = HashMap::new();
|
||||
let data = include_str!("../data/mac-vendors-export.json");
|
||||
|
||||
let json: Vec<MacRecord> = match serde_json::from_str(&data) {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
error!(
|
||||
"Error parsing mac vendors database (data/mac-vendors-export.json): {error}"
|
||||
);
|
||||
panic!(
|
||||
"Error parsing mac vendors database (data/mac-vendors-export.json): {error}"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
debug!("Found {} records in the database", json.len());
|
||||
|
||||
for el in json {
|
||||
database.insert(el.mac_prefix.to_uppercase(), el.vendor_name);
|
||||
}
|
||||
|
||||
info!("MAC vendor database loaded");
|
||||
database
|
||||
};
|
||||
}
|
||||
|
||||
// Find a vendor based on the MAC prefix
|
||||
pub fn find(mac_prefix: String) -> String {
|
||||
MAC_VENDORS_DATABASE
|
||||
.get(&mac_prefix.to_uppercase())
|
||||
.unwrap_or(&"".to_string())
|
||||
.to_string()
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
use crate::settings::CONFIG;
|
||||
use log::{LevelFilter, debug, info};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::time::{Duration, sleep};
|
||||
|
||||
mod db;
|
||||
mod device_finders;
|
||||
mod events;
|
||||
mod mac_vendor_finder;
|
||||
mod settings;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), String> {
|
||||
// Initialize logging
|
||||
let log_level = match CONFIG.log.level.as_str() {
|
||||
"off" => LevelFilter::Off,
|
||||
"error" => LevelFilter::Error,
|
||||
"warn" => LevelFilter::Warn,
|
||||
"info" => LevelFilter::Info,
|
||||
"debug" => LevelFilter::Debug,
|
||||
"trace" => LevelFilter::Trace,
|
||||
_ => LevelFilter::Error,
|
||||
};
|
||||
|
||||
env_logger::Builder::new()
|
||||
.filter(None, log_level)
|
||||
.write_style(env_logger::WriteStyle::Always)
|
||||
.init();
|
||||
|
||||
// Now onto the important stuff
|
||||
info!("Starting up oott");
|
||||
|
||||
// Get database connection - thread protected
|
||||
let db_conn = Arc::new(Mutex::new(db::init_db().unwrap()));
|
||||
|
||||
loop {
|
||||
// Find online devices via ARP
|
||||
let devices = device_finders::arp::find(CONFIG.networking.interface.to_string()).await?;
|
||||
|
||||
info!("Done with ARP probes");
|
||||
info!("Found {} online devices", devices.iter().count());
|
||||
|
||||
// Process found devices
|
||||
for device in devices.iter() {
|
||||
debug!("Online device found {}", device);
|
||||
|
||||
// Using just one connection for now, need to update if moved DB portion to multi-thread
|
||||
// need to change to a clone if we need multiple threads
|
||||
let db_conn_clone = Arc::clone(&db_conn);
|
||||
|
||||
// Read device from database
|
||||
let recorded_device_result =
|
||||
db::devices::read(db_conn_clone.lock().unwrap(), device.mac_address.clone());
|
||||
|
||||
match recorded_device_result {
|
||||
Some(recorded_device) => {
|
||||
// If it exists update its last seen date
|
||||
debug!(
|
||||
"Device found in database {}. Updating to {}.",
|
||||
recorded_device, device
|
||||
);
|
||||
db::devices::update(db_conn_clone.lock().unwrap(), device.clone())?;
|
||||
events::trigger_existing_device(recorded_device, device.clone()).ok(); // Ignoring errors here, do not stop loop if notification delivery fails
|
||||
}
|
||||
None => {
|
||||
// If it doesn't exist insert it
|
||||
debug!(
|
||||
"Device with MAC address {} not found in database. Inserting it.",
|
||||
device.mac_address
|
||||
);
|
||||
|
||||
db::devices::insert(db_conn_clone.lock().unwrap(), device.clone())?;
|
||||
events::trigger_new_device(device.clone()).ok(); // Ignoring errors here, do not stop loop if notification delivery fails
|
||||
}
|
||||
};
|
||||
}
|
||||
info!(
|
||||
"Scan finished. Sleeping for {} seconds",
|
||||
CONFIG.timings.wait_between_scans
|
||||
);
|
||||
sleep(Duration::from(CONFIG.timings.wait_between_scans)).await;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
use clap::Parser;
|
||||
use config::{Config, ConfigError, File};
|
||||
use duration_string::DurationString;
|
||||
use lazy_static::lazy_static;
|
||||
use log::{error, info};
|
||||
use serde::Deserialize;
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// Configuration structure
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Database {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Networking {
|
||||
pub interface: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Log {
|
||||
pub level: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Timings {
|
||||
pub wait_between_scans: DurationString,
|
||||
pub arp_sender_timeout: DurationString,
|
||||
pub arp_scan_duration: DurationString,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Pushover {
|
||||
pub token: String,
|
||||
pub user_key: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Notifications {
|
||||
pub method: String,
|
||||
pub pushover: Pushover,
|
||||
pub notify_when_not_seen_for: DurationString,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Settings {
|
||||
pub database: Database,
|
||||
pub networking: Networking,
|
||||
pub log: Log,
|
||||
pub timings: Timings,
|
||||
pub notifications: Notifications,
|
||||
}
|
||||
// End configuration structure
|
||||
// -----------------------------------------------------------
|
||||
|
||||
// Command line parameters relevant to configuration
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Config file path
|
||||
#[arg(short, long)]
|
||||
config: Option<String>,
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG_FILE_PATH: &str = "./oott.toml";
|
||||
|
||||
impl Settings {
|
||||
pub fn new() -> Result<Self, ConfigError> {
|
||||
let args = Args::parse();
|
||||
|
||||
let config_path = args.config.unwrap_or(DEFAULT_CONFIG_FILE_PATH.to_string());
|
||||
|
||||
info!("Reading configuration from {}", config_path);
|
||||
|
||||
let local_settings = Config::builder()
|
||||
.add_source(File::with_name(config_path.as_str()))
|
||||
.build()?;
|
||||
|
||||
local_settings.try_deserialize()
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CONFIG: Settings = match Settings::new() {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
error!("Error reading configuration file: {error}");
|
||||
panic!("Error reading configuration file: {error}");
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user