mirror of
https://github.com/edera-dev/krata.git
synced 2025-08-02 12:50:54 +00:00
hypha: implement basic networking support
This commit is contained in:
parent
70dc2f943f
commit
21c6a27097
@ -43,6 +43,10 @@ url = "2.5.0"
|
||||
cli-tables = "0.2.1"
|
||||
rand = "0.8.5"
|
||||
arrayvec = "0.7.4"
|
||||
rtnetlink = "0.14.1"
|
||||
futures = "0.3.30"
|
||||
ipnetwork = "0.20.0"
|
||||
smoltcp = "0.11.0"
|
||||
|
||||
[workspace.dependencies.uuid]
|
||||
version = "1.6.1"
|
||||
@ -55,3 +59,7 @@ default-features = false
|
||||
[workspace.dependencies.clap]
|
||||
version = "4.4.18"
|
||||
features = ["derive"]
|
||||
|
||||
[workspace.dependencies.tokio]
|
||||
version = "1.35.1"
|
||||
features = ["macros", "rt", "rt-multi-thread"]
|
||||
|
@ -26,6 +26,11 @@ sys-mount = { workspace = true }
|
||||
oci-spec = { workspace = true }
|
||||
backhand = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
rtnetlink = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
ipnetwork = { workspace = true }
|
||||
smoltcp = { workspace = true }
|
||||
|
||||
[dependencies.nix]
|
||||
workspace = true
|
||||
@ -53,3 +58,7 @@ path = "bin/controller.rs"
|
||||
[[bin]]
|
||||
name = "hyphactr"
|
||||
path = "bin/container.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "hyphanet"
|
||||
path = "bin/network.rs"
|
||||
|
@ -3,7 +3,8 @@ use env_logger::Env;
|
||||
use hypha::container::init::ContainerInit;
|
||||
use std::env;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
env::set_var("RUST_BACKTRACE", "1");
|
||||
env_logger::Builder::from_env(Env::default().default_filter_or("warn")).init();
|
||||
if env::var("HYPHA_UNSAFE_ALWAYS_ALLOW_INIT").unwrap_or("0".to_string()) != "1" {
|
||||
@ -18,6 +19,6 @@ fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
let mut container = ContainerInit::new();
|
||||
container.init()?;
|
||||
container.init().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -110,12 +110,13 @@ fn main() -> Result<()> {
|
||||
Commands::List { .. } => {
|
||||
let containers = controller.list()?;
|
||||
let mut table = cli_tables::Table::new();
|
||||
let header = vec!["domain", "uuid", "image"];
|
||||
let header = vec!["domain", "uuid", "ipv4", "image"];
|
||||
table.push_row(&header)?;
|
||||
for container in containers {
|
||||
let row = vec![
|
||||
container.domid.to_string(),
|
||||
container.uuid.to_string(),
|
||||
container.ipv4,
|
||||
container.image,
|
||||
];
|
||||
table.push_row_string(&row)?;
|
||||
|
20
hypha/bin/network.rs
Normal file
20
hypha/bin/network.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use env_logger::Env;
|
||||
use hypha::network::HyphaNetwork;
|
||||
|
||||
#[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<()> {
|
||||
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()?;
|
||||
Ok(())
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
use crate::shared::LaunchInfo;
|
||||
use crate::shared::{LaunchInfo, LaunchNetwork};
|
||||
use anyhow::{anyhow, Result};
|
||||
use futures::stream::TryStreamExt;
|
||||
use ipnetwork::IpNetwork;
|
||||
use log::{trace, warn};
|
||||
use nix::libc::{c_int, dup2, wait};
|
||||
use nix::unistd::{execve, fork, ForkResult, Pid};
|
||||
@ -53,7 +55,7 @@ impl ContainerInit {
|
||||
ContainerInit {}
|
||||
}
|
||||
|
||||
pub fn init(&mut self) -> Result<()> {
|
||||
pub async fn init(&mut self) -> Result<()> {
|
||||
self.early_init()?;
|
||||
|
||||
trace!("opening console descriptor");
|
||||
@ -72,6 +74,13 @@ impl ContainerInit {
|
||||
self.mount_new_root()?;
|
||||
self.nuke_initrd()?;
|
||||
self.bind_new_root()?;
|
||||
|
||||
if let Some(network) = &launch.network {
|
||||
if let Err(error) = self.network_setup(network).await {
|
||||
warn!("failed to initialize network: {}", error);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(cfg) = config.config() {
|
||||
self.run(cfg, &launch)?;
|
||||
} else {
|
||||
@ -271,6 +280,38 @@ impl ContainerInit {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn network_setup(&mut self, network: &LaunchNetwork) -> Result<()> {
|
||||
trace!(
|
||||
"setting up network with link {} and ipv4 {}",
|
||||
network.link,
|
||||
network.ipv4
|
||||
);
|
||||
|
||||
let (connection, handle, _) = rtnetlink::new_connection()?;
|
||||
tokio::spawn(connection);
|
||||
|
||||
let ip: IpNetwork = network.ipv4.parse()?;
|
||||
|
||||
let mut links = handle
|
||||
.link()
|
||||
.get()
|
||||
.match_name(network.link.clone())
|
||||
.execute();
|
||||
if let Some(link) = links.try_next().await? {
|
||||
handle
|
||||
.address()
|
||||
.add(link.header.index, ip.ip(), ip.prefix())
|
||||
.execute()
|
||||
.await?;
|
||||
|
||||
handle.link().set(link.header.index).up().execute().await?;
|
||||
} else {
|
||||
warn!("unable to find link named {}", network.link);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&mut self, config: &Config, launch: &LaunchInfo) -> Result<()> {
|
||||
let mut cmd = match config.cmd() {
|
||||
None => vec![],
|
||||
|
@ -5,11 +5,13 @@ use crate::ctl::cfgblk::ConfigBlock;
|
||||
use crate::image::cache::ImageCache;
|
||||
use crate::image::name::ImageName;
|
||||
use crate::image::{ImageCompiler, ImageInfo};
|
||||
use crate::shared::LaunchInfo;
|
||||
use crate::shared::{LaunchInfo, LaunchNetwork};
|
||||
use advmac::MacAddr6;
|
||||
use anyhow::{anyhow, Result};
|
||||
use ipnetwork::Ipv4Network;
|
||||
use loopdev::LoopControl;
|
||||
use std::io::{Read, Write};
|
||||
use std::net::Ipv4Addr;
|
||||
use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
use std::str::FromStr;
|
||||
@ -36,6 +38,7 @@ pub struct ContainerInfo {
|
||||
pub domid: u32,
|
||||
pub image: String,
|
||||
pub loops: Vec<ContainerLoopInfo>,
|
||||
pub ipv4: String,
|
||||
}
|
||||
|
||||
impl Controller {
|
||||
@ -77,7 +80,16 @@ impl Controller {
|
||||
let uuid = Uuid::new_v4();
|
||||
let name = format!("hypha-{uuid}");
|
||||
let image_info = self.compile(image)?;
|
||||
let launch_config = LaunchInfo { env, run };
|
||||
|
||||
let ipv4 = self.allocate_ipv4()?;
|
||||
let launch_config = LaunchInfo {
|
||||
network: Some(LaunchNetwork {
|
||||
link: "eth0".to_string(),
|
||||
ipv4: format!("{}/24", ipv4),
|
||||
}),
|
||||
env,
|
||||
run,
|
||||
};
|
||||
|
||||
let cfgblk = ConfigBlock::new(&uuid, &image_info, config_bundle_path)?;
|
||||
cfgblk.build(&launch_config)?;
|
||||
@ -102,7 +114,10 @@ impl Controller {
|
||||
let cmdline_options = [if debug { "debug" } else { "quiet" }, "elevator=noop"];
|
||||
let cmdline = cmdline_options.join(" ");
|
||||
|
||||
let mac = MacAddr6::random().to_string().replace('-', ":");
|
||||
let mac = MacAddr6::random()
|
||||
.to_string()
|
||||
.replace('-', ":")
|
||||
.to_lowercase();
|
||||
let config = DomainConfig {
|
||||
backend_domid: 0,
|
||||
name: &name,
|
||||
@ -126,8 +141,8 @@ impl Controller {
|
||||
vifs: vec![DomainNetworkInterface {
|
||||
mac: &mac,
|
||||
mtu: 1500,
|
||||
bridge: "xenbr0",
|
||||
script: "/etc/xen/scripts/vif-bridge",
|
||||
bridge: None,
|
||||
script: None,
|
||||
}],
|
||||
filesystems: vec![],
|
||||
extra_keys: vec![
|
||||
@ -144,6 +159,7 @@ impl Controller {
|
||||
),
|
||||
),
|
||||
("hypha/image".to_string(), image.to_string()),
|
||||
("hypha/ipv4".to_string(), ipv4.to_string()),
|
||||
],
|
||||
};
|
||||
match self.client.create(&config) {
|
||||
@ -267,12 +283,18 @@ impl Controller {
|
||||
.store
|
||||
.read_string_optional(&format!("{}/hypha/loops", &dom_path))?
|
||||
.unwrap_or("".to_string());
|
||||
let ipv4 = self
|
||||
.client
|
||||
.store
|
||||
.read_string_optional(&format!("{}/hypha/ipv4", &dom_path))?
|
||||
.unwrap_or("unknown".to_string());
|
||||
let loops = Controller::parse_loop_set(&loops);
|
||||
containers.push(ContainerInfo {
|
||||
uuid,
|
||||
domid,
|
||||
image,
|
||||
loops,
|
||||
ipv4,
|
||||
});
|
||||
}
|
||||
Ok(containers)
|
||||
@ -308,4 +330,37 @@ impl Controller {
|
||||
})
|
||||
.collect::<Vec<ContainerLoopInfo>>()
|
||||
}
|
||||
|
||||
fn allocate_ipv4(&mut self) -> Result<Ipv4Addr> {
|
||||
let network = Ipv4Network::new(Ipv4Addr::new(192, 168, 42, 0), 24)?;
|
||||
let mut used: Vec<Ipv4Addr> = vec![
|
||||
Ipv4Addr::new(192, 168, 42, 0),
|
||||
Ipv4Addr::new(192, 168, 42, 1),
|
||||
Ipv4Addr::new(192, 168, 42, 255),
|
||||
];
|
||||
for domid_candidate in self.client.store.list_any("/local/domain")? {
|
||||
let dom_path = format!("/local/domain/{}", domid_candidate);
|
||||
let ip_path = format!("{}/hypha/ipv4", dom_path);
|
||||
let existing_ip = self.client.store.read_string_optional(&ip_path)?;
|
||||
if let Some(existing_ip) = existing_ip {
|
||||
used.push(Ipv4Addr::from_str(&existing_ip)?);
|
||||
}
|
||||
}
|
||||
|
||||
let mut found: Option<Ipv4Addr> = None;
|
||||
for ip in network.iter() {
|
||||
if !used.contains(&ip) {
|
||||
found = Some(ip);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if found.is_none() {
|
||||
return Err(anyhow!(
|
||||
"unable to find ipv4 to allocate to container, ipv4 addresses are exhausted"
|
||||
));
|
||||
}
|
||||
|
||||
Ok(found.unwrap())
|
||||
}
|
||||
}
|
||||
|
@ -2,4 +2,5 @@ pub mod autoloop;
|
||||
pub mod container;
|
||||
pub mod ctl;
|
||||
pub mod image;
|
||||
pub mod network;
|
||||
pub mod shared;
|
||||
|
47
hypha/src/network/mod.rs
Normal file
47
hypha/src/network/mod.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::str::FromStr;
|
||||
|
||||
use advmac::MacAddr6;
|
||||
use anyhow::{anyhow, Result};
|
||||
use smoltcp::iface::{Config, Interface, SocketSet};
|
||||
use smoltcp::phy::{self, RawSocket};
|
||||
use smoltcp::time::Instant;
|
||||
use smoltcp::wire::{EthernetAddress, HardwareAddress, IpCidr};
|
||||
|
||||
pub struct HyphaNetwork {
|
||||
pub device: RawSocket,
|
||||
pub addresses: Vec<IpCidr>,
|
||||
}
|
||||
|
||||
impl HyphaNetwork {
|
||||
pub fn new(iface: &str, cidrs: &[&str]) -> Result<HyphaNetwork> {
|
||||
let device = RawSocket::new(iface, smoltcp::phy::Medium::Ethernet)?;
|
||||
let mut addresses: Vec<IpCidr> = Vec::new();
|
||||
for cidr in cidrs {
|
||||
let address =
|
||||
IpCidr::from_str(cidr).map_err(|_| anyhow!("failed to parse cidr: {}", *cidr))?;
|
||||
addresses.push(address);
|
||||
}
|
||||
Ok(HyphaNetwork { device, addresses })
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<()> {
|
||||
let mac = MacAddr6::random();
|
||||
let mac = HardwareAddress::Ethernet(EthernetAddress(mac.to_array()));
|
||||
let config = Config::new(mac);
|
||||
let mut iface = Interface::new(config, &mut self.device, Instant::now());
|
||||
iface.update_ip_addrs(|addrs| {
|
||||
addrs
|
||||
.extend_from_slice(&self.addresses)
|
||||
.expect("failed to set ip addresses");
|
||||
});
|
||||
|
||||
let mut sockets = SocketSet::new(vec![]);
|
||||
let fd = self.device.as_raw_fd();
|
||||
loop {
|
||||
let timestamp = Instant::now();
|
||||
iface.poll(timestamp, &mut self.device, &mut sockets);
|
||||
phy::wait(fd, iface.poll_delay(timestamp, &sockets))?;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,14 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct LaunchNetwork {
|
||||
pub link: String,
|
||||
pub ipv4: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct LaunchInfo {
|
||||
pub network: Option<LaunchNetwork>,
|
||||
pub env: Option<Vec<String>>,
|
||||
pub run: Option<Vec<String>>,
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
TARGET="x86_64-unknown-linux-gnu"
|
||||
TARGET="x86_64-unknown-linux-musl"
|
||||
|
||||
export RUSTFLAGS="-Ctarget-feature=+crt-static"
|
||||
cd "$(dirname "${0}")/.."
|
||||
|
@ -52,8 +52,8 @@ pub struct DomainFilesystem<'a> {
|
||||
pub struct DomainNetworkInterface<'a> {
|
||||
pub mac: &'a str,
|
||||
pub mtu: u32,
|
||||
pub bridge: &'a str,
|
||||
pub script: &'a str,
|
||||
pub bridge: Option<&'a str>,
|
||||
pub script: Option<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -524,24 +524,38 @@ impl XenClient {
|
||||
vif: &DomainNetworkInterface,
|
||||
) -> Result<()> {
|
||||
let id = 20 + index as u64;
|
||||
let backend_items: Vec<(&str, String)> = vec![
|
||||
let mut backend_items: Vec<(&str, String)> = vec![
|
||||
("frontend-id", domid.to_string()),
|
||||
("online", "1".to_string()),
|
||||
("state", "1".to_string()),
|
||||
("mac", vif.mac.to_string()),
|
||||
("mtu", vif.mtu.to_string()),
|
||||
("type", "vif".to_string()),
|
||||
("bridge", vif.bridge.to_string()),
|
||||
("handle", id.to_string()),
|
||||
("script", vif.script.to_string()),
|
||||
("hotplug-status", "".to_string()),
|
||||
];
|
||||
|
||||
if vif.bridge.is_some() {
|
||||
backend_items.extend_from_slice(&[("bridge", vif.bridge.unwrap().to_string())]);
|
||||
}
|
||||
|
||||
if vif.script.is_some() {
|
||||
backend_items.extend_from_slice(&[
|
||||
("script", vif.script.unwrap().to_string()),
|
||||
("hotplug-status", "".to_string()),
|
||||
]);
|
||||
} else {
|
||||
backend_items.extend_from_slice(&[
|
||||
("script", "".to_string()),
|
||||
("hotplug-status", "connected".to_string()),
|
||||
]);
|
||||
}
|
||||
|
||||
let frontend_items: Vec<(&str, String)> = vec![
|
||||
("backend-id", backend_domid.to_string()),
|
||||
("state", "1".to_string()),
|
||||
("mac", vif.mac.to_string()),
|
||||
("trusted", "1".to_string()),
|
||||
("mtu", vif.mtu.to_string()),
|
||||
];
|
||||
|
||||
self.device_add(
|
||||
|
@ -2,5 +2,5 @@
|
||||
set -e
|
||||
|
||||
cd "$(dirname "${0}")/.."
|
||||
cargo fmt --all
|
||||
cargo clippy --target x86_64-unknown-linux-gnu --fix --allow-dirty --allow-staged
|
||||
cargo fmt --all
|
||||
|
11
scripts/hyphanet-debug.sh
Executable file
11
scripts/hyphanet-debug.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if [ -z "${RUST_LOG}" ]
|
||||
then
|
||||
RUST_LOG="INFO"
|
||||
fi
|
||||
|
||||
cd "$(dirname "${0}")/.."
|
||||
cargo build --target x86_64-unknown-linux-gnu --bin hyphanet
|
||||
exec sudo RUST_LOG="${RUST_LOG}" target/x86_64-unknown-linux-gnu/debug/hyphanet "${@}"
|
Loading…
Reference in New Issue
Block a user