mirror of
https://github.com/rzuasti/oott.git
synced 2026-07-08 19:21:54 +02:00
Error handling, overall code review and moved mac vendors database to a
static instance
This commit is contained in:
@@ -1,6 +0,0 @@
|
|||||||
- [ ] Proper error handling across the board
|
|
||||||
- [ ] Insert new devices in database or update existing ones last_seen
|
|
||||||
- [ ] Generate an event when a new device is inserted
|
|
||||||
- [ ] Run as an endless loop with a configurable pause
|
|
||||||
- [ ] Deploy as a service in a docker container
|
|
||||||
- [ ] Deploy as a service via a nix flake
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use include_dir::{Dir, include_dir};
|
use include_dir::{Dir, include_dir};
|
||||||
use log::debug;
|
use log::{debug, error};
|
||||||
use rusqlite::{Connection, params};
|
use rusqlite::Connection;
|
||||||
use rusqlite_migration::Migrations;
|
use rusqlite_migration::Migrations;
|
||||||
use std::result::Result;
|
use std::result::Result;
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
@@ -11,15 +11,26 @@ static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/database_migratio
|
|||||||
static MIGRATIONS: LazyLock<Migrations<'static>> =
|
static MIGRATIONS: LazyLock<Migrations<'static>> =
|
||||||
LazyLock::new(|| Migrations::from_directory(&MIGRATIONS_DIR).unwrap());
|
LazyLock::new(|| Migrations::from_directory(&MIGRATIONS_DIR).unwrap());
|
||||||
|
|
||||||
pub fn init_db() -> Result<Connection, &'static str> {
|
pub fn init_db() -> Result<Connection, String> {
|
||||||
debug!("Opening database");
|
debug!("Opening database.");
|
||||||
let mut conn = Connection::open("./oott.db").unwrap();
|
let mut conn = match Connection::open("./oott.db") {
|
||||||
|
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");
|
debug!("Database open, executing migrations if needed.");
|
||||||
// Update the database schema, atomically
|
// Update the database schema, atomically
|
||||||
MIGRATIONS.to_latest(&mut conn);
|
match MIGRATIONS.to_latest(&mut conn) {
|
||||||
|
Ok(_) => {
|
||||||
debug!("Database up to date");
|
debug!("Database up to date.");
|
||||||
|
Ok(conn)
|
||||||
Ok(conn)
|
}
|
||||||
|
Err(error) => {
|
||||||
|
error!("Error updating database: {error}");
|
||||||
|
Err(format!("Error updating database: {error}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,16 +5,21 @@ use std::fmt;
|
|||||||
pub struct Device {
|
pub struct Device {
|
||||||
pub mac_address: String,
|
pub mac_address: String,
|
||||||
pub ipv4_address: String,
|
pub ipv4_address: String,
|
||||||
|
pub vendor: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Device {
|
impl Device {
|
||||||
pub fn get_mac_prefix(&self) -> String {
|
// pub fn get_mac_prefix(&self) -> String {
|
||||||
self.mac_address.get(0..8).unwrap_or("").to_string()
|
// self.mac_address.get(0..8).unwrap_or("").to_string()
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Device {
|
impl fmt::Display for Device {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "mac={}, ip={}", self.mac_address, self.ipv4_address)
|
write!(
|
||||||
|
f,
|
||||||
|
"mac={}, ip={}, vendor={}",
|
||||||
|
self.mac_address, self.ipv4_address, self.vendor
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-13
@@ -1,7 +1,7 @@
|
|||||||
mod packet_send_receive;
|
mod packet_send_receive;
|
||||||
|
|
||||||
use crate::{config, device_finders::Device};
|
use crate::{config, device_finders::Device};
|
||||||
use log::{debug, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use packet_send_receive::{listen_for_packets, send_packet};
|
use packet_send_receive::{listen_for_packets, send_packet};
|
||||||
use pnet::{
|
use pnet::{
|
||||||
datalink::{self, Channel, NetworkInterface},
|
datalink::{self, Channel, NetworkInterface},
|
||||||
@@ -12,20 +12,25 @@ use tokio::time::{Duration, timeout};
|
|||||||
const DEFAULT_SENDER_TIMEOUT: u64 = 60; // 1 minute to send all packets - good for a class C network
|
const DEFAULT_SENDER_TIMEOUT: u64 = 60; // 1 minute to send all packets - good for a class C network
|
||||||
const DEFAULT_SCAN_DURATION: u64 = 300; // 5 minutes to wait per round to receive responses
|
const DEFAULT_SCAN_DURATION: u64 = 300; // 5 minutes to wait per round to receive responses
|
||||||
|
|
||||||
pub async fn find(interface: &str) -> Vec<Device> {
|
pub async fn find(interface: &str) -> Result<Vec<Device>, String> {
|
||||||
debug!("Looking up devices via ARP using interface {}", interface);
|
debug!("Looking up devices via ARP using interface {}", interface);
|
||||||
|
|
||||||
// Get the network device to use
|
// Get the network device to use
|
||||||
let network_interface: NetworkInterface = datalink::interfaces()
|
let network_interface: NetworkInterface = match datalink::interfaces()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|el| el.is_up())
|
.filter(|el| el.is_up())
|
||||||
.filter(|el| el.name == interface)
|
.filter(|el| el.name == interface)
|
||||||
.next()
|
.next()
|
||||||
.expect("Selected interface not found")
|
{
|
||||||
.clone();
|
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
|
// Get the IPV4 network to use
|
||||||
let ipv4_net = network_interface
|
let ipv4_net = match network_interface
|
||||||
.ips
|
.ips
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|el| match el {
|
.filter_map(|el| match el {
|
||||||
@@ -33,13 +38,25 @@ pub async fn find(interface: &str) -> Vec<Device> {
|
|||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.next()
|
.next()
|
||||||
.expect("No local ip address found");
|
{
|
||||||
|
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
|
// Get the local MAC address
|
||||||
let mac = match network_interface.mac {
|
let mac = match network_interface.mac {
|
||||||
Some(mac) => mac,
|
Some(mac) => mac,
|
||||||
None => {
|
None => {
|
||||||
panic!("No local MAC address found");
|
error!("Could not get MAC address for selected interface ({interface}).");
|
||||||
|
return Err(
|
||||||
|
format!("Could not get MAC address for selected interface ({interface}).")
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -48,11 +65,20 @@ pub async fn find(interface: &str) -> Vec<Device> {
|
|||||||
|
|
||||||
// Data channel
|
// Data channel
|
||||||
debug!("Creating data channel");
|
debug!("Creating data channel");
|
||||||
let tunnel = datalink::channel(&network_interface, datalink::Config::default())
|
let tunnel: Channel = match datalink::channel(&network_interface, datalink::Config::default()) {
|
||||||
.expect("Failed to create datalink channel");
|
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 {
|
let (sender, receiver) = match tunnel {
|
||||||
Channel::Ethernet(tx, rx) => (tx, rx),
|
Channel::Ethernet(tx, rx) => (tx, rx),
|
||||||
_ => panic!("Unsupported data channel type"),
|
_ => {
|
||||||
|
error!("Unsupported data channel type");
|
||||||
|
return Err("Unsupported data channel type".to_string());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let send_interface = network_interface.clone();
|
let send_interface = network_interface.clone();
|
||||||
@@ -68,7 +94,10 @@ pub async fn find(interface: &str) -> Vec<Device> {
|
|||||||
info!("Receiver timeout set to {} seconds", receiver_timeout);
|
info!("Receiver timeout set to {} seconds", receiver_timeout);
|
||||||
|
|
||||||
if sender_timeout >= scan_duration {
|
if sender_timeout >= scan_duration {
|
||||||
panic!("OOTT_ARP_SENDER_TIMEOUT needs to be smaller than OOTT_ARP_SCAN_DURATION");
|
warn!(
|
||||||
|
"OOTT_ARP_SENDER_TIMEOUT ({sender_timeout}) needs to be smaller than OOTT_ARP_SCAN_DURATION ({scan_duration})."
|
||||||
|
);
|
||||||
|
return Err(format!("OOTT_ARP_SENDER_TIMEOUT ({sender_timeout}) needs to be smaller than OOTT_ARP_SCAN_DURATION ({scan_duration}).").to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = tokio::join!(
|
let result = tokio::join!(
|
||||||
@@ -88,5 +117,5 @@ pub async fn find(interface: &str) -> Vec<Device> {
|
|||||||
(_, Err(_)) => info!("ARP receiver timed out"),
|
(_, Err(_)) => info!("ARP receiver timed out"),
|
||||||
};
|
};
|
||||||
|
|
||||||
result.1.unwrap_or(Vec::new())
|
Ok(result.1.unwrap_or(Vec::new()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::device_finders::Device;
|
use crate::device_finders::Device;
|
||||||
|
use crate::mac_vendor_finder;
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
use pnet::datalink::{DataLinkReceiver, DataLinkSender, NetworkInterface};
|
use pnet::datalink::{DataLinkReceiver, DataLinkSender, NetworkInterface};
|
||||||
use pnet::ipnetwork::Ipv4Network;
|
use pnet::ipnetwork::Ipv4Network;
|
||||||
@@ -84,14 +85,20 @@ pub async fn listen_for_packets(
|
|||||||
if arp_packet.get_operation() == ArpOperations::Reply {
|
if arp_packet.get_operation() == ArpOperations::Reply {
|
||||||
debug!("It is an ARP reply packet");
|
debug!("It is an ARP reply packet");
|
||||||
if arp_packet.get_target_proto_addr() == ipv4_net.ip() {
|
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!(
|
debug!(
|
||||||
"Found online device - IP addr={} - MAC addr={}",
|
"Found online device - IP addr={} - MAC addr={} - vendor={}",
|
||||||
arp_packet.get_sender_proto_addr(),
|
packet_ip_address, packet_mac_address, packet_vendor
|
||||||
arp_packet.get_sender_hw_addr()
|
|
||||||
);
|
);
|
||||||
devices.push(Device {
|
devices.push(Device {
|
||||||
mac_address: arp_packet.get_sender_hw_addr().to_string(),
|
mac_address: packet_mac_address,
|
||||||
ipv4_address: arp_packet.get_sender_proto_addr().to_string(),
|
ipv4_address: packet_ip_address,
|
||||||
|
vendor: packet_vendor,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-24
@@ -1,11 +1,8 @@
|
|||||||
use log::{debug, info};
|
use log::{debug, error, info};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::sync::LazyLock;
|
||||||
pub struct MacVendorFinder {
|
|
||||||
mac_vendors_database: HashMap<String, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -17,29 +14,41 @@ struct MacRecord {
|
|||||||
// last_update: String,
|
// last_update: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MacVendorFinder {
|
// Initialize mac vendors database as a static lazy loaded unit
|
||||||
pub fn new() -> MacVendorFinder {
|
static MAC_VENDORS_DATABASE: LazyLock<HashMap<String, String>> = LazyLock::new(|| {
|
||||||
info!("Loading MAC vendor database into memory");
|
info!("Loading MAC vendor database into memory");
|
||||||
|
|
||||||
let mut database = HashMap::new();
|
let mut database = HashMap::new();
|
||||||
let data = fs::read_to_string("data/mac-vendors-export.json").unwrap();
|
let data = match fs::read_to_string("data/mac-vendors-export.json") {
|
||||||
let json: Vec<MacRecord> = serde_json::from_str(&data).unwrap();
|
Ok(value) => value,
|
||||||
|
Err(error) => {
|
||||||
debug!("Found {} records in the database", json.len());
|
error!("Error reading mac vendors database (data/mac-vendors-export.json): {error}");
|
||||||
|
panic!("Error reading mac vendors database (data/mac-vendors-export.json): {error}");
|
||||||
for el in json {
|
|
||||||
database.insert(el.mac_prefix.to_uppercase(), el.vendor_name);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
info!("MAC vendor database loaded");
|
let json: Vec<MacRecord> = match serde_json::from_str(&data) {
|
||||||
|
Ok(value) => value,
|
||||||
MacVendorFinder {
|
Err(error) => {
|
||||||
mac_vendors_database: database,
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find(&mut self, mac_prefix: &str) -> Option<&String> {
|
info!("MAC vendor database loaded");
|
||||||
self.mac_vendors_database
|
database
|
||||||
.get(mac_prefix.to_uppercase().as_str())
|
});
|
||||||
}
|
|
||||||
|
// 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()
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-16
@@ -1,6 +1,4 @@
|
|||||||
use crate::mac_vendor_finder::MacVendorFinder;
|
use log::{debug, info};
|
||||||
use log::info;
|
|
||||||
use rusqlite::Connection;
|
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod db;
|
mod db;
|
||||||
@@ -12,21 +10,18 @@ async fn main() {
|
|||||||
env_logger::init();
|
env_logger::init();
|
||||||
info!("Starting up oott");
|
info!("Starting up oott");
|
||||||
|
|
||||||
let mut mac_vendor_finder = MacVendorFinder::new();
|
let db_conn = db::init_db();
|
||||||
let mut db_conn = db::init_db().unwrap();
|
|
||||||
|
|
||||||
// let devices = device_finders::arp::find("eno1").await;
|
let devices = device_finders::arp::find("eno1").await.unwrap();
|
||||||
|
|
||||||
// info!("Done with ARP probes");
|
info!("Done with ARP probes");
|
||||||
// info!("Found {} online devices", devices.iter().count());
|
info!("Found {} online devices", devices.iter().count());
|
||||||
// for device in devices.iter() {
|
|
||||||
// info!("Device found {}", device);
|
// mac_vendor_finder.populate_vendors(&devices);
|
||||||
// let vendor_result = mac_vendor_finder.find(device.get_mac_prefix().as_str());
|
|
||||||
// match vendor_result {
|
for device in devices.iter() {
|
||||||
// Some(vendor) => info!("Device vendor = {}", vendor),
|
debug!("Device found {}", device);
|
||||||
// None => info!("Device vendor not found"),
|
}
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
info!("Exiting");
|
info!("Exiting");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user