THE ABSOLUTE CRIME of not committing frequently
This commit is contained in:
1 parent
fabf67297e
commit
1e7db089d5
29 files changed
+538
-489
No files matched your search
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
@@ -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.
@@ -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())
|
||||
}
|
||||
@@ -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
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user