From 6551a0a7adc297d03176be06cfe57e341af741b3 Mon Sep 17 00:00:00 2001 From: Joseph Hobbs Date: Fri, 26 Jul 2024 13:55:13 -0400 Subject: [PATCH] Add native MAC address structure --- Cargo.lock | 4 + examples/list.rs | 6 +- proton_arp/Cargo.toml | 3 + proton_arp/src/arp.rs | 4 +- proton_arp/src/cache.rs | 2 +- proton_arp/src/scan/reply.rs | 2 +- proton_dev/Cargo.toml | 5 +- proton_dev/src/device.rs | 11 +- proton_dev/src/manager.rs | 2 +- proton_mac/Cargo.toml | 13 ++- proton_mac/src/error.rs | 30 ----- proton_mac/src/lib.rs | 11 +- proton_mac/src/mac.rs | 54 +++++++++ proton_mac/src/mac_addr_policy.rs | 188 ------------------------------ 14 files changed, 95 insertions(+), 240 deletions(-) delete mode 100644 proton_mac/src/error.rs create mode 100644 proton_mac/src/mac.rs delete mode 100644 proton_mac/src/mac_addr_policy.rs diff --git a/Cargo.lock b/Cargo.lock index 059a7e3..875d3a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -365,6 +365,7 @@ dependencies = [ "cidr", "pnet", "proton_err", + "proton_mac", "proton_nif", "tokio", ] @@ -379,6 +380,7 @@ dependencies = [ "pnet", "proton_arp", "proton_err", + "proton_mac", "serde", "serde_json", ] @@ -392,6 +394,8 @@ name = "proton_mac" version = "0.1.0" dependencies = [ "pnet", + "serde", + "serde_json", ] [[package]] diff --git a/examples/list.rs b/examples/list.rs index 7031585..c2981c5 100644 --- a/examples/list.rs +++ b/examples/list.rs @@ -12,15 +12,17 @@ use proton::{ #[tokio::main] async fn main() -> ProtonResult<()> { + let ifname = "wlp4s0"; + let mut ap = AccessPoint::new( Ipv4Cidr::new( // Internal network range Ipv4Addr::new(192, 168, 0, 0), // Network address 24, // Network length ).unwrap(), - "wlp4s0", + ifname, )?; - println!("Scanning network interface: {}...", "wlp4s0"); + println!("Scanning network interface: {}...", ifname); let devices: Vec = ap.scan().await?; diff --git a/proton_arp/Cargo.toml b/proton_arp/Cargo.toml index a69a31f..653f2fd 100644 --- a/proton_arp/Cargo.toml +++ b/proton_arp/Cargo.toml @@ -20,5 +20,8 @@ version = "0.35.0" [dependencies.proton_err] path = "../proton_err" +[dependencies.proton_mac] +path = "../proton_mac" + [dependencies.proton_nif] path = "../proton_nif" \ No newline at end of file diff --git a/proton_arp/src/arp.rs b/proton_arp/src/arp.rs index b70b5e5..6c8ec7e 100644 --- a/proton_arp/src/arp.rs +++ b/proton_arp/src/arp.rs @@ -4,10 +4,10 @@ use std::net::Ipv4Addr; use cidr::Ipv4Cidr; -use pnet::datalink::MacAddr; - use proton_err::ProtonResult; +use proton_mac::MacAddr; + use crate::{ ArpCache, ArpCacheIterator, diff --git a/proton_arp/src/cache.rs b/proton_arp/src/cache.rs index 29d0c91..884be9d 100644 --- a/proton_arp/src/cache.rs +++ b/proton_arp/src/cache.rs @@ -9,7 +9,7 @@ use std::{ }, }; -use pnet::datalink::MacAddr; +use proton_mac::MacAddr; #[derive(Clone)] /// An address resolution cache. diff --git a/proton_arp/src/scan/reply.rs b/proton_arp/src/scan/reply.rs index 35d330f..5b236a3 100644 --- a/proton_arp/src/scan/reply.rs +++ b/proton_arp/src/scan/reply.rs @@ -68,7 +68,7 @@ pub async fn listen( // Construct cache entry let entry = ArpCacheEntry::new( arp_packet.get_sender_proto_addr(), - arp_packet.get_sender_hw_addr(), + arp_packet.get_sender_hw_addr().into(), ); // Send the reply diff --git a/proton_dev/Cargo.toml b/proton_dev/Cargo.toml index 399fd27..445a448 100644 --- a/proton_dev/Cargo.toml +++ b/proton_dev/Cargo.toml @@ -24,4 +24,7 @@ features = ["derive"] path = "../proton_arp" [dependencies.proton_err] -path = "../proton_err" \ No newline at end of file +path = "../proton_err" + +[dependencies.proton_mac] +path = "../proton_mac" \ No newline at end of file diff --git a/proton_dev/src/device.rs b/proton_dev/src/device.rs index bc305e9..4af3f06 100644 --- a/proton_dev/src/device.rs +++ b/proton_dev/src/device.rs @@ -12,11 +12,13 @@ use serde::Serialize; use proton_arp::ArpManager; +use proton_mac::MacAddr; + #[derive(Serialize, Clone, Copy, Debug)] /// Information about a connected network device. pub struct Device { /// MAC address of the device. - pub mac: [u8; 6], + pub mac: MacAddr, /// IPv4 address of the device. pub ipv4: Ipv4Addr, @@ -32,13 +34,14 @@ impl Device { /// Convert a `Station` into a `Device` by checking the ARP cache. pub fn from_station(station: Station, arp: &ArpManager) -> Self { // Get hardware address of the station - let mac: [u8; 6] = station.bssid + let mac: MacAddr = station.bssid .unwrap_or_default() .try_into() - .unwrap_or([0; 6]); + .unwrap_or([0; 6]) + .into(); // Get IPv4 address of the station - let ipv4: Ipv4Addr = arp.lookup_mac(mac.into()) + let ipv4: Ipv4Addr = arp.lookup_mac(mac) .unwrap_or(Ipv4Addr::new(0, 0, 0, 0)); // Get signal strength of this station diff --git a/proton_dev/src/manager.rs b/proton_dev/src/manager.rs index e0649dd..7a4f142 100644 --- a/proton_dev/src/manager.rs +++ b/proton_dev/src/manager.rs @@ -68,7 +68,7 @@ impl DeviceManager { self.arp_manager.scan().await?; // Determine Wi-Fi device by name - let check_wifi_device = |iface: &Interface| parse_string(&iface.name.clone().unwrap_or_default()) == self.wlifname; + let check_wifi_device = |iface: &Interface| parse_string(&iface.name.clone().unwrap_or_default()).trim_end_matches('\0') == self.wlifname; // Get the Wi-Fi device let interface = self.socket.get_interfaces_info()? diff --git a/proton_mac/Cargo.toml b/proton_mac/Cargo.toml index b17a4a6..da5aca7 100644 --- a/proton_mac/Cargo.toml +++ b/proton_mac/Cargo.toml @@ -3,7 +3,16 @@ name = "proton_mac" version = "0.1.0" edition = "2021" +[lib] +name = "proton_mac" +path = "src/lib.rs" + +[dependencies] +serde_json = "1.0.120" + [dependencies.pnet] version = "0.35.0" -default-features = false -features = ["std"] + +[dependencies.serde] +version = "1.0.204" +features = ["derive"] diff --git a/proton_mac/src/error.rs b/proton_mac/src/error.rs deleted file mode 100644 index fbda919..0000000 --- a/proton_mac/src/error.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! MAC address policy errors. - -use std::{ - error::Error, - fmt, -}; - -#[derive(PartialEq, Eq, Clone, Copy, Debug)] -#[non_exhaustive] -/// A MAC address policy error. -pub enum MacAddrPolicyError { - /// The user tried to whitelist a device on a non-whitelist policy. - NotWhitelistPolicy, - - /// The user tried to blacklist a device on a non-blacklist policy. - NotBlacklistPolicy, -} - -impl fmt::Display for MacAddrPolicyError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let error = match self { - Self::NotWhitelistPolicy => "cannot whitelist device on a non-whitelist policy", - Self::NotBlacklistPolicy => "cannot blacklist device on a non-blacklist policy", - }; - - write!(f, "{}", error) - } -} - -impl Error for MacAddrPolicyError { } \ No newline at end of file diff --git a/proton_mac/src/lib.rs b/proton_mac/src/lib.rs index f592ea1..4f85124 100644 --- a/proton_mac/src/lib.rs +++ b/proton_mac/src/lib.rs @@ -1,10 +1,5 @@ -//! MAC address policy management for the Proton access point management library. +//! MAC address data structure for the Proton access point management library. -#![deny(warnings)] -#![deny(missing_docs)] +mod mac; -mod error; -mod mac_addr_policy; - -pub use error::MacAddrPolicyError; -pub use mac_addr_policy::MacAddrPolicy; \ No newline at end of file +pub use mac::MacAddr; \ No newline at end of file diff --git a/proton_mac/src/mac.rs b/proton_mac/src/mac.rs new file mode 100644 index 0000000..d6d4cf9 --- /dev/null +++ b/proton_mac/src/mac.rs @@ -0,0 +1,54 @@ +//! MAC address type. + +use std::fmt::{ + Display, + Debug, + Formatter, + Result, +}; + +use serde::Serialize; + +#[derive(Serialize, PartialEq, Eq, Clone, Copy)] +/// A hardware (MAC) address consisting of six octets. +pub struct MacAddr ([u8; 6]); + +impl Display for MacAddr { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!( + f, + "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + self.0[0], + self.0[1], + self.0[2], + self.0[3], + self.0[4], + self.0[5], + ) + } +} + +impl Debug for MacAddr { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "{}", self) + } +} + +impl From<[u8; 6]> for MacAddr { + fn from(octets: [u8; 6]) -> Self { + Self (octets) + } +} + +impl From for MacAddr { + fn from(mac: pnet::datalink::MacAddr) -> Self { + Self ([ + mac.0, + mac.1, + mac.2, + mac.3, + mac.4, + mac.5, + ]) + } +} \ No newline at end of file diff --git a/proton_mac/src/mac_addr_policy.rs b/proton_mac/src/mac_addr_policy.rs deleted file mode 100644 index a54dc40..0000000 --- a/proton_mac/src/mac_addr_policy.rs +++ /dev/null @@ -1,188 +0,0 @@ -//! MAC address policy abstraction. - -use pnet::datalink::MacAddr; - -use crate::MacAddrPolicyError; - -/// Result type for MAC address policy actions. -pub type PolicyResult = Result<(), MacAddrPolicyError>; - -#[derive(Clone)] -/// A MAC address policy. -/// -/// This defines the policy by which MAC addresses (hardware addresses -/// on Layer 2 of the OSI Model) are permitted to join the wireless access -/// point and send and receive traffic. -pub enum MacAddrPolicy { - /// All MAC addresses may join the access point. - Public, - - /// Only a specified list of MAC addresses may join the access point. - Whitelist (Vec), - - /// All MAC addresses except a specified list may join the access point. - Blacklist (Vec), -} - -impl MacAddrPolicy { - /// Create a new public MAC address policy. - /// - /// # Parameters - /// None. - /// - /// # Returns - /// A new `MacAddrPolicy`. - pub fn public() -> Self { - Self::Public - } - - /// Create a new MAC address whitelist policy. - /// - /// # Parameters - /// None. - /// - /// # Returns - /// A new `MacAddrPolicy` with an empty whitelist. - pub fn whitelist() -> Self { - Self::Whitelist (Vec::new()) - } - - /// Create a new MAC address blacklist policy. - /// - /// # Parameters - /// None. - /// - /// # Returns - /// A new `MacAddrPolicy` with an empty blacklist. - pub fn blacklist() -> Self { - Self::Blacklist (Vec::new()) - } - - /// Add a MAC address to the whitelist. The policy must be initialized - /// as a whitelist. - /// - /// # Parameters - /// - `device` (`MacAddr`): the MAC address of the device allowed - /// - /// # Returns - /// A `PolicyResult` indicating whether or not the whitelisting was successful. - /// This method will return an error if the policy was not a whitelist policy. - pub fn allow(&mut self, device: MacAddr) -> PolicyResult { - if let Self::Whitelist (wl) = self { - wl.push(device); - Ok (()) - } else { - Err (MacAddrPolicyError::NotWhitelistPolicy) - } - } - - /// Add a MAC address to the blacklist. The policy must be initialized - /// as a blacklist. - /// - /// # Parameters - /// - `device` (`MacAddr`): the MAC address of the device denied - /// - /// # Returns - /// A `PolicyResult` indicating whether or not the blacklisting was successful. - /// This method will return an error if the policy was not a blacklist policy. - pub fn deny(&mut self, device: MacAddr) -> PolicyResult { - if let Self::Blacklist (bl) = self { - bl.push(device); - Ok (()) - } else { - Err (MacAddrPolicyError::NotBlacklistPolicy) - } - } - - /// Check if a MAC address is permitted by the policy. - /// - /// # Parameters - /// - `address` (`MacAddr`): the MAC address to be checked - /// - /// # Returns - /// A `bool` indicating whether or not the MAC address is permitted - /// by the policy. - pub fn check(&self, address: MacAddr) -> bool { - match self { - // A public policy permits all MAC addresses - Self::Public => true, - - // A whitelist allows only some MAC addresses - Self::Whitelist (wl) => wl.contains(&address), - - // A blacklist denies only some MAC addresses - Self::Blacklist (bl) => !bl.contains(&address), - } - } -} - -impl Default for MacAddrPolicy { - fn default() -> Self { - Self::public() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn create_public_mac_policy() { - // Create a public MAC address policy - let policy = MacAddrPolicy::public(); - - // Define arbitrary MAC addresses - let mac1 = MacAddr::new(32, 103, 244, 102, 34, 1); - let mac2 = MacAddr::new(250, 33, 39, 3, 48, 73); - let mac3 = MacAddr::new(49, 123, 86, 38, 20, 67); - - // Check MAC addresses - assert!(policy.check(mac1)); - assert!(policy.check(mac2)); - assert!(policy.check(mac3)); - } - - #[test] - fn create_whitelist_mac_policy() { - // Create a whitelist MAC address policy - let mut policy = MacAddrPolicy::whitelist(); - - // Define arbitrary MAC addresses - let mac1 = MacAddr::new(32, 103, 244, 102, 34, 1); - let mac2 = MacAddr::new(250, 33, 39, 3, 48, 73); - let mac3 = MacAddr::new(49, 123, 86, 38, 20, 67); - - // Allow one address on the policy - let result = policy.allow(mac1); - - // Check for successful whitelisting - assert_eq!(result, Ok (())); - - // Check MAC addresses - assert!(policy.check(mac1)); - assert!(!policy.check(mac2)); - assert!(!policy.check(mac3)); - } - - #[test] - fn create_blacklist_mac_policy() { - // Create a blacklist MAC address policy - let mut policy = MacAddrPolicy::blacklist(); - - // Define arbitrary MAC addresses - let mac1 = MacAddr::new(32, 103, 244, 102, 34, 1); - let mac2 = MacAddr::new(250, 33, 39, 3, 48, 73); - let mac3 = MacAddr::new(49, 123, 86, 38, 20, 67); - - // Deny one address on the policy - let result = policy.deny(mac1); - - // Check for successful blacklisting - assert_eq!(result, Ok(())); - - // Check MAC addresses - assert!(!policy.check(mac1)); - assert!(policy.check(mac2)); - assert!(policy.check(mac3)); - } -} \ No newline at end of file