use std::cell::RefCell;
use indexmap::IndexMap;
mod dns;
mod fabric;
pub mod fixture;
mod kernel;
mod netstat;
mod rule;
pub mod shim;
use crate::dns::Dns;
pub use crate::dns::{ToIpAddr, ToIpAddrs};
use crate::fabric::Fabric;
pub use crate::fabric::HostId;
use crate::kernel::Kernel;
pub use crate::kernel::{KernelConfig, Packet, TcpFlags, TcpSegment, Transport, UdpDatagram};
pub use crate::netstat::{Netstat, NetstatEntry, NetstatState, Proto};
pub use crate::rule::{Latency, Rule, RuleGuard, RuleId, Verdict};
thread_local! {
static CURRENT: RefCell<Option<Net>> = const { RefCell::new(None) };
}
pub struct Net {
fabric: Fabric,
dns: Dns,
current: Option<HostId>,
rules: IndexMap<RuleId, Box<dyn Rule>>,
next_rule_id: u64,
}
impl Net {
pub fn new() -> Self {
Self::with_config(KernelConfig::default())
}
pub fn with_config(cfg: KernelConfig) -> Self {
Self {
fabric: Fabric::new(cfg),
dns: Dns::new(),
current: None,
rules: IndexMap::new(),
next_rule_id: 1,
}
}
pub fn add_host<A: ToIpAddrs>(&mut self, addrs: A) -> HostId {
let ips = addrs.to_ip_addrs(&mut self.dns);
let id = self.fabric.add_host(ips);
if self.current.is_none() {
self.current = Some(id);
}
id
}
pub fn lookup(&mut self, name: &str) -> std::net::IpAddr {
self.dns.resolve(name)
}
pub fn host_ids(&self) -> impl Iterator<Item = HostId> + '_ {
self.fabric.host_ids()
}
pub fn rule(&mut self, rule: impl Rule) {
self.install_rule(Box::new(rule));
}
fn install_rule(&mut self, rule: Box<dyn Rule>) -> RuleId {
let id = RuleId(self.next_rule_id);
self.next_rule_id += 1;
self.rules.insert(id, rule);
id
}
fn uninstall_rule(&mut self, id: RuleId) {
self.rules.shift_remove(&id);
}
fn evaluate(&mut self, pkt: &Packet) -> Verdict {
for rule in self.rules.values_mut() {
match rule.on_packet(pkt) {
Verdict::Pass => continue,
v => return v,
}
}
Verdict::Pass
}
pub fn enter(self) -> EnterGuard {
CURRENT.with(|c| {
let mut slot = c.borrow_mut();
assert!(slot.is_none(), "another Net is already installed");
*slot = Some(self);
});
EnterGuard { _priv: () }
}
}
impl std::fmt::Debug for Net {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Net")
.field("fabric", &self.fabric)
.field("dns", &self.dns)
.field("current", &self.current)
.field("rules", &format!("{} installed", self.rules.len()))
.finish()
}
}
impl Default for Net {
fn default() -> Self {
Self::new()
}
}
#[must_use = "a Net is only active while the guard is held"]
pub struct EnterGuard {
_priv: (),
}
impl EnterGuard {
pub fn egress_all(&self, out: &mut Vec<Packet>) {
CURRENT.with(|c| {
c.borrow_mut()
.as_mut()
.expect("guard is live")
.fabric
.egress_all(out)
});
}
pub fn deliver(&self, pkt: Packet) {
CURRENT.with(|c| {
c.borrow_mut()
.as_mut()
.expect("guard is live")
.fabric
.deliver(pkt)
});
}
pub fn evaluate(&self, pkt: &Packet) -> Verdict {
CURRENT.with(|c| {
c.borrow_mut()
.as_mut()
.expect("guard is live")
.evaluate(pkt)
})
}
pub fn set_current(&self, id: HostId) {
CURRENT.with(|c| {
c.borrow_mut().as_mut().expect("guard is live").current = Some(id);
});
}
pub fn rule(&self, r: impl Rule) -> RuleGuard {
RuleGuard::new(install_rule(Box::new(r)))
}
}
impl Drop for EnterGuard {
fn drop(&mut self) {
CURRENT.with(|c| *c.borrow_mut() = None);
}
}
pub(crate) fn sys<R>(f: impl FnOnce(&mut Kernel) -> R) -> R {
CURRENT.with(|c| {
let mut cell = c.borrow_mut();
let net = cell
.as_mut()
.expect("no Net installed — call Net::enter() first");
let id = net
.current
.expect("no current host — register one with Net::add_host()");
f(net.fabric.kernel_mut(id))
})
}
pub fn lookup_host(name: &str) -> Option<std::net::IpAddr> {
CURRENT.with(|c| c.borrow().as_ref().and_then(|net| net.dns.lookup(name)))
}
pub fn set_current(id: HostId) {
CURRENT.with(|c| {
c.borrow_mut()
.as_mut()
.expect("no Net installed — call Net::enter() first")
.current = Some(id);
});
}
pub fn rule(r: impl Rule) -> RuleGuard {
RuleGuard::new(install_rule(Box::new(r)))
}
fn install_rule(r: Box<dyn Rule>) -> RuleId {
CURRENT.with(|c| {
c.borrow_mut()
.as_mut()
.expect("no Net installed — call Net::enter() first")
.install_rule(r)
})
}
fn uninstall_rule(id: RuleId) {
CURRENT.with(|c| {
if let Some(net) = c.borrow_mut().as_mut() {
net.uninstall_rule(id);
}
});
}
pub fn netstat<H: ToIpAddr>(host: H) -> Netstat {
CURRENT.with(|c| {
let cell = c.borrow();
let net = cell
.as_ref()
.expect("no Net installed — call Net::enter() first");
let ip = host
.try_to_ip_addr(&net.dns)
.expect("hostname not registered");
let id = net
.fabric
.host_for_ip(ip)
.unwrap_or_else(|| panic!("no host registered for {ip}"));
netstat::snapshot(net.fabric.kernel(id))
})
}