{%+ include("rule.uc", { fw4, rule }) %}
{% endfor %}
{% for (let zone in fw4.zones()): for (let rule in zone.match_rules): %}
- {%+ include("zone-match.uc", { fw4, zone, rule, direction: "input" }) %}
+ {%+ include("zone-jump.uc", { fw4, zone, rule, direction: "input" }) %}
{% endfor; endfor %}
{% if (fw4.input_policy() == "reject"): %}
jump handle_reject
{%+ include("rule.uc", { fw4, rule }) %}
{% endfor %}
{% for (let zone in fw4.zones()): for (let rule in zone.match_rules): %}
- {%+ include("zone-match.uc", { fw4, zone, rule, direction: "forward" }) %}
+ {%+ include("zone-jump.uc", { fw4, zone, rule, direction: "forward" }) %}
{% endfor; endfor %}
{% if (fw4.forward_policy() == "reject"): %}
jump handle_reject
{%+ include("rule.uc", { fw4, rule }) %}
{% endfor %}
{% for (let zone in fw4.zones()): for (let rule in zone.match_rules): %}
- {%+ include("zone-match.uc", { fw4, zone, rule, direction: "output" }) %}
+ {%+ include("zone-jump.uc", { fw4, zone, rule, direction: "output" }) %}
{% endfor; endfor %}
{% if (fw4.output_policy() == "reject"): %}
jump handle_reject
{% for (let zone in fw4.zones()): %}
{% if (zone.dflags.dnat): %}
{% for (let rule in zone.match_rules): %}
- {%+ include("zone-match.uc", { fw4, zone, rule, direction: "dstnat" }) %}
+ {%+ include("zone-jump.uc", { fw4, zone, rule, direction: "dstnat" }) %}
{% endfor %}
{% endif %}
{% endfor %}
{% for (let zone in fw4.zones()): %}
{% if (zone.dflags.snat): %}
{% for (let rule in zone.match_rules): %}
- {%+ include("zone-match.uc", { fw4, zone, rule, direction: "srcnat" }) %}
+ {%+ include("zone-jump.uc", { fw4, zone, rule, direction: "srcnat" }) %}
{% endfor %}
{% endif %}
{% endfor %}
--- /dev/null
+{%+ if (rule.family): -%}
+ meta nfproto {{ fw4.nfproto(rule.family) }} {%+ endif -%}
+{%+ include("zone-match.uc", { egress: (direction in ["output", "srcnat"]), rule }) -%}
+jump {{ direction }}_{{ zone.name }} comment "!fw4: Handle {{ zone.name }} {{
+ fw4.nfproto(rule.family, true)
+}} {{ direction }} traffic"
-{%+ if (rule.family): -%}
- meta nfproto {{ fw4.nfproto(rule.family) }} {%+ endif -%}
{%+ if (rule.devices_pos): -%}
- {{ (direction in ["output", "srcnat"])
- ? "oifname" : "iifname" }} {{ fw4.set(rule.devices_pos) }} {%+ endif -%}
+ {{ egress ? "oifname" : "iifname" }} {{ fw4.set(rule.devices_pos) }} {%+ endif -%}
{%+ if (rule.devices_neg): -%}
- {{ (direction in ["output", "srcnat"])
- ? "oifname" : "iifname"
- }} != {{ fw4.set(rule.devices_neg) }} {%+ endif -%}
+ {{ egress ? "oifname" : "iifname" }} != {{ fw4.set(rule.devices_neg) }} {%+ endif -%}
+{%+ for (let wcndev in rule.devices_neg_wildcard): -%}
+ {{ egress ? "oifname" : "iifname" }} != {{ fw4.quote(wcndev) }} {%+ endfor -%}
{%+ if (rule.subnets_pos): -%}
- {{ fw4.ipproto(rule.family) }} {{
- (direction in ["output", "srcnat"]) ? "daddr" : "saddr"
- }} {{ fw4.set(rule.subnets_pos) }} {%+ endif -%}
+ {{ fw4.ipproto(rule.family) }} {{ egress ? "daddr" : "saddr" }} {{ fw4.set(rule.subnets_pos) }} {%+ endif -%}
{%+ if (rule.subnets_neg): -%}
- {{ fw4.ipproto(rule.family) }} {{
- (direction in ["output", "srcnat"]) ? "daddr" : "saddr"
- }} != {{ fw4.set(rule.subnets_neg) }} {%+ endif -%}
-jump {{ direction }}_{{ zone.name }} comment "!fw4: Handle {{ zone.name }} {{
- fw4.nfproto(rule.family, true)
-}} {{ direction }} traffic"
+ {{ fw4.ipproto(rule.family) }} {{ egress ? "daddr" : "saddr" }} != {{ fw4.set(rule.subnets_neg) }} {%+ endif -%}
{%+ if (rule.family): -%}
meta nfproto {{ fw4.nfproto(rule.family) }} {%+ endif -%}
-{%+ if (rule.devices_pos): -%}
- {{ egress ? "oifname" : "iifname" }} {{ fw4.set(rule.devices_pos) }} {%+ endif -%}
-{%+ if (rule.devices_neg): -%}
- {{ egress ? "oifname" : "iifname" }} != {{ fw4.set(rule.devices_neg) }} {%+ endif -%}
-{%+ if (rule.subnets_pos): -%}
- {{ fw4.ipproto(rule.family) }} {{ egress ? "daddr" : "saddr" }} {{ fw4.set(rule.subnets_pos) }} {%+ endif -%}
-{%+ if (rule.subnets_neg): -%}
- {{ fw4.ipproto(rule.family) }} {{ egress ? "daddr" : "saddr" }} != {{ fw4.set(rule.subnets_neg) }} {%+ endif -%}
+{%+ include("zone-match.uc", { egress, rule }) -%}
tcp flags syn tcp option maxseg size set rt mtu {%+ if (zone.log & 2): -%}
log prefix "MSSFIX {{ zone.name }} out: " {%+ endif -%}
comment "!fw4: Zone {{ zone.name }} {{
{%+ if (rule.family): -%}
meta nfproto {{ fw4.nfproto(rule.family) }} {%+ endif -%}
-{%+ if (rule.devices_pos): -%}
- {{ egress ? "oifname" : "iifname" }} {{ fw4.set(rule.devices_pos) }} {%+ endif -%}
-{%+ if (rule.devices_neg): -%}
- {{ egress ? "oifname" : "iifname"
- }} != {{ fw4.set(rule.devices_neg) }} {%+ endif -%}
-{%+ if (rule.subnets_pos): -%}
- {{ fw4.ipproto(rule.family) }} {{ egress ? "daddr" : "saddr" }} {{ fw4.set(rule.subnets_pos) }} {%+ endif -%}
-{%+ if (rule.subnets_neg): -%}
- {{ fw4.ipproto(rule.family) }} {{ egress ? "daddr" : "saddr" }} != {{ fw4.set(rule.subnets_neg) }} {%+ endif -%}
+{%+ include("zone-match.uc", { egress, rule }) -%}
{%+ if (zone.counter): -%}
counter {%+ endif -%}
{%+ if (verdict != "accept" && (zone.log & 1)): -%}
return length(rv) ? rv : null;
}
+function filter_neg_nonwildcard(x) {
+ let rv = filter(x, e => e.invert && !e.wildcard);
+ return length(rv) ? rv : null;
+}
+
+function filter_neg_wildcard(x) {
+ let rv = filter(x, e => e.invert && e.wildcard);
+ return length(rv) ? rv : null;
+}
+
function subnets_split_af(x) {
let rv = [];
r.family = family;
r.devices_pos = map(filter_pos(devices), d => d.device);
- r.devices_neg = map(filter_neg(devices), d => d.device);
+ r.devices_neg = map(filter_neg_nonwildcard(devices), d => d.device);
+ r.devices_neg_wildcard = map(filter_neg_wildcard(devices), d => d.device);
r.subnets_pos = map(filter_pos(subnets), this.cidr);
r.subnets_neg = map(filter_neg(subnets), this.cidr);
zone.helper, "ct helper"
]);
+ // group non-inverted device matches into wildcard and non-wildcard ones
+ let wildcard_devices = [], plain_devices = [], match_all_devices = false;
+
+ for (let device in match_devices) {
+ let m = match(device.device, /^([^+]*)(\+)?$/);
+
+ if (!m) {
+ this.warn_section(data, "skipping invalid wildcard pattern '" + device.device + '"');
+ continue;
+ }
+
+ // filter `+` (match any device) since nftables does not support
+ // wildcard only matches
+ if (!device.invert && m[0] == '+') {
+ match_all_devices = true;
+ continue;
+ }
+
+ // replace inverted `+` (match no device) with invalid pattern
+ if (device.invert && m[0] == '+') {
+ device.device = '/never/';
+ device.invert = false;
+ }
+
+ // replace "name+" matches with "name*"
+ else if (m[2] == '+')
+ device.device = m[1] + '*';
+
+ device.wildcard = !!m[2];
+
+ if (!device.invert && device.wildcard)
+ push(wildcard_devices, device);
+ else
+ push(plain_devices, device);
+ }
+
+ // loop wildcard devices
+ for (let wildcard_device in wildcard_devices) {
+ // cover this wildcard and any inverted wildcard or non-wildcard interface
+ let devices = [ wildcard_device, ...(filter_neg(plain_devices) || []) ];
+
+ // check if there's no AF specific bits, in this case we can do AF agnostic matching
+ if (!family && length(devices) && !length(match_subnets[0]) && !length(match_subnets[1])) {
+ add_rule(0, devices, null, zone);
+ }
+
+ // we need to emit one or two AF specific rules
+ else {
+ if (family_is_ipv4(zone) && (length(devices) || length(match_subnets[0])))
+ add_rule(4, devices, match_subnets[0], zone);
+
+ if (family_is_ipv6(zone) && (length(devices) || length(match_subnets[1])))
+ add_rule(6, devices, match_subnets[1], zone);
+ }
+ }
+
// check if there's no AF specific bits, in this case we can do AF agnostic matching
- if (!family && length(match_devices) && !length(match_subnets[0]) && !length(match_subnets[1])) {
- add_rule(0, match_devices, null, zone);
+ if (!family && (match_all_devices || length(plain_devices)) && !length(match_subnets[0]) && !length(match_subnets[1])) {
+ add_rule(0, plain_devices, null, zone);
}
// we need to emit one or two AF specific rules
else {
- if (family_is_ipv4(zone) && (length(match_devices) || length(match_subnets[0])))
- add_rule(4, match_devices, match_subnets[0], zone);
+ if (family_is_ipv4(zone) && (match_all_devices || length(plain_devices) || length(match_subnets[0])))
+ add_rule(4, plain_devices, match_subnets[0], zone);
- if (family_is_ipv6(zone) && (length(match_devices) || length(match_subnets[1])))
- add_rule(6, match_devices, match_subnets[1], zone);
+ if (family_is_ipv6(zone) && (match_all_devices || length(plain_devices) || length(match_subnets[1])))
+ add_rule(6, plain_devices, match_subnets[1], zone);
}
zone.match_rules = match_rules;
--- /dev/null
+Test that wildcard devices are properly handled.
+
+-- Testcase --
+{%
+ include("./root/usr/share/firewall4/main.uc", {
+ getenv: function(varname) {
+ switch (varname) {
+ case 'ACTION':
+ return 'print';
+ }
+ }
+ })
+%}
+-- End --
+
+-- File uci/helpers.json --
+{}
+-- End --
+
+-- File fs/open~_sys_class_net_never_flags.txt --
+0x0
+-- End --
+
+-- File fs/open~_sys_class_net_test_flags.txt --
+0x0
+-- End --
+
+-- File fs/open~_sys_class_net_foo_flags.txt --
+0x0
+-- End --
+
+-- File fs/open~_sys_class_net_bar_flags.txt --
+0x0
+-- End --
+
+-- File fs/open~_sys_class_net_baz_flags.txt --
+0x0
+-- End --
+
+-- File fs/open~_sys_class_net_qrx_flags.txt --
+0x0
+-- End --
+
+-- File fs/open~_sys_class_net_test1_flags.txt --
+0x1103
+-- End --
+
+-- File fs/open~_sys_class_net_test2_flags.txt --
+0x1103
+-- End --
+
+-- File uci/firewall.json --
+{
+ "zone": [
+ {
+ ".description": "A '+' device match should translate to no ifname match at all",
+ "name": "test1",
+ "device": [ "+" ]
+ },
+ {
+ ".description": "An inverted '+' device match should result in a match that always fails",
+ "name": "test2",
+ "device": [ "!+" ]
+ },
+ {
+ ".description": "A 'name+' device match should translate to an nft wildcard pattern",
+ "name": "test3",
+ "device": [ "test+" ]
+ },
+ {
+ ".description": "Wildcard matches must not be grouped into sets",
+ "name": "test4",
+ "device": [ "foo+", "bar+", "test1", "test2" ]
+ },
+ {
+ ".description": "Multiple inverted wildcard matches may be grouped into one rule",
+ "name": "test5",
+ "device": [ "foo+", "bar+", "!baz+", "!qrx+", "test1", "test2", "!test3", "!test4" ]
+ }
+ ]
+}
+-- End --
+
+-- Expect stdout --
+table inet fw4
+flush table inet fw4
+
+table inet fw4 {
+ #
+ # Set definitions
+ #
+
+
+ #
+ # Defines
+ #
+
+ define test1_devices = { "+" }
+ define test2_devices = { "/never/" }
+ define test3_devices = { "test*" }
+ define test4_devices = { "foo*", "bar*", "test1", "test2" }
+ define test5_devices = { "foo*", "bar*", "test1", "test2" }
+
+ #
+ # User includes
+ #
+
+ include "/etc/nftables.d/*.nft"
+
+
+ #
+ # Filter rules
+ #
+
+ chain input {
+ type filter hook input priority filter; policy drop;
+
+ iifname "lo" accept comment "!fw4: Accept traffic from loopback"
+
+ ct state established,related accept comment "!fw4: Allow inbound established and related flows"
+ jump input_test1 comment "!fw4: Handle test1 IPv4/IPv6 input traffic"
+ iifname "/never/" jump input_test2 comment "!fw4: Handle test2 IPv4/IPv6 input traffic"
+ iifname "test*" jump input_test3 comment "!fw4: Handle test3 IPv4/IPv6 input traffic"
+ iifname "foo*" jump input_test4 comment "!fw4: Handle test4 IPv4/IPv6 input traffic"
+ iifname "bar*" jump input_test4 comment "!fw4: Handle test4 IPv4/IPv6 input traffic"
+ iifname { "test1", "test2" } jump input_test4 comment "!fw4: Handle test4 IPv4/IPv6 input traffic"
+ iifname "foo*" iifname != { "test3", "test4" } iifname != "baz*" iifname != "qrx*" jump input_test5 comment "!fw4: Handle test5 IPv4/IPv6 input traffic"
+ iifname "bar*" iifname != { "test3", "test4" } iifname != "baz*" iifname != "qrx*" jump input_test5 comment "!fw4: Handle test5 IPv4/IPv6 input traffic"
+ iifname { "test1", "test2" } iifname != { "test3", "test4" } iifname != "baz*" iifname != "qrx*" jump input_test5 comment "!fw4: Handle test5 IPv4/IPv6 input traffic"
+ }
+
+ chain forward {
+ type filter hook forward priority filter; policy drop;
+
+ ct state established,related accept comment "!fw4: Allow forwarded established and related flows"
+ jump forward_test1 comment "!fw4: Handle test1 IPv4/IPv6 forward traffic"
+ iifname "/never/" jump forward_test2 comment "!fw4: Handle test2 IPv4/IPv6 forward traffic"
+ iifname "test*" jump forward_test3 comment "!fw4: Handle test3 IPv4/IPv6 forward traffic"
+ iifname "foo*" jump forward_test4 comment "!fw4: Handle test4 IPv4/IPv6 forward traffic"
+ iifname "bar*" jump forward_test4 comment "!fw4: Handle test4 IPv4/IPv6 forward traffic"
+ iifname { "test1", "test2" } jump forward_test4 comment "!fw4: Handle test4 IPv4/IPv6 forward traffic"
+ iifname "foo*" iifname != { "test3", "test4" } iifname != "baz*" iifname != "qrx*" jump forward_test5 comment "!fw4: Handle test5 IPv4/IPv6 forward traffic"
+ iifname "bar*" iifname != { "test3", "test4" } iifname != "baz*" iifname != "qrx*" jump forward_test5 comment "!fw4: Handle test5 IPv4/IPv6 forward traffic"
+ iifname { "test1", "test2" } iifname != { "test3", "test4" } iifname != "baz*" iifname != "qrx*" jump forward_test5 comment "!fw4: Handle test5 IPv4/IPv6 forward traffic"
+ }
+
+ chain output {
+ type filter hook output priority filter; policy drop;
+
+ oifname "lo" accept comment "!fw4: Accept traffic towards loopback"
+
+ ct state established,related accept comment "!fw4: Allow outbound established and related flows"
+ jump output_test1 comment "!fw4: Handle test1 IPv4/IPv6 output traffic"
+ oifname "/never/" jump output_test2 comment "!fw4: Handle test2 IPv4/IPv6 output traffic"
+ oifname "test*" jump output_test3 comment "!fw4: Handle test3 IPv4/IPv6 output traffic"
+ oifname "foo*" jump output_test4 comment "!fw4: Handle test4 IPv4/IPv6 output traffic"
+ oifname "bar*" jump output_test4 comment "!fw4: Handle test4 IPv4/IPv6 output traffic"
+ oifname { "test1", "test2" } jump output_test4 comment "!fw4: Handle test4 IPv4/IPv6 output traffic"
+ oifname "foo*" oifname != { "test3", "test4" } oifname != "baz*" oifname != "qrx*" jump output_test5 comment "!fw4: Handle test5 IPv4/IPv6 output traffic"
+ oifname "bar*" oifname != { "test3", "test4" } oifname != "baz*" oifname != "qrx*" jump output_test5 comment "!fw4: Handle test5 IPv4/IPv6 output traffic"
+ oifname { "test1", "test2" } oifname != { "test3", "test4" } oifname != "baz*" oifname != "qrx*" jump output_test5 comment "!fw4: Handle test5 IPv4/IPv6 output traffic"
+ }
+
+ chain handle_reject {
+ meta l4proto tcp reject with tcp reset comment "!fw4: Reject TCP traffic"
+ reject with icmpx type port-unreachable comment "!fw4: Reject any other traffic"
+ }
+
+ chain input_test1 {
+ jump drop_from_test1
+ }
+
+ chain output_test1 {
+ jump drop_to_test1
+ }
+
+ chain forward_test1 {
+ jump drop_to_test1
+ }
+
+ chain drop_from_test1 {
+ counter drop comment "!fw4: drop test1 IPv4/IPv6 traffic"
+ }
+
+ chain drop_to_test1 {
+ counter drop comment "!fw4: drop test1 IPv4/IPv6 traffic"
+ }
+
+ chain input_test2 {
+ jump drop_from_test2
+ }
+
+ chain output_test2 {
+ jump drop_to_test2
+ }
+
+ chain forward_test2 {
+ jump drop_to_test2
+ }
+
+ chain drop_from_test2 {
+ iifname "/never/" counter drop comment "!fw4: drop test2 IPv4/IPv6 traffic"
+ }
+
+ chain drop_to_test2 {
+ oifname "/never/" counter drop comment "!fw4: drop test2 IPv4/IPv6 traffic"
+ }
+
+ chain input_test3 {
+ jump drop_from_test3
+ }
+
+ chain output_test3 {
+ jump drop_to_test3
+ }
+
+ chain forward_test3 {
+ jump drop_to_test3
+ }
+
+ chain drop_from_test3 {
+ iifname "test*" counter drop comment "!fw4: drop test3 IPv4/IPv6 traffic"
+ }
+
+ chain drop_to_test3 {
+ oifname "test*" counter drop comment "!fw4: drop test3 IPv4/IPv6 traffic"
+ }
+
+ chain input_test4 {
+ jump drop_from_test4
+ }
+
+ chain output_test4 {
+ jump drop_to_test4
+ }
+
+ chain forward_test4 {
+ jump drop_to_test4
+ }
+
+ chain drop_from_test4 {
+ iifname "foo*" counter drop comment "!fw4: drop test4 IPv4/IPv6 traffic"
+ iifname "bar*" counter drop comment "!fw4: drop test4 IPv4/IPv6 traffic"
+ iifname { "test1", "test2" } counter drop comment "!fw4: drop test4 IPv4/IPv6 traffic"
+ }
+
+ chain drop_to_test4 {
+ oifname "foo*" counter drop comment "!fw4: drop test4 IPv4/IPv6 traffic"
+ oifname "bar*" counter drop comment "!fw4: drop test4 IPv4/IPv6 traffic"
+ oifname { "test1", "test2" } counter drop comment "!fw4: drop test4 IPv4/IPv6 traffic"
+ }
+
+ chain input_test5 {
+ jump drop_from_test5
+ }
+
+ chain output_test5 {
+ jump drop_to_test5
+ }
+
+ chain forward_test5 {
+ jump drop_to_test5
+ }
+
+ chain drop_from_test5 {
+ iifname "foo*" iifname != { "test3", "test4" } iifname != "baz*" iifname != "qrx*" counter drop comment "!fw4: drop test5 IPv4/IPv6 traffic"
+ iifname "bar*" iifname != { "test3", "test4" } iifname != "baz*" iifname != "qrx*" counter drop comment "!fw4: drop test5 IPv4/IPv6 traffic"
+ iifname { "test1", "test2" } iifname != { "test3", "test4" } iifname != "baz*" iifname != "qrx*" counter drop comment "!fw4: drop test5 IPv4/IPv6 traffic"
+ }
+
+ chain drop_to_test5 {
+ oifname "foo*" oifname != { "test3", "test4" } oifname != "baz*" oifname != "qrx*" counter drop comment "!fw4: drop test5 IPv4/IPv6 traffic"
+ oifname "bar*" oifname != { "test3", "test4" } oifname != "baz*" oifname != "qrx*" counter drop comment "!fw4: drop test5 IPv4/IPv6 traffic"
+ oifname { "test1", "test2" } oifname != { "test3", "test4" } oifname != "baz*" oifname != "qrx*" counter drop comment "!fw4: drop test5 IPv4/IPv6 traffic"
+ }
+
+
+ #
+ # NAT rules
+ #
+
+ chain dstnat {
+ type nat hook prerouting priority dstnat; policy accept;
+ }
+
+ chain srcnat {
+ type nat hook postrouting priority srcnat; policy accept;
+ }
+
+
+ #
+ # Raw rules (notrack & helper)
+ #
+
+ chain raw_prerouting {
+ type filter hook prerouting priority raw; policy accept;
+ iifname "/never/" jump helper_test2 comment "!fw4: test2 IPv4/IPv6 CT helper assignment"
+ iifname "test*" jump helper_test3 comment "!fw4: test3 IPv4/IPv6 CT helper assignment"
+ iifname "foo*" jump helper_test4 comment "!fw4: test4 IPv4/IPv6 CT helper assignment"
+ iifname "bar*" jump helper_test4 comment "!fw4: test4 IPv4/IPv6 CT helper assignment"
+ iifname { "test1", "test2" } jump helper_test4 comment "!fw4: test4 IPv4/IPv6 CT helper assignment"
+ iifname "foo*" iifname != { "test3", "test4" } jump helper_test5 comment "!fw4: test5 IPv4/IPv6 CT helper assignment"
+ iifname "bar*" iifname != { "test3", "test4" } jump helper_test5 comment "!fw4: test5 IPv4/IPv6 CT helper assignment"
+ iifname { "test1", "test2" } iifname != { "test3", "test4" } jump helper_test5 comment "!fw4: test5 IPv4/IPv6 CT helper assignment"
+ }
+
+ chain raw_output {
+ type filter hook output priority raw; policy accept;
+ }
+
+ chain helper_test1 {
+ }
+
+ chain helper_test2 {
+ }
+
+ chain helper_test3 {
+ }
+
+ chain helper_test4 {
+ }
+
+ chain helper_test5 {
+ }
+
+
+ #
+ # Mangle rules
+ #
+
+ chain mangle_prerouting {
+ type filter hook prerouting priority mangle; policy accept;
+ }
+
+ chain mangle_postrouting {
+ type filter hook postrouting priority mangle; policy accept;
+ }
+
+ chain mangle_input {
+ type filter hook input priority mangle; policy accept;
+ }
+
+ chain mangle_output {
+ type filter hook output priority mangle; policy accept;
+ }
+
+ chain mangle_forward {
+ type filter hook forward priority mangle; policy accept;
+ }
+}
+-- End --