Add a generic NAT table: port forward, source NAT, masquerade
router.firewall.nat replaces the dnat/toPort/hairpin fields of the filter rules. Filter rules gain natOnly, and may use wan with log and rate limit in the web UI. The Firewall page edits the NAT table and can add the matching allow rule. Settings schema 1 (migrations/1.jq) converts old forwards. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
This commit is contained in:
+103
-21
@@ -393,6 +393,100 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
firewall.nat = mkOption {
|
||||
default = [ ];
|
||||
description = ''
|
||||
Address translation, separate from the allow rules. `dnat` sends
|
||||
traffic that arrives in zone `from` (`wan` for the uplink) and matches
|
||||
the rule to `toAddress` (a host in zone `to`); `snat` and `masquerade`
|
||||
rewrite the source of traffic leaving through zone `to` (`wan` for the
|
||||
uplink) to `toAddress`, or to the address of the outgoing interface.
|
||||
LANs with `internet = true` and tunnels with `masquerade` are already
|
||||
masqueraded; these rules come first. A translation does not allow
|
||||
anything: a forward needs a matching entry in `firewall.rules`.
|
||||
'';
|
||||
type = types.listOf (
|
||||
types.submodule {
|
||||
options = {
|
||||
type = mkOption {
|
||||
type = types.enum [
|
||||
"dnat"
|
||||
"snat"
|
||||
"masquerade"
|
||||
];
|
||||
description = "Kind of translation.";
|
||||
};
|
||||
from = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Zone the traffic comes from, or `wan`. For `snat` and `masquerade` null means any.";
|
||||
};
|
||||
to = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
`dnat`: the zone of the host the traffic is sent to. `snat` and
|
||||
`masquerade`: the zone (or `wan`) the traffic leaves through.
|
||||
'';
|
||||
};
|
||||
protocol = mkOption {
|
||||
type = types.enum [
|
||||
"any"
|
||||
"tcp"
|
||||
"udp"
|
||||
"icmp"
|
||||
];
|
||||
default = "any";
|
||||
};
|
||||
ports = mkOption {
|
||||
type = types.listOf (types.either types.port (types.strMatching "[0-9]+-[0-9]+"));
|
||||
default = [ ];
|
||||
description = "Destination ports or ranges to match (tcp/udp only); empty means all.";
|
||||
};
|
||||
sourcePorts = mkOption {
|
||||
type = types.listOf (types.either types.port (types.strMatching "[0-9]+-[0-9]+"));
|
||||
default = [ ];
|
||||
description = "Source ports or ranges to match (tcp/udp only).";
|
||||
};
|
||||
sources = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
description = "Only match traffic from these IPv4 addresses or CIDRs; empty means any.";
|
||||
};
|
||||
destination = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "203.0.113.7";
|
||||
description = ''
|
||||
Only match traffic addressed to this IPv4 address or CIDR.
|
||||
For `dnat` null means the router's own addresses (from `wan`:
|
||||
anything that arrives on the uplink).
|
||||
'';
|
||||
};
|
||||
toAddress = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "192.168.10.5";
|
||||
description = "`dnat`: the host to send the traffic to. `snat`: the source address to use. Not for `masquerade`.";
|
||||
};
|
||||
toPort = mkOption {
|
||||
type = types.nullOr types.port;
|
||||
default = null;
|
||||
description = "`dnat`: the port on the host when it differs (needs exactly one `ports` entry).";
|
||||
};
|
||||
hairpin = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
`dnat` from `wan` only: let LANs with `internet = true` reach
|
||||
the forward through the router's public address (NAT
|
||||
loopback, the source is masqueraded) and allow it to the host.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
firewall.rules = mkOption {
|
||||
default = [ ];
|
||||
description = ''
|
||||
@@ -402,7 +496,8 @@ in
|
||||
the reserved zone `wan` (traffic arriving on the uplinks), which
|
||||
publishes services to the outside: such rules must name a
|
||||
`destination` and protocol (and ports for tcp/udp), so a typo cannot
|
||||
expose a whole zone.
|
||||
expose a whole zone. Address translation is separate (`firewall.nat`);
|
||||
a port forward needs an allow rule as well, usually with `natOnly`.
|
||||
'';
|
||||
type = types.listOf (
|
||||
types.submodule {
|
||||
@@ -433,15 +528,15 @@ in
|
||||
];
|
||||
description = ''
|
||||
Destination ports or ranges (tcp/udp only); empty means all
|
||||
ports. With `dnat` these are the ports on the router's
|
||||
public address.
|
||||
ports. After a port forward these are the translated ports
|
||||
(its `toPort`, if it has one).
|
||||
'';
|
||||
};
|
||||
sourcePorts = mkOption {
|
||||
type = types.listOf (types.either types.port (types.strMatching "[0-9]+-[0-9]+"));
|
||||
default = [ ];
|
||||
example = [ 53 ];
|
||||
description = "Source ports or ranges (tcp/udp only); empty means any. Not for port forwards.";
|
||||
description = "Source ports or ranges (tcp/udp only); empty means any.";
|
||||
};
|
||||
destination = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
@@ -460,26 +555,13 @@ in
|
||||
example = [ "198.51.100.0/24" ];
|
||||
description = "Only match traffic from these IPv4/IPv6 addresses or CIDRs; empty means any source.";
|
||||
};
|
||||
dnat = mkOption {
|
||||
natOnly = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Port forward: traffic from `wan` to `ports` on the router's
|
||||
public IPv4 address is redirected to `destination` (one IPv4
|
||||
host). The source address is preserved.
|
||||
'';
|
||||
};
|
||||
toPort = mkOption {
|
||||
type = types.nullOr types.port;
|
||||
default = null;
|
||||
description = "Port on `destination` when it differs from the public port (single-port forwards only).";
|
||||
};
|
||||
hairpin = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Let LANs with `internet = true` reach this forward through the
|
||||
router's public address (NAT loopback, source is masqueraded).
|
||||
Only match connections that a `firewall.nat` port forward
|
||||
translated (the allow rule that goes with a forward), not
|
||||
traffic addressed directly to the destination.
|
||||
'';
|
||||
};
|
||||
rateLimit = mkOption {
|
||||
|
||||
Reference in New Issue
Block a user