move things around do some BullShit
This commit is contained in:
+49
-14
@@ -58,8 +58,6 @@ pub fn extract_host(input: &str) -> &str {
|
||||
s.trim()
|
||||
}
|
||||
|
||||
use macaddr::MacAddr;
|
||||
use serde::Serializer;
|
||||
/// key for yes: "1" | "true" | "yes" | "on" | "y"
|
||||
///
|
||||
/// frfr
|
||||
@@ -102,18 +100,6 @@ pub fn boolish_str(s: &str) -> bool {
|
||||
&& 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;
|
||||
@@ -153,3 +139,52 @@ pub mod de_many {
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod mac {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
pub use mac::*;
|
||||
|
||||
+5
-239
@@ -1,240 +1,6 @@
|
||||
use crate::arpparse::NUDState;
|
||||
use crate::dhcpparse::DhcpLeaseLine;
|
||||
use crate::utils::parse::serialize_mac;
|
||||
use std::net::IpAddr;
|
||||
pub mod dev;
|
||||
pub mod leases;
|
||||
pub mod macs;
|
||||
|
||||
#[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},
|
||||
utils::{
|
||||
cmd::exec_command,
|
||||
error::{self, Error, Result},
|
||||
},
|
||||
};
|
||||
|
||||
/// 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?
|
||||
.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.iter().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)
|
||||
// .map(IpNeighLine::with_dev(dev)) // this could be after flatmap up there
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
});
|
||||
let res = futures::future::try_join_all(futures).await?; // async move block errs.
|
||||
Ok(res
|
||||
.into_iter()
|
||||
.flatten() /* resolve double vec */
|
||||
// .flatten() /* drop parse errors (flat_map cleared) */
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn get_macs(
|
||||
machine_name: Option<&str>,
|
||||
ips: Option<&[IpAddr]>,
|
||||
dev: Option<&str>,
|
||||
state: Option<NUDState>,
|
||||
) -> Result<Vec<IpNeighLine>> {
|
||||
// Collect IPs early (before any await) to avoid holding generics across await points
|
||||
let ip_list: Option<Vec<IpAddr>> =
|
||||
ips.map(|slice| slice.iter().copied().map(|ip| ip.to_canonical()).collect());
|
||||
|
||||
// Resolve by machine name if no IPs provided but we have a name
|
||||
let ip_list = match (ip_list, machine_name) {
|
||||
(Some(list), _) => list,
|
||||
(None, Some(name)) => get_ips(name).await?.into_iter().collect(),
|
||||
(None, None) => Vec::new(),
|
||||
};
|
||||
|
||||
// Helper to convert NUDState to the string expected by `ip neigh`
|
||||
let nud_arg = state.map(NUDState::as_ip_neigh_arg);
|
||||
// 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()];
|
||||
if let Some(ip) = to_ip {
|
||||
args.push("to".into());
|
||||
args.push(ip.to_string());
|
||||
}
|
||||
if let Some(d) = dev {
|
||||
args.push("dev".into());
|
||||
args.push(d.to_string());
|
||||
}
|
||||
if let Some(nud) = nud_arg {
|
||||
args.push("nud".into());
|
||||
args.push(nud.to_string());
|
||||
}
|
||||
|
||||
let o = exec_command("ip", args.iter().map(String::as_str).collect::<Vec<_>>()).await?; // hope to rustc that it knows how to unfuck ts
|
||||
if !o.status.success() {
|
||||
return Err(Error::CommandFailed {
|
||||
cmd: "ip",
|
||||
args,
|
||||
status: o.status.code(),
|
||||
stderr: String::from_utf8_lossy(&o.stderr).into(),
|
||||
});
|
||||
}
|
||||
|
||||
let lines = String::from_utf8_lossy(&o.stdout);
|
||||
// Parse lines and, if a specific dev filter was used, stamp that dev onto rows
|
||||
let parsed = lines.lines().flat_map(arpparse::parse_ip_neigh_line);
|
||||
let rows: Vec<IpNeighLine> = if let Some(d) = dev {
|
||||
parsed.map(IpNeighLine::with_dev(d)).collect()
|
||||
} else {
|
||||
parsed.collect()
|
||||
};
|
||||
Ok::<Vec<IpNeighLine>, error::Error>(rows)
|
||||
};
|
||||
|
||||
// If we have specific IPs, query each; otherwise query the whole table once
|
||||
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())
|
||||
} else {
|
||||
run_one(None).await
|
||||
}
|
||||
}
|
||||
|
||||
pub mod dev {
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub fn get_dev() -> HashSet<String> {
|
||||
// Prefer /sys/class/net, fallback to /proc/net/dev; filter out loopback
|
||||
let mut devs: HashSet<String> = HashSet::new();
|
||||
if let Ok(rd) = std::fs::read_dir("/sys/class/net") {
|
||||
for e in rd.flatten() {
|
||||
if let Ok(name) = e.file_name().into_string()
|
||||
&& name != "lo"
|
||||
&& !name.is_empty()
|
||||
{
|
||||
devs.insert(name);
|
||||
}
|
||||
}
|
||||
} else if let Ok(txt) = std::fs::read_to_string("/proc/net/dev") {
|
||||
for line in txt.lines().skip(2) {
|
||||
// skip headers
|
||||
if let Some((name, _rest)) = line.split_once(':') {
|
||||
let n = name.trim().to_string();
|
||||
if n != "lo" && !n.is_empty() {
|
||||
devs.insert(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
devs
|
||||
}
|
||||
|
||||
pub fn devs_sorted() -> Vec<String> {
|
||||
let mut v: Vec<String> = get_dev().into_iter().collect();
|
||||
v.sort();
|
||||
v
|
||||
}
|
||||
|
||||
pub fn has_dev(name: &str) -> bool {
|
||||
get_dev().contains(name)
|
||||
}
|
||||
}
|
||||
pub use leases::*;
|
||||
pub use macs::*;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub fn get_dev() -> HashSet<String> {
|
||||
let mut devs: HashSet<String> = HashSet::new();
|
||||
if let Ok(rd) = std::fs::read_dir("/sys/class/net") {
|
||||
for e in rd.flatten() {
|
||||
if let Ok(name) = e.file_name().into_string()
|
||||
&& name != "lo"
|
||||
&& !name.is_empty()
|
||||
{
|
||||
devs.insert(name);
|
||||
}
|
||||
}
|
||||
} else if let Ok(txt) = std::fs::read_to_string("/proc/net/dev") {
|
||||
for line in txt.lines().skip(2) {
|
||||
if let Some((name, _rest)) = line.split_once(':') {
|
||||
let n = name.trim().to_string();
|
||||
if n != "lo" && !n.is_empty() {
|
||||
devs.insert(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
devs
|
||||
}
|
||||
|
||||
pub fn devs_sorted() -> Vec<String> {
|
||||
let mut v: Vec<String> = get_dev().into_iter().collect();
|
||||
v.sort();
|
||||
v
|
||||
}
|
||||
|
||||
pub fn has_dev(name: &str) -> bool {
|
||||
get_dev().contains(name)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
use crate::arpparse::NUDState;
|
||||
use crate::dhcpparse::DhcpLeaseLine;
|
||||
use crate::utils::parse::serialize_mac;
|
||||
use serde_with::skip_serializing_none;
|
||||
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::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 {
|
||||
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()
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
use crate::arpparse::{self, IpNeighLine, NUDState};
|
||||
use crate::utils::{
|
||||
cmd::exec_command,
|
||||
error::{self, Error, Result},
|
||||
};
|
||||
use macaddr::MacAddr;
|
||||
use std::collections::HashSet;
|
||||
use std::net::IpAddr;
|
||||
|
||||
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?
|
||||
.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.iter().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())
|
||||
}
|
||||
|
||||
pub async fn get_macs(
|
||||
machine_name: Option<&str>,
|
||||
ips: Option<&[IpAddr]>,
|
||||
dev: Option<&str>,
|
||||
state: Option<NUDState>,
|
||||
) -> Result<Vec<IpNeighLine>> {
|
||||
let ip_list: Option<Vec<IpAddr>> =
|
||||
ips.map(|slice| slice.iter().copied().map(|ip| ip.to_canonical()).collect());
|
||||
let ip_list = match (ip_list, machine_name) {
|
||||
(Some(list), _) => list,
|
||||
(None, Some(name)) => get_ips(name).await?.into_iter().collect(),
|
||||
(None, None) => Vec::new(),
|
||||
};
|
||||
let nud_arg = state.map(NUDState::as_ip_neigh_arg);
|
||||
let run_one = |to_ip: Option<IpAddr>| async move {
|
||||
let mut args: Vec<String> = vec!["neigh".into(), "show".into()];
|
||||
if let Some(ip) = to_ip {
|
||||
args.push("to".into());
|
||||
args.push(ip.to_string());
|
||||
}
|
||||
if let Some(d) = dev {
|
||||
args.push("dev".into());
|
||||
args.push(d.to_string());
|
||||
}
|
||||
if let Some(nud) = nud_arg {
|
||||
args.push("nud".into());
|
||||
args.push(nud.to_string());
|
||||
}
|
||||
let o = exec_command("ip", args.iter().map(String::as_str).collect::<Vec<_>>()).await?;
|
||||
if !o.status.success() {
|
||||
return Err(Error::CommandFailed {
|
||||
cmd: "ip",
|
||||
args,
|
||||
status: o.status.code(),
|
||||
stderr: String::from_utf8_lossy(&o.stderr).into(),
|
||||
});
|
||||
}
|
||||
let lines = String::from_utf8_lossy(&o.stdout);
|
||||
let parsed = lines.lines().flat_map(arpparse::parse_ip_neigh_line);
|
||||
let rows: Vec<IpNeighLine> = if let Some(d) = dev {
|
||||
parsed.map(IpNeighLine::with_dev(d)).collect()
|
||||
} else {
|
||||
parsed.collect()
|
||||
};
|
||||
Ok::<Vec<IpNeighLine>, error::Error>(rows)
|
||||
};
|
||||
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())
|
||||
} else {
|
||||
run_one(None).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
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 {
|
||||
(
|
||||
[
|
||||
(header::CONTENT_TYPE, "application/javascript"),
|
||||
(header::CACHE_CONTROL, "public, max-age=300"),
|
||||
],
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod impls;
|
||||
use std::{io, net::IpAddr};
|
||||
|
||||
use macaddr::MacAddr;
|
||||
use tokio::net::UdpSocket;
|
||||
|
||||
use crate::utils::query::get_macs_2_mac;
|
||||
@@ -30,3 +32,67 @@ pub async fn wake(machine_name: &str) -> io::Result<u32> {
|
||||
}
|
||||
Ok(sent_ok)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash)]
|
||||
pub struct WakeTarget {
|
||||
pub ip: IpAddr,
|
||||
pub mac: MacAddr,
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, Hash)]
|
||||
pub struct WakeTargetResult {
|
||||
pub ip: IpAddr,
|
||||
pub mac: MacAddr,
|
||||
pub status: WakeStatus,
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, Hash)]
|
||||
pub enum WakeStatus {
|
||||
Success,
|
||||
NonexistentAddress,
|
||||
WrongSize,
|
||||
}
|
||||
impl WakeTarget {
|
||||
fn _new(ip: IpAddr, mac: MacAddr) -> Self {
|
||||
Self { ip, mac }
|
||||
}
|
||||
fn good(self) -> WakeTargetResult {
|
||||
WakeTargetResult::new(self.ip, self.mac, WakeStatus::Success)
|
||||
}
|
||||
fn bad(self) -> WakeTargetResult {
|
||||
WakeTargetResult::new(self.ip, self.mac, WakeStatus::WrongSize)
|
||||
}
|
||||
fn errored(self) -> WakeTargetResult {
|
||||
WakeTargetResult::new(self.ip, self.mac, WakeStatus::NonexistentAddress)
|
||||
}
|
||||
}
|
||||
impl WakeTargetResult {
|
||||
fn new(ip: IpAddr, mac: MacAddr, status: WakeStatus) -> Self {
|
||||
Self { ip, mac, status }
|
||||
}
|
||||
}
|
||||
|
||||
// its time. we have the ip; the macs. we dont need to send to the uh the broadcast anymore???
|
||||
pub async fn _wake_multi(
|
||||
targets: impl IntoIterator<Item = WakeTarget>,
|
||||
) -> io::Result<Vec<WakeTargetResult>> {
|
||||
let sock = UdpSocket::bind("0.0.0.0:0").await?;
|
||||
sock.set_broadcast(true)?;
|
||||
let fs = targets.into_iter().map(|t| wake_one(&sock, t));
|
||||
Ok(futures::future::join_all(fs).await)
|
||||
}
|
||||
|
||||
pub async fn wake_one(sock: &UdpSocket, t: WakeTarget) -> WakeTargetResult {
|
||||
let mac = t.mac;
|
||||
let mb = mac.as_bytes();
|
||||
let mut pac = [0; 6 + 6 * 16];
|
||||
pac[..6].fill(0xff);
|
||||
for i in 1..=16 {
|
||||
pac[i * 6..(i + 1) * 6].copy_from_slice(mb);
|
||||
}
|
||||
let ip = t.ip;
|
||||
let port = 9;
|
||||
match sock.send_to(&pac, (ip, port)).await {
|
||||
Ok(n) if n == pac.len() => t.good(),
|
||||
Ok(_) => t.bad(),
|
||||
Err(_) => t.errored(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
use super::{WakeStatus, WakeTarget, WakeTargetResult};
|
||||
use crate::route::wake::{
|
||||
WakeTarget as RouteWakeTarget, WakeTargetResult as RouteWakeResult,
|
||||
WakeTargetStatus as RouteWakeStatus,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Incomplete;
|
||||
|
||||
impl TryFrom<RouteWakeTarget> for WakeTarget {
|
||||
type Error = Incomplete;
|
||||
fn try_from(value: RouteWakeTarget) -> Result<Self, Self::Error> {
|
||||
if let RouteWakeTarget {
|
||||
ip: Some(ip),
|
||||
mac: Some(mac),
|
||||
} = value
|
||||
{
|
||||
Ok(Self { ip, mac })
|
||||
} else {
|
||||
Err(Incomplete)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WakeTargetResult> for RouteWakeResult {
|
||||
fn from(WakeTargetResult { ip, mac, status }: WakeTargetResult) -> Self {
|
||||
Self {
|
||||
ip: Some(ip),
|
||||
mac: Some(mac),
|
||||
status: status.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RouteWakeTarget {
|
||||
pub fn to_incomplete(self) -> RouteWakeResult {
|
||||
RouteWakeResult {
|
||||
ip: self.ip,
|
||||
mac: self.mac,
|
||||
status: RouteWakeStatus::Incomplete,
|
||||
}
|
||||
}
|
||||
pub fn is_incomplete(&self) -> bool {
|
||||
!matches!(
|
||||
self,
|
||||
Self {
|
||||
ip: Some(_),
|
||||
mac: Some(_)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WakeStatus> for RouteWakeStatus {
|
||||
fn from(value: WakeStatus) -> Self {
|
||||
match value {
|
||||
WakeStatus::NonexistentAddress => Self::NonexistentAddress,
|
||||
WakeStatus::Success => Self::Succeed,
|
||||
WakeStatus::WrongSize => Self::WrongSize,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user