nf_tables 0.1.0

Pure Rust crate to interact with the Linux nf_tables subsystem
Documentation
mod builder;

pub use builder::RuleBuilder;

use std::ffi::CString;

use bytes::BufMut;
use libc::{
    NF_ACCEPT, NF_DROP, NF_QUEUE, NFT_BREAK, NFT_CMP_EQ, NFT_CMP_GT, NFT_CMP_GTE, NFT_CMP_LT,
    NFT_CMP_LTE, NFT_CMP_NEQ, NFT_CONTINUE, NFT_GOTO, NFT_JUMP, NFT_META_BRI_IIFNAME,
    NFT_META_BRI_OIFNAME, NFT_META_CGROUP, NFT_META_CPU, NFT_META_IIF, NFT_META_IIFGROUP,
    NFT_META_IIFNAME, NFT_META_IIFTYPE, NFT_META_L4PROTO, NFT_META_LEN, NFT_META_MARK,
    NFT_META_NFPROTO, NFT_META_OIF, NFT_META_OIFGROUP, NFT_META_OIFNAME, NFT_META_OIFTYPE,
    NFT_META_PKTTYPE, NFT_META_PRANDOM, NFT_META_PRIORITY, NFT_META_PROTOCOL, NFT_META_RTCLASSID,
    NFT_META_SECMARK, NFT_META_SKGID, NFT_META_SKUID, NFT_PAYLOAD_LL_HEADER,
    NFT_PAYLOAD_NETWORK_HEADER, NFT_PAYLOAD_TRANSPORT_HEADER, NFT_REG_1, NFT_REG_2, NFT_REG_3,
    NFT_REG_4, NFT_REG_VERDICT, NFT_RETURN,
};

use crate::constants::*;
use crate::{Encode, write_attribute};

#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
#[non_exhaustive]
pub enum Expr {
    Meta(Meta),
    Cmp(Cmp),
    Bitwise(Bitwise),
    Payload(Payload),
    Verdict(Verdict),
}

impl Encode for Expr {
    fn encode<B>(&self, buf: B)
    where
        B: BufMut,
    {
        match self {
            Self::Meta(expr) => expr.encode(buf),
            Self::Cmp(expr) => expr.encode(buf),
            Self::Bitwise(expr) => expr.encode(buf),
            Self::Payload(expr) => expr.encode(buf),
            Self::Verdict(expr) => expr.encode(buf),
        }
    }
}

#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Meta {
    pub key: MetaProperty,
    pub register: Register,
    pub src_register: bool,
}

impl Encode for Meta {
    fn encode<B>(&self, mut buf: B)
    where
        B: BufMut,
    {
        let mut expr = Vec::new();
        write_attribute(&mut expr, NFTA_META_KEY, &(self.key as u32).to_be_bytes());
        if self.src_register {
            write_attribute(
                &mut expr,
                NFTA_META_SREG,
                &(self.register as u32).to_be_bytes(),
            );
        } else {
            write_attribute(
                &mut expr,
                NFTA_META_DREG,
                &(self.register as u32).to_be_bytes(),
            );
        }

        write_attribute(&mut buf, NFTA_EXPR_NAME, c"meta".to_bytes_with_nul());
        write_attribute(&mut buf, NFTA_EXPR_DATA | NLA_F_NESTED, &expr);
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[repr(i32)]
#[non_exhaustive]
pub enum MetaProperty {
    Len = NFT_META_LEN,
    Protocol = NFT_META_PROTOCOL,
    Priority = NFT_META_PRIORITY,
    Mask = NFT_META_MARK,
    Iif = NFT_META_IIF,
    Oif = NFT_META_OIF,
    IIfName = NFT_META_IIFNAME,
    OifName = NFT_META_OIFNAME,
    IIfType = NFT_META_IIFTYPE,
    OifType = NFT_META_OIFTYPE,
    SkUid = NFT_META_SKUID,
    SkGid = NFT_META_SKGID,
    RtClassId = NFT_META_RTCLASSID,
    SecMark = NFT_META_SECMARK,
    NfProto = NFT_META_NFPROTO,
    L4Proto = NFT_META_L4PROTO,
    BriIfName = NFT_META_BRI_IIFNAME,
    BrOifName = NFT_META_BRI_OIFNAME,
    PktType = NFT_META_PKTTYPE,
    Cpu = NFT_META_CPU,
    IIfGroup = NFT_META_IIFGROUP,
    OifGroup = NFT_META_OIFGROUP,
    Cgroup = NFT_META_CGROUP,
    PRandom = NFT_META_PRANDOM,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[repr(i32)]
#[non_exhaustive]
pub enum Register {
    Reg1 = NFT_REG_1,
    Reg2 = NFT_REG_2,
    Reg3 = NFT_REG_3,
    Reg4 = NFT_REG_4,
}

#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Cmp {
    pub op: CmpOp,
    pub register: Register,
    pub data: Vec<u8>,
}

impl Encode for Cmp {
    fn encode<B>(&self, mut buf: B)
    where
        B: BufMut,
    {
        let mut data = Vec::new();
        write_attribute(&mut data, NFTA_DATA_VALUE, &self.data);

        let mut expr = Vec::new();
        write_attribute(
            &mut expr,
            NFTA_CMP_SREG,
            &(self.register as u32).to_be_bytes(),
        );
        write_attribute(&mut expr, NFTA_CMP_OP, &(self.op as u32).to_be_bytes());
        write_attribute(&mut expr, NFTA_CMP_DATA | NLA_F_NESTED, &data);

        write_attribute(&mut buf, NFTA_EXPR_NAME, c"cmp".to_bytes_with_nul());
        write_attribute(&mut buf, NFTA_EXPR_DATA | NLA_F_NESTED, &expr);
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[repr(i32)]
#[non_exhaustive]
pub enum CmpOp {
    Equal = NFT_CMP_EQ,
    NotEqual = NFT_CMP_NEQ,
    LessThan = NFT_CMP_LT,
    LessThanOrEqual = NFT_CMP_LTE,
    GreaterThan = NFT_CMP_GT,
    GreaterThanOrEqual = NFT_CMP_GTE,
}

#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
#[non_exhaustive]
pub enum Verdict {
    Accept,
    Drop,
    Queue,
    Continue,
    Break,
    Jump(CString),
    Goto(CString),
    Return,
}

impl Encode for Verdict {
    fn encode<B>(&self, mut buf: B)
    where
        B: BufMut,
    {
        let mut verdict_data = Vec::new();

        let code: i32 = match self {
            Self::Accept => NF_ACCEPT,
            Self::Drop => NF_DROP,
            Self::Queue => NF_QUEUE,
            Self::Continue => NFT_CONTINUE,
            Self::Break => NFT_BREAK,
            Self::Jump(_) => NFT_JUMP,
            Self::Goto(_) => NFT_GOTO,
            Self::Return => NFT_RETURN,
        };
        write_attribute(&mut verdict_data, NFTA_VERDICT_CODE, &code.to_be_bytes());

        if let Verdict::Jump(chain) | Verdict::Goto(chain) = self {
            write_attribute(
                &mut verdict_data,
                NFTA_VERDICT_CHAIN,
                chain.to_bytes_with_nul(),
            );
        }

        let mut verdict = Vec::new();
        write_attribute(
            &mut verdict,
            NFTA_DATA_VERDICT | NLA_F_NESTED,
            &verdict_data,
        );

        let mut imm = Vec::new();
        write_attribute(
            &mut imm,
            NFTA_IMMEDIATE_DREG,
            &NFT_REG_VERDICT.to_be_bytes(),
        );
        write_attribute(&mut imm, NFTA_IMMEDIATE_DATA | NLA_F_NESTED, &verdict);

        write_attribute(&mut buf, NFTA_EXPR_NAME, c"immediate".to_bytes_with_nul());
        write_attribute(&mut buf, NFTA_EXPR_DATA | NLA_F_NESTED, &imm);
    }
}

#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Bitwise {
    pub src_register: Register,
    pub dst_register: Register,
    pub len: u32,
    pub mask: Vec<u8>,
    pub xor: Vec<u8>,
}

impl Encode for Bitwise {
    fn encode<B>(&self, mut buf: B)
    where
        B: BufMut,
    {
        let mut mask = Vec::new();
        write_attribute(&mut mask, NFTA_DATA_VALUE, &self.mask);

        let mut xor = Vec::new();
        write_attribute(&mut xor, NFTA_DATA_VALUE, &self.xor);

        let mut expr = Vec::new();
        write_attribute(
            &mut expr,
            NFTA_BITWISE_SREG,
            &(self.src_register as u32).to_be_bytes(),
        );
        write_attribute(
            &mut expr,
            NFTA_BITWISE_DREG,
            &(self.dst_register as u32).to_be_bytes(),
        );
        write_attribute(&mut expr, NFTA_BITWISE_LEN, &self.len.to_be_bytes());
        write_attribute(&mut expr, NFTA_BITWISE_MASK | NLA_F_NESTED, &mask);
        write_attribute(&mut expr, NFTA_BITWISE_XOR | NLA_F_NESTED, &xor);

        write_attribute(&mut buf, NFTA_EXPR_NAME, c"bitwise".to_bytes_with_nul());
        write_attribute(&mut buf, NFTA_EXPR_DATA, &expr);
    }
}

#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Payload {
    pub op: PayloadOp,
    pub base: PayloadBase,
    pub offset: u32,
    pub len: u32,
}

impl Encode for Payload {
    fn encode<B>(&self, mut buf: B)
    where
        B: BufMut,
    {
        let mut expr = Vec::new();

        match self.op {
            PayloadOp::Load(reg) => {
                write_attribute(&mut expr, NFTA_PAYLOAD_DREG, &(reg as u32).to_be_bytes());
            }
            PayloadOp::Write(reg) => {
                write_attribute(&mut expr, NFTA_PAYLOAD_SREG, &(reg as u32).to_be_bytes());
            }
        }

        write_attribute(
            &mut expr,
            NFTA_PAYLOAD_BASE,
            &(self.base as u32).to_be_bytes(),
        );
        write_attribute(&mut expr, NFTA_PAYLOAD_OFFSET, &self.offset.to_be_bytes());
        write_attribute(&mut expr, NFTA_PAYLOAD_LEN, &self.len.to_be_bytes());

        write_attribute(&mut buf, NFTA_EXPR_NAME, c"payload".to_bytes_with_nul());
        write_attribute(&mut buf, NFTA_EXPR_DATA | NLA_F_NESTED, &expr);
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum PayloadOp {
    Load(Register),
    Write(Register),
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[repr(i32)]
#[non_exhaustive]
pub enum PayloadBase {
    LinkLayer = NFT_PAYLOAD_LL_HEADER,
    Network = NFT_PAYLOAD_NETWORK_HEADER,
    Transport = NFT_PAYLOAD_TRANSPORT_HEADER,
}