diff --git a/Cargo.toml b/Cargo.toml index fac9f03..a1cba8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ etherparse = "0.14.2" async-trait = "0.1.77" bytes = "1.5.0" path-absolutize = "3.1.1" +tokio-tun = "0.11.2" [workspace.dependencies.uuid] version = "1.6.1" diff --git a/network/Cargo.toml b/network/Cargo.toml index 0124feb..c6efd94 100644 --- a/network/Cargo.toml +++ b/network/Cargo.toml @@ -20,6 +20,7 @@ etherparse = { workspace = true } async-trait = { workspace = true } uuid = { workspace = true } bytes = { workspace = true } +tokio-tun = { workspace = true } [dependencies.advmac] path = "../libs/advmac" diff --git a/network/bin/network.rs b/network/bin/network.rs index d49fa14..3cd5a91 100644 --- a/network/bin/network.rs +++ b/network/bin/network.rs @@ -10,6 +10,6 @@ struct NetworkArgs {} async fn main() -> Result<()> { env_logger::Builder::from_env(Env::default().default_filter_or("warn")).init(); let _ = NetworkArgs::parse(); - let mut service = NetworkService::new()?; + let mut service = NetworkService::new().await?; service.watch().await } diff --git a/network/src/hbridge.rs b/network/src/hbridge.rs new file mode 100644 index 0000000..dc21035 --- /dev/null +++ b/network/src/hbridge.rs @@ -0,0 +1,146 @@ +use std::net::{IpAddr, Ipv4Addr}; + +use advmac::MacAddr6; +use anyhow::{anyhow, Result}; +use bytes::BytesMut; +use futures::TryStreamExt; +use log::error; +use smoltcp::wire::EthernetAddress; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + select, + sync::mpsc::channel, + task::JoinHandle, +}; +use tokio_tun::Tun; + +use crate::vbridge::{BridgeJoinHandle, VirtualBridge}; + +pub struct HostBridge { + task: JoinHandle<()>, +} + +impl HostBridge { + pub async fn new(interface: String, bridge: &VirtualBridge) -> Result { + let tun = Tun::builder() + .name(&interface) + .tap(true) + .mtu(1500) + .packet_info(false) + .try_build()?; + + let (connection, handle, _) = rtnetlink::new_connection()?; + tokio::spawn(connection); + + let mut mac = MacAddr6::random(); + mac.set_local(true); + mac.set_multicast(false); + + let mut links = handle.link().get().match_name(interface.clone()).execute(); + let link = links.try_next().await?; + if link.is_none() { + return Err(anyhow!( + "unable to find network interface named {}", + interface + )); + } + let link = link.unwrap(); + + handle + .address() + .add( + link.header.index, + IpAddr::V4(Ipv4Addr::new(10, 75, 0, 1)), + 16, + ) + .execute() + .await?; + + handle + .address() + .add(link.header.index, IpAddr::V6(mac.to_link_local_ipv6()), 10) + .execute() + .await?; + + handle + .link() + .set(link.header.index) + .address(mac.to_array().to_vec()) + .up() + .execute() + .await?; + + let mac = EthernetAddress(mac.to_array()); + let bridge_handle = bridge.join(mac).await?; + + let task = tokio::task::spawn(async move { + if let Err(error) = HostBridge::process(tun, bridge_handle).await { + error!("failed to process host bridge: {}", error); + } + }); + + Ok(HostBridge { task }) + } + + async fn process(tun: Tun, mut bridge_handle: BridgeJoinHandle) -> Result<()> { + let (rx_sender, mut rx_receiver) = channel::(100); + let (mut read, mut write) = tokio::io::split(tun); + tokio::task::spawn(async move { + let mut buffer = vec![0u8; 1500]; + loop { + let size = match read.read(&mut buffer).await { + Ok(size) => size, + Err(error) => { + error!("failed to read tap device: {}", error); + break; + } + }; + match rx_sender.send(buffer[0..size].into()).await { + Ok(_) => {} + Err(error) => { + error!( + "failed to send data from tap device to processor: {}", + error + ); + break; + } + } + } + }); + loop { + select! { + x = bridge_handle.from_bridge_receiver.recv() => match x { + Some(bytes) => { + write.write_all(&bytes).await?; + }, + None => { + break; + } + }, + x = bridge_handle.from_broadcast_receiver.recv() => match x { + Ok(bytes) => { + write.write_all(&bytes).await?; + }, + Err(error) => { + return Err(error.into()); + } + }, + x = rx_receiver.recv() => match x { + Some(bytes) => { + bridge_handle.to_bridge_sender.send(bytes).await?; + }, + None => { + break; + } + } + }; + } + Ok(()) + } +} + +impl Drop for HostBridge { + fn drop(&mut self) { + self.task.abort(); + } +} diff --git a/network/src/lib.rs b/network/src/lib.rs index 52183ba..a7159c1 100644 --- a/network/src/lib.rs +++ b/network/src/lib.rs @@ -3,6 +3,7 @@ use std::{collections::HashMap, thread, time::Duration}; use anyhow::Result; use autonet::{AutoNetworkChangeset, AutoNetworkCollector, NetworkMetadata}; use futures::{future::join_all, TryFutureExt}; +use hbridge::HostBridge; use log::warn; use tokio::{task::JoinHandle, time::sleep}; use uuid::Uuid; @@ -13,6 +14,7 @@ use crate::backend::NetworkBackend; pub mod autonet; pub mod backend; pub mod chandev; +pub mod hbridge; pub mod icmp; pub mod nat; pub mod pkt; @@ -23,13 +25,17 @@ pub mod vbridge; pub struct NetworkService { pub backends: HashMap>, pub bridge: VirtualBridge, + pub hbridge: HostBridge, } impl NetworkService { - pub fn new() -> Result { + pub async fn new() -> Result { + let bridge = VirtualBridge::new()?; + let hbridge = HostBridge::new("krata0".to_string(), &bridge).await?; Ok(NetworkService { backends: HashMap::new(), - bridge: VirtualBridge::new()?, + bridge, + hbridge, }) } }