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]
linker = "arm-none-eabi-gcc"
# [target.armv7-unknown-linux-gnueabihf]
# linker = "arm-none-eabi-gcc"
# 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"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "http"
version = "1.3.1"
@@ -594,6 +600,28 @@ dependencies = [
"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]]
name = "syn"
version = "2.0.106"
@@ -611,6 +639,26 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "thread_local"
version = "1.1.9"
@@ -740,6 +788,8 @@ dependencies = [
"color-eyre",
"futures",
"macaddr",
"strum",
"thiserror",
"tokio",
]
+3 -1
View File
@@ -9,4 +9,6 @@ axum = "0.8.4"
color-eyre = "0.6.5"
futures = "0.3.31"
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(())
// }
//! 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;
mod arpparse;
@@ -23,20 +17,23 @@ async fn home() -> Html<String> {
</body>
</html>
"#,
match get_ips(MACHINE_NAME).await {
Ok(ips) => {
let addrs: Vec<SocketAddr> = ips.into_iter().map(|ip|(ip, 22).into()).collect();
if ping_ip(&*addrs).await { "on" } else { "off" }
}
Err(_) => "off",
}
if ping_ip((MACHINE_NAME, 22)).await {
"on"
} else {
"off"
} // match get_ips(MACHINE_NAME).await {
// Ok(ips) => {
// // let addrs: Vec<SocketAddr> = ips.into_iter().map(|ip|(ip, 22).into()).collect();
// }
// Err(_) => "off",
// }
))
}
async fn wake_handler() -> &'static str {
match wake(MACHINE_NAME).await {
Err(_) => "Wake failed",
Ok(_) => "Packet sent!",
Ok(x) if x > 0 => "Packet sent!",
_ => "Wake failed",
}
}
@@ -48,34 +45,36 @@ async fn status() -> Html<String> {
.map(|ip| format!("<tr><td>{ip}</td></tr>"))
.collect();
format!(
"<p>the ips of {m} are:</p>
r#"<p>the ips of {m} are:</p>
<table>
<tr><th>IP</th></tr>
{string}
</table>",
m = MACHINE_NAME )
</table>"#,
m = MACHINE_NAME
)
}
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) => {
let the: String = table
.iter()
.map(|(ip, mac)| {
let mac_str = if let Some(mac) = mac {
back_to_str(mac)
} else {
"None".into()
};
let mac_str = // if let Some(mac) = mac {
mac.to_string()
// } else {
// "None".into()
// }
;
format!("<tr><td>{ip}</td><td>{mac_str}</td></tr>")
})
.collect();
format!(
"<p>the macs here:</p>
r#"<p>the macs here:</p>
<table>
<tr><th>IP</th><th>MAC</th></tr>
{the}
</table>"
</table>"#
)
}
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<()> {
color_eyre::install()?;
let app = Router::new()
@@ -104,3 +104,11 @@ async fn main() -> color_eyre::Result<()> {
axum::serve(port, app.into_make_service()).await?;
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] = [
// 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 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?;
suh.set_broadcast(true)?;
for (_, mac) in get_macs(machine_name).await? {
let Some(mac) = mac else {
continue;
};
let pac: Vec<u8> = iter::once([0xff; 6])
.chain(iter::repeat_n(mac, 16))
let mut macs = get_macs_2_1(machine_name).await.unwrap_or_default();
macs.extend(LDA_MACS_2.map(|m| ([192, 168, 100, 255].into(), m)));
let len = macs.len() as u32;
// count - count fail
let mut count_fail = 0;
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()
.copied()
.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;
}
Ok(())
}); // 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.
}
use std::{collections::HashSet, iter, net::IpAddr, time::Duration};
Ok(len - count_fail)
}
use std::{collections::HashSet, iter, net::IpAddr, str::FromStr, sync::LazyLock, time::Duration};
use macaddr::MacAddr;
use tokio::{
@@ -27,22 +55,28 @@ use tokio::{
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 {
// thanks cahtgpt
timeout(Duration::from_secs(1), TcpStream::connect(addr))
.await
.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>> {
Ok(tokio::net::lookup_host((machine_name, 0))
.await?
.map(|c| c.ip())
.collect())
}
pub async fn get_macs(machine_name: &str) -> io::Result<Vec<(IpAddr, Option<[u8; 6]>)>> {
// not used lets go
pub async fn get_macs(machine_name: &str) -> io::Result<Vec<(IpAddr, MacAddr)>> {
let ips = get_ips(machine_name).await?;
let futures = ips
.iter()
@@ -50,40 +84,78 @@ pub async fn get_macs(machine_name: &str) -> io::Result<Vec<(IpAddr, Option<[u8;
.map(|ip| {
let ip = ip.to_canonical();
async move {
let mut u = tokio::process::Command::new("ip");
u.args(["neigh", "show", "to", &ip.to_string()]);
let o = u.output().await.ok()?;
let mac = o.status.success().then(|| {
let o = exec_command("ip", ["neigh", "show", "to", &ip.to_string()])
.await
.ok()?;
o.status.success().then(|| {
let stdout = String::from_utf8_lossy(&o.stdout);
stdout
.lines()
.filter_map(|line| {
let mut parts = line.split_whitespace();
parts.find(|&x| x == "lladdr")?;
let macstr = parts.next()?;
to_arr(macstr)
})
parts
.next()
})?;
Some((ip, mac))
.and_then(|mac| MacAddr::from_str(mac).ok()) // 100% correct because its ip neigh brother they know how to code.
.map(|mac| (ip, mac))
})
.collect::<Vec<_>>()
})
}
});
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>> {
Ok(get_macs(machine_name)
.await?
pub async fn get_macs_1(machine_name: &str) -> io::Result<Vec<arpparse::IpNeighLine>> {
let ips = get_ips(machine_name).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()
.filter_map(|(_, m)| m.map(MacAddr::from))
.flatten() /* resolve double vec */
.flatten() /* drop parse errors */
.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]> {
let mut this = [0u8; 6];
(macstr.split(':').count() == 6).then(|| {
for (n, h) in this.iter_mut().zip(macstr.split(':')) {
let c: Vec<_> = macstr.split(':').collect();
(c.len() == 6).then(|| {
for (n, h) in this.iter_mut().zip(c) {
*n = u8::from_str_radix(h, 16).ok()?;
}
Some(this)