Spanning tree init and Node lifecycle refactoring

Spanning tree initialization:
- TreeState now uses sequence=1 and current timestamp per protocol spec
- Added ParentDeclaration::sign() and TreeState::sign_declaration() methods
- Node initialization logs identity (node_id, address)

Node lifecycle refactoring:
- Moved TUN init and thread spawning into async Node::start()
- Moved TUN shutdown and thread cleanup into async Node::stop()
- Node owns I/O infrastructure (thread handles, TunTx channel)
- Removed: init_tun(), take_tun_device(), tun_device(), tun_device_mut()
- Added: tun_tx() accessor for packet sending

tun.rs:
- Added run_tun_reader() function (moved from binary)

fips.rs binary reduced from 230 to 117 lines. All 171 tests pass.
This commit is contained in:
Johnathan Corgan
2026-01-30 17:40:33 +00:00
parent ca24ba02b7
commit c421dad525
4 changed files with 280 additions and 218 deletions
+28 -141
View File
@@ -2,64 +2,10 @@
//!
//! Loads configuration and creates the top-level node instance.
use fips::{
build_dest_unreachable, log_ipv6_packet, should_send_icmp_error, shutdown_tun_interface,
Config, DestUnreachableCode, FipsAddress, Node, TunDevice, TunTx,
};
use tracing::{debug, error, info, warn, Level};
use fips::{Config, Node, TunState};
use tracing::{error, info, warn, Level};
use tracing_subscriber::{fmt, EnvFilter};
/// TUN packet reader loop.
///
/// Reads packets from the TUN device, logs them, and sends ICMPv6
/// Destination Unreachable responses for packets we can't route.
///
/// This runs in a separate thread since TUN reads are blocking.
fn run_tun_reader(mut device: TunDevice, mtu: u16, our_addr: FipsAddress, tun_tx: TunTx) {
let mut buf = vec![0u8; mtu as usize + 100]; // Extra space for headers
loop {
match device.read_packet(&mut buf) {
Ok(n) if n > 0 => {
let packet = &buf[..n];
log_ipv6_packet(packet);
// Currently no routing capability - send ICMPv6 Destination Unreachable
// for all packets that qualify for an error response
if should_send_icmp_error(packet) {
if let Some(response) = build_dest_unreachable(
packet,
DestUnreachableCode::NoRoute,
our_addr.to_ipv6(),
) {
debug!(
len = response.len(),
"Sending ICMPv6 Destination Unreachable"
);
if tun_tx.send(response).is_err() {
info!("TUN writer channel closed, reader stopping");
break;
}
}
}
}
Ok(_) => {
// Zero-length read, continue
}
Err(e) => {
// "Bad address" (EFAULT) is expected during shutdown when interface is deleted
let err_str = e.to_string();
if err_str.contains("Bad address") {
info!("TUN interface deleted, reader stopping");
} else {
error!("TUN read error: {}", e);
}
break;
}
}
}
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
// Initialize logging
@@ -119,96 +65,40 @@ async fn main() {
info!(" node_id: {}", hex::encode(node.node_id().as_bytes()));
info!(" address: {}", node.identity().address());
// Initialize TUN interface
// Start the node (initializes TUN, spawns I/O threads)
info!(
tun_state = %node.tun_state(),
"TUN interface"
"Starting node"
);
if node.tun_state() != fips::TunState::Disabled {
info!(
name = node.config().tun.name(),
mtu = node.config().tun.mtu(),
"Initializing TUN device"
);
if let Err(e) = node.start().await {
error!("Failed to start node: {}", e);
std::process::exit(1);
}
match node.init_tun().await {
Ok(true) => {
let device = node.tun_device().unwrap();
info!(
name = device.name(),
mtu = device.mtu(),
address = %device.address(),
"TUN device active"
);
// Show interface details for debugging
let output = std::process::Command::new("ip")
.args(["link", "show", device.name()])
.output();
match output {
Ok(out) => {
if out.status.success() {
info!(
"ip link show {}:\n{}",
device.name(),
String::from_utf8_lossy(&out.stdout)
);
}
}
Err(e) => {
warn!("Failed to run ip link: {}", e);
// Show TUN interface details if active
if node.tun_state() == TunState::Active {
if let Some(tun_name) = node.config().tun.name.as_deref() {
let output = std::process::Command::new("ip")
.args(["link", "show", tun_name])
.output();
match output {
Ok(out) => {
if out.status.success() {
info!(
"ip link show {}:\n{}",
tun_name,
String::from_utf8_lossy(&out.stdout)
);
}
}
}
Ok(false) => {
info!("TUN disabled");
}
Err(e) => {
error!("Failed to initialize TUN: {}", e);
warn!("Continuing without TUN interface");
Err(e) => {
warn!("Failed to run ip link: {}", e);
}
}
}
}
info!("FIPS initialized successfully");
// Spawn TUN reader and writer threads if TUN is active
let tun_name = if let Some(tun_device) = node.take_tun_device() {
let mtu = tun_device.mtu();
let name = tun_device.name().to_string();
let our_addr = *tun_device.address();
// Create writer (dups the fd for independent write access)
let (writer, tun_tx) = match tun_device.create_writer() {
Ok(w) => w,
Err(e) => {
error!("Failed to create TUN writer: {}", e);
std::process::exit(1);
}
};
info!(mtu, name = %name, "Starting TUN reader and writer");
// Spawn writer thread
std::thread::spawn(move || {
writer.run();
});
// Spawn reader thread
std::thread::spawn(move || {
run_tun_reader(tun_device, mtu, our_addr, tun_tx);
});
Some(name)
} else {
None
};
// TODO: Spawn additional event-driven tasks here:
// - Transport listeners/senders
// - Periodic timers (tree announcements, keepalives, etc.)
info!("FIPS running, press Ctrl+C to exit");
match tokio::signal::ctrl_c().await {
@@ -218,12 +108,9 @@ async fn main() {
info!("FIPS shutting down");
// Shutdown TUN interface if active
if let Some(name) = tun_name {
info!(name = %name, "Shutting down TUN interface");
if let Err(e) = shutdown_tun_interface(&name).await {
warn!("Failed to shutdown TUN interface: {}", e);
}
// Stop the node (shuts down TUN, stops I/O threads)
if let Err(e) = node.stop().await {
warn!("Error during shutdown: {}", e);
}
info!("FIPS shutdown complete");