aboutsummaryrefslogtreecommitdiff
path: root/rustables/src
diff options
context:
space:
mode:
Diffstat (limited to 'rustables/src')
-rw-r--r--rustables/src/batch.rs208
-rw-r--r--rustables/src/chain.rs291
-rw-r--r--rustables/src/expr/bitwise.rs68
-rw-r--r--rustables/src/expr/cmp.rs230
-rw-r--r--rustables/src/expr/counter.rs13
-rw-r--r--rustables/src/expr/ct.rs65
-rw-r--r--rustables/src/expr/immediate.rs54
-rw-r--r--rustables/src/expr/log.rs12
-rw-r--r--rustables/src/expr/lookup.rs56
-rw-r--r--rustables/src/expr/masquerade.rs12
-rw-r--r--rustables/src/expr/meta.rs134
-rw-r--r--rustables/src/expr/mod.rs113
-rw-r--r--rustables/src/expr/nat.rs48
-rw-r--r--rustables/src/expr/payload.rs368
-rw-r--r--rustables/src/expr/verdict.rs175
-rw-r--r--rustables/src/lib.rs183
-rw-r--r--rustables/src/query.rs132
-rw-r--r--rustables/src/rule.rs227
-rw-r--r--rustables/src/set.rs266
-rw-r--r--rustables/src/table.rs169
20 files changed, 2824 insertions, 0 deletions
diff --git a/rustables/src/batch.rs b/rustables/src/batch.rs
new file mode 100644
index 0000000..0f43402
--- /dev/null
+++ b/rustables/src/batch.rs
@@ -0,0 +1,208 @@
+use crate::{MsgType, NlMsg};
+use rustables_sys::{self as sys, libc};
+use std::ffi::c_void;
+use std::os::raw::c_char;
+use std::ptr;
+use thiserror::Error;
+use tracing::trace;
+
+/// Error while communicating with netlink
+#[derive(Error, Debug)]
+#[error("Error while communicating with netlink")]
+pub struct NetlinkError(());
+
+/// Check if the kernel supports batched netlink messages to netfilter.
+pub fn batch_is_supported() -> std::result::Result<bool, NetlinkError> {
+ match unsafe { sys::nftnl_batch_is_supported() } {
+ 1 => Ok(true),
+ 0 => Ok(false),
+ _ => Err(NetlinkError(())),
+ }
+}
+
+/// A batch of netfilter messages to be performed in one atomic operation. Corresponds to
+/// `nftnl_batch` in libnftnl.
+pub struct Batch {
+ batch: *mut sys::nftnl_batch,
+ seq: u32,
+ is_empty: bool,
+}
+
+// Safety: It should be safe to pass this around and *read* from it
+// from multiple threads
+unsafe impl Send for Batch {}
+unsafe impl Sync for Batch {}
+
+impl Batch {
+ /// Creates a new nftnl batch with the [default page size].
+ ///
+ /// [default page size]: fn.default_batch_page_size.html
+ pub fn new() -> Self {
+ Self::with_page_size(default_batch_page_size())
+ }
+
+ pub unsafe fn from_raw(batch: *mut sys::nftnl_batch, seq: u32) -> Self {
+ Batch {
+ batch,
+ seq,
+ // we assume this batch is not empty by default
+ is_empty: false,
+ }
+ }
+
+ /// Creates a new nftnl batch with the given batch size.
+ pub fn with_page_size(batch_page_size: u32) -> Self {
+ let batch = try_alloc!(unsafe {
+ sys::nftnl_batch_alloc(batch_page_size, crate::nft_nlmsg_maxsize())
+ });
+ let mut this = Batch {
+ batch,
+ seq: 0,
+ is_empty: true,
+ };
+ this.write_begin_msg();
+ this
+ }
+
+ /// Adds the given message to this batch.
+ pub fn add<T: NlMsg>(&mut self, msg: &T, msg_type: MsgType) {
+ trace!("Writing NlMsg with seq {} to batch", self.seq);
+ unsafe { msg.write(self.current(), self.seq, msg_type) };
+ self.is_empty = false;
+ self.next()
+ }
+
+ /// Adds all the messages in the given iterator to this batch. If any message fails to be added
+ /// the error for that failure is returned and all messages up until that message stays added
+ /// to the batch.
+ pub fn add_iter<T, I>(&mut self, msg_iter: I, msg_type: MsgType)
+ where
+ T: NlMsg,
+ I: Iterator<Item = T>,
+ {
+ for msg in msg_iter {
+ self.add(&msg, msg_type);
+ }
+ }
+
+ /// Adds the final end message to the batch and returns a [`FinalizedBatch`] that can be used
+ /// to send the messages to netfilter.
+ ///
+ /// Return None if there is no object in the batch (this could block forever).
+ ///
+ /// [`FinalizedBatch`]: struct.FinalizedBatch.html
+ pub fn finalize(mut self) -> Option<FinalizedBatch> {
+ self.write_end_msg();
+ if self.is_empty {
+ return None;
+ }
+ Some(FinalizedBatch { batch: self })
+ }
+
+ fn current(&self) -> *mut c_void {
+ unsafe { sys::nftnl_batch_buffer(self.batch) }
+ }
+
+ fn next(&mut self) {
+ if unsafe { sys::nftnl_batch_update(self.batch) } < 0 {
+ // See try_alloc definition.
+ std::process::abort();
+ }
+ self.seq += 1;
+ }
+
+ fn write_begin_msg(&mut self) {
+ unsafe { sys::nftnl_batch_begin(self.current() as *mut c_char, self.seq) };
+ self.next();
+ }
+
+ fn write_end_msg(&mut self) {
+ unsafe { sys::nftnl_batch_end(self.current() as *mut c_char, self.seq) };
+ self.next();
+ }
+
+ /// Returns the raw handle.
+ pub fn as_ptr(&self) -> *const sys::nftnl_batch {
+ self.batch as *const sys::nftnl_batch
+ }
+
+ /// Returns a mutable version of the raw handle.
+ pub fn as_mut_ptr(&mut self) -> *mut sys::nftnl_batch {
+ self.batch
+ }
+}
+
+impl Drop for Batch {
+ fn drop(&mut self) {
+ unsafe { sys::nftnl_batch_free(self.batch) };
+ }
+}
+
+/// A wrapper over [`Batch`], guaranteed to start with a proper batch begin and end with a proper
+/// batch end message. Created from [`Batch::finalize`].
+///
+/// Can be turned into an iterator of the byte buffers to send to netlink to execute this batch.
+///
+/// [`Batch`]: struct.Batch.html
+/// [`Batch::finalize`]: struct.Batch.html#method.finalize
+pub struct FinalizedBatch {
+ batch: Batch,
+}
+
+impl FinalizedBatch {
+ /// Returns the iterator over byte buffers to send to netlink.
+ pub fn iter(&mut self) -> Iter<'_> {
+ let num_pages = unsafe { sys::nftnl_batch_iovec_len(self.batch.as_mut_ptr()) as usize };
+ let mut iovecs = vec![
+ libc::iovec {
+ iov_base: ptr::null_mut(),
+ iov_len: 0,
+ };
+ num_pages
+ ];
+ let iovecs_ptr = iovecs.as_mut_ptr() as *mut [u8; 0];
+ unsafe {
+ sys::nftnl_batch_iovec(self.batch.as_mut_ptr(), iovecs_ptr, num_pages as u32);
+ }
+ Iter {
+ iovecs: iovecs.into_iter(),
+ _marker: ::std::marker::PhantomData,
+ }
+ }
+}
+
+impl<'a> IntoIterator for &'a mut FinalizedBatch {
+ type Item = &'a [u8];
+ type IntoIter = Iter<'a>;
+
+ fn into_iter(self) -> Iter<'a> {
+ self.iter()
+ }
+}
+
+pub struct Iter<'a> {
+ iovecs: ::std::vec::IntoIter<libc::iovec>,
+ _marker: ::std::marker::PhantomData<&'a ()>,
+}
+
+// Safety: It should be safe to pass this around and *read* from it
+// from multiple threads.
+unsafe impl<'a> Send for Iter<'a> {}
+unsafe impl<'a> Sync for Iter<'a> {}
+
+impl<'a> Iterator for Iter<'a> {
+ type Item = &'a [u8];
+
+ fn next(&mut self) -> Option<&'a [u8]> {
+ self.iovecs.next().map(|iovec| unsafe {
+ ::std::slice::from_raw_parts(iovec.iov_base as *const u8, iovec.iov_len)
+ })
+ }
+}
+
+/// selected batch page is 256 Kbytes long to load ruleset of
+/// half a million rules without hitting -EMSGSIZE due to large
+/// iovec.
+pub fn default_batch_page_size() -> u32 {
+ unsafe { libc::sysconf(libc::_SC_PAGESIZE) as u32 * 32 }
+}
diff --git a/rustables/src/chain.rs b/rustables/src/chain.rs
new file mode 100644
index 0000000..25c3604
--- /dev/null
+++ b/rustables/src/chain.rs
@@ -0,0 +1,291 @@
+use crate::{MsgType, Table};
+use rustables_sys::{self as sys, libc};
+use std::sync::Arc;
+use std::{
+ convert::TryFrom,
+ ffi::{c_void, CStr, CString},
+ fmt,
+ os::raw::c_char,
+};
+use tracing::error;
+
+pub type Priority = i32;
+
+/// The netfilter event hooks a chain can register for.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[repr(u16)]
+pub enum Hook {
+ /// Hook into the pre-routing stage of netfilter. Corresponds to `NF_INET_PRE_ROUTING`.
+ PreRouting = libc::NF_INET_PRE_ROUTING as u16,
+ /// Hook into the input stage of netfilter. Corresponds to `NF_INET_LOCAL_IN`.
+ In = libc::NF_INET_LOCAL_IN as u16,
+ /// Hook into the forward stage of netfilter. Corresponds to `NF_INET_FORWARD`.
+ Forward = libc::NF_INET_FORWARD as u16,
+ /// Hook into the output stage of netfilter. Corresponds to `NF_INET_LOCAL_OUT`.
+ Out = libc::NF_INET_LOCAL_OUT as u16,
+ /// Hook into the post-routing stage of netfilter. Corresponds to `NF_INET_POST_ROUTING`.
+ PostRouting = libc::NF_INET_POST_ROUTING as u16,
+}
+
+/// A chain policy. Decides what to do with a packet that was processed by the chain but did not
+/// match any rules.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[repr(u32)]
+pub enum Policy {
+ /// Accept the packet.
+ Accept = libc::NF_ACCEPT as u32,
+ /// Drop the packet.
+ Drop = libc::NF_DROP as u32,
+}
+
+/// Base chain type.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum ChainType {
+ /// Used to filter packets.
+ /// Supported protocols: ip, ip6, inet, arp, and bridge tables.
+ Filter,
+ /// Used to reroute packets if IP headers or packet marks are modified.
+ /// Supported protocols: ip, and ip6 tables.
+ Route,
+ /// Used to perform NAT.
+ /// Supported protocols: ip, and ip6 tables.
+ Nat,
+}
+
+impl ChainType {
+ fn as_c_str(&self) -> &'static [u8] {
+ match *self {
+ ChainType::Filter => b"filter\0",
+ ChainType::Route => b"route\0",
+ ChainType::Nat => b"nat\0",
+ }
+ }
+}
+
+/// Abstraction of a `nftnl_chain`. Chains reside inside [`Table`]s and they hold [`Rule`]s.
+///
+/// There are two types of chains, "base chain" and "regular chain". See [`set_hook`] for more
+/// details.
+///
+/// [`Table`]: struct.Table.html
+/// [`Rule`]: struct.Rule.html
+/// [`set_hook`]: #method.set_hook
+pub struct Chain {
+ chain: *mut sys::nftnl_chain,
+ table: Arc<Table>,
+}
+
+// Safety: It should be safe to pass this around and *read* from it
+// from multiple threads
+unsafe impl Send for Chain {}
+unsafe impl Sync for Chain {}
+
+impl Chain {
+ /// Creates a new chain instance inside the given [`Table`] and with the given name.
+ ///
+ /// [`Table`]: struct.Table.html
+ pub fn new<T: AsRef<CStr>>(name: &T, table: Arc<Table>) -> Chain {
+ unsafe {
+ let chain = try_alloc!(sys::nftnl_chain_alloc());
+ sys::nftnl_chain_set_u32(
+ chain,
+ sys::NFTNL_CHAIN_FAMILY as u16,
+ table.get_family() as u32,
+ );
+ sys::nftnl_chain_set_str(
+ chain,
+ sys::NFTNL_CHAIN_TABLE as u16,
+ table.get_name().as_ptr(),
+ );
+ sys::nftnl_chain_set_str(chain, sys::NFTNL_CHAIN_NAME as u16, name.as_ref().as_ptr());
+ Chain { chain, table }
+ }
+ }
+
+ pub unsafe fn from_raw(chain: *mut sys::nftnl_chain, table: Arc<Table>) -> Self {
+ Chain { chain, table }
+ }
+
+ /// Sets the hook and priority for this chain. Without calling this method the chain well
+ /// become a "regular chain" without any hook and will thus not receive any traffic unless
+ /// some rule forward packets to it via goto or jump verdicts.
+ ///
+ /// By calling `set_hook` with a hook the chain that is created will be registered with that
+ /// hook and is thus a "base chain". A "base chain" is an entry point for packets from the
+ /// networking stack.
+ pub fn set_hook(&mut self, hook: Hook, priority: Priority) {
+ unsafe {
+ sys::nftnl_chain_set_u32(self.chain, sys::NFTNL_CHAIN_HOOKNUM as u16, hook as u32);
+ sys::nftnl_chain_set_s32(self.chain, sys::NFTNL_CHAIN_PRIO as u16, priority);
+ }
+ }
+
+ /// Set the type of a base chain. This only applies if the chain has been registered
+ /// with a hook by calling `set_hook`.
+ pub fn set_type(&mut self, chain_type: ChainType) {
+ unsafe {
+ sys::nftnl_chain_set_str(
+ self.chain,
+ sys::NFTNL_CHAIN_TYPE as u16,
+ chain_type.as_c_str().as_ptr() as *const c_char,
+ );
+ }
+ }
+
+ /// Sets the default policy for this chain. That means what action netfilter will apply to
+ /// packets processed by this chain, but that did not match any rules in it.
+ pub fn set_policy(&mut self, policy: Policy) {
+ unsafe {
+ sys::nftnl_chain_set_u32(self.chain, sys::NFTNL_CHAIN_POLICY as u16, policy as u32);
+ }
+ }
+
+ /// Returns the userdata of this chain.
+ pub fn get_userdata(&self) -> Option<&CStr> {
+ unsafe {
+ let ptr = sys::nftnl_chain_get_str(self.chain, sys::NFTNL_CHAIN_USERDATA as u16);
+ if ptr == std::ptr::null() {
+ return None;
+ }
+ Some(CStr::from_ptr(ptr))
+ }
+ }
+
+ /// Update the userdata of this chain.
+ pub fn set_userdata(&self, data: &CStr) {
+ unsafe {
+ sys::nftnl_chain_set_str(self.chain, sys::NFTNL_CHAIN_USERDATA as u16, data.as_ptr());
+ }
+ }
+
+ /// Returns the name of this chain.
+ pub fn get_name(&self) -> &CStr {
+ unsafe {
+ let ptr = sys::nftnl_chain_get_str(self.chain, sys::NFTNL_CHAIN_NAME as u16);
+ CStr::from_ptr(ptr)
+ }
+ }
+
+ /// Returns a textual description of the chain.
+ pub fn get_str(&self) -> CString {
+ let mut descr_buf = vec![0i8; 4096];
+ unsafe {
+ sys::nftnl_chain_snprintf(
+ descr_buf.as_mut_ptr(),
+ (descr_buf.len() - 1) as u64,
+ self.chain,
+ sys::NFTNL_OUTPUT_DEFAULT,
+ 0,
+ );
+ CStr::from_ptr(descr_buf.as_ptr()).to_owned()
+ }
+ }
+
+ /// Returns a reference to the [`Table`] this chain belongs to
+ ///
+ /// [`Table`]: struct.Table.html
+ pub fn get_table(&self) -> Arc<Table> {
+ self.table.clone()
+ }
+
+ /// Returns the raw handle.
+ pub fn as_ptr(&self) -> *const sys::nftnl_chain {
+ self.chain as *const sys::nftnl_chain
+ }
+
+ /// Returns a mutable version of the raw handle.
+ pub fn as_mut_ptr(&mut self) -> *mut sys::nftnl_chain {
+ self.chain
+ }
+}
+
+impl fmt::Debug for Chain {
+ /// Return a string representation of the chain.
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(fmt, "{:?}", self.get_str())
+ }
+}
+
+impl PartialEq for Chain {
+ fn eq(&self, other: &Self) -> bool {
+ self.get_table() == other.get_table() && self.get_name() == other.get_name()
+ }
+}
+
+unsafe impl crate::NlMsg for Chain {
+ unsafe fn write(&self, buf: *mut c_void, seq: u32, msg_type: MsgType) {
+ let raw_msg_type = match msg_type {
+ MsgType::Add => libc::NFT_MSG_NEWCHAIN,
+ MsgType::Del => libc::NFT_MSG_DELCHAIN,
+ };
+ let flags: u16 = match msg_type {
+ MsgType::Add => (libc::NLM_F_ACK | libc::NLM_F_CREATE) as u16,
+ MsgType::Del => libc::NLM_F_ACK as u16,
+ } | libc::NLM_F_ACK as u16;
+ let header = sys::nftnl_nlmsg_build_hdr(
+ buf as *mut c_char,
+ raw_msg_type as u16,
+ self.table.get_family() as u16,
+ flags,
+ seq,
+ );
+ sys::nftnl_chain_nlmsg_build_payload(header, self.chain);
+ }
+}
+
+impl Drop for Chain {
+ fn drop(&mut self) {
+ unsafe { sys::nftnl_chain_free(self.chain) };
+ }
+}
+
+#[cfg(feature = "query")]
+pub fn get_chains_cb<'a>(
+ header: &libc::nlmsghdr,
+ (table, chains): &mut (&Arc<Table>, &mut Vec<Chain>),
+) -> libc::c_int {
+ unsafe {
+ let chain = sys::nftnl_chain_alloc();
+ if chain == std::ptr::null_mut() {
+ return mnl::mnl_sys::MNL_CB_ERROR;
+ }
+ let err = sys::nftnl_chain_nlmsg_parse(header, chain);
+ if err < 0 {
+ error!("Failed to parse nelink chain message - {}", err);
+ sys::nftnl_chain_free(chain);
+ return err;
+ }
+
+ let table_name = CStr::from_ptr(sys::nftnl_chain_get_str(
+ chain,
+ sys::NFTNL_CHAIN_TABLE as u16,
+ ));
+ let family = sys::nftnl_chain_get_u32(chain, sys::NFTNL_CHAIN_FAMILY as u16);
+ let family = match crate::ProtoFamily::try_from(family as i32) {
+ Ok(family) => family,
+ Err(crate::InvalidProtocolFamily) => {
+ error!("The netlink table didn't have a valid protocol family !?");
+ sys::nftnl_chain_free(chain);
+ return mnl::mnl_sys::MNL_CB_ERROR;
+ }
+ };
+
+ if table_name != table.get_name() {
+ sys::nftnl_chain_free(chain);
+ return mnl::mnl_sys::MNL_CB_OK;
+ }
+
+ if family != crate::ProtoFamily::Unspec && family != table.get_family() {
+ sys::nftnl_chain_free(chain);
+ return mnl::mnl_sys::MNL_CB_OK;
+ }
+
+ chains.push(Chain::from_raw(chain, table.clone()));
+ }
+ mnl::mnl_sys::MNL_CB_OK
+}
+
+#[cfg(feature = "query")]
+pub fn list_chains_for_table(table: Arc<Table>) -> Result<Vec<Chain>, crate::query::Error> {
+ crate::query::list_objects_with_data(libc::NFT_MSG_GETCHAIN as u16, get_chains_cb, &table, None)
+}
diff --git a/rustables/src/expr/bitwise.rs b/rustables/src/expr/bitwise.rs
new file mode 100644
index 0000000..1eb81ab
--- /dev/null
+++ b/rustables/src/expr/bitwise.rs
@@ -0,0 +1,68 @@
+use super::{Expression, Rule};
+use crate::expr::cmp::ToSlice;
+use rustables_sys::{self as sys, 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 to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ unsafe {
+ let expr = try_alloc!(sys::nftnl_expr_alloc(
+ b"bitwise\0" as *const _ as *const c_char
+ ));
+
+ 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/rustables/src/expr/cmp.rs b/rustables/src/expr/cmp.rs
new file mode 100644
index 0000000..f22a3ff
--- /dev/null
+++ b/rustables/src/expr/cmp.rs
@@ -0,0 +1,230 @@
+use super::{Expression, Rule};
+use rustables_sys::{self as sys, libc};
+use std::{
+ borrow::Cow,
+ ffi::{c_void, CString},
+ net::{IpAddr, Ipv4Addr, Ipv6Addr},
+ os::raw::c_char,
+ slice,
+};
+use tracing::trace;
+
+/// 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) -> *mut sys::nftnl_expr {
+ unsafe {
+ let expr = try_alloc!(sys::nftnl_expr_alloc(b"cmp\0" as *const _ as *const c_char));
+
+ 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_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<'a> ToSlice for [u8; 0] {
+ fn to_slice(&self) -> Cow<'_, [u8]> {
+ Cow::Borrowed(&[])
+ }
+}
+
+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 { 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())
+ }
+}
+
+/// 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/rustables/src/expr/counter.rs b/rustables/src/expr/counter.rs
new file mode 100644
index 0000000..c2a0b5d
--- /dev/null
+++ b/rustables/src/expr/counter.rs
@@ -0,0 +1,13 @@
+use super::{Expression, Rule};
+use rustables_sys as 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.
+pub struct Counter;
+
+impl Expression for Counter {
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ try_alloc!(unsafe { sys::nftnl_expr_alloc(b"counter\0" as *const _ as *const c_char) })
+ }
+}
diff --git a/rustables/src/expr/ct.rs b/rustables/src/expr/ct.rs
new file mode 100644
index 0000000..c0349ab
--- /dev/null
+++ b/rustables/src/expr/ct.rs
@@ -0,0 +1,65 @@
+use super::{Expression, Rule};
+use rustables_sys::{self as sys, 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 to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ unsafe {
+ let expr = try_alloc!(sys::nftnl_expr_alloc(b"ct\0" as *const _ as *const c_char));
+
+ 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/rustables/src/expr/immediate.rs b/rustables/src/expr/immediate.rs
new file mode 100644
index 0000000..e5ccc2a
--- /dev/null
+++ b/rustables/src/expr/immediate.rs
@@ -0,0 +1,54 @@
+use super::{Expression, Register, Rule};
+use rustables_sys as sys;
+use std::ffi::c_void;
+use std::mem::size_of_val;
+use std::os::raw::c_char;
+
+/// An immediate expression. Used to set immediate data.
+/// Verdicts are handled separately by [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> Expression for Immediate<T> {
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ 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,
+ self.register.to_raw(),
+ );
+
+ sys::nftnl_expr_set(
+ expr,
+ sys::NFTNL_EXPR_IMM_DATA as u16,
+ &self.data as *const _ as *const c_void,
+ size_of_val(&self.data) as u32,
+ );
+
+ expr
+ }
+ }
+}
+
+#[macro_export]
+macro_rules! nft_expr_immediate {
+ (data $value:expr) => {
+ $crate::expr::Immediate {
+ data: $value,
+ register: $crate::expr::Register::Reg1,
+ }
+ };
+}
diff --git a/rustables/src/expr/log.rs b/rustables/src/expr/log.rs
new file mode 100644
index 0000000..d6e0089
--- /dev/null
+++ b/rustables/src/expr/log.rs
@@ -0,0 +1,12 @@
+use super::{Expression, Rule};
+use rustables_sys as sys;
+use std::os::raw::c_char;
+
+/// A Log expression will log all packets that match the rule.
+pub struct Log;
+
+impl Expression for Log {
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ try_alloc!(unsafe { sys::nftnl_expr_alloc(b"log\0" as *const _ as *const c_char) })
+ }
+}
diff --git a/rustables/src/expr/lookup.rs b/rustables/src/expr/lookup.rs
new file mode 100644
index 0000000..ac22440
--- /dev/null
+++ b/rustables/src/expr/lookup.rs
@@ -0,0 +1,56 @@
+use super::{Expression, Rule};
+use crate::set::Set;
+use rustables_sys::{self as sys, libc};
+use std::ffi::CString;
+use std::os::raw::c_char;
+
+pub struct Lookup {
+ set_name: CString,
+ set_id: u32,
+}
+
+impl Lookup {
+ pub fn new<K>(set: &Set<'_, K>) -> Self {
+ Lookup {
+ set_name: set.get_name().to_owned(),
+ set_id: set.get_id(),
+ }
+ }
+}
+
+impl Expression for Lookup {
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ unsafe {
+ let expr = try_alloc!(sys::nftnl_expr_alloc(
+ b"lookup\0" as *const _ as *const c_char
+ ));
+
+ 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/rustables/src/expr/masquerade.rs b/rustables/src/expr/masquerade.rs
new file mode 100644
index 0000000..66e9e0e
--- /dev/null
+++ b/rustables/src/expr/masquerade.rs
@@ -0,0 +1,12 @@
+use super::{Expression, Rule};
+use rustables_sys as sys;
+use std::os::raw::c_char;
+
+/// Sets the source IP to that of the output interface.
+pub struct Masquerade;
+
+impl Expression for Masquerade {
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ try_alloc!(unsafe { sys::nftnl_expr_alloc(b"masq\0" as *const _ as *const c_char) })
+ }
+}
diff --git a/rustables/src/expr/meta.rs b/rustables/src/expr/meta.rs
new file mode 100644
index 0000000..a91cb27
--- /dev/null
+++ b/rustables/src/expr/meta.rs
@@ -0,0 +1,134 @@
+use super::{Expression, Rule};
+use rustables_sys::{self as sys, libc};
+use std::os::raw::c_char;
+
+/// A meta expression refers to meta data associated with a packet.
+#[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,
+ }
+ }
+}
+
+impl Expression for Meta {
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ unsafe {
+ let expr = try_alloc!(sys::nftnl_expr_alloc(
+ b"meta\0" as *const _ as *const c_char
+ ));
+
+ 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/rustables/src/expr/mod.rs b/rustables/src/expr/mod.rs
new file mode 100644
index 0000000..1364904
--- /dev/null
+++ b/rustables/src/expr/mod.rs
@@ -0,0 +1,113 @@
+//! 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 super::rule::Rule;
+use rustables_sys::{self as sys, libc};
+
+/// Trait for every safe wrapper of an nftables expression.
+pub trait Expression {
+ /// 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 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 {
+ 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
+ }
+}
+
+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 verdict;
+pub use self::verdict::*;
+
+#[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
+ };
+ (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) => {
+ 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/rustables/src/expr/nat.rs b/rustables/src/expr/nat.rs
new file mode 100644
index 0000000..d60e5ea
--- /dev/null
+++ b/rustables/src/expr/nat.rs
@@ -0,0 +1,48 @@
+use super::{Expression, Register, Rule};
+use crate::ProtoFamily;
+use rustables_sys::{self as sys, libc};
+use std::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,
+}
+
+/// A source or destination NAT statement. Modifies the source or destination address
+/// (and possibly port) of packets.
+pub struct Nat {
+ pub nat_type: NatType,
+ pub family: ProtoFamily,
+ pub ip_register: Register,
+ pub port_register: Option<Register>,
+}
+
+impl Expression for Nat {
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ let expr =
+ try_alloc!(unsafe { sys::nftnl_expr_alloc(b"nat\0" as *const _ as *const c_char) });
+
+ 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/rustables/src/expr/payload.rs b/rustables/src/expr/payload.rs
new file mode 100644
index 0000000..2da4e1f
--- /dev/null
+++ b/rustables/src/expr/payload.rs
@@ -0,0 +1,368 @@
+use super::{Expression, Rule};
+use rustables_sys::{self as sys, libc};
+use std::os::raw::c_char;
+
+trait HeaderField {
+ fn offset(&self) -> u32;
+ fn len(&self) -> u32;
+}
+
+/// Payload expressions refer to data from the packet's payload.
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub enum Payload {
+ LinkLayer(LLHeaderField),
+ Network(NetworkHeaderField),
+ Transport(TransportHeaderField),
+}
+
+impl Payload {
+ fn base(self) -> u32 {
+ match self {
+ Payload::LinkLayer(_) => libc::NFT_PAYLOAD_LL_HEADER as u32,
+ Payload::Network(_) => libc::NFT_PAYLOAD_NETWORK_HEADER as u32,
+ Payload::Transport(_) => libc::NFT_PAYLOAD_TRANSPORT_HEADER as u32,
+ }
+ }
+}
+
+impl HeaderField for Payload {
+ fn offset(&self) -> u32 {
+ use self::Payload::*;
+ match *self {
+ LinkLayer(ref f) => f.offset(),
+ Network(ref f) => f.offset(),
+ Transport(ref f) => f.offset(),
+ }
+ }
+
+ fn len(&self) -> u32 {
+ use self::Payload::*;
+ match *self {
+ LinkLayer(ref f) => f.len(),
+ Network(ref f) => f.len(),
+ Transport(ref f) => f.len(),
+ }
+ }
+}
+
+impl Expression for Payload {
+ fn to_expr(&self, _rule: &Rule) -> *mut sys::nftnl_expr {
+ unsafe {
+ let expr = try_alloc!(sys::nftnl_expr_alloc(
+ b"payload\0" as *const _ as *const c_char
+ ));
+
+ 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(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,
+ }
+ }
+}
+
+#[derive(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(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,
+ }
+ }
+}
+
+#[derive(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,
+ }
+ }
+}
+
+#[derive(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(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,
+ }
+ }
+}
+
+#[derive(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,
+ }
+ }
+}
+
+#[derive(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,
+ }
+ }
+}
+
+#[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/rustables/src/expr/verdict.rs b/rustables/src/expr/verdict.rs
new file mode 100644
index 0000000..dc006bb
--- /dev/null
+++ b/rustables/src/expr/verdict.rs
@@ -0,0 +1,175 @@
+use super::{Expression, Rule};
+use crate::ProtoFamily;
+use rustables_sys::{
+ self as sys,
+ 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,
+ /// Reject the packet and return a message.
+ Reject(RejectionType),
+ Queue,
+ Continue,
+ Break,
+ Jump {
+ chain: CString,
+ },
+ Goto {
+ chain: CString,
+ },
+ Return,
+}
+
+/// The type of rejection message sent by the Reject verdict.
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
+pub enum RejectionType {
+ /// Return an ICMP unreachable packet
+ Icmp(IcmpCode),
+ /// Reject by sending a TCP RST packet
+ TcpRst,
+}
+
+impl RejectionType {
+ fn to_raw(&self, family: ProtoFamily) -> u32 {
+ use libc::*;
+ let value = match *self {
+ RejectionType::Icmp(..) => match family {
+ ProtoFamily::Bridge | ProtoFamily::Inet => NFT_REJECT_ICMPX_UNREACH,
+ _ => NFT_REJECT_ICMP_UNREACH,
+ },
+ RejectionType::TcpRst => NFT_REJECT_TCP_RST,
+ };
+ value as u32
+ }
+}
+
+/// 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 Verdict {
+ unsafe fn to_immediate_expr(&self, immediate_const: i32) -> *mut sys::nftnl_expr {
+ 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
+ }
+
+ unsafe fn to_reject_expr(
+ &self,
+ reject_type: RejectionType,
+ family: ProtoFamily,
+ ) -> *mut sys::nftnl_expr {
+ let expr = try_alloc!(sys::nftnl_expr_alloc(
+ b"reject\0" as *const _ as *const c_char
+ ));
+
+ sys::nftnl_expr_set_u32(
+ expr,
+ sys::NFTNL_EXPR_REJECT_TYPE as u16,
+ reject_type.to_raw(family),
+ );
+
+ let reject_code = match reject_type {
+ RejectionType::Icmp(code) => code as u8,
+ RejectionType::TcpRst => 0,
+ };
+
+ sys::nftnl_expr_set_u8(expr, sys::NFTNL_EXPR_REJECT_CODE as u16, reject_code);
+
+ expr
+ }
+
+ 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 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,
+ Verdict::Reject(reject_type) => {
+ return unsafe {
+ self.to_reject_expr(reject_type, rule.get_chain().get_table().get_family())
+ }
+ }
+ };
+ unsafe { self.to_immediate_expr(immediate_const) }
+ }
+}
+
+#[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/rustables/src/lib.rs b/rustables/src/lib.rs
new file mode 100644
index 0000000..e5b5375
--- /dev/null
+++ b/rustables/src/lib.rs
@@ -0,0 +1,183 @@
+// Copyryght (c) 2021 GPL lafleur@boum.org and Simon Thoby
+//
+// This file is free software: you may copy, redistribute and/or modify it
+// under the terms of the GNU General Public License as published by the
+// Free Software Foundation, either version 3 of the License, or (at your
+// option) any later version.
+//
+// This file is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see the LICENSE file.
+//
+// This file incorporates work covered by the following copyright and
+// permission notice:
+//
+// Copyright 2018 Amagicom AB.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Safe abstraction for [`libnftnl`]. Provides low-level userspace access to the in-kernel
+//! nf_tables subsystem. See [`rustables-sys`] for the low level FFI bindings to the C library.
+//!
+//! Can be used to create and remove tables, chains, sets and rules from the nftables firewall,
+//! the successor to iptables.
+//!
+//! This library currently has quite rough edges and does not make adding and removing netfilter
+//! entries super easy and elegant. That is partly because the library needs more work, but also
+//! partly because nftables is super low level and extremely customizable, making it hard, and
+//! probably wrong, to try and create a too simple/limited wrapper. See examples for inspiration.
+//! One can also look at how the original project this crate was developed to support uses it:
+//! [Mullvad VPN app](https://github.com/mullvad/mullvadvpn-app)
+//!
+//! Understanding how to use [`libnftnl`] and implementing this crate has mostly been done by
+//! reading the source code for the [`nftables`] program and attaching debuggers to the `nft`
+//! binary. Since the implementation is mostly based on trial and error, there might of course be
+//! a number of places where the underlying library is used in an invalid or not intended way.
+//! Large portions of [`libnftnl`] are also not covered yet. Contributions are welcome!
+//!
+//! # Selecting version of `libnftnl`
+//!
+//! See the documentation for the corresponding sys crate for details: [`rustables-sys`].
+//! This crate has the same features as the sys crate, and selecting version works the same.
+//!
+//! [`libnftnl`]: https://netfilter.org/projects/libnftnl/
+//! [`nftables`]: https://netfilter.org/projects/nftables/
+//! [`rustables-sys`]: https://crates.io/crates/rustables-sys
+
+use thiserror::Error;
+
+pub use rustables_sys;
+use rustables_sys::libc;
+use std::{convert::TryFrom, ffi::c_void, ops::Deref};
+
+macro_rules! try_alloc {
+ ($e:expr) => {{
+ let ptr = $e;
+ if ptr.is_null() {
+ // OOM, and the tried allocation was likely very small,
+ // so we are in a very tight situation. We do what libstd does, aborts.
+ std::process::abort();
+ }
+ ptr
+ }};
+}
+
+mod batch;
+#[cfg(feature = "query")]
+pub use batch::{batch_is_supported, default_batch_page_size};
+pub use batch::{Batch, FinalizedBatch, NetlinkError};
+
+pub mod expr;
+
+pub mod table;
+pub use table::Table;
+#[cfg(feature = "query")]
+pub use table::{get_tables_cb, list_tables};
+
+mod chain;
+#[cfg(feature = "query")]
+pub use chain::{get_chains_cb, list_chains_for_table};
+pub use chain::{Chain, ChainType, Hook, Policy, Priority};
+
+pub mod query;
+
+mod rule;
+pub use rule::Rule;
+#[cfg(feature = "query")]
+pub use rule::{get_rules_cb, list_rules_for_chain};
+
+pub mod set;
+
+/// The type of the message as it's sent to netfilter. A message consists of an object, such as a
+/// [`Table`], [`Chain`] or [`Rule`] for example, and a [`MsgType`] to describe what to do with
+/// that object. If a [`Table`] object is sent with `MsgType::Add` then that table will be added
+/// to netfilter, if sent with `MsgType::Del` it will be removed.
+///
+/// [`Table`]: struct.Table.html
+/// [`Chain`]: struct.Chain.html
+/// [`Rule`]: struct.Rule.html
+/// [`MsgType`]: enum.MsgType.html
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum MsgType {
+ /// Add the object to netfilter.
+ Add,
+ /// Remove the object from netfilter.
+ Del,
+}
+
+/// Denotes a protocol. Used to specify which protocol a table or set belongs to.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[repr(u16)]
+pub enum ProtoFamily {
+ Unspec = libc::NFPROTO_UNSPEC as u16,
+ /// Inet - Means both IPv4 and IPv6
+ Inet = libc::NFPROTO_INET as u16,
+ Ipv4 = libc::NFPROTO_IPV4 as u16,
+ Arp = libc::NFPROTO_ARP as u16,
+ NetDev = libc::NFPROTO_NETDEV as u16,
+ Bridge = libc::NFPROTO_BRIDGE as u16,
+ Ipv6 = libc::NFPROTO_IPV6 as u16,
+ DecNet = libc::NFPROTO_DECNET as u16,
+}
+#[derive(Error, Debug)]
+#[error("Couldn't find a matching protocol")]
+pub struct InvalidProtocolFamily;
+
+impl TryFrom<i32> for ProtoFamily {
+ type Error = InvalidProtocolFamily;
+ fn try_from(value: i32) -> Result<Self, Self::Error> {
+ match value {
+ libc::NFPROTO_UNSPEC => Ok(ProtoFamily::Unspec),
+ libc::NFPROTO_INET => Ok(ProtoFamily::Inet),
+ libc::NFPROTO_IPV4 => Ok(ProtoFamily::Ipv4),
+ libc::NFPROTO_ARP => Ok(ProtoFamily::Arp),
+ libc::NFPROTO_NETDEV => Ok(ProtoFamily::NetDev),
+ libc::NFPROTO_BRIDGE => Ok(ProtoFamily::Bridge),
+ libc::NFPROTO_IPV6 => Ok(ProtoFamily::Ipv6),
+ libc::NFPROTO_DECNET => Ok(ProtoFamily::DecNet),
+ _ => Err(InvalidProtocolFamily),
+ }
+ }
+}
+
+/// Trait for all types in this crate that can serialize to a Netlink message.
+///
+/// # Unsafe
+///
+/// This trait is unsafe to implement because it must never serialize to anything larger than the
+/// largest possible netlink message. Internally the `nft_nlmsg_maxsize()` function is used to make
+/// sure the `buf` pointer passed to `write` always has room for the largest possible Netlink
+/// message.
+pub unsafe trait NlMsg {
+ /// Serializes the Netlink message to the buffer at `buf`. `buf` must have space for at least
+ /// `nft_nlmsg_maxsize()` bytes. This is not checked by the compiler, which is why this method
+ /// is unsafe.
+ unsafe fn write(&self, buf: *mut c_void, seq: u32, msg_type: MsgType);
+}
+
+unsafe impl<T, R> NlMsg for T
+where
+ T: Deref<Target = R>,
+ R: NlMsg,
+{
+ unsafe fn write(&self, buf: *mut c_void, seq: u32, msg_type: MsgType) {
+ self.deref().write(buf, seq, msg_type);
+ }
+}
+
+/// The largest nf_tables netlink message is the set element message, which
+/// contains the NFTA_SET_ELEM_LIST_ELEMENTS attribute. This attribute is
+/// a nest that describes the set elements. Given that the netlink attribute
+/// length (nla_len) is 16 bits, the largest message is a bit larger than
+/// 64 KBytes.
+pub fn nft_nlmsg_maxsize() -> u32 {
+ u32::from(::std::u16::MAX) + unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as u32
+}
diff --git a/rustables/src/query.rs b/rustables/src/query.rs
new file mode 100644
index 0000000..c5a6e6c
--- /dev/null
+++ b/rustables/src/query.rs
@@ -0,0 +1,132 @@
+use crate::{nft_nlmsg_maxsize, rustables_sys as sys, ProtoFamily};
+use sys::libc;
+
+/// Returns a buffer containing a netlink message which requests a list of all the netfilter
+/// matching objects (e.g. tables, chains, rules, ...).
+/// Supply the type of objects to retrieve (e.g. libc::NFT_MSG_GETTABLE), and optionally
+/// a callback to execute on the header, to set parameters for example.
+/// To pass arbitrary data inside that callback, please use a closure.
+pub fn get_list_of_objects<Error>(
+ seq: u32,
+ target: u16,
+ setup_cb: Option<&dyn Fn(&mut libc::nlmsghdr) -> Result<(), Error>>,
+) -> Result<Vec<u8>, Error> {
+ let mut buffer = vec![0; nft_nlmsg_maxsize() as usize];
+ let hdr = unsafe {
+ &mut *sys::nftnl_nlmsg_build_hdr(
+ buffer.as_mut_ptr() as *mut libc::c_char,
+ target,
+ ProtoFamily::Unspec as u16,
+ (libc::NLM_F_ROOT | libc::NLM_F_MATCH) as u16,
+ seq,
+ )
+ };
+ if let Some(cb) = setup_cb {
+ cb(hdr)?;
+ }
+ Ok(buffer)
+}
+
+#[cfg(feature = "query")]
+mod inner {
+ use crate::FinalizedBatch;
+
+ use tracing::debug;
+
+ use super::*;
+
+ #[derive(thiserror::Error, Debug)]
+ pub enum Error {
+ #[error("Unable to open netlink socket to netfilter")]
+ NetlinkOpenError(#[source] std::io::Error),
+
+ #[error("Unable to send netlink command to netfilter")]
+ NetlinkSendError(#[source] std::io::Error),
+
+ #[error("Error while reading from netlink socket")]
+ NetlinkRecvError(#[source] std::io::Error),
+
+ #[error("Error while processing an incoming netlink message")]
+ ProcessNetlinkError(#[source] std::io::Error),
+
+ #[error("Custom error when customizing the query")]
+ InitError(#[from] Box<dyn std::error::Error + 'static>),
+
+ #[error("Couldn't allocate a netlink object, out of memory ?")]
+ NetlinkAllocationFailed,
+ }
+
+ /// List objects of a certain type (e.g. libc::NFT_MSG_GETTABLE) with the help of an helper
+ /// function called by mnl::cb_run2.
+ /// The callback expect a tuple of additional data (supplied as an argument to
+ /// this function) and of the output vector, to which it should append the parsed
+ /// object it received.
+ pub fn list_objects_with_data<'a, A, T>(
+ data_type: u16,
+ cb: fn(&libc::nlmsghdr, &mut (&'a A, &mut Vec<T>)) -> libc::c_int,
+ additional_data: &'a A,
+ req_hdr_customize: Option<&dyn Fn(&mut libc::nlmsghdr) -> Result<(), Error>>,
+ ) -> Result<Vec<T>, Error>
+ where
+ T: 'a,
+ {
+ debug!("listing objects of kind {}", data_type);
+ let socket = mnl::Socket::new(mnl::Bus::Netfilter).map_err(Error::NetlinkOpenError)?;
+
+ let seq = 0;
+ let portid = 0;
+
+ let chains_buf = get_list_of_objects(seq, data_type, req_hdr_customize)?;
+ socket.send(&chains_buf).map_err(Error::NetlinkSendError)?;
+
+ let mut res = Vec::new();
+
+ let mut msg_buffer = vec![0; nft_nlmsg_maxsize() as usize];
+ while socket
+ .recv(&mut msg_buffer)
+ .map_err(Error::NetlinkRecvError)?
+ > 0
+ {
+ if let mnl::CbResult::Stop = mnl::cb_run2(
+ &msg_buffer,
+ seq,
+ portid,
+ cb,
+ &mut (additional_data, &mut res),
+ )
+ .map_err(Error::ProcessNetlinkError)?
+ {
+ break;
+ }
+ }
+
+ Ok(res)
+ }
+
+ pub fn send_batch(batch: &mut FinalizedBatch) -> Result<(), Error> {
+ let socket = mnl::Socket::new(mnl::Bus::Netfilter).map_err(Error::NetlinkOpenError)?;
+
+ let seq = 0;
+ let portid = socket.portid();
+
+ socket.send_all(batch).map_err(Error::NetlinkSendError)?;
+ debug!("sent");
+
+ let mut msg_buffer = vec![0; nft_nlmsg_maxsize() as usize];
+ while socket
+ .recv(&mut msg_buffer)
+ .map_err(Error::NetlinkRecvError)?
+ > 0
+ {
+ if let mnl::CbResult::Stop =
+ mnl::cb_run(&msg_buffer, seq, portid).map_err(Error::ProcessNetlinkError)?
+ {
+ break;
+ }
+ }
+ Ok(())
+ }
+}
+
+#[cfg(feature = "query")]
+pub use inner::*;
diff --git a/rustables/src/rule.rs b/rustables/src/rule.rs
new file mode 100644
index 0000000..acd3450
--- /dev/null
+++ b/rustables/src/rule.rs
@@ -0,0 +1,227 @@
+use crate::{chain::Chain, expr::Expression, MsgType};
+use rustables_sys::{self as sys, libc};
+use std::ffi::{c_void, CStr, CString};
+use std::fmt::Debug;
+use std::os::raw::c_char;
+use std::sync::Arc;
+use tracing::error;
+
+/// A nftables firewall rule.
+pub struct Rule {
+ rule: *mut sys::nftnl_rule,
+ chain: Arc<Chain>,
+}
+
+// Safety: It should be safe to pass this around and *read* from it
+// from multiple threads
+unsafe impl Send for Rule {}
+unsafe impl Sync for Rule {}
+
+impl Rule {
+ /// Creates a new rule object in the given [`Chain`].
+ ///
+ /// [`Chain`]: struct.Chain.html
+ pub fn new(chain: Arc<Chain>) -> Rule {
+ unsafe {
+ let rule = try_alloc!(sys::nftnl_rule_alloc());
+ sys::nftnl_rule_set_u32(
+ rule,
+ sys::NFTNL_RULE_FAMILY as u16,
+ chain.get_table().get_family() as u32,
+ );
+ sys::nftnl_rule_set_str(
+ rule,
+ sys::NFTNL_RULE_TABLE as u16,
+ chain.get_table().get_name().as_ptr(),
+ );
+ sys::nftnl_rule_set_str(
+ rule,
+ sys::NFTNL_RULE_CHAIN as u16,
+ chain.get_name().as_ptr(),
+ );
+
+ Rule { rule, chain }
+ }
+ }
+
+ pub unsafe fn from_raw(rule: *mut sys::nftnl_rule, chain: Arc<Chain>) -> Self {
+ Rule { rule, chain }
+ }
+
+ pub fn get_position(&self) -> u64 {
+ unsafe { sys::nftnl_rule_get_u64(self.rule, sys::NFTNL_RULE_POSITION as u16) }
+ }
+
+ /// Sets the position of this rule within the chain it lives in. By default a new rule is added
+ /// to the end of the chain.
+ pub fn set_position(&mut self, position: u64) {
+ unsafe {
+ sys::nftnl_rule_set_u64(self.rule, sys::NFTNL_RULE_POSITION as u16, position);
+ }
+ }
+
+ pub fn get_handle(&self) -> u64 {
+ unsafe { sys::nftnl_rule_get_u64(self.rule, sys::NFTNL_RULE_HANDLE as u16) }
+ }
+
+ pub fn set_handle(&mut self, handle: u64) {
+ unsafe {
+ sys::nftnl_rule_set_u64(self.rule, sys::NFTNL_RULE_HANDLE as u16, handle);
+ }
+ }
+
+ /// Adds an expression to this rule. Expressions are evaluated from first to last added.
+ /// As soon as an expression does not match the packet it's being evaluated for, evaluation
+ /// stops and the packet is evaluated against the next rule in the chain.
+ pub fn add_expr(&mut self, expr: &impl Expression) {
+ unsafe { sys::nftnl_rule_add_expr(self.rule, expr.to_expr(self)) }
+ }
+
+ /// Returns a reference to the [`Chain`] this rule lives in.
+ ///
+ /// [`Chain`]: struct.Chain.html
+ pub fn get_chain(&self) -> Arc<Chain> {
+ self.chain.clone()
+ }
+
+ /// Returns the userdata of this chain.
+ pub fn get_userdata(&self) -> Option<&CStr> {
+ unsafe {
+ let ptr = sys::nftnl_rule_get_str(self.rule, sys::NFTNL_RULE_USERDATA as u16);
+ if ptr == std::ptr::null() {
+ return None;
+ }
+ Some(CStr::from_ptr(ptr))
+ }
+ }
+
+ /// Update the userdata of this chain.
+ pub fn set_userdata(&self, data: &CStr) {
+ unsafe {
+ sys::nftnl_rule_set_str(self.rule, sys::NFTNL_RULE_USERDATA as u16, data.as_ptr());
+ }
+ }
+
+ /// Returns a textual description of the rule.
+ pub fn get_str(&self) -> CString {
+ let mut descr_buf = vec![0i8; 4096];
+ unsafe {
+ sys::nftnl_rule_snprintf(
+ descr_buf.as_mut_ptr(),
+ (descr_buf.len() - 1) as u64,
+ self.rule,
+ sys::NFTNL_OUTPUT_DEFAULT,
+ 0,
+ );
+ CStr::from_ptr(descr_buf.as_ptr()).to_owned()
+ }
+ }
+
+ /// Returns the raw handle.
+ pub fn as_ptr(&self) -> *const sys::nftnl_rule {
+ self.rule as *const sys::nftnl_rule
+ }
+
+ /// Returns a mutable version of the raw handle.
+ pub fn as_mut_ptr(&mut self) -> *mut sys::nftnl_rule {
+ self.rule
+ }
+}
+
+impl Debug for Rule {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{:?}", self.get_str())
+ }
+}
+
+impl PartialEq for Rule {
+ fn eq(&self, other: &Self) -> bool {
+ self.get_chain() == other.get_chain() && self.get_handle() == other.get_handle()
+ }
+}
+
+unsafe impl crate::NlMsg for Rule {
+ unsafe fn write(&self, buf: *mut c_void, seq: u32, msg_type: MsgType) {
+ let type_ = match msg_type {
+ MsgType::Add => libc::NFT_MSG_NEWRULE,
+ MsgType::Del => libc::NFT_MSG_DELRULE,
+ };
+ let flags: u16 = match msg_type {
+ MsgType::Add => (libc::NLM_F_CREATE | libc::NLM_F_APPEND | libc::NLM_F_EXCL) as u16,
+ MsgType::Del => 0u16,
+ } | libc::NLM_F_ACK as u16;
+ let header = sys::nftnl_nlmsg_build_hdr(
+ buf as *mut c_char,
+ type_ as u16,
+ self.chain.get_table().get_family() as u16,
+ flags,
+ seq,
+ );
+ sys::nftnl_rule_nlmsg_build_payload(header, self.rule);
+ }
+}
+
+impl Drop for Rule {
+ fn drop(&mut self) {
+ unsafe { sys::nftnl_rule_free(self.rule) };
+ }
+}
+
+#[cfg(feature = "query")]
+pub fn get_rules_cb(
+ header: &libc::nlmsghdr,
+ (chain, rules): &mut (&Arc<Chain>, &mut Vec<Rule>),
+) -> libc::c_int {
+ unsafe {
+ let rule = sys::nftnl_rule_alloc();
+ if rule == std::ptr::null_mut() {
+ return mnl::mnl_sys::MNL_CB_ERROR;
+ }
+ let err = sys::nftnl_rule_nlmsg_parse(header, rule);
+ if err < 0 {
+ error!("Failed to parse nelink rule message - {}", err);
+ sys::nftnl_rule_free(rule);
+ return err;
+ }
+
+ rules.push(Rule::from_raw(rule, chain.clone()));
+ }
+ mnl::mnl_sys::MNL_CB_OK
+}
+
+#[cfg(feature = "query")]
+pub fn list_rules_for_chain(chain: &Arc<Chain>) -> Result<Vec<Rule>, crate::query::Error> {
+ crate::query::list_objects_with_data(
+ libc::NFT_MSG_GETRULE as u16,
+ get_rules_cb,
+ &chain,
+ // only retrieve rules from the currently targetted chain
+ Some(&|hdr| unsafe {
+ let rule = sys::nftnl_rule_alloc();
+ if rule as *const _ == std::ptr::null() {
+ return Err(crate::query::Error::NetlinkAllocationFailed);
+ }
+
+ sys::nftnl_rule_set_str(
+ rule,
+ sys::NFTNL_RULE_TABLE as u16,
+ chain.get_table().get_name().as_ptr(),
+ );
+ sys::nftnl_rule_set_u32(
+ rule,
+ sys::NFTNL_RULE_FAMILY as u16,
+ chain.get_table().get_family() as u32,
+ );
+ sys::nftnl_rule_set_str(
+ rule,
+ sys::NFTNL_RULE_CHAIN as u16,
+ chain.get_name().as_ptr(),
+ );
+
+ sys::nftnl_rule_nlmsg_build_payload(hdr, rule);
+
+ sys::nftnl_rule_free(rule);
+ Ok(())
+ }),
+ )
+}
diff --git a/rustables/src/set.rs b/rustables/src/set.rs
new file mode 100644
index 0000000..a431afc
--- /dev/null
+++ b/rustables/src/set.rs
@@ -0,0 +1,266 @@
+use crate::{table::Table, MsgType, ProtoFamily};
+use rustables_sys::{self as sys, libc};
+use std::{
+ cell::Cell,
+ ffi::{c_void, CStr, CString},
+ fmt::Debug,
+ net::{Ipv4Addr, Ipv6Addr},
+ os::raw::c_char,
+ rc::Rc,
+};
+use tracing::trace;
+
+#[macro_export]
+macro_rules! nft_set {
+ ($name:expr, $id:expr, $table:expr, $family:expr) => {
+ $crate::set::Set::new($name, $id, $table, $family)
+ };
+ ($name:expr, $id:expr, $table:expr, $family:expr; [ ]) => {
+ nft_set!($name, $id, $table, $family)
+ };
+ ($name:expr, $id:expr, $table:expr, $family:expr; [ $($value:expr,)* ]) => {{
+ let mut set = nft_set!($name, $id, $table, $family).expect("Set allocation failed");
+ $(
+ set.add($value).expect(stringify!(Unable to add $value to set $name));
+ )*
+ set
+ }};
+}
+
+pub struct Set<'a, K> {
+ set: *mut sys::nftnl_set,
+ table: &'a Table,
+ family: ProtoFamily,
+ _marker: ::std::marker::PhantomData<K>,
+}
+
+impl<'a, K> Set<'a, K> {
+ pub fn new(name: &CStr, id: u32, table: &'a Table, family: ProtoFamily) -> Self
+ where
+ K: SetKey,
+ {
+ unsafe {
+ let set = try_alloc!(sys::nftnl_set_alloc());
+
+ sys::nftnl_set_set_u32(set, sys::NFTNL_SET_FAMILY as u16, family as u32);
+ sys::nftnl_set_set_str(set, sys::NFTNL_SET_TABLE as u16, table.get_name().as_ptr());
+ sys::nftnl_set_set_str(set, sys::NFTNL_SET_NAME as u16, name.as_ptr());
+ sys::nftnl_set_set_u32(set, sys::NFTNL_SET_ID as u16, id);
+
+ sys::nftnl_set_set_u32(
+ set,
+ sys::NFTNL_SET_FLAGS as u16,
+ (libc::NFT_SET_ANONYMOUS | libc::NFT_SET_CONSTANT) as u32,
+ );
+ sys::nftnl_set_set_u32(set, sys::NFTNL_SET_KEY_TYPE as u16, K::TYPE);
+ sys::nftnl_set_set_u32(set, sys::NFTNL_SET_KEY_LEN as u16, K::LEN);
+
+ Set {
+ set,
+ table,
+ family,
+ _marker: ::std::marker::PhantomData,
+ }
+ }
+ }
+
+ pub unsafe fn from_raw(set: *mut sys::nftnl_set, table: &'a Table, family: ProtoFamily) -> Self
+ where
+ K: SetKey,
+ {
+ Set {
+ set,
+ table,
+ family,
+ _marker: ::std::marker::PhantomData,
+ }
+ }
+
+ pub fn add(&mut self, key: &K)
+ where
+ K: SetKey,
+ {
+ unsafe {
+ let elem = try_alloc!(sys::nftnl_set_elem_alloc());
+
+ let data = key.data();
+ let data_len = data.len() as u32;
+ trace!("Adding key {:?} with len {}", data, data_len);
+ sys::nftnl_set_elem_set(
+ elem,
+ sys::NFTNL_SET_ELEM_KEY as u16,
+ data.as_ref() as *const _ as *const c_void,
+ data_len,
+ );
+ sys::nftnl_set_elem_add(self.set, elem);
+ }
+ }
+
+ pub fn elems_iter(&'a self) -> SetElemsIter<'a, K> {
+ SetElemsIter::new(self)
+ }
+
+ /// Returns the raw handle.
+ pub fn as_ptr(&self) -> *const sys::nftnl_set {
+ self.set as *const sys::nftnl_set
+ }
+
+ /// Returns a mutable version of the raw handle.
+ pub fn as_mut_ptr(&self) -> *mut sys::nftnl_set {
+ self.set
+ }
+
+ pub fn get_family(&self) -> ProtoFamily {
+ self.family
+ }
+
+ /// Returns a textual description of the set.
+ pub fn get_str(&self) -> CString {
+ let mut descr_buf = vec![0i8; 4096];
+ unsafe {
+ sys::nftnl_set_snprintf(
+ descr_buf.as_mut_ptr(),
+ (descr_buf.len() - 1) as u64,
+ self.set,
+ sys::NFTNL_OUTPUT_DEFAULT,
+ 0,
+ );
+ CStr::from_ptr(descr_buf.as_ptr()).to_owned()
+ }
+ }
+
+ pub fn get_name(&self) -> &CStr {
+ unsafe {
+ let ptr = sys::nftnl_set_get_str(self.set, sys::NFTNL_SET_NAME as u16);
+ CStr::from_ptr(ptr)
+ }
+ }
+
+ pub fn get_id(&self) -> u32 {
+ unsafe { sys::nftnl_set_get_u32(self.set, sys::NFTNL_SET_ID as u16) }
+ }
+}
+
+impl<'a, K> Debug for Set<'a, K> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{:?}", self.get_str())
+ }
+}
+
+unsafe impl<'a, K> crate::NlMsg for Set<'a, K> {
+ unsafe fn write(&self, buf: *mut c_void, seq: u32, msg_type: MsgType) {
+ let type_ = match msg_type {
+ MsgType::Add => libc::NFT_MSG_NEWSET,
+ MsgType::Del => libc::NFT_MSG_DELSET,
+ };
+ let header = sys::nftnl_nlmsg_build_hdr(
+ buf as *mut c_char,
+ type_ as u16,
+ self.table.get_family() as u16,
+ (libc::NLM_F_APPEND | libc::NLM_F_CREATE | libc::NLM_F_ACK) as u16,
+ seq,
+ );
+ sys::nftnl_set_nlmsg_build_payload(header, self.set);
+ }
+}
+
+impl<'a, K> Drop for Set<'a, K> {
+ fn drop(&mut self) {
+ unsafe { sys::nftnl_set_free(self.set) };
+ }
+}
+
+pub struct SetElemsIter<'a, K> {
+ set: &'a Set<'a, K>,
+ iter: *mut sys::nftnl_set_elems_iter,
+ ret: Rc<Cell<i32>>,
+}
+
+impl<'a, K> SetElemsIter<'a, K> {
+ fn new(set: &'a Set<'a, K>) -> Self {
+ let iter = try_alloc!(unsafe { sys::nftnl_set_elems_iter_create(set.as_ptr()) });
+ SetElemsIter {
+ set,
+ iter,
+ ret: Rc::new(Cell::new(1)),
+ }
+ }
+}
+
+impl<'a, K: 'a> Iterator for SetElemsIter<'a, K> {
+ type Item = SetElemsMsg<'a, K>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.ret.get() <= 0 || unsafe { sys::nftnl_set_elems_iter_cur(self.iter).is_null() } {
+ trace!("SetElemsIter iterator ending");
+ None
+ } else {
+ trace!("SetElemsIter returning new SetElemsMsg");
+ Some(SetElemsMsg {
+ set: self.set,
+ iter: self.iter,
+ ret: self.ret.clone(),
+ })
+ }
+ }
+}
+
+impl<'a, K> Drop for SetElemsIter<'a, K> {
+ fn drop(&mut self) {
+ unsafe { sys::nftnl_set_elems_iter_destroy(self.iter) };
+ }
+}
+
+pub struct SetElemsMsg<'a, K> {
+ set: &'a Set<'a, K>,
+ iter: *mut sys::nftnl_set_elems_iter,
+ ret: Rc<Cell<i32>>,
+}
+
+unsafe impl<'a, K> crate::NlMsg for SetElemsMsg<'a, K> {
+ unsafe fn write(&self, buf: *mut c_void, seq: u32, msg_type: MsgType) {
+ trace!("Writing SetElemsMsg to NlMsg");
+ let (type_, flags) = match msg_type {
+ MsgType::Add => (
+ libc::NFT_MSG_NEWSETELEM,
+ libc::NLM_F_CREATE | libc::NLM_F_EXCL | libc::NLM_F_ACK,
+ ),
+ MsgType::Del => (libc::NFT_MSG_DELSETELEM, libc::NLM_F_ACK),
+ };
+ let header = sys::nftnl_nlmsg_build_hdr(
+ buf as *mut c_char,
+ type_ as u16,
+ self.set.get_family() as u16,
+ flags as u16,
+ seq,
+ );
+ self.ret.set(sys::nftnl_set_elems_nlmsg_build_payload_iter(
+ header, self.iter,
+ ));
+ }
+}
+
+pub trait SetKey {
+ const TYPE: u32;
+ const LEN: u32;
+
+ fn data(&self) -> Box<[u8]>;
+}
+
+impl SetKey for Ipv4Addr {
+ const TYPE: u32 = 7;
+ const LEN: u32 = 4;
+
+ fn data(&self) -> Box<[u8]> {
+ self.octets().to_vec().into_boxed_slice()
+ }
+}
+
+impl SetKey for Ipv6Addr {
+ const TYPE: u32 = 8;
+ const LEN: u32 = 16;
+
+ fn data(&self) -> Box<[u8]> {
+ self.octets().to_vec().into_boxed_slice()
+ }
+}
diff --git a/rustables/src/table.rs b/rustables/src/table.rs
new file mode 100644
index 0000000..9de6f5e
--- /dev/null
+++ b/rustables/src/table.rs
@@ -0,0 +1,169 @@
+use crate::{MsgType, ProtoFamily};
+use rustables_sys::{self as sys, libc};
+use std::{
+ convert::TryFrom,
+ ffi::{c_void, CStr, CString},
+ fmt::Debug,
+ os::raw::c_char,
+};
+use tracing::error;
+
+/// Abstraction of `nftnl_table`. The top level container in netfilter. A table has a protocol
+/// family and contain [`Chain`]s that in turn hold the rules.
+///
+/// [`Chain`]: struct.Chain.html
+pub struct Table {
+ table: *mut sys::nftnl_table,
+ family: ProtoFamily,
+}
+
+// Safety: It should be safe to pass this around and *read* from it
+// from multiple threads
+unsafe impl Send for Table {}
+unsafe impl Sync for Table {}
+
+impl Table {
+ /// Creates a new table instance with the given name and protocol family.
+ pub fn new<T: AsRef<CStr>>(name: &T, family: ProtoFamily) -> Table {
+ unsafe {
+ let table = try_alloc!(sys::nftnl_table_alloc());
+
+ sys::nftnl_table_set_u32(table, sys::NFTNL_TABLE_FAMILY as u16, family as u32);
+ sys::nftnl_table_set_str(table, sys::NFTNL_TABLE_NAME as u16, name.as_ref().as_ptr());
+ sys::nftnl_table_set_u32(table, sys::NFTNL_TABLE_FLAGS as u16, 0u32);
+ Table { table, family }
+ }
+ }
+
+ pub unsafe fn from_raw(table: *mut sys::nftnl_table, family: ProtoFamily) -> Self {
+ Table { table, family }
+ }
+
+ /// Returns the name of this table.
+ pub fn get_name(&self) -> &CStr {
+ unsafe {
+ let ptr = sys::nftnl_table_get_str(self.table, sys::NFTNL_TABLE_NAME as u16);
+ CStr::from_ptr(ptr)
+ }
+ }
+
+ /// Returns a textual description of the table.
+ pub fn get_str(&self) -> CString {
+ let mut descr_buf = vec![0i8; 4096];
+ unsafe {
+ sys::nftnl_table_snprintf(
+ descr_buf.as_mut_ptr(),
+ (descr_buf.len() - 1) as u64,
+ self.table,
+ sys::NFTNL_OUTPUT_DEFAULT,
+ 0,
+ );
+ CStr::from_ptr(descr_buf.as_ptr()).to_owned()
+ }
+ }
+
+ /// Returns the protocol family for this table.
+ pub fn get_family(&self) -> ProtoFamily {
+ self.family
+ }
+
+ /// Returns the userdata of this chain.
+ pub fn get_userdata(&self) -> Option<&CStr> {
+ unsafe {
+ let ptr = sys::nftnl_table_get_str(self.table, sys::NFTNL_TABLE_USERDATA as u16);
+ if ptr == std::ptr::null() {
+ return None;
+ }
+ Some(CStr::from_ptr(ptr))
+ }
+ }
+
+ /// Update the userdata of this chain.
+ pub fn set_userdata(&self, data: &CStr) {
+ unsafe {
+ sys::nftnl_table_set_str(self.table, sys::NFTNL_TABLE_USERDATA as u16, data.as_ptr());
+ }
+ }
+
+ /// Returns the raw handle.
+ pub fn as_ptr(&self) -> *const sys::nftnl_table {
+ self.table as *const sys::nftnl_table
+ }
+
+ /// Returns a mutable version of the raw handle.
+ pub fn as_mut_ptr(&self) -> *mut sys::nftnl_table {
+ self.table
+ }
+}
+
+impl PartialEq for Table {
+ fn eq(&self, other: &Self) -> bool {
+ self.get_name() == other.get_name() && self.get_family() == other.get_family()
+ }
+}
+
+impl Debug for Table {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{:?}", self.get_str())
+ }
+}
+
+unsafe impl crate::NlMsg for Table {
+ unsafe fn write(&self, buf: *mut c_void, seq: u32, msg_type: MsgType) {
+ let raw_msg_type = match msg_type {
+ MsgType::Add => libc::NFT_MSG_NEWTABLE,
+ MsgType::Del => libc::NFT_MSG_DELTABLE,
+ };
+ let header = sys::nftnl_nlmsg_build_hdr(
+ buf as *mut c_char,
+ raw_msg_type as u16,
+ self.family as u16,
+ libc::NLM_F_ACK as u16,
+ seq,
+ );
+ sys::nftnl_table_nlmsg_build_payload(header, self.table);
+ }
+}
+
+impl Drop for Table {
+ fn drop(&mut self) {
+ unsafe { sys::nftnl_table_free(self.table) };
+ }
+}
+
+#[cfg(feature = "query")]
+/// A callback to parse the response for messages created with `get_tables_nlmsg`.
+pub fn get_tables_cb(
+ header: &libc::nlmsghdr,
+ (_, tables): &mut (&(), &mut Vec<Table>),
+) -> libc::c_int {
+ unsafe {
+ let table = sys::nftnl_table_alloc();
+ if table == std::ptr::null_mut() {
+ return mnl::mnl_sys::MNL_CB_ERROR;
+ }
+ let err = sys::nftnl_table_nlmsg_parse(header, table);
+ if err < 0 {
+ error!("Failed to parse nelink table message - {}", err);
+ sys::nftnl_table_free(table);
+ return err;
+ }
+ let family = sys::nftnl_table_get_u32(table, sys::NFTNL_TABLE_FAMILY as u16);
+ match crate::ProtoFamily::try_from(family as i32) {
+ Ok(family) => {
+ tables.push(Table::from_raw(table, family));
+ mnl::mnl_sys::MNL_CB_OK
+ }
+ Err(crate::InvalidProtocolFamily) => {
+ error!("The netlink table didn't have a valid protocol family !?");
+ sys::nftnl_table_free(table);
+ mnl::mnl_sys::MNL_CB_ERROR
+ }
+ }
+ }
+}
+
+#[cfg(feature = "query")]
+pub fn list_tables() -> Result<Vec<Table>, crate::query::Error> {
+ crate::query::list_objects_with_data(libc::NFT_MSG_GETTABLE as u16, get_tables_cb, &(), None)
+}