Skip to content

Commit 7fc5e9b

Browse files
Anh-Kagifaern
authored andcommittedJan 23, 2025
added support for ingress hook
- added Ingress in enum Hook - added set_device() method in struct Chain - updated libc dependency in nftnl-sys - added an example file for ingress hook - update MSRV to 1.63.0
1 parent e7c9913 commit 7fc5e9b

File tree

6 files changed

+168
-6
lines changed

6 files changed

+168
-6
lines changed
 

‎.github/workflows/build-and-test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
strategy:
1919
matrix:
2020
# Keep MSRV in sync with rust-version in Cargo.toml
21-
rust: [stable, beta, nightly, 1.56.0]
21+
rust: [stable, beta, nightly, 1.63.0]
2222
runs-on: ubuntu-latest
2323
steps:
2424
- name: Install build dependencies

‎Cargo.lock

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎nftnl-sys/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ readme = "README.md"
99
keywords = ["nftables", "nft", "firewall", "iptables", "netfilter"]
1010
categories = ["network-programming", "os::unix-apis", "external-ffi-bindings", "no-std"]
1111
edition = "2021"
12-
rust-version = "1.56.0"
12+
rust-version = "1.63.0"
1313

1414

1515
[features]
@@ -22,7 +22,7 @@ nftnl-1-1-2 = ["nftnl-1-1-1"]
2222

2323
[dependencies]
2424
cfg-if = "1.0"
25-
libc = "0.2.44"
25+
libc = "0.2.166"
2626

2727
[build-dependencies]
2828
cfg-if = "1.0"

‎nftnl/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ readme = "../README.md"
99
keywords = ["nftables", "nft", "firewall", "iptables", "netfilter"]
1010
categories = ["network-programming", "os::unix-apis", "api-bindings"]
1111
edition = "2021"
12-
rust-version = "1.56.0"
12+
rust-version = "1.63.0"
1313

1414
[features]
1515
nftnl-1-0-7 = ["nftnl-sys/nftnl-1-0-7"]

‎nftnl/examples/add-ingress-rule.rs

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
//! Adds a table, an ingress chain and some rules to netfilter.
2+
//!
3+
//! This example uses `verdict accept` everywhere. So even after running this the firewall won't
4+
//! block anything. This is so anyone trying to run this does not end up in a strange state
5+
//! where they don't understand why their network is broken. Try changing to `verdict drop` if
6+
//! you want to see the block working.
7+
//!
8+
//! Run the following to print out current active tables, chains and rules in netfilter. Must be
9+
//! executed as root:
10+
//! ```bash
11+
//! # nft list ruleset
12+
//! ```
13+
//! After running this example, the output should be the following:
14+
//! ```ignore
15+
//! table inet example-table {
16+
//! chain chain-for-ingress {
17+
//! type filter hook ingress priority -450; policy accept;
18+
//! ip saddr 127.0.0.1 counter packets 0 bytes 0 accept
19+
//! }
20+
//! }
21+
//! ```
22+
//!
23+
//! Try pinging any IP in the network range denoted by the outgoing rule and see the counter
24+
//! increment:
25+
//! ```bash
26+
//! $ ping 127.0.0.2
27+
//! ```
28+
//!
29+
//! Everything created by this example can be removed by running
30+
//! ```bash
31+
//! # nft delete table inet example-table
32+
//! ```
33+
34+
use nftnl::{
35+
nft_expr, nftnl_sys::libc, Batch, Chain, ChainType, FinalizedBatch, Policy, ProtoFamily, Rule,
36+
Table,
37+
};
38+
use std::{ffi::CString, io, net::Ipv4Addr};
39+
40+
const TABLE_NAME: &str = "example-table";
41+
const CHAIN_NAME: &str = "chain-for-ingress";
42+
43+
fn main() -> Result<(), Box<dyn std::error::Error>> {
44+
// Create a batch. This is used to store all the netlink messages we will later send.
45+
// Creating a new batch also automatically writes the initial batch begin message needed
46+
// to tell netlink this is a single transaction that might arrive over multiple netlink packets.
47+
let mut batch = Batch::new();
48+
49+
// Create a netfilter table operating on both IPv4 and IPv6 (ProtoFamily::Inet)
50+
let table = Table::new(&CString::new(TABLE_NAME).unwrap(), ProtoFamily::Inet);
51+
// Add the table to the batch with the `MsgType::Add` type, thus instructing netfilter to add
52+
// this table under its `ProtoFamily::Inet` ruleset.
53+
batch.add(&table, nftnl::MsgType::Add);
54+
55+
// Create input and output chains under the table we created above.
56+
let mut chain = Chain::new(&CString::new(CHAIN_NAME).unwrap(), &table);
57+
58+
// Hook the chain to the input and output event hooks, with highest priority (priority zero).
59+
// See the `Chain::set_hook` documentation for details.
60+
chain.set_hook(nftnl::Hook::Ingress, -450); // -450 priority places this chain before any conntrack or defragmentation
61+
62+
// Setting the chain type to filter is not necessary, as it is the default type.
63+
chain.set_type(ChainType::Filter);
64+
65+
// Ingress hooks need a device to bind to.
66+
chain.set_device(&CString::new("lo").unwrap());
67+
68+
// Set the default policies on the chains. If no rule matches a packet processed by the
69+
// `out_chain` or the `in_chain` it will accept the packet.
70+
chain.set_policy(Policy::Accept);
71+
72+
// Add the two chains to the batch with the `MsgType` to tell netfilter to create the chains
73+
// under the table.
74+
batch.add(&chain, nftnl::MsgType::Add);
75+
76+
// === ADD A RULE ALLOWING (AND COUNTING) ALL PACKETS FROM THE 127.0.0.1 IP ADDRESS ===
77+
78+
let mut rule = Rule::new(&chain);
79+
let local_ip = Ipv4Addr::new(127, 0, 0, 1);
80+
81+
// Load the `nfproto` metadata into the netfilter register. This metadata denotes which layer3
82+
// protocol the packet being processed is using.
83+
rule.add_expr(&nft_expr!(meta nfproto));
84+
// Check if the currently processed packet is an IPv4 packet. This must be done before payload
85+
// data assuming the packet uses IPv4 can be loaded in the next expression.
86+
rule.add_expr(&nft_expr!(cmp == libc::NFPROTO_IPV4 as u8));
87+
88+
// Load the IPv4 destination address into the netfilter register.
89+
rule.add_expr(&nft_expr!(payload ipv4 saddr));
90+
// Compare the register with the IP we are interested in.
91+
rule.add_expr(&nft_expr!(cmp == local_ip));
92+
93+
// Add a packet counter to the rule. Shows how many packets have been evaluated against this
94+
// expression. Since expressions are evaluated from first to last, putting this counter before
95+
// the above IP net check would make the counter increment on all packets also *not* matching
96+
// those expressions. Because the counter would then be evaluated before it fails a check.
97+
// Similarly, if the counter was added after the verdict it would always remain at zero. Since
98+
// when the packet hits the verdict expression any further processing of expressions stop.
99+
rule.add_expr(&nft_expr!(counter));
100+
101+
// Accept all the packets matching the rule so far.
102+
rule.add_expr(&nft_expr!(verdict accept));
103+
104+
// Add the rule to the batch. Without this nothing would be sent over netlink and netfilter,
105+
// and all the work on `block_out_to_private_net_rule` so far would go to waste.
106+
batch.add(&rule, nftnl::MsgType::Add);
107+
108+
// === FINALIZE THE TRANSACTION AND SEND THE DATA TO NETFILTER ===
109+
110+
// Finalize the batch. This means the batch end message is written into the batch, telling
111+
// netfilter that we reached the end of the transaction message. It's also converted to a type
112+
// that implements `IntoIterator<Item = &'a [u8]>`, thus allowing us to get the raw netlink data
113+
// out so it can be sent over a netlink socket to netfilter.
114+
let finalized_batch = batch.finalize();
115+
116+
// Send the entire batch and process any returned messages.
117+
send_and_process(&finalized_batch)?;
118+
Ok(())
119+
}
120+
121+
fn send_and_process(batch: &FinalizedBatch) -> io::Result<()> {
122+
// Create a netlink socket to netfilter.
123+
let socket = mnl::Socket::new(mnl::Bus::Netfilter)?;
124+
// Send all the bytes in the batch.
125+
socket.send_all(batch)?;
126+
127+
// Try to parse the messages coming back from netfilter. This part is still very unclear.
128+
let portid = socket.portid();
129+
let mut buffer = vec![0; nftnl::nft_nlmsg_maxsize() as usize];
130+
let very_unclear_what_this_is_for = 2;
131+
while let Some(message) = socket_recv(&socket, &mut buffer[..])? {
132+
match mnl::cb_run(message, very_unclear_what_this_is_for, portid)? {
133+
mnl::CbResult::Stop => {
134+
break;
135+
}
136+
mnl::CbResult::Ok => (),
137+
}
138+
}
139+
Ok(())
140+
}
141+
142+
fn socket_recv<'a>(socket: &mnl::Socket, buf: &'a mut [u8]) -> io::Result<Option<&'a [u8]>> {
143+
let ret = socket.recv(buf)?;
144+
if ret > 0 {
145+
Ok(Some(&buf[..ret]))
146+
} else {
147+
Ok(None)
148+
}
149+
}

‎nftnl/src/chain.rs

+13
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ pub enum Hook {
2222
Out = libc::NF_INET_LOCAL_OUT as u16,
2323
/// Hook into the post-routing stage of netfilter. Corresponds to `NF_INET_POST_ROUTING`.
2424
PostRouting = libc::NF_INET_POST_ROUTING as u16,
25+
/// Hook into the ingress stage of netfilter. Corresponds to `NF_INET_INGRESS`.
26+
Ingress = libc::NF_INET_INGRESS as u16,
2527
}
2628

2729
/// A chain policy. Decides what to do with a packet that was processed by the chain but did not
@@ -133,6 +135,17 @@ impl<'a> Chain<'a> {
133135
}
134136
}
135137

138+
/// Sets the device for this chain. This only applies if the chain has been registered with an `ingress` hook by calling `set_hook`.
139+
pub fn set_device<T: AsRef<CStr>>(&mut self, device: &T) {
140+
unsafe {
141+
sys::nftnl_chain_set_str(
142+
self.chain,
143+
sys::NFTNL_CHAIN_DEV as u16,
144+
device.as_ref().as_ptr(),
145+
);
146+
}
147+
}
148+
136149
/// Returns the name of this chain.
137150
pub fn get_name(&self) -> &CStr {
138151
unsafe {

0 commit comments

Comments
 (0)