122 lines
3.3 KiB
Rust
122 lines
3.3 KiB
Rust
use std::hash::RandomState;
|
|
use std::net::IpAddr;
|
|
use std::net::SocketAddr;
|
|
|
|
use anyhow::Context as _;
|
|
use anyhow::Result;
|
|
use moka::future::Cache;
|
|
use nzr_api::{
|
|
config::Config,
|
|
model::{Instance, SubnetData},
|
|
net::mac::MacAddr,
|
|
NazrinClient,
|
|
};
|
|
use tokio::net::UdpSocket;
|
|
use tokio::net::UnixStream;
|
|
|
|
pub struct Context {
|
|
subnet_cache: Cache<String, SubnetData, RandomState>,
|
|
server_sock: UdpSocket,
|
|
listen_addr: SocketAddr,
|
|
host_cache: Cache<MacAddr, Instance, RandomState>,
|
|
api_client: NazrinClient,
|
|
}
|
|
|
|
impl Context {
|
|
async fn hydrate_hosts(&self) -> Result<()> {
|
|
let instances = self
|
|
.api_client
|
|
.get_instances(nzr_api::default_ctx(), false)
|
|
.await?
|
|
.map_err(|e| anyhow::anyhow!("nzrd error: {e}"))?;
|
|
|
|
for instance in instances {
|
|
if let Some(cached) = self.host_cache.get(&instance.lease.mac_addr).await {
|
|
if cached.lease.addr == instance.lease.addr {
|
|
// Already cached
|
|
continue;
|
|
} else {
|
|
// Same MAC address, but different IP? Invalidate
|
|
self.host_cache.remove(&cached.lease.mac_addr).await;
|
|
}
|
|
}
|
|
|
|
self.host_cache
|
|
.insert(instance.lease.mac_addr, instance)
|
|
.await;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn hydrate_nets(&self) -> Result<()> {
|
|
let subnets = self
|
|
.api_client
|
|
.get_subnets(nzr_api::default_ctx())
|
|
.await?
|
|
.map_err(|e| anyhow::anyhow!("nzrd error: {e}"))?;
|
|
|
|
for net in subnets {
|
|
self.subnet_cache.insert(net.name, net.data).await;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn new(cfg: &Config) -> Result<Self> {
|
|
let api_client = {
|
|
let sock = UnixStream::connect(&cfg.rpc.socket_path)
|
|
.await
|
|
.context("Connection to nzrd failed")?;
|
|
nzr_api::new_client(sock)
|
|
};
|
|
|
|
let listen_addr: SocketAddr = {
|
|
let ip: IpAddr = cfg
|
|
.dhcp
|
|
.listen_addr
|
|
.parse()
|
|
.context("Malformed listen address")?;
|
|
(ip, cfg.dhcp.port).into()
|
|
};
|
|
|
|
let server_sock = UdpSocket::bind(listen_addr)
|
|
.await
|
|
.context("Unable to listen")?;
|
|
|
|
Ok(Self {
|
|
subnet_cache: Cache::new(50),
|
|
host_cache: Cache::new(2000),
|
|
server_sock,
|
|
api_client,
|
|
listen_addr,
|
|
})
|
|
}
|
|
|
|
pub fn sock(&self) -> &UdpSocket {
|
|
&self.server_sock
|
|
}
|
|
|
|
pub fn addr(&self) -> SocketAddr {
|
|
self.listen_addr
|
|
}
|
|
|
|
pub async fn instance_by_mac(&self, addr: MacAddr) -> anyhow::Result<Option<Instance>> {
|
|
if let Some(inst) = self.host_cache.get(&addr).await {
|
|
Ok(Some(inst))
|
|
} else {
|
|
self.hydrate_hosts().await?;
|
|
Ok(self.host_cache.get(&addr).await)
|
|
}
|
|
}
|
|
|
|
pub async fn get_subnet(&self, name: impl AsRef<str>) -> anyhow::Result<Option<SubnetData>> {
|
|
let name = name.as_ref();
|
|
if let Some(net) = self.subnet_cache.get(name).await {
|
|
Ok(Some(net))
|
|
} else {
|
|
self.hydrate_nets().await?;
|
|
Ok(self.subnet_cache.get(name).await)
|
|
}
|
|
}
|
|
}
|