1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
use serde::Deserialize; use serde_json; use serde_json::error::Error as JsonError; use std::collections::HashMap; use std::io::Error as IoError; use std::net::IpAddr; use std::process::Command; use std::str::Utf8Error; #[derive(Deserialize, Debug, PartialEq)] pub struct LlAddr(String); #[derive(Deserialize, Debug)] struct Neighbor { dst: IpAddr, dev: String, lladdr: Option<LlAddr>, state: Vec<State>, } #[derive(Deserialize, Debug)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] enum State { Delay, Failed, Reachable, Stale, } #[derive(Debug)] pub enum LookupError { IoError(IoError), Utf8Error(Utf8Error), JsonError(JsonError), } impl From<IoError> for LookupError { fn from(e: IoError) -> LookupError { LookupError::IoError(e) } } impl From<Utf8Error> for LookupError { fn from(e: Utf8Error) -> LookupError { LookupError::Utf8Error(e) } } impl From<JsonError> for LookupError { fn from(e: JsonError) -> LookupError { LookupError::JsonError(e) } } #[cfg(target_os = "linux")] fn neighbors_raw() -> Result<Vec<Neighbor>, LookupError> { let json = Command::new("ip") .arg("--json") .arg("neighbor") .stderr(std::process::Stdio::inherit()) .output()? .stdout; Ok(serde_json::from_str(std::str::from_utf8(json.as_ref())?)?) } #[cfg(target_os = "linux")] pub fn neighbors() -> Result<HashMap<IpAddr, LlAddr>, LookupError> { let neighbors: Vec<Neighbor> = neighbors_raw()?; let neighbors_map = neighbors .into_iter() .filter_map(|n| match n { Neighbor { lladdr: Some(l), .. } => Some((n.dst, l)), Neighbor { lladdr: None, .. } => None, }) .collect(); Ok(neighbors_map) } #[cfg(target_os = "linux")] pub fn lookup(ip_addr: IpAddr) -> Result<Option<LlAddr>, LookupError> { let ip_addr = normalize_ip_addr(ip_addr); let neighbors: Vec<Neighbor> = neighbors_raw()?; let lladdr = neighbors .into_iter() .filter(|n| n.dst == ip_addr) .filter_map(|n| n.lladdr) .nth(0); Ok(lladdr) } fn normalize_ip_addr(ip_addr: IpAddr) -> IpAddr { match ip_addr { IpAddr::V4(_) => ip_addr, IpAddr::V6(v6) => match v6.to_ipv4() { Some(converted_v4) => IpAddr::V4(converted_v4), None => ip_addr, }, } } #[cfg(test)] #[cfg(target_os = "linux")] mod tests { use super::*; #[test] fn test_neighbors() { assert!(neighbors().unwrap().keys().count() > 0); } #[test] fn test_lookup() { for (ip, lladdr) in neighbors().unwrap().into_iter() { assert_eq!(lookup(ip).unwrap().unwrap(), lladdr); } } }