diff --git a/Cargo.toml b/Cargo.toml index 46df2db..9b83a3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ cli-tables = "0.2.1" rand = "0.8.5" arrayvec = "0.7.4" rtnetlink = "0.14.1" +netlink-packet-route = "0.19.0" futures = "0.3.30" ipnetwork = "0.20.0" smoltcp = "0.11.0" diff --git a/hypha/Cargo.toml b/hypha/Cargo.toml index acba865..4f822ea 100644 --- a/hypha/Cargo.toml +++ b/hypha/Cargo.toml @@ -27,6 +27,7 @@ oci-spec = { workspace = true } backhand = { workspace = true } uuid = { workspace = true } rtnetlink = { workspace = true } +netlink-packet-route = { workspace = true } tokio = { workspace = true } futures = { workspace = true } ipnetwork = { workspace = true } diff --git a/hypha/bin/network.rs b/hypha/bin/network.rs index b70f61e..d263746 100644 --- a/hypha/bin/network.rs +++ b/hypha/bin/network.rs @@ -1,20 +1,19 @@ use anyhow::Result; use clap::Parser; use env_logger::Env; -use hypha::network::HyphaNetwork; +use hypha::network::NetworkService; #[derive(Parser, Debug)] struct NetworkArgs { - #[arg(short, long)] - interface: String, #[arg(short, long, default_value = "192.168.42.1/24")] network: String, } -fn main() -> Result<()> { +#[tokio::main] +async fn main() -> Result<()> { env_logger::Builder::from_env(Env::default().default_filter_or("warn")).init(); let args = NetworkArgs::parse(); - let mut network = HyphaNetwork::new(&args.interface, &[&args.network])?; - network.run()?; + let mut service = NetworkService::new(args.network)?; + service.watch().await?; Ok(()) } diff --git a/hypha/src/network/mod.rs b/hypha/src/network/mod.rs index fae2020..4ad7909 100644 --- a/hypha/src/network/mod.rs +++ b/hypha/src/network/mod.rs @@ -1,20 +1,39 @@ use std::os::fd::AsRawFd; use std::str::FromStr; +use std::thread; +use std::time::Duration; use advmac::MacAddr6; use anyhow::{anyhow, Result}; +use futures::TryStreamExt; +use log::{error, info, warn}; +use netlink_packet_route::link::LinkAttribute; use smoltcp::iface::{Config, Interface, SocketSet}; use smoltcp::phy::{self, RawSocket}; use smoltcp::time::Instant; use smoltcp::wire::{EthernetAddress, HardwareAddress, IpCidr}; +use tokio::time::sleep; -pub struct HyphaNetwork { +pub struct NetworkBackend { + pub interface: String, pub device: RawSocket, pub addresses: Vec, } -impl HyphaNetwork { - pub fn new(iface: &str, cidrs: &[&str]) -> Result { +unsafe impl Send for NetworkBackend {} + +pub struct NetworkService { + pub network: String, +} + +impl NetworkService { + pub fn new(network: String) -> Result { + Ok(NetworkService { network }) + } +} + +impl NetworkBackend { + pub fn new(iface: &str, cidrs: &[&str]) -> Result { let device = RawSocket::new(iface, smoltcp::phy::Medium::Ethernet)?; let mut addresses: Vec = Vec::new(); for cidr in cidrs { @@ -22,7 +41,32 @@ impl HyphaNetwork { IpCidr::from_str(cidr).map_err(|_| anyhow!("failed to parse cidr: {}", *cidr))?; addresses.push(address); } - Ok(HyphaNetwork { device, addresses }) + Ok(NetworkBackend { + interface: iface.to_string(), + device, + addresses, + }) + } + + pub async fn init(&mut self) -> Result<()> { + let (connection, handle, _) = rtnetlink::new_connection()?; + tokio::spawn(connection); + + let mut links = handle + .link() + .get() + .match_name(self.interface.clone()) + .execute(); + let link = links.try_next().await?; + if link.is_none() { + return Err(anyhow!( + "unable to find network interface named {}", + self.interface + )); + } + let link = link.unwrap(); + handle.link().set(link.header.index).up().execute().await?; + Ok(()) } pub fn run(&mut self) -> Result<()> { @@ -45,3 +89,62 @@ impl HyphaNetwork { } } } + +impl NetworkService { + pub async fn watch(&mut self) -> Result<()> { + let mut spawned: Vec = Vec::new(); + let (connection, handle, _) = rtnetlink::new_connection()?; + tokio::spawn(connection); + loop { + let mut stream = handle.link().get().execute(); + while let Some(message) = stream.try_next().await? { + let mut name: Option = None; + for attribute in &message.attributes { + if let LinkAttribute::IfName(if_name) = attribute { + name = Some(if_name.clone()); + } + } + + if name.is_none() { + continue; + } + + let name = name.unwrap(); + if !name.starts_with("vif") { + continue; + } + + if spawned.contains(&name) { + continue; + } + + if let Err(error) = self.add_network_backend(&name).await { + warn!( + "failed to initialize network backend for interface {}: {}", + name, error + ); + } + + spawned.push(name); + } + + sleep(Duration::from_secs(5)).await; + } + } + + async fn add_network_backend(&mut self, interface: &str) -> Result<()> { + let interface = interface.to_string(); + let mut network = NetworkBackend::new(&interface, &[&self.network])?; + network.init().await?; + info!("spawning network backend for interface {}", interface); + thread::spawn(move || { + if let Err(error) = network.run() { + error!( + "failed to run network backend for interface {}: {}", + interface, error + ); + } + }); + Ok(()) + } +}