mirror of
				https://github.com/edera-dev/krata.git
				synced 2025-11-03 23:29:39 +00:00 
			
		
		
		
	krata: event-based network backend startup and api enhancements
This commit is contained in:
		@ -1,11 +1,24 @@
 | 
			
		||||
use anyhow::{anyhow, Result};
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use krata::{
 | 
			
		||||
    events::EventStream,
 | 
			
		||||
    v1::{
 | 
			
		||||
        common::Guest,
 | 
			
		||||
        control::{
 | 
			
		||||
            control_service_client::ControlServiceClient, watch_events_reply::Event,
 | 
			
		||||
            ListGuestsRequest, WatchEventsRequest,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
use log::warn;
 | 
			
		||||
use smoltcp::wire::{EthernetAddress, Ipv4Cidr, Ipv6Cidr};
 | 
			
		||||
use std::{collections::HashMap, str::FromStr};
 | 
			
		||||
use std::{collections::HashMap, str::FromStr, time::Duration};
 | 
			
		||||
use tokio::{select, sync::broadcast::Receiver, time::sleep};
 | 
			
		||||
use tonic::transport::Channel;
 | 
			
		||||
use uuid::Uuid;
 | 
			
		||||
use xenstore::{XsdClient, XsdInterface, XsdTransaction};
 | 
			
		||||
 | 
			
		||||
pub struct AutoNetworkCollector {
 | 
			
		||||
    client: XsdClient,
 | 
			
		||||
pub struct AutoNetworkWatcher {
 | 
			
		||||
    control: ControlServiceClient<Channel>,
 | 
			
		||||
    pub events: EventStream,
 | 
			
		||||
    known: HashMap<Uuid, NetworkMetadata>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -36,116 +49,88 @@ pub struct AutoNetworkChangeset {
 | 
			
		||||
    pub removed: Vec<NetworkMetadata>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl AutoNetworkCollector {
 | 
			
		||||
    pub async fn new() -> Result<AutoNetworkCollector> {
 | 
			
		||||
        Ok(AutoNetworkCollector {
 | 
			
		||||
            client: XsdClient::open().await?,
 | 
			
		||||
impl AutoNetworkWatcher {
 | 
			
		||||
    pub async fn new(mut control: ControlServiceClient<Channel>) -> Result<AutoNetworkWatcher> {
 | 
			
		||||
        let watch_events_response = control.watch_events(WatchEventsRequest {}).await?;
 | 
			
		||||
 | 
			
		||||
        Ok(AutoNetworkWatcher {
 | 
			
		||||
            control,
 | 
			
		||||
            events: EventStream::open(watch_events_response.into_inner()).await?,
 | 
			
		||||
            known: HashMap::new(),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn read(&mut self) -> Result<Vec<NetworkMetadata>> {
 | 
			
		||||
        let mut networks = Vec::new();
 | 
			
		||||
        let tx = self.client.transaction().await?;
 | 
			
		||||
        for domid_string in tx.list("/local/domain").await? {
 | 
			
		||||
            let Ok(domid) = domid_string.parse::<u32>() else {
 | 
			
		||||
        let mut all_guests: HashMap<Uuid, Guest> = HashMap::new();
 | 
			
		||||
        for guest in self
 | 
			
		||||
            .control
 | 
			
		||||
            .list_guests(ListGuestsRequest {})
 | 
			
		||||
            .await?
 | 
			
		||||
            .into_inner()
 | 
			
		||||
            .guests
 | 
			
		||||
        {
 | 
			
		||||
            let Ok(uuid) = Uuid::from_str(&guest.id) else {
 | 
			
		||||
                continue;
 | 
			
		||||
            };
 | 
			
		||||
            all_guests.insert(uuid, guest);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut networks: Vec<NetworkMetadata> = Vec::new();
 | 
			
		||||
        for (uuid, guest) in &all_guests {
 | 
			
		||||
            let Some(ref state) = guest.state else {
 | 
			
		||||
                continue;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            let dom_path = format!("/local/domain/{}", domid_string);
 | 
			
		||||
            let Some(uuid_string) = tx.read_string(&format!("{}/krata/uuid", dom_path)).await?
 | 
			
		||||
            else {
 | 
			
		||||
            if state.domid == u32::MAX {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let Some(ref network) = state.network else {
 | 
			
		||||
                continue;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            let Ok(uuid) = uuid_string.parse::<Uuid>() else {
 | 
			
		||||
            let Ok(guest_ipv4_cidr) = Ipv4Cidr::from_str(&network.guest_ipv4) else {
 | 
			
		||||
                continue;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            let Ok(guest) =
 | 
			
		||||
                AutoNetworkCollector::read_network_side(uuid, &tx, &dom_path, "guest").await
 | 
			
		||||
            else {
 | 
			
		||||
            let Ok(guest_ipv6_cidr) = Ipv6Cidr::from_str(&network.guest_ipv6) else {
 | 
			
		||||
                continue;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            let Ok(gateway) =
 | 
			
		||||
                AutoNetworkCollector::read_network_side(uuid, &tx, &dom_path, "gateway").await
 | 
			
		||||
            else {
 | 
			
		||||
            let Ok(guest_mac) = EthernetAddress::from_str(&network.guest_mac) else {
 | 
			
		||||
                continue;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            let Ok(gateway_ipv4_cidr) = Ipv4Cidr::from_str(&network.gateway_ipv4) else {
 | 
			
		||||
                continue;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            let Ok(gateway_ipv6_cidr) = Ipv6Cidr::from_str(&network.gateway_ipv6) else {
 | 
			
		||||
                continue;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            let Ok(gateway_mac) = EthernetAddress::from_str(&network.gateway_mac) else {
 | 
			
		||||
                continue;
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            networks.push(NetworkMetadata {
 | 
			
		||||
                domid,
 | 
			
		||||
                uuid,
 | 
			
		||||
                guest,
 | 
			
		||||
                gateway,
 | 
			
		||||
                domid: state.domid,
 | 
			
		||||
                uuid: *uuid,
 | 
			
		||||
                guest: NetworkSide {
 | 
			
		||||
                    ipv4: guest_ipv4_cidr,
 | 
			
		||||
                    ipv6: guest_ipv6_cidr,
 | 
			
		||||
                    mac: guest_mac,
 | 
			
		||||
                },
 | 
			
		||||
                gateway: NetworkSide {
 | 
			
		||||
                    ipv4: gateway_ipv4_cidr,
 | 
			
		||||
                    ipv6: gateway_ipv6_cidr,
 | 
			
		||||
                    mac: gateway_mac,
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        tx.commit().await?;
 | 
			
		||||
        Ok(networks)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn read_network_side(
 | 
			
		||||
        uuid: Uuid,
 | 
			
		||||
        tx: &XsdTransaction,
 | 
			
		||||
        dom_path: &str,
 | 
			
		||||
        side: &str,
 | 
			
		||||
    ) -> Result<NetworkSide> {
 | 
			
		||||
        let side_path = format!("{}/krata/network/{}", dom_path, side);
 | 
			
		||||
        let Some(ipv4) = tx.read_string(&format!("{}/ipv4", side_path)).await? else {
 | 
			
		||||
            return Err(anyhow!(
 | 
			
		||||
                "krata domain {} is missing {} ipv4 network entry",
 | 
			
		||||
                uuid,
 | 
			
		||||
                side
 | 
			
		||||
            ));
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let Some(ipv6) = tx.read_string(&format!("{}/ipv6", side_path)).await? else {
 | 
			
		||||
            return Err(anyhow!(
 | 
			
		||||
                "krata domain {} is missing {} ipv6 network entry",
 | 
			
		||||
                uuid,
 | 
			
		||||
                side
 | 
			
		||||
            ));
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let Some(mac) = tx.read_string(&format!("{}/mac", side_path)).await? else {
 | 
			
		||||
            return Err(anyhow!(
 | 
			
		||||
                "krata domain {} is missing {} mac address entry",
 | 
			
		||||
                uuid,
 | 
			
		||||
                side
 | 
			
		||||
            ));
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let Ok(ipv4) = Ipv4Cidr::from_str(&ipv4) else {
 | 
			
		||||
            return Err(anyhow!(
 | 
			
		||||
                "krata domain {} has invalid {} ipv4 network cidr entry: {}",
 | 
			
		||||
                uuid,
 | 
			
		||||
                side,
 | 
			
		||||
                ipv4
 | 
			
		||||
            ));
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let Ok(ipv6) = Ipv6Cidr::from_str(&ipv6) else {
 | 
			
		||||
            return Err(anyhow!(
 | 
			
		||||
                "krata domain {} has invalid {} ipv6 network cidr entry: {}",
 | 
			
		||||
                uuid,
 | 
			
		||||
                side,
 | 
			
		||||
                ipv6
 | 
			
		||||
            ));
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let Ok(mac) = EthernetAddress::from_str(&mac) else {
 | 
			
		||||
            return Err(anyhow!(
 | 
			
		||||
                "krata domain {} has invalid {} mac address entry: {}",
 | 
			
		||||
                uuid,
 | 
			
		||||
                side,
 | 
			
		||||
                mac
 | 
			
		||||
            ));
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Ok(NetworkSide { ipv4, ipv6, mac })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn read_changes(&mut self) -> Result<AutoNetworkChangeset> {
 | 
			
		||||
        let mut seen: Vec<Uuid> = Vec::new();
 | 
			
		||||
        let mut added: Vec<NetworkMetadata> = Vec::new();
 | 
			
		||||
@ -179,6 +164,27 @@ impl AutoNetworkCollector {
 | 
			
		||||
        Ok(AutoNetworkChangeset { added, removed })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn wait(&mut self, receiver: &mut Receiver<Event>) -> Result<()> {
 | 
			
		||||
        loop {
 | 
			
		||||
            select! {
 | 
			
		||||
                x = receiver.recv() => match x {
 | 
			
		||||
                    Ok(Event::GuestChanged(_)) => {
 | 
			
		||||
                        break;
 | 
			
		||||
                    },
 | 
			
		||||
 | 
			
		||||
                    Err(error) => {
 | 
			
		||||
                        warn!("failed to receive event: {}", error);
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
 | 
			
		||||
                _ = sleep(Duration::from_secs(10)) => {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn mark_unknown(&mut self, uuid: Uuid) -> Result<bool> {
 | 
			
		||||
        Ok(self.known.remove(&uuid).is_some())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,17 @@
 | 
			
		||||
use std::{collections::HashMap, time::Duration};
 | 
			
		||||
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use autonet::{AutoNetworkChangeset, AutoNetworkCollector, NetworkMetadata};
 | 
			
		||||
use autonet::{AutoNetworkChangeset, AutoNetworkWatcher, NetworkMetadata};
 | 
			
		||||
use futures::{future::join_all, TryFutureExt};
 | 
			
		||||
use hbridge::HostBridge;
 | 
			
		||||
use krata::{
 | 
			
		||||
    client::ControlClientProvider,
 | 
			
		||||
    dial::ControlDialAddress,
 | 
			
		||||
    v1::{common::Guest, control::control_service_client::ControlServiceClient},
 | 
			
		||||
};
 | 
			
		||||
use log::warn;
 | 
			
		||||
use tokio::{task::JoinHandle, time::sleep};
 | 
			
		||||
use tonic::transport::Channel;
 | 
			
		||||
use uuid::Uuid;
 | 
			
		||||
use vbridge::VirtualBridge;
 | 
			
		||||
 | 
			
		||||
@ -26,17 +32,22 @@ const HOST_BRIDGE_MTU: usize = 1500;
 | 
			
		||||
pub const EXTRA_MTU: usize = 20;
 | 
			
		||||
 | 
			
		||||
pub struct NetworkService {
 | 
			
		||||
    pub control: ControlServiceClient<Channel>,
 | 
			
		||||
    pub guests: HashMap<Uuid, Guest>,
 | 
			
		||||
    pub backends: HashMap<Uuid, JoinHandle<()>>,
 | 
			
		||||
    pub bridge: VirtualBridge,
 | 
			
		||||
    pub hbridge: HostBridge,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl NetworkService {
 | 
			
		||||
    pub async fn new() -> Result<NetworkService> {
 | 
			
		||||
    pub async fn new(control_address: ControlDialAddress) -> Result<NetworkService> {
 | 
			
		||||
        let control = ControlClientProvider::dial(control_address).await?;
 | 
			
		||||
        let bridge = VirtualBridge::new()?;
 | 
			
		||||
        let hbridge =
 | 
			
		||||
            HostBridge::new(HOST_BRIDGE_MTU + EXTRA_MTU, "krata0".to_string(), &bridge).await?;
 | 
			
		||||
        Ok(NetworkService {
 | 
			
		||||
            control,
 | 
			
		||||
            guests: HashMap::new(),
 | 
			
		||||
            backends: HashMap::new(),
 | 
			
		||||
            bridge,
 | 
			
		||||
            hbridge,
 | 
			
		||||
@ -46,18 +57,19 @@ impl NetworkService {
 | 
			
		||||
 | 
			
		||||
impl NetworkService {
 | 
			
		||||
    pub async fn watch(&mut self) -> Result<()> {
 | 
			
		||||
        let mut collector = AutoNetworkCollector::new().await?;
 | 
			
		||||
        let mut watcher = AutoNetworkWatcher::new(self.control.clone()).await?;
 | 
			
		||||
        let mut receiver = watcher.events.subscribe();
 | 
			
		||||
        loop {
 | 
			
		||||
            let changeset = collector.read_changes().await?;
 | 
			
		||||
            self.process_network_changeset(&mut collector, changeset)
 | 
			
		||||
            let changeset = watcher.read_changes().await?;
 | 
			
		||||
            self.process_network_changeset(&mut watcher, changeset)
 | 
			
		||||
                .await?;
 | 
			
		||||
            sleep(Duration::from_secs(2)).await;
 | 
			
		||||
            watcher.wait(&mut receiver).await?;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn process_network_changeset(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        collector: &mut AutoNetworkCollector,
 | 
			
		||||
        collector: &mut AutoNetworkWatcher,
 | 
			
		||||
        changeset: AutoNetworkChangeset,
 | 
			
		||||
    ) -> Result<()> {
 | 
			
		||||
        for removal in &changeset.removed {
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@ impl NatHandlerContext {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn reclaim(&self) -> Result<()> {
 | 
			
		||||
        self.reclaim_sender.try_send(self.key)?;
 | 
			
		||||
        let _ = self.reclaim_sender.try_send(self.key);
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -266,6 +266,12 @@ impl AsyncRawSocketChannel {
 | 
			
		||||
                            if error.kind() == ErrorKind::WouldBlock {
 | 
			
		||||
                                continue;
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            // device no longer exists
 | 
			
		||||
                            if error.raw_os_error() == Some(6) {
 | 
			
		||||
                                break;
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            return Err(anyhow!("failed to read from raw socket: {}", error));
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user