fcnet 0.12.0

An in-process Firecracker microVM networking backend based on fcnet-types.
Documentation
use std::{borrow::Cow, ffi::OsStr, net::IpAddr};

use fcnet_types::{FirecrackerIpStack, FirecrackerNetwork};
use futures_util::TryStreamExt;
use nftables::{
    batch::Batch,
    schema::{Chain, NfListObject, NfObject, Nftables, Table},
    types::{NfChainPolicy, NfChainType, NfFamily, NfHook},
};

use crate::{FirecrackerNetworkError, FirecrackerNetworkObjectType, NFT_FILTER_CHAIN, NFT_POSTROUTING_CHAIN, NFT_TABLE};

pub const NO_NFT_ARGS: std::iter::Empty<&OsStr> = std::iter::empty();

pub async fn get_link_index(link: String, netlink_handle: &rtnetlink::Handle) -> Result<u32, FirecrackerNetworkError> {
    Ok(netlink_handle
        .link()
        .get()
        .match_name(link)
        .execute()
        .try_next()
        .await
        .map_err(FirecrackerNetworkError::NetlinkOperationError)?
        .ok_or(FirecrackerNetworkError::ObjectNotFound(FirecrackerNetworkObjectType::IpLink))?
        .header
        .index)
}

pub fn add_base_chains_if_needed(
    network: &FirecrackerNetwork,
    current_ruleset: &Nftables,
    batch: &mut Batch,
) -> Result<(), FirecrackerNetworkError> {
    let mut table_exists = false;
    let mut postrouting_chain_exists = false;
    let mut filter_chain_exists = false;

    for object in current_ruleset.objects.iter() {
        match object {
            NfObject::ListObject(object) => match object {
                NfListObject::Table(table) if table.name == NFT_TABLE && table.family == network.nf_family() => {
                    table_exists = true;
                }
                NfListObject::Chain(chain) => {
                    if chain.name == NFT_POSTROUTING_CHAIN && chain.table == NFT_TABLE {
                        postrouting_chain_exists = true;
                    } else if chain.name == NFT_FILTER_CHAIN && chain.table == NFT_TABLE {
                        filter_chain_exists = true;
                    }
                }
                _ => continue,
            },
            _ => continue,
        }
    }

    if !table_exists {
        batch.add(NfListObject::Table(Table {
            family: network.nf_family(),
            name: NFT_TABLE.into(),
            handle: None,
        }));
    }

    if !postrouting_chain_exists {
        batch.add(NfListObject::Chain(Chain {
            family: network.nf_family(),
            table: NFT_TABLE.into(),
            name: NFT_POSTROUTING_CHAIN.into(),
            _type: Some(NfChainType::NAT),
            hook: Some(NfHook::Postrouting),
            prio: Some(100),
            policy: Some(NfChainPolicy::Accept),
            newname: None,
            dev: None,
            handle: None,
        }));
    }

    if !filter_chain_exists {
        batch.add(NfListObject::Chain(Chain {
            family: network.nf_family(),
            table: NFT_TABLE.into(),
            name: NFT_FILTER_CHAIN.into(),
            _type: Some(NfChainType::Filter),
            hook: Some(NfHook::Forward),
            prio: Some(0),
            policy: Some(NfChainPolicy::Accept),
            handle: None,
            newname: None,
            dev: None,
        }));
    }

    Ok(())
}

pub fn check_base_chains(network: &FirecrackerNetwork, current_ruleset: &Nftables) -> Result<(), FirecrackerNetworkError> {
    let mut table_exists = false;
    let mut postrouting_chain_exists = false;
    let mut filter_chain_exists = false;

    for object in current_ruleset.objects.iter() {
        match object {
            NfObject::ListObject(object) => match object {
                NfListObject::Table(table) if table.name == NFT_TABLE && table.family == network.nf_family() => {
                    table_exists = true;
                }
                NfListObject::Chain(chain) if chain.table == NFT_TABLE => {
                    if chain.name == NFT_POSTROUTING_CHAIN {
                        postrouting_chain_exists = true;
                    } else if chain.name == NFT_FILTER_CHAIN {
                        filter_chain_exists = true;
                    }
                }
                _ => continue,
            },
            _ => continue,
        }
    }

    if !table_exists {
        return Err(FirecrackerNetworkError::ObjectNotFound(FirecrackerNetworkObjectType::NfTable));
    }

    if !postrouting_chain_exists {
        return Err(FirecrackerNetworkError::ObjectNotFound(
            FirecrackerNetworkObjectType::NfPostroutingChain,
        ));
    }

    if !filter_chain_exists {
        return Err(FirecrackerNetworkError::ObjectNotFound(
            FirecrackerNetworkObjectType::NfFilterChain,
        ));
    }

    Ok(())
}

#[inline]
pub fn nat_proto_from_addr(addr: IpAddr) -> Cow<'static, str> {
    match addr {
        IpAddr::V4(_) => "ip".into(),
        IpAddr::V6(_) => "ip6".into(),
    }
}

pub trait FirecrackerNetworkExt {
    fn nf_family(&self) -> NfFamily;
    fn nft_program(&self) -> Option<&str>;
}

impl FirecrackerNetworkExt for FirecrackerNetwork {
    #[inline]
    fn nf_family(&self) -> NfFamily {
        match self.ip_stack {
            FirecrackerIpStack::V4 => NfFamily::IP,
            FirecrackerIpStack::V6 => NfFamily::IP6,
            FirecrackerIpStack::Dual => NfFamily::INet,
        }
    }

    #[inline]
    fn nft_program(&self) -> Option<&str> {
        self.nft_path.as_ref().map(|p| p.as_str())
    }
}