Compilation Pipeline¶
This document gives the big picture of how FirewallFabrik turns a firewall
definition (stored as .fwf / YAML, or imported from .fwb / XML) into an
executable shell script for iptables or nftables. Read this first; the
Rule Processors document drills into the individual
processor steps afterwards.
Big picture¶
Every layer, its source files, and its role, in execution order:
========== INPUT ==========
.fwf / YAML or .fwb / XML
parsed by core/_yaml_reader.py / core/_xml_reader.py
|
v
========== OBJECT MODEL ========== (SQLAlchemy, in-memory SQLite)
core/_database.py DatabaseManager: sessions, undo stack
core/objects/*.py Firewall, RuleSet, Rule, rule elements (STI models)
|
v
========== ORCHESTRATION ==========
driver/_compiler_driver.py base driver (shared)
platforms/<p>/_compiler_driver.py platform driver (iptables or nftables)
Looks up firewall, loops address families, dispatches to sub-compilers,
assembles the final script.
|
| For each address family (IPv4, IPv6):
v
========== PREPROCESSOR ==========
platforms/linux/_preprocessor.py
Normalises objects for the current address family.
|
v
========== RULE-PROCESSOR PIPELINE ==========
Engine (platform-independent):
compiler/_rule_processor.py BasicRuleProcessor base class
compiler/_compiler.py Compiler: run_rule_processors() (pull-based)
compiler/_comp_rule.py CompRule: mutable working copy of a Rule
Sub-compilers (a processor chain each):
compiler/_nat_compiler.py + platforms/<p>/_nat_compiler.py
compiler/_policy_compiler.py + platforms/<p>/_policy_compiler.py
mangle pass iptables only, reuses policy compiler
Processors in each chain come from:
compiler/processors/_generic.py shared: Begin, ExpandGroups, ...
compiler/processors/_policy.py policy base: InterfacePolicyRules, ...
compiler/processors/_service.py service separation: SeparateTCPWithFlags
platforms/<p>/_policy_compiler.py platform-specific policy processors
platforms/<p>/_nat_compiler.py platform-specific NAT processors
Final processor is PrintRule: emits iptables / nft text.
|
v (end of per-AF loop)
========== ROUTING ==========
compiler/_routing_compiler.py
Runs once per firewall, not per address family.
|
v
========== SCRIPT ASSEMBLY ==========
driver/_configlet.py loads template fragments
driver/_jinja2_template.py Jinja rendering
resources/configlets/linux24/ prolog, reset, installer, epilog
|
v
========== OUTPUT ==========
<firewall>.fw
bash script (iptables) or nft -f script (nftables)
The key insight: the compiler driver is the orchestrator, the rule processors inside each sub-compiler are the workers, and the configlets provide the shell-script scaffolding around the generated rules.
Note on "orchestration": in this document the term refers to the coordination layer of a single in-process compile run — looking up the firewall, dispatching the address-family loop, calling the sub-compilers in order, collecting their output, and assembling the final script. It has nothing to do with container orchestration (Kubernetes, Swarm) or multi-host coordination. Everything happens sequentially in one Python process.
End-to-end flow (iptables)¶
This walks through what happens when fwf-ipt firewall1 (or the GUI
Compile action) is triggered.
-
Driver startup —
CompilerDriver_ipt.run(cluster_id, fw_id, …)inplatforms/iptables/_compiler_driver.py:- Open a DB session, look up the
Firewallobject. - Validate interface addresses, read firewall options, warn on unsupported options.
- Build an
OSConfigurator_linux24(handlesip_forward, kernel vars, module loading, …). - Gather all
PolicyandNATrule sets for this firewall. - Decide IPv4 / IPv6 run order from the
ipv4_6_orderoption.
- Open a DB session, look up the
-
Per address family (IPv4 first, then IPv6 by default):
- Preprocessor (
platforms/linux/_preprocessor.py) — normalises objects for the selected address family. - NAT compilation — instantiate
NATCompiler_ipt, run its ~50-processor pipeline (see iptables NAT pipeline order in RuleProcessors.md). Output goes into the*nattable section. - Policy compilation — instantiate
PolicyCompiler_ipt, run its ~77-processor pipeline (see Main compilation pass in RuleProcessors.md). Output is split across the*filterand*mangletables viaipt_chainon each rule. - Mangle pass — a dedicated
PolicyCompiler_iptrun for the mangle table (tagging / classify / routing).
- Preprocessor (
-
Routing pass —
RoutingCompilerruns once per firewall (not per address family). Producesip rule/ip routestatements. -
Script assembly —
_assemble_script_skeleton()loads configlet templates fromresources/configlets/linux24/and renders the final shell script: shebang, header, prolog,reset_all, per-AF rule blocks, routing block, epilog, installer commands. -
Output — written to
<firewall>.fwin the working directory. The driver reportsCompiled successfullyorCompiled with errorsand collects all warnings/errors inall_errors/all_warnings.
nftables follows the same shape, but simpler: no separate mangle pass
(native meta mark set), no temp-chain tricks (native != for negation,
native sets for multiport), and fewer processors overall (~35 policy, ~30
NAT). The final script starts with #!/usr/sbin/nft -f and a
flush ruleset, then emits table inet filter { chain input { … } … }
blocks.
Minimum you need to know to navigate the code¶
- If you're debugging output correctness (wrong iptables command, wrong chain, missing rule), start in the processor chain → see RuleProcessors.md, particularly the Full pipeline order section.
- If you're debugging script scaffolding (wrong shebang, missing
reset_all, broken installer), start in the platform_compiler_driver.pyand the configlet templates underresources/configlets/linux24/. - If you're debugging input (wrong rule in DB), start in the YAML /
XML readers or in
core/objects/_rules.py. - For per-rule tracing through the processor chain, enable the
Debuginterceptor via--xp/--xn/--xr— see Debugging.
Further reading¶
- Rule Processors — every processor, every pipeline, in execution order.
- Database Manager — how
core/_database.pymanages sessions and undo state. - Platform Defaults — where default option values live and how they flow into the compiler.
- Debugging — how to trace a single rule through the pipeline.
- Testing — how the expected-output regression tests guard compiler output.