This commit is contained in:
lda
2025-08-25 01:08:59 +07:00 Unverified
parent a3c91ae288
commit ecbb1c653c
5 changed files with 78 additions and 46 deletions
+3 -3
View File
@@ -123,7 +123,7 @@ impl NUDState {
}
}
/// dumb boolean: Some(true)=on, Some(false)=off, None=shrug
pub fn dumber_state_this_way(&self) -> Option<bool> {
pub fn _dumber_state_this_way(&self) -> Option<bool> {
match self {
NUDState::Permanent | NUDState::Reachable => Some(true),
NUDState::Failed => Some(false),
@@ -199,7 +199,7 @@ impl IpNeighLine {
..self_
}
}
pub fn with_mac(mac: MacAddr) -> impl FnMut(Self) -> Self {
pub fn _with_mac(mac: MacAddr) -> impl FnMut(Self) -> Self {
move |self_| Self {
mac: Some(mac),
..self_
@@ -235,7 +235,7 @@ impl Ord for NUDState {
impl IpNeighLine {
// score for “local and online”: state, has-mac, v4, iface preference
pub fn score(&self) -> (u8, u8, u8, u8) {
pub fn _score(&self) -> (u8, u8, u8, u8) {
let iface = self
.dev
.as_deref()
+3 -3
View File
@@ -115,11 +115,11 @@ async fn main() -> color_eyre::Result<()> {
#[cfg(not(target_os = "linux"))]
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
use std::net::ToSocketAddrs;
// use crate::arpparse::NUDState;
// println!("{}", NUDState::Reachable.to_string().to_lowercase());
// println!("{:?}", std::net::TcpStream::connect("svuhuvshdv:331"));
// // Err(Os { code: 11001, kind: Uncategorized, message: "No such host is known." })
println!("{:?}", "svuhuvshdv:331".to_socket_addrs());
// Err(Os { code: 11001, kind: Uncategorized, message: "No such host is known." })
Err(color_eyre::eyre::eyre!(
"OS not supported! run this on your ahh router!"
))
+6 -7
View File
@@ -11,23 +11,22 @@ pub mod wake;
use std::{net::IpAddr, sync::LazyLock};
use macaddr::MacAddr;
use tokio::io;
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) -> io::Result<Vec<IpAddr>> {
Ok(tokio::net::lookup_host((machine_name, 0))
.await?
.map(|c| c.ip())
.collect())
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;
mod error;
// no custom ip deserializer needed when using axum_extra::extract::Query
// but we add a generic one to ignore blanks and accept OneOrMany
+43 -12
View File
@@ -1,17 +1,48 @@
use std::io;
use std::{fmt, io};
enum cuh<T> {
DNSError(io::Error),
ToolUseError,
Other(T),
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
/// Failure to resolve a host name to IPs.
DnsResolve { name: String, source: io::Error },
/// External command failed (e.g., ip neigh)
CommandFailed {
cmd: &'static str,
args: Vec<String>,
status: Option<i32>,
stderr: String,
},
/// Generic IO error fallback
Io(io::Error),
}
impl<T> From<io::Error> for cuh<T> {
fn from(value: io::Error) -> Self {
todo!()
// if matches!(value.kind(), ) {
// }
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::DnsResolve { name, source } => write!(f, "DNS resolve failed for {name}: {source}"),
Error::CommandFailed { cmd, args, status, stderr } => {
let code = status.map(|c| c.to_string()).unwrap_or_else(|| "signal".into());
write!(
f,
"{cmd} {args:?} failed (status: {code}): {stderr}",
)
}
Error::Io(e) => write!(f, "IO error: {e}"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::DnsResolve { source, .. } => Some(source),
Error::Io(e) => Some(e),
_ => None,
}
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self { Error::Io(e) }
}
+22 -20
View File
@@ -1,14 +1,14 @@
use std::{collections::HashSet, net::IpAddr};
use macaddr::MacAddr;
use tokio::io;
// use tokio::io;
use crate::{
arpparse::{self, IpNeighLine, NUDState},
utils::{cmd::exec_command, get_ips},
utils::{cmd::exec_command, error::{self, Error, Result}, get_ips},
};
pub async fn get_macs_2_1(machine_name: &str) -> io::Result<HashSet<(IpAddr, MacAddr, NUDState)>> {
pub async fn get_macs_2_1(machine_name: &str) -> Result<HashSet<(IpAddr, MacAddr, NUDState)>> {
Ok(get_macs_1(machine_name)
.await?
.into_iter()
@@ -22,7 +22,7 @@ pub async fn get_macs_2_1(machine_name: &str) -> io::Result<HashSet<(IpAddr, Mac
)
.collect())
}
pub async fn get_macs_2_mac(machine_name: &str) -> io::Result<HashSet<MacAddr>> {
pub async fn get_macs_2_mac(machine_name: &str) -> Result<HashSet<MacAddr>> {
Ok(get_macs_1(machine_name)
.await?
.into_iter()
@@ -37,20 +37,22 @@ pub async fn get_macs_2_mac(machine_name: &str) -> io::Result<HashSet<MacAddr>>
.collect())
}
pub async fn get_macs_1(machine_name: &str) -> io::Result<Vec<arpparse::IpNeighLine>> {
pub async fn get_macs_1(machine_name: &str) -> Result<Vec<arpparse::IpNeighLine>> {
let dev = "br-lan";
let ips = get_ips(machine_name).await?;
let futures = ips.iter().map(|ip| {
let ip = ip.to_canonical();
async move {
let o =
exec_command("ip", ["neigh", "show", "to", &ip.to_string(), "dev", dev]).await?;
let cmd = "ip";
let args = ["neigh", "show", "to", &ip.to_string(), "dev", dev];
let o = exec_command(cmd, args).await?;
if !o.status.success() {
return Err(io::Error::other(format!(
"`ip neigh` failed for {ip} (status: {st}): {err}",
st = o.status,
err = String::from_utf8_lossy(&o.stderr),
)));
return Err(Error::CommandFailed {
cmd,
args: args.iter().map(ToString::to_string).collect(),
status: o.status.code(),
stderr: String::from_utf8_lossy(&o.stderr).into(),
});
};
Ok(String::from_utf8_lossy(&o.stdout)
.lines()
@@ -72,7 +74,7 @@ pub async fn get_macs(
ips: Option<&[IpAddr]>,
dev: Option<&str>,
state: Option<NUDState>,
) -> io::Result<Vec<IpNeighLine>> {
) -> Result<Vec<IpNeighLine>> {
// Collect IPs early (before any await) to avoid holding generics across await points
let ip_list: Option<Vec<IpAddr>> = ips.map(|slice| {
slice
@@ -110,12 +112,12 @@ pub async fn get_macs(
let o = exec_command("ip", args.iter().map(String::as_str).collect::<Vec<_>>()).await?; // hope to rustc that it knows how to unfuck ts
if !o.status.success() {
return Err(io::Error::other(format!(
"`ip neigh` failed{ctx} (status: {st}): {err}",
ctx = to_ip.map(|ip| format!(" for {ip}")).unwrap_or_default(),
st = o.status,
err = String::from_utf8_lossy(&o.stderr),
)));
return Err(Error::CommandFailed {
cmd: "ip",
args,
status: o.status.code(),
stderr: String::from_utf8_lossy(&o.stderr).into(),
});
}
let lines = String::from_utf8_lossy(&o.stdout);
@@ -126,7 +128,7 @@ pub async fn get_macs(
} else {
parsed.collect()
};
Ok::<Vec<IpNeighLine>, io::Error>(rows)
Ok::<Vec<IpNeighLine>, error::Error>(rows)
};
// If we have specific IPs, query each; otherwise query the whole table once