THE ABSOLUTE CRIME of not committing frequently

This commit is contained in:
lda committed 2025-10-20 22:33:23 +07:00
1 parent fabf67297e
commit 1e7db089d5
29 files changed
+538 -489

No files matched your search

+23
View File
@@ -0,0 +1,23 @@
// pub const LDA_MACS: [[u8; 6]; 2] = [
// // ether
// [0x04, 0x7c, 0x16, 0x79, 0x6d, 0xee],
// // wifi
// [0xbc, 0x09, 0x1b, 0xec, 0x65, 0xd0],
// ];
/// 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;
pub mod cmd;
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;
pub mod query;
pub mod query_parser;
pub mod route;
// no custom ip deserializer needed when using axum_extra::extract::Query
// but we add a generic one to ignore blanks and accept OneOrMany
pub(crate) mod parse;
+2 -2
View File
@@ -7,7 +7,7 @@ use tokio::{
time::timeout,
};
use crate::{arpparse::NUDState, utils::query::get_macs};
use crate::{arpparse::NUDState, utils::query::get_mac};
pub async fn _ping_ip<T: ToSocketAddrs>(addr: T) -> bool {
timeout(Duration::from_secs(1), TcpStream::connect(addr))
@@ -19,7 +19,7 @@ pub async fn _ping_ip_2<T: ToSocketAddrs>(_addr: T) -> bool {
}
pub async fn _ping_ip_3<T: Into<IpAddr>>(addr: T) -> u8 {
match get_macs(None, Some(&[addr.into()]), None, None).await {
match get_mac(Some(addr.into()), None, None).await {
Err(_) => 0,
Ok(l) => l
.into_iter()
+2 -2
View File
@@ -1,5 +1,6 @@
use crate::arpparse::NUDState;
use crate::dhcpparse::DhcpLeaseLine;
use crate::utils::query::get_macs;
use serde_with::skip_serializing_none;
use std::net::IpAddr;
@@ -14,11 +15,10 @@ pub struct DhcpLeaseOut {
/// 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::macs::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 {
if let Ok(rows) = get_macs(&[] as &[&str], &ips, &[] as &[&str], &[], &[]).await {
for row in rows {
let state = row.state;
let r = state.rank();
+99 -79
View File
@@ -1,13 +1,14 @@
use macaddr::MacAddr;
use crate::arpparse::{self, IpNeighLine, NUDState};
use crate::utils::{
cmd::exec_command,
error::{self, Error, Result},
error::{self, Result},
};
use macaddr::MacAddr;
use std::collections::HashSet;
use std::net::IpAddr;
pub async fn get_ips(machine_name: &str) -> error::Result<impl Iterator<Item = IpAddr>> {
pub async fn get_ips(machine_name: &str) -> Result<impl Iterator<Item = IpAddr>> {
Ok(tokio::net::lookup_host((machine_name, 0))
.await
.map_err(|e| error::Error::DnsResolve {
@@ -17,84 +18,103 @@ pub async fn get_ips(machine_name: &str) -> error::Result<impl Iterator<Item = I
.map(|c| c.ip()))
}
pub async fn _get_macs_2_1(machine_name: &str) -> Result<HashSet<(IpAddr, MacAddr, NUDState)>> {
Ok(get_macs_1(machine_name)
.await?
.into_iter()
.filter_map(
|IpNeighLine {
ip,
dev: _,
mac,
state,
}| mac.map(|mac| (ip, mac, state)),
)
.collect())
}
pub async fn get_macs_2_mac(machine_name: &str) -> Result<HashSet<MacAddr>> {
Ok(get_macs_1(machine_name)
.await?
.into_iter()
.filter_map(
|IpNeighLine {
ip: _,
dev: _,
mac,
state: _,
}| mac,
)
.collect())
}
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.map(|ip| {
let ip = ip.to_canonical();
async move {
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(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()
.flat_map(arpparse::parse_ip_neigh_line)
.collect::<Vec<_>>())
}
});
let res = futures::future::try_join_all(futures).await?;
Ok(res.into_iter().flatten().collect())
}
// #[deprecated(
// since = "0.1.5",
// note = "just call once everything with get mac
// and then filter it bro WHY DO YOU EVEN DO TS"
// )]
// good now
//
// Current logic: When filtering by exactly 1 dev/mac, exclude entries missing that field.
// This is because missing dev/mac usually means the entry is incomplete/transient.
//
// when there is only one MACs (getmac got some), the result will not have them fields.
// so there are three cases:
//
// 1. dont got nothing: take all of them (macset.is_empty())
// 2. exactly one: pre-filtered by ip, everything matches,
// devset.len() != 1 returns false, but then it works????
// OH THIS fuckass code i added it in the get_mac
// 3. devset.len() > 1. if none then absolutely not match,
// if some then check with the set; thats normal
pub async fn get_macs(
machine_name: Option<&str>,
ips: Option<&[IpAddr]>,
dev: Option<&str>,
state: Option<NUDState>,
machine_names: &[impl AsRef<str>],
ips: &[IpAddr],
devs: &[impl AsRef<str>],
state: &[NUDState],
macs: &[MacAddr],
) -> Result<Vec<IpNeighLine>> {
let ip_list: Option<Vec<IpAddr>> =
ips.map(|slice| slice.iter().map(|ip| ip.to_canonical()).collect());
let ip_list = match (ip_list, machine_name) {
(Some(list), _) => list,
(None, Some(name)) => get_ips(name).await?.collect(),
(None, None) => Vec::new(),
};
let run_one = |to_ip: Option<IpAddr>| get_mac(to_ip, dev, state);
if !ip_list.is_empty() {
let futures = ip_list.into_iter().map(|ip| run_one(Some(ip)));
let res = futures::future::try_join_all(futures).await?;
Ok(res.into_iter().flatten().collect())
let mut ip_set: HashSet<IpAddr> = ips.iter().map(|ip| ip.to_canonical()).collect();
let ip_m: HashSet<IpAddr> = futures::future::try_join_all(
machine_names
.iter()
.map(|c| async { get_ips(c.as_ref()).await }),
)
.await?
.into_iter()
.flatten()
.collect();
let ip_all = if ip_set.is_empty() && ip_m.is_empty() {
None
} else if ip_set.is_empty() {
Some(ip_m)
} else if ip_m.is_empty() {
Some(ip_set)
} else {
run_one(None).await
}
Some({
ip_set.retain(|c| ip_m.contains(c)); // inline AHHH
ip_set
})
};
let opt_dev = if devs.len() > 1 {
None
} else {
devs.iter().next().map(AsRef::as_ref)
};
let opt_state = if state.len() > 1 {
None
} else {
state.iter().next().copied()
};
let run_one = |to_ip: Option<IpAddr>| get_mac(to_ip, opt_dev, opt_state);
let mut ip_filtered = if let Some(something) = ip_all {
if something.len() == 1 {
run_one(something.into_iter().next()).await?
} else {
run_one(None)
.await?
.into_iter()
.filter(|c| something.contains(&c.ip))
.collect()
}
} else {
run_one(None).await?
};
// Apply additional filters if any were provided
if !devs.is_empty() || !macs.is_empty() || !state.is_empty() {
let devset: HashSet<_> = devs.iter().map(AsRef::as_ref).collect();
let nudset: HashSet<_> = state.iter().collect();
let macset: HashSet<_> = macs.iter().collect();
ip_filtered.retain(|entry| {
// Dev filter: if we're filtering by dev, entry must have a dev AND it must be in the set
let dev_ok =
devset.is_empty() || entry.dev.as_deref().is_some_and(|d| devset.contains(d));
// NUD filter: always present, simple check
let nud_ok = nudset.is_empty() || nudset.contains(&entry.state);
// MAC filter: if we're filtering by MAC, entry must have a MAC AND it must be in the set
let mac_ok = macset.is_empty() || entry.mac.is_some_and(|m| macset.contains(&m));
dev_ok && nud_ok && mac_ok
})
};
Ok(ip_filtered)
}
/// the atomic get_macs. handle ONE thing only.
@@ -102,7 +122,7 @@ pub async fn get_mac(
ip: Option<IpAddr>,
dev: Option<&str>,
state: Option<NUDState>,
) -> error::Result<Vec<IpNeighLine>> {
) -> Result<Vec<IpNeighLine>> {
let mut args: Vec<String> = vec!["neigh".into(), "show".into()];
if let Some(ip) = ip {
args.push("to".into());
File renamed without changes.
+44
View File
@@ -0,0 +1,44 @@
use std::net::IpAddr;
use macaddr::MacAddr;
use crate::{arpparse::NUDState, utils::query::dev::has_dev};
pub enum QueryType {
Ip(IpAddr),
Mac(MacAddr),
Dev(String),
Nud(NUDState),
Unknown(String),
}
pub fn parse_query(q: String) -> QueryType {
let s = if cfg!(feature = "very-smart-parsing") {
crate::utils::parse::extract_host(&q)
} else {
q.trim()
};
// 1) IP
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 QueryType::Ip(ip);
}
// 2) MAC
if let Ok(mac) = s.parse::<MacAddr>() {
return QueryType::Mac(mac);
}
// 3) NUD state (reachable, stale, ...)
if let Ok(state) = s.parse::<NUDState>() {
return QueryType::Nud(state);
}
// 4) Known device? prefer dev first
if has_dev(s) {
return QueryType::Dev(s.to_string());
}
// Default: name last // it will fail also
QueryType::Unknown(s.to_string())
}
-61
View File
@@ -1,9 +1,4 @@
use std::net::IpAddr;
use axum::{http::header, response::IntoResponse};
use macaddr::MacAddr;
use crate::{arpparse::NUDState, route::DeviceQuery, utils::query::dev::has_dev};
pub async fn serve_js(content: &'static str) -> impl IntoResponse {
(
@@ -14,59 +9,3 @@ pub async fn serve_js(content: &'static str) -> impl IntoResponse {
content,
)
}
pub async fn status_smart_redirect(q: String) -> DeviceQuery {
let s = if cfg!(feature = "very-smart-parsing") {
crate::utils::parse::extract_host(&q)
} else {
q.trim()
};
// 1) IP
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 {
let ip = vec![ip];
return DeviceQuery {
ip,
..Default::default()
};
}
// 2) MAC
if let Ok(mac) = s.parse::<MacAddr>() {
let mac = vec![mac];
return DeviceQuery {
mac,
..Default::default()
};
}
// 3) NUD state (reachable, stale, ...)
if let Ok(state) = s.parse::<NUDState>() {
let nud = vec![state];
return DeviceQuery {
nud,
..Default::default()
};
}
// 4) Known device? prefer dev first
if has_dev(s) {
return DeviceQuery {
dev: vec![s.to_string()],
..Default::default()
};
}
// 5) Try DNS: if it resolves, treat as name
/* if tokio::net::lookup_host((s, 0)).await.is_ok() {
return DeviceQuery {
name: Some(s.to_string()),
..Default::default()
};
} */
// Default: name last // it will fail also
DeviceQuery {
name: Some(s.to_string()),
..Default::default()
}
}
+11 -4
View File
@@ -22,11 +22,19 @@ impl TryFrom<RouteWakeTarget> for WakeTarget {
}
}
impl From<WakeTargetResult> for RouteWakeResult {
fn from(WakeTargetResult { ip, mac, status }: WakeTargetResult) -> Self {
impl From<WakeTarget> for RouteWakeTarget {
fn from(WakeTarget { ip, mac }: WakeTarget) -> Self {
Self {
ip: Some(ip),
mac: Some(mac),
}
}
}
impl From<WakeTargetResult> for RouteWakeResult {
fn from(WakeTargetResult { target, status }: WakeTargetResult) -> Self {
Self {
target: target.into(),
status: status.into(),
}
}
@@ -35,8 +43,7 @@ impl From<WakeTargetResult> for RouteWakeResult {
impl RouteWakeTarget {
pub fn to_incomplete(self) -> RouteWakeResult {
RouteWakeResult {
ip: self.ip,
mac: self.mac,
target: self,
status: RouteWakeStatus::Incomplete,
}
}
+6 -36
View File
@@ -5,35 +5,6 @@ use futures::TryFutureExt;
use macaddr::MacAddr;
use tokio::net::UdpSocket;
use crate::utils::query::get_macs_2_mac;
pub async fn wake(machine_name: &str) -> io::Result<u32> {
let suh = UdpSocket::bind("0.0.0.0:0").await?;
suh.set_broadcast(true)?;
let /* mut */ macs = get_macs_2_mac(machine_name).await.unwrap_or_default();
// macs.extend(*LDA_MACS_2);
let mut sent_ok = 0;
for mac in macs {
let mb = mac.as_bytes();
let mut pac = [0; 6 + 6 * 16]; // 6x FF + 16x mac6
pac[..6].fill(0xff);
for i in 1..=16 {
pac[i * 6..(i + 1) * 6].copy_from_slice(mb);
}
match suh
.send_to(&pac, (IpAddr::from([192, 168, 100, 255]), 9))
.await
{
Ok(n) if n == pac.len() => sent_ok += 1,
Ok(n) => eprintln!("partial send ({n}/{})", pac.len()),
Err(e) => eprintln!("send error: {e}"),
}
}
Ok(sent_ok)
}
#[derive(Debug, Clone, Copy, Hash)]
pub struct WakeTarget {
pub ip: IpAddr,
@@ -41,8 +12,7 @@ pub struct WakeTarget {
}
#[derive(Debug, Clone, Copy, Hash)]
pub struct WakeTargetResult {
pub ip: IpAddr,
pub mac: MacAddr,
pub target: WakeTarget,
pub status: WakeStatus,
}
#[derive(Debug, Clone, Copy, Hash)]
@@ -56,18 +26,18 @@ impl WakeTarget {
Self { ip, mac }
}
fn good(self) -> WakeTargetResult {
WakeTargetResult::new(self.ip, self.mac, WakeStatus::Success)
WakeTargetResult::new(self, WakeStatus::Success)
}
fn bad(self) -> WakeTargetResult {
WakeTargetResult::new(self.ip, self.mac, WakeStatus::WrongSize)
WakeTargetResult::new(self, WakeStatus::WrongSize)
}
fn errored(self) -> WakeTargetResult {
WakeTargetResult::new(self.ip, self.mac, WakeStatus::NonexistentAddress)
WakeTargetResult::new(self, WakeStatus::NonexistentAddress)
}
}
impl WakeTargetResult {
fn new(ip: IpAddr, mac: MacAddr, status: WakeStatus) -> Self {
Self { ip, mac, status }
fn new(target: WakeTarget, status: WakeStatus) -> Self {
Self { target, status }
}
}