-
Notifications
You must be signed in to change notification settings - Fork 28
/
Copy pathadd-rules.rs
216 lines (183 loc) · 10.1 KB
/
add-rules.rs
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
//! Adds a table, two chains and some rules to netfilter.
//!
//! This example uses `verdict accept` everywhere. So even after running this the firewall won't
//! block anything. This is so anyone trying to run this does not end up in a strange state
//! where they don't understand why their network is broken. Try changing to `verdict drop` if
//! you want to see the block working.
//!
//! 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-table {
//! chain chain-for-outgoing-packets {
//! type filter hook output priority 0; policy accept;
//! ip daddr 10.1.0.0/24 counter packets 0 bytes 0 accept
//! }
//!
//! chain chain-for-incoming-packets {
//! type filter hook input priority 0; policy accept;
//! iif "lo" accept
//! }
//! }
//! ```
//!
//! Try pinging any IP in the network range denoted by the outgoing rule and see the counter
//! increment:
//! ```bash
//! $ ping 10.1.0.7
//! ```
//!
//! Everything created by this example can be removed by running
//! ```bash
//! # nft delete table inet example-table
//! ```
use ipnetwork::{IpNetwork, Ipv4Network};
use nftnl::{nft_expr, nftnl_sys::libc, Batch, Chain, FinalizedBatch, ProtoFamily, Rule, Table};
use std::{ffi::CString, io, net::Ipv4Addr};
const TABLE_NAME: &str = "example-table";
const OUT_CHAIN_NAME: &str = "chain-for-outgoing-packets";
const IN_CHAIN_NAME: &str = "chain-for-incoming-packets";
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a batch. This is used to store all the netlink messages we will later send.
// Creating a new batch also automatically writes the initial batch begin message needed
// to tell netlink this is a single transaction that might arrive over multiple netlink packets.
let mut batch = Batch::new();
// Create a netfilter table operating on both IPv4 and IPv6 (ProtoFamily::Inet)
let table = Table::new(&CString::new(TABLE_NAME).unwrap(), ProtoFamily::Inet);
// Add the table to the batch with the `MsgType::Add` type, thus instructing netfilter to add
// this table under its `ProtoFamily::Inet` ruleset.
batch.add(&table, nftnl::MsgType::Add);
// Create input and output chains under the table we created above.
let mut out_chain = Chain::new(&CString::new(OUT_CHAIN_NAME).unwrap(), &table);
let mut in_chain = Chain::new(&CString::new(IN_CHAIN_NAME).unwrap(), &table);
// Hook the chains to the input and output event hooks, with highest priority (priority zero).
// See the `Chain::set_hook` documentation for details.
out_chain.set_hook(nftnl::Hook::Out, 0);
in_chain.set_hook(nftnl::Hook::In, 0);
// Set the default policies on the chains. If no rule matches a packet processed by the
// `out_chain` or the `in_chain` it will accept the packet.
out_chain.set_policy(nftnl::Policy::Accept);
in_chain.set_policy(nftnl::Policy::Accept);
// Add the two chains to the batch with the `MsgType` to tell netfilter to create the chains
// under the table.
batch.add(&out_chain, nftnl::MsgType::Add);
batch.add(&in_chain, nftnl::MsgType::Add);
// === ADD RULE ALLOWING ALL TRAFFIC TO THE LOOPBACK DEVICE ===
// Create a new rule object under the input chain.
let mut allow_loopback_in_rule = Rule::new(&in_chain);
// Lookup the interface index of the loopback interface.
let lo_iface_index = iface_index("lo")?;
// First expression to be evaluated in this rule is load the meta information "iif"
// (incoming interface index) into the comparison register of netfilter.
// When an incoming network packet is processed by this rule it will first be processed by this
// expression, which will load the interface index of the interface the packet came from into
// a special "register" in netfilter.
allow_loopback_in_rule.add_expr(&nft_expr!(meta iif));
// Next expression in the rule is to compare the value loaded into the register with our desired
// interface index, and succeed only if it's equal. For any packet processed where the equality
// does not hold the packet is said to not match this rule, and the packet moves on to be
// processed by the next rule in the chain instead.
allow_loopback_in_rule.add_expr(&nft_expr!(cmp == lo_iface_index));
// Add a verdict expression to the rule. Any packet getting this far in the expression
// processing without failing any expression will be given the verdict added here.
allow_loopback_in_rule.add_expr(&nft_expr!(verdict accept));
// Add the rule to the batch.
batch.add(&allow_loopback_in_rule, nftnl::MsgType::Add);
// === ADD A RULE ALLOWING (AND COUNTING) ALL PACKETS TO THE 10.1.0.0/24 NETWORK ===
let mut block_out_to_private_net_rule = Rule::new(&out_chain);
let private_net_ip = Ipv4Addr::new(10, 1, 0, 0);
let private_net_prefix = 24;
let private_net = IpNetwork::V4(Ipv4Network::new(private_net_ip, private_net_prefix)?);
// Load the `nfproto` metadata into the netfilter register. This metadata denotes which layer3
// protocol the packet being processed is using.
block_out_to_private_net_rule.add_expr(&nft_expr!(meta nfproto));
// Check if the currently processed packet is an IPv4 packet. This must be done before payload
// data assuming the packet uses IPv4 can be loaded in the next expression.
block_out_to_private_net_rule.add_expr(&nft_expr!(cmp == libc::NFPROTO_IPV4 as u8));
// Load the IPv4 destination address into the netfilter register.
block_out_to_private_net_rule.add_expr(&nft_expr!(payload ipv4 daddr));
// Mask out the part of the destination address that is not part of the network bits. The result
// of this bitwise masking is stored back into the same netfilter register.
block_out_to_private_net_rule.add_expr(&nft_expr!(bitwise mask private_net.mask(), xor 0));
// Compare the result of the masking with the IP of the network we are interested in.
block_out_to_private_net_rule.add_expr(&nft_expr!(cmp == private_net.ip()));
// Add a packet counter to the rule. Shows how many packets have been evaluated against this
// expression. Since expressions are evaluated from first to last, putting this counter before
// the above IP net check would make the counter increment on all packets also *not* matching
// those expressions. Because the counter would then be evaluated before it fails a check.
// Similarly, if the counter was added after the verdict it would always remain at zero. Since
// when the packet hits the verdict expression any further processing of expressions stop.
block_out_to_private_net_rule.add_expr(&nft_expr!(counter));
// Accept all the packets matching the rule so far.
block_out_to_private_net_rule.add_expr(&nft_expr!(verdict accept));
// Add the rule to the batch. Without this nothing would be sent over netlink and netfilter,
// and all the work on `block_out_to_private_net_rule` so far would go to waste.
batch.add(&block_out_to_private_net_rule, nftnl::MsgType::Add);
// === ADD A RULE ALLOWING ALL OUTGOING ICMPv6 PACKETS WITH TYPE 133 AND CODE 0 ===
let mut allow_router_solicitation = Rule::new(&out_chain);
// Check that the packet is IPv6 and ICMPv6
allow_router_solicitation.add_expr(&nft_expr!(meta nfproto));
allow_router_solicitation.add_expr(&nft_expr!(cmp == libc::NFPROTO_IPV6 as u8));
allow_router_solicitation.add_expr(&nft_expr!(meta l4proto));
allow_router_solicitation.add_expr(&nft_expr!(cmp == libc::IPPROTO_ICMPV6 as u8));
allow_router_solicitation.add_expr(&nftnl::expr::Payload::Transport(
nftnl::expr::TransportHeaderField::Icmpv6(nftnl::expr::Icmpv6HeaderField::Type),
));
allow_router_solicitation.add_expr(&nft_expr!(cmp == 133u8));
allow_router_solicitation.add_expr(&nftnl::expr::Payload::Transport(
nftnl::expr::TransportHeaderField::Icmpv6(nftnl::expr::Icmpv6HeaderField::Code),
));
allow_router_solicitation.add_expr(&nft_expr!(cmp == 0u8));
allow_router_solicitation.add_expr(&nft_expr!(verdict accept));
batch.add(&allow_router_solicitation, nftnl::MsgType::Add);
// === FINALIZE THE TRANSACTION AND SEND THE DATA TO NETFILTER ===
// Finalize the batch. This means the batch end message is written into the batch, telling
// netfilter the we reached the end of the transaction message. It's also converted to a type
// that implements `IntoIterator<Item = &'a [u8]>`, thus allowing us to get the raw netlink data
// out so it can be sent over a netlink socket to netfilter.
let finalized_batch = batch.finalize();
// Send the entire batch and process any returned messages.
send_and_process(&finalized_batch)?;
Ok(())
}
// Look up the interface index for a given interface name.
fn iface_index(name: &str) -> io::Result<libc::c_uint> {
let c_name = CString::new(name).unwrap();
let index = unsafe { libc::if_nametoindex(c_name.as_ptr()) };
if index == 0 {
Err(io::Error::last_os_error())
} else {
Ok(index)
}
}
fn send_and_process(batch: &FinalizedBatch) -> io::Result<()> {
// 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]) -> io::Result<Option<&'a [u8]>> {
let ret = socket.recv(buf)?;
if ret > 0 {
Ok(Some(&buf[..ret]))
} else {
Ok(None)
}
}