#[must_use]
pub fn detect_gateway_mac() -> Option<String> {
#[cfg(target_os = "macos")]
{
detect_macos()
}
#[cfg(target_os = "linux")]
{
detect_linux()
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
{
None
}
}
#[cfg(target_os = "macos")]
fn detect_macos() -> Option<String> {
let route = run(&["route", "-n", "get", "default"])?;
let gw_ip = parse_macos_gateway_ip(&route)?;
let arp = run(&["arp", "-n", &gw_ip])?;
parse_macos_arp_mac(&arp)
}
#[cfg(target_os = "linux")]
fn detect_linux() -> Option<String> {
let route = run(&["ip", "route", "show", "default"])?;
let gw_ip = parse_linux_gateway_ip(&route)?;
let neigh = run(&["ip", "neigh", "show", &gw_ip])?;
parse_linux_neigh_mac(&neigh)
}
#[cfg(any(target_os = "macos", target_os = "linux"))]
fn run(args: &[&str]) -> Option<String> {
let (cmd, rest) = args.split_first()?;
super::capture_stdout("gateway-mac detection", cmd, rest)
}
#[must_use]
pub fn parse_macos_gateway_ip(s: &str) -> Option<String> {
s.lines().find_map(|l| {
l.trim()
.strip_prefix("gateway:")
.map(str::trim)
.map(String::from)
})
}
#[must_use]
pub fn parse_macos_arp_mac(s: &str) -> Option<String> {
s.split_whitespace().find(|t| is_mac(t)).map(String::from)
}
#[must_use]
pub fn parse_linux_gateway_ip(s: &str) -> Option<String> {
for line in s.lines() {
let mut it = line.split_whitespace();
if it.next() == Some("default") && it.next() == Some("via") {
return it.next().map(String::from);
}
}
None
}
#[must_use]
pub fn parse_linux_neigh_mac(s: &str) -> Option<String> {
let mut tokens = s.split_whitespace();
while let Some(t) = tokens.next() {
if t == "lladdr" {
return tokens.next().filter(|m| is_mac(m)).map(String::from);
}
}
None
}
fn is_mac(s: &str) -> bool {
if s.len() != 17 {
return false;
}
let bytes = s.as_bytes();
for (i, b) in bytes.iter().enumerate() {
match i % 3 {
2 => {
if *b != b':' {
return false;
}
}
_ => {
if !b.is_ascii_hexdigit() {
return false;
}
}
}
}
true
}