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);
        }
    }
}