From 0a1e8d6171e0672e675f972fd0531a2843c3f9c3 Mon Sep 17 00:00:00 2001 From: rzuasti Date: Tue, 13 Jan 2026 11:01:53 -0500 Subject: [PATCH] Restructured modules, integrated async-arp --- Cargo.lock | 11 +++++ Cargo.toml | 5 +++ .../mod.rs => device_finders.rs} | 0 src/device_finders/arp.rs | 16 ++----- src/device_finders/arp/helper_async_arp.rs | 39 ++++++++++++++++ .../arp/helper_async_arp/common.rs | 45 +++++++++++++++++++ 6 files changed, 103 insertions(+), 13 deletions(-) rename src/{device_finders/mod.rs => device_finders.rs} (100%) create mode 100644 src/device_finders/arp/helper_async_arp.rs create mode 100644 src/device_finders/arp/helper_async_arp/common.rs diff --git a/Cargo.lock b/Cargo.lock index 0c3e605..5217731 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -178,6 +178,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + [[package]] name = "ipnetwork" version = "0.20.0" @@ -226,7 +232,12 @@ checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" name = "oott" version = "0.1.0" dependencies = [ + "afpacket", "async-arp", + "futures", + "ipnet", + "pnet", + "tokio", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d0d25d1..1448d6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,8 @@ edition = "2024" [dependencies] async-arp = "0.4.0" +afpacket = {version = "0.2.3", features = ["async-tokio"]} +ipnet = "2.11.0" +pnet = "0.35.0" +tokio = {version = "1.43.0", features = ["io-util", "time", "rt", "macros", "sync"]} +futures = "0.3.31" diff --git a/src/device_finders/mod.rs b/src/device_finders.rs similarity index 100% rename from src/device_finders/mod.rs rename to src/device_finders.rs diff --git a/src/device_finders/arp.rs b/src/device_finders/arp.rs index a9f9bc9..7ea7538 100644 --- a/src/device_finders/arp.rs +++ b/src/device_finders/arp.rs @@ -1,19 +1,9 @@ use crate::device_finders::Device; -use async_arp::{Client, ClientConfigBuilder, ClientSpinner, ProbeStatus, Result}; -use std::io::Write; -use std::time::{Duration, Instant}; + +mod helper_async_arp; pub fn find() -> Vec { - let mut devices = Vec::new(); - devices.push(Device { - mac_address: String::from("mac1"), - }); - devices.push(Device { - mac_address: String::from("mac2"), - }); - devices.push(Device { - mac_address: String::from("mac3"), - }); + let devices = helper_async_arp::execute_probe(&String::from("eno1")); devices } diff --git a/src/device_finders/arp/helper_async_arp.rs b/src/device_finders/arp/helper_async_arp.rs new file mode 100644 index 0000000..4114a61 --- /dev/null +++ b/src/device_finders/arp/helper_async_arp.rs @@ -0,0 +1,39 @@ +use async_arp::{Client, ClientConfigBuilder, ProbeStatus}; +use std::time::{Duration, Instant}; + +use crate::device_finders::Device; +mod common; + +#[tokio::main(flavor = "current_thread")] +pub async fn execute_probe(iface: &str) -> Vec { + let interface = common::interface_from(&iface); + let net = common::net_from(&interface).unwrap(); + + let client = Client::new( + ClientConfigBuilder::new(&iface) + .with_response_timeout(Duration::from_millis(500)) + .build(), + ) + .unwrap(); + + let inputs = common::generate_probe_inputs(net, interface); + let start = Instant::now(); + + let futures = inputs.into_iter().map(|input| client.probe(input)); + let outcomes = futures::future::join_all(futures).await; + let scan_duration = start.elapsed(); + + let occupied = outcomes + .into_iter() + .filter_map(|outcome| outcome.ok()) + .filter(|outcome| outcome.status == ProbeStatus::Occupied); + + let mut devices = Vec::new(); + for outcome in occupied { + devices.push(Device { + mac_address: outcome.target_ip.to_string(), + }); + } + + devices +} diff --git a/src/device_finders/arp/helper_async_arp/common.rs b/src/device_finders/arp/helper_async_arp/common.rs new file mode 100644 index 0000000..5bd9321 --- /dev/null +++ b/src/device_finders/arp/helper_async_arp/common.rs @@ -0,0 +1,45 @@ +use async_arp::{ProbeInput, ProbeInputBuilder}; +use ipnet::Ipv4Net; +use pnet::datalink::{self, NetworkInterface}; +use std::net::IpAddr; + +// Generate the probe inputs for a scan +pub(crate) fn generate_probe_inputs(net: Ipv4Net, interface: NetworkInterface) -> Vec { + net.hosts() + .map(|target_ip| { + let sender_mac = interface + .mac + .ok_or("interface does not have mac address") + .unwrap(); + ProbeInputBuilder::new() + .with_sender_mac(sender_mac) + .with_target_ip(target_ip) + .build() + .unwrap() + }) + .collect() +} + +// Find the desired network interface +pub(crate) fn interface_from(interface_name: &str) -> NetworkInterface { + datalink::interfaces() + .into_iter() + .find(|iface| iface.name == interface_name) + .ok_or_else(|| format!("interface {} not found", interface_name)) + .unwrap() +} + +// Find the network from which we are probing +pub(crate) fn net_from(interface: &NetworkInterface) -> Option { + let net = interface + .ips + .iter() + .filter(|net| net.is_ipv4()) + .take(1) + .next()?; + if let IpAddr::V4(ipv4) = net.ip() { + Some(Ipv4Net::new(ipv4, net.prefix()).unwrap()) + } else { + None + } +}