more bullshit IN!
This commit is contained in:
1 parent
7371d2ae8c
commit
9de286f59b
22 files changed
+845
-122
No files matched your search
+1
-17
@@ -10,8 +10,8 @@
|
||||
|
||||
use std::{net::IpAddr, str::FromStr};
|
||||
|
||||
use r#impl::ser_opm;
|
||||
use macaddr::MacAddr;
|
||||
use serde::{Deserialize, Serialize, Serializer, de};
|
||||
use serde_with::skip_serializing_none;
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
@@ -39,22 +39,6 @@ pub struct IpNeighLine {
|
||||
pub state: NUDState,
|
||||
}
|
||||
|
||||
/// serialize an [`Option<MacAddr>`]
|
||||
pub fn ser_opm<S: Serializer>(bro: &Option<MacAddr>, ser: S) -> Result<S::Ok, S::Error> {
|
||||
Option::<String>::serialize(&bro.as_ref().map(ToString::to_string), ser)
|
||||
}
|
||||
|
||||
/// deserialize an [`Option<MacAddr>`]
|
||||
pub fn _des_opm<'de, D>(des: D) -> Result<Option<MacAddr>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
Option::<&str>::deserialize(des)?
|
||||
.map(str::parse)
|
||||
.transpose()
|
||||
.map_err(de::Error::custom)
|
||||
}
|
||||
|
||||
// NUDState custom Deserialize now lives in arpparse/impl.rs; use serde_with OneOrMany for Vec
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, EnumString, Display, Clone, Copy, Hash, serde::Serialize)]
|
||||
|
||||
+20
-1
@@ -1,4 +1,7 @@
|
||||
use serde::{Deserialize, Deserializer, de};
|
||||
//! r#impl AHHHHHH
|
||||
|
||||
use macaddr::MacAddr;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
|
||||
|
||||
use super::NUDState;
|
||||
|
||||
@@ -12,3 +15,19 @@ impl<'de> Deserialize<'de> for NUDState {
|
||||
s.parse().map_err(de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
/// serialize an [`Option<MacAddr>`]
|
||||
pub fn ser_opm<S: Serializer>(bro: &Option<MacAddr>, ser: S) -> Result<S::Ok, S::Error> {
|
||||
Option::<String>::serialize(&bro.as_ref().map(ToString::to_string), ser)
|
||||
}
|
||||
|
||||
/// deserialize an [`Option<MacAddr>`]
|
||||
pub fn _des_opm<'de, D>(des: D) -> Result<Option<MacAddr>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
Option::<&str>::deserialize(des)?
|
||||
.map(str::parse)
|
||||
.transpose()
|
||||
.map_err(de::Error::custom)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub const HOME_2: &str = include_str!("../static/home_2.html");
|
||||
pub const HOME_2_CSS: &str = include_str!("../static/assets/home_2.css");
|
||||
pub const HOME_2_JS: &str = include_str!("../static/assets/home_2.js");
|
||||
@@ -1,3 +1,45 @@
|
||||
/// MAC->name cache location (ephemeral)
|
||||
const MAC_NAME_CACHE: &str = "/tmp/wakey_mac_names.json";
|
||||
|
||||
/// Load MAC->name cache from disk
|
||||
async fn load_mac_name_cache() -> io::Result<std::collections::BTreeMap<String, String>> {
|
||||
match tokio::fs::read_to_string(MAC_NAME_CACHE).await {
|
||||
Ok(s) => serde_json::from_str(&s).map_err(io::Error::other),
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => Ok(Default::default()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Save MAC->name cache to disk
|
||||
async fn save_mac_name_cache(map: &std::collections::BTreeMap<String, String>) -> io::Result<()> {
|
||||
let s = serde_json::to_string(map).map_err(io::Error::other)?;
|
||||
let _ = tokio::fs::write(MAC_NAME_CACHE, s).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read all leases, filling names from MAC->name cache if missing
|
||||
pub async fn read_dhcp_leases_with_names() -> io::Result<Vec<DhcpLeaseLine>> {
|
||||
let leases = read_dhcp_leases().await?;
|
||||
let mut cache = load_mac_name_cache().await.unwrap_or_default();
|
||||
let mut changed = false;
|
||||
let mut leases_with_names = Vec::with_capacity(leases.len());
|
||||
for mut l in leases {
|
||||
let mac_s = l.mac.to_string();
|
||||
if let Some(ref name) = l.name {
|
||||
if cache.get(&mac_s).map(|v| v != name).unwrap_or(true) {
|
||||
cache.insert(mac_s, name.clone());
|
||||
changed = true;
|
||||
}
|
||||
} else if let Some(prev) = cache.get(&mac_s) {
|
||||
l.name = Some(prev.clone());
|
||||
}
|
||||
leases_with_names.push(l);
|
||||
}
|
||||
if changed {
|
||||
let _ = save_mac_name_cache(&cache).await;
|
||||
}
|
||||
Ok(leases_with_names)
|
||||
}
|
||||
use macaddr::MacAddr;
|
||||
use serde::Serializer;
|
||||
use std::io::{self, ErrorKind};
|
||||
|
||||
+1
-1
@@ -4,9 +4,9 @@ use axum::{
|
||||
};
|
||||
use tokio::net::TcpListener;
|
||||
mod arpparse;
|
||||
pub mod assets;
|
||||
mod dhcpparse;
|
||||
mod route;
|
||||
pub mod r#static;
|
||||
mod utils;
|
||||
use std::io;
|
||||
|
||||
|
||||
+4
-4
@@ -2,7 +2,7 @@ pub mod api;
|
||||
|
||||
pub use crate::route::api::{DeviceQuery, api_router};
|
||||
use crate::{
|
||||
r#static as st,
|
||||
assets,
|
||||
utils::{ping::_ping_ip, wake::wake},
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ use axum_extra::extract::Query;
|
||||
use crate::{MACHINE_NAME, utils::_status_build};
|
||||
|
||||
pub async fn home_2() -> Html<&'static str> {
|
||||
Html(st::HOME_2)
|
||||
Html(assets::HOME_2)
|
||||
}
|
||||
async fn home_2_css() -> impl IntoResponse {
|
||||
(
|
||||
@@ -25,7 +25,7 @@ async fn home_2_css() -> impl IntoResponse {
|
||||
(header::CONTENT_TYPE, "text/css; charset=utf-8"),
|
||||
(header::CACHE_CONTROL, "public, max-age=300"),
|
||||
],
|
||||
st::HOME_2_CSS,
|
||||
assets::HOME_2_CSS,
|
||||
)
|
||||
}
|
||||
async fn home_2_js() -> impl IntoResponse {
|
||||
@@ -34,7 +34,7 @@ async fn home_2_js() -> impl IntoResponse {
|
||||
(header::CONTENT_TYPE, "application/javascript"),
|
||||
(header::CACHE_CONTROL, "public, max-age=300"),
|
||||
],
|
||||
st::HOME_2_JS,
|
||||
assets::HOME_2_JS,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+49
-15
@@ -1,4 +1,4 @@
|
||||
use crate::utils::de_many;
|
||||
use crate::utils::parse::{boolish_str, de_many, serialize_macs};
|
||||
use std::collections::HashSet;
|
||||
use std::net::IpAddr;
|
||||
|
||||
@@ -11,7 +11,6 @@ use axum::{
|
||||
};
|
||||
use axum_extra::extract::Query;
|
||||
use macaddr::MacAddr;
|
||||
use serde::ser::Serializer;
|
||||
use serde_with::skip_serializing_none;
|
||||
|
||||
use crate::{
|
||||
@@ -25,9 +24,18 @@ use crate::{
|
||||
|
||||
// Smart redirect: accept IP, MAC, dev, or NUD state and redirect to /api/status accordingly
|
||||
pub async fn status_smart_redirect(Path(q): Path<String>) -> Redirect {
|
||||
let s = q.trim();
|
||||
let s = if cfg!(feature = "very-smart-parsing") {
|
||||
crate::utils::parse::extract_host(&q)
|
||||
} else {
|
||||
q.trim()
|
||||
};
|
||||
// 1) IP
|
||||
if let Ok(ip) = s.parse::<IpAddr>() {
|
||||
let ip = if cfg!(feature = "very-smart-parsing") {
|
||||
crate::utils::parse::parse_numeric_ipv4(s).or_else(|| s.parse::<IpAddr>().ok())
|
||||
} else {
|
||||
s.parse::<IpAddr>().ok()
|
||||
};
|
||||
if let Some(ip) = ip {
|
||||
return Redirect::to(&format!("/api/status?ip={ip}"));
|
||||
}
|
||||
// 2) MAC
|
||||
@@ -46,7 +54,7 @@ pub async fn status_smart_redirect(Path(q): Path<String>) -> Redirect {
|
||||
if tokio::net::lookup_host((s, 0)).await.is_ok() {
|
||||
return Redirect::to(&format!("/api/status?name={}", urlencoding::encode(s)));
|
||||
}
|
||||
// Default: name last
|
||||
// Default: name last // it will fail also
|
||||
Redirect::to(&format!("/api/status?name={}", urlencoding::encode(s)))
|
||||
}
|
||||
|
||||
@@ -54,9 +62,42 @@ pub async fn devs_router() -> Json<Vec<String>> {
|
||||
dev::devs_sorted().into()
|
||||
}
|
||||
|
||||
async fn get_dhcp_leases() -> impl IntoResponse {
|
||||
match dhcpparse::read_dhcp_leases().await {
|
||||
Ok(leases) => (StatusCode::OK, Json(leases)).into_response(),
|
||||
#[derive(Debug, Default, Clone, serde::Deserialize)]
|
||||
struct DhcpLeasesQueryRaw {
|
||||
include_state: Option<String>,
|
||||
}
|
||||
|
||||
// #[skip_serializing_none]
|
||||
// #[derive(Debug, Clone, serde::Serialize)]
|
||||
// struct DhcpLeaseOut {
|
||||
// expires_epoch: u64,
|
||||
// ip: IpAddr,
|
||||
// #[serde(serialize_with = "serialize_mac")]
|
||||
// mac: macaddr::MacAddr,
|
||||
// // #[serde(skip_serializing_if = "Option::is_none")]
|
||||
// name: Option<String>,
|
||||
// // extras
|
||||
// // #[serde(skip_serializing_if = "Option::is_none")]
|
||||
// nud_state: Option<NUDState>,
|
||||
// // #[serde(skip_serializing_if = "Option::is_none")]
|
||||
// rank: Option<u8>,
|
||||
// }
|
||||
|
||||
async fn get_dhcp_leases(Query(raw): Query<DhcpLeasesQueryRaw>) -> impl IntoResponse {
|
||||
let include_state = raw
|
||||
.include_state
|
||||
.as_deref()
|
||||
.map(boolish_str)
|
||||
.unwrap_or(false);
|
||||
|
||||
match dhcpparse::read_dhcp_leases_with_names().await {
|
||||
Ok(leases_with_names) => {
|
||||
if !include_state {
|
||||
return (StatusCode::OK, Json(leases_with_names)).into_response();
|
||||
}
|
||||
let out = crate::utils::query::enrich_leases_with_nud_state(leases_with_names).await;
|
||||
(StatusCode::OK, Json(out)).into_response()
|
||||
}
|
||||
Err(e) => (
|
||||
StatusCode::BAD_GATEWAY,
|
||||
Json(StatusError {
|
||||
@@ -110,13 +151,6 @@ pub struct Filters {
|
||||
mac: Vec<MacAddr>,
|
||||
}
|
||||
|
||||
fn serialize_macs<S>(macs: &[MacAddr], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let strings: Vec<String> = macs.iter().map(|m| m.to_string()).collect();
|
||||
serde::Serialize::serialize(&strings, serializer)
|
||||
}
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct StatusError {
|
||||
|
||||
+1
-52
@@ -8,71 +8,20 @@
|
||||
/// is it time to lookup host lda.lan for this...
|
||||
// pub static LDA_MACS_2: LazyLock<[MacAddr; 2]> = LazyLock::new(|| LDA_MACS.map(MacAddr::from));
|
||||
pub mod wake;
|
||||
use std::net::IpAddr;
|
||||
|
||||
use crate::utils::query::_get_macs_2_1;
|
||||
|
||||
pub mod error;
|
||||
|
||||
/// generic so you can do "123.45.67.89:22" or "lda.lan:22" as an input
|
||||
// this is so bad
|
||||
pub mod ping;
|
||||
|
||||
/// this is because i like [`IpAddr`] more than [`SocketAddr`](std::net::SocketAddr)
|
||||
pub async fn get_ips(machine_name: &str) -> error::Result<Vec<IpAddr>> {
|
||||
let it = tokio::net::lookup_host((machine_name, 0))
|
||||
.await
|
||||
.map_err(|e| error::Error::DnsResolve {
|
||||
name: machine_name.to_string(),
|
||||
source: e,
|
||||
})?;
|
||||
Ok(it.map(|c| c.ip()).collect())
|
||||
}
|
||||
|
||||
pub mod cmd;
|
||||
pub mod query;
|
||||
|
||||
// no custom ip deserializer needed when using axum_extra::extract::Query
|
||||
// but we add a generic one to ignore blanks and accept OneOrMany
|
||||
pub mod de_many {
|
||||
use serde::Deserialize;
|
||||
use serde::de;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum OneOrMany<T> {
|
||||
One(T),
|
||||
Many(Vec<T>),
|
||||
}
|
||||
|
||||
pub fn vec_from_strs<'de, D, T>(des: D) -> Result<Vec<T>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
T: std::str::FromStr,
|
||||
T::Err: std::fmt::Display,
|
||||
{
|
||||
let raw: OneOrMany<String> = OneOrMany::<String>::deserialize(des)?;
|
||||
let mut out = Vec::new();
|
||||
match raw {
|
||||
OneOrMany::One(s) => {
|
||||
let t = s.trim();
|
||||
if !t.is_empty() {
|
||||
out.push(t.parse().map_err(de::Error::custom)?);
|
||||
}
|
||||
}
|
||||
OneOrMany::Many(vs) => {
|
||||
for s in vs {
|
||||
let t = s.trim();
|
||||
if t.is_empty() {
|
||||
continue;
|
||||
}
|
||||
out.push(t.parse().map_err(de::Error::custom)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
pub(crate) mod parse;
|
||||
|
||||
pub async fn _status_build(machine_name: &str) -> String {
|
||||
let formatted_macs = match _get_macs_2_1(machine_name).await {
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/// Parses Chrome-style numeric IPv4 forms: hex (0x...), decimal, octal.
|
||||
pub fn parse_numeric_ipv4(s: &str) -> Option<std::net::IpAddr> {
|
||||
let s = s.trim();
|
||||
// hex
|
||||
if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X"))
|
||||
&& hex.chars().all(|c| c.is_ascii_hexdigit())
|
||||
&& let Ok(n) = u32::from_str_radix(hex, 16)
|
||||
{
|
||||
return Some(std::net::IpAddr::V4(std::net::Ipv4Addr::from(n)));
|
||||
}
|
||||
// decimal
|
||||
if s.chars().all(|c| c.is_ascii_digit())
|
||||
&& let Ok(n) = s.parse::<u32>()
|
||||
{
|
||||
return Some(std::net::IpAddr::V4(std::net::Ipv4Addr::from(n)));
|
||||
}
|
||||
// octal (leading 0, all octal digits)
|
||||
if s.len() > 1
|
||||
&& s.as_bytes()[0] == b'0'
|
||||
&& s.chars().all(|c| matches!(c, '0'..='7'))
|
||||
&& let Ok(n) = u32::from_str_radix(s, 8)
|
||||
{
|
||||
return Some(std::net::IpAddr::V4(std::net::Ipv4Addr::from(n)));
|
||||
}
|
||||
None
|
||||
}
|
||||
/// Extracts the host portion from a URL-like string, for smart input parsing.
|
||||
pub fn extract_host(input: &str) -> &str {
|
||||
let mut s = input.trim();
|
||||
// Strip scheme (e.g., http://, https://, ssh://) or network-path reference (//host)
|
||||
if let Some(idx) = s.find("://") {
|
||||
s = &s[idx + 3..];
|
||||
} else if let Some(rest) = s.strip_prefix("//") {
|
||||
s = rest;
|
||||
}
|
||||
// Strip potential userinfo (user@host)
|
||||
if let Some((_, host)) = s.rsplit_once('@') {
|
||||
s = host;
|
||||
}
|
||||
// If bracketed IPv6 like [::1]:8080/path -> extract inside brackets
|
||||
if let Some(host) = s.strip_prefix('[') {
|
||||
if let Some(end) = host.find(']') {
|
||||
s = &host[..end];
|
||||
}
|
||||
} else {
|
||||
// Trim path suffix if any
|
||||
if let Some(pos) = s.find('/') {
|
||||
s = &s[..pos];
|
||||
}
|
||||
// Drop trailing :port if present and numeric, but only if there's exactly one ':'
|
||||
if let Some((host, port)) = s.rsplit_once(':')
|
||||
&& s.matches(':').count() == 1
|
||||
&& port.chars().all(|c| c.is_ascii_digit())
|
||||
{
|
||||
s = host;
|
||||
}
|
||||
}
|
||||
s.trim()
|
||||
}
|
||||
|
||||
use macaddr::MacAddr;
|
||||
use serde::Serializer;
|
||||
/// key for yes: "1" | "true" | "yes" | "on" | "y"
|
||||
///
|
||||
/// frfr
|
||||
pub fn _de_boolish<'de, D>(des: D) -> Result<bool, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::Deserialize;
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum Boolish {
|
||||
B(bool),
|
||||
I(u8),
|
||||
S(String),
|
||||
}
|
||||
Ok(match Boolish::deserialize(des)? {
|
||||
Boolish::B(b) => b,
|
||||
Boolish::I(i) => i != 0,
|
||||
Boolish::S(s) => {
|
||||
let t = s.trim().to_ascii_lowercase();
|
||||
if t.is_empty() {
|
||||
true // presence implies true
|
||||
} else {
|
||||
matches!(t.as_str(), "1" | "true" | "yes" | "on" | "y")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a tolerant boolean value from a string.
|
||||
/// Accepts: "1", "true", "yes", "on", "y" as true; "0", "false", "no", "off", "n" as false.
|
||||
/// Empty string means true (presence-only query flag).
|
||||
pub fn boolish_str(s: &str) -> bool {
|
||||
let t = s.trim().to_ascii_lowercase();
|
||||
if t.is_empty() {
|
||||
return true;
|
||||
}
|
||||
matches!(t.as_str(), "1" | "true" | "yes" | "on" | "y")
|
||||
|| (!matches!(t.as_str(), "0" | "false" | "no" | "off" | "n")
|
||||
&& t.parse::<u64>().map(|n| n != 0).unwrap_or(false))
|
||||
}
|
||||
|
||||
pub fn serialize_macs<S>(macs: &[MacAddr], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let strings: Vec<String> = macs.iter().map(|m| m.to_string()).collect();
|
||||
serde::Serialize::serialize(&strings, serializer)
|
||||
}
|
||||
|
||||
pub fn serialize_mac<S: serde::Serializer>(m: &macaddr::MacAddr, s: S) -> Result<S::Ok, S::Error> {
|
||||
s.serialize_str(&m.to_string())
|
||||
}
|
||||
|
||||
pub mod de_many {
|
||||
use serde::Deserialize;
|
||||
use serde::de;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum OneOrMany<T> {
|
||||
One(T),
|
||||
Many(Vec<T>),
|
||||
}
|
||||
|
||||
pub fn vec_from_strs<'de, D, T>(des: D) -> Result<Vec<T>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
T: std::str::FromStr,
|
||||
T::Err: std::fmt::Display,
|
||||
{
|
||||
let raw: OneOrMany<String> = OneOrMany::<String>::deserialize(des)?;
|
||||
let mut out = Vec::new();
|
||||
match raw {
|
||||
OneOrMany::One(s) => {
|
||||
let t = s.trim();
|
||||
if !t.is_empty() {
|
||||
out.push(t.parse().map_err(de::Error::custom)?);
|
||||
}
|
||||
}
|
||||
OneOrMany::Many(vs) => {
|
||||
for s in vs {
|
||||
let t = s.trim();
|
||||
if t.is_empty() {
|
||||
continue;
|
||||
}
|
||||
out.push(t.parse().map_err(de::Error::custom)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
+15
-1
@@ -1,12 +1,14 @@
|
||||
// this ENTIRE file is redundant... or?
|
||||
|
||||
use std::time::Duration;
|
||||
use std::{net::IpAddr, time::Duration};
|
||||
|
||||
use tokio::{
|
||||
net::{TcpStream, ToSocketAddrs},
|
||||
time::timeout,
|
||||
};
|
||||
|
||||
use crate::{arpparse::NUDState, utils::query::get_macs};
|
||||
|
||||
pub async fn _ping_ip<T: ToSocketAddrs>(addr: T) -> bool {
|
||||
timeout(Duration::from_secs(1), TcpStream::connect(addr))
|
||||
.await
|
||||
@@ -15,3 +17,15 @@ pub async fn _ping_ip<T: ToSocketAddrs>(addr: T) -> bool {
|
||||
pub async fn _ping_ip_2<T: ToSocketAddrs>(_addr: T) -> bool {
|
||||
todo!("use icmp")
|
||||
}
|
||||
|
||||
pub async fn _ping_ip_3<T: Into<IpAddr>>(addr: T) -> u8 {
|
||||
match get_macs(None, Some(&[addr.into()]), None, None).await {
|
||||
Err(_) => 0,
|
||||
Ok(l) => l
|
||||
.into_iter()
|
||||
.map(|e| e.state)
|
||||
.max()
|
||||
.map(NUDState::rank)
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
+66
-9
@@ -1,17 +1,76 @@
|
||||
use std::{collections::HashSet, net::IpAddr};
|
||||
use crate::arpparse::NUDState;
|
||||
use crate::dhcpparse::DhcpLeaseLine;
|
||||
use crate::utils::parse::serialize_mac;
|
||||
use std::net::IpAddr;
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct DhcpLeaseOut {
|
||||
pub expires_epoch: u64,
|
||||
pub ip: IpAddr,
|
||||
#[serde(serialize_with = "serialize_mac")]
|
||||
pub mac: macaddr::MacAddr,
|
||||
pub name: Option<String>,
|
||||
pub nud_state: Option<NUDState>,
|
||||
pub rank: Option<u8>,
|
||||
}
|
||||
|
||||
/// Enrich DHCP leases with NUD state and rank using get_macs
|
||||
pub async fn enrich_leases_with_nud_state(leases: Vec<DhcpLeaseLine>) -> Vec<DhcpLeaseOut> {
|
||||
use crate::utils::query::get_macs;
|
||||
let ips: Vec<IpAddr> = leases.iter().map(|l| l.ip).collect();
|
||||
let mut map: std::collections::HashMap<IpAddr, (NUDState, u8)> =
|
||||
std::collections::HashMap::new();
|
||||
if let Ok(rows) = get_macs(None, Some(&ips), None, None).await {
|
||||
for row in rows {
|
||||
let state = row.state;
|
||||
let r = state.rank();
|
||||
map.entry(row.ip)
|
||||
.and_modify(|e| {
|
||||
if r > e.1 {
|
||||
*e = (state, r)
|
||||
}
|
||||
})
|
||||
.or_insert((state, r));
|
||||
}
|
||||
}
|
||||
leases
|
||||
.into_iter()
|
||||
.map(|l| DhcpLeaseOut {
|
||||
expires_epoch: l.expires_epoch,
|
||||
ip: l.ip,
|
||||
mac: l.mac,
|
||||
name: l.name,
|
||||
nud_state: map.get(&l.ip).map(|(s, _)| *s),
|
||||
rank: map.get(&l.ip).map(|(_, r)| *r),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
use std::collections::HashSet;
|
||||
|
||||
use macaddr::MacAddr;
|
||||
use serde_with::skip_serializing_none;
|
||||
// use tokio::io;
|
||||
|
||||
use crate::{
|
||||
arpparse::{self, IpNeighLine, NUDState},
|
||||
arpparse::{self, IpNeighLine},
|
||||
utils::{
|
||||
cmd::exec_command,
|
||||
error::{self, Error, Result},
|
||||
get_ips,
|
||||
},
|
||||
};
|
||||
|
||||
/// this is because i like [`IpAddr`] more than [`SocketAddr`](std::net::SocketAddr)
|
||||
pub async fn get_ips(machine_name: &str) -> error::Result<Vec<IpAddr>> {
|
||||
let it = tokio::net::lookup_host((machine_name, 0))
|
||||
.await
|
||||
.map_err(|e| error::Error::DnsResolve {
|
||||
name: machine_name.to_string(),
|
||||
source: e,
|
||||
})?;
|
||||
Ok(it.map(|c| c.ip()).collect())
|
||||
}
|
||||
|
||||
pub async fn _get_macs_2_1(machine_name: &str) -> Result<HashSet<(IpAddr, MacAddr, NUDState)>> {
|
||||
Ok(get_macs_1(machine_name)
|
||||
.await?
|
||||
@@ -92,7 +151,7 @@ pub async fn get_macs(
|
||||
|
||||
// Helper to convert NUDState to the string expected by `ip neigh`
|
||||
let nud_arg = state.map(NUDState::as_ip_neigh_arg);
|
||||
// let nud_arg = Rc::new(state.map(|s| s.to_string().to_lowercase()));
|
||||
// let nud_arg = state.map(|s| s.to_string().to_lowercase());
|
||||
// Build a closure to run one `ip neigh` invocation and parse results
|
||||
let run_one = |to_ip: Option<IpAddr>| async move {
|
||||
let mut args: Vec<String> = vec!["neigh".into(), "show".into()];
|
||||
@@ -141,7 +200,7 @@ pub async fn get_macs(
|
||||
}
|
||||
|
||||
pub mod dev {
|
||||
use std::{collections::HashSet, sync::LazyLock};
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub fn get_dev() -> HashSet<String> {
|
||||
// Prefer /sys/class/net, fallback to /proc/net/dev; filter out loopback
|
||||
@@ -169,15 +228,13 @@ pub mod dev {
|
||||
devs
|
||||
}
|
||||
|
||||
pub static DEVS: LazyLock<HashSet<String>> = LazyLock::new(get_dev);
|
||||
|
||||
pub fn devs_sorted() -> Vec<String> {
|
||||
let mut v: Vec<String> = DEVS.iter().cloned().collect();
|
||||
let mut v: Vec<String> = get_dev().into_iter().collect();
|
||||
v.sort();
|
||||
v
|
||||
}
|
||||
|
||||
pub fn has_dev(name: &str) -> bool {
|
||||
DEVS.contains(name)
|
||||
get_dev().contains(name)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user