diff --git a/.cargo/config.toml b/.cargo/config.toml index 200f37f..b043bf8 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,4 +1,4 @@ [env] -RUST_LOG="debug" +RUST_LOG="info" OOTT_ARP_SENDER_TIMEOUT="30" OOTT_ARP_SCAN_DURATION="60" diff --git a/Cargo.lock b/Cargo.lock index 96871fe..97c8cc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.21" @@ -61,6 +70,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "bitflags" version = "2.10.0" @@ -95,12 +110,31 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "colorchoice" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "env_filter" version = "0.1.4" @@ -182,6 +216,30 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "include_dir" version = "0.7.4" @@ -311,6 +369,15 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -327,6 +394,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" name = "oott" version = "0.1.0" dependencies = [ + "chrono", "env_logger", "include_dir", "log", @@ -552,6 +620,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1c93dd1c9683b438c392c492109cb702b8090b2bfc8fed6f6e4eb4523f17af3" dependencies = [ "bitflags", + "chrono", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -820,12 +889,65 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.60.2" diff --git a/Cargo.toml b/Cargo.toml index 019d8a1..90047a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ pnet = "0.35.0" tokio = { version = "1.0", features = ["full"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -rusqlite = { version = "0.38.0", features = ["bundled"] } +rusqlite = { version = "0.38.0", features = ["bundled", "chrono"] } rusqlite_migration = { version = "2.4.0", features = ["from-directory"] } include_dir = "0.7.4" +chrono = "0.4.43" diff --git a/database_migrations/01-initial_structure/up.sql b/database_migrations/01-initial_structure/up.sql index 5f77633..2c5dc10 100644 --- a/database_migrations/01-initial_structure/up.sql +++ b/database_migrations/01-initial_structure/up.sql @@ -1,6 +1,6 @@ CREATE TABLE devices( mac_address TEXT PRIMARY KEY, - ip_address TEXT NOT NULL, - vendor_name TEXT, + ipv4_address TEXT NOT NULL, + vendor TEXT, last_seen TEXT NOT NULL ); diff --git a/src/db.rs b/src/db.rs index cbc18fd..a876b73 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,3 +1,5 @@ +pub mod devices; + use include_dir::{Dir, include_dir}; use log::{debug, error}; use rusqlite::Connection; diff --git a/src/db/devices.rs b/src/db/devices.rs new file mode 100644 index 0000000..30e7638 --- /dev/null +++ b/src/db/devices.rs @@ -0,0 +1,71 @@ +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, mac_address: String) -> Option { + // TODO: Add last_seen + let result: Result = 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, 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, 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()) + } + } +} diff --git a/src/device_finders.rs b/src/device_finders.rs index 238ad6b..677cda9 100644 --- a/src/device_finders.rs +++ b/src/device_finders.rs @@ -1,25 +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, -} - -impl Device { - // pub fn get_mac_prefix(&self) -> String { - // self.mac_address.get(0..8).unwrap_or("").to_string() - // } + pub last_seen: NaiveDateTime, } impl fmt::Display for Device { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "mac={}, ip={}, vendor={}", - self.mac_address, self.ipv4_address, self.vendor + "mac={}, ip={}, vendor={}, last_seen={}", + self.mac_address, self.ipv4_address, self.vendor, self.last_seen ) } } diff --git a/src/device_finders/arp/packet_send_receive.rs b/src/device_finders/arp/packet_send_receive.rs index 3bab913..891df12 100644 --- a/src/device_finders/arp/packet_send_receive.rs +++ b/src/device_finders/arp/packet_send_receive.rs @@ -1,5 +1,6 @@ use crate::device_finders::Device; use crate::mac_vendor_finder; +use chrono::Local; use log::{debug, info}; use pnet::datalink::{DataLinkReceiver, DataLinkSender, NetworkInterface}; use pnet::ipnetwork::Ipv4Network; @@ -99,6 +100,7 @@ pub async fn listen_for_packets( mac_address: packet_mac_address, ipv4_address: packet_ip_address, vendor: packet_vendor, + last_seen: Local::now().naive_local(), }); } } diff --git a/src/main.rs b/src/main.rs index b99038c..755ae3b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use log::{debug, info}; +use std::sync::{Arc, Mutex}; mod config; mod db; @@ -6,22 +7,54 @@ mod device_finders; mod mac_vendor_finder; #[tokio::main] -async fn main() { +async fn main() -> Result<(), String> { env_logger::init(); info!("Starting up oott"); - let db_conn = db::init_db(); + // Get database connection - thread protected + let db_conn = Arc::new(Mutex::new(db::init_db().unwrap())); + // Find online devices via ARP let devices = device_finders::arp::find("eno1").await.unwrap(); info!("Done with ARP probes"); info!("Found {} online devices", devices.iter().count()); - // mac_vendor_finder.populate_vendors(&devices); - + // Process found devices for device in devices.iter() { - debug!("Device found {}", device); + 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 + info!( + "Device found in database {}. Updating to {}.", + recorded_device, device + ); + // TODO: If IPv4 or vendor changed do something + db::devices::update(db_conn_clone.lock().unwrap(), device.clone()); + } + None => { + // If it doesn't exist insert it + info!( + "Device with MAC address {} not found in database. Inserting it.", + device.mac_address + ); + + db::devices::insert(db_conn_clone.lock().unwrap(), device.clone()); + } + }; } info!("Exiting"); + + Ok(()) }