machines/rust/program/hetzner-static-ip/src/iproute2.rs

69 lines
1.7 KiB
Rust
Raw Normal View History

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<AddrInfo>,
}
#[derive(Debug, Deserialize)]
struct AddrInfo {
local: String,
prefixlen: usize,
}
#[derive(Debug, Deserialize)]
struct Route {
dst: String,
gateway: Option<String>,
}
pub fn get_link_names() -> anyhow::Result<BTreeSet<String>> {
let mut proc = Command::new("ip");
proc.args(["-j", "link"]);
let out = proc.try_spawn_to_json::<Vec<Link>>()?;
Ok(out.into_iter().map(|l| l.ifname).collect())
}
pub fn has_ipv6_address(dev: &str, addr: &str) -> anyhow::Result<bool> {
let mut proc = Command::new("ip");
proc.args(["-j", "-6", "addr", "show", dev]);
let out: Vec<Addr> = 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<bool> {
let mut proc = Command::new("ip");
proc.args(["-j", "-6", "route", "show", "dev", dev]);
let out: Vec<Route> = proc.try_spawn_to_json()?;
Ok(out
.iter()
.any(|r| r.dst == "default" && r.gateway.as_deref().is_some_and(|gw| gw == gateway)))
}