1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
//! Adds a table, chain and a rule that blocks all traffic to a given MAC address
//!
//! Run the following to print out current active tables, chains and rules in netfilter. Must be
//! executed as root:
//! ```bash
//! # nft list ruleset
//! ```
//! After running this example, the output should be the following:
//! ```ignore
//! table inet example-filter-ethernet {
//! chain chain-for-outgoing-packets {
//! type filter hook output priority 3; policy accept;
//! ether daddr 00:00:00:00:00:00 drop
//! counter packets 0 bytes 0 meta random > 2147483647 counter packets 0 bytes 0
//! }
//! }
//! ```
//!
//!
//! Everything created by this example can be removed by running
//! ```bash
//! # nft delete table inet example-filter-ethernet
//! ```
use nftnl::{nft_expr, nftnl_sys::libc, Batch, Chain, FinalizedBatch, ProtoFamily, Rule, Table};
use std::{ffi::CString, io};
const TABLE_NAME: &str = "example-filter-ethernet";
const OUT_CHAIN_NAME: &str = "chain-for-outgoing-packets";
const BLOCK_THIS_MAC: &[u8] = &[0, 0, 0, 0, 0, 0];
fn main() -> Result<(), Error> {
// For verbose explanations of what all these lines up until the rule creation does, see the
// `add-rules` example.
let mut batch = Batch::new();
let table = Table::new(&CString::new(TABLE_NAME).unwrap(), ProtoFamily::Inet);
batch.add(&table, nftnl::MsgType::Add);
let mut out_chain = Chain::new(&CString::new(OUT_CHAIN_NAME).unwrap(), &table);
out_chain.set_hook(nftnl::Hook::Out, 3);
out_chain.set_policy(nftnl::Policy::Accept);
batch.add(&out_chain, nftnl::MsgType::Add);
// === ADD RULE DROPPING ALL TRAFFIC TO THE MAC ADDRESS IN `BLOCK_THIS_MAC` ===
let mut block_ethernet_rule = Rule::new(&out_chain);
// Check that the interface type is an ethernet interface. Must be done before we can check
// payload values in the ethernet header.
block_ethernet_rule.add_expr(&nft_expr!(meta iiftype));
block_ethernet_rule.add_expr(&nft_expr!(cmp == libc::ARPHRD_ETHER));
// Compare the ethernet destination address against the MAC address we want to drop
block_ethernet_rule.add_expr(&nft_expr!(payload ethernet daddr));
block_ethernet_rule.add_expr(&nft_expr!(cmp == BLOCK_THIS_MAC));
// Drop the matching packets.
block_ethernet_rule.add_expr(&nft_expr!(verdict drop));
batch.add(&block_ethernet_rule, nftnl::MsgType::Add);
// === FOR FUN, ADD A PACKET THAT MATCHES 50% OF ALL PACKETS ===
// This packet has a counter before and after the check that has 50% chance of matching.
// So after a number of packets has passed through this rule, the first counter should have a
// value approximately double that of the second counter. This rule has no verdict, so it never
// does anything with the matching packets.
let mut random_rule = Rule::new(&out_chain);
// This counter expression will be evaluated (and increment the counter) for all packets coming
// through.
random_rule.add_expr(&nft_expr!(counter));
// Load a pseudo-random 32 bit unsigned integer into the netfilter register.
random_rule.add_expr(&nft_expr!(meta random));
// Check if the random integer is larger than `u32::MAX/2`, thus having 50% chance of success.
random_rule.add_expr(&nft_expr!(cmp > (::std::u32::MAX / 2).to_be()));
// Add a second counter. This will only be incremented for the packets passing the random check.
random_rule.add_expr(&nft_expr!(counter));
batch.add(&random_rule, nftnl::MsgType::Add);
// === FINALIZE THE TRANSACTION AND SEND THE DATA TO NETFILTER ===
let finalized_batch = batch.finalize();
send_and_process(&finalized_batch)?;
Ok(())
}
fn send_and_process(batch: &FinalizedBatch) -> Result<(), Error> {
// Create a netlink socket to netfilter.
let socket = mnl::Socket::new(mnl::Bus::Netfilter)?;
// Send all the bytes in the batch.
socket.send_all(batch)?;
// Try to parse the messages coming back from netfilter. This part is still very unclear.
let portid = socket.portid();
let mut buffer = vec![0; nftnl::nft_nlmsg_maxsize() as usize];
let very_unclear_what_this_is_for = 2;
while let Some(message) = socket_recv(&socket, &mut buffer[..])? {
match mnl::cb_run(message, very_unclear_what_this_is_for, portid)? {
mnl::CbResult::Stop => {
break;
}
mnl::CbResult::Ok => (),
}
}
Ok(())
}
fn socket_recv<'a>(socket: &mnl::Socket, buf: &'a mut [u8]) -> Result<Option<&'a [u8]>, Error> {
let ret = socket.recv(buf)?;
if ret > 0 {
Ok(Some(&buf[..ret]))
} else {
Ok(None)
}
}
#[derive(Debug)]
struct Error(String);
impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
Error(error.to_string())
}
}
|