fe80::/10 in the IPv6 antispoof list (or your prefixes) dropped the ISP's router adverts and DHCPv6 replies in the early chain, before the ICMPv6 and DHCPv6 client built-ins could accept them: the router lost its IPv6 default route. Validation now refuses it (antispoof_link_local), the settings page says so, and the importer leaves it out with a note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
468 lines
21 KiB
Markdown
468 lines
21 KiB
Markdown
# Zones, sets, services and rules
|
|
|
|
## Zones
|
|
|
|
A zone names one or more interfaces, so rules can say "from the LAN"
|
|
rather than `eth1`. An interface belongs to one zone at most. A trailing
|
|
`*` matches many: `incusbr*`.
|
|
|
|
```sh
|
|
hc zone add wan --interface wan --external
|
|
hc zone add lan --interface eth1 --interface 'eth1.*'
|
|
hc zone add wg --interface wg0
|
|
```
|
|
|
|
**External** zones face the internet. Antispoofing, MSS clamping, the
|
|
DHCPv6 client rule and port forwards (without public addresses) apply on
|
|
them. Everything else is internal: ping to the firewall from internal
|
|
zones is accepted by a built-in.
|
|
|
|
A zone may name an interface that doesn't exist yet (a WireGuard
|
|
interface that comes up later): its rules match once it appears, and
|
|
`hc status` warns until then.
|
|
|
|
**VLANs.** A VLAN sub-interface is an interface like any other, by its
|
|
name: `enp2s0.20` (or whatever your network configuration calls it,
|
|
`vlan20`, `br0.20`). Tagged traffic arrives on the sub-interface, and
|
|
untagged traffic on the parent, so `enp2s0` alone doesn't cover
|
|
`enp2s0.20`. Give each VLAN its own zone to keep them apart:
|
|
|
|
```sh
|
|
hc zone add lan --interface enp2s0 # untagged
|
|
hc zone add iot --interface enp2s0.20
|
|
hc zone add guest --interface enp2s0.30
|
|
hc rule add forward --in iot --out wan
|
|
```
|
|
|
|
or one zone for the parent and all of its VLANs, when they should be
|
|
treated alike: `--interface enp2s0 --interface 'enp2s0.*'`. Interface
|
|
names are at most 15 characters (the kernel's limit). The flowtable
|
|
takes VLAN sub-interfaces too, by exact name
|
|
([flowtable.md](flowtable.md)).
|
|
|
|
**Renaming** a zone (`hc zone update dhcp --name lan`, or Edit on the
|
|
Zones page) changes every rule and NAT rule that names it, in the same
|
|
change: the history shows them together, and undoing it puts them all
|
|
back. Sets and services rename the same way, except dynamic sets: their
|
|
entries, and the API tokens and programs that fill them, go by their
|
|
name.
|
|
|
|
## Address sets
|
|
|
|
A set is a named list of IPv4 and IPv6 addresses, prefixes and ranges,
|
|
mixed:
|
|
|
|
```sh
|
|
hc set add trusted 192.168.1.0/24 192.168.2.0/24 2001:db8:1::/64
|
|
hc set add dns-servers 192.168.1.5 2001:db8:1::5 192.168.1.250-192.168.1.254
|
|
```
|
|
|
|
In the ruleset each becomes two nftables sets, `trusted_v4` and
|
|
`trusted_v6` (one if the set has only one family). A rule that names it
|
|
becomes one nftables rule per family. Rules can also give addresses
|
|
directly (`--from 192.168.1.3`); a set is better when several rules share
|
|
them.
|
|
|
|
A **dynamic** set's entries are added at run time, each with a timeout,
|
|
and nftables removes them when it runs out, even with housecarld stopped.
|
|
It has no configured entries (validation refuses them); its timeout is
|
|
the default for entries added without one, and without a timeout they
|
|
stay until removed:
|
|
|
|
```sh
|
|
hc set add guests --dynamic --timeout 24h
|
|
hc set entries add guests 10.0.30.17 # for 24 hours
|
|
hc set entries add guests 2001:db8:3::17 --timeout 2h
|
|
hc set entries guests # with the time left
|
|
hc set entries remove guests 10.0.30.17
|
|
```
|
|
|
|
Entries aren't configuration: they aren't in the history and aren't on
|
|
probation, but each change is in the audit log. They survive every
|
|
change to the ruleset, and a restart of housecarld; a reboot empties
|
|
them. A token can be limited to one set ([permissions.md](permissions.md)):
|
|
that is how another program (Steward's captive portal) lets a client in.
|
|
|
|
A set **from Jarl** holds the addresses of names in Jarl, the BIND
|
|
manager: every A and AAAA record of each name, CNAMEs followed, read again
|
|
every minute. Only addresses within the zones and networks it names are
|
|
used, so a changed record can't open the firewall to anything else:
|
|
|
|
```sh
|
|
hc set add nas --jarl nas.example.com@internal --within lan
|
|
hc set jarl nas # what it holds, and why
|
|
```
|
|
|
|
Views, CNAMEs, what happens when Jarl can't be reached and the rest are in
|
|
[jarl.md](jarl.md#sets-from-jarl).
|
|
|
|
## Services
|
|
|
|
A service names ports of a protocol (`tcp`, `udp`, `sctp`) and ICMP
|
|
types:
|
|
|
|
```sh
|
|
hc service add web tcp/8080 tcp/8443
|
|
hc service add games udp/27015-27030 tcp/27015
|
|
hc service add pmtu icmp/destination-unreachable icmpv6/packet-too-big
|
|
```
|
|
|
|
Many are built in (`hc service list`): `ssh`, `dns` (TCP and UDP 53),
|
|
`dot`, `http`, `https` (TCP, and UDP for QUIC), `smtp`, `submission`,
|
|
`smtps`, `imaps`, `ntp`, `dhcp`, `dhcpv6`, `wireguard`, `openvpn`, `ipsec`,
|
|
`snmp`, `syslog`, `mdns`, `rdp`, and `ping` (ICMP and ICMPv6 echo
|
|
requests). Adding a service with a built-in's name replaces it. Rules may
|
|
also give an entry directly: `--service tcp/8080`.
|
|
|
|
## Rules
|
|
|
|
Rules are kept in order, in four chains:
|
|
|
|
| Chain | | Hook |
|
|
|---|---|---|
|
|
| `input` | traffic to the firewall itself | input |
|
|
| `forward` | traffic through it, between zones | forward |
|
|
| `output` | traffic from it | output |
|
|
| `early` | before connection tracking: cheap drops of what can never be right | prerouting, priority raw |
|
|
|
|
Each chain is checked top to bottom, and the first rule that accepts,
|
|
drops or rejects decides. What no rule decides gets the chain's
|
|
**policy** (Settings): by default input and forward drop, output
|
|
accepts.
|
|
|
|
On the Rules page, ↑ and ↓ move a rule one place; **Move…** puts it
|
|
before any other rule of its chain, or at the end, in one change
|
|
(`hc rule move ID --before ID`). In a changeset, moves of the same rule
|
|
one after another are kept as one operation.
|
|
|
|
```sh
|
|
hc rule add input --from trusted --service ssh --comment "SSH from home"
|
|
hc rule add input --in wg --service dns
|
|
hc rule add forward --in lan --out wan
|
|
hc rule add forward --in wan -a drop --comment "nothing else gets in"
|
|
hc rule list input
|
|
hc rule move 12 --before 7
|
|
```
|
|
|
|
A rule matches on what it gives, and all of it:
|
|
|
|
| | |
|
|
|---|---|
|
|
| `--in`, `--out` | zones it comes in on, goes out on (`--not-in`, `--not-out`: any other). Input has no out zone, output no in zone |
|
|
| `--from`, `--to` | sets, addresses, prefixes, ranges (`--not-from`, `--not-to`: anything else) |
|
|
| `--service` | services or entries (`tcp/8080`, `icmp/echo-request`) |
|
|
| `--family` | `ipv4` or `ipv6` only; otherwise it follows the addresses |
|
|
| `--state` | connection states: `new`, `established`, `related`, `invalid`, `untracked` |
|
|
| `--status` | connection status: `dnat` (port forwarded), `snat` |
|
|
| `--days`, `--hours` | a time window: `--days Monday,Friday --hours 08:00-17:30`; `22:00-06:00` passes midnight. In the firewall's time zone, as the kernel knows it |
|
|
| `--limit` | a rate: `10/minute`, or bytes: `1mbytes/second`; with `,burst=N`, `,over` (match beyond the rate) and `,per-source` or `,per-destination` ([Bandwidth and quotas](#bandwidth-and-quotas)) |
|
|
| `--quota` | an allowance of traffic: `50gbytes,over,per-source,monthly=15` ([Bandwidth and quotas](#bandwidth-and-quotas)) |
|
|
| `--conn-limit N` | a source with more than N connections |
|
|
|
|
The action is `accept`, `drop`, `reject` (an ICMP error, or with
|
|
`--reject-with tcp-reset` a TCP reset) or `log` (log and go on).
|
|
`--log` logs the packets of any rule ([operations.md](operations.md#the-log)).
|
|
`--disabled` (or `hc rule disable ID`) keeps a rule without using it.
|
|
`--every-packet` ("every packet" in the web UI) checks a drop or reject
|
|
against every packet, before established connections are accepted, so it
|
|
also stops connections already open, not just new ones; it can't accept
|
|
([operations.md](operations.md#connections)).
|
|
|
|
Examples:
|
|
|
|
```sh
|
|
# SSH from the internet, at most 3 new connections a minute per source
|
|
hc rule add input --in wan --service ssh --state new --limit 3/minute,burst=5,over,per-source -a drop --log
|
|
hc rule add input --in wan --service ssh
|
|
|
|
# Children's devices offline at night
|
|
hc rule add forward --from kids --out wan --hours 22:00-07:00 -a reject
|
|
|
|
# What was port forwarded may go on, the rest from the internet may not
|
|
hc rule add forward --in wan --status dnat
|
|
hc rule add forward --in wan -a drop
|
|
```
|
|
|
|
### Matching everything but a few
|
|
|
|
A field matches **any of** its entries, or with `--not-…` ("any but" in
|
|
the web UI) anything else; it can't do both. "The LAN except the NAS" is
|
|
still one rule: the zone says which network, and the addresses say which
|
|
of it to leave out, because zones and addresses are different fields and
|
|
a rule needs all of them to match.
|
|
|
|
| What you want | One rule |
|
|
|---|---|
|
|
| the LAN, except the NAS | `--in lan --from nas --not-from` |
|
|
| everyone on guest, except one device, has a quota | `--in guest --from tv --not-from --quota 10gbytes,over,daily` |
|
|
| the internet, except one host | `--out wan --to blocked --not-to` |
|
|
|
|
```sh
|
|
# Each device on the LAN: 50 GB a month, but not the NAS (its backups)
|
|
hc rule add forward --in lan --out wan --from nas --not-from --quota 50gbytes,over,per-source,monthly -a drop
|
|
```
|
|
|
|
Leaving part of an address range out ("10.0.0.0/8 except 10.0.5.0/24")
|
|
when it isn't a zone takes one of:
|
|
|
|
* **Two rules, the exception first**, when the exception gets the
|
|
opposite action: `--from 10.0.5.0/24 -a drop` above `--from
|
|
10.0.0.0/8`. There is no action that skips a rule and goes on, so this
|
|
doesn't work when the exception should just fall through to later
|
|
rules.
|
|
* **Ranges around the hole**: `--from 10.0.0.0-10.0.4.255 --from
|
|
10.0.6.0-10.255.255.255`.
|
|
* **A [raw rule](#raw-rules)**: `ip saddr 10.0.0.0/8 ip saddr != 10.0.5.0/24 accept`.
|
|
|
|
There's no need to leave a zone out of other zones: an interface belongs
|
|
to one zone, so "any of lan, guest" already leaves out everything else.
|
|
|
|
### Bandwidth and quotas
|
|
|
|
A **byte rate** limits bandwidth: `--limit 20mbytes/second,over,per-source
|
|
-a drop` drops what a device sends beyond 20 MB/s. Units are `bytes`,
|
|
`kbytes` and `mbytes` (`kb`, `mb` for short); the burst is in the same
|
|
unit. nftables can only hold byte rates per second (up to 4 GB/s) or per
|
|
minute (up to 293 MB/minute); validation says so for larger ones. This is
|
|
policing: traffic beyond the rate is dropped, not queued, so TCP slows to
|
|
fit, and UDP loses packets. Shaping (queues, fair shares) is `tc`'s work,
|
|
not a firewall's.
|
|
|
|
A **quota** is an allowance of traffic. `over` matches once it is used up,
|
|
to drop what comes after:
|
|
|
|
```sh
|
|
# Each device on the LAN: 50 GB a month, from the 15th (a billing day)
|
|
hc rule add forward --in lan --out wan --quota 50gbytes,over,per-source,monthly=15 -a drop
|
|
# The whole guest network: 10 GB a day together
|
|
hc rule add forward --in guest --out wan --quota 10gbytes,over,daily -a drop
|
|
hc quotas # what each used, and the next reset
|
|
hc rule reset-quota 31 # start again now
|
|
hc rule reset-quota 31 --address 192.168.1.23 # one device only
|
|
hc rule reset-quota 31 --give 5gbytes --address 192.168.1.23
|
|
```
|
|
|
|
`--give` is a top-up: it takes that much off what was used (never below
|
|
zero), so the device gets 5 GB more until the quota's next reset, and the
|
|
rule stays as it is. In the web UI, the quota's line under the rule
|
|
opens its dialog: usage (each address's), resets, and **Reset** and
|
|
**Give back…** for everyone or one address. Like a reset, these are
|
|
done at once (audited, not staged or on probation); Housecarl puts the
|
|
ruleset in force again with the new figure.
|
|
|
|
In the web UI each quota's use is on its rule (with **Reset**), all of
|
|
them together on the dashboard, and their history under **Traffic →
|
|
Statistics**.
|
|
|
|
| | |
|
|
|---|---|
|
|
| `per-source`, `per-destination` | each address its own allowance; without, one allowance for everything the rule matches |
|
|
| `daily`, `weekly`, `monthly=DAY` | when it starts again: at midnight; weekly on Mondays; monthly on that day (the last day of a shorter month). Without one, only by hand |
|
|
|
|
nftables counts a quota but never resets it, and forgets it when its
|
|
table is replaced: Housecarl carries what each quota used across every
|
|
change and restart, saves it every five minutes (and after each change,
|
|
and when housecarld stops) for after a reboot, and resets quotas when their period ends.
|
|
A per-address quota counts an address: a device that gets a new one
|
|
starts again. With Steward's reservations IPv4 addresses stay; IPv6 is
|
|
grouped by /64 (below).
|
|
|
|
Changing the amount, `over` or the resets keeps what was used. Changing
|
|
how the quota counts carries what can be carried:
|
|
|
|
| Change | What was used |
|
|
|---|---|
|
|
| per address to one total | the addresses' sum |
|
|
| to wider groups (IPv6 /64 to /56) | added up per new group |
|
|
| a total to per address | starts again: a total can't be split |
|
|
| per source to per destination (or back) | starts again |
|
|
| to finer groups (/64 to /128) | starts again: a group can't be split |
|
|
|
|
A change that starts a quota again says so (warning `quota_restarts`),
|
|
when it is staged for review and in `hc` (also with `-n`).
|
|
|
|
**Rules with a quota or a byte rate count every packet.** The built-in
|
|
that accepts established connections comes before your rules, so a rule
|
|
there only sees each connection's first packet. Rules that count traffic
|
|
are checked before it instead, in their order (the web UI lists them
|
|
first, marked "every packet"). So they can drop, reject or log, but not
|
|
accept: an accept there would let packets past every later rule. Let the
|
|
traffic in with a rule of its own. The flowtable's fast path skips the
|
|
forward chain for established connections, so with it on, forward
|
|
quotas and byte rates only count first packets (a warning says so;
|
|
[flowtable.md](flowtable.md)).
|
|
|
|
**Per-address limits group addresses**: each IPv4 address on its own, and
|
|
IPv6 addresses by /64, since one device uses several (privacy addresses
|
|
change). `--group-v4 24` counts each /24 together; `--group-v6 128` each
|
|
IPv6 address alone. This applies to per-address rates, connection limits
|
|
and quotas.
|
|
|
|
### Raw rules
|
|
|
|
For what Housecarl can't say, a rule can be one nftables rule written by
|
|
hand, placed like any other:
|
|
|
|
```sh
|
|
hc rule add forward --raw 'iifname != "wan" ct state invalid ip daddr @trusted_v4 accept'
|
|
```
|
|
|
|
It is used exactly as written: its text replaces the rule's zones,
|
|
addresses, services, action, limits and log, so a raw rule with any of
|
|
those set is refused (`raw_conflict`); write them in the text. It is
|
|
checked by nftables with the rest of the ruleset, and can use the sets'
|
|
nftables names (`@trusted_v4`). The importer makes raw rules from
|
|
what it can't translate. It goes into the ruleset as written: add
|
|
`counter` to it to count its packets.
|
|
|
|
In shadow mode ([shadow.md](shadow.md)) a raw rule's final `drop` or
|
|
`reject` becomes `accept`; a raw rule that does anything else to packets
|
|
(NAT, `meta mark set`, `jump`, `notrack`, a verdict map…) is left out,
|
|
with a warning (`shadow_raw_left_out`).
|
|
|
|
## Ban lists
|
|
|
|
Programs such as Guard and fail2ban ban addresses into ban lists, and
|
|
those bans come first of all in the input and forward chains (after
|
|
loopback in input), before the built-ins and every rule. See
|
|
[bans.md](bans.md).
|
|
|
|
## Built-ins
|
|
|
|
Housecarl adds the rules every firewall needs before yours. Each can be
|
|
turned off (`hc settings set --builtin NAME=off`; Settings in the web
|
|
UI), and `hc ruleset` shows them:
|
|
|
|
| Built-in | Default | Chains | What |
|
|
|---|---|---|---|
|
|
| `loopback` | on | input (and output if its policy is drop) | accept everything on `lo` |
|
|
| `established` | on | input, forward (and output) | accept established and related connections |
|
|
| `drop_invalid` | on | input, forward | drop packets connection tracking calls invalid |
|
|
| `icmpv6` | on | input, forward (and output) | the ICMPv6 IPv6 can't work without (RFC 4890): errors and echo; neighbour discovery with hop limit 255 and MLD from link-local, to the firewall |
|
|
| `ping_internal` | on | input | ping (ICMP echo) to the firewall from internal zones |
|
|
| `dhcpv6_client` | on | input | DHCPv6 replies from link-local on external zones, so the firewall gets its prefix delegation |
|
|
| `antispoof` | on | early | drop sources that can't come from the internet, on external zones: the antispoof lists (private, loopback, documentation and multicast ranges) and your own prefixes |
|
|
| `mss_clamp` | off | forward | clamp TCP's MSS to the route's MTU on external zones: needed with PPPoE |
|
|
|
|
**Your prefixes** (`--own-prefix 2001:db8::/48`) are your delegated
|
|
IPv6 prefix and public addresses: nothing from outside should come from
|
|
them. The antispoof lists can be changed with `--antispoof-v4` and
|
|
`--antispoof-v6`. Link-local addresses (`fe80::/10`) are refused in the
|
|
IPv6 list and in your prefixes (`antispoof_link_local`): your ISP's
|
|
router sends its router adverts and DHCPv6 replies from them, and
|
|
antispoofing runs in the early chain, before the ICMPv6 and DHCPv6
|
|
client built-ins could accept them, so the router would lose its IPv6
|
|
default route and, later, its delegated prefix.
|
|
|
|
The **flowtable** (`--flowtable wan --flowtable eth1`) lets established
|
|
TCP and UDP connections forwarded between those devices skip the rules,
|
|
which helps when the CPU can't keep up with the line. It needs exact
|
|
device names, and costs quotas, byte rates and traffic per device in the
|
|
forward chain their accuracy: see [flowtable.md](flowtable.md) for when
|
|
to use it, and when not.
|
|
|
|
## The zone matrix
|
|
|
|
The Matrix page (`hc matrix`) sums up what the rules do to **new
|
|
connections** between zones: a row per zone the traffic comes from, a
|
|
column per zone it goes to, and the firewall itself (to it: the input
|
|
chain; from it: output; zone to zone: forward).
|
|
|
|
```
|
|
$ hc matrix
|
|
FROM \ TO guest lan wan firewall
|
|
guest block * block partly +1 partly +4
|
|
lan block block * allow partly +4
|
|
wan partly +1 -1 partly +1 -1 block -1 * partly +3 -1
|
|
firewall allow allow allow -
|
|
|
|
Let through for part of the traffic:
|
|
guest → wan http, https
|
|
guest → firewall ICMPv6; ping; ssh from admins; dns
|
|
lan → firewall ICMPv6; ping; ssh from admins; dns
|
|
wan → guest 1 port forward
|
|
wan → lan 1 port forward
|
|
wan → firewall ICMPv6; DHCPv6; ssh from admins
|
|
|
|
allow, block: every new connection; partly: some are let through, some not.
|
|
+N: rules that let part of the traffic through; -N: rules that block part of it. 'hc matrix FROM TO' lists them.
|
|
*: a zone to itself: only traffic routed between its interfaces passes the firewall.
|
|
```
|
|
|
|
Each cell walks that pair's rules in order, as nftables would, with the
|
|
built-ins that act on new connections (ping from internal zones,
|
|
ICMPv6, the DHCPv6 client, antispoofing, port forwards):
|
|
|
|
* a rule that applies to the whole pair decides it;
|
|
* a rule that applies to part of its traffic (some addresses, services,
|
|
times, rates, a quota) is an **exception**, and the walk goes on;
|
|
* what's left falls to the deciding rule or the chain's policy.
|
|
|
|
A cell reads **allow** or **block** when every new connection gets the
|
|
same, and **partly** when exceptions let some through while the rest is
|
|
blocked (or block some while the rest is allowed). **+N** counts the
|
|
exceptions that let traffic through, **-N** those that block it. Under
|
|
the grid (in the web UI, in each cell) is what the exceptions let
|
|
through: their services, with the addresses they are limited to.
|
|
|
|
A zone to itself (`*`, faded in the web UI) is mostly traffic that never
|
|
reaches the firewall: within one interface's network it goes straight
|
|
from host to host. Only what is routed between the zone's interfaces (or
|
|
back out of the one it came in on) passes.
|
|
|
|
`hc matrix guest wan` (or clicking the cell) lists the exceptions in
|
|
order, with each rule's hits:
|
|
|
|
```
|
|
guest → wan (the forward chain), new connections: partly allowed
|
|
VERDICT WHAT BY HITS
|
|
allowed http, https rule forward #14 1520
|
|
blocked the rest forward policy 37
|
|
```
|
|
|
|
The hits are the rule's packets since housecarld started, in all of its
|
|
traffic: a rule without zones counts for every pair it covers. They are
|
|
how you tell an exception that is used from one that isn't.
|
|
|
|
A raw rule is read when it is a match followed by `accept`, `drop` or
|
|
`reject` (with `counter` and `log` if you like): it counts as an exception
|
|
for every pair of its chain, shown as `raw: …`. One that matches
|
|
interfaces (the matrix can't tell which zones), jumps, translates, sets
|
|
something or uses a verdict map shows as **unknown**: check it yourself.
|
|
Raw rules have hits only if they say `counter`.
|
|
|
|
A rule whose addresses all lie in networks directly connected to other
|
|
zones is left out of a pair: traffic between those zones can't have them
|
|
(`from wg_net` doesn't concern traffic coming in on the LAN). Routed
|
|
networks, mixed lists and dynamic sets might, so they stay. Replies and
|
|
established connections aren't shown: the built-in lets them through. In
|
|
review mode, the matrix shows your pending changes.
|
|
|
|
## The ruleset
|
|
|
|
`hc ruleset` prints what Housecarl builds; the web UI and `hc rule list`
|
|
show each rule's hits. The table looks like this (shortened):
|
|
|
|
```
|
|
table inet housecarl {
|
|
set trusted_v4 { type ipv4_addr; flags interval; elements = { 192.168.1.0/24, 192.168.2.0/24 } }
|
|
set trusted_v6 { type ipv6_addr; flags interval; elements = { 2001:db8:1::/64 } }
|
|
chain early { type filter hook prerouting priority raw; policy accept; ... }
|
|
chain input { type filter hook input priority filter; policy drop;
|
|
iifname "lo" accept comment "hc:b-loopback"
|
|
ct state established,related counter accept comment "hc:b-established"
|
|
...
|
|
ip saddr @trusted_v4 tcp dport 22 counter accept comment "hc:r4 SSH from home"
|
|
ip6 saddr @trusted_v6 tcp dport 22 counter accept comment "hc:r4 SSH from home"
|
|
counter comment "hc:p-input"
|
|
}
|
|
chain forward { ... }
|
|
chain output { ... }
|
|
chain dstnat { type nat hook prerouting priority dstnat; ... }
|
|
chain srcnat { type nat hook postrouting priority srcnat; ... }
|
|
}
|
|
```
|
|
|
|
Other tables still apply: a packet must get past every table's chains on
|
|
its hook. The dashboard and `hc status` list them.
|