aboutsummaryrefslogtreecommitdiff
path: root/src/expr
diff options
context:
space:
mode:
Diffstat (limited to 'src/expr')
-rw-r--r--src/expr/bitwise.rs69
-rw-r--r--src/expr/cmp.rs220
-rw-r--r--src/expr/counter.rs46
-rw-r--r--src/expr/ct.rs87
-rw-r--r--src/expr/immediate.rs126
-rw-r--r--src/expr/log.rs112
-rw-r--r--src/expr/lookup.rs79
-rw-r--r--src/expr/masquerade.rs24
-rw-r--r--src/expr/meta.rs175
-rw-r--r--src/expr/mod.rs242
-rw-r--r--src/expr/nat.rs99
-rw-r--r--src/expr/payload.rs531
-rw-r--r--src/expr/register.rs34
-rw-r--r--src/expr/reject.rs96
-rw-r--r--src/expr/verdict.rs148
-rw-r--r--src/expr/wrapper.rs60
16 files changed, 2148 insertions, 0 deletions
diff --git a/src/expr/bitwise.rs b/src/expr/bitwise.rs
new file mode 100644
index 0000000..59ef41b
--- /dev/null
+++ b/src/expr/bitwise.rs
@@ -0,0 +1,69 @@
+use super::{Expression, Rule, ToSlice};
+use crate::sys::{self, libc};
+use std::ffi::c_void;
+use std::os::raw::c_char;
+
+/// Expression for performing bitwise masking and XOR on the data in a register.
+pub struct Bitwise<M: ToSlice, X: ToSlice> {
+ mask: M,
+ xor: X,
+}
+
+impl<M: ToSlice, X: ToSlice> Bitwise<M, X> {
+ /// Returns a new `Bitwise` instance that first masks the value it's applied to with `mask`
+ /// and then performs xor with the value in `xor`.
+ pub fn new(mask: M, xor: X) -> Self {
+ Self { mask, xor }
+ }
+}
+
+impl<M: ToSlice, X: ToSlice> Expression for Bitwise<M, X> {
+ fn get_raw_name() -> *const c_char {
+ b"bitwise\0" as *const _ as *const c_char
+ }
+
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ unsafe {
+ let expr = try_alloc!(sys::nftnl_expr_alloc(Self::get_raw_name()));
+
+ let mask = self.mask.to_slice();
+ let xor = self.xor.to_slice();
+ assert!(mask.len() == xor.len());
+ let len = mask.len() as u32;
+
+ sys::nftnl_expr_set_u32(
+ expr,
+ sys::NFTNL_EXPR_BITWISE_SREG as u16,
+ libc::NFT_REG_1 as u32,
+ );
+ sys::nftnl_expr_set_u32(
+ expr,
+ sys::NFTNL_EXPR_BITWISE_DREG as u16,
+ libc::NFT_REG_1 as u32,
+ );
+ sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_BITWISE_LEN as u16, len);
+
+ sys::nftnl_expr_set(
+ expr,
+ sys::NFTNL_EXPR_BITWISE_MASK as u16,
+ mask.as_ref() as *const _ as *const c_void,
+ len,
+ );
+ sys::nftnl_expr_set(
+ expr,
+ sys::NFTNL_EXPR_BITWISE_XOR as u16,
+ xor.as_ref() as *const _ as *const c_void,
+ len,
+ );
+
+ expr
+ }
+ }
+}
+
+#[macro_export]
+macro_rules! nft_expr_bitwise {
+ (mask $mask:expr,xor $xor:expr) => {
+ $crate::expr::Bitwise::new($mask, $xor)
+ };
+}
diff --git a/src/expr/cmp.rs b/src/expr/cmp.rs
new file mode 100644
index 0000000..384f0b4
--- /dev/null
+++ b/src/expr/cmp.rs
@@ -0,0 +1,220 @@
+use super::{DeserializationError, Expression, Rule, ToSlice};
+use crate::sys::{self, libc};
+use std::{
+ borrow::Cow,
+ ffi::{c_void, CString},
+ os::raw::c_char,
+};
+
+/// Comparison operator.
+#[derive(Debug, 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,
+ }
+ }
+
+ pub fn from_raw(val: u32) -> Result<Self, DeserializationError> {
+ use self::CmpOp::*;
+ match val as i32 {
+ libc::NFT_CMP_EQ => Ok(Eq),
+ libc::NFT_CMP_NEQ => Ok(Neq),
+ libc::NFT_CMP_LT => Ok(Lt),
+ libc::NFT_CMP_LTE => Ok(Lte),
+ libc::NFT_CMP_GT => Ok(Gt),
+ libc::NFT_CMP_GTE => Ok(Gte),
+ _ => Err(DeserializationError::InvalidValue),
+ }
+ }
+}
+
+/// Comparator expression. Allows comparing the content of the netfilter register with any value.
+#[derive(Debug, PartialEq)]
+pub struct Cmp<T> {
+ 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 get_raw_name() -> *const c_char {
+ b"cmp\0" as *const _ as *const c_char
+ }
+
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ unsafe {
+ let expr = try_alloc!(sys::nftnl_expr_alloc(Self::get_raw_name()));
+
+ let data = self.data.to_slice();
+ trace!("Creating a cmp expr comparing with data {:?}", data);
+
+ sys::nftnl_expr_set_u32(
+ expr,
+ sys::NFTNL_EXPR_CMP_SREG as u16,
+ libc::NFT_REG_1 as u32,
+ );
+ sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_CMP_OP as u16, self.op.to_raw());
+ sys::nftnl_expr_set(
+ expr,
+ sys::NFTNL_EXPR_CMP_DATA as u16,
+ data.as_ptr() as *const c_void,
+ data.len() as u32,
+ );
+
+ expr
+ }
+ }
+}
+
+impl<const N: usize> Expression for Cmp<[u8; N]> {
+ fn get_raw_name() -> *const c_char {
+ Cmp::<u8>::get_raw_name()
+ }
+
+ /// The raw data contained inside `Cmp` expressions can only be deserialized to
+ /// arrays of bytes, to ensure that the memory layout of retrieved data cannot be
+ /// violated. It is your responsibility to provide the correct length of the byte
+ /// data. If the data size is invalid, you will get the error
+ /// `DeserializationError::InvalidDataSize`.
+ ///
+ /// Example (warning, no error checking!):
+ /// ```rust
+ /// use std::ffi::CString;
+ /// use std::net::Ipv4Addr;
+ /// use std::rc::Rc;
+ ///
+ /// use rustables::{Chain, expr::{Cmp, CmpOp}, ProtoFamily, Rule, Table};
+ ///
+ /// let table = Rc::new(Table::new(&CString::new("mytable").unwrap(), ProtoFamily::Inet));
+ /// let chain = Rc::new(Chain::new(&CString::new("mychain").unwrap(), table));
+ /// let mut rule = Rule::new(chain);
+ /// rule.add_expr(&Cmp::new(CmpOp::Eq, 1337u16));
+ /// for expr in Rc::new(rule).get_exprs() {
+ /// println!("{:?}", expr.decode_expr::<Cmp<[u8; 2]>>().unwrap());
+ /// }
+ /// ```
+ /// These limitations occur because casting bytes to any type of the same size
+ /// as the raw input would be *extremely* dangerous in terms of memory safety.
+ fn from_expr(expr: *const sys::nftnl_expr) -> Result<Self, DeserializationError> {
+ unsafe {
+ let ref_len = std::mem::size_of::<[u8; N]>() as u32;
+ let mut data_len = 0;
+ let data = sys::nftnl_expr_get(
+ expr,
+ sys::NFTNL_EXPR_CMP_DATA as u16,
+ &mut data_len as *mut u32,
+ );
+
+ if data.is_null() {
+ return Err(DeserializationError::NullPointer);
+ } else if data_len != ref_len {
+ return Err(DeserializationError::InvalidDataSize);
+ }
+
+ let data = *(data as *const [u8; N]);
+
+ let op = CmpOp::from_raw(sys::nftnl_expr_get_u32(expr, sys::NFTNL_EXPR_CMP_OP as u16))?;
+ Ok(Cmp { op, data })
+ }
+ }
+
+ // call to the other implementation to generate the expression
+ fn to_expr(&self, rule: &Rule) -> *mut sys::nftnl_expr {
+ Cmp {
+ data: &self.data as &[u8],
+ op: self.op,
+ }
+ .to_expr(rule)
+ }
+}
+
+#[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)
+ };
+}
+
+/// 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(ref name) => name.as_bytes_with_nul(),
+ InterfaceName::StartingWith(ref name) => name.as_bytes(),
+ };
+ Cow::from(bytes)
+ }
+}
+
+impl<'a> ToSlice for &'a InterfaceName {
+ fn to_slice(&self) -> Cow<'_, [u8]> {
+ let bytes = match *self {
+ InterfaceName::Exact(ref name) => name.as_bytes_with_nul(),
+ InterfaceName::StartingWith(ref name) => name.as_bytes(),
+ };
+ Cow::from(bytes)
+ }
+}
diff --git a/src/expr/counter.rs b/src/expr/counter.rs
new file mode 100644
index 0000000..71064df
--- /dev/null
+++ b/src/expr/counter.rs
@@ -0,0 +1,46 @@
+use super::{DeserializationError, Expression, Rule};
+use crate::sys;
+use std::os::raw::c_char;
+
+/// A counter expression adds a counter to the rule that is incremented to count number of packets
+/// and number of bytes for all packets that has matched the rule.
+#[derive(Debug, PartialEq)]
+pub struct Counter {
+ pub nb_bytes: u64,
+ pub nb_packets: u64,
+}
+
+impl Counter {
+ pub fn new() -> Self {
+ Self {
+ nb_bytes: 0,
+ nb_packets: 0,
+ }
+ }
+}
+
+impl Expression for Counter {
+ fn get_raw_name() -> *const c_char {
+ b"counter\0" as *const _ as *const c_char
+ }
+
+ fn from_expr(expr: *const sys::nftnl_expr) -> Result<Self, DeserializationError> {
+ unsafe {
+ let nb_bytes = sys::nftnl_expr_get_u64(expr, sys::NFTNL_EXPR_CTR_BYTES as u16);
+ let nb_packets = sys::nftnl_expr_get_u64(expr, sys::NFTNL_EXPR_CTR_PACKETS as u16);
+ Ok(Counter {
+ nb_bytes,
+ nb_packets,
+ })
+ }
+ }
+
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ unsafe {
+ let expr = try_alloc!(sys::nftnl_expr_alloc(Self::get_raw_name()));
+ sys::nftnl_expr_set_u64(expr, sys::NFTNL_EXPR_CTR_BYTES as u16, self.nb_bytes);
+ sys::nftnl_expr_set_u64(expr, sys::NFTNL_EXPR_CTR_PACKETS as u16, self.nb_packets);
+ expr
+ }
+ }
+}
diff --git a/src/expr/ct.rs b/src/expr/ct.rs
new file mode 100644
index 0000000..7d6614c
--- /dev/null
+++ b/src/expr/ct.rs
@@ -0,0 +1,87 @@
+use super::{DeserializationError, Expression, Rule};
+use crate::sys::{self, libc};
+use std::os::raw::c_char;
+
+bitflags::bitflags! {
+ pub struct States: u32 {
+ const INVALID = 1;
+ const ESTABLISHED = 2;
+ const RELATED = 4;
+ const NEW = 8;
+ const UNTRACKED = 64;
+ }
+}
+
+pub enum Conntrack {
+ State,
+ Mark { set: bool },
+}
+
+impl Conntrack {
+ fn raw_key(&self) -> u32 {
+ match *self {
+ Conntrack::State => libc::NFT_CT_STATE as u32,
+ Conntrack::Mark { .. } => libc::NFT_CT_MARK as u32,
+ }
+ }
+}
+
+impl Expression for Conntrack {
+ fn get_raw_name() -> *const c_char {
+ b"ct\0" as *const _ as *const c_char
+ }
+
+ fn from_expr(expr: *const sys::nftnl_expr) -> Result<Self, DeserializationError>
+ where
+ Self: Sized,
+ {
+ unsafe {
+ let ct_key = sys::nftnl_expr_get_u32(expr, sys::NFTNL_EXPR_CT_KEY as u16);
+ let ct_sreg_is_set = sys::nftnl_expr_is_set(expr, sys::NFTNL_EXPR_CT_SREG as u16);
+
+ match ct_key as i32 {
+ libc::NFT_CT_STATE => Ok(Conntrack::State),
+ libc::NFT_CT_MARK => Ok(Conntrack::Mark {
+ set: ct_sreg_is_set,
+ }),
+ _ => Err(DeserializationError::InvalidValue),
+ }
+ }
+ }
+
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ unsafe {
+ let expr = try_alloc!(sys::nftnl_expr_alloc(Self::get_raw_name()));
+
+ if let Conntrack::Mark { set: true } = self {
+ sys::nftnl_expr_set_u32(
+ expr,
+ sys::NFTNL_EXPR_CT_SREG as u16,
+ libc::NFT_REG_1 as u32,
+ );
+ } else {
+ sys::nftnl_expr_set_u32(
+ expr,
+ sys::NFTNL_EXPR_CT_DREG as u16,
+ libc::NFT_REG_1 as u32,
+ );
+ }
+ sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_CT_KEY as u16, self.raw_key());
+
+ expr
+ }
+ }
+}
+
+#[macro_export]
+macro_rules! nft_expr_ct {
+ (state) => {
+ $crate::expr::Conntrack::State
+ };
+ (mark set) => {
+ $crate::expr::Conntrack::Mark { set: true }
+ };
+ (mark) => {
+ $crate::expr::Conntrack::Mark { set: false }
+ };
+}
diff --git a/src/expr/immediate.rs b/src/expr/immediate.rs
new file mode 100644
index 0000000..0787e06
--- /dev/null
+++ b/src/expr/immediate.rs
@@ -0,0 +1,126 @@
+use super::{DeserializationError, Expression, Register, Rule, ToSlice};
+use crate::sys;
+use std::ffi::c_void;
+use std::os::raw::c_char;
+
+/// An immediate expression. Used to set immediate data.
+/// Verdicts are handled separately by [crate::expr::Verdict].
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct Immediate<T> {
+ pub data: T,
+ pub register: Register,
+}
+
+impl<T> Immediate<T> {
+ pub fn new(data: T, register: Register) -> Self {
+ Self { data, register }
+ }
+}
+
+impl<T: ToSlice> Expression for Immediate<T> {
+ fn get_raw_name() -> *const c_char {
+ b"immediate\0" as *const _ as *const c_char
+ }
+
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ unsafe {
+ let expr = try_alloc!(sys::nftnl_expr_alloc(Self::get_raw_name()));
+
+ sys::nftnl_expr_set_u32(
+ expr,
+ sys::NFTNL_EXPR_IMM_DREG as u16,
+ self.register.to_raw(),
+ );
+
+ let data = self.data.to_slice();
+ sys::nftnl_expr_set(
+ expr,
+ sys::NFTNL_EXPR_IMM_DATA as u16,
+ data.as_ptr() as *const c_void,
+ data.len() as u32,
+ );
+
+ expr
+ }
+ }
+}
+
+impl<const N: usize> Expression for Immediate<[u8; N]> {
+ fn get_raw_name() -> *const c_char {
+ Immediate::<u8>::get_raw_name()
+ }
+
+ /// The raw data contained inside `Immediate` expressions can only be deserialized to
+ /// arrays of bytes, to ensure that the memory layout of retrieved data cannot be
+ /// violated. It is your responsibility to provide the correct length of the byte
+ /// data. If the data size is invalid, you will get the error
+ /// `DeserializationError::InvalidDataSize`.
+ ///
+ /// Example (warning, no error checking!):
+ /// ```rust
+ /// use std::ffi::CString;
+ /// use std::net::Ipv4Addr;
+ /// use std::rc::Rc;
+ ///
+ /// use rustables::{Chain, expr::{Immediate, Register}, ProtoFamily, Rule, Table};
+ ///
+ /// let table = Rc::new(Table::new(&CString::new("mytable").unwrap(), ProtoFamily::Inet));
+ /// let chain = Rc::new(Chain::new(&CString::new("mychain").unwrap(), table));
+ /// let mut rule = Rule::new(chain);
+ /// rule.add_expr(&Immediate::new(42u8, Register::Reg1));
+ /// for expr in Rc::new(rule).get_exprs() {
+ /// println!("{:?}", expr.decode_expr::<Immediate<[u8; 1]>>().unwrap());
+ /// }
+ /// ```
+ /// These limitations occur because casting bytes to any type of the same size
+ /// as the raw input would be *extremely* dangerous in terms of memory safety.
+ // As casting bytes to any type of the same size as the input would
+ // be *extremely* dangerous in terms of memory safety,
+ // rustables only accept to deserialize expressions with variable-size data
+ // to arrays of bytes, so that the memory layout cannot be invalid.
+ fn from_expr(expr: *const sys::nftnl_expr) -> Result<Self, DeserializationError> {
+ unsafe {
+ let ref_len = std::mem::size_of::<[u8; N]>() as u32;
+ let mut data_len = 0;
+ let data = sys::nftnl_expr_get(
+ expr,
+ sys::NFTNL_EXPR_IMM_DATA as u16,
+ &mut data_len as *mut u32,
+ );
+
+ if data.is_null() {
+ return Err(DeserializationError::NullPointer);
+ } else if data_len != ref_len {
+ return Err(DeserializationError::InvalidDataSize);
+ }
+
+ let data = *(data as *const [u8; N]);
+
+ let register = Register::from_raw(sys::nftnl_expr_get_u32(
+ expr,
+ sys::NFTNL_EXPR_IMM_DREG as u16,
+ ))?;
+
+ Ok(Immediate { data, register })
+ }
+ }
+
+ // call to the other implementation to generate the expression
+ fn to_expr(&self, rule: &Rule) -> *mut sys::nftnl_expr {
+ Immediate {
+ register: self.register,
+ data: &self.data as &[u8],
+ }
+ .to_expr(rule)
+ }
+}
+
+#[macro_export]
+macro_rules! nft_expr_immediate {
+ (data $value:expr) => {
+ $crate::expr::Immediate {
+ data: $value,
+ register: $crate::expr::Register::Reg1,
+ }
+ };
+}
diff --git a/src/expr/log.rs b/src/expr/log.rs
new file mode 100644
index 0000000..5c06897
--- /dev/null
+++ b/src/expr/log.rs
@@ -0,0 +1,112 @@
+use super::{DeserializationError, Expression, Rule};
+use crate::sys;
+use std::ffi::{CStr, CString};
+use std::os::raw::c_char;
+use thiserror::Error;
+
+/// A Log expression will log all packets that match the rule.
+#[derive(Debug, PartialEq)]
+pub struct Log {
+ pub group: Option<LogGroup>,
+ pub prefix: Option<LogPrefix>,
+}
+
+impl Expression for Log {
+ fn get_raw_name() -> *const sys::libc::c_char {
+ b"log\0" as *const _ as *const c_char
+ }
+
+ fn from_expr(expr: *const sys::nftnl_expr) -> Result<Self, DeserializationError>
+ where
+ Self: Sized,
+ {
+ unsafe {
+ let mut group = None;
+ if sys::nftnl_expr_is_set(expr, sys::NFTNL_EXPR_LOG_GROUP as u16) {
+ group = Some(LogGroup(sys::nftnl_expr_get_u32(
+ expr,
+ sys::NFTNL_EXPR_LOG_GROUP as u16,
+ ) as u16));
+ }
+ let mut prefix = None;
+ if sys::nftnl_expr_is_set(expr, sys::NFTNL_EXPR_LOG_PREFIX as u16) {
+ let raw_prefix = sys::nftnl_expr_get_str(expr, sys::NFTNL_EXPR_LOG_PREFIX as u16);
+ if raw_prefix.is_null() {
+ return Err(DeserializationError::NullPointer);
+ } else {
+ prefix = Some(LogPrefix(CStr::from_ptr(raw_prefix).to_owned()));
+ }
+ }
+ Ok(Log { group, prefix })
+ }
+ }
+
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ unsafe {
+ let expr = try_alloc!(sys::nftnl_expr_alloc(b"log\0" as *const _ as *const c_char));
+ if let Some(log_group) = self.group {
+ sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_LOG_GROUP as u16, log_group.0 as u32);
+ };
+ if let Some(LogPrefix(prefix)) = &self.prefix {
+ sys::nftnl_expr_set_str(expr, sys::NFTNL_EXPR_LOG_PREFIX as u16, prefix.as_ptr());
+ };
+
+ expr
+ }
+ }
+}
+
+#[derive(Error, Debug)]
+pub enum LogPrefixError {
+ #[error("The log prefix string is more than 128 characters long")]
+ TooLongPrefix,
+ #[error("The log prefix string contains an invalid Nul character.")]
+ PrefixContainsANul(#[from] std::ffi::NulError),
+}
+
+/// The NFLOG group that will be assigned to each log line.
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+pub struct LogGroup(pub u16);
+
+/// A prefix that will get prepended to each log line.
+#[derive(Debug, Clone, PartialEq)]
+pub struct LogPrefix(CString);
+
+impl LogPrefix {
+ /// Create a new LogPrefix from a String. Converts it to CString as needed by nftnl. Note
+ /// that LogPrefix should not be more than 127 characters long.
+ pub fn new(prefix: &str) -> Result<Self, LogPrefixError> {
+ if prefix.chars().count() > 127 {
+ return Err(LogPrefixError::TooLongPrefix);
+ }
+ Ok(LogPrefix(CString::new(prefix)?))
+ }
+}
+
+#[macro_export]
+macro_rules! nft_expr_log {
+ (group $group:ident prefix $prefix:expr) => {
+ $crate::expr::Log {
+ group: $group,
+ prefix: $prefix,
+ }
+ };
+ (prefix $prefix:expr) => {
+ $crate::expr::Log {
+ group: None,
+ prefix: $prefix,
+ }
+ };
+ (group $group:ident) => {
+ $crate::expr::Log {
+ group: $group,
+ prefix: None,
+ }
+ };
+ () => {
+ $crate::expr::Log {
+ group: None,
+ prefix: None,
+ }
+ };
+}
diff --git a/src/expr/lookup.rs b/src/expr/lookup.rs
new file mode 100644
index 0000000..8e288a0
--- /dev/null
+++ b/src/expr/lookup.rs
@@ -0,0 +1,79 @@
+use super::{DeserializationError, Expression, Rule};
+use crate::set::Set;
+use crate::sys::{self, libc};
+use std::ffi::{CStr, CString};
+use std::os::raw::c_char;
+
+#[derive(Debug, PartialEq)]
+pub struct Lookup {
+ set_name: CString,
+ set_id: u32,
+}
+
+impl Lookup {
+ /// Creates a new lookup entry.
+ /// May return None if the set have no name.
+ pub fn new<K>(set: &Set<'_, K>) -> Option<Self> {
+ set.get_name().map(|set_name| Lookup {
+ set_name: set_name.to_owned(),
+ set_id: set.get_id(),
+ })
+ }
+}
+
+impl Expression for Lookup {
+ fn get_raw_name() -> *const libc::c_char {
+ b"lookup\0" as *const _ as *const c_char
+ }
+
+ fn from_expr(expr: *const sys::nftnl_expr) -> Result<Self, DeserializationError>
+ where
+ Self: Sized,
+ {
+ unsafe {
+ let set_name = sys::nftnl_expr_get_str(expr, sys::NFTNL_EXPR_LOOKUP_SET as u16);
+ let set_id = sys::nftnl_expr_get_u32(expr, sys::NFTNL_EXPR_LOOKUP_SET_ID as u16);
+
+ if set_name.is_null() {
+ return Err(DeserializationError::NullPointer);
+ }
+
+ let set_name = CStr::from_ptr(set_name).to_owned();
+
+ Ok(Lookup { set_id, set_name })
+ }
+ }
+
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ unsafe {
+ let expr = try_alloc!(sys::nftnl_expr_alloc(Self::get_raw_name()));
+
+ sys::nftnl_expr_set_u32(
+ expr,
+ sys::NFTNL_EXPR_LOOKUP_SREG as u16,
+ libc::NFT_REG_1 as u32,
+ );
+ sys::nftnl_expr_set_str(
+ expr,
+ sys::NFTNL_EXPR_LOOKUP_SET as u16,
+ self.set_name.as_ptr() as *const _ as *const c_char,
+ );
+ sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_LOOKUP_SET_ID as u16, self.set_id);
+
+ // This code is left here since it's quite likely we need it again when we get further
+ // if self.reverse {
+ // sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_LOOKUP_FLAGS as u16,
+ // libc::NFT_LOOKUP_F_INV as u32);
+ // }
+
+ expr
+ }
+ }
+}
+
+#[macro_export]
+macro_rules! nft_expr_lookup {
+ ($set:expr) => {
+ $crate::expr::Lookup::new($set)
+ };
+}
diff --git a/src/expr/masquerade.rs b/src/expr/masquerade.rs
new file mode 100644
index 0000000..c1a06de
--- /dev/null
+++ b/src/expr/masquerade.rs
@@ -0,0 +1,24 @@
+use super::{DeserializationError, Expression, Rule};
+use crate::sys;
+use std::os::raw::c_char;
+
+/// Sets the source IP to that of the output interface.
+#[derive(Debug, PartialEq)]
+pub struct Masquerade;
+
+impl Expression for Masquerade {
+ fn get_raw_name() -> *const sys::libc::c_char {
+ b"masq\0" as *const _ as *const c_char
+ }
+
+ fn from_expr(_expr: *const sys::nftnl_expr) -> Result<Self, DeserializationError>
+ where
+ Self: Sized,
+ {
+ Ok(Masquerade)
+ }
+
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ try_alloc!(unsafe { sys::nftnl_expr_alloc(Self::get_raw_name()) })
+ }
+}
diff --git a/src/expr/meta.rs b/src/expr/meta.rs
new file mode 100644
index 0000000..bf77774
--- /dev/null
+++ b/src/expr/meta.rs
@@ -0,0 +1,175 @@
+use super::{DeserializationError, Expression, Rule};
+use crate::sys::{self, libc};
+use std::os::raw::c_char;
+
+/// A meta expression refers to meta data associated with a packet.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[non_exhaustive]
+pub enum Meta {
+ /// Packet ethertype protocol (skb->protocol), invalid in OUTPUT.
+ Protocol,
+ /// Packet mark.
+ Mark { set: bool },
+ /// Packet input interface index (dev->ifindex).
+ Iif,
+ /// Packet output interface index (dev->ifindex).
+ Oif,
+ /// Packet input interface name (dev->name)
+ IifName,
+ /// Packet output interface name (dev->name).
+ OifName,
+ /// Packet input interface type (dev->type).
+ IifType,
+ /// Packet output interface type (dev->type).
+ OifType,
+ /// Originating socket UID (fsuid).
+ SkUid,
+ /// Originating socket GID (fsgid).
+ SkGid,
+ /// Netfilter protocol (Transport layer protocol).
+ NfProto,
+ /// Layer 4 protocol number.
+ L4Proto,
+ /// Socket control group (skb->sk->sk_classid).
+ Cgroup,
+ /// A 32bit pseudo-random number
+ PRandom,
+}
+
+impl Meta {
+ /// Returns the corresponding `NFT_*` constant for this meta expression.
+ pub fn to_raw_key(&self) -> u32 {
+ use Meta::*;
+ match *self {
+ Protocol => libc::NFT_META_PROTOCOL as u32,
+ Mark { .. } => libc::NFT_META_MARK as u32,
+ Iif => libc::NFT_META_IIF as u32,
+ Oif => libc::NFT_META_OIF as u32,
+ IifName => libc::NFT_META_IIFNAME as u32,
+ OifName => libc::NFT_META_OIFNAME as u32,
+ IifType => libc::NFT_META_IIFTYPE as u32,
+ OifType => libc::NFT_META_OIFTYPE as u32,
+ SkUid => libc::NFT_META_SKUID as u32,
+ SkGid => libc::NFT_META_SKGID as u32,
+ NfProto => libc::NFT_META_NFPROTO as u32,
+ L4Proto => libc::NFT_META_L4PROTO as u32,
+ Cgroup => libc::NFT_META_CGROUP as u32,
+ PRandom => libc::NFT_META_PRANDOM as u32,
+ }
+ }
+
+ fn from_raw(val: u32) -> Result<Self, DeserializationError> {
+ match val as i32 {
+ libc::NFT_META_PROTOCOL => Ok(Self::Protocol),
+ libc::NFT_META_MARK => Ok(Self::Mark { set: false }),
+ libc::NFT_META_IIF => Ok(Self::Iif),
+ libc::NFT_META_OIF => Ok(Self::Oif),
+ libc::NFT_META_IIFNAME => Ok(Self::IifName),
+ libc::NFT_META_OIFNAME => Ok(Self::OifName),
+ libc::NFT_META_IIFTYPE => Ok(Self::IifType),
+ libc::NFT_META_OIFTYPE => Ok(Self::OifType),
+ libc::NFT_META_SKUID => Ok(Self::SkUid),
+ libc::NFT_META_SKGID => Ok(Self::SkGid),
+ libc::NFT_META_NFPROTO => Ok(Self::NfProto),
+ libc::NFT_META_L4PROTO => Ok(Self::L4Proto),
+ libc::NFT_META_CGROUP => Ok(Self::Cgroup),
+ libc::NFT_META_PRANDOM => Ok(Self::PRandom),
+ _ => Err(DeserializationError::InvalidValue),
+ }
+ }
+}
+
+impl Expression for Meta {
+ fn get_raw_name() -> *const libc::c_char {
+ b"meta\0" as *const _ as *const c_char
+ }
+
+ fn from_expr(expr: *const sys::nftnl_expr) -> Result<Self, DeserializationError>
+ where
+ Self: Sized,
+ {
+ unsafe {
+ let mut ret = Self::from_raw(sys::nftnl_expr_get_u32(
+ expr,
+ sys::NFTNL_EXPR_META_KEY as u16,
+ ))?;
+
+ if let Self::Mark { ref mut set } = ret {
+ *set = sys::nftnl_expr_is_set(expr, sys::NFTNL_EXPR_META_SREG as u16);
+ }
+
+ Ok(ret)
+ }
+ }
+
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ unsafe {
+ let expr = try_alloc!(sys::nftnl_expr_alloc(Self::get_raw_name()));
+
+ if let Meta::Mark { set: true } = self {
+ sys::nftnl_expr_set_u32(
+ expr,
+ sys::NFTNL_EXPR_META_SREG as u16,
+ libc::NFT_REG_1 as u32,
+ );
+ } else {
+ sys::nftnl_expr_set_u32(
+ expr,
+ sys::NFTNL_EXPR_META_DREG as u16,
+ libc::NFT_REG_1 as u32,
+ );
+ }
+ sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_META_KEY as u16, self.to_raw_key());
+ expr
+ }
+ }
+}
+
+#[macro_export]
+macro_rules! nft_expr_meta {
+ (proto) => {
+ $crate::expr::Meta::Protocol
+ };
+ (mark set) => {
+ $crate::expr::Meta::Mark { set: true }
+ };
+ (mark) => {
+ $crate::expr::Meta::Mark { set: false }
+ };
+ (iif) => {
+ $crate::expr::Meta::Iif
+ };
+ (oif) => {
+ $crate::expr::Meta::Oif
+ };
+ (iifname) => {
+ $crate::expr::Meta::IifName
+ };
+ (oifname) => {
+ $crate::expr::Meta::OifName
+ };
+ (iiftype) => {
+ $crate::expr::Meta::IifType
+ };
+ (oiftype) => {
+ $crate::expr::Meta::OifType
+ };
+ (skuid) => {
+ $crate::expr::Meta::SkUid
+ };
+ (skgid) => {
+ $crate::expr::Meta::SkGid
+ };
+ (nfproto) => {
+ $crate::expr::Meta::NfProto
+ };
+ (l4proto) => {
+ $crate::expr::Meta::L4Proto
+ };
+ (cgroup) => {
+ $crate::expr::Meta::Cgroup
+ };
+ (random) => {
+ $crate::expr::Meta::PRandom
+ };
+}
diff --git a/src/expr/mod.rs b/src/expr/mod.rs
new file mode 100644
index 0000000..fbf49d6
--- /dev/null
+++ b/src/expr/mod.rs
@@ -0,0 +1,242 @@
+//! A module with all the nftables expressions that can be added to [`Rule`]s to build up how
+//! they match against packets.
+//!
+//! [`Rule`]: struct.Rule.html
+
+use std::borrow::Cow;
+use std::net::IpAddr;
+use std::net::Ipv4Addr;
+use std::net::Ipv6Addr;
+
+use super::rule::Rule;
+use crate::sys::{self, libc};
+use thiserror::Error;
+
+mod bitwise;
+pub use self::bitwise::*;
+
+mod cmp;
+pub use self::cmp::*;
+
+mod counter;
+pub use self::counter::*;
+
+pub mod ct;
+pub use self::ct::*;
+
+mod immediate;
+pub use self::immediate::*;
+
+mod log;
+pub use self::log::*;
+
+mod lookup;
+pub use self::lookup::*;
+
+mod masquerade;
+pub use self::masquerade::*;
+
+mod meta;
+pub use self::meta::*;
+
+mod nat;
+pub use self::nat::*;
+
+mod payload;
+pub use self::payload::*;
+
+mod reject;
+pub use self::reject::{IcmpCode, Reject};
+
+mod register;
+pub use self::register::Register;
+
+mod verdict;
+pub use self::verdict::*;
+
+mod wrapper;
+pub use self::wrapper::ExpressionWrapper;
+
+#[derive(Debug, Error)]
+pub enum DeserializationError {
+ #[error("The expected expression type doesn't match the name of the raw expression")]
+ /// The expected expression type doesn't match the name of the raw expression
+ InvalidExpressionKind,
+
+ #[error("Deserializing the requested type isn't implemented yet")]
+ /// Deserializing the requested type isn't implemented yet
+ NotImplemented,
+
+ #[error("The expression value cannot be deserialized to the requested type")]
+ /// The expression value cannot be deserialized to the requested type
+ InvalidValue,
+
+ #[error("A pointer was null while a non-null pointer was expected")]
+ /// A pointer was null while a non-null pointer was expected
+ NullPointer,
+
+ #[error(
+ "The size of a raw value was incoherent with the expected type of the deserialized value"
+ )]
+ /// The size of a raw value was incoherent with the expected type of the deserialized value
+ InvalidDataSize,
+
+ #[error(transparent)]
+ /// Couldn't find a matching protocol
+ InvalidProtolFamily(#[from] super::InvalidProtocolFamily),
+}
+
+/// Trait for every safe wrapper of an nftables expression.
+pub trait Expression {
+ /// Returns the raw name used by nftables to identify the rule.
+ fn get_raw_name() -> *const libc::c_char;
+
+ /// Try to parse the expression from a raw nftables expression,
+ /// returning a [DeserializationError] if the attempted parsing failed.
+ fn from_expr(_expr: *const sys::nftnl_expr) -> Result<Self, DeserializationError>
+ where
+ Self: Sized,
+ {
+ Err(DeserializationError::NotImplemented)
+ }
+
+ /// Allocates and returns the low level `nftnl_expr` representation of this expression.
+ /// The caller to this method is responsible for freeing the expression.
+ fn to_expr(&self, rule: &Rule) -> *mut sys::nftnl_expr;
+}
+
+/// 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<'a> ToSlice for &'a [u8] {
+ fn to_slice(&self) -> Cow<'_, [u8]> {
+ Cow::Borrowed(self)
+ }
+}
+
+impl<'a> ToSlice for &'a [u16] {
+ fn to_slice(&self) -> Cow<'_, [u8]> {
+ let ptr = self.as_ptr() as *const u8;
+ let len = self.len() * 2;
+ Cow::Borrowed(unsafe { std::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]> {
+ Cow::Owned(self.octets().to_vec())
+ }
+}
+
+impl ToSlice for Ipv6Addr {
+ fn to_slice(&self) -> Cow<'_, [u8]> {
+ Cow::Owned(self.octets().to_vec())
+ }
+}
+
+impl ToSlice for u8 {
+ fn to_slice(&self) -> Cow<'_, [u8]> {
+ Cow::Owned(vec![*self])
+ }
+}
+
+impl ToSlice for u16 {
+ fn to_slice(&self) -> Cow<'_, [u8]> {
+ let b0 = (*self & 0x00ff) as u8;
+ let b1 = (*self >> 8) as u8;
+ Cow::Owned(vec![b0, b1])
+ }
+}
+
+impl ToSlice for u32 {
+ fn to_slice(&self) -> Cow<'_, [u8]> {
+ let b0 = *self as u8;
+ let b1 = (*self >> 8) as u8;
+ let b2 = (*self >> 16) as u8;
+ let b3 = (*self >> 24) as u8;
+ Cow::Owned(vec![b0, b1, b2, b3])
+ }
+}
+
+impl ToSlice for i32 {
+ fn to_slice(&self) -> Cow<'_, [u8]> {
+ let b0 = *self as u8;
+ let b1 = (*self >> 8) as u8;
+ let b2 = (*self >> 16) as u8;
+ let b3 = (*self >> 24) as u8;
+ Cow::Owned(vec![b0, b1, b2, b3])
+ }
+}
+
+impl<'a> ToSlice for &'a str {
+ fn to_slice(&self) -> Cow<'_, [u8]> {
+ Cow::from(self.as_bytes())
+ }
+}
+
+#[macro_export(local_inner_macros)]
+macro_rules! nft_expr {
+ (bitwise mask $mask:expr,xor $xor:expr) => {
+ nft_expr_bitwise!(mask $mask, xor $xor)
+ };
+ (cmp $op:tt $data:expr) => {
+ nft_expr_cmp!($op $data)
+ };
+ (counter) => {
+ $crate::expr::Counter { nb_bytes: 0, nb_packets: 0}
+ };
+ (ct $key:ident set) => {
+ nft_expr_ct!($key set)
+ };
+ (ct $key:ident) => {
+ nft_expr_ct!($key)
+ };
+ (immediate $expr:ident $value:expr) => {
+ nft_expr_immediate!($expr $value)
+ };
+ (log group $group:ident prefix $prefix:expr) => {
+ nft_expr_log!(group $group prefix $prefix)
+ };
+ (log group $group:ident) => {
+ nft_expr_log!(group $group)
+ };
+ (log prefix $prefix:expr) => {
+ nft_expr_log!(prefix $prefix)
+ };
+ (log) => {
+ nft_expr_log!()
+ };
+ (lookup $set:expr) => {
+ nft_expr_lookup!($set)
+ };
+ (masquerade) => {
+ $crate::expr::Masquerade
+ };
+ (meta $expr:ident set) => {
+ nft_expr_meta!($expr set)
+ };
+ (meta $expr:ident) => {
+ nft_expr_meta!($expr)
+ };
+ (payload $proto:ident $field:ident) => {
+ nft_expr_payload!($proto $field)
+ };
+ (verdict $verdict:ident) => {
+ nft_expr_verdict!($verdict)
+ };
+ (verdict $verdict:ident $chain:expr) => {
+ nft_expr_verdict!($verdict $chain)
+ };
+}
diff --git a/src/expr/nat.rs b/src/expr/nat.rs
new file mode 100644
index 0000000..8beaa30
--- /dev/null
+++ b/src/expr/nat.rs
@@ -0,0 +1,99 @@
+use super::{DeserializationError, Expression, Register, Rule};
+use crate::ProtoFamily;
+use crate::sys::{self, libc};
+use std::{convert::TryFrom, os::raw::c_char};
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[repr(i32)]
+pub enum NatType {
+ /// Source NAT. Changes the source address of a packet
+ SNat = libc::NFT_NAT_SNAT,
+ /// Destination NAT. Changeth the destination address of a packet
+ DNat = libc::NFT_NAT_DNAT,
+}
+
+impl NatType {
+ fn from_raw(val: u32) -> Result<Self, DeserializationError> {
+ match val as i32 {
+ libc::NFT_NAT_SNAT => Ok(NatType::SNat),
+ libc::NFT_NAT_DNAT => Ok(NatType::DNat),
+ _ => Err(DeserializationError::InvalidValue),
+ }
+ }
+}
+
+/// A source or destination NAT statement. Modifies the source or destination address
+/// (and possibly port) of packets.
+#[derive(Debug, PartialEq)]
+pub struct Nat {
+ pub nat_type: NatType,
+ pub family: ProtoFamily,
+ pub ip_register: Register,
+ pub port_register: Option<Register>,
+}
+
+impl Expression for Nat {
+ fn get_raw_name() -> *const libc::c_char {
+ b"nat\0" as *const _ as *const c_char
+ }
+
+ fn from_expr(expr: *const sys::nftnl_expr) -> Result<Self, DeserializationError>
+ where
+ Self: Sized,
+ {
+ unsafe {
+ let nat_type = NatType::from_raw(sys::nftnl_expr_get_u32(
+ expr,
+ sys::NFTNL_EXPR_NAT_TYPE as u16,
+ ))?;
+
+ let family = ProtoFamily::try_from(sys::nftnl_expr_get_u32(
+ expr,
+ sys::NFTNL_EXPR_NAT_FAMILY as u16,
+ ) as i32)?;
+
+ let ip_register = Register::from_raw(sys::nftnl_expr_get_u32(
+ expr,
+ sys::NFTNL_EXPR_NAT_REG_ADDR_MIN as u16,
+ ))?;
+
+ let mut port_register = None;
+ if sys::nftnl_expr_is_set(expr, sys::NFTNL_EXPR_NAT_REG_PROTO_MIN as u16) {
+ port_register = Some(Register::from_raw(sys::nftnl_expr_get_u32(
+ expr,
+ sys::NFTNL_EXPR_NAT_REG_PROTO_MIN as u16,
+ ))?);
+ }
+
+ Ok(Nat {
+ ip_register,
+ nat_type,
+ family,
+ port_register,
+ })
+ }
+ }
+
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ let expr = try_alloc!(unsafe { sys::nftnl_expr_alloc(Self::get_raw_name()) });
+
+ unsafe {
+ sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_NAT_TYPE as u16, self.nat_type as u32);
+ sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_NAT_FAMILY as u16, self.family as u32);
+ sys::nftnl_expr_set_u32(
+ expr,
+ sys::NFTNL_EXPR_NAT_REG_ADDR_MIN as u16,
+ self.ip_register.to_raw(),
+ );
+ if let Some(port_register) = self.port_register {
+ sys::nftnl_expr_set_u32(
+ expr,
+ sys::NFTNL_EXPR_NAT_REG_PROTO_MIN as u16,
+ port_register.to_raw(),
+ );
+ }
+ }
+
+ expr
+ }
+}
diff --git a/src/expr/payload.rs b/src/expr/payload.rs
new file mode 100644
index 0000000..7612fd9
--- /dev/null
+++ b/src/expr/payload.rs
@@ -0,0 +1,531 @@
+use super::{DeserializationError, Expression, Rule};
+use crate::sys::{self, libc};
+use std::os::raw::c_char;
+
+pub trait HeaderField {
+ fn offset(&self) -> u32;
+ fn len(&self) -> u32;
+}
+
+/// Payload expressions refer to data from the packet's payload.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum Payload {
+ LinkLayer(LLHeaderField),
+ Network(NetworkHeaderField),
+ Transport(TransportHeaderField),
+}
+
+impl Payload {
+ pub fn build(&self) -> RawPayload {
+ match *self {
+ Payload::LinkLayer(ref f) => RawPayload::LinkLayer(RawPayloadData {
+ offset: f.offset(),
+ len: f.len(),
+ }),
+ Payload::Network(ref f) => RawPayload::Network(RawPayloadData {
+ offset: f.offset(),
+ len: f.len(),
+ }),
+ Payload::Transport(ref f) => RawPayload::Transport(RawPayloadData {
+ offset: f.offset(),
+ len: f.offset(),
+ }),
+ }
+ }
+}
+
+impl Expression for Payload {
+ fn get_raw_name() -> *const libc::c_char {
+ RawPayload::get_raw_name()
+ }
+
+ fn to_expr(&self, rule: &Rule) -> *mut sys::nftnl_expr {
+ self.build().to_expr(rule)
+ }
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub struct RawPayloadData {
+ offset: u32,
+ len: u32,
+}
+
+/// Because deserializing a `Payload` expression is not possible (there is not enough information
+/// in the expression itself, this enum should be used to deserialize payloads.
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum RawPayload {
+ LinkLayer(RawPayloadData),
+ Network(RawPayloadData),
+ Transport(RawPayloadData),
+}
+
+impl RawPayload {
+ fn base(&self) -> u32 {
+ match self {
+ Self::LinkLayer(_) => libc::NFT_PAYLOAD_LL_HEADER as u32,
+ Self::Network(_) => libc::NFT_PAYLOAD_NETWORK_HEADER as u32,
+ Self::Transport(_) => libc::NFT_PAYLOAD_TRANSPORT_HEADER as u32,
+ }
+ }
+}
+
+impl HeaderField for RawPayload {
+ fn offset(&self) -> u32 {
+ match self {
+ Self::LinkLayer(ref f) | Self::Network(ref f) | Self::Transport(ref f) => f.offset,
+ }
+ }
+
+ fn len(&self) -> u32 {
+ match self {
+ Self::LinkLayer(ref f) | Self::Network(ref f) | Self::Transport(ref f) => f.len,
+ }
+ }
+}
+
+impl Expression for RawPayload {
+ fn get_raw_name() -> *const libc::c_char {
+ b"payload\0" as *const _ as *const c_char
+ }
+
+ fn from_expr(expr: *const sys::nftnl_expr) -> Result<Self, DeserializationError> {
+ unsafe {
+ let base = sys::nftnl_expr_get_u32(expr, sys::NFTNL_EXPR_PAYLOAD_BASE as u16);
+ let offset = sys::nftnl_expr_get_u32(expr, sys::NFTNL_EXPR_PAYLOAD_OFFSET as u16);
+ let len = sys::nftnl_expr_get_u32(expr, sys::NFTNL_EXPR_PAYLOAD_LEN as u16);
+ match base as i32 {
+ libc::NFT_PAYLOAD_LL_HEADER => Ok(Self::LinkLayer(RawPayloadData { offset, len })),
+ libc::NFT_PAYLOAD_NETWORK_HEADER => {
+ Ok(Self::Network(RawPayloadData { offset, len }))
+ }
+ libc::NFT_PAYLOAD_TRANSPORT_HEADER => {
+ Ok(Self::Transport(RawPayloadData { offset, len }))
+ }
+
+ _ => return Err(DeserializationError::InvalidValue),
+ }
+ }
+ }
+
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ unsafe {
+ let expr = try_alloc!(sys::nftnl_expr_alloc(Self::get_raw_name()));
+
+ sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_PAYLOAD_BASE as u16, self.base());
+ sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_PAYLOAD_OFFSET as u16, self.offset());
+ sys::nftnl_expr_set_u32(expr, sys::NFTNL_EXPR_PAYLOAD_LEN as u16, self.len());
+ sys::nftnl_expr_set_u32(
+ expr,
+ sys::NFTNL_EXPR_PAYLOAD_DREG as u16,
+ libc::NFT_REG_1 as u32,
+ );
+
+ expr
+ }
+ }
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[non_exhaustive]
+pub enum LLHeaderField {
+ Daddr,
+ Saddr,
+ EtherType,
+}
+
+impl HeaderField for LLHeaderField {
+ fn offset(&self) -> u32 {
+ use self::LLHeaderField::*;
+ match *self {
+ Daddr => 0,
+ Saddr => 6,
+ EtherType => 12,
+ }
+ }
+
+ fn len(&self) -> u32 {
+ use self::LLHeaderField::*;
+ match *self {
+ Daddr => 6,
+ Saddr => 6,
+ EtherType => 2,
+ }
+ }
+}
+
+impl LLHeaderField {
+ pub fn from_raw_data(data: &RawPayloadData) -> Result<Self, DeserializationError> {
+ let off = data.offset;
+ let len = data.len;
+
+ if off == 0 && len == 6 {
+ Ok(Self::Daddr)
+ } else if off == 6 && len == 6 {
+ Ok(Self::Saddr)
+ } else if off == 12 && len == 2 {
+ Ok(Self::EtherType)
+ } else {
+ Err(DeserializationError::InvalidValue)
+ }
+ }
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum NetworkHeaderField {
+ Ipv4(Ipv4HeaderField),
+ Ipv6(Ipv6HeaderField),
+}
+
+impl HeaderField for NetworkHeaderField {
+ fn offset(&self) -> u32 {
+ use self::NetworkHeaderField::*;
+ match *self {
+ Ipv4(ref f) => f.offset(),
+ Ipv6(ref f) => f.offset(),
+ }
+ }
+
+ fn len(&self) -> u32 {
+ use self::NetworkHeaderField::*;
+ match *self {
+ Ipv4(ref f) => f.len(),
+ Ipv6(ref f) => f.len(),
+ }
+ }
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[non_exhaustive]
+pub enum Ipv4HeaderField {
+ Ttl,
+ Protocol,
+ Saddr,
+ Daddr,
+}
+
+impl HeaderField for Ipv4HeaderField {
+ fn offset(&self) -> u32 {
+ use self::Ipv4HeaderField::*;
+ match *self {
+ Ttl => 8,
+ Protocol => 9,
+ Saddr => 12,
+ Daddr => 16,
+ }
+ }
+
+ fn len(&self) -> u32 {
+ use self::Ipv4HeaderField::*;
+ match *self {
+ Ttl => 1,
+ Protocol => 1,
+ Saddr => 4,
+ Daddr => 4,
+ }
+ }
+}
+
+impl Ipv4HeaderField {
+ pub fn from_raw_data(data: &RawPayloadData) -> Result<Self, DeserializationError> {
+ let off = data.offset;
+ let len = data.len;
+
+ if off == 8 && len == 1 {
+ Ok(Self::Ttl)
+ } else if off == 9 && len == 1 {
+ Ok(Self::Protocol)
+ } else if off == 12 && len == 4 {
+ Ok(Self::Saddr)
+ } else if off == 16 && len == 4 {
+ Ok(Self::Daddr)
+ } else {
+ Err(DeserializationError::InvalidValue)
+ }
+ }
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[non_exhaustive]
+pub enum Ipv6HeaderField {
+ NextHeader,
+ HopLimit,
+ Saddr,
+ Daddr,
+}
+
+impl HeaderField for Ipv6HeaderField {
+ fn offset(&self) -> u32 {
+ use self::Ipv6HeaderField::*;
+ match *self {
+ NextHeader => 6,
+ HopLimit => 7,
+ Saddr => 8,
+ Daddr => 24,
+ }
+ }
+
+ fn len(&self) -> u32 {
+ use self::Ipv6HeaderField::*;
+ match *self {
+ NextHeader => 1,
+ HopLimit => 1,
+ Saddr => 16,
+ Daddr => 16,
+ }
+ }
+}
+
+impl Ipv6HeaderField {
+ pub fn from_raw_data(data: &RawPayloadData) -> Result<Self, DeserializationError> {
+ let off = data.offset;
+ let len = data.len;
+
+ if off == 6 && len == 1 {
+ Ok(Self::NextHeader)
+ } else if off == 7 && len == 1 {
+ Ok(Self::HopLimit)
+ } else if off == 8 && len == 16 {
+ Ok(Self::Saddr)
+ } else if off == 24 && len == 16 {
+ Ok(Self::Daddr)
+ } else {
+ Err(DeserializationError::InvalidValue)
+ }
+ }
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[non_exhaustive]
+pub enum TransportHeaderField {
+ Tcp(TcpHeaderField),
+ Udp(UdpHeaderField),
+ Icmpv6(Icmpv6HeaderField),
+}
+
+impl HeaderField for TransportHeaderField {
+ fn offset(&self) -> u32 {
+ use self::TransportHeaderField::*;
+ match *self {
+ Tcp(ref f) => f.offset(),
+ Udp(ref f) => f.offset(),
+ Icmpv6(ref f) => f.offset(),
+ }
+ }
+
+ fn len(&self) -> u32 {
+ use self::TransportHeaderField::*;
+ match *self {
+ Tcp(ref f) => f.len(),
+ Udp(ref f) => f.len(),
+ Icmpv6(ref f) => f.len(),
+ }
+ }
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[non_exhaustive]
+pub enum TcpHeaderField {
+ Sport,
+ Dport,
+}
+
+impl HeaderField for TcpHeaderField {
+ fn offset(&self) -> u32 {
+ use self::TcpHeaderField::*;
+ match *self {
+ Sport => 0,
+ Dport => 2,
+ }
+ }
+
+ fn len(&self) -> u32 {
+ use self::TcpHeaderField::*;
+ match *self {
+ Sport => 2,
+ Dport => 2,
+ }
+ }
+}
+
+impl TcpHeaderField {
+ pub fn from_raw_data(data: &RawPayloadData) -> Result<Self, DeserializationError> {
+ let off = data.offset;
+ let len = data.len;
+
+ if off == 0 && len == 2 {
+ Ok(Self::Sport)
+ } else if off == 2 && len == 2 {
+ Ok(Self::Dport)
+ } else {
+ Err(DeserializationError::InvalidValue)
+ }
+ }
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[non_exhaustive]
+pub enum UdpHeaderField {
+ Sport,
+ Dport,
+ Len,
+}
+
+impl HeaderField for UdpHeaderField {
+ fn offset(&self) -> u32 {
+ use self::UdpHeaderField::*;
+ match *self {
+ Sport => 0,
+ Dport => 2,
+ Len => 4,
+ }
+ }
+
+ fn len(&self) -> u32 {
+ use self::UdpHeaderField::*;
+ match *self {
+ Sport => 2,
+ Dport => 2,
+ Len => 2,
+ }
+ }
+}
+
+impl UdpHeaderField {
+ pub fn from_raw_data(data: &RawPayloadData) -> Result<Self, DeserializationError> {
+ let off = data.offset;
+ let len = data.len;
+
+ if off == 0 && len == 2 {
+ Ok(Self::Sport)
+ } else if off == 2 && len == 2 {
+ Ok(Self::Dport)
+ } else if off == 4 && len == 2 {
+ Ok(Self::Len)
+ } else {
+ Err(DeserializationError::InvalidValue)
+ }
+ }
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+#[non_exhaustive]
+pub enum Icmpv6HeaderField {
+ Type,
+ Code,
+ Checksum,
+}
+
+impl HeaderField for Icmpv6HeaderField {
+ fn offset(&self) -> u32 {
+ use self::Icmpv6HeaderField::*;
+ match *self {
+ Type => 0,
+ Code => 1,
+ Checksum => 2,
+ }
+ }
+
+ fn len(&self) -> u32 {
+ use self::Icmpv6HeaderField::*;
+ match *self {
+ Type => 1,
+ Code => 1,
+ Checksum => 2,
+ }
+ }
+}
+
+impl Icmpv6HeaderField {
+ pub fn from_raw_data(data: &RawPayloadData) -> Result<Self, DeserializationError> {
+ let off = data.offset;
+ let len = data.len;
+
+ if off == 0 && len == 1 {
+ Ok(Self::Type)
+ } else if off == 1 && len == 1 {
+ Ok(Self::Code)
+ } else if off == 2 && len == 2 {
+ Ok(Self::Checksum)
+ } else {
+ Err(DeserializationError::InvalidValue)
+ }
+ }
+}
+
+#[macro_export(local_inner_macros)]
+macro_rules! nft_expr_payload {
+ (@ipv4_field ttl) => {
+ $crate::expr::Ipv4HeaderField::Ttl
+ };
+ (@ipv4_field protocol) => {
+ $crate::expr::Ipv4HeaderField::Protocol
+ };
+ (@ipv4_field saddr) => {
+ $crate::expr::Ipv4HeaderField::Saddr
+ };
+ (@ipv4_field daddr) => {
+ $crate::expr::Ipv4HeaderField::Daddr
+ };
+
+ (@ipv6_field nextheader) => {
+ $crate::expr::Ipv6HeaderField::NextHeader
+ };
+ (@ipv6_field hoplimit) => {
+ $crate::expr::Ipv6HeaderField::HopLimit
+ };
+ (@ipv6_field saddr) => {
+ $crate::expr::Ipv6HeaderField::Saddr
+ };
+ (@ipv6_field daddr) => {
+ $crate::expr::Ipv6HeaderField::Daddr
+ };
+
+ (@tcp_field sport) => {
+ $crate::expr::TcpHeaderField::Sport
+ };
+ (@tcp_field dport) => {
+ $crate::expr::TcpHeaderField::Dport
+ };
+
+ (@udp_field sport) => {
+ $crate::expr::UdpHeaderField::Sport
+ };
+ (@udp_field dport) => {
+ $crate::expr::UdpHeaderField::Dport
+ };
+ (@udp_field len) => {
+ $crate::expr::UdpHeaderField::Len
+ };
+
+ (ethernet daddr) => {
+ $crate::expr::Payload::LinkLayer($crate::expr::LLHeaderField::Daddr)
+ };
+ (ethernet saddr) => {
+ $crate::expr::Payload::LinkLayer($crate::expr::LLHeaderField::Saddr)
+ };
+ (ethernet ethertype) => {
+ $crate::expr::Payload::LinkLayer($crate::expr::LLHeaderField::EtherType)
+ };
+
+ (ipv4 $field:ident) => {
+ $crate::expr::Payload::Network($crate::expr::NetworkHeaderField::Ipv4(
+ nft_expr_payload!(@ipv4_field $field),
+ ))
+ };
+ (ipv6 $field:ident) => {
+ $crate::expr::Payload::Network($crate::expr::NetworkHeaderField::Ipv6(
+ nft_expr_payload!(@ipv6_field $field),
+ ))
+ };
+
+ (tcp $field:ident) => {
+ $crate::expr::Payload::Transport($crate::expr::TransportHeaderField::Tcp(
+ nft_expr_payload!(@tcp_field $field),
+ ))
+ };
+ (udp $field:ident) => {
+ $crate::expr::Payload::Transport($crate::expr::TransportHeaderField::Udp(
+ nft_expr_payload!(@udp_field $field),
+ ))
+ };
+}
diff --git a/src/expr/register.rs b/src/expr/register.rs
new file mode 100644
index 0000000..f0aed94
--- /dev/null
+++ b/src/expr/register.rs
@@ -0,0 +1,34 @@
+use std::fmt::Debug;
+
+use crate::sys::libc;
+
+use super::DeserializationError;
+
+/// A netfilter data register. The expressions store and read data to and from these
+/// when evaluating rule statements.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[repr(i32)]
+pub enum Register {
+ Verdict = libc::NFT_REG_VERDICT,
+ Reg1 = libc::NFT_REG_1,
+ Reg2 = libc::NFT_REG_2,
+ Reg3 = libc::NFT_REG_3,
+ Reg4 = libc::NFT_REG_4,
+}
+
+impl Register {
+ pub fn to_raw(self) -> u32 {
+ self as u32
+ }
+
+ pub fn from_raw(val: u32) -> Result<Self, DeserializationError> {
+ match val as i32 {
+ libc::NFT_REG_VERDICT => Ok(Self::Verdict),
+ libc::NFT_REG_1 => Ok(Self::Reg1),
+ libc::NFT_REG_2 => Ok(Self::Reg2),
+ libc::NFT_REG_3 => Ok(Self::Reg3),
+ libc::NFT_REG_4 => Ok(Self::Reg4),
+ _ => Err(DeserializationError::InvalidValue),
+ }
+ }
+}
diff --git a/src/expr/reject.rs b/src/expr/reject.rs
new file mode 100644
index 0000000..2ea0cbf
--- /dev/null
+++ b/src/expr/reject.rs
@@ -0,0 +1,96 @@
+use super::{DeserializationError, Expression, Rule};
+use crate::ProtoFamily;
+use crate::sys::{self, libc::{self, c_char}};
+
+/// A reject expression that defines the type of rejection message sent
+/// when discarding a packet.
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+pub enum Reject {
+ /// Return an ICMP unreachable packet
+ Icmp(IcmpCode),
+ /// Reject by sending a TCP RST packet
+ TcpRst,
+}
+
+impl Reject {
+ fn to_raw(&self, family: ProtoFamily) -> u32 {
+ use libc::*;
+ let value = match *self {
+ Self::Icmp(..) => match family {
+ ProtoFamily::Bridge | ProtoFamily::Inet => NFT_REJECT_ICMPX_UNREACH,
+ _ => NFT_REJECT_ICMP_UNREACH,
+ },
+ Self::TcpRst => NFT_REJECT_TCP_RST,
+ };
+ value as u32
+ }
+}
+
+impl Expression for Reject {
+ fn get_raw_name() -> *const libc::c_char {
+ b"reject\0" as *const _ as *const c_char
+ }
+
+ fn from_expr(expr: *const sys::nftnl_expr) -> Result<Self, DeserializationError>
+ where
+ Self: Sized,
+ {
+ unsafe {
+ if sys::nftnl_expr_get_u32(expr, sys::NFTNL_EXPR_REJECT_TYPE as u16)
+ == libc::NFT_REJECT_TCP_RST as u32
+ {
+ Ok(Self::TcpRst)
+ } else {
+ Ok(Self::Icmp(IcmpCode::from_raw(sys::nftnl_expr_get_u8(
+ expr,
+ sys::NFTNL_EXPR_REJECT_CODE as u16,
+ ))?))
+ }
+ }
+ }
+
+ fn to_expr(&self, rule: &Rule) -> *mut sys::nftnl_expr {
+ let family = rule.get_chain().get_table().get_family();
+
+ unsafe {
+ let expr = try_alloc!(sys::nftnl_expr_alloc(Self::get_raw_name()));
+
+ sys::nftnl_expr_set_u32(
+ expr,
+ sys::NFTNL_EXPR_REJECT_TYPE as u16,
+ self.to_raw(family),
+ );
+
+ let reject_code = match *self {
+ Reject::Icmp(code) => code as u8,
+ Reject::TcpRst => 0,
+ };
+
+ sys::nftnl_expr_set_u8(expr, sys::NFTNL_EXPR_REJECT_CODE as u16, reject_code);
+
+ expr
+ }
+ }
+}
+
+/// An ICMP reject code.
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+#[repr(u8)]
+pub enum IcmpCode {
+ NoRoute = libc::NFT_REJECT_ICMPX_NO_ROUTE as u8,
+ PortUnreach = libc::NFT_REJECT_ICMPX_PORT_UNREACH as u8,
+ HostUnreach = libc::NFT_REJECT_ICMPX_HOST_UNREACH as u8,
+ AdminProhibited = libc::NFT_REJECT_ICMPX_ADMIN_PROHIBITED as u8,
+}
+
+impl IcmpCode {
+ fn from_raw(code: u8) -> Result<Self, DeserializationError> {
+ match code as i32 {
+ libc::NFT_REJECT_ICMPX_NO_ROUTE => Ok(Self::NoRoute),
+ libc::NFT_REJECT_ICMPX_PORT_UNREACH => Ok(Self::PortUnreach),
+ libc::NFT_REJECT_ICMPX_HOST_UNREACH => Ok(Self::HostUnreach),
+ libc::NFT_REJECT_ICMPX_ADMIN_PROHIBITED => Ok(Self::AdminProhibited),
+ _ => Err(DeserializationError::InvalidValue),
+ }
+ }
+}
diff --git a/src/expr/verdict.rs b/src/expr/verdict.rs
new file mode 100644
index 0000000..3c4c374
--- /dev/null
+++ b/src/expr/verdict.rs
@@ -0,0 +1,148 @@
+use super::{DeserializationError, Expression, Rule};
+use crate::sys::{self, libc::{self, c_char}};
+use std::ffi::{CStr, CString};
+
+/// A verdict expression. In the background, this is usually an "Immediate" expression in nftnl
+/// terms, but here it is simplified to only represent a verdict.
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub enum Verdict {
+ /// Silently drop the packet.
+ Drop,
+ /// Accept the packet and let it pass.
+ Accept,
+ Queue,
+ Continue,
+ Break,
+ Jump {
+ chain: CString,
+ },
+ Goto {
+ chain: CString,
+ },
+ Return,
+}
+
+impl Verdict {
+ fn chain(&self) -> Option<&CStr> {
+ match *self {
+ Verdict::Jump { ref chain } => Some(chain.as_c_str()),
+ Verdict::Goto { ref chain } => Some(chain.as_c_str()),
+ _ => None,
+ }
+ }
+}
+
+impl Expression for Verdict {
+ fn get_raw_name() -> *const libc::c_char {
+ b"immediate\0" as *const _ as *const c_char
+ }
+
+ fn from_expr(expr: *const sys::nftnl_expr) -> Result<Self, DeserializationError> {
+ unsafe {
+ let mut chain = None;
+ if sys::nftnl_expr_is_set(expr, sys::NFTNL_EXPR_IMM_CHAIN as u16) {
+ let raw_chain = sys::nftnl_expr_get_str(expr, sys::NFTNL_EXPR_IMM_CHAIN as u16);
+
+ if raw_chain.is_null() {
+ return Err(DeserializationError::NullPointer);
+ }
+ chain = Some(CStr::from_ptr(raw_chain).to_owned());
+ }
+
+ let verdict = sys::nftnl_expr_get_u32(expr, sys::NFTNL_EXPR_IMM_VERDICT as u16);
+
+ match verdict as i32 {
+ libc::NF_DROP => Ok(Verdict::Drop),
+ libc::NF_ACCEPT => Ok(Verdict::Accept),
+ libc::NF_QUEUE => Ok(Verdict::Queue),
+ libc::NFT_CONTINUE => Ok(Verdict::Continue),
+ libc::NFT_BREAK => Ok(Verdict::Break),
+ libc::NFT_JUMP => {
+ if let Some(chain) = chain {
+ Ok(Verdict::Jump { chain })
+ } else {
+ Err(DeserializationError::InvalidValue)
+ }
+ }
+ libc::NFT_GOTO => {
+ if let Some(chain) = chain {
+ Ok(Verdict::Goto { chain })
+ } else {
+ Err(DeserializationError::InvalidValue)
+ }
+ }
+ libc::NFT_RETURN => Ok(Verdict::Return),
+ _ => Err(DeserializationError::InvalidValue),
+ }
+ }
+ }
+
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ let immediate_const = match *self {
+ Verdict::Drop => libc::NF_DROP,
+ Verdict::Accept => libc::NF_ACCEPT,
+ Verdict::Queue => libc::NF_QUEUE,
+ Verdict::Continue => libc::NFT_CONTINUE,
+ Verdict::Break => libc::NFT_BREAK,
+ Verdict::Jump { .. } => libc::NFT_JUMP,
+ Verdict::Goto { .. } => libc::NFT_GOTO,
+ Verdict::Return => libc::NFT_RETURN,
+ };
+ unsafe {
+ let expr = try_alloc!(sys::nftnl_expr_alloc(
+ b"immediate\0" as *const _ as *const c_char
+ ));
+
+ sys::nftnl_expr_set_u32(
+ expr,
+ sys::NFTNL_EXPR_IMM_DREG as u16,
+ libc::NFT_REG_VERDICT as u32,
+ );
+
+ if let Some(chain) = self.chain() {
+ sys::nftnl_expr_set_str(expr, sys::NFTNL_EXPR_IMM_CHAIN as u16, chain.as_ptr());
+ }
+ sys::nftnl_expr_set_u32(
+ expr,
+ sys::NFTNL_EXPR_IMM_VERDICT as u16,
+ immediate_const as u32,
+ );
+
+ expr
+ }
+ }
+}
+
+#[macro_export]
+macro_rules! nft_expr_verdict {
+ (drop) => {
+ $crate::expr::Verdict::Drop
+ };
+ (accept) => {
+ $crate::expr::Verdict::Accept
+ };
+ (reject icmp $code:expr) => {
+ $crate::expr::Verdict::Reject(RejectionType::Icmp($code))
+ };
+ (reject tcp-rst) => {
+ $crate::expr::Verdict::Reject(RejectionType::TcpRst)
+ };
+ (queue) => {
+ $crate::expr::Verdict::Queue
+ };
+ (continue) => {
+ $crate::expr::Verdict::Continue
+ };
+ (break) => {
+ $crate::expr::Verdict::Break
+ };
+ (jump $chain:expr) => {
+ $crate::expr::Verdict::Jump { chain: $chain }
+ };
+ (goto $chain:expr) => {
+ $crate::expr::Verdict::Goto { chain: $chain }
+ };
+ (return) => {
+ $crate::expr::Verdict::Return
+ };
+}
diff --git a/src/expr/wrapper.rs b/src/expr/wrapper.rs
new file mode 100644
index 0000000..1bcc520
--- /dev/null
+++ b/src/expr/wrapper.rs
@@ -0,0 +1,60 @@
+use std::ffi::CStr;
+use std::ffi::CString;
+use std::fmt::Debug;
+use std::rc::Rc;
+
+use super::{DeserializationError, Expression};
+use crate::{sys, Rule};
+
+pub struct ExpressionWrapper {
+ pub(crate) expr: *const sys::nftnl_expr,
+ // we also need the rule here to ensure that the rule lives as long as the `expr` pointer
+ #[allow(dead_code)]
+ pub(crate) rule: Rc<Rule>,
+}
+
+impl Debug for ExpressionWrapper {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{:?}", self.get_str())
+ }
+}
+
+impl ExpressionWrapper {
+ /// Retrieves a textual description of the expression.
+ pub fn get_str(&self) -> CString {
+ let mut descr_buf = vec![0i8; 4096];
+ unsafe {
+ sys::nftnl_expr_snprintf(
+ descr_buf.as_mut_ptr(),
+ (descr_buf.len() - 1) as u64,
+ self.expr,
+ sys::NFTNL_OUTPUT_DEFAULT,
+ 0,
+ );
+ CStr::from_ptr(descr_buf.as_ptr()).to_owned()
+ }
+ }
+
+ /// Retrieves the type of expression ("log", "counter", ...).
+ pub fn get_kind(&self) -> Option<&CStr> {
+ unsafe {
+ let ptr = sys::nftnl_expr_get_str(self.expr, sys::NFTNL_EXPR_NAME as u16);
+ if !ptr.is_null() {
+ Some(CStr::from_ptr(ptr))
+ } else {
+ None
+ }
+ }
+ }
+
+ /// Attempt to decode the expression as the type T.
+ pub fn decode_expr<T: Expression>(&self) -> Result<T, DeserializationError> {
+ if let Some(kind) = self.get_kind() {
+ let raw_name = unsafe { CStr::from_ptr(T::get_raw_name()) };
+ if kind == raw_name {
+ return T::from_expr(self.expr);
+ }
+ }
+ Err(DeserializationError::InvalidExpressionKind)
+ }
+}