migration is crazy

This commit is contained in:
lda
2025-08-23 05:19:01 +07:00 Unverified
parent 48fb5bbac7
commit 69f4662826
9 changed files with 374 additions and 62 deletions
+5 -2
View File
@@ -1,4 +1,7 @@
[target.armv7-unknown-linux-gnueabihf] # [target.armv7-unknown-linux-gnueabihf]
linker = "arm-none-eabi-gcc" # linker = "arm-none-eabi-gcc"
# advices from gpt5rustup target add armv7-unknown-linux-musleabihf # advices from gpt5rustup target add armv7-unknown-linux-musleabihf
[build]
target = "armv7-unknown-linux-musleabihf"
+3
View File
@@ -0,0 +1,3 @@
{
"rust-analyzer.cargo.target": "armv7-unknown-linux-musleabihf"
}
Generated
+50
View File
@@ -257,6 +257,12 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "http" name = "http"
version = "1.3.1" version = "1.3.1"
@@ -594,6 +600,28 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "strum"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.106" version = "2.0.106"
@@ -611,6 +639,26 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
[[package]]
name = "thiserror"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "thread_local" name = "thread_local"
version = "1.1.9" version = "1.1.9"
@@ -740,6 +788,8 @@ dependencies = [
"color-eyre", "color-eyre",
"futures", "futures",
"macaddr", "macaddr",
"strum",
"thiserror",
"tokio", "tokio",
] ]
+3 -1
View File
@@ -9,4 +9,6 @@ axum = "0.8.4"
color-eyre = "0.6.5" color-eyre = "0.6.5"
futures = "0.3.31" futures = "0.3.31"
macaddr = "1.0.1" macaddr = "1.0.1"
tokio = { version = "1.47.1", features = ["fs", "process"] } strum = { version = "0.27.2", features = ["derive", "strum_macros"] }
thiserror = "2.0.16"
tokio = { version = "1.47.1", features = ["fs", "process", "rt-multi-thread"] }
+2
View File
@@ -0,0 +1,2 @@
[build]
default-target = "armv7-unknown-linux-musleabihf"
+142
View File
@@ -6,3 +6,145 @@
// Ok(()) // Ok(())
// } // }
//! ip neigh pass
use std::{net::IpAddr, str::FromStr};
use macaddr::MacAddr;
use strum::{Display, EnumString};
use crate::arpparse::error::IPNeighParseError;
mod error;
/// ip neigh has some cool shit.
/// IP
/// dev DEV | None
/// lladdr MAC | None
/// status { permanent | noarp | stale | reachable | none | incomplete | delay | probe | failed } (ip neigh help)
/// so you can see its damn good
///
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct IpNeighLine {
pub ip: IpAddr,
pub dev: Option<String>,
/// link layer address
pub mac: Option<MacAddr>,
/// Neighbour Unreachability Detection
pub state: NUDState,
}
#[derive(Debug, PartialEq, Eq, EnumString, Display, Clone, Copy, Hash)]
#[strum(serialize_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,
}
impl NUDState {
/// dumb UI label
pub fn dumber_state(&self) -> &'static str {
match self {
NUDState::Permanent | NUDState::Reachable => "online",
NUDState::Stale => "maybe online",
NUDState::Delay | NUDState::Probe | NUDState::Incomplete => "resolving",
NUDState::Noarp => "static",
NUDState::None => "unknown",
NUDState::Failed => "offline",
}
}
/// dumb boolean: Some(true)=on, Some(false)=off, None=shrug
pub fn dumber_state_this_way(&self) -> Option<bool> {
match self {
NUDState::Permanent | NUDState::Reachable => Some(true),
NUDState::Failed => Some(false),
_ => None,
}
}
}
// thanks copilot for the PEAK
pub fn parse_ip_neigh_line(s: &str) -> Result<IpNeighLine, IPNeighParseError> {
let mut it = s.split_whitespace();
let ip: IpAddr = it.next().ok_or(IPNeighParseError::IpWhere)?.parse()?;
let mut dev: Option<String> = None;
let mut mac: Option<MacAddr> = None;
let mut state: Option<NUDState> = None;
let mut last_tok: Option<&str> = None;
while let Some(tok) = it.next() {
match tok {
"dev" => dev = it.next().map(str::to_string),
"lladdr" => {
mac = it.next().map(|m| m.parse()).transpose()?;
}
"nud" => {
// we know this aint happening
state = it.next().map(|st| st.parse()).transpose()?;
}
other => last_tok = Some(other),
}
}
// If no explicit "nud", many outputs end with STATE
if state.is_none()
&& let Some(st) = last_tok
{
state = Some(st.parse()?);
}
Ok(IpNeighLine {
ip,
dev,
mac,
state: state.ok_or(IPNeighParseError::StateWhere)?,
})
}
impl FromStr for IpNeighLine {
type Err = IPNeighParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse_ip_neigh_line(s)
}
}
/// pls dont touch ts
impl IpNeighLine {
pub fn set_ip(&mut self, ip: IpAddr) {
self.ip = ip;
}
pub fn set_state(&mut self, ip: IpAddr) {
self.ip = ip;
}
}
+30
View File
@@ -0,0 +1,30 @@
use std::net::AddrParseError;
use strum::Display;
use thiserror::Error;
#[derive(Debug, Display, Error)]
pub enum IPNeighParseError {
IpWhere,
IpParseError(AddrParseError),
DevWhere,
MacParseError(macaddr::ParseError),
StateWhere,
StateParseError(strum::ParseError),
}
impl From<AddrParseError> for IPNeighParseError {
fn from(value: AddrParseError) -> Self {
Self::IpParseError(value)
}
}
impl From<macaddr::ParseError> for IPNeighParseError {
fn from(value: macaddr::ParseError) -> Self {
Self::MacParseError(value)
}
}
impl From<strum::ParseError> for IPNeighParseError {
fn from(value: strum::ParseError) -> Self {
Self::StateParseError(value)
}
}
+36 -28
View File
@@ -1,10 +1,4 @@
use std::net::SocketAddr; use axum::{Router, response::Html, routing::get};
use axum::{
Router,
response::Html,
routing::get,
};
use tokio::net::TcpListener; use tokio::net::TcpListener;
mod arpparse; mod arpparse;
@@ -23,20 +17,23 @@ async fn home() -> Html<String> {
</body> </body>
</html> </html>
"#, "#,
match get_ips(MACHINE_NAME).await { if ping_ip((MACHINE_NAME, 22)).await {
Ok(ips) => { "on"
let addrs: Vec<SocketAddr> = ips.into_iter().map(|ip|(ip, 22).into()).collect(); } else {
if ping_ip(&*addrs).await { "on" } else { "off" } "off"
} } // match get_ips(MACHINE_NAME).await {
Err(_) => "off", // Ok(ips) => {
} // // let addrs: Vec<SocketAddr> = ips.into_iter().map(|ip|(ip, 22).into()).collect();
// }
// Err(_) => "off",
// }
)) ))
} }
async fn wake_handler() -> &'static str { async fn wake_handler() -> &'static str {
match wake(MACHINE_NAME).await { match wake(MACHINE_NAME).await {
Err(_) => "Wake failed", Ok(x) if x > 0 => "Packet sent!",
Ok(_) => "Packet sent!", _ => "Wake failed",
} }
} }
@@ -48,34 +45,36 @@ async fn status() -> Html<String> {
.map(|ip| format!("<tr><td>{ip}</td></tr>")) .map(|ip| format!("<tr><td>{ip}</td></tr>"))
.collect(); .collect();
format!( format!(
"<p>the ips of {m} are:</p> r#"<p>the ips of {m} are:</p>
<table> <table>
<tr><th>IP</th></tr> <tr><th>IP</th></tr>
{string} {string}
</table>", </table>"#,
m = MACHINE_NAME ) m = MACHINE_NAME
)
} }
Err(e) => format!("<p>error getting ips: {e}</p>"), Err(e) => format!("<p>error getting ips: {e}</p>"),
}; };
let formatted_macs = match get_macs(MACHINE_NAME).await { let formatted_macs = match get_macs_2_1(MACHINE_NAME).await {
Ok(table) => { Ok(table) => {
let the: String = table let the: String = table
.iter() .iter()
.map(|(ip, mac)| { .map(|(ip, mac)| {
let mac_str = if let Some(mac) = mac { let mac_str = // if let Some(mac) = mac {
back_to_str(mac) mac.to_string()
} else { // } else {
"None".into() // "None".into()
}; // }
;
format!("<tr><td>{ip}</td><td>{mac_str}</td></tr>") format!("<tr><td>{ip}</td><td>{mac_str}</td></tr>")
}) })
.collect(); .collect();
format!( format!(
"<p>the macs here:</p> r#"<p>the macs here:</p>
<table> <table>
<tr><th>IP</th><th>MAC</th></tr> <tr><th>IP</th><th>MAC</th></tr>
{the} {the}
</table>" </table>"#
) )
} }
Err(e) => format!("<p>cant get macs either: {e}</p>"), Err(e) => format!("<p>cant get macs either: {e}</p>"),
@@ -92,7 +91,8 @@ async fn status() -> Html<String> {
"#, "#,
)) ))
} }
#[tokio::main(flavor = "current_thread")] #[cfg(target_os = "linux")]
#[tokio::main]
async fn main() -> color_eyre::Result<()> { async fn main() -> color_eyre::Result<()> {
color_eyre::install()?; color_eyre::install()?;
let app = Router::new() let app = Router::new()
@@ -104,3 +104,11 @@ async fn main() -> color_eyre::Result<()> {
axum::serve(port, app.into_make_service()).await?; axum::serve(port, app.into_make_service()).await?;
Ok(()) Ok(())
} }
#[cfg(not(target_os = "linux"))]
fn main() {
use std::process::exit;
eprintln!("OS not supported! run this on your ahh router!");
exit(1)
}
+103 -31
View File
@@ -1,24 +1,52 @@
pub const LDA_MACS: [[u8; 6]; 2] = [ pub const LDA_MACS: [[u8; 6]; 2] = [
// ether
[0x04, 0x7c, 0x16, 0x79, 0x6d, 0xee], [0x04, 0x7c, 0x16, 0x79, 0x6d, 0xee],
// wifi
[0xbc, 0x09, 0x1b, 0xec, 0x65, 0xd0], [0xbc, 0x09, 0x1b, 0xec, 0x65, 0xd0],
]; // is it time to lookup host lda.lan for this... ];
pub async fn wake(machine_name: &str) -> io::Result<()> { /// 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 async fn wake(machine_name: &str) -> io::Result<u32> {
let suh = UdpSocket::bind("0.0.0.0:0").await?; let suh = UdpSocket::bind("0.0.0.0:0").await?;
suh.set_broadcast(true)?; suh.set_broadcast(true)?;
for (_, mac) in get_macs(machine_name).await? { let mut macs = get_macs_2_1(machine_name).await.unwrap_or_default();
let Some(mac) = mac else { macs.extend(LDA_MACS_2.map(|m| ([192, 168, 100, 255].into(), m)));
continue; let len = macs.len() as u32;
}; // count - count fail
let pac: Vec<u8> = iter::once([0xff; 6]) let mut count_fail = 0;
.chain(iter::repeat_n(mac, 16)) for (ip, mac) in macs.into_iter() {
// let Some(mac) = mac else {
// continue;
// };
let mb = mac.as_bytes();
let start = [0xff; 6];
let pac: Vec<u8> = iter::once(start.as_slice())
.chain(iter::repeat_n(mb, 16))
// what happens here?
.flatten() .flatten()
.copied()
.collect(); .collect();
suh.send_to(&pac, "192.168.100.255:9").await?; suh.send_to(&pac, (ip, 9))
.await
.inspect_err(|e| {
eprintln!("ping error: {e}");
count_fail += 1;
})
.ok()
.inspect(|f| {
if *f < (6 + 16 * 6) {
// rare ass code path
eprintln!("not complete transmission");
count_fail += 1;
}
}); // type shit
// suh.send_to(&pac, "192.168.100.255:9").await?; // type good
// suh.send_to(&pac, "255.255.255.255:9").await?; // type ass; this doesnt work somehow.
} }
Ok(()) Ok(len - count_fail)
} }
use std::{collections::HashSet, iter, net::IpAddr, time::Duration}; use std::{collections::HashSet, iter, net::IpAddr, str::FromStr, sync::LazyLock, time::Duration};
use macaddr::MacAddr; use macaddr::MacAddr;
use tokio::{ use tokio::{
@@ -27,22 +55,28 @@ use tokio::{
time::timeout, time::timeout,
}; };
/// generic so you can do "123.45.67.89:22" as an input use crate::arpparse::{self, IpNeighLine};
/// generic so you can do "123.45.67.89:22" or "lda.lan:22" as an input
// this is so bad
pub async fn ping_ip<T: ToSocketAddrs>(addr: T) -> bool { pub async fn ping_ip<T: ToSocketAddrs>(addr: T) -> bool {
// thanks cahtgpt
timeout(Duration::from_secs(1), TcpStream::connect(addr)) timeout(Duration::from_secs(1), TcpStream::connect(addr))
.await .await
.is_ok() .is_ok()
} }
pub async fn _ping_ip_2<T: ToSocketAddrs>(_addr: T) -> bool {
todo!("use icmp")
}
/// this is because i like [`IpAddr`] more than [`SocketAddr`](std::net::SocketAddr)
pub async fn get_ips(machine_name: &str) -> io::Result<Vec<IpAddr>> { pub async fn get_ips(machine_name: &str) -> io::Result<Vec<IpAddr>> {
Ok(tokio::net::lookup_host((machine_name, 0)) Ok(tokio::net::lookup_host((machine_name, 0))
.await? .await?
.map(|c| c.ip()) .map(|c| c.ip())
.collect()) .collect())
} }
// not used lets go
pub async fn get_macs(machine_name: &str) -> io::Result<Vec<(IpAddr, Option<[u8; 6]>)>> { pub async fn get_macs(machine_name: &str) -> io::Result<Vec<(IpAddr, MacAddr)>> {
let ips = get_ips(machine_name).await?; let ips = get_ips(machine_name).await?;
let futures = ips let futures = ips
.iter() .iter()
@@ -50,40 +84,78 @@ pub async fn get_macs(machine_name: &str) -> io::Result<Vec<(IpAddr, Option<[u8;
.map(|ip| { .map(|ip| {
let ip = ip.to_canonical(); let ip = ip.to_canonical();
async move { async move {
let mut u = tokio::process::Command::new("ip"); let o = exec_command("ip", ["neigh", "show", "to", &ip.to_string()])
u.args(["neigh", "show", "to", &ip.to_string()]); .await
let o = u.output().await.ok()?; .ok()?;
let mac = o.status.success().then(|| { o.status.success().then(|| {
let stdout = String::from_utf8_lossy(&o.stdout); let stdout = String::from_utf8_lossy(&o.stdout);
stdout stdout
.lines() .lines()
.filter_map(|line| { .filter_map(|line| {
let mut parts = line.split_whitespace(); let mut parts = line.split_whitespace();
parts.find(|&x| x == "lladdr")?; parts.find(|&x| x == "lladdr")?;
let macstr = parts.next()?; parts
to_arr(macstr) .next()
.and_then(|mac| MacAddr::from_str(mac).ok()) // 100% correct because its ip neigh brother they know how to code.
.map(|mac| (ip, mac))
}) })
.next() .collect::<Vec<_>>()
})?; })
Some((ip, mac))
} }
}); });
let results = futures::future::join_all(futures).await; let results = futures::future::join_all(futures).await;
Ok(results.into_iter().flatten().collect()) Ok(results.into_iter().flatten().flatten().collect())
} }
pub async fn get_macs_2(machine_name: &str) -> io::Result<HashSet<MacAddr>> { pub async fn get_macs_1(machine_name: &str) -> io::Result<Vec<arpparse::IpNeighLine>> {
Ok(get_macs(machine_name) let ips = get_ips(machine_name).await?;
.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()]).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),
)));
};
Ok(String::from_utf8_lossy(&o.stdout)
.lines()
.map(arpparse::parse_ip_neigh_line)
.collect::<Vec<_>>())
}
});
let res = futures::future::try_join_all(futures).await?; // async move block errs.
Ok(res
.into_iter() .into_iter()
.filter_map(|(_, m)| m.map(MacAddr::from)) .flatten() /* resolve double vec */
.flatten() /* drop parse errors */
.collect()) .collect())
} }
async fn exec_command<S: AsRef<std::ffi::OsStr>>(
cmd: S,
args: impl IntoIterator<Item = S>,
) -> io::Result<std::process::Output> {
let mut u = tokio::process::Command::new(cmd);
u.args(args);
u.output().await
}
pub async fn get_macs_2(machine_name: &str) -> io::Result<HashSet<(IpAddr, MacAddr)>> {
Ok(get_macs(machine_name).await?.into_iter().collect())
}
pub async fn get_macs_2_1(machine_name: &str) -> io::Result<HashSet<(IpAddr, MacAddr)>> {
Ok(get_macs_1(machine_name).await?.into_iter().filter_map(|IpNeighLine { ip, dev: _, mac, state: _ }| mac.map(|mac| (ip, mac))).collect())
}
pub fn to_arr(macstr: &str) -> Option<[u8; 6]> { pub fn to_arr(macstr: &str) -> Option<[u8; 6]> {
let mut this = [0u8; 6]; let mut this = [0u8; 6];
(macstr.split(':').count() == 6).then(|| { let c: Vec<_> = macstr.split(':').collect();
for (n, h) in this.iter_mut().zip(macstr.split(':')) { (c.len() == 6).then(|| {
for (n, h) in this.iter_mut().zip(c) {
*n = u8::from_str_radix(h, 16).ok()?; *n = u8::from_str_radix(h, 16).ok()?;
} }
Some(this) Some(this)