more bullshit IN!

This commit is contained in:
lda
2025-08-26 21:47:52 +07:00 Unverified
parent 7371d2ae8c
commit 9de286f59b
22 changed files with 845 additions and 122 deletions
+155
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
}