Skip to content

Commit c19a317

Browse files
committed
Netavark: nftables support
Adds an nftables firewall backend and tests for said backend. Implements basic forwarding, port forwarding, and teardown for all relevant rules. Heavily based on our existing iptables driver but with a number of improvements (we live in a dedicated table, so this should play much more nicely with other tools using the firewall; IPv4 and IPv6 share a table and almost all code; and rule structure is a bit simpler because we do have our own table and don't have to worry about cluttering up the FORWARD chain, we'll the the only ones using it. This implementation presently does not support isolation; that will be added in a followon. Fixes #816 Signed-off-by: Matthew Heon <matthew.heon@pm.me>
1 parent 86ce937 commit c19a317

File tree

8 files changed

+2172
-69
lines changed

8 files changed

+2172
-69
lines changed

Cargo.lock

Lines changed: 44 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ sha2 = "0.10.8"
4949
netlink-packet-utils = "0.5.2"
5050
netlink-packet-route = "0.18.1"
5151
netlink-packet-core = "0.7.0"
52+
nftables = "0.2.4"
5253
fs2 = "0.4.3"
5354
netlink-sys = "0.8.5"
5455
tokio = { version = "1.35", features = ["rt", "rt-multi-thread", "signal", "fs"] }

src/error/mod.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ pub enum NetavarkError {
8181
DHCPProxy(tonic::Status),
8282

8383
List(NetavarkErrorList),
84+
85+
Nftables(nftables::helper::NftablesError),
86+
87+
SubnetParse(ipnet::AddrParseError),
88+
AddrParse(std::net::AddrParseError),
8489
}
8590

8691
/// Internal struct for JSON output
@@ -160,6 +165,9 @@ impl fmt::Display for NetavarkError {
160165
Ok(())
161166
}
162167
}
168+
NetavarkError::Nftables(e) => write!(f, "nftables error: {e}"),
169+
NetavarkError::SubnetParse(e) => write!(f, "parsing IP subnet error: {e}"),
170+
NetavarkError::AddrParse(e) => write!(f, "parsing IP address error: {e}"),
163171
}
164172
}
165173
}
@@ -213,3 +221,21 @@ impl From<tonic::Status> for NetavarkError {
213221
NetavarkError::DHCPProxy(err)
214222
}
215223
}
224+
225+
impl From<nftables::helper::NftablesError> for NetavarkError {
226+
fn from(err: nftables::helper::NftablesError) -> Self {
227+
NetavarkError::Nftables(err)
228+
}
229+
}
230+
231+
impl From<ipnet::AddrParseError> for NetavarkError {
232+
fn from(err: ipnet::AddrParseError) -> Self {
233+
NetavarkError::SubnetParse(err)
234+
}
235+
}
236+
237+
impl From<std::net::AddrParseError> for NetavarkError {
238+
fn from(err: std::net::AddrParseError) -> Self {
239+
NetavarkError::AddrParse(err)
240+
}
241+
}

src/firewall/firewalld.rs

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::network::internal_types;
44
use crate::network::internal_types::{PortForwardConfig, TearDownNetwork, TeardownPortForward};
55
use crate::network::types::PortMapping;
66
use core::convert::TryFrom;
7-
use log::{debug, info};
7+
use log::{debug, info, warn};
88
use std::collections::HashMap;
99
use std::vec::Vec;
1010
use zbus::{
@@ -679,3 +679,70 @@ fn make_port_tuple(port: &PortMapping, addr: &str) -> (String, String, String, S
679679
to_return
680680
}
681681
}
682+
683+
/// Check if firewalld is running.
684+
/// Not used within the firewalld driver, but by other drivers that may need to
685+
/// interact with firewalld.
686+
pub fn is_firewalld_running(conn: &Connection) -> bool {
687+
conn.call_method(
688+
Some("org.freedesktop.DBus"),
689+
"/org/freedesktop/DBus",
690+
Some("org.freedesktop.DBus"),
691+
"GetNameOwner",
692+
&"org.fedoraproject.FirewallD1",
693+
)
694+
.is_ok()
695+
}
696+
697+
/// If possible, add a firewalld rule to allow traffic.
698+
/// Ignore all errors, beyond possibly logging them.
699+
/// Not used within the firewalld driver, but by other drivers that may need to
700+
/// interact with firewalld.
701+
pub fn add_firewalld_if_possible(net: &ipnet::IpNet) {
702+
let conn = match Connection::system() {
703+
Ok(conn) => conn,
704+
Err(_) => return,
705+
};
706+
if !is_firewalld_running(&conn) {
707+
return;
708+
}
709+
debug!("Adding firewalld rules for network {}", net.to_string());
710+
711+
match add_source_subnets_to_zone(&conn, "trusted", &[*net]) {
712+
Ok(_) => {}
713+
Err(e) => warn!(
714+
"Error adding subnet {} from firewalld trusted zone: {}",
715+
net.to_string(),
716+
e
717+
),
718+
}
719+
}
720+
721+
/// If possible, remove a firewalld rule to allow traffic.
722+
/// Ignore all errors, beyond possibly logging them.
723+
/// Not used within the firewalld driver, but by other drivers that may need to
724+
/// interact with firewalld.
725+
pub fn rm_firewalld_if_possible(net: &ipnet::IpNet) {
726+
let conn = match Connection::system() {
727+
Ok(conn) => conn,
728+
Err(_) => return,
729+
};
730+
if !is_firewalld_running(&conn) {
731+
return;
732+
}
733+
debug!("Removing firewalld rules for IPs {}", net.to_string());
734+
match conn.call_method(
735+
Some("org.fedoraproject.FirewallD1"),
736+
"/org/fedoraproject/FirewallD1",
737+
Some("org.fedoraproject.FirewallD1.zone"),
738+
"removeSource",
739+
&("trusted", net.to_string()),
740+
) {
741+
Ok(_) => {}
742+
Err(e) => warn!(
743+
"Error removing subnet {} from firewalld trusted zone: {}",
744+
net.to_string(),
745+
e
746+
),
747+
};
748+
}

src/firewall/iptables.rs

Lines changed: 2 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ use crate::network::internal_types::{
1010
};
1111
use iptables;
1212
use iptables::IPTables;
13-
use log::{debug, warn};
14-
use zbus::blocking::Connection;
1513

1614
pub(crate) const MAX_HASH_SIZE: usize = 13;
1715

@@ -64,7 +62,7 @@ impl firewall::FirewallDriver for IptablesDriver {
6462

6563
create_network_chains(chains)?;
6664

67-
add_firewalld_if_possible(&network);
65+
firewalld::add_firewalld_if_possible(&network);
6866
}
6967
}
7068
Ok(())
@@ -106,7 +104,7 @@ impl firewall::FirewallDriver for IptablesDriver {
106104
}
107105

108106
if tear.complete_teardown {
109-
rm_firewalld_if_possible(&network)
107+
firewalld::rm_firewalld_if_possible(&network)
110108
}
111109
}
112110
}
@@ -216,64 +214,3 @@ impl firewall::FirewallDriver for IptablesDriver {
216214
Result::Ok(())
217215
}
218216
}
219-
220-
/// Check if firewalld is running
221-
fn is_firewalld_running(conn: &Connection) -> bool {
222-
conn.call_method(
223-
Some("org.freedesktop.DBus"),
224-
"/org/freedesktop/DBus",
225-
Some("org.freedesktop.DBus"),
226-
"GetNameOwner",
227-
&"org.fedoraproject.FirewallD1",
228-
)
229-
.is_ok()
230-
}
231-
232-
/// If possible, add a firewalld rule to allow traffic.
233-
/// Ignore all errors, beyond possibly logging them.
234-
fn add_firewalld_if_possible(net: &ipnet::IpNet) {
235-
let conn = match Connection::system() {
236-
Ok(conn) => conn,
237-
Err(_) => return,
238-
};
239-
if !is_firewalld_running(&conn) {
240-
return;
241-
}
242-
debug!("Adding firewalld rules for network {}", net.to_string());
243-
244-
match firewalld::add_source_subnets_to_zone(&conn, "trusted", &[*net]) {
245-
Ok(_) => {}
246-
Err(e) => warn!(
247-
"Error adding subnet {} from firewalld trusted zone: {}",
248-
net.to_string(),
249-
e
250-
),
251-
}
252-
}
253-
254-
// If possible, remove a firewalld rule to allow traffic.
255-
// Ignore all errors, beyond possibly logging them.
256-
fn rm_firewalld_if_possible(net: &ipnet::IpNet) {
257-
let conn = match Connection::system() {
258-
Ok(conn) => conn,
259-
Err(_) => return,
260-
};
261-
if !is_firewalld_running(&conn) {
262-
return;
263-
}
264-
debug!("Removing firewalld rules for IPs {}", net.to_string());
265-
match conn.call_method(
266-
Some("org.fedoraproject.FirewallD1"),
267-
"/org/fedoraproject/FirewallD1",
268-
Some("org.fedoraproject.FirewallD1.zone"),
269-
"removeSource",
270-
&("trusted", net.to_string()),
271-
) {
272-
Ok(_) => {}
273-
Err(e) => warn!(
274-
"Error removing subnet {} from firewalld trusted zone: {}",
275-
net.to_string(),
276-
e
277-
),
278-
};
279-
}

src/firewall/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use zbus::blocking::Connection;
88
pub mod firewalld;
99
pub mod fwnone;
1010
pub mod iptables;
11+
pub mod nft;
1112
pub mod state;
1213
mod varktables;
1314

@@ -108,9 +109,7 @@ pub fn get_supported_firewall_driver(
108109
}
109110
FirewallImpl::Nftables => {
110111
info!("Using nftables firewall driver");
111-
Err(NetavarkError::msg(
112-
"nftables support presently not available",
113-
))
112+
nft::new()
114113
}
115114
FirewallImpl::Fwnone => {
116115
info!("Not using firewall");

0 commit comments

Comments
 (0)