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, server_sock: UdpSocket, listen_addr: SocketAddr, host_cache: Cache, 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 { 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> { 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) -> anyhow::Result> { 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) } } }