# firewallmngr architecture

`firewallmngr` is a BBFDM data-model micro-service. It registers the
`Device.Firewall.` and `Device.NAT.` TR-181 sub-trees with `bbfdmd` and
implements them on top of OpenWrt's `firewall` (and `port-trigger`) UCI
packages. It runs no daemon of its own — it is a plugin loaded by `bbfdmd`
that translates TR-181 get/set/add/delete calls into UCI reads and UCI
writes.

Several TR-181 objects (`Device.Firewall.Level`, `Device.Firewall.Chain`,
`Device.Firewall.Chain.{i}.Rule`) do not map one-to-one to firewall UCI
sections; the synthesis is described in the sections that follow.

## Plugin registration

`firewallmngr.c` is the entry point. It exposes a single `tDynamicObj`
mapping that attaches both `Device.Firewall.` and `Device.NAT.` under
`Device.`. `bbfdmd` discovers it via the standard plugin loader and mounts
the two sub-trees. Every parameter is flagged `BBFDM_BOTH`
(`BBFDM_CWMP | BBFDM_USP`) so the sub-trees are addressable from both
TR-069 and USP; local-bus access is provided by `bbfdmd` itself.

## TR-181 ↔ firewall UCI mapping

The data-model objects map to UCI section types as follows:

| TR-181 object | UCI package | UCI section type | dmmap shadow |
| --- | --- | --- | --- |
| `Device.Firewall.` (leaves) | `firewall` | `defaults` (global) | — |
| `Device.Firewall.Level.{i}.` | — (synthetic, single instance) | — | `dmmap_firewall/level` |
| `Device.Firewall.Chain.{i}.` | — (synthetic, see below) | — | `dmmap_firewall/chain` |
| `Device.Firewall.Chain.{i}.Rule.{i}.` | `firewall` | `rule` *and* `forwarding`, plus UPnP runtime entries | `dmmap_firewall/rule`, `dmmap_firewall/forwarding` |
| `Device.Firewall.DMZ.{i}.` | `firewall` | `dmz` | `dmmap_dmz/dmz` |
| `Device.Firewall.Service.{i}.` | `firewall` | `service` | `dmmap_firewall/service` |
| `Device.NAT.InterfaceSetting.{i}.` | `firewall` | `zone` | `dmmap_firewall/zone` |
| `Device.NAT.PortMapping.{i}.` | `firewall` | `redirect` | `dmmap_firewall/redirect` |
| `Device.NAT.PortTrigger.{i}.` | `port-trigger` | `port_trigger` | `dmmap_port_trigger/port_trigger` |

`Device.Firewall.Chain.{i}.` is synthetic: the "Defaults" chain always
exists; a second "PortMapping" chain is added when `/etc/config/upnpd` is
present. `Device.Firewall.Level.{i}.` is also synthetic — a single
instance whose only state lives in the dmmap shadow.

The `Device.NAT.PortTrigger.{i}.` object is gated by the build-time flag
`INCLUDE_PORT_TRIGGER`.

## The dmmap shadow pattern

TR-181 parameters that have no natural home in the firewall UCI (per-object
`Alias`, `__instance__`, and ordering metadata) are persisted in shadow UCI
files maintained by firewallmngr:

- `dmmap_firewall` — shadows `firewall` sections of type `rule`,
  `forwarding`, `zone`, `redirect`, and `service`, and holds the synthetic
  `level` and `chain` rows.
- `dmmap_dmz` — shadows `firewall/dmz`.
- `dmmap_port_trigger` — shadows `port-trigger/port_trigger` (and its
  `rule` children).

`synchronize_firewall_sections_with_dmmap()` keeps the two in sync: on each
browse it ensures every real UCI section has a dmmap counterpart keyed by
an immutable option (typically `name`), and removes dmmap entries whose
backing section is gone.

## Chain.{i}.Rule.{i} — three sources behind one parameter table

This is the most unusual part of the design. The `Rule.{i}` table looks
flat to the management protocol, but `browseRuleInst()` in `firewall.c`
populates it from **three** different sources, dispatched by the parent
chain's `creator` value (stored in the chain's dmmap section):

- **`creator = "Defaults"`** (Chain.1) — entries come from `firewall/rule`
  sections ("rule-backed") **and** `firewall/forwarding` sections
  ("forwarding-backed", zone-to-zone forwarding policy). The two are
  flattened into a single instance space under Chain.1.
- **`creator = "PortMapping"`** (Chain.2, present only when
  `/etc/config/upnpd` exists) — entries are read line-by-line from
  `/var/run/miniupnpd.leases` and have no UCI section behind them.

Each entry carries a `rule_sec` record with two discriminators that the
getters/setters branch on:

- An ordinary `firewall/rule` section sets neither flag: regular UCI
  read/write path.
- A UPnP lease sets `is_dynamic_rule = true` and supplies a parsed
  `dynamic_rule` array; with no UCI section behind it, writes are
  silently skipped and reads come from the parsed lease fields.
- A `firewall/forwarding` section also sets `is_dynamic_rule = true` but
  leaves `dynamic_rule` unset; the helper `is_forwarding_section()`
  distinguishes it from the UPnP case by inspecting the section type.

Most rule getters and setters key off `is_dynamic_rule` to skip work that
doesn't apply to UPnP leases (target, port ranges, source IP, etc.). Where
a parameter is genuinely meaningful for forwarding sections — `Enable`,
`Description`, `SourceInterface`, `DestInterface`, `IPVersion`,
`X_IOWRT_Origin` — the guard is widened with `!is_forwarding_section(...)`
so forwarding entries fall through to the regular UCI path.

## NAT.PortMapping ↔ firewall/redirect

`Device.NAT.PortMapping.{i}.` does not have a dedicated UCI section type;
it reuses `firewall/redirect`. The data-model parameter names (e.g.
`ExternalPort`, `InternalClient`) translate to redirect options
(`src_dport`, `dest_ip`). NAT-aware `redirect` options like `target='DNAT'`
are managed implicitly when an entry is created via the data model.

`Device.NAT.InterfaceSetting.{i}.` similarly piggybacks on `firewall/zone`
— every zone is exposed as an InterfaceSetting; `Enable` reflects the
zone's `masq` option.

## Apply path

firewallmngr writes UCI in-place during VALUESET; the bbfdm transaction
layer commits the underlying UCI files at the end of the set. The plugin
does not exec `/etc/init.d/firewall` itself — picking up the new
configuration (running `fw3`/`fw4`, restarting `firewall`, etc.) is
expected to be handled outside this codebase, typically by a system-level
UCI commit hook or supervisor. No long-lived state is held in process.

## Implicit zone creation on SourceInterface / DestInterface

`SourceInterface` and `DestInterface` on a rule are TR-181 references to
`Device.IP.Interface.{i}.`. When a set targets an IP.Interface that has no
matching `firewall/zone` yet, `firewall__create_zone_section()` creates
one before writing the rule:

- The new zone is named after the resolved interface name.
- Its `input`, `output`, and `forward` policies are copied from
  `firewall.@defaults[0]` — whatever the global defaults are at the time
  of creation.
- Its `network` list is set to that same interface name.

This is what allows clients to add per-interface firewall rules without
having to provision the matching zone separately.

## Quirks and limitations

- **Protocol = 255 sentinel** — when a rule's `proto` is unset or contains
  multiple values (e.g. `proto='tcp udp'`), `Device.Firewall.Chain.{i}.Rule.{i}.Protocol`
  reports `255`, which is reserved in the IANA protocol numbering. A
  `proto='0'` or `proto='all'` reports `-1`.
- **Single Level instance** — only one `Device.Firewall.Level.{i}.` exists
  (synthetic, backed by `dmmap_firewall/level`); additional instances
  cannot be added via the data model, and `Device.Firewall.AdvancedLevel`
  only accepts `Device.Firewall.Level.1.`.
- **Chain instances are not user-creatable** — the "Defaults" chain (and,
  when UPnP is present, the "PortMapping" chain) are created by
  firewallmngr. Clients cannot add their own chains.
- **`TargetChain` is not exposed** as a parameter on
  `Device.Firewall.Chain.{i}.Rule.{i}` — the leaf is commented out in the
  parameter table. The string `"TargetChain"` is accepted as a `Target`
  value at validation time but has no effect on the underlying UCI rule.
- **`Device.Firewall.Config`** only accepts `'Advanced'` mode; setting
  `High`, `Low`, or `Off` returns a fault.

## Source layout

- `firewallmngr.c/h` — plugin entry; declares the root `tDynamicObj`.
- `firewall.c/h` — implementation of the `Device.Firewall.*` sub-tree.
- `nat.c/h` — `Device.NAT.InterfaceSetting` and `Device.NAT.PortMapping`.
- `nat_porttrigger.c/h` — `Device.NAT.PortTrigger`, gated by `INCLUDE_PORT_TRIGGER`.

External dependencies are limited to `libbbfdm-api` (UCI helpers, dmmap
plumbing, type validators) and OpenWrt's `libuci`.

## References

1. [OpenWrt firewall UCI documentation](https://openwrt.org/docs/guide-user/firewall/firewall_configuration)
2. [OpenWrt network UCI documentation](https://openwrt.org/docs/guide-user/base-system/basic-networking)
