This commit is contained in:
lda
2026-04-06 04:56:44 +07:00 Unverified
parent 95c0743ee3
commit 32eb01efe6
14 changed files with 572 additions and 513 deletions
+1 -4
View File
@@ -7,10 +7,7 @@ pub mod leases {
pub mod parser { pub mod parser {
pub use wakey_core::QueryInput as QueryType; pub use wakey_core::QueryInput as QueryType;
pub use wakey_linux::devices::classify_query as parse_query;
pub async fn parse_query(q: String) -> QueryType {
wakey_linux::devices::classify_query(q).await
}
} }
pub use leases::*; pub use leases::*;
-395
View File
@@ -1,395 +0,0 @@
use macaddr::MacAddr;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use serde_with::{DisplayFromStr, OneOrMany, serde_as};
use std::{net::IpAddr, str::FromStr};
use strum::{Display, EnumString};
use crate::parse::mac;
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize)]
pub struct NeighborEntry {
pub ip: IpAddr,
pub dev: Option<String>,
#[serde(with = "mac::option_mac")]
pub mac: Option<MacAddr>,
pub state: NeighborState,
}
#[derive(
Debug, PartialEq, Eq, EnumString, Display, Clone, Copy, Hash, Serialize, Deserialize, Default,
)]
#[strum(serialize_all = "UPPERCASE", ascii_case_insensitive)]
#[serde(rename_all = "UPPERCASE")]
pub enum NeighborState {
Permanent,
Noarp,
Reachable,
Stale,
Incomplete,
Delay,
Probe,
Failed,
#[serde(other)]
#[default]
None,
}
impl NeighborState {
pub const fn as_ip_neigh_arg(self) -> &'static str {
match self {
NeighborState::Permanent => "permanent",
NeighborState::Reachable => "reachable",
NeighborState::Stale => "stale",
NeighborState::Delay => "delay",
NeighborState::Probe => "probe",
NeighborState::Incomplete => "incomplete",
NeighborState::Noarp => "noarp",
NeighborState::None => "none",
NeighborState::Failed => "failed",
}
}
pub const fn rank(self) -> u8 {
match self {
NeighborState::Permanent | NeighborState::Reachable => 5,
NeighborState::Stale => 4,
NeighborState::Delay | NeighborState::Probe | NeighborState::Incomplete => 3,
NeighborState::Noarp => 2,
NeighborState::None => 1,
NeighborState::Failed => 0,
}
}
}
impl PartialOrd for NeighborState {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for NeighborState {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.rank().cmp(&other.rank())
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum Presence {
Online,
LikelyOnline,
#[default]
Unknown,
Offline,
}
impl From<NeighborState> for Presence {
fn from(value: NeighborState) -> Self {
match value {
NeighborState::Permanent | NeighborState::Reachable => Self::Online,
NeighborState::Stale => Self::LikelyOnline,
NeighborState::Failed => Self::Offline,
NeighborState::Delay
| NeighborState::Probe
| NeighborState::Incomplete
| NeighborState::Noarp
| NeighborState::None => Self::Unknown,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize)]
pub struct DeviceId {
#[serde(with = "mac")]
pub mac: MacAddr,
}
#[skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct Device {
pub id: Option<DeviceId>,
pub names: Vec<String>,
pub ips: Vec<IpAddr>,
#[serde(with = "mac::vec_mac")]
pub macs: Vec<MacAddr>,
pub interfaces: Vec<String>,
pub neighbors: Vec<NeighborEntry>,
pub leases: Vec<DhcpLease>,
pub presence: Presence,
}
impl Device {
pub fn from_parts(neighbors: Vec<NeighborEntry>, leases: Vec<DhcpLease>) -> Self {
use std::collections::BTreeSet;
let mut names = BTreeSet::new();
let mut ips = BTreeSet::new();
let mut macs = BTreeSet::new();
let mut interfaces = BTreeSet::new();
let mut presence = Presence::Unknown;
for lease in &leases {
ips.insert(lease.ip);
macs.insert(lease.mac);
if let Some(name) = &lease.name {
names.insert(name.clone());
}
}
for neighbor in &neighbors {
ips.insert(neighbor.ip);
if let Some(mac) = neighbor.mac {
macs.insert(mac);
}
if let Some(dev) = &neighbor.dev {
interfaces.insert(dev.clone());
}
presence = std::cmp::max(
presence_rank(presence),
presence_rank(neighbor.state.into()),
)
.into();
}
let macs: Vec<MacAddr> = macs.into_iter().collect();
Self {
id: macs.first().copied().map(|mac| DeviceId { mac }),
names: names.into_iter().collect(),
ips: ips.into_iter().collect(),
macs,
interfaces: interfaces.into_iter().collect(),
neighbors,
leases,
presence,
}
}
}
const fn presence_rank(presence: Presence) -> u8 {
match presence {
Presence::Online => 3,
Presence::LikelyOnline => 2,
Presence::Unknown => 1,
Presence::Offline => 0,
}
}
impl From<u8> for Presence {
fn from(value: u8) -> Self {
match value {
3 => Self::Online,
2 => Self::LikelyOnline,
0 => Self::Offline,
_ => Self::Unknown,
}
}
}
#[derive(Debug, Default, Clone, Hash, Deserialize, Serialize)]
pub struct DeviceQuery {
pub name: Option<String>,
#[serde(flatten)]
pub filter: DeviceFilters,
}
#[serde_as]
#[derive(Debug, Default, Clone, Hash, Serialize, Deserialize)]
pub struct DeviceFilters {
#[serde_as(as = "OneOrMany<_>")]
#[serde(default)]
pub ips: Vec<IpAddr>,
#[serde_as(as = "OneOrMany<_>")]
#[serde(default)]
pub devs: Vec<String>,
#[serde_as(as = "OneOrMany<_>")]
#[serde(default)]
pub nuds: Vec<NeighborState>,
#[serde_as(as = "OneOrMany<DisplayFromStr>")]
#[serde(default)]
pub macs: Vec<MacAddr>,
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize)]
pub struct Status<T> {
pub name: Option<String>,
pub table: Vec<T>,
pub filters: DeviceFilters,
}
#[derive(Debug, Default, Clone, Hash, Deserialize)]
pub struct NamePath {
pub name: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct DhcpLease {
pub expires_epoch: u64,
pub ip: IpAddr,
#[serde(with = "mac")]
pub mac: MacAddr,
pub name: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct DhcpLeaseWithState {
#[serde(flatten)]
pub lease_line: DhcpLease,
pub nud_state: Option<NeighborState>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct LeaseQuery {
pub include_state: bool,
}
#[derive(Debug, Default, Clone, Serialize)]
pub struct DeviceInventory {
pub devices: Vec<Device>,
}
#[skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct InterfaceSummary {
pub ifindex: u32,
pub ifname: String,
pub operstate: String,
#[serde(with = "mac::option_mac")]
pub mac: Option<MacAddr>,
pub addrs: Vec<InterfaceAddr>,
}
#[skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct InterfaceAddr {
pub family: Option<String>,
pub cidr: Option<String>,
pub broadcast: Option<std::net::Ipv4Addr>,
pub scope: Option<String>,
pub label: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Hash, PartialEq, Eq)]
pub struct WakeTarget {
#[serde(default)]
pub ip: Option<IpAddr>,
#[serde(default, with = "mac::option_mac")]
pub mac: Option<MacAddr>,
}
impl WakeTarget {
pub const fn is_complete(&self) -> bool {
matches!(
self,
Self {
ip: Some(_),
mac: Some(_)
}
)
}
}
#[derive(Debug, Serialize, Clone)]
pub struct WakeResult {
pub result: Vec<WakeTargetResult>,
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Clone, Copy)]
pub struct WakeTargetResult {
#[serde(flatten)]
pub target: WakeTarget,
pub status: WakeStatus,
}
#[derive(Debug, Serialize, Clone, Copy, Hash, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum WakeStatus {
Succeed,
NonexistentAddress,
WrongSize,
Incomplete,
}
impl WakeTargetResult {
pub const fn incomplete(target: WakeTarget) -> Self {
Self {
target,
status: WakeStatus::Incomplete,
}
}
}
#[derive(Debug)]
pub enum QueryInput {
Ip(IpAddr),
Mac(MacAddr),
Dev(String),
Nud(NeighborState),
Name(String),
}
#[derive(Debug, Clone)]
pub enum Query {
Text(String),
Ip(IpAddr),
Mac(MacAddr),
Interface(String),
NeighborState(NeighborState),
}
#[derive(Debug, Display, thiserror::Error)]
pub enum NeighborParseError {
IpWhere,
IpParseError(#[from] std::net::AddrParseError),
MacParseError(#[from] macaddr::ParseError),
StateWhere,
StateParseError(#[from] strum::ParseError),
}
pub fn parse_neighbor_line(s: &str) -> Result<NeighborEntry, NeighborParseError> {
let mut it = s.split_whitespace();
let ip: IpAddr = it.next().ok_or(NeighborParseError::IpWhere)?.parse()?;
let mut dev: Option<String> = None;
let mut mac: Option<MacAddr> = None;
let mut state: Option<NeighborState> = 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" => {
state = it.next().map(|st| st.parse()).transpose()?;
}
other => last_tok = Some(other),
}
}
if state.is_none()
&& let Some(st) = last_tok
{
state = Some(st.parse()?);
}
Ok(NeighborEntry {
ip,
dev,
mac,
state: state.ok_or(NeighborParseError::StateWhere)?,
})
}
impl FromStr for NeighborEntry {
type Err = NeighborParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse_neighbor_line(s)
}
}
+123
View File
@@ -0,0 +1,123 @@
use macaddr::MacAddr;
use serde::Serialize;
use serde_with::skip_serializing_none;
use std::net::IpAddr;
use crate::model::{DhcpLease, NeighborEntry, NeighborState};
use crate::parse::mac;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Serialize, serde::Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum Presence {
Online,
LikelyOnline,
#[default]
Unknown,
Offline,
}
impl From<NeighborState> for Presence {
fn from(value: NeighborState) -> Self {
match value {
NeighborState::Permanent | NeighborState::Reachable => Self::Online,
NeighborState::Stale => Self::LikelyOnline,
NeighborState::Failed => Self::Offline,
NeighborState::Delay
| NeighborState::Probe
| NeighborState::Incomplete
| NeighborState::Noarp
| NeighborState::None => Self::Unknown,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize)]
pub struct DeviceId {
#[serde(with = "mac")]
pub mac: MacAddr,
}
#[skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct Device {
pub id: Option<DeviceId>,
pub names: Vec<String>,
pub ips: Vec<IpAddr>,
#[serde(with = "mac::vec_mac")]
pub macs: Vec<MacAddr>,
pub interfaces: Vec<String>,
pub neighbors: Vec<NeighborEntry>,
pub leases: Vec<DhcpLease>,
pub presence: Presence,
}
impl Device {
pub fn from_parts(neighbors: Vec<NeighborEntry>, leases: Vec<DhcpLease>) -> Self {
use std::collections::BTreeSet;
let mut names = BTreeSet::new();
let mut ips = BTreeSet::new();
let mut macs = BTreeSet::new();
let mut interfaces = BTreeSet::new();
let mut presence = Presence::Unknown;
for lease in &leases {
ips.insert(lease.ip);
macs.insert(lease.mac);
if let Some(name) = &lease.name {
names.insert(name.clone());
}
}
for neighbor in &neighbors {
ips.insert(neighbor.ip);
if let Some(mac) = neighbor.mac {
macs.insert(mac);
}
if let Some(dev) = &neighbor.dev {
interfaces.insert(dev.clone());
}
presence = std::cmp::max(
presence_rank(presence),
presence_rank(neighbor.state.into()),
)
.into();
}
let macs: Vec<MacAddr> = macs.into_iter().collect();
Self {
id: macs.first().copied().map(|mac| DeviceId { mac }),
names: names.into_iter().collect(),
ips: ips.into_iter().collect(),
macs,
interfaces: interfaces.into_iter().collect(),
neighbors,
leases,
presence,
}
}
}
const fn presence_rank(presence: Presence) -> u8 {
match presence {
Presence::Online => 3,
Presence::LikelyOnline => 2,
Presence::Unknown => 1,
Presence::Offline => 0,
}
}
impl From<u8> for Presence {
fn from(value: u8) -> Self {
match value {
3 => Self::Online,
2 => Self::LikelyOnline,
0 => Self::Offline,
_ => Self::Unknown,
}
}
}
#[derive(Debug, Default, Clone, Serialize)]
pub struct DeviceInventory {
pub devices: Vec<Device>,
}
+29
View File
@@ -0,0 +1,29 @@
use macaddr::MacAddr;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use std::net::IpAddr;
use crate::model::NeighborState;
use crate::parse::mac;
#[derive(Debug, Clone, Serialize)]
pub struct DhcpLease {
pub expires_epoch: u64,
pub ip: IpAddr,
#[serde(with = "mac")]
pub mac: MacAddr,
pub name: Option<String>,
}
#[skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct DhcpLeaseWithState {
#[serde(flatten)]
pub lease_line: DhcpLease,
pub nud_state: Option<NeighborState>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct LeaseQuery {
pub include_state: bool,
}
+26
View File
@@ -0,0 +1,26 @@
use macaddr::MacAddr;
use serde::Serialize;
use serde_with::skip_serializing_none;
use crate::parse::mac;
#[skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct InterfaceSummary {
pub ifindex: u32,
pub ifname: String,
pub operstate: String,
#[serde(with = "mac::option_mac")]
pub mac: Option<MacAddr>,
pub addrs: Vec<InterfaceAddr>,
}
#[skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
pub struct InterfaceAddr {
pub family: Option<String>,
pub cidr: Option<String>,
pub broadcast: Option<std::net::Ipv4Addr>,
pub scope: Option<String>,
pub label: Option<String>,
}
+15
View File
@@ -0,0 +1,15 @@
mod device;
mod dhcp;
mod interface;
mod neighbor;
mod query;
mod status;
mod wake;
pub use device::{Device, DeviceId, DeviceInventory, Presence};
pub use dhcp::{DhcpLease, DhcpLeaseWithState, LeaseQuery};
pub use interface::{InterfaceAddr, InterfaceSummary};
pub use neighbor::{NeighborEntry, NeighborParseError, NeighborState, parse_neighbor_line};
pub use query::{DeviceFilters, DeviceQuery, NamePath, Query, QueryInput};
pub use status::Status;
pub use wake::{WakeResult, WakeStatus, WakeTarget, WakeTargetResult};
+128
View File
@@ -0,0 +1,128 @@
use macaddr::MacAddr;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use std::{net::IpAddr, str::FromStr};
use strum::{Display, EnumString};
use crate::parse::mac;
#[skip_serializing_none]
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize)]
pub struct NeighborEntry {
pub ip: IpAddr,
pub dev: Option<String>,
#[serde(with = "mac::option_mac")]
pub mac: Option<MacAddr>,
pub state: NeighborState,
}
#[derive(
Debug, PartialEq, Eq, EnumString, Display, Clone, Copy, Hash, Serialize, Deserialize, Default,
)]
#[strum(serialize_all = "UPPERCASE", ascii_case_insensitive)]
#[serde(rename_all = "UPPERCASE")]
pub enum NeighborState {
Permanent,
Noarp,
Reachable,
Stale,
Incomplete,
Delay,
Probe,
Failed,
#[serde(other)]
#[default]
None,
}
impl NeighborState {
pub const fn as_ip_neigh_arg(self) -> &'static str {
match self {
NeighborState::Permanent => "permanent",
NeighborState::Reachable => "reachable",
NeighborState::Stale => "stale",
NeighborState::Delay => "delay",
NeighborState::Probe => "probe",
NeighborState::Incomplete => "incomplete",
NeighborState::Noarp => "noarp",
NeighborState::None => "none",
NeighborState::Failed => "failed",
}
}
pub const fn rank(self) -> u8 {
match self {
NeighborState::Permanent | NeighborState::Reachable => 5,
NeighborState::Stale => 4,
NeighborState::Delay | NeighborState::Probe | NeighborState::Incomplete => 3,
NeighborState::Noarp => 2,
NeighborState::None => 1,
NeighborState::Failed => 0,
}
}
}
impl PartialOrd for NeighborState {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for NeighborState {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.rank().cmp(&other.rank())
}
}
#[derive(Debug, Display, thiserror::Error)]
pub enum NeighborParseError {
IpWhere,
IpParseError(#[from] std::net::AddrParseError),
MacParseError(#[from] macaddr::ParseError),
StateWhere,
StateParseError(#[from] strum::ParseError),
}
pub fn parse_neighbor_line(s: &str) -> Result<NeighborEntry, NeighborParseError> {
let mut it = s.split_whitespace();
let ip: IpAddr = it.next().ok_or(NeighborParseError::IpWhere)?.parse()?;
let mut dev: Option<String> = None;
let mut mac: Option<MacAddr> = None;
let mut state: Option<NeighborState> = 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" => {
state = it.next().map(|st| st.parse()).transpose()?;
}
other => last_tok = Some(other),
}
}
if state.is_none()
&& let Some(st) = last_tok
{
state = Some(st.parse()?);
}
Ok(NeighborEntry {
ip,
dev,
mac,
state: state.ok_or(NeighborParseError::StateWhere)?,
})
}
impl FromStr for NeighborEntry {
type Err = NeighborParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse_neighbor_line(s)
}
}
+53
View File
@@ -0,0 +1,53 @@
use macaddr::MacAddr;
use serde::{Deserialize, Serialize};
use serde_with::{DisplayFromStr, OneOrMany, serde_as};
use std::net::IpAddr;
use crate::model::NeighborState;
#[derive(Debug, Default, Clone, Hash, Deserialize, Serialize)]
pub struct DeviceQuery {
pub name: Option<String>,
#[serde(flatten)]
pub filter: DeviceFilters,
}
#[serde_as]
#[derive(Debug, Default, Clone, Hash, Serialize, Deserialize)]
pub struct DeviceFilters {
#[serde_as(as = "OneOrMany<_>")]
#[serde(default)]
pub ips: Vec<IpAddr>,
#[serde_as(as = "OneOrMany<_>")]
#[serde(default)]
pub devs: Vec<String>,
#[serde_as(as = "OneOrMany<_>")]
#[serde(default)]
pub nuds: Vec<NeighborState>,
#[serde_as(as = "OneOrMany<DisplayFromStr>")]
#[serde(default)]
pub macs: Vec<MacAddr>,
}
#[derive(Debug, Default, Clone, Hash, Deserialize)]
pub struct NamePath {
pub name: String,
}
#[derive(Debug)]
pub enum QueryInput {
Ip(IpAddr),
Mac(MacAddr),
Dev(String),
Nud(NeighborState),
Name(String),
}
#[derive(Debug, Clone)]
pub enum Query {
Text(String),
Ip(IpAddr),
Mac(MacAddr),
Interface(String),
NeighborState(NeighborState),
}
+12
View File
@@ -0,0 +1,12 @@
use serde::Serialize;
use serde_with::skip_serializing_none;
use crate::model::DeviceFilters;
#[skip_serializing_none]
#[derive(Debug, Default, Serialize)]
pub struct Status<T> {
pub name: Option<String>,
pub table: Vec<T>,
pub filters: DeviceFilters,
}
+58
View File
@@ -0,0 +1,58 @@
use macaddr::MacAddr;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use std::net::IpAddr;
use crate::parse::mac;
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Hash, PartialEq, Eq)]
pub struct WakeTarget {
#[serde(default)]
pub ip: Option<IpAddr>,
#[serde(default, with = "mac::option_mac")]
pub mac: Option<MacAddr>,
}
impl WakeTarget {
pub const fn is_complete(&self) -> bool {
matches!(
self,
Self {
ip: Some(_),
mac: Some(_)
}
)
}
}
#[derive(Debug, Serialize, Clone)]
pub struct WakeResult {
pub result: Vec<WakeTargetResult>,
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Clone, Copy)]
pub struct WakeTargetResult {
#[serde(flatten)]
pub target: WakeTarget,
pub status: WakeStatus,
}
#[derive(Debug, Serialize, Clone, Copy, Hash, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum WakeStatus {
Succeed,
NonexistentAddress,
WrongSize,
Incomplete,
}
impl WakeTargetResult {
pub const fn incomplete(target: WakeTarget) -> Self {
Self {
target,
status: WakeStatus::Incomplete,
}
}
}
+96
View File
@@ -0,0 +1,96 @@
use anyhow::{Context, Result};
use lda_ipjs::subcommands::address;
use std::collections::HashSet;
use wakey_core::{InterfaceAddr, InterfaceSummary};
pub async fn list_devs() -> HashSet<String> {
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 e.file_type()
.map(|ft| {
if ft.is_symlink() {
std::fs::metadata(e.path())
.map(|m| m.is_dir())
.unwrap_or(false)
} else {
ft.is_dir()
}
})
.unwrap_or(false)
&& 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
}
tokio::task::spawn_blocking(get_dev)
.await
.unwrap_or_default()
}
pub async fn devs_sorted() -> Vec<String> {
let mut v: Vec<String> = list_devs().await.into_iter().collect();
v.sort();
v
}
pub async fn list_interface_summaries() -> Result<Vec<InterfaceSummary>> {
#[cfg(unix)]
let rows = address::nl::get(None)
.await
.context("rtnetlink address query failed")?;
#[cfg(not(unix))]
let rows = address::get_with_backend(address::Backend::Json, None)
.await
.context("ip -j address show failed")?;
let mut out: Vec<InterfaceSummary> = rows
.into_iter()
.filter(|row| row.ifname != "lo")
.map(|row| InterfaceSummary {
ifindex: row.ifindex,
ifname: row.ifname,
operstate: row.operstate.as_str().to_ascii_lowercase(),
mac: row.address,
addrs: row
.addr_info
.into_iter()
.map(|info| InterfaceAddr {
family: info.family.map(|family| family.as_str().to_string()),
cidr: info
.cidr
.local
.zip(info.cidr.prefixlen)
.map(|(addr, prefixlen)| format!("{addr}/{prefixlen}")),
broadcast: info.broadcast,
scope: info.scope,
label: info.label,
})
.collect(),
})
.collect();
out.sort_by(|a, b| a.ifname.cmp(&b.ifname));
Ok(out)
}
pub async fn has_dev(name: &str) -> bool {
list_devs().await.contains(name)
}
+7
View File
@@ -0,0 +1,7 @@
mod interfaces;
mod neighbors;
mod query;
pub use interfaces::{devs_sorted, has_dev, list_devs, list_interface_summaries};
pub use neighbors::{get_ips, get_neighbors, query_status};
pub use query::classify_query;
@@ -1,12 +1,9 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use futures::future::try_join_all; use futures::future::try_join_all;
use lda_ipjs::subcommands::neighbor;
use std::collections::HashSet; use std::collections::HashSet;
use std::net::IpAddr; use std::net::IpAddr;
use wakey_core::{ use wakey_core::{DeviceQuery, NeighborEntry, NeighborState};
DeviceQuery, InterfaceAddr, InterfaceSummary, NeighborEntry, NeighborState, QueryInput, parse,
};
use lda_ipjs::subcommands::{address, neighbor};
pub async fn get_ips(machine_name: &str) -> Result<impl Iterator<Item = IpAddr>> { pub async fn get_ips(machine_name: &str) -> Result<impl Iterator<Item = IpAddr>> {
Ok(tokio::net::lookup_host((machine_name, 0)) Ok(tokio::net::lookup_host((machine_name, 0))
@@ -114,115 +111,6 @@ pub async fn query_status(query: &DeviceQuery) -> Result<Vec<NeighborEntry>> {
.await .await
} }
pub async fn list_devs() -> HashSet<String> {
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 e.file_type()
.map(|ft| {
if ft.is_symlink() {
std::fs::metadata(e.path())
.map(|m| m.is_dir())
.unwrap_or(false)
} else {
ft.is_dir()
}
})
.unwrap_or(false)
&& 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
}
tokio::task::spawn_blocking(get_dev)
.await
.unwrap_or_default()
}
pub async fn devs_sorted() -> Vec<String> {
let mut v: Vec<String> = list_devs().await.into_iter().collect();
v.sort();
v
}
pub async fn list_interface_summaries() -> Result<Vec<InterfaceSummary>> {
#[cfg(unix)]
let rows = address::nl::get(None)
.await
.context("rtnetlink address query failed")?;
#[cfg(not(unix))]
let rows = address::get_with_backend(address::Backend::Json, None)
.await
.context("ip -j address show failed")?;
let mut out: Vec<InterfaceSummary> = rows
.into_iter()
.filter(|row| row.ifname != "lo")
.map(|row| InterfaceSummary {
ifindex: row.ifindex,
ifname: row.ifname,
operstate: row.operstate.as_str().to_ascii_lowercase(),
mac: row.address,
addrs: row
.addr_info
.into_iter()
.map(|info| InterfaceAddr {
family: info.family.map(|family| family.as_str().to_string()),
cidr: info
.cidr
.local
.zip(info.cidr.prefixlen)
.map(|(addr, prefixlen)| format!("{addr}/{prefixlen}")),
broadcast: info.broadcast,
scope: info.scope,
label: info.label,
})
.collect(),
})
.collect();
out.sort_by(|a, b| a.ifname.cmp(&b.ifname));
Ok(out)
}
pub async fn has_dev(name: &str) -> bool {
list_devs().await.contains(name)
}
pub async fn classify_query(q: String) -> QueryInput {
let s = parse::extract_host(&q);
if let Some(ip) = parse::parse_numeric_ipv4(s).or_else(|| s.parse::<IpAddr>().ok()) {
return QueryInput::Ip(ip);
}
if let Ok(mac) = s.parse::<macaddr::MacAddr>() {
return QueryInput::Mac(mac);
}
if let Ok(state) = s.parse::<NeighborState>() {
return QueryInput::Nud(state);
}
if has_dev(s).await {
return QueryInput::Dev(s.to_string());
}
QueryInput::Name(s.to_string())
}
fn to_ipjs_state(value: NeighborState) -> lda_ipjs::subcommands::neighbor::NUDState { fn to_ipjs_state(value: NeighborState) -> lda_ipjs::subcommands::neighbor::NUDState {
match value { match value {
NeighborState::Permanent => lda_ipjs::subcommands::neighbor::NUDState::Permanent, NeighborState::Permanent => lda_ipjs::subcommands::neighbor::NUDState::Permanent,
+22
View File
@@ -0,0 +1,22 @@
use std::net::IpAddr;
use wakey_core::{NeighborState, QueryInput, parse};
use crate::devices::interfaces::has_dev;
pub async fn classify_query(q: String) -> QueryInput {
let s = parse::extract_host(&q);
if let Some(ip) = parse::parse_numeric_ipv4(s).or_else(|| s.parse::<IpAddr>().ok()) {
return QueryInput::Ip(ip);
}
if let Ok(mac) = s.parse::<macaddr::MacAddr>() {
return QueryInput::Mac(mac);
}
if let Ok(state) = s.parse::<NeighborState>() {
return QueryInput::Nud(state);
}
if has_dev(s).await {
return QueryInput::Dev(s.to_string());
}
QueryInput::Name(s.to_string())
}