Skip to content

Soft nftables reload support#108

Open
IsoLeyN wants to merge 2 commits into
mossdef-org:1.2.3from
IsoLeyN:1.2.3
Open

Soft nftables reload support#108
IsoLeyN wants to merge 2 commits into
mossdef-org:1.2.3from
IsoLeyN:1.2.3

Conversation

@IsoLeyN
Copy link
Copy Markdown

@IsoLeyN IsoLeyN commented May 14, 2026

Right now, PBR reloads all tables from scratch, which kills the internet connection for a few seconds. Added a soft reload feature to keep things running smoothly during restarts.

@IsoLeyN IsoLeyN marked this pull request as draft May 14, 2026 21:43
@IsoLeyN IsoLeyN marked this pull request as ready for review May 14, 2026 22:51
@IsoLeyN IsoLeyN marked this pull request as draft May 14, 2026 23:20
@IsoLeyN IsoLeyN marked this pull request as ready for review May 15, 2026 00:38
@stangri stangri requested a review from Copilot May 15, 2026 01:05
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in "soft reload" mode that, when a previous pbr ruleset is already installed, applies only the dynamic portion of the generated nft script to the live ruleset and rewrites pkg.nft_main_file without invoking fw4 -q reload, avoiding the brief connectivity gap caused by a full fw4 reload. The structural prefix of the generated nft script is now tracked via nft_structural_end, a new pbr_chains cleanup action flushes only the pbr-prefixed chains, and the sets cleanup case is reworked (incidentally fixing a pre-existing split-delimiter bug). A new nft_soft_reload boolean is added to the config schema and the default UCI file (off by default).

Changes:

  • Track a structural-section boundary in the generated nft script and add a soft-apply branch in nft.install() gated by cfg.nft_soft_reload and presence of a previously installed jump.
  • Add pbr_chains cleanup case and rewrite sets cleanup to flush+delete using space-split parsing; wire soft-reload cleanup actions in pbr.uc.
  • Add nft_soft_reload boolean to config.uc schema and default files/etc/config/pbr (default 0).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
files/lib/pbr/nft.uc Adds soft-reload install path, structural-end marker, pbr_chains cleanup, reworks sets cleanup; significant duplication and a few correctness concerns.
files/lib/pbr/pbr.uc Branches the reload cleanup action list on cfg.nft_soft_reload.
files/lib/pbr/config.uc Declares the new nft_soft_reload boolean option (default false).
files/etc/config/pbr Adds option nft_soft_reload '0' to the default config.
Comments suppressed due to low confidence (2)

files/lib/pbr/nft.uc:704

  • In soft-reload mode, cleanup('...', 'sets') still falls through to the case 'sets': block, which performs both a flush and a delete on every pbr set. Deleting sets defeats the purpose of soft reload: rules currently loaded that reference a set will either prevent the delete (leaving stale sets that then conflict with add set ... in the new ruleset) or, if delete succeeds, the references in the still-installed jumps break briefly until the soft-applied ruleset re-adds them. Soft reload should only flush set contents (so they can be repopulated by the new ruleset) and skip the delete step. Consider introducing a separate cleanup action (e.g. sets_flush) used by the soft path, or branching on cfg.nft_soft_reload inside the case.
			case 'sets': {
				let nft_sets_str = get_nft_sets();
				if (nft_sets_str) {
					let sets_list = split(nft_sets_str, ' ');
					for (let s in sets_list) {
						s = trim(s);
						if (s)
							nft_call('flush', 'set', 'inet', pkg.nft_table, s);
					}
					for (let s in sets_list) {
						s = trim(s);
						if (s)
							nft_call('delete', 'set', 'inet', pkg.nft_table, s);
					}
				}
				break;
			}

files/lib/pbr/nft.uc:310

  • In the soft-reload path, on success only output.okn() is called — output.info/output.verbose are no longer told this was a soft (live) apply versus a full reload. For an operations feature that explicitly changes behavior in a non-obvious way, please surface the distinction (e.g. log "soft" vs "full" reload) so support diagnostics can tell which code path executed without enabling debug instrumentation.
					if (nft_call('-f', pkg.nft_temp_file)) {
						writefile(pkg.nft_main_file, full_content);
						output.okn();
						return true;
					} else {
						push(state.errors, { code: 'errorNftMainFileInstall', info: pkg.nft_temp_file });
						output.failn();
						return false;
					}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread files/lib/pbr/nft.uc Outdated
let nft_sets_str = get_nft_sets();
if (nft_sets_str) {
let sets_list = split(nft_sets_str, '\n');
let sets_list = split(nft_sets_str, ' ');
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate this fix and I'd be much obliged if you submit this on its own.

Comment thread files/lib/pbr/nft.uc
Comment on lines +297 to +332
if (cfg.nft_soft_reload && nft_structural_end > 0) {
let chain_exists = sh.run('nft list chain inet ' + pkg.nft_table + ' mangle_prerouting 2>/dev/null | grep -q ' + sh.quote('jump ' + pkg.nft_prefix + '_prerouting')) == 0;
if (chain_exists) {
let soft_lines = slice(nft_lines, nft_structural_end);
writefile(pkg.nft_temp_file, '#!/usr/sbin/nft -f\n\n' + join('\n', soft_lines) + '\n');
if (nft_call('-f', pkg.nft_temp_file)) {
writefile(pkg.nft_main_file, full_content);
output.okn();
return true;
} else {
push(state.errors, { code: 'errorNftMainFileInstall', info: pkg.nft_temp_file });
output.failn();
return false;
}
} else {
if (sh.run('cp -f ' + sh.quote(pkg.nft_temp_file) + ' ' + sh.quote(pkg.nft_main_file)) == 0) {
output.okn();
sh.run('fw4 -q reload');
return true;
} else {
push(state.errors, { code: 'errorNftMainFileInstall', info: pkg.nft_temp_file });
output.failn();
return false;
}
}
} else {
if (sh.run('cp -f ' + sh.quote(pkg.nft_temp_file) + ' ' + sh.quote(pkg.nft_main_file)) == 0) {
output.okn();
sh.run('fw4 -q reload');
return true;
} else {
push(state.errors, { code: 'errorNftMainFileInstall', info: pkg.nft_temp_file });
output.failn();
return false;
}
}
Comment thread files/lib/pbr/nft.uc
return false;
}
if (cfg.nft_soft_reload && nft_structural_end > 0) {
let chain_exists = sh.run('nft list chain inet ' + pkg.nft_table + ' mangle_prerouting 2>/dev/null | grep -q ' + sh.quote('jump ' + pkg.nft_prefix + '_prerouting')) == 0;
Comment thread files/lib/pbr/nft.uc
Comment on lines +302 to +305
if (nft_call('-f', pkg.nft_temp_file)) {
writefile(pkg.nft_main_file, full_content);
output.okn();
return true;
Comment thread files/lib/pbr/config.uc
nft_set_counter: ['bool', false],
nft_set_flags_timeout: ['bool', false],
nft_user_set_counter: ['bool', false],
netifd_enabled: ['bool', false],
Comment thread files/lib/pbr/nft.uc
push(nft_lines, 'add rule inet ' + nft_table + ' mangle_output jump ' + nft_prefix + '_output');
push(nft_lines, 'add rule inet ' + nft_table + ' mangle_forward jump ' + nft_prefix + '_forward');
push(nft_lines, '');
nft_structural_end = length(nft_lines);
@stangri
Copy link
Copy Markdown
Collaborator

stangri commented May 15, 2026

What's the use case for soft reload?

@IsoLeyN
Copy link
Copy Markdown
Author

IsoLeyN commented May 15, 2026

Run the “ping 1.1.1.1” command on a device connected to a router with PBR. Then restart the PBR. You will see that packet transmission is interrupted for a moment.

The same thing happens when you add or modify any rules. If you add another IP address to the list, the entire table will be regenerated, causing the internet connection to drop again. This is particularly critical for games and servers.

@stangri
Copy link
Copy Markdown
Collaborator

stangri commented May 15, 2026

Use pbr in netifd/mwan4 mode and/or disable strict_enforcement (to prevent forwarding.disable() from running) if you want to reduce connection drops while operating pbr. Otherwise, it is working as intended that reload/start/restart in the dynamic routing tables mode cause connection to temporarily drop.

Please let me know if you'd want to create PR for the nftset.cleanup() function fix separately.

@IsoLeyN
Copy link
Copy Markdown
Author

IsoLeyN commented May 15, 2026

Added nftset.cleanup(new_sets) that only removes sets no longer needed. Unchanged sets are left alone, avoiding the flush/delete/recreate cycle that disrupts connections.

Why this is better

Instead of switching modes or disabling safety features:

Solution Works? Functionality Security Migration
Switch to netifd/mwan4 Sorta
Disable strict_enforcement ⚠️
nftset.cleanup()
  • No need to change your routing mode
  • No need to disable safety checks
  • Works with your existing setup
  • Only touches sets that actually changed

Backward compatible, cleanup('sets') still works the same.

@egc112
Copy link
Copy Markdown
Collaborator

egc112 commented May 15, 2026

I think your problem might be caused by the forwarding which is disabled on reload of interfaces and is a safety measure to make sure there are no leaks.
Disabling strict enforcement will also disable this safety measure so should be a solution for your problem

@IsoLeyN
Copy link
Copy Markdown
Author

IsoLeyN commented May 15, 2026

I already have strict enforcement disabled, but that doesn't help. The internet still goes down for a few seconds while the tables are being generated. My nftset.cleanup() method addresses this by only updating sets that have actually changed, which should reduce the reload time and minimize disruption.

@egc112
Copy link
Copy Markdown
Collaborator

egc112 commented May 15, 2026

I did a test my self and did not experience any loss of connection when a non related interfaces goes up and down if strict enforcement is disabled.

PBR 1.2.3-61
Default route is via the WG interface.
Lan clients are routed with PBR via the WAN.

If strict enforcement is enabled and wg interface disconnection is simulated with ifup wginterface a brief disconnection is apparent, but without strict enforcement there is no disconnection any more

@IsoLeyN
Copy link
Copy Markdown
Author

IsoLeyN commented May 15, 2026

@IsoLeyN IsoLeyN changed the title Support soft reload Soft nftables reload support May 16, 2026
@IsoLeyN
Copy link
Copy Markdown
Author

IsoLeyN commented May 16, 2026

I fixed the issue with gateway detection. Now I think my pull request is ready for full review.

Earlier, I shared a link to a YouTube video demonstrating how PBR works without "Soft nftables," and you can see there that using strict_enforcement doesn't cause the internet to go down.

While I was writing the code, I also noticed issue #109, which had already been fixed. I've updated my code to match PR #109.

@IsoLeyN IsoLeyN reopened this May 16, 2026
@IsoLeyN IsoLeyN marked this pull request as ready for review May 16, 2026 08:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants