the start of Greatness.

This commit is contained in:
lda
2025-10-21 01:06:12 +07:00 Unverified
parent 1307cec52b
commit d7c34ee9f5
11 changed files with 206 additions and 14 deletions
Generated
+11
View File
@@ -610,6 +610,17 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "lda-ipjs"
version = "0.0.1"
dependencies = [
"macaddr",
"serde",
"serde_json",
"serde_with",
"strum",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.175" version = "0.2.175"
+3
View File
@@ -26,3 +26,6 @@ strip = true
[features] [features]
default = ["very-smart-parsing"] default = ["very-smart-parsing"]
very-smart-parsing = [] # this is the a-bit-redundant parse thing that copilot made very-smart-parsing = [] # this is the a-bit-redundant parse thing that copilot made
[workspace]
members = ["ipjs"]
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "lda-ipjs"
description = "ip -j show schemas"
version = "0.0.1"
edition = "2024"
[dependencies]
macaddr = { version = "1.0.1", features = ["serde", "serde_std"] }
strum = { version = "0.27.2", features = ["derive", "strum_macros"] }
serde_json = "1.0.143"
serde_with = { version = "3.14.0", features = ["json"] }
serde = { version = "1.0.219", features = ["derive"] }
+19
View File
@@ -0,0 +1,19 @@
//! low
//!
//! # lda-ipj
//!
//! this package will represent all my needs with the all the subcommands of ip -j.
//!
//! ## what i need
//!
//! ```console
//! ip -j neigh
//! ```
//!
//! i also need to see devices and idk MAYBE maybe not MAYBE UHHHHHH maybe broadcast
//!
pub mod neighbor;
pub mod utils;
// i want a generalized way to build and call
+72
View File
@@ -0,0 +1,72 @@
//! ```
//! ip -j n s
//! ```
//!
//! yes. this is a real call.
use crate::utils::serialize::mac::{des_opm, ser_opm};
use std::{borrow::Cow, net::IpAddr};
use macaddr::MacAddr;
use serde::{Deserialize, Serialize};
use strum::{Display, EnumString};
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
pub struct NeighborInput {
/// supports only the last item (ignore), `to` keyword is optional
to: IpAddr,
/// supports only one item (it complains)
dev: String, // im all for simplicity
/// takes multiple, has to have `nud` before bro or it will think you `to`
nud: Vec<NUDState>,
}
// as input this must be lowercase. as output it is uppercase
#[derive(Debug, PartialEq, Eq, EnumString, Display, Clone, Copy, Hash, Serialize, Deserialize)]
#[strum(serialize_all = "lowercase", ascii_case_insensitive)]
#[serde(rename_all = "UPPERCASE")]
pub enum NUDState {
/// the neighbour entry is valid forever and can
/// be only be removed administratively.
Permanent,
/// the neighbour entry is valid. No attempts to
/// validate this entry will be made but it can
/// be removed when its lifetime expires.
Noarp,
/// the neighbour entry is valid until the
/// reachability timeout expires.
Reachable,
/// the neighbour entry is valid but suspicious.
/// This option to ip neigh does not change the
/// neighbour state if it was valid and the
/// address is not changed by this command.
Stale,
/// this is a pseudo state used when initially
/// creating a neighbour entry or after trying to
/// remove it before it becomes free to do so.
None,
/// the neighbour entry has not (yet) been
/// validated/resolved.
Incomplete,
/// neighbor entry validation is currently
/// delayed.
Delay,
/// neighbor is being probed.
Probe,
/// max number of probes exceeded without
/// success, neighbor validation has ultimately
/// failed.
Failed,
}
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
pub struct NeighborItem {
dst: IpAddr,
dev: String,
#[serde(deserialize_with = "des_opm", serialize_with = "ser_opm")]
lladdr: Option<MacAddr>,
state: Vec<NUDState>,
}
+1
View File
@@ -0,0 +1 @@
pub mod serialize;
+44
View File
@@ -0,0 +1,44 @@
use macaddr::MacAddr;
use serde::{self, Deserialize, Deserializer, de::Error as DeError};
use serde::{Serialize, Serializer, de};
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)
}
/// Serialize a MacAddr as a string
pub fn serialize_mac<S>(mac: &MacAddr, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&mac.to_string())
}
/// Deserialize a MacAddr from a string
pub fn deserialize_mac<'de, D>(deserializer: D) -> Result<MacAddr, D::Error>
where
D: Deserializer<'de>,
{
let s = <String as serde::Deserialize>::deserialize(deserializer)?;
s.parse::<MacAddr>().map_err(DeError::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)
}
+2
View File
@@ -0,0 +1,2 @@
pub mod mac;
pub mod vec;
+37
View File
@@ -0,0 +1,37 @@
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)
}
+1 -1
View File
@@ -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 { pub async fn _ping_ip_3<T: Into<IpAddr>>(addr: T) -> u8 {
match get_mac(Some(addr.into()), None, None).await { match get_mac(Some(addr.into()), None, &[] as &[NUDState]).await {
Err(_) => 0, Err(_) => 0,
Ok(l) => l Ok(l) => l
.into_iter() .into_iter()
+4 -13
View File
@@ -72,13 +72,8 @@ pub async fn get_macs(
} else { } else {
devs.iter().next().map(AsRef::as_ref) 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 run_one = |to_ip: Option<IpAddr>| get_mac(to_ip, opt_dev, state);
let mut ip_filtered = if let Some(something) = ip_all { let mut ip_filtered = if let Some(something) = ip_all {
if something.len() == 1 { if something.len() == 1 {
@@ -97,7 +92,6 @@ pub async fn get_macs(
// Apply additional filters if any were provided // Apply additional filters if any were provided
if !devs.is_empty() || !macs.is_empty() || !state.is_empty() { if !devs.is_empty() || !macs.is_empty() || !state.is_empty() {
let devset: HashSet<_> = devs.iter().map(AsRef::as_ref).collect(); let devset: HashSet<_> = devs.iter().map(AsRef::as_ref).collect();
let nudset: HashSet<_> = state.iter().collect();
let macset: HashSet<_> = macs.iter().collect(); let macset: HashSet<_> = macs.iter().collect();
ip_filtered.retain(|entry| { ip_filtered.retain(|entry| {
@@ -105,13 +99,10 @@ pub async fn get_macs(
let dev_ok = let dev_ok =
devset.is_empty() || entry.dev.as_deref().is_some_and(|d| devset.contains(d)); 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 // 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)); let mac_ok = macset.is_empty() || entry.mac.is_some_and(|m| macset.contains(&m));
dev_ok && nud_ok && mac_ok dev_ok && mac_ok
}) })
}; };
Ok(ip_filtered) Ok(ip_filtered)
@@ -121,7 +112,7 @@ pub async fn get_macs(
pub async fn get_mac( pub async fn get_mac(
ip: Option<IpAddr>, ip: Option<IpAddr>,
dev: Option<&str>, dev: Option<&str>,
state: Option<NUDState>, state: &[NUDState],
) -> Result<Vec<IpNeighLine>> { ) -> Result<Vec<IpNeighLine>> {
let mut args: Vec<String> = vec!["neigh".into(), "show".into()]; let mut args: Vec<String> = vec!["neigh".into(), "show".into()];
if let Some(ip) = ip { if let Some(ip) = ip {
@@ -132,7 +123,7 @@ pub async fn get_mac(
args.push("dev".into()); args.push("dev".into());
args.push(d.to_string()); args.push(d.to_string());
} }
if let Some(nud) = state { for nud in state {
args.push("nud".into()); args.push("nud".into());
args.push(nud.as_ip_neigh_arg().into()); args.push(nud.as_ip_neigh_arg().into());
} }