nftnl 0.9.2

Safe abstraction for libnftnl. Provides low-level userspace access to the in-kernel nf_tables subsystem
Documentation
use super::{Expression, Rule};
use nftnl_sys::{self as sys, libc};
use std::{
    borrow::Cow,
    ffi::{CStr, CString, c_void},
    net::{IpAddr, Ipv4Addr, Ipv6Addr},
    ptr, slice,
};

/// Comparison operator.
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum CmpOp {
    /// Equals.
    Eq,
    /// Not equal.
    Neq,
    /// Less than.
    Lt,
    /// Less than, or equal.
    Lte,
    /// Greater than.
    Gt,
    /// Greater than, or equal.
    Gte,
}

impl CmpOp {
    /// Returns the corresponding `NFT_*` constant for this comparison operation.
    pub fn to_raw(self) -> u32 {
        use self::CmpOp::*;
        match self {
            Eq => libc::NFT_CMP_EQ as u32,
            Neq => libc::NFT_CMP_NEQ as u32,
            Lt => libc::NFT_CMP_LT as u32,
            Lte => libc::NFT_CMP_LTE as u32,
            Gt => libc::NFT_CMP_GT as u32,
            Gte => libc::NFT_CMP_GTE as u32,
        }
    }
}

/// Comparator expression. Allows comparing the content of the netfilter register with any value.
pub struct Cmp<T: ToSlice> {
    op: CmpOp,
    data: T,
}

impl<T: ToSlice> Cmp<T> {
    /// Returns a new comparison expression comparing the value loaded in the register with the
    /// data in `data` using the comparison operator `op`.
    pub fn new(op: CmpOp, data: T) -> Self {
        Cmp { op, data }
    }
}

impl<T: ToSlice> Expression for Cmp<T> {
    fn to_expr(&self, _rule: &Rule) -> ptr::NonNull<sys::nftnl_expr> {
        let expr = try_alloc!(unsafe { sys::nftnl_expr_alloc(c"cmp".as_ptr()) });

        let data = self.data.to_slice();
        trace!("Creating a cmp expr comparing with data {data:?}");

        unsafe {
            sys::nftnl_expr_set_u32(
                expr.as_ptr(),
                sys::NFTNL_EXPR_CMP_SREG as u16,
                libc::NFT_REG_1 as u32,
            );
            sys::nftnl_expr_set_u32(
                expr.as_ptr(),
                sys::NFTNL_EXPR_CMP_OP as u16,
                self.op.to_raw(),
            );
            sys::nftnl_expr_set(
                expr.as_ptr(),
                sys::NFTNL_EXPR_CMP_DATA as u16,
                data.as_ref() as *const _ as *const c_void,
                data.len() as u32,
            );
        }

        expr
    }
}

#[macro_export(local_inner_macros)]
macro_rules! nft_expr_cmp {
    (@cmp_op ==) => {
        $crate::expr::CmpOp::Eq
    };
    (@cmp_op !=) => {
        $crate::expr::CmpOp::Neq
    };
    (@cmp_op <) => {
        $crate::expr::CmpOp::Lt
    };
    (@cmp_op <=) => {
        $crate::expr::CmpOp::Lte
    };
    (@cmp_op >) => {
        $crate::expr::CmpOp::Gt
    };
    (@cmp_op >=) => {
        $crate::expr::CmpOp::Gte
    };
    ($op:tt $data:expr) => {
        $crate::expr::Cmp::new(nft_expr_cmp!(@cmp_op $op), $data)
    };
}

/// A type that can be converted into a byte buffer.
pub trait ToSlice {
    /// Returns the data this type represents.
    fn to_slice(&self) -> Cow<'_, [u8]>;
}

impl ToSlice for [u8; 0] {
    fn to_slice(&self) -> Cow<'_, [u8]> {
        Cow::Borrowed(&[])
    }
}

impl ToSlice for &'_ [u8] {
    fn to_slice(&self) -> Cow<'_, [u8]> {
        Cow::Borrowed(self)
    }
}

impl ToSlice for &'_ [u16] {
    fn to_slice(&self) -> Cow<'_, [u8]> {
        let ptr = self.as_ptr() as *const u8;
        let len = self.len() * 2;
        Cow::Borrowed(unsafe { slice::from_raw_parts(ptr, len) })
    }
}

impl ToSlice for IpAddr {
    fn to_slice(&self) -> Cow<'_, [u8]> {
        match *self {
            IpAddr::V4(ref addr) => addr.to_slice(),
            IpAddr::V6(ref addr) => addr.to_slice(),
        }
    }
}

impl ToSlice for Ipv4Addr {
    fn to_slice(&self) -> Cow<'_, [u8]> {
        // TODO: replace with self.as_octets() when stabilized
        Cow::Owned(self.octets().to_vec())
    }
}

impl ToSlice for Ipv6Addr {
    fn to_slice(&self) -> Cow<'_, [u8]> {
        // TODO: replace with self.as_octets() when stabilized
        Cow::Owned(self.octets().to_vec())
    }
}

impl ToSlice for u8 {
    fn to_slice(&self) -> Cow<'_, [u8]> {
        Cow::Borrowed(slice::from_ref(self))
    }
}

// TODO use zerocopy for integer ToSlice impls to avoid allocations
impl ToSlice for u16 {
    fn to_slice(&self) -> Cow<'_, [u8]> {
        Cow::Owned(self.to_ne_bytes().to_vec())
    }
}

impl ToSlice for u32 {
    fn to_slice(&self) -> Cow<'_, [u8]> {
        Cow::Owned(self.to_ne_bytes().to_vec())
    }
}

impl ToSlice for u64 {
    fn to_slice(&self) -> Cow<'_, [u8]> {
        Cow::Owned(self.to_ne_bytes().to_vec())
    }
}

impl ToSlice for i32 {
    fn to_slice(&self) -> Cow<'_, [u8]> {
        Cow::Owned(self.to_ne_bytes().to_vec())
    }
}

impl ToSlice for &'_ CStr {
    fn to_slice(&self) -> Cow<'_, [u8]> {
        Cow::from(self.to_bytes_with_nul())
    }
}

/// Can be used to compare the value loaded by [`Meta::IifName`] and [`Meta::OifName`]. Please
/// note that it is faster to check interface index than name.
///
/// [`Meta::IifName`]: enum.Meta.html#variant.IifName
/// [`Meta::OifName`]: enum.Meta.html#variant.OifName
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum InterfaceName {
    /// Interface name must be exactly the value of the `CString`.
    Exact(CString),
    /// Interface name must start with the value of the `CString`.
    ///
    /// `InterfaceName::StartingWith("eth")` will look like `eth*` when printed and match against
    /// `eth0`, `eth1`, ..., `eth99` and so on.
    StartingWith(CString),
}

impl ToSlice for InterfaceName {
    fn to_slice(&self) -> Cow<'_, [u8]> {
        let bytes = match self {
            InterfaceName::Exact(name) => name.as_bytes_with_nul(),
            InterfaceName::StartingWith(name) => name.as_bytes(),
        };
        Cow::from(bytes)
    }
}

impl ToSlice for &'_ InterfaceName {
    fn to_slice(&self) -> Cow<'_, [u8]> {
        let bytes = match *self {
            InterfaceName::Exact(name) => name.as_bytes_with_nul(),
            InterfaceName::StartingWith(name) => name.as_bytes(),
        };
        Cow::from(bytes)
    }
}