Repository navigation
Commit 644a640
nftables backend: router-shaped matches and staged atomic apply (#13)
* Read the nftables ruleset, and write only where it is ours
`nft -j list ruleset` is the source of truth: one JSON read, decoded into
tables, base chains with their hook and policy, rules whose expressions
become the columns a router rule list needs — verdict, interfaces,
protocol, addresses, ports, counter, comment — and named sets with the
number of rules that refer to each. An expression this package does not
model is rendered as text beside the columns rather than dropped, because
a rule shown half of is worse than a rule shown verbatim.
Writing is the narrow half. Every change is one nft invocation with an
argv built here and previewed by the same runner that executes it, and
the guard refuses anything outside a base chain with a policy or outside
the table this tool owns, with the sentence saying which of the two it
was and why it matters.
Selection learns the third backend. nftables is the answer for a machine
nothing else manages, so it is chosen last, and when the loaded ruleset
carries ufw's chains or firewalld's table the detector names that tool
instead and says so in the detail --check and --report now print.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCypLQF5AD8JD831Ea9S9q
* Test the reader against real nft output, and fuzz all four of them
Four fixtures, all of them bytes nft itself wrote. `nft -j list ruleset`
needs root and these tests do not have it, but a user namespace with a
network namespace of its own carries CAP_NET_ADMIN over that namespace's
empty ruleset — so `unshare -rn nft -f x.nft && nft -j -a list ruleset`
produces a real listing while touching nothing on the host. The .nft
source of each sits beside its JSON, so anyone can produce them again.
The fuzz targets found four things worth having found. A chain with no
hook could arrive carrying a policy, which is a promise the kernel is not
making and the mutation guard reads. A control character in a rule or in
a set element went straight into a table cell, where a newline breaks the
table rather than the cell. An expression with several fields lost all
but one of them. And a set name counted before sanitising was a reference
to something the user could not see. Their inputs are seeds now.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCypLQF5AD8JD831Ea9S9q
* Give nftables its own screens, and the manifest its version floor
A filter rule, an address translation and a named set are three different
things to read, and one set of columns is honest about none of them. So a
group now names the layout it wants: the rule table gains the interface,
port and counter columns when the backend fills them — which is the
OPNsense reading of a rule, and which ufw and firewalld would only answer
with dashes — while NAT gets the translation target up front and the
aliases get their member count and, more to the point, the number of
rules that would break if one went away.
v opens the group picker, which the update loop could already answer and
nothing had ever asked. Nothing else changed keys. The three keys
nftables cannot honour stop being advertised on it, because a key that
only ever says "not supported here" is worse than no key.
The manifest floor is 0.9 and the reason is in the notes beside it: that
is where nft grew `-j` and stamped the schema this backend reads. Below
it there is no ruleset to read in any form this tool will parse, and
parsing nft's human output is not a thing it will do.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCypLQF5AD8JD831Ea9S9q
* Smoke the nftables read path against the machine's own ruleset
The other two arms of the smoke test count what the firewall itself
reports and compare. This one does the same with `nft list ruleset`: the
base chains, the sets, the version that printed the JSON. A tool that
fetched the output and failed to parse it reports zero, and a tool that
misread a hook reports the wrong number; both fail here.
It also stops claiming the NAT and alias views are writable on a machine
with no table of ours. They are not: everything they create lives in that
table, and the refusal now points at the action that creates it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCypLQF5AD8JD831Ea9S9q
* nftables phase 2: router-shaped matches and a staged atomic apply
Close the seven UI gaps the phase-1 lab run surfaced, and add the
connectivity-safe batch apply.
Filter rules gain interface (iifname/oifname), connection-state
(ct state established,related) and ICMP (ip protocol icmp / ip6 nexthdr
ipv6-icmp, with an optional type) matches, each built for the table's
family. Masquerade takes an optional source network. A rule that matches
an alias reads its family off the set's element type instead of demanding
the Family field. The actions menu can delete a chain of the tool's own
table and drop the whole table, guarded so it never removes someone
else's. Pressing a in the NAT and alias views opens the actions those
views add through instead of a refusal.
Staging (internal/nftables/staging) collects mutations into a pending
set, applies them as one `nft -f` transaction — all-or-nothing — after a
single preview and confirm, snapshots the ruleset first, and rolls it
back on its own if the operator does not confirm they still have access
within a keep window (default 60s). Driven from the TUI with s/S/k, and
reported by --check and --report.
Covered by table-driven tests, staging unit tests with a hand-driven
timer, two new fuzz targets plus ct-state/icmp parser seeds (which caught
a whitespace-only ct state), and a real-nft run in a `unshare -rn`
namespace that applies a forward policy drop with its accept atomically
and restores the snapshot on a missed keep.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCypLQF5AD8JD831Ea9S9q
* Document the nftables backend and render its screens
Add the six nftables --demo frames the README now references — the flow
rule list, the NAT and alias views, the phase-2 add form, and the staged
atomic apply with its transaction preview — and wire them into the
screenshots target and the manifest.
Write the nftables section: the third backend and when it is picked, the
router reading of a rule (interfaces, connection state, counter), the NAT
and alias views, where it will and will not write, and the staged atomic
apply with its connectivity-safe rollback. Note the router-topology lab
coverage where the compat table still shows no tested version.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCypLQF5AD8JD831Ea9S9q
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>1 parent ac88f41 commit 644a640
60 files changed
Lines changed: 10527 additions & 123 deletions
File tree
- cmd/tui-firewall
- docs/screenshots
- internal
- backends
- firewalld
- firewall
- nftables
- staging
- testdata
- fuzz
- FuzzBuildRuleCommand
- FuzzDecodeExprs
- FuzzParseRuleset
- FuzzRenderElements
- ufw
- test
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
70 | 70 | | |
71 | 71 | | |
72 | 72 | | |
73 | | - | |
| 73 | + | |
74 | 74 | | |
75 | 75 | | |
76 | 76 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
62 | 62 | | |
63 | 63 | | |
64 | 64 | | |
65 | | - | |
66 | | - | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
67 | 69 | | |
68 | 70 | | |
69 | 71 | | |
| |||
73 | 75 | | |
74 | 76 | | |
75 | 77 | | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
76 | 85 | | |
77 | 86 | | |
78 | 87 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
11 | | - | |
12 | | - | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
13 | 13 | | |
14 | | - | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
20 | 22 | | |
21 | 23 | | |
22 | 24 | | |
| |||
185 | 187 | | |
186 | 188 | | |
187 | 189 | | |
| 190 | + | |
188 | 191 | | |
189 | 192 | | |
190 | 193 | | |
191 | 194 | | |
192 | | - | |
193 | | - | |
194 | | - | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
195 | 200 | | |
196 | 201 | | |
197 | 202 | | |
| |||
207 | 212 | | |
208 | 213 | | |
209 | 214 | | |
| 215 | + | |
210 | 216 | | |
211 | 217 | | |
212 | 218 | | |
213 | 219 | | |
| 220 | + | |
214 | 221 | | |
215 | 222 | | |
216 | 223 | | |
| |||
380 | 387 | | |
381 | 388 | | |
382 | 389 | | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
383 | 465 | | |
384 | 466 | | |
385 | | - | |
| 467 | + | |
386 | 468 | | |
387 | 469 | | |
388 | 470 | | |
| |||
416 | 498 | | |
417 | 499 | | |
418 | 500 | | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
419 | 517 | | |
420 | 518 | | |
421 | 519 | | |
| |||
427 | 525 | | |
428 | 526 | | |
429 | 527 | | |
430 | | - | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
431 | 532 | | |
432 | 533 | | |
433 | 534 | | |
| |||
469 | 570 | | |
470 | 571 | | |
471 | 572 | | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
472 | 587 | | |
473 | 588 | | |
474 | 589 | | |
475 | 590 | | |
476 | 591 | | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
477 | 600 | | |
478 | 601 | | |
479 | 602 | | |
| |||
522 | 645 | | |
523 | 646 | | |
524 | 647 | | |
525 | | - | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
526 | 653 | | |
527 | 654 | | |
528 | 655 | | |
529 | 656 | | |
530 | 657 | | |
531 | 658 | | |
532 | | - | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
533 | 663 | | |
534 | 664 | | |
535 | 665 | | |
| |||
0 commit comments