Skip to content

Commit 05ecade

Browse files
committed
feat: Add firewall-reload command for nftables systems
On systems using nftables directly without firewalld, restarting the nftables.service would flush all of Netavark's rules, breaking container networking. This introduces a new "oneshot" command, `netavark firewall-reload`, which reads the container network state from /run/containers/netavark/ and re-applies all necessary firewall rules. To automate this, a new systemd service, `netavark-nftables-reload.service`, is added. This service is procedurally linked to `nftables.service` and triggers the reload command automatically whenever the main nftables service is started or reloaded. Fixes: #1258 Signed-off-by: Rishikpulhani <[email protected]>
1 parent 2cafebd commit 05ecade

File tree

8 files changed

+117
-4
lines changed

8 files changed

+117
-4
lines changed

.github/workflows/release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ jobs:
9393
VERSION: ${{needs.check.outputs.version}}
9494
steps:
9595
- name: Checkout Version
96-
uses: actions/checkout@v5
96+
uses: actions/checkout@v4
9797
with:
9898
ref: ${{needs.check.outputs.version}}
9999
- name: Get release notes
@@ -112,7 +112,7 @@ jobs:
112112
- name: Display release notes
113113
run: cat $VERSION-release-notes.md
114114
- name: Download all artifacts
115-
uses: actions/download-artifact@v5
115+
uses: actions/download-artifact@v4
116116
- name: Create release
117117
env:
118118
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -158,7 +158,7 @@ jobs:
158158
sudo apt-get update
159159
sudo apt-get -y install protobuf-compiler libprotobuf-dev
160160
- name: Checkout Version
161-
uses: actions/checkout@v5
161+
uses: actions/checkout@v4
162162
with:
163163
ref: ${{needs.check.outputs.version}}
164164
- name: Publish crate

Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ docs: ## build the docs on the host
9292
$(MAKE) -C docs
9393

9494
NV_UNIT_FILES = contrib/systemd/system/netavark-dhcp-proxy.service \
95-
contrib/systemd/system/netavark-firewalld-reload.service
95+
contrib/systemd/system/netavark-firewalld-reload.service \
96+
contrib/systemd/system/netavark-nftables-reload.service
9697

9798
%.service: %.service.in
9899
sed -e 's;@@NETAVARK@@;$(LIBEXECPODMAN)/netavark;g' $< >$@.tmp.$$ \
@@ -106,6 +107,7 @@ install: $(NV_UNIT_FILES)
106107
install ${SELINUXOPT} -m 644 contrib/systemd/system/netavark-dhcp-proxy.socket ${DESTDIR}${SYSTEMDDIR}/netavark-dhcp-proxy.socket
107108
install ${SELINUXOPT} -m 644 contrib/systemd/system/netavark-dhcp-proxy.service ${DESTDIR}${SYSTEMDDIR}/netavark-dhcp-proxy.service
108109
install ${SELINUXOPT} -m 644 contrib/systemd/system/netavark-firewalld-reload.service ${DESTDIR}${SYSTEMDDIR}/netavark-firewalld-reload.service
110+
install ${SELINUXOPT} -m 644 contrib/systemd/system/netavark-nftables-reload.service ${DESTDIR}${SYSTEMDDIR}/netavark-nftables-reload.service
109111

110112
.PHONY: uninstall
111113
uninstall:
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[Unit]
2+
Description=Restore Netavark firewall rules after nftables service is reloaded
3+
# This ensures our service is always started/restarted along with nftables.
4+
# It also ensures this service is stopped if nftables is stopped.
5+
PartOf=nftables.service
6+
# This ensures nftables has finished starting before we run.
7+
After=nftables.service
8+
9+
[Service]
10+
Type=oneshot
11+
# This uses the placeholder, which will be replaced by the Makefile during the build.
12+
ExecStart=@@NETAVARK@@ firewall-reload
13+
14+
[Install]
15+
# This tells systemd that this service should be enabled when nftables is enabled.
16+
WantedBy=nftables.service

rpm/netavark.spec

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,12 @@ cd docs
118118
%preun
119119
%systemd_preun %{name}-dhcp-proxy.service
120120
%systemd_preun %{name}-firewalld-reload.service
121+
%systemd_preun %{name}-nftables-reload.service
121122

122123
%postun
123124
%systemd_postun %{name}-dhcp-proxy.service
124125
%systemd_postun %{name}-firewalld-reload.service
126+
%systemd_postun %{name}-nftables-reload.service
125127

126128
%files
127129
%license LICENSE
@@ -136,6 +138,7 @@ cd docs
136138
%{_unitdir}/%{name}-dhcp-proxy.service
137139
%{_unitdir}/%{name}-dhcp-proxy.socket
138140
%{_unitdir}/%{name}-firewalld-reload.service
141+
%{_unitdir}/%{name}-nftables-reload.service
139142

140143
%changelog
141144
%autochangelog

src/commands/firewall_reload.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use std::{
2+
ffi::{OsStr, OsString},
3+
path::Path,
4+
};
5+
use zbus::{blocking::Connection};
6+
use crate::{
7+
error::{ErrorWrap, NetavarkResult},
8+
firewall::{get_supported_firewall_driver, state::read_fw_config},
9+
network::constants,
10+
};
11+
12+
pub fn firewall_reload(config_dir: Option<OsString>) -> NetavarkResult<()> {
13+
// Set the path to the directory where Podman stores the container network state.
14+
let config_dir = Path::new(
15+
config_dir
16+
.as_deref()
17+
.unwrap_or(OsStr::new(constants::DEFAULT_CONFIG_DIR)), // path to the config dir mainatined by podman
18+
);
19+
log::debug!("looking for firewall configs in {config_dir:?}");
20+
21+
let conn = Connection::system().ok();
22+
23+
reload_rules(config_dir,&conn)?;
24+
25+
Ok(())
26+
}
27+
28+
// This function is copied directly from firewalld_reload.rs.
29+
fn reload_rules(config_dir: &Path, conn: &Option<Connection>) -> NetavarkResult<()> {
30+
reload_rules_inner(config_dir,conn)?;
31+
Ok(())
32+
}
33+
34+
// This is the core logic, also copied directly.
35+
fn reload_rules_inner(config_dir: &Path, conn: &Option<Connection>) -> NetavarkResult<()> {
36+
// read_fw_config reads all the JSON files from `/run/containers/netavark/`
37+
let conf = read_fw_config(config_dir).wrap("read firewall config")?;
38+
39+
// If there are no config files, there are no running containers, so we do nothing.
40+
if let Some(conf) = conf {
41+
// Get the appropriate firewall driver
42+
let fw_driver = get_supported_firewall_driver(Some(conf.driver))?;
43+
44+
// Loop through each network configuration and restore its rules.
45+
for net in conf.net_confs {
46+
fw_driver.setup_network(net, conn)?;
47+
}
48+
// Loop through each container's port mappings and restore them.
49+
for port in &conf.port_confs {
50+
fw_driver.setup_port_forward(port.into(), conn)?;
51+
}
52+
log::info!("Successfully reloaded firewall rules");
53+
}
54+
55+
Ok(())
56+
}

src/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::ffi::OsString;
33
use crate::error::{NetavarkError, NetavarkResult};
44

55
pub mod dhcp_proxy;
6+
pub mod firewall_reload;
67
pub mod firewalld_reload;
78
pub mod setup;
89
pub mod teardown;

src/main.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::ffi::OsString;
33
use clap::{Parser, Subcommand};
44

55
use netavark::commands::dhcp_proxy;
6+
use netavark::commands::firewall_reload;
67
use netavark::commands::firewalld_reload;
78
use netavark::commands::setup;
89
use netavark::commands::teardown;
@@ -51,6 +52,9 @@ enum SubCommand {
5152
/// Listen for the firewalld reload event and reload fw rules
5253
#[command(name = "firewalld-reload")]
5354
FirewallDReload,
55+
// Re-applies firewall rules for all networks.
56+
#[command(name = "firewall-reload")]
57+
FirewallReload,
5458
}
5559

5660
fn main() {
@@ -84,6 +88,7 @@ fn main() {
8488
SubCommand::Version(version) => version.exec(),
8589
SubCommand::DHCPProxy(proxy) => dhcp_proxy::serve(proxy),
8690
SubCommand::FirewallDReload => firewalld_reload::listen(config),
91+
SubCommand::FirewallReload => firewall_reload::firewall_reload(config),
8792
};
8893

8994
match result {

test/250-bridge-nftables.bats

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,36 @@ net/ipv4/conf/podman1/rp_filter = 2"
985985
@test "$fw_driver - port forwarding ipv4 - tcp with firewalld reload" {
986986
test_port_fw firewalld_reload=true
987987
}
988+
@test "$fw_driver - test firewall-reload" {
989+
# setup a simple bridge network
990+
run_netavark --file ${TESTSDIR}/testfiles/simplebridge.json setup $(get_container_netns_path)
991+
992+
# verify the rules are there initially
993+
check_simple_bridge_nftables
994+
995+
# check that the firewall config files exist
996+
net_id=$(jq -r '.network_info.podman.id' < "${TESTSDIR}/testfiles/simplebridge.json")
997+
config_file="$NETAVARK_TMPDIR/config/firewall/networks/$net_id"
998+
run_helper test -f "$config_file"
999+
assert "$status" == "0" "network config file $config_file should exist"
1000+
# flush all nftables rules
1001+
run_in_host_netns nft flush ruleset
1002+
1003+
# verify the netavark table is gone
1004+
expected_rc=1 run_in_host_netns nft list table inet netavark
1005+
assert "$output" =~ "Error: No such file or directory" "netavark table should be gone"
1006+
1007+
# run firewall-reload to restore the rules
1008+
RUST_LOG=netavark=debug run_netavark firewall-reload
1009+
assert "$output" =~ "\[INFO netavark::commands::firewall_reload\] Successfully reloaded firewall rules" "firewall-reload success message"
1010+
1011+
# check that the rules are back
1012+
check_simple_bridge_nftables
1013+
1014+
# teardown the network
1015+
run_netavark --file ${TESTSDIR}/testfiles/simplebridge.json teardown $(get_container_netns_path)
1016+
}
1017+
9881018

9891019
function check_simple_bridge_nftables() {
9901020
# check nftables POSTROUTING chain

0 commit comments

Comments
 (0)