diff --git a/.github/workflows/release-oci.yaml b/.github/workflows/release-oci.yaml index 24dbdfa..7966514 100644 --- a/.github/workflows/release-oci.yaml +++ b/.github/workflows/release-oci.yaml @@ -4,7 +4,6 @@ on: push: branches: - master - - feat/container tags: - '*' paths-ignore: @@ -74,8 +73,8 @@ jobs: with: path: | target/kernel/ - key: ${{ runner.os }}-kernel-${{ hashFiles('/hack/kernel/**') }} - restore-keys: ${{ runner.os }}-kernel- + key: ${{ runner.os }}-kernel-${{ hashFiles('hack/kernel/**') }} + restore-keys: ${{ runner.os }}-kernel-${{ hashFiles('hack/kernel/**') }} - name: Build Kernel run: make kernel diff --git a/Cargo.lock b/Cargo.lock index c3fede4..1dc3886 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "acpi_tables" diff --git a/Makefile b/Makefile index 80456e4..b488731 100644 --- a/Makefile +++ b/Makefile @@ -20,5 +20,5 @@ test: clippy include hack/hack.mk -feos_client: - cd client && cargo build +cli: + cargo build --bin feos-cli diff --git a/hack/initramfs/make.mk b/hack/initramfs/make.mk index 15590b7..cf42507 100644 --- a/hack/initramfs/make.mk +++ b/hack/initramfs/make.mk @@ -2,8 +2,10 @@ initramfs: target/cloud-hypervisor target/cloud-hypervisor-firmware container-re mkdir -p target/rootfs/bin mkdir -p target/rootfs/etc/feos mkdir -p target/rootfs/usr/share/cloud-hypervisor + mkdir -p target/rootfs/usr/share/feos cp target/cloud-hypervisor/target/cloud-hypervisor-static target/rootfs/bin/cloud-hypervisor cp target/cloud-hypervisor/target/hypervisor-fw target/rootfs/usr/share/cloud-hypervisor + cp target/kernel/vmlinuz target/rootfs/usr/share/feos/vmlinuz cp target/x86_64-unknown-linux-musl/release/feos target/rootfs/bin/feos sudo chown -R `whoami` target/rootfs/etc/feos/ cd target/rootfs && rm -f init && ln -s bin/feos init diff --git a/hack/initramfs/mk-initramfs b/hack/initramfs/mk-initramfs index 4057ab1..635db9f 100755 --- a/hack/initramfs/mk-initramfs +++ b/hack/initramfs/mk-initramfs @@ -35,6 +35,10 @@ echo "Install libraries for FeOS (copy from host)" install_libs $ROOTFS_DIR/bin/feos cp -ra /etc/ssl/certs usr/local/ssl/ +echo "Create initramfs (non-compressed)" +find . -print0 | cpio --create --format=newc --null > "${TARGET_DIR}/initramfs" +mv "${TARGET_DIR}/initramfs" "${ROOTFS_DIR}/usr/share/feos/" + echo "Create initramfs.zst" find . -print0 | cpio --create --format=newc --null | zstd -3 > "${TARGET_DIR}/initramfs.zst" diff --git a/hack/kernel/config/feos-linux-6.6.60.config b/hack/kernel/config/feos-linux-6.6.60.config index 30bf9c7..f5b106f 100644 --- a/hack/kernel/config/feos-linux-6.6.60.config +++ b/hack/kernel/config/feos-linux-6.6.60.config @@ -172,21 +172,21 @@ CONFIG_CC_NO_ARRAY_BOUNDS=y CONFIG_ARCH_SUPPORTS_INT128=y # CONFIG_NUMA_BALANCING is not set CONFIG_CGROUPS=y +CONFIG_MEMCG=y # CONFIG_CGROUP_FAVOR_DYNMODS is not set -# CONFIG_MEMCG is not set # CONFIG_BLK_CGROUP is not set CONFIG_CGROUP_SCHED=y CONFIG_FAIR_GROUP_SCHED=y # CONFIG_CFS_BANDWIDTH is not set # CONFIG_RT_GROUP_SCHED is not set CONFIG_SCHED_MM_CID=y -# CONFIG_CGROUP_PIDS is not set +CONFIG_CGROUP_PIDS=y # CONFIG_CGROUP_RDMA is not set CONFIG_CGROUP_FREEZER=y # CONFIG_CGROUP_HUGETLB is not set CONFIG_CPUSETS=y CONFIG_PROC_PID_CPUSET=y -# CONFIG_CGROUP_DEVICE is not set +CONFIG_CGROUP_DEVICE=y CONFIG_CGROUP_CPUACCT=y # CONFIG_CGROUP_PERF is not set # CONFIG_CGROUP_MISC is not set @@ -1544,8 +1544,8 @@ CONFIG_NET_CORE=y # CONFIG_AMT is not set # CONFIG_MACSEC is not set # CONFIG_NETCONSOLE is not set -# CONFIG_TUN is not set # CONFIG_TUN_VNET_CROSS_LE is not set +CONFIG_TUN=y CONFIG_VETH=y CONFIG_VIRTIO_NET=y CONFIG_NLMON=y @@ -3091,7 +3091,7 @@ CONFIG_QFMT_V2=y CONFIG_QUOTACTL=y CONFIG_AUTOFS_FS=y # CONFIG_FUSE_FS is not set -# CONFIG_OVERLAY_FS is not set +CONFIG_OVERLAY_FS=y # # Caches diff --git a/hack/libvirt/libvirt-kvm.xml b/hack/libvirt/libvirt-kvm.xml index beb513c..3c6cc78 100644 --- a/hack/libvirt/libvirt-kvm.xml +++ b/hack/libvirt/libvirt-kvm.xml @@ -5,7 +5,7 @@ - 2048 + 8048 4 hvm @@ -17,7 +17,7 @@ - + diff --git a/proto/feos.proto b/proto/feos.proto index 28565cb..3df8009 100644 --- a/proto/feos.proto +++ b/proto/feos.proto @@ -2,24 +2,30 @@ syntax = "proto3"; package feos_grpc; service FeosGrpc { - rpc Reboot (RebootRequest) returns (RebootResponse); - rpc Shutdown (ShutdownRequest) returns (ShutdownResponse); - rpc HostInfo (HostInfoRequest) returns (HostInfoResponse); + rpc Reboot (RebootRequest) returns (RebootResponse); + rpc Shutdown (ShutdownRequest) returns (ShutdownResponse); + rpc HostInfo (HostInfoRequest) returns (HostInfoResponse); - rpc Ping (Empty) returns (Empty); - - rpc FetchImage (FetchImageRequest) returns (FetchImageResponse); + rpc Ping (Empty) returns (Empty); - rpc CreateVM (CreateVMRequest) returns (CreateVMResponse); - rpc GetVM (GetVMRequest) returns (GetVMResponse); - rpc BootVM (BootVMRequest) returns (BootVMResponse); - rpc ConsoleVM (stream ConsoleVMRequest) returns (stream ConsoleVMResponse); - rpc AttachNicVM (AttachNicVMRequest) returns (AttachNicVMResponse); - rpc ShutdownVM (ShutdownVMRequest) returns (ShutdownVMResponse); - rpc PingVM (PingVMRequest) returns (PingVMResponse); + rpc FetchImage (FetchImageRequest) returns (FetchImageResponse); - rpc GetFeOSKernelLogs (GetFeOSKernelLogRequest) returns (stream GetFeOSKernelLogResponse); - rpc GetFeOSLogs(GetFeOsLogRequest) returns (stream GetFeOsLogResponse); + rpc CreateVM (CreateVMRequest) returns (CreateVMResponse); + rpc GetVM (GetVMRequest) returns (GetVMResponse); + rpc BootVM (BootVMRequest) returns (BootVMResponse); + rpc ConsoleVM (stream ConsoleVMRequest) returns (stream ConsoleVMResponse); + rpc AttachNicVM (AttachNicVMRequest) returns (AttachNicVMResponse); + rpc ShutdownVM (ShutdownVMRequest) returns (ShutdownVMResponse); + rpc PingVM (PingVMRequest) returns (PingVMResponse); + + rpc GetFeOSKernelLogs (GetFeOSKernelLogRequest) returns (stream GetFeOSKernelLogResponse); + rpc GetFeOSLogs(GetFeOsLogRequest) returns (stream GetFeOsLogResponse); +} + +enum NicType { + MAC = 0; + PCI = 1; + TAP = 2; } message ConsoleVMRequest { @@ -40,16 +46,20 @@ message GetFeOSKernelLogResponse { message GetFeOsLogRequest {} message GetFeOsLogResponse { - string message = 1; + string message = 1; } message AttachNicVMRequest { - string uuid = 1; - string mac_address = 2; - string pci_address = 3; + string uuid = 1; + NicType nic_type = 2; + + oneof nic_data { + string mac_address = 3; + string pci_address = 4; + } } -message AttachNicVMResponse {} +message AttachNicVMResponse {} message RebootRequest {} message RebootResponse {} @@ -67,14 +77,13 @@ message HostInfoResponse { } message NetInterface { - string name = 1; - string pci_address = 2; - string mac_address = 3; + string name = 1; + string pci_address = 2; + string mac_address = 3; } message Empty {} - message FetchImageRequest { string image = 1; } @@ -83,12 +92,11 @@ message FetchImageResponse { string uuid = 1; } - message CreateVMRequest { - uint32 cpu = 1; - uint64 memory_bytes = 2; + uint32 cpu = 1; + uint64 memory_bytes = 2; string image_uuid = 3; - optional string ignition = 4; + optional string ignition = 4; } message CreateVMResponse { @@ -96,14 +104,14 @@ message CreateVMResponse { } message GetVMRequest { - string uuid = 1; + string uuid = 1; } message GetVMResponse { string info = 1; } message BootVMRequest { - string uuid = 1; + string uuid = 1; } message BootVMResponse {} @@ -115,4 +123,4 @@ message ShutdownVMResponse {} message PingVMRequest { string uuid = 1; } -message PingVMResponse {} \ No newline at end of file +message PingVMResponse {} diff --git a/src/bin/feos_cli/client.rs b/src/bin/feos_cli/client.rs index cec4031..fbd60df 100644 --- a/src/bin/feos_cli/client.rs +++ b/src/bin/feos_cli/client.rs @@ -55,8 +55,10 @@ pub enum Command { }, AttachNicVM { uuid: String, - mac_address: String, - pci_address: String, + #[structopt(long)] + mac_address: Option, + #[structopt(long)] + pci_address: Option, }, GetFeOSKernelLogs, GetFeOSLogs, @@ -152,7 +154,7 @@ pub async fn run_client(opt: Opt) -> Result<(), Box> { Command::PingVM { uuid } => { let request = Request::new(PingVmRequest { uuid }); let response = client.ping_vm(request).await?; - println!("BOOT VM RESPONSE={:?}", response); + println!("PING VM RESPONSE={:?}", response); } Command::ShutdownVM { uuid } => { let request = Request::new(ShutdownVmRequest { uuid }); @@ -164,11 +166,31 @@ pub async fn run_client(opt: Opt) -> Result<(), Box> { mac_address, pci_address, } => { + let (nic_type, nic_data) = match (&mac_address, &pci_address) { + (Some(mac), None) => ( + feos_grpc::NicType::Mac as i32, + Some(attach_nic_vm_request::NicData::MacAddress(mac.clone())), + ), + (None, Some(pci)) => ( + feos_grpc::NicType::Pci as i32, + Some(attach_nic_vm_request::NicData::PciAddress(pci.clone())), + ), + (None, None) => (feos_grpc::NicType::Tap as i32, None), + (Some(_), Some(_)) => { + eprintln!("Error: Provide either --mac_address or --pci_address, not both."); + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Provide either --mac_address or --pci_address, not both.", + ))); + } + }; + let request = Request::new(AttachNicVmRequest { uuid, - mac_address, - pci_address, + nic_type, + nic_data, }); + let response = client.attach_nic_vm(request).await?; println!("ATTACH NIC VM RESPONSE={:?}", response); } diff --git a/src/daemon.rs b/src/daemon.rs index 7e1fcfb..392e0d2 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -7,12 +7,12 @@ use tonic::{transport::Server, Request, Response, Status}; use crate::feos_grpc; use crate::feos_grpc::feos_grpc_server::*; use crate::feos_grpc::{ - AttachNicVmRequest, AttachNicVmResponse, BootVmRequest, BootVmResponse, ConsoleVmResponse, - CreateVmRequest, CreateVmResponse, Empty, FetchImageRequest, FetchImageResponse, - GetFeOsKernelLogRequest, GetFeOsKernelLogResponse, GetFeOsLogRequest, GetFeOsLogResponse, - GetVmRequest, GetVmResponse, HostInfoRequest, HostInfoResponse, NetInterface, PingVmRequest, - PingVmResponse, RebootRequest, RebootResponse, ShutdownRequest, ShutdownResponse, - ShutdownVmRequest, ShutdownVmResponse, + attach_nic_vm_request::NicData, AttachNicVmRequest, AttachNicVmResponse, BootVmRequest, + BootVmResponse, ConsoleVmResponse, CreateVmRequest, CreateVmResponse, Empty, FetchImageRequest, + FetchImageResponse, GetFeOsKernelLogRequest, GetFeOsKernelLogResponse, GetFeOsLogRequest, + GetFeOsLogResponse, GetVmRequest, GetVmResponse, HostInfoRequest, HostInfoResponse, + NetInterface, NicType as ProtoNicType, PingVmRequest, PingVmResponse, RebootRequest, + RebootResponse, ShutdownRequest, ShutdownResponse, ShutdownVmRequest, ShutdownVmResponse, }; use crate::host; use crate::ringbuffer::*; @@ -23,12 +23,13 @@ use hyper_util::rt::TokioIo; use nix::libc::VMADDR_CID_ANY; use nix::unistd::Uid; use std::sync::Arc; -use tokio::fs::File; -use tokio::io::AsyncReadExt; -use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; -use tokio::net::UnixStream; -use tokio::sync::{mpsc, Mutex}; -use tokio::time::{sleep, Duration}; +use tokio::{ + fs::File, + io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}, + net::UnixStream, + sync::{mpsc, Mutex}, + time::{sleep, Duration}, +}; use tokio_stream::wrappers::ReceiverStream; use tokio_vsock::{VsockAddr, VsockListener}; use tonic::transport::{Endpoint, Uri}; @@ -37,13 +38,14 @@ use uuid::Uuid; use crate::filesystem::mount_virtual_filesystems; use crate::isolated_container::{isolated_container_service, IsolatedContainerAPI}; -use crate::network::{configure_network_devices, configure_sriov}; +use crate::network::configure_network_devices; #[derive(Debug)] pub struct FeOSAPI { vmm: Arc, buffer: Arc, log_receiver: Arc>>, + network: Arc, } impl FeOSAPI { @@ -51,11 +53,13 @@ impl FeOSAPI { vmm: Arc, buffer: Arc, log_receiver: Arc>>, + network: Arc, ) -> Self { FeOSAPI { vmm, buffer, log_receiver, + network, } } @@ -80,6 +84,13 @@ impl FeOSAPI { "failed to connect to cloud hypervisor", ) } + vm::Error::NetworkingError(e) => { + info!("failed to prepare network {:?}", e); + Status::new( + tonic::Code::Internal, + format!("failed to prepare network: {:?}", e), + ) + } vm::Error::CHApiFailure(e) => { info!("failed to connect to cloud hypervisor api: {:?}", e); Status::new( @@ -240,6 +251,13 @@ impl FeosGrpc for FeOSAPI { .map_err(|_| Status::invalid_argument("Failed to parse UUID"))?; self.vmm.boot_vm(id).map_err(|e| self.handle_error(e))?; + //TODO remove this sleep + sleep(Duration::from_secs(2)).await; + self.network + .start_dhcp(id) + .await + .map_err(vm::Error::NetworkingError) + .map_err(|e| self.handle_error(e))?; Ok(Response::new(feos_grpc::BootVmResponse {})) } @@ -321,25 +339,46 @@ impl FeosGrpc for FeOSAPI { &self, request: Request, ) -> Result, Status> { - info!("Got attach_nic_vm request"); + info!("Received AttachNicVM request"); - let id = request.get_ref().uuid.to_owned(); - let id = - Uuid::parse_str(&id).map_err(|_| Status::invalid_argument("failed to parse uuid"))?; + let req = request.into_inner(); - let net_config = if !request.get_ref().mac_address.is_empty() { - vm::NetworkMode::MACAddress(request.get_ref().mac_address.clone()) - } else if !request.get_ref().pci_address.is_empty() { - vm::NetworkMode::PCIAddress(request.get_ref().pci_address.clone()) - } else { - return Err(Status::invalid_argument("no network config provided")); + let vm_uuid = Uuid::parse_str(&req.uuid) + .map_err(|_| Status::invalid_argument("Failed to parse UUID"))?; + + let net_config = match ProtoNicType::try_from(req.nic_type) { + Ok(ProtoNicType::Mac) => { + if let Some(NicData::MacAddress(mac)) = req.nic_data { + vm::NetworkMode::MACAddress(mac) + } else { + return Err(Status::invalid_argument( + "mac_address must be provided for NIC type MAC", + )); + } + } + Ok(ProtoNicType::Pci) => { + if let Some(NicData::PciAddress(pci)) = req.nic_data { + vm::NetworkMode::PCIAddress(pci) + } else { + return Err(Status::invalid_argument( + "pci_address must be provided for NIC type PCI", + )); + } + } + Ok(ProtoNicType::Tap) => { + let tap_name = network::Manager::device_name(&vm_uuid); + vm::NetworkMode::TAPDeviceName(tap_name) + } + Err(_) => { + return Err(Status::invalid_argument("Invalid NIC type provided")); + } }; self.vmm - .add_net_device(id, net_config) + .add_net_device(vm_uuid, net_config) .map_err(|e| self.handle_error(e))?; - Ok(Response::new(feos_grpc::AttachNicVmResponse {})) + Ok(Response::new(AttachNicVmResponse {})) } async fn shutdown_vm( @@ -351,6 +390,12 @@ impl FeosGrpc for FeOSAPI { let id = Uuid::parse_str(&request.get_ref().uuid) .map_err(|_| Status::invalid_argument("Failed to parse UUID"))?; + self.network + .stop_dhcp(id) + .await + .map_err(vm::Error::NetworkingError) + .map_err(|e| self.handle_error(e))?; + // TODO differentiate between kill and shutdown self.vmm.kill_vm(id).map_err(|e| self.handle_error(e))?; @@ -480,7 +525,7 @@ pub async fn daemon_start( log_receiver: Arc>>, is_nested: bool, ) -> Result<(), Box> { - let api = FeOSAPI::new(vmm.clone(), buffer, log_receiver); + let api = FeOSAPI::new(vmm.clone(), buffer, log_receiver, network.clone()); let isolated_container_api = IsolatedContainerAPI::new(vmm, network); if is_nested { @@ -513,10 +558,9 @@ pub async fn daemon_start( Ok(()) } -pub async fn start_feos(ipv6_address: Ipv6Addr, prefix_length: u8) -> Result<(), String> { +pub async fn start_feos(mut ipv6_address: Ipv6Addr, mut prefix_length: u8) -> Result<(), String> { println!( " - ███████╗███████╗ ██████╗ ███████╗ ██╔════╝██╔════╝██╔═══██╗██╔════╝ █████╗ █████╗ ██║ ██║███████╗ @@ -537,6 +581,10 @@ pub async fn start_feos(ipv6_address: Ipv6Addr, prefix_length: u8) -> Result<(), warn!("Not running as root! (uid: {})", Uid::current()); } + if ipv6_address == Ipv6Addr::UNSPECIFIED { + info!("No --ipam flag found. Expecting Prefix Delegation from the dhcpv6 server"); + } + if std::process::id() == 1 { info!("Mounting virtual filesystems..."); mount_virtual_filesystems(); @@ -554,22 +602,25 @@ pub async fn start_feos(ipv6_address: Ipv6Addr, prefix_length: u8) -> Result<(), if std::process::id() == 1 { info!("Configuring network devices..."); - configure_network_devices() + if let Some((delegated_prefix, delegated_prefix_length)) = configure_network_devices() .await - .expect("could not configure network devices"); + .expect("could not configure network devices") + { + ipv6_address = delegated_prefix; + prefix_length = delegated_prefix_length; + } } // Special stuff for pid 1 if std::process::id() == 1 && !is_nested { - info!("Configuring sriov..."); - const VFS_NUM: u32 = 125; + info!("Skip configuring sriov..."); + /*const VFS_NUM: u32 = 125; if let Err(e) = configure_sriov(VFS_NUM).await { warn!("failed to configure sriov: {}", e.to_string()) - } + }*/ } let vmm = Manager::new(String::from("cloud-hypervisor")); - let network_manager = network::Manager::new(ipv6_address, prefix_length); info!("Starting FeOS daemon..."); diff --git a/src/host/info.rs b/src/host/info.rs index e9dc0fd..c67d988 100644 --- a/src/host/info.rs +++ b/src/host/info.rs @@ -33,7 +33,7 @@ fn get_pci_address(interface_name: &str) -> Option { fn get_mac_address(interface_name: &str) -> Option { let path = format!("/sys/class/net/{}/address", interface_name); if let Ok(mac) = fs::read_to_string(path) { - return Some(mac.trim().to_string()); + Some(mac.trim().to_string()) } else { None } diff --git a/src/isolated_container/mod.rs b/src/isolated_container/mod.rs index 6451fb4..9d66f07 100644 --- a/src/isolated_container/mod.rs +++ b/src/isolated_container/mod.rs @@ -8,10 +8,12 @@ use hyper_util::rt::TokioIo; use isolated_container_service::isolated_container_service_server::IsolatedContainerService; use log::info; use std::sync::Arc; +use std::time::Duration; use std::{collections::HashMap, sync::Mutex}; use std::{fmt::Debug, io, path::PathBuf}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::UnixStream; +use tokio::time::sleep; use tonic::transport::{Channel, Endpoint, Uri}; use tonic::{transport, Request, Response, Status}; use tower::service_fn; @@ -117,7 +119,7 @@ impl IsolatedContainerAPI { id, 2, // TODO make configurable through container request - 536870912, + 4294967296, vm::BootMode::KernelBoot(vm::KernelBootMode { kernel: PathBuf::from("/usr/share/feos/vmlinuz"), initramfs: PathBuf::from("/usr/share/feos/initramfs"), @@ -129,8 +131,6 @@ impl IsolatedContainerAPI { ) .map_err(Error::VMError)?; - self.vmm.boot_vm(id).map_err(Error::VMError)?; - self.vmm .add_net_device( id, @@ -138,6 +138,8 @@ impl IsolatedContainerAPI { ) .map_err(Error::VMError)?; + self.vmm.boot_vm(id).map_err(Error::VMError)?; + Ok(()) } @@ -191,6 +193,8 @@ impl IsolatedContainerService for IsolatedContainerAPI { let id = Uuid::new_v4(); self.prepare_vm(id).map_err(|e| self.handle_error(e))?; + //TODO get rid of sleep + sleep(Duration::from_secs(2)).await; self.network .start_dhcp(id) @@ -209,9 +213,15 @@ impl IsolatedContainerService for IsolatedContainerAPI { let response = client .create_container(request) .await - .map_err(|_| match self.vmm.kill_vm(id) { - Ok(_) => Error::Error("failed to create container".to_string()), - Err(e) => Error::Error(format!("failed to create container: {:?}", e)), + .map_err(|e| { + info!("Failed to create container: {:?}", e); + match self.vmm.kill_vm(id) { + Ok(_) => Error::Error("failed to create container".to_string()), + Err(kill_error) => Error::Error(format!( + "failed to create container: {:?}, kill VM error: {:?}", + e, kill_error + )), + } }) .map_err(|e| self.handle_error(e))?; diff --git a/src/main.rs b/src/main.rs index 42e7ca0..e96f6ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,11 +6,11 @@ use std::env; use std::net::Ipv6Addr; use std::str::FromStr; use std::{env::args, ffi::CString}; - use tokio::io; use tokio::io::{AsyncBufReadExt, BufReader}; -#[tokio::main] +//TODO remove this in future, the reason https://github.com/youki-dev/youki/issues/2144 +#[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), String> { let mut ipv6_address = Ipv6Addr::UNSPECIFIED; let mut prefix_length = 64; @@ -36,14 +36,18 @@ async fn main() -> Result<(), String> { fn parse_command_line() -> Result<(Ipv6Addr, u8), String> { let args: Vec = env::args().collect(); - if args.len() != 3 { - return Err("Usage: --ipam /".into()); + if args.len() < 2 { + return Ok((Ipv6Addr::UNSPECIFIED, 64)); } if args[1] != "--ipam" { return Err("Expected '--ipam' flag".into()); } + if args.len() != 3 { + return Err("Usage: --ipam /".into()); + } + let prefix_input = &args[2]; let parts: Vec<&str> = prefix_input.split('/').collect(); diff --git a/src/move_root.rs b/src/move_root.rs index 3de343b..269457f 100644 --- a/src/move_root.rs +++ b/src/move_root.rs @@ -1,3 +1,4 @@ +use crate::fsmount::{fsconfig, fsmount, fsopen, FSCONFIG_CMD_CREATE, FSCONFIG_SET_STRING}; use nix::{ fcntl::{openat, OFlag}, mount::{mount, MsFlags}, @@ -14,8 +15,6 @@ use std::{ path::Path, }; -use crate::fsmount::{fsconfig, fsmount, fsopen, FSCONFIG_CMD_CREATE, FSCONFIG_SET_STRING}; - #[allow(unsafe_code)] pub fn get_root_fstype() -> Result> { let proc_fs_raw = fsopen("proc", 0)?; diff --git a/src/network/dhcpv6.rs b/src/network/dhcpv6.rs index 55671a2..488a24b 100644 --- a/src/network/dhcpv6.rs +++ b/src/network/dhcpv6.rs @@ -252,15 +252,26 @@ pub fn is_dhcpv6_needed(interface_name: String, ignore_ra_flag: bool) -> Option< sender_ipv6_address } +pub struct PrefixInfo { + pub prefix: Ipv6Addr, + pub prefix_length: u8, +} + +pub struct Dhcpv6Result { + pub address: Ipv6Addr, + pub prefix: Option, +} + pub async fn run_dhcpv6_client( interface_name: String, -) -> Result> { +) -> Result> { let chaddr = vec![ 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, ]; let random_xid: [u8; 3] = [0x12, 0x34, 0x56]; let multicast_address = "[FF02::1:2]:547".parse::().unwrap(); let mut ia_addr_confirm: Option = None; + let mut ia_pd_confirm: Option = None; let interface_index = get_interface_index(interface_name.clone()).await?; let socket = create_multicast_socket(interface_name.clone(), interface_index, 546)?; @@ -278,6 +289,8 @@ pub async fn run_dhcpv6_client( oro.opts.push(OptionCode::ClientFqdn); oro.opts.push(OptionCode::SntpServers); oro.opts.push(OptionCode::RapidCommit); + oro.opts.push(OptionCode::IAPD); + oro.opts.push(OptionCode::IAPrefix); msg.opts_mut().insert(DhcpOption::ORO(oro)); @@ -300,6 +313,27 @@ pub async fn run_dhcpv6_client( msg.opts_mut().insert(DhcpOption::IANA(iana_instance)); + // Request Prefix Delegation + let iaprefix_instance = IAPrefix { + preferred_lifetime: 0, + prefix_len: 80, + opts: DhcpOptions::default(), + valid_lifetime: 0, + prefix_ip: Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0), + }; + + let mut iapd_opts = DhcpOptions::default(); + iapd_opts.insert(DhcpOption::IAPrefix(iaprefix_instance)); + + let iapd_instance = IAPD { + id: 456, + t1: 3600, + t2: 7200, + opts: iapd_opts, + }; + + msg.opts_mut().insert(DhcpOption::IAPD(iapd_instance)); + let mut buf = Vec::new(); let mut encoder = Encoder::new(&mut buf); msg.encode(&mut encoder)?; @@ -311,6 +345,7 @@ pub async fn run_dhcpv6_client( let response = Message::decode(&mut dhcproto::v6::Decoder::new(&recv_buf[..size]))?; let mut serverid: Option<&DhcpOption> = None; let mut ia_addr: Option<&DhcpOption> = None; + let mut ia_pd: Option<&DhcpOption> = None; match response.msg_type() { MessageType::Advertise => { @@ -320,6 +355,11 @@ pub async fn run_dhcpv6_client( ia_addr = Some(ia_addr_opt); } } + if let Some(DhcpOption::IAPD(iapd)) = response.opts().get(OptionCode::IAPD) { + if let Some(iaprefix_opt) = iapd.opts.get(OptionCode::IAPrefix) { + ia_pd = Some(iaprefix_opt); + } + } if let Some(server_option) = response.opts().get(OptionCode::ServerId) { serverid = Some(server_option); } @@ -361,6 +401,22 @@ pub async fn run_dhcpv6_client( warn!("No IP was found in Advertise message"); } + if let Some(DhcpOption::IAPrefix(iaprefix)) = ia_pd { + let iapd_instance = IAPD { + id: 456, + t1: 3600, + t2: 7200, + opts: { + let mut opts = DhcpOptions::default(); + opts.insert(DhcpOption::IAPrefix((*iaprefix).clone())); + opts + }, + }; + request_msg + .opts_mut() + .insert(DhcpOption::IAPD(iapd_instance)); + } + buf.clear(); request_msg.encode(&mut Encoder::new(&mut buf))?; socket.send_to(&buf, multicast_address).await?; @@ -371,6 +427,13 @@ pub async fn run_dhcpv6_client( ia_addr_confirm = Some((*ia_addr_opt).clone()); } } + if let Some(DhcpOption::IAPD(iapd)) = response.opts().get(OptionCode::IAPD) { + if let Some(DhcpOption::IAPrefix(iaprefix)) = + iapd.opts.get(OptionCode::IAPrefix) + { + ia_pd_confirm = Some((*iaprefix).clone()); + } + } let mut confirm_msg = Message::new(MessageType::Confirm); confirm_msg.set_xid(random_xid); @@ -396,7 +459,25 @@ pub async fn run_dhcpv6_client( "DHCPv6 processing finished, setting IPv6 address {}", ia_a.addr ); - return Ok(ia_a.addr); + + let prefix_info = ia_pd_confirm.map(|iaprefix| PrefixInfo { + prefix: iaprefix.prefix_ip, + prefix_length: iaprefix.prefix_len, + }); + + if let Some(ref pfx) = prefix_info { + info!( + "Received delegated prefix {} with length {}", + pfx.prefix, pfx.prefix_length + ); + } else { + info!("No prefix delegation received."); + } + + return Ok(Dhcpv6Result { + address: ia_a.addr, + prefix: prefix_info, + }); } Err("No valid address received".into()) @@ -926,7 +1007,7 @@ pub fn add_to_ipv6(addr: Ipv6Addr, prefix_length: u8, increment: u128) -> Ipv6Ad Ipv6Addr::from(new_addr) } -async fn _set_ipv6_gateway( +pub async fn set_ipv6_gateway( handle: &Handle, interface_name: &str, ipv6_gateway: Ipv6Addr, diff --git a/src/network/mod.rs b/src/network/mod.rs index e75238b..642ba74 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -16,8 +16,8 @@ use crate::network::dhcpv6::{ add_ipv6_route, add_to_ipv6, adjust_base_ip, run_dhcpv6_server, IpRange, }; use radv::start_radv_server; +pub use utils::_configure_sriov; pub use utils::configure_network_devices; -pub use utils::configure_sriov; #[derive(Debug)] pub enum Error { diff --git a/src/network/utils.rs b/src/network/utils.rs index 42d319b..ceb8e81 100644 --- a/src/network/utils.rs +++ b/src/network/utils.rs @@ -1,9 +1,12 @@ +use std::fs::File; use std::io; +use std::io::Write; use crate::network::dhcpv6::*; use futures::stream::TryStreamExt; -use log::{info, warn}; -use rtnetlink::new_connection; +use log::{debug, info, warn}; +use rtnetlink::{new_connection, Handle, IpVersion}; +use std::net::Ipv6Addr; use tokio::fs::{read_link, OpenOptions}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::time::{self, sleep, Duration}; @@ -19,16 +22,20 @@ use pnet::packet::udp::UdpPacket; use pnet::packet::Packet; use netlink_packet_route::neighbour::*; +use netlink_packet_route::route::{RouteAddress, RouteAttribute}; const INTERFACE_NAME: &str = "eth0"; -pub async fn configure_network_devices() -> Result<(), String> { +pub async fn configure_network_devices() -> Result, String> { let ignore_ra_flag = true; // Till the RA has the correct flags (O or M), ignore the flag let interface_name = String::from(INTERFACE_NAME); let (connection, handle, _) = new_connection().unwrap(); let mut mac_bytes_option: Option> = None; + let mut delegated_prefix_option: Option<(Ipv6Addr, u8)> = None; tokio::spawn(connection); + enable_ipv6_forwarding().map_err(|e| format!("Failed to enable ipv6 forwarding: {}", e))?; + let mut link_ts = handle .link() .get() @@ -107,7 +114,27 @@ pub async fn configure_network_devices() -> Result<(), String> { if let Some(ipv6_gateway) = is_dhcpv6_needed(interface_name.clone(), ignore_ra_flag) { time::sleep(Duration::from_secs(4)).await; match run_dhcpv6_client(interface_name.clone()).await { - Ok(addr) => send_neigh_solicitation(interface_name.clone(), &ipv6_gateway, &addr), + Ok(result) => { + send_neigh_solicitation(interface_name.clone(), &ipv6_gateway, &result.address); + if let Some(prefix_info) = result.prefix { + let delegated_prefix = prefix_info.prefix; + let prefix_length = prefix_info.prefix_length; + info!( + "Received delegated prefix {} with length {}", + delegated_prefix, prefix_length + ); + delegated_prefix_option = Some((delegated_prefix, prefix_length)); + } else { + info!("No prefix delegation received."); + } + info!( + "Setting IPv6 gateway to {} on interface {}", + ipv6_gateway, interface_name + ); + if let Err(e) = set_ipv6_gateway(&handle, &interface_name, ipv6_gateway).await { + warn!("Failed to set IPv6 gateway: {}", e); + } + } Err(e) => warn!("Error: {}", e), } } @@ -130,6 +157,127 @@ pub async fn configure_network_devices() -> Result<(), String> { } } + Ok(delegated_prefix_option) +} + +pub fn enable_ipv6_forwarding() -> Result<(), std::io::Error> { + let forwarding_paths = ["/proc/sys/net/ipv6/conf/all/forwarding"]; + + for path in forwarding_paths { + let mut file = File::create(path)?; + file.write_all(b"1")?; + } + + Ok(()) +} + +// Keep for debugging purposes +async fn _print_ipv6_routes( + handle: &Handle, + iface_index: u32, + interface_name: &str, +) -> Result<(), Box> { + info!("IPv6 Routes:"); + + let mut route_ts = handle.route().get(IpVersion::V6).execute(); + + while let Some(route_msg) = route_ts + .try_next() + .await + .map_err(|e| format!("Could not get route: {}", e))? + { + let mut destination: Option = None; + let mut gateway: Option = None; + let mut oif: Option = None; + + for attr in &route_msg.attributes { + match attr { + RouteAttribute::Oif(oif_idx) => { + oif = Some(*oif_idx); + debug!("Route OIF: {}", oif_idx); + } + + RouteAttribute::Destination(dest) => { + match dest { + RouteAddress::Inet6(addr) => { + destination = Some(format!( + "{}/{}", + addr, route_msg.header.destination_prefix_length + )); + debug!("Parsed IPv6 Destination: {}", addr); + } + RouteAddress::Other(v) => { + if v.is_empty() { + destination = Some("::/0".to_string()); + debug!("Parsed Default Route"); + } else { + // Unknown or unsupported address + let hex_str = v + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(":"); + destination = Some(format!("unknown({})", hex_str)); + debug!("Parsed Unknown Destination: {}", hex_str); + } + } + _ => { + debug!("Unhandled Destination variant"); + } + } + } + + RouteAttribute::Gateway(gw) => { + match gw { + RouteAddress::Inet6(addr) => { + gateway = Some(addr.to_string()); + debug!("Parsed IPv6 Gateway: {}", addr); + } + RouteAddress::Other(v) => { + // Some other form of gateway, handle if needed + if v.is_empty() { + debug!("Parsed Empty Gateway"); + } else { + let hex_str = v + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(":"); + gateway = Some(format!("unknown({})", hex_str)); + debug!("Parsed Unknown Gateway: {}", hex_str); + } + } + _ => { + debug!("Unhandled Gateway variant"); + } + } + } + _ => {} + } + } + + if oif != Some(iface_index) { + debug!( + "Skipping route not associated with interface '{}'", + interface_name + ); + continue; + } + + if route_msg.header.destination_prefix_length == 0 && destination.is_none() { + destination = Some("::/0".to_string()); + debug!("Default route detected (no destination attribute)"); + } + + let dest_str = destination.unwrap_or_else(|| "unknown".to_string()); + let mut route_str = dest_str.to_string(); + if let Some(gw) = gateway { + route_str.push_str(&format!(" via {}", gw)); + } + route_str.push_str(&format!(" dev {}", interface_name)); + + info!("- {}", route_str); + } Ok(()) } @@ -141,7 +289,7 @@ fn format_mac(bytes: Vec) -> String { .join(":") } -pub async fn configure_sriov(num_vfs: u32) -> Result<(), String> { +pub async fn _configure_sriov(num_vfs: u32) -> Result<(), String> { let base_path = format!("/sys/class/net/{}/device", INTERFACE_NAME); let file_path = format!("{}/sriov_numvfs", base_path); diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 41ef1ac..b399cec 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -32,6 +32,7 @@ pub enum Error { InvalidInput(TryFromIntError), CHCommandFailure(std::io::Error), CHApiFailure(api_client::Error), + NetworkingError(network::Error), } pub enum BootMode {