← Start of tutorial

WIUNIX ACADEMY

Atomic nftables changes: rehearse timeout rollback before risking remote access

Page 2 of 4. Review the earlier steps and prerequisites before continuing.

YOUR SYSTEM. YOUR STEPS.

Where are you working?

Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.

Red Hat Enterprise Linux 9Reviewed against official documentation ·

USE YOUR OWN VALUES

Configure this tutorial

Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.

Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.

Set values for my environment8 editable values
05

LET’S BUILD

Reserve the baseline and intentionally bad proposal files

Create two new root-only files in the exclusive evidence directory and open them with sudoedit. Paste each following panel into its matching file. Existing files cause refusal; if the second reservation fails, the first empty file can remain. Resolve that exact collision before continuing.

These files are lab inputs, not host startup configuration. Do not place either into a distribution firewall include directory or attach it to a production boot service.

bash

Terminal on the disposable lab VM

sudo sh -c 'set -euC; : > "$1/baseline.nft"; : > "$1/candidate.nft"' sh '/var/lib/academy-nft-rollback-lab' || exit 1
sudo chmod 0600 '/var/lib/academy-nft-rollback-lab/baseline.nft' '/var/lib/academy-nft-rollback-lab/candidate.nft' || exit 1
sudoedit '/var/lib/academy-nft-rollback-lab/baseline.nft' '/var/lib/academy-nft-rollback-lab/candidate.nft' || exit 1
How do I know it worked?

Two newly reserved files are ready for the following baseline and candidate content.

Permit only the isolated client’s echo request

The baseline creates one inet input chain with a drop policy and permits loopback and the chosen client’s ICMP echo requests. Replies leave through the namespace’s otherwise unfiltered output path. This intentionally tiny IPv4 experiment is not a complete IPv4/IPv6, DNS or SSH policy.

The table name is quoted so an accepted public identifier is not interpreted as nft syntax. On an actual host, hook priority, other chains and manager-owned policy would also need review; our fresh namespace keeps that scope explicit.

nft · /var/lib/academy-nft-rollback-lab/baseline.nft

File contents — not a shell command

table inet "academy_guard" {
    chain input {
        type filter hook input priority 0; policy drop;
        iifname "lo" accept
        ip saddr 192.0.2.2 icmp type echo-request counter accept
    }
}

Make a syntactically valid proposal that blocks the probe

The proposal removes and recreates only the lab table in one native nft batch. Its explicit drop rule replaces the baseline echo-request allow. Both table operations commit together when this file is applied; a shell loop of separate deletion and addition commands would create a different, inconsistent transition.

A check of this file can succeed even though the intended reachability is broken. We use that deliberate mistake to test recovery rather than pretending parser acceptance is a connectivity test.

nft · /var/lib/academy-nft-rollback-lab/candidate.nft

File contents — not a shell command

delete table inet "academy_guard"
table inet "academy_guard" {
    chain input {
        type filter hook input priority 0; policy drop;
        iifname "lo" accept
        ip saddr 192.0.2.2 icmp type echo-request counter drop
    }
}
06

LET’S BUILD

Check and load the baseline inside the server namespace

The first invocation asks the kernel/userspace stack to validate the batch without applying it. The second applies it to the explicitly named namespace. Confirm that the same isolated client can still reach the server and inspect this table’s counter.

Do not omit the namespace wrapper or replace the target with the host ruleset. If the baseline probe fails, stop here: there is no known-good connectivity state to restore yet.

bash

Terminal on the disposable lab VM

sudo ip netns exec 'academy-nft-server' nft -c -f '/var/lib/academy-nft-rollback-lab/baseline.nft' || exit 1
sudo ip netns exec 'academy-nft-server' nft -f '/var/lib/academy-nft-rollback-lab/baseline.nft' || exit 1
sudo ip netns exec 'academy-nft-client' ping -c 2 -W 2 '192.0.2.1' || exit 1
sudo ip netns exec 'academy-nft-server' nft -n list table inet '"academy_guard"'
How do I know it worked?

The baseline table is present and the isolated probe still succeeds.

07

LET’S BUILD

Capture a table-scoped restore and check both batches

Build a root-only rollback file from this table’s actual baseline. It first deletes the table, then recreates the captured policy in the same nft transaction. A plain listing is not an exclusive replacement: loading it into existing objects can merge or duplicate configuration instead of undoing a change.

The stateless listing deliberately omits live counter state. It is a policy snapshot, not a backup of connection tracking, dynamic external manager state or application sessions. This fixture has no named timeout sets or dependent cross-table objects. A stale snapshot on a managed host could undo someone else’s later changes; that is why this restore owns one isolated table and no one edits it concurrently.

bash

Terminal on the disposable lab VM

sudo sh -c 'set -euC
out="$1/rollback.nft"
printf "delete table inet \"%s\"\n" "$3" > "$out"
ip netns exec "$2" nft -s -n list table inet "\"$3\"" >> "$out"
chmod 0600 "$out"
' sh '/var/lib/academy-nft-rollback-lab' 'academy-nft-server' 'academy_guard' || exit 1
sudo ip netns exec 'academy-nft-server' nft -c -f '/var/lib/academy-nft-rollback-lab/rollback.nft' || exit 1
sudo ip netns exec 'academy-nft-server' nft -c -f '/var/lib/academy-nft-rollback-lab/candidate.nft' || exit 1
How do I know it worked?

A complete table-scoped rollback file and candidate both pass the namespace check. A failed capture/check stops the experiment.

08

LET’S BUILD

Prove the detached timer can execute the known-good restore

Schedule a separate short rehearsal timer through the system service manager. Its command takes the same local file lock and restores the unchanged baseline in the namespace. It is not a background sleep tied to this terminal. Wait for the explicit success result and repeat the probe before arming the actual change.

If the VM is heavily delayed, the four-second observation may arrive before completion; inspect the named timer/service and wait for its real result rather than calling it a pass. RemainAfterExit keeps the successful oneshot active in exited state for inspection until cleanup; this one-shot timer is not a recurring job.

bash

Terminal on the disposable lab VM

ip_bin=$(sudo sh -c 'command -v ip') || exit 1
nft_bin=$(sudo sh -c 'command -v nft') || exit 1
flock_bin=$(sudo sh -c 'command -v flock') || exit 1
sudo systemd-run --unit='academy-nft-rollback-rehearsal' --on-active=2s --timer-property=AccuracySec=1s --property=Type=oneshot --property=RemainAfterExit=yes "$flock_bin" -x '/var/lib/academy-nft-rollback-lab/change.lock' "$ip_bin" netns exec 'academy-nft-server' "$nft_bin" -f '/var/lib/academy-nft-rollback-lab/rollback.nft' || exit 1
sleep 4
test "$(systemctl show -p ActiveState --value 'academy-nft-rollback-rehearsal.service')" = active || exit 1
test "$(systemctl show -p SubState --value 'academy-nft-rollback-rehearsal.service')" = exited || exit 1
test "$(systemctl show -p Result --value 'academy-nft-rollback-rehearsal.service')" = success || exit 1
test "$(systemctl show -p ExecMainStatus --value 'academy-nft-rollback-rehearsal.service')" = 0 || exit 1
sudo journalctl -u 'academy-nft-rollback-rehearsal.service' --no-pager -n 30
sudo ip netns exec 'academy-nft-client' ping -c 2 -W 2 '192.0.2.1' || exit 1
How do I know it worked?

The rehearsal service has Result=success and exit status zero, and the isolated client still reaches the server.

Explore all tutorials ↗