use std::collections::BTreeSet; use anyhow::Context as _; use common::proc::Command; use serde::Deserialize; #[derive(Debug, Deserialize)] struct Link { ifname: String, } #[derive(Debug, Deserialize)] struct Addr { addr_info: Vec, } #[derive(Debug, Deserialize)] struct AddrInfo { local: String, prefixlen: usize, } #[derive(Debug, Deserialize)] struct Route { dst: String, gateway: Option, } pub fn get_link_names() -> anyhow::Result> { let mut proc = Command::new("ip"); proc.args(["-j", "link"]); let out = proc.try_spawn_to_json::>()?; Ok(out.into_iter().map(|l| l.ifname).collect()) } pub fn has_ipv6_address(dev: &str, addr: &str) -> anyhow::Result { let mut proc = Command::new("ip"); proc.args(["-j", "-6", "addr", "show", dev]); let out: Vec = proc.try_spawn_to_json()?; let idx = addr .char_indices() .find_map(|(idx, ch)| if ch == '/' { Some(idx) } else { None }) .expect("Address should contain prefixlen"); let prefixlen = &addr[idx + 1..]; let addr = &addr[..idx]; let prefixlen: usize = prefixlen .parse() .context("Ipv6 prefixlen was not a usize")?; Ok(out.iter().any(|a| { a.addr_info .iter() .any(|a| a.local == addr && a.prefixlen == prefixlen) })) } pub fn has_default_route(dev: &str, gateway: &str) -> anyhow::Result { let mut proc = Command::new("ip"); proc.args(["-j", "-6", "route", "show", "dev", dev]); let out: Vec = proc.try_spawn_to_json()?; Ok(out .iter() .any(|r| r.dst == "default" && r.gateway.as_deref().is_some_and(|gw| gw == gateway))) }