feat: router arp entry

This commit is contained in:
2024-12-05 14:13:39 +00:00
parent 7ffff1e698
commit 81b233a047
9 changed files with 197 additions and 30 deletions

View File

@ -1,29 +1,101 @@
use crate::router::Router;
use crate::router::{ArpEntry, Router};
use anyhow::{bail, Result};
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use log::debug;
use reqwest::{Client, Method, Url};
use rocket::async_trait;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::net::IpAddr;
pub struct MikrotikRouter {
url: String,
token: String,
url: Url,
username: String,
password: String,
client: Client,
arp_interface: String,
}
impl MikrotikRouter {
pub fn new(url: &str, token: &str) -> Self {
pub fn new(url: &str, username: &str, password: &str, arp_interface: &str) -> Self {
Self {
url: url.to_string(),
token: token.to_string(),
url: url.parse().unwrap(),
username: username.to_string(),
password: password.to_string(),
client: Client::builder()
.danger_accept_invalid_certs(true)
.build()
.unwrap(),
arp_interface: arp_interface.to_string(),
}
}
async fn req<T: DeserializeOwned, R: Serialize>(
&self,
method: Method,
path: &str,
body: R,
) -> Result<T> {
let body = serde_json::to_string(&body)?;
debug!(">> {} {}: {}", method.clone(), path, &body);
let rsp = self
.client
.request(method.clone(), self.url.join(path)?)
.header(
"Authorization",
format!(
"Basic {}",
STANDARD.encode(format!("{}:{}", self.username, self.password))
),
)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.body(body)
.send()
.await?;
let status = rsp.status();
let text = rsp.text().await?;
#[cfg(debug_assertions)]
debug!("<< {}", text);
if status.is_success() {
Ok(serde_json::from_str(&text)?)
} else {
bail!("{} {}: {}", method, path, status);
}
}
}
#[async_trait]
impl Router for MikrotikRouter {
async fn add_arp_entry(
&self,
ip: IpAddr,
mac: &[u8; 6],
comment: Option<&str>,
) -> anyhow::Result<()> {
todo!()
async fn list_arp_entry(&self) -> Result<Vec<ArpEntry>> {
let rsp: Vec<ArpEntry> = self.req(Method::GET, "/rest/ip/arp", ()).await?;
Ok(rsp)
}
async fn add_arp_entry(&self, ip: IpAddr, mac: &str, comment: Option<&str>) -> Result<()> {
let _rsp: ArpEntry = self
.req(
Method::PUT,
"/rest/ip/arp",
ArpEntry {
address: ip.to_string(),
mac_address: Some(mac.to_string()),
interface: self.arp_interface.to_string(),
comment: comment.map(|c| c.to_string()),
..Default::default()
},
)
.await?;
Ok(())
}
async fn remove_arp_entry(&self, id: &str) -> Result<()> {
let _rsp: ArpEntry = self
.req(Method::DELETE, &format!("/rest/ip/arp/{id}"), ())
.await?;
Ok(())
}
}

View File

@ -1,5 +1,6 @@
use anyhow::Result;
use rocket::async_trait;
use rocket::serde::{Deserialize, Serialize};
use std::net::IpAddr;
/// Router defines a network device used to access the hosts
@ -10,9 +11,27 @@ use std::net::IpAddr;
///
/// It also prevents people from re-assigning their IP to another in the range,
#[async_trait]
pub trait Router {
async fn add_arp_entry(&self, ip: IpAddr, mac: &[u8; 6], comment: Option<&str>) -> Result<()>;
pub trait Router: Send + Sync {
async fn list_arp_entry(&self) -> Result<Vec<ArpEntry>>;
async fn add_arp_entry(&self, ip: IpAddr, mac: &str, comment: Option<&str>) -> Result<()>;
async fn remove_arp_entry(&self, id: &str) -> Result<()>;
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ArpEntry {
#[serde(rename = ".id")]
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub address: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "mac-address")]
pub mac_address: Option<String>,
pub interface: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
}
#[cfg(feature = "mikrotik")]
mod mikrotik;
#[cfg(feature = "mikrotik")]
pub use mikrotik::*;