Files
abrown38andClaude Opus 5.5 de075edd5d Shadow mode: Housecarl beside the firewall in force, changing nothing
shadow.enabled in housecarl.yaml loads Housecarl's ruleset so that it
blocks, rejects and translates nothing: drops and rejects count and
accept (an accept only ends Housecarl's chain), policies accept and log
what they would drop, NAT only counts, MSS clamping and the flowtable
are left out, and raw rules that do more than accept, drop or reject are
left out with a warning. Comments mark what would happen ([would drop]).
Filter chains run after the other firewall's (+10) or before (-10); NAT
chains always first. Every change is checked by nftables in both forms.
No probation; takeovers and closing connections are refused. The boot
fallback filters nothing. internal/tablewatch puts the table back after
a flush ruleset. Mode in status, healthz, whoami, the web UI, the log
and the tracer; hc ruleset --enforcing. docs/shadow.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 18:11:01 +01:00

87 lines
3.4 KiB
Markdown

# NAT
## Port forwards
A port forward publishes an inside host's service: connections to a
public port are sent to the host, and let through the forward chain.
One object does what takes three rules by hand (the DNAT, the forward
accept, and the hairpin masquerade).
In shadow mode ([shadow.md](shadow.md)) NAT rules translate nothing:
they count the connections they would translate, and the other
firewall's NAT does the work.
```sh
hc forward add 8443 --to 192.168.1.10:443 --public 203.0.113.10 --hairpin --name web
hc forward add 27015-27030 --proto tcp,udp --to 192.168.1.20 --public 203.0.113.10 --hairpin --name game
hc forward add 25 --to 192.168.1.25 --from mail-relays --name smtp
hc forward list
```
| | |
|---|---|
| `PORTS` | the public port or range |
| `--proto` | `tcp`, `udp` or both (`tcp,udp`); `tcp` by default |
| `--to HOST[:PORT]` | the inside IPv4 host, and its port if it differs. A range keeps its ports |
| `--public` | the public IPv4 addresses (or a set) to match. Without it, any address arriving on an external zone |
| `--from` | only these sources may connect |
| `--hairpin` | the public address also works from inside (below); `--hairpin-from` limits that to some inside sources |
| `--to6` | an inside IPv6 host to open the port to (below) |
In the ruleset, a forward is a `dnat ip to` rule in the `dstnat` chain,
an accept in `forward` for connections it translated (`ct status dnat`),
placed after the built-ins and before your forward rules, so a later
"drop everything from wan" rule doesn't stop it.
### Hairpin NAT
From inside, a client that connects to the public address (because DNS
gives it) has its connection sent to the inside host, which would answer
it directly, and the client would drop the answer. With `--hairpin`,
Housecarl also masquerades those connections, so the answers come back
through the firewall. It needs `--public`: without the public address,
there is nothing to recognise those connections by.
### IPv6
IPv6 needs no translation: inside hosts have public addresses. `--to6`
opens the same port (or the `--to` port, if given) to an IPv6 host:
```sh
hc forward add 8443 --to 192.168.1.10:443 --to6 2001:db8:1::10 --public 203.0.113.10 --name web
```
IPv6 clients connect to `[2001:db8:1::10]:443` directly. A forward
can have only `--to6`, to open an IPv6 port alone.
## Source NAT
Source NAT rewrites the source of traffic leaving the firewall:
masquerade (the outgoing interface's address) or a fixed address with
`--snat-to`. Like rules, they are in order and match by zone, address and
service; the first that matches decides.
```sh
hc snat add --out wan --from trusted --comment "the LAN to the internet"
hc snat add --out wg0 --not-out --from wg-clients
hc snat add --out wan --from fd00:1::/48 --family ipv6 --snat-to 2001:db8::1
```
Source NAT is IPv4 only unless `--family ipv6` says otherwise: with a
delegated prefix, IPv6 needs no NAT, and NAT66 is for unusual cases
(a ULA network behind one public address).
## Redirects
A redirect sends matching traffic to a port on the firewall itself, for
example to make every client use the firewall's DNS server:
```sh
hc redirect add --in lan --service dns --not-to 192.168.1.5 --comment "force DNS"
hc redirect add --in lan --service http --to-port 3128
```
Without `--to-port`, the service's own port. The input chain must
accept the redirected traffic: redirected DNS needs a rule accepting DNS
from `lan`.