about as good as i want idfk yet
This commit is contained in:
1 parent
7eee1e0bab
commit
310b0b462d
7 files changed
+375
-26
No files matched your search
@@ -11,7 +11,10 @@ pub mod nl;
|
||||
pub use crate::subcommands::Backend;
|
||||
use crate::utils::serialize::mac::option_mac;
|
||||
use macaddr::MacAddr;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
|
||||
use crate::subcommands::link::OperState;
|
||||
|
||||
/// i dont include what i dont know about (almost all ts)
|
||||
#[derive(Serialize, Debug, Deserialize)]
|
||||
@@ -19,7 +22,7 @@ pub struct AddrOutput {
|
||||
pub ifindex: u32,
|
||||
pub ifname: String,
|
||||
/// i imagine UP or DOWN, unknown
|
||||
pub operstate: String,
|
||||
pub operstate: OperState,
|
||||
// 6 has a serde and the enum doesnt? why. (serializing ts is ass although... im not given an array. they string formatted ts)
|
||||
#[serde(with = "option_mac", default)]
|
||||
pub address: Option<MacAddr>,
|
||||
@@ -31,18 +34,102 @@ pub struct AddrOutput {
|
||||
// Raw JSON shape from ip -j -4 address show
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct AddrInfo {
|
||||
pub family: Option<String>,
|
||||
pub family: Option<AddressFamily>,
|
||||
|
||||
pub local: Option<String>,
|
||||
pub prefixlen: Option<u8>,
|
||||
#[serde(flatten, default)]
|
||||
pub cidr: InterfaceCidr,
|
||||
|
||||
pub broadcast: Option<String>,
|
||||
pub broadcast: Option<Ipv4Addr>,
|
||||
|
||||
pub scope: Option<String>,
|
||||
pub label: Option<String>,
|
||||
// many more exist; we only take what we need
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AddressFamily {
|
||||
Inet,
|
||||
Inet6,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl AddressFamily {
|
||||
pub fn parse_lossy(value: &str) -> Self {
|
||||
match value.to_ascii_lowercase().as_str() {
|
||||
"inet" => Self::Inet,
|
||||
"inet6" => Self::Inet6,
|
||||
_ => Self::Other,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Inet => "inet",
|
||||
Self::Inet6 => "inet6",
|
||||
Self::Other => "other",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for AddressFamily {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for AddressFamily {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value = String::deserialize(deserializer)?;
|
||||
Ok(Self::parse_lossy(&value))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
|
||||
pub struct InterfaceCidr {
|
||||
pub local: Option<IpAddr>,
|
||||
pub prefixlen: Option<u8>,
|
||||
}
|
||||
|
||||
impl InterfaceCidr {
|
||||
pub fn is_complete(&self) -> bool {
|
||||
self.local.is_some() && self.prefixlen.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl AddrInfo {
|
||||
pub fn local_addr(&self) -> Option<IpAddr> {
|
||||
self.cidr.local
|
||||
}
|
||||
|
||||
pub fn prefixlen(&self) -> Option<u8> {
|
||||
self.cidr.prefixlen
|
||||
}
|
||||
|
||||
pub fn is_ipv4(&self) -> bool {
|
||||
matches!(self.family, Some(AddressFamily::Inet))
|
||||
}
|
||||
|
||||
pub fn is_ipv6(&self) -> bool {
|
||||
matches!(self.family, Some(AddressFamily::Inet6))
|
||||
}
|
||||
}
|
||||
|
||||
impl AddrOutput {
|
||||
pub fn ipv4_addrs(&self) -> impl Iterator<Item = &AddrInfo> {
|
||||
self.addr_info.iter().filter(|info| info.is_ipv4())
|
||||
}
|
||||
|
||||
pub fn ipv6_addrs(&self) -> impl Iterator<Item = &AddrInfo> {
|
||||
self.addr_info.iter().filter(|info| info.is_ipv6())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get(dev: Option<&str>) -> anyhow::Result<Vec<AddrOutput>> {
|
||||
get_with_backend(Backend::Json, dev).await
|
||||
}
|
||||
@@ -57,3 +144,45 @@ pub async fn get_with_backend(
|
||||
Backend::Netlink => nl::get(dev).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
|
||||
use super::{AddrInfo, AddrOutput, AddressFamily};
|
||||
use crate::subcommands::link::OperState;
|
||||
|
||||
#[test]
|
||||
fn address_family_parses_known_values() {
|
||||
assert_eq!(AddressFamily::parse_lossy("inet"), AddressFamily::Inet);
|
||||
assert_eq!(AddressFamily::parse_lossy("INET6"), AddressFamily::Inet6);
|
||||
assert_eq!(AddressFamily::parse_lossy("weird"), AddressFamily::Other);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn addr_info_deserializes_typed_ip_fields() {
|
||||
let info: AddrInfo = serde_json::from_str(
|
||||
r#"{"family":"inet","local":"192.168.1.1","prefixlen":24,"broadcast":"192.168.1.255","scope":"global"}"#,
|
||||
)
|
||||
.expect("addr_info json should deserialize");
|
||||
|
||||
assert_eq!(info.family, Some(AddressFamily::Inet));
|
||||
assert_eq!(
|
||||
info.local_addr(),
|
||||
Some(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)))
|
||||
);
|
||||
assert_eq!(info.prefixlen(), Some(24));
|
||||
assert_eq!(info.broadcast, Some(Ipv4Addr::new(192, 168, 1, 255)));
|
||||
assert!(info.cidr.is_complete());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn addr_output_deserializes_typed_operstate() {
|
||||
let output: AddrOutput = serde_json::from_str(
|
||||
r#"{"ifindex":2,"ifname":"br-lan","operstate":"UP","address":"aa:bb:cc:dd:ee:ff","addr_info":[]}"#,
|
||||
)
|
||||
.expect("addr_output json should deserialize");
|
||||
|
||||
assert_eq!(output.operstate, OperState::Up);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,109 @@
|
||||
//! i said i aint doing ts no more why am i still here
|
||||
#![cfg(unix)]
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use futures::TryStreamExt;
|
||||
use rtnetlink::Handle;
|
||||
use rtnetlink::packet_route::{AddressFamily, address::AddressAttribute};
|
||||
|
||||
use crate::subcommands::address::AddrOutput;
|
||||
use crate::subcommands::{
|
||||
address::{AddrInfo, AddrOutput, AddressFamily as IpAddressFamily, InterfaceCidr},
|
||||
link,
|
||||
};
|
||||
|
||||
// shit this one is even worse you needa collect info from two places
|
||||
pub async fn get(dev: Option<&str>) -> anyhow::Result<Vec<AddrOutput>> {
|
||||
let (conn, handle, _) = rtnetlink::new_connection()?;
|
||||
tokio::spawn(conn); // every time?
|
||||
let mut address = handle.address().get();
|
||||
let mut link = handle.link().get();
|
||||
if let Some(dev) = dev {
|
||||
link = link.match_name(dev.to_owned());
|
||||
if let Some(ind) = link.execute().try_next().await?.map(|a| a.header.index) {
|
||||
address = address.set_link_index_filter(ind);
|
||||
};
|
||||
address.execute().try_next().await?;
|
||||
} else {
|
||||
};
|
||||
todo!();
|
||||
tokio::spawn(conn);
|
||||
|
||||
get_with_handle(&handle, dev).await
|
||||
}
|
||||
|
||||
pub async fn get_with_handle(
|
||||
handle: &Handle,
|
||||
dev: Option<&str>,
|
||||
) -> anyhow::Result<Vec<AddrOutput>> {
|
||||
let links = link::nl::get_with_handle(handle, dev).await?;
|
||||
let link_by_index: BTreeMap<u32, crate::subcommands::link::LinkOutput> =
|
||||
links.into_iter().map(|link| (link.ifindex, link)).collect();
|
||||
|
||||
let mut address = handle.address().get();
|
||||
if let Some(dev) = dev {
|
||||
if let Some(index) = link_by_index
|
||||
.values()
|
||||
.find(|link| link.ifname == dev)
|
||||
.map(|link| link.ifindex)
|
||||
{
|
||||
address = address.set_link_index_filter(index);
|
||||
} else {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
let mut stream = address.execute();
|
||||
let mut addr_info_by_index: BTreeMap<u32, Vec<AddrInfo>> = BTreeMap::new();
|
||||
|
||||
while let Some(msg) = stream.try_next().await? {
|
||||
if !matches!(
|
||||
msg.header.family,
|
||||
AddressFamily::Inet | AddressFamily::Inet6
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let family = match msg.header.family {
|
||||
AddressFamily::Inet => Some(IpAddressFamily::Inet),
|
||||
AddressFamily::Inet6 => Some(IpAddressFamily::Inet6),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let mut local = None;
|
||||
let mut prefixlen = Some(msg.header.prefix_len);
|
||||
let mut broadcast = None;
|
||||
let mut scope = Some(format!("{:?}", msg.header.scope).to_lowercase());
|
||||
let mut label = None;
|
||||
|
||||
for attr in msg.attributes {
|
||||
match attr {
|
||||
AddressAttribute::Address(addr) | AddressAttribute::Local(addr) => {
|
||||
if local.is_none() {
|
||||
local = addr.to_string().parse().ok();
|
||||
}
|
||||
}
|
||||
AddressAttribute::Broadcast(addr) => {
|
||||
broadcast = addr.to_string().parse().ok();
|
||||
}
|
||||
AddressAttribute::Label(name) => {
|
||||
label = Some(name);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
addr_info_by_index
|
||||
.entry(msg.header.index)
|
||||
.or_default()
|
||||
.push(AddrInfo {
|
||||
family,
|
||||
cidr: InterfaceCidr {
|
||||
local,
|
||||
prefixlen: prefixlen.take(),
|
||||
},
|
||||
broadcast,
|
||||
scope: scope.take(),
|
||||
label,
|
||||
});
|
||||
}
|
||||
|
||||
let out = link_by_index
|
||||
.into_values()
|
||||
.map(|link| AddrOutput {
|
||||
ifindex: link.ifindex,
|
||||
ifname: link.ifname,
|
||||
operstate: link.operstate.unwrap_or(link::OperState::Unknown),
|
||||
address: link.address,
|
||||
addr_info: addr_info_by_index.remove(&link.ifindex).unwrap_or_default(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
Reference in new issue
Block a user