fw4: fix handling the ipset "comment" option
authorJo-Philipp Wich <jo@mein.io>
Sat, 7 Jan 2023 16:00:18 +0000 (17:00 +0100)
committerJo-Philipp Wich <jo@mein.io>
Fri, 3 Feb 2023 11:04:15 +0000 (12:04 +0100)
The comment option for ipset definitions is incorrectly declared as bool
and not actually used anywhere in the nftables output rendering.

Solve this issue by changing it to the proper "string" type and expose
the user configured comment as "comment" property in the generated nftables
output.

Also add some initial test coverage for ipset declarations to better spot
such inconsistencies in the future.

Ref: https://github.com/openwrt/luci/pull/6187#issuecomment-1374506633
Reported-by: Paul Dee <itsascambutmailmeanyway@gmail.com>
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
15 files changed:
root/usr/share/firewall4/templates/mangle-rule.uc [new file with mode: 0644]
root/usr/share/firewall4/templates/ruleset.uc
root/usr/share/firewall4/templates/ruleset.uc.orig [new file with mode: 0644]
root/usr/share/firewall4/templates/ruleset.uc.rej [new file with mode: 0644]
root/usr/share/ucode/fw4.uc
tests/05_includes/01_nft_includes [deleted file]
tests/05_includes/02_firewall.user_include [deleted file]
tests/05_includes/03_script_includes [deleted file]
tests/05_includes/04_disabled_include [deleted file]
tests/05_ipsets/01_declaration [new file with mode: 0644]
tests/05_ipsets/02_usage [new file with mode: 0644]
tests/06_includes/01_nft_includes [new file with mode: 0644]
tests/06_includes/02_firewall.user_include [new file with mode: 0644]
tests/06_includes/03_script_includes [new file with mode: 0644]
tests/06_includes/04_disabled_include [new file with mode: 0644]

diff --git a/root/usr/share/firewall4/templates/mangle-rule.uc b/root/usr/share/firewall4/templates/mangle-rule.uc
new file mode 100644 (file)
index 0000000..90637bb
--- /dev/null
@@ -0,0 +1,93 @@
+{%+ for (let src_devices in rule.src?.zone) }
+
+{%+ if (rule.family && !rule.has_addrs): -%}
+       meta nfproto {{ fw4.nfproto(rule.family) }} {%+ endif -%}
+{%+ if (!rule.proto.any && !rule.has_ports && !rule.icmp_types && !rule.icmp_codes): -%}
+       meta l4proto {{
+               (rule.proto.name == 'icmp' && rule.family == 6) ? 'ipv6-icmp' : rule.proto.name
+       }} {%+ endif -%}
+{%+ if (rule.saddrs_pos): -%}
+       {{ fw4.ipproto(rule.family) }} saddr {{ fw4.set(rule.saddrs_pos) }} {%+ endif -%}
+{%+ if (rule.saddrs_neg): -%}
+       {{ fw4.ipproto(rule.family) }} saddr != {{ fw4.set(rule.saddrs_neg) }} {%+ endif -%}
+{%+ if (rule.daddrs_pos): -%}
+       {{ fw4.ipproto(rule.family) }} daddr {{ fw4.set(rule.daddrs_pos) }} {%+ endif -%}
+{%+ if (rule.daddrs_neg): -%}
+       {{ fw4.ipproto(rule.family) }} daddr != {{ fw4.set(rule.daddrs_neg) }} {%+ endif -%}
+{%+ if (rule.sports_pos): -%}
+       {{ rule.proto.name }} sport {{ fw4.set(rule.sports_pos) }} {%+ endif -%}
+{%+ if (rule.sports_neg): -%}
+       {{ rule.proto.name }} sport != {{ fw4.set(rule.sports_neg) }} {%+ endif -%}
+{%+ if (rule.dports_pos): -%}
+       {{ rule.proto.name }} dport {{ fw4.set(rule.dports_pos) }} {%+ endif -%}
+{%+ if (rule.dports_neg): -%}
+       {{ rule.proto.name }} dport != {{ fw4.set(rule.dports_neg) }} {%+ endif -%}
+{%+ if (rule.smacs_pos): -%}
+       ether saddr {{ fw4.set(rule.smacs_pos) }} {%+ endif -%}
+{%+ if (rule.smacs_neg): -%}
+       ether saddr != {{ fw4.set(rule.smacs_neg) }} {%+ endif -%}
+{%+ if (rule.icmp_types): -%}
+       {{ (rule.family == 4) ? "icmp" : "icmpv6" }} type {{ fw4.set(rule.icmp_types) }} {%+ endif -%}
+{%+ if (rule.icmp_codes): -%}
+       {{ (rule.family == 4) ? "icmp" : "icmpv6" }} type . {{ (rule.family == 4) ? "icmp" : "icmpv6" }} code {{
+               fw4.set(rule.icmp_codes, true)
+       }} {%+ endif -%}
+{%+ if (rule.helper): -%}
+       ct helper{% if (rule.helper.invert): %} !={% endif %} {{ fw4.quote(rule.helper.name, true) }} {%+ endif -%}
+{%+ if (rule.limit): -%}
+       limit rate {{ rule.limit.rate }}/{{ rule.limit.unit }}
+       {%- if (rule.limit_burst): %} burst {{ rule.limit_burst }} packets{% endif %} {%+ endif -%}
+{%+ if (rule.start_date): -%}
+       meta time >= {{
+               exists(rule.start_date, "hour") ? fw4.datetime(rule.start_date) : fw4.date(rule.start_date)
+       }} {%+ endif -%}
+{%+ if (rule.stop_date): -%}
+       meta time <= {{
+               exists(rule.stop_date, "hour") ? fw4.datetime(rule.stop_date) : fw4.date(rule.stop_date)
+       }} {%+ endif -%}
+{%+ if (rule.start_time): -%}
+       meta hour >= {{ fw4.time(rule.start_time) }} {%+ endif -%}
+{%+ if (rule.stop_time): -%}
+       meta hour <= {{ fw4.time(rule.stop_time) }} {%+ endif -%}
+{%+ if (rule.weekdays): -%}
+       meta day{% if (rule.weekdays.invert): %} !={% endif %} {{ fw4.set(rule.weekdays.days) }} {%+ endif -%}
+{%+ if (rule.mark && rule.mark.mask < 0xFFFFFFFF): -%}
+       meta mark and {{ fw4.hex(rule.mark.mask) }} {{
+               rule.mark.invert ? '!=' : '=='
+       }} {{ fw4.hex(rule.mark.mark) }} {%+ endif -%}
+{%+ if (rule.mark && rule.mark.mask == 0xFFFFFFFF): -%}
+       meta mark{% if (rule.mark.invert): %} !={% endif %} {{ fw4.hex(rule.mark.mark) }} {%+ endif -%}
+{%+ if (rule.dscp): -%}
+       dscp{% if (rule.dscp.invert): %} !={% endif %} {{ fw4.hex(rule.dscp.dscp) }} {%+ endif -%}
+{%+ if (rule.ipset): -%}
+       {{ fw4.concat(rule.ipset.fields) }}{{
+               rule.ipset.invert ? ' !=' : ''
+       }} @{{ rule.ipset.name }} {%+ endif -%}
+{%+ if (rule.counter): -%}
+       counter {%+ endif -%}
+{%+ if (rule.log): -%}
+       log prefix {{ fw4.quote(rule.log, true) }} {%+ endif -%}
+{%+ if (rule.target == "mark"): -%}
+       meta mark set {{
+               (rule.set_xmark.mask == 0xFFFFFFFF)
+                       ? fw4.hex(rule.set_xmark.mark)
+                       : (rule.set_xmark.mark == 0)
+                               ? 'mark and ' + fw4.hex(~rule.set_xmark.mask & 0xFFFFFFFF)
+                               : (rule.set_xmark.mark == rule.set_xmark.mask)
+                                       ? 'mark or ' + fw4.hex(rule.set_xmark.mark)
+                                       : (rule.set_xmark.mask == 0)
+                                               ? 'mark xor ' + fw4.hex(rule.set_xmark.mark)
+                                               : 'mark and ' + fw4.hex(~r.set_xmark.mask & 0xFFFFFFFF) + ' xor ' + fw4.hex(r.set_xmark.mark)
+       }} {%+
+   elif (rule.target == "dscp"): -%}
+       {{ fw4.ipproto(rule.family) }} dscp set {{ fw4.hex(rule.set_dscp.dscp) }} {%+
+   elif (rule.target == "notrack"): -%}
+       notrack {%+
+   elif (rule.target == "helper"): -%}
+       ct helper set {{ fw4.quote(rule.set_helper.name, true) }} {%+
+   elif (rule.jump_chain): -%}
+       jump {{ rule.jump_chain }} {%+
+   elif (rule.target): -%}
+       {{ rule.target }} {%+
+   endif -%}
+comment {{ fw4.quote(`!fw4: ${rule.name}`, true) }}
index d6eedfd55d599b7982cfab2bd1f93ddfc9dca50e..9537ba2879603c5a52a2d23d79f667759a03fdb7 100644 (file)
@@ -49,6 +49,9 @@ table inet fw4 {
 
 {%  for (let set in defined_ipsets): %}
        set {{ set.name }} {
+{%   if (set.comment): %}
+               comment {{ fw4.quote(set.comment, true) }}
+{%   endif %}
                type {{ fw4.concat(set.types) }}
 {%   if (set.maxelem > 0): %}
                size {{ set.maxelem }}
diff --git a/root/usr/share/firewall4/templates/ruleset.uc.orig b/root/usr/share/firewall4/templates/ruleset.uc.orig
new file mode 100644 (file)
index 0000000..2e33d5d
--- /dev/null
@@ -0,0 +1,449 @@
+{%
+       let flowtable_devices = fw4.resolve_offload_devices();
+       let available_helpers = filter(fw4.helpers(), h => h.available);
+       let defined_ipsets = fw4.ipsets();
+-%}
+
+table inet fw4
+flush table inet fw4
+{% if (fw4.check_flowtable()): %}
+delete flowtable inet fw4 ft
+{% endif %}
+{% fw4.includes('ruleset-prepend') %}
+
+table inet fw4 {
+{% if (length(flowtable_devices) > 0): %}
+       #
+       # Flowtable
+       #
+
+       flowtable ft {
+               hook ingress priority 0;
+               devices = {{ fw4.set(flowtable_devices, true) }};
+{% if (fw4.default_option("flow_offloading_hw")): %}
+               flags offload;
+{% endif %}
+       }
+
+
+{% endif %}
+{% if (length(available_helpers)): %}
+       #
+       # CT helper definitions
+       #
+
+{%  for (let helper in available_helpers): %}
+{%   for (let proto in helper.proto): %}
+       ct helper {{ helper.name }} {
+               type {{ fw4.quote(helper.name, true) }} protocol {{ proto.name }};
+       }
+
+{%   endfor %}
+{%  endfor %}
+
+{% endif %}
+{% if (length(defined_ipsets)): %}
+       #
+       # Set definitions
+       #
+
+{%  for (let set in defined_ipsets): %}
+       set {{ set.name }} {
+               type {{ fw4.concat(set.types) }}
+{%   if (set.maxelem > 0): %}
+               size {{ set.maxelem }}
+{%   endif %}
+{%   if (set.timeout > 0): %}
+               timeout {{ set.timeout }}s
+{%   endif %}
+{%   if (set.interval): %}
+               auto-merge
+{%   endif %}
+{%   if (set.flags): %}
+               flags {{ join(',', set.flags) }}
+{%   endif %}
+{%   fw4.print_setentries(set) %}
+       }
+
+{%  endfor %}
+
+{% endif %}
+       #
+       # Defines
+       #
+
+{% for (let zone in fw4.zones()): %}
+       define {{ replace(zone.name, /^[0-9]/, '_$&') }}_devices = {{ fw4.set(zone.match_devices, true) }}
+       define {{ replace(zone.name, /^[0-9]/, '_$&') }}_subnets = {{ fw4.set(zone.match_subnets, true) }}
+
+{% endfor %}
+
+       #
+       # User includes
+       #
+
+       include "/etc/nftables.d/*.nft"
+{% fw4.includes('table-prepend') %}
+
+
+       #
+       # Filter rules
+       #
+
+       chain input {
+               type filter hook input priority filter; policy {{ fw4.input_policy(true) }};
+
+               iifname "lo" accept comment "!fw4: Accept traffic from loopback"
+
+{% fw4.includes('chain-prepend', 'input') %}
+               ct state established,related accept comment "!fw4: Allow inbound established and related flows"
+{% if (fw4.default_option("drop_invalid")): %}
+               ct state invalid drop comment "!fw4: Drop flows with invalid conntrack state"
+{% endif %}
+{% if (fw4.default_option("synflood_protect") && fw4.default_option("synflood_rate")): %}
+               tcp flags & (fin | syn | rst | ack) == syn jump syn_flood comment "!fw4: Rate limit TCP syn packets"
+{% endif %}
+{% for (let rule in fw4.rules("input")): %}
+               {%+ include("rule.uc", { fw4, rule }) %}
+{% endfor %}
+{% for (let zone in fw4.zones()): for (let rule in zone.match_rules): %}
+               {%+ include("zone-jump.uc", { fw4, zone, rule, direction: "input" }) %}
+{% endfor; endfor %}
+{% if (fw4.input_policy() == "reject"): %}
+               jump handle_reject
+{% endif %}
+{% fw4.includes('chain-append', 'input') %}
+       }
+
+       chain forward {
+               type filter hook forward priority filter; policy {{ fw4.forward_policy(true) }};
+
+{% if (length(flowtable_devices) > 0): %}
+               meta l4proto { tcp, udp } flow offload @ft;
+{% endif %}
+{% fw4.includes('chain-prepend', 'forward') %}
+               ct state established,related accept comment "!fw4: Allow forwarded established and related flows"
+{% if (fw4.default_option("drop_invalid")): %}
+               ct state invalid drop comment "!fw4: Drop flows with invalid conntrack state"
+{% endif %}
+{% for (let rule in fw4.rules("forward")): %}
+               {%+ include("rule.uc", { fw4, rule }) %}
+{% endfor %}
+{% for (let zone in fw4.zones()): for (let rule in zone.match_rules): %}
+               {%+ include("zone-jump.uc", { fw4, zone, rule, direction: "forward" }) %}
+{% endfor; endfor %}
+{% fw4.includes('chain-append', 'forward') %}
+{% if (fw4.forward_policy() == "reject"): %}
+               jump handle_reject
+{% endif %}
+       }
+
+       chain output {
+               type filter hook output priority filter; policy {{ fw4.output_policy(true) }};
+
+               oifname "lo" accept comment "!fw4: Accept traffic towards loopback"
+
+{% fw4.includes('chain-prepend', 'output') %}
+               ct state established,related accept comment "!fw4: Allow outbound established and related flows"
+{% if (fw4.default_option("drop_invalid")): %}
+               ct state invalid drop comment "!fw4: Drop flows with invalid conntrack state"
+{% endif %}
+{% for (let rule in fw4.rules("output")): %}
+               {%+ include("rule.uc", { fw4, rule }) %}
+{% endfor %}
+{% for (let zone in fw4.zones()): %}
+{%  for (let rule in zone.match_rules): %}
+{%   if (zone.dflags.helper): %}
+{%    let devices_pos = fw4.filter_loopback_devs(rule.devices_pos, true); %}
+{%    let subnets_pos = fw4.filter_loopback_addrs(rule.subnets_pos, true); %}
+{%    if (devices_pos || subnets_pos): %}
+               {%+ include("zone-jump.uc", { fw4, zone, rule: { ...rule, devices_pos, subnets_pos }, direction: "helper" }) %}
+{%    endif %}
+{%   endif %}
+               {%+ include("zone-jump.uc", { fw4, zone, rule, direction: "output" }) %}
+{%  endfor %}
+{% endfor %}
+{% fw4.includes('chain-append', 'output') %}
+{% if (fw4.output_policy() == "reject"): %}
+               jump handle_reject
+{% endif %}
+       }
+
+       chain prerouting {
+               type filter hook prerouting priority filter; policy accept;
+{% for (let zone in fw4.zones()): %}
+{%  if (zone.dflags.helper): %}
+{%   for (let rule in zone.match_rules): %}
+{%    let devices_pos = fw4.filter_loopback_devs(rule.devices_pos, false); %}
+{%    let subnets_pos = fw4.filter_loopback_addrs(rule.subnets_pos, false); %}
+{%    if (rule.devices_neg || rule.subnets_neg || devices_pos || subnets_pos): %}
+               {%+ include("zone-jump.uc", { fw4, zone, rule: { ...rule, devices_pos, subnets_pos }, direction: "helper" }) %}
+{%    endif %}
+{%   endfor %}
+{%  endif %}
+{% endfor %}
+       }
+
+       chain handle_reject {
+               meta l4proto tcp reject with {{
+                       (fw4.default_option("tcp_reject_code") != "tcp-reset")
+                               ? `icmpx type ${fw4.default_option("tcp_reject_code")}`
+                               : "tcp reset"
+               }} comment "!fw4: Reject TCP traffic"
+               reject with {{
+                       (fw4.default_option("any_reject_code") != "tcp-reset")
+                               ? `icmpx type ${fw4.default_option("any_reject_code")}`
+                               : "tcp reset"
+               }} comment "!fw4: Reject any other traffic"
+       }
+
+{% if (fw4.default_option("synflood_protect") && fw4.default_option("synflood_rate")):
+       let r = fw4.default_option("synflood_rate");
+       let b = fw4.default_option("synflood_burst");
+%}
+       chain syn_flood {
+               limit rate {{ r.rate }}/{{ r.unit }}
+               {%- if (b): %} burst {{ b }} packets{% endif %} return comment "!fw4: Accept SYN packets below rate-limit"
+               drop comment "!fw4: Drop excess packets"
+       }
+
+{% endif %}
+{% for (let zone in fw4.zones()): %}
+       chain input_{{ zone.name }} {
+{%  fw4.includes('chain-prepend', `input_${zone.name}`) %}
+{%  for (let rule in fw4.rules(`input_${zone.name}`)): %}
+               {%+ include("rule.uc", { fw4, rule }) %}
+{%  endfor %}
+{%  if (zone.dflags.dnat): %}
+               ct status dnat accept comment "!fw4: Accept port redirections"
+{%  endif %}
+{%  fw4.includes('chain-append', `input_${zone.name}`) %}
+               jump {{ zone.input }}_from_{{ zone.name }}
+       }
+
+       chain output_{{ zone.name }} {
+{%  fw4.includes('chain-prepend', `output_${zone.name}`) %}
+{%  for (let rule in fw4.rules(`output_${zone.name}`)): %}
+               {%+ include("rule.uc", { fw4, rule }) %}
+{%  endfor %}
+{%  fw4.includes('chain-append', `output_${zone.name}`) %}
+               jump {{ zone.output }}_to_{{ zone.name }}
+       }
+
+       chain forward_{{ zone.name }} {
+{%  fw4.includes('chain-prepend', `forward_${zone.name}`) %}
+{%  for (let rule in fw4.rules(`forward_${zone.name}`)): %}
+               {%+ include("rule.uc", { fw4, rule }) %}
+{%  endfor %}
+{%  if (zone.dflags.dnat): %}
+               ct status dnat accept comment "!fw4: Accept port forwards"
+{%  endif %}
+{%  fw4.includes('chain-append', `forward_${zone.name}`) %}
+               jump {{ zone.forward }}_to_{{ zone.name }}
+       }
+
+{%  if (zone.dflags.helper): %}
+       chain helper_{{ zone.name }} {
+{%   for (let rule in fw4.rules(`helper_${zone.name}`)): %}
+               {%+ include("rule.uc", { fw4, rule }) %}
+{%   endfor %}
+       }
+
+{%  endif %}
+{%  for (let verdict in ["accept", "reject", "drop"]): %}
+{%   if (zone.sflags[verdict]): %}
+       chain {{ verdict }}_from_{{ zone.name }} {
+{%    for (let rule in zone.match_rules): %}
+               {%+ include("zone-verdict.uc", { fw4, zone, rule, egress: false, verdict }) %}
+{%    endfor %}
+       }
+
+{%   endif %}
+{%   if (zone.dflags[verdict]): %}
+       chain {{ verdict }}_to_{{ zone.name }} {
+{%   for (let rule in zone.match_rules): %}
+               {%+ include("zone-verdict.uc", { fw4, zone, rule, egress: true, verdict }) %}
+{%   endfor %}
+       }
+
+{%   endif %}
+{%  endfor %}
+{% endfor %}
+
+       #
+       # NAT rules
+       #
+
+       chain dstnat {
+               type nat hook prerouting priority dstnat; policy accept;
+{% fw4.includes('chain-prepend', 'dstnat') %}
+{% for (let zone in fw4.zones()): %}
+{%  if (zone.dflags.dnat): %}
+{%   for (let rule in zone.match_rules): %}
+               {%+ include("zone-jump.uc", { fw4, zone, rule, direction: "dstnat" }) %}
+{%   endfor %}
+{%  endif %}
+{% endfor %}
+{% fw4.includes('chain-append', 'dstnat') %}
+       }
+
+       chain srcnat {
+               type nat hook postrouting priority srcnat; policy accept;
+{% fw4.includes('chain-prepend', 'srcnat') %}
+{% for (let redirect in fw4.redirects("srcnat")): %}
+               {%+ include("redirect.uc", { fw4, redirect }) %}
+{% endfor %}
+{% for (let zone in fw4.zones()): %}
+{%  if (zone.dflags.snat): %}
+{%   for (let rule in zone.match_rules): %}
+               {%+ include("zone-jump.uc", { fw4, zone, rule, direction: "srcnat" }) %}
+{%   endfor %}
+{%  endif %}
+{% endfor %}
+{% fw4.includes('chain-append', 'srcnat') %}
+       }
+
+{% for (let zone in fw4.zones()): %}
+{%  if (zone.dflags.dnat): %}
+       chain dstnat_{{ zone.name }} {
+{%   fw4.includes('chain-prepend', `dstnat_${zone.name}`) %}
+{%   for (let redirect in fw4.redirects(`dstnat_${zone.name}`)): %}
+               {%+ include("redirect.uc", { fw4, redirect }) %}
+{%   endfor %}
+{%   fw4.includes('chain-append', `dstnat_${zone.name}`) %}
+       }
+
+{%  endif %}
+{%  if (zone.dflags.snat): %}
+       chain srcnat_{{ zone.name }} {
+{%   fw4.includes('chain-prepend', `srcnat_${zone.name}`) %}
+{%   for (let redirect in fw4.redirects(`srcnat_${zone.name}`)): %}
+               {%+ include("redirect.uc", { fw4, redirect }) %}
+{%   endfor %}
+{%   if (zone.masq): %}
+{%    for (let saddrs in zone.masq4_src_subnets): %}
+{%     for (let daddrs in zone.masq4_dest_subnets): %}
+               {%+ include("zone-masq.uc", { fw4, zone, family: 4, saddrs, daddrs }) %}
+{%     endfor %}
+{%    endfor %}
+{%   endif %}
+{%   if (zone.masq6): %}
+{%    for (let saddrs in zone.masq6_src_subnets): %}
+{%     for (let daddrs in zone.masq6_dest_subnets): %}
+               {%+ include("zone-masq.uc", { fw4, zone, family: 6, saddrs, daddrs }) %}
+{%     endfor %}
+{%    endfor %}
+{%   endif %}
+{%   fw4.includes('chain-append', `srcnat_${zone.name}`) %}
+       }
+
+{%  endif %}
+{% endfor %}
+
+       #
+       # Raw rules (notrack)
+       #
+
+       chain raw_prerouting {
+               type filter hook prerouting priority raw; policy accept;
+{% for (let zone in fw4.zones()): %}
+{%  if (zone.dflags["notrack"]): %}
+{%   for (let rule in zone.match_rules): %}
+{%    let devices_pos = fw4.filter_loopback_devs(rule.devices_pos, false); %}
+{%    let subnets_pos = fw4.filter_loopback_addrs(rule.subnets_pos, false); %}
+{%    if (rule.devices_neg || rule.subnets_neg || devices_pos || subnets_pos): %}
+               {%+ include("zone-jump.uc", { fw4, zone, rule: { ...rule, devices_pos, subnets_pos }, direction: "notrack" }) %}
+{%    endif %}
+{%   endfor %}
+{%  endif %}
+{% endfor %}
+{% fw4.includes('chain-append', 'raw_prerouting') %}
+       }
+
+       chain raw_output {
+               type filter hook output priority raw; policy accept;
+{% fw4.includes('chain-prepend', 'raw_output') %}
+{% for (let zone in fw4.zones()): %}
+{%  if (zone.dflags["notrack"]): %}
+{%   for (let rule in zone.match_rules): %}
+{%    let devices_pos = fw4.filter_loopback_devs(rule.devices_pos, true); %}
+{%    let subnets_pos = fw4.filter_loopback_addrs(rule.subnets_pos, true); %}
+{%    if (devices_pos || subnets_pos): %}
+               {%+ include("zone-jump.uc", { fw4, zone, rule: { ...rule, devices_pos, subnets_pos }, direction: "notrack" }) %}
+{%    endif %}
+{%   endfor %}
+{%  endif %}
+{% endfor %}
+{% fw4.includes('chain-append', 'raw_output') %}
+       }
+
+{% for (let zone in fw4.zones()): %}
+{%   if (zone.dflags.notrack): %}
+       chain notrack_{{ zone.name }} {
+{% for (let rule in fw4.rules(`notrack_${zone.name}`)): %}
+               {%+ include("rule.uc", { fw4, rule }) %}
+{% endfor %}
+       }
+
+{%   endif %}
+{% endfor %}
+
+       #
+       # Mangle rules
+       #
+
+       chain mangle_prerouting {
+               type filter hook prerouting priority mangle; policy accept;
+{% fw4.includes('chain-prepend', 'mangle_prerouting') %}
+{% for (let rule in fw4.rules("mangle_prerouting")): %}
+               {%+ include("rule.uc", { fw4, rule }) %}
+{% endfor %}
+{% fw4.includes('chain-append', 'mangle_prerouting') %}
+       }
+
+       chain mangle_postrouting {
+               type filter hook postrouting priority mangle; policy accept;
+{% fw4.includes('chain-prepend', 'mangle_postrouting') %}
+{% for (let rule in fw4.rules("mangle_postrouting")): %}
+               {%+ include("rule.uc", { fw4, rule }) %}
+{% endfor %}
+{% fw4.includes('chain-append', 'mangle_postrouting') %}
+       }
+
+       chain mangle_input {
+               type filter hook input priority mangle; policy accept;
+{% fw4.includes('chain-prepend', 'mangle_input') %}
+{% for (let rule in fw4.rules("mangle_input")): %}
+               {%+ include("rule.uc", { fw4, rule }) %}
+{% endfor %}
+{% fw4.includes('chain-append', 'mangle_input') %}
+       }
+
+       chain mangle_output {
+               type route hook output priority mangle; policy accept;
+{% fw4.includes('chain-prepend', 'mangle_output') %}
+{% for (let rule in fw4.rules("mangle_output")): %}
+               {%+ include("rule.uc", { fw4, rule }) %}
+{% endfor %}
+{% fw4.includes('chain-append', 'mangle_output') %}
+       }
+
+       chain mangle_forward {
+               type filter hook forward priority mangle; policy accept;
+{% fw4.includes('chain-prepend', 'mangle_forward') %}
+{% for (let rule in fw4.rules("mangle_forward")): %}
+               {%+ include("rule.uc", { fw4, rule }) %}
+{% endfor %}
+{% for (let zone in fw4.zones()): %}
+{%  if (zone.mtu_fix): %}
+{%   for (let rule in zone.match_rules): %}
+               {%+ include("zone-mssfix.uc", { fw4, zone, rule, egress: false }) %}
+               {%+ include("zone-mssfix.uc", { fw4, zone, rule, egress: true }) %}
+{%   endfor %}
+{%  endif %}
+{% endfor %}
+{% fw4.includes('chain-append', 'mangle_forward') %}
+       }
+{% fw4.includes('table-append') %}
+}
+{% fw4.includes('ruleset-append') %}
diff --git a/root/usr/share/firewall4/templates/ruleset.uc.rej b/root/usr/share/firewall4/templates/ruleset.uc.rej
new file mode 100644 (file)
index 0000000..35e8dea
--- /dev/null
@@ -0,0 +1,12 @@
+--- root/usr/share/firewall4/templates/ruleset.uc
++++ root/usr/share/firewall4/templates/ruleset.uc
+@@ -240,6 +240,9 @@ table inet fw4 {
+ {%  endif %}
+ {%  fw4.includes('chain-append', `forward_${zone.name}`) %}
+                jump {{ zone.forward }}_to_{{ zone.name }}
++{%  if (fw4.forward_policy() != "accept" && (zone.log & 1)): %}
++               log prefix "{{ fw4.forward_policy() }} {{ zone.name }} forward: "
++{%  endif %}
+        }
+ {%  if (zone.dflags.helper): %}
index 5dce90dd09c2657f8fcc6dda0dfddb4f4ad13f91..0393508c159db612f7d41fe1f347f30eba60ec53 100644 (file)
@@ -3191,7 +3191,7 @@ return {
                        enabled: [ "bool", "1" ],
                        reload_set: [ "bool" ],
                        counters: [ "bool" ],
-                       comment: [ "bool" ],
+                       comment: [ "string" ],
 
                        name: [ "string", null, REQUIRED ],
                        family: [ "family", "4" ],
diff --git a/tests/05_includes/01_nft_includes b/tests/05_includes/01_nft_includes
deleted file mode 100644 (file)
index 314a428..0000000
+++ /dev/null
@@ -1,266 +0,0 @@
-Testing the correct placement of potential include positions.
-
--- 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_eth0_flags.txt --
-0x1103
--- End --
-
--- File fs/open~_usr_share_nftables_d_include-ruleset-start_nft.txt --
-# dummy
--- End --
-
--- File fs/open~_usr_share_nftables_d_include-table-start_nft.txt --
-# dummy
--- End --
-
--- File fs/open~_usr_share_nftables_d_include-chain-start-forward_nft.txt --
-# dummy
--- End --
-
--- File fs/open~_usr_share_nftables_d_include-chain-end-forward_nft.txt --
-# dummy
--- End --
-
--- File fs/open~_usr_share_nftables_d_include-table-end-1_nft.txt --
-# dummy
--- End --
-
--- File fs/open~_usr_share_nftables_d_include-table-end-2_nft.txt --
-# dummy
--- End --
-
--- File fs/open~_usr_share_nftables_d_include-ruleset-end_nft.txt --
-# dummy
--- End --
-
--- File uci/firewall.json --
-{
-       "zone": [
-               {
-                       "name": "test",
-                       "device": [ "eth0" ],
-                       "auto_helper": 0
-               }
-       ],
-       "include": [
-               {
-                       ".description": "Position 'table-pre' (or 'table-prepend') will place an include before the first chain",
-                       "path": "/usr/share/nftables.d/include-table-start.nft",
-                       "type": "nftables",
-                       "position": "table-pre"
-               },
-
-               {
-                       ".description": "Position defaults to 'table-append', means after the last chain in the table scope",
-                       "path": "/usr/share/nftables.d/include-table-end-1.nft",
-                       "type": "nftables"
-               },
-
-               {
-                       ".description": "Position 'table-post' (or 'table-postpend') may be used as alias for 'table-append'",
-                       "path": "/usr/share/nftables.d/include-table-end-2.nft",
-                       "type": "nftables",
-                       "position": "table-post"
-               },
-
-               {
-                       ".description": "Position 'ruleset-pre' (or 'ruleset-prepend') will place an include before the table declaration",
-                       "path": "/usr/share/nftables.d/include-ruleset-start.nft",
-                       "type": "nftables",
-                       "position": "ruleset-pre"
-               },
-
-               {
-                       ".description": "Position 'ruleset-post' (or 'ruleset-append') will place an include after the table declaration",
-                       "path": "/usr/share/nftables.d/include-ruleset-end.nft",
-                       "type": "nftables",
-                       "position": "ruleset-post"
-               },
-
-               {
-                       ".description": "Position 'chain-pre' (or 'chain-prepend') will place an include at the top of a specified chain",
-                       "path": "/usr/share/nftables.d/include-chain-start-forward.nft",
-                       "type": "nftables",
-                       "position": "chain-pre",
-                       "chain": "forward"
-               },
-
-               {
-                       ".description": "Position 'chain-post' (or 'chain-append') will place an include at the bottom of a specified chain",
-                       "path": "/usr/share/nftables.d/include-chain-end-forward.nft",
-                       "type": "nftables",
-                       "position": "chain-post",
-                       "chain": "forward"
-               },
-
-               {
-                       ".description": "Position 'chain-pre' or 'chain-post' without chain option will yield and error",
-                       "path": "/usr/share/nftables.d/include-chain-end-forward.nft",
-                       "type": "nftables",
-                       "position": "chain-post"
-               },
-       ]
-}
--- End --
-
--- Expect stderr --
-[!] Section @include[7] must specify 'chain' for position chain-append, ignoring section
--- End --
-
--- Expect stdout --
-table inet fw4
-flush table inet fw4
-
-include "/usr/share/nftables.d/include-ruleset-start.nft"
-
-table inet fw4 {
-       #
-       # Defines
-       #
-
-       define test_devices = { "eth0" }
-       define test_subnets = {  }
-
-
-       #
-       # User includes
-       #
-
-       include "/etc/nftables.d/*.nft"
-
-       include "/usr/share/nftables.d/include-table-start.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"
-               iifname "eth0" jump input_test comment "!fw4: Handle test IPv4/IPv6 input traffic"
-       }
-
-       chain forward {
-               type filter hook forward priority filter; policy drop;
-
-               include "/usr/share/nftables.d/include-chain-start-forward.nft"
-               ct state established,related accept comment "!fw4: Allow forwarded established and related flows"
-               iifname "eth0" jump forward_test comment "!fw4: Handle test IPv4/IPv6 forward traffic"
-               include "/usr/share/nftables.d/include-chain-end-forward.nft"
-       }
-
-       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"
-               oifname "eth0" jump output_test comment "!fw4: Handle test IPv4/IPv6 output traffic"
-       }
-
-       chain prerouting {
-               type filter hook prerouting priority filter; policy accept;
-       }
-
-       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_test {
-               jump drop_from_test
-       }
-
-       chain output_test {
-               jump drop_to_test
-       }
-
-       chain forward_test {
-               jump drop_to_test
-       }
-
-       chain drop_from_test {
-               iifname "eth0" counter drop comment "!fw4: drop test IPv4/IPv6 traffic"
-       }
-
-       chain drop_to_test {
-               oifname "eth0" counter drop comment "!fw4: drop test 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)
-       #
-
-       chain raw_prerouting {
-               type filter hook prerouting priority raw; policy accept;
-       }
-
-       chain raw_output {
-               type filter hook output priority raw; policy accept;
-       }
-
-
-       #
-       # 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 route hook output priority mangle; policy accept;
-       }
-
-       chain mangle_forward {
-               type filter hook forward priority mangle; policy accept;
-       }
-
-       include "/usr/share/nftables.d/include-table-end-1.nft"
-       include "/usr/share/nftables.d/include-table-end-2.nft"
-}
-
-include "/usr/share/nftables.d/include-ruleset-end.nft"
--- End --
diff --git a/tests/05_includes/02_firewall.user_include b/tests/05_includes/02_firewall.user_include
deleted file mode 100644 (file)
index fa398ff..0000000
+++ /dev/null
@@ -1,196 +0,0 @@
-Testing that /etc/firewall.user is treated specially and requires an extra
-option to be marked compatible.
-
--- 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_eth0_flags.txt --
-0x1103
--- End --
-
--- File fs/open~_etc_firewall_user.txt --
-# dummy
--- End --
-
--- File fs/open~_usr_share_miniupnpd_firewall_include.txt --
-# dummy
--- End --
-
--- File uci/firewall.json --
-{
-       "zone": [
-               {
-                       "name": "test",
-                       "device": [ "eth0" ],
-                       "auto_helper": 0
-               }
-       ],
-       "include": [
-               {
-                       ".description": "By default, this /etc/firewall.user include should be skipped with a warning",
-                       "path": "/etc/firewall.user"
-               },
-
-               {
-                       ".description": "This /etc/firewall.user include should be added due to the compatible flag",
-                       "path": "/etc/firewall.user",
-                       "fw4_compatible": 1
-               },
-
-               {
-                       ".description": "An include of another path should not require a compatible flag",
-                       "path": "/usr/share/miniupnpd/firewall.include"
-               }
-       ]
-}
--- End --
-
--- Expect stderr --
-[!] Section @include[0] is not marked as compatible with fw4, ignoring section
-[!] Section @include[0] requires 'option fw4_compatible 1' to be considered compatible
--- End --
-
--- Expect stdout --
-table inet fw4
-flush table inet fw4
-
-table inet fw4 {
-       #
-       # Defines
-       #
-
-       define test_devices = { "eth0" }
-       define test_subnets = {  }
-
-
-       #
-       # 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"
-               iifname "eth0" jump input_test comment "!fw4: Handle test 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"
-               iifname "eth0" jump forward_test comment "!fw4: Handle test 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"
-               oifname "eth0" jump output_test comment "!fw4: Handle test IPv4/IPv6 output traffic"
-       }
-
-       chain prerouting {
-               type filter hook prerouting priority filter; policy accept;
-       }
-
-       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_test {
-               jump drop_from_test
-       }
-
-       chain output_test {
-               jump drop_to_test
-       }
-
-       chain forward_test {
-               jump drop_to_test
-       }
-
-       chain drop_from_test {
-               iifname "eth0" counter drop comment "!fw4: drop test IPv4/IPv6 traffic"
-       }
-
-       chain drop_to_test {
-               oifname "eth0" counter drop comment "!fw4: drop test 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)
-       #
-
-       chain raw_prerouting {
-               type filter hook prerouting priority raw; policy accept;
-       }
-
-       chain raw_output {
-               type filter hook output priority raw; policy accept;
-       }
-
-
-       #
-       # 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 route hook output priority mangle; policy accept;
-       }
-
-       chain mangle_forward {
-               type filter hook forward priority mangle; policy accept;
-       }
-}
--- End --
diff --git a/tests/05_includes/03_script_includes b/tests/05_includes/03_script_includes
deleted file mode 100644 (file)
index 3eeea31..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-Testing the execution of include scripts.
-
--- Testcase --
-{%
-       include("./root/usr/share/firewall4/main.uc", {
-               TRACE_CALLS: "stderr",
-
-               getenv: function(varname) {
-                       switch (varname) {
-                       case 'ACTION':
-                               return 'includes';
-                       }
-               }
-       })
-%}
--- End --
-
--- File fs/open~_var_run_fw4_state.txt --
-{
-       "includes": [
-               {
-                       "enabled": true,
-                       "path": "/usr/share/miniupnpd/firewall.include",
-                       "type": "script",
-                       "fw4_compatible": true,
-                       "position": "table-append"
-               },
-
-               {
-                       "enabled": true,
-                       "path": "/etc/example.sh",
-                       "type": "script",
-                       "fw4_compatible": true,
-                       "position": "table-append"
-               }
-       ]
-}
--- End --
-
--- Expect stderr --
-[call] fs.open path </var/run/fw4.state> mode <r>
-[call] system command <[ "sh", "-c", "exec 1000>&-; config() { echo \"You cannot use UCI in firewall in..." ]> timeout <30000>
-[call] system command <[ "sh", "-c", "exec 1000>&-; config() { echo \"You cannot use UCI in firewall in..." ]> timeout <30000>
--- End --
diff --git a/tests/05_includes/04_disabled_include b/tests/05_includes/04_disabled_include
deleted file mode 100644 (file)
index ac0a6c8..0000000
+++ /dev/null
@@ -1,205 +0,0 @@
-Testing that include sections with `option enabled 0` are skipped.
-
--- 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_eth0_flags.txt --
-0x1103
--- End --
-
--- File fs/open~_etc_testinclude1_nft.txt --
-# dummy
--- End --
-
--- File fs/open~_etc_testinclude2_nft.txt --
-# dummy
--- End --
-
--- File fs/open~_etc_testinclude3_nft.txt --
-# dummy
--- End --
-
--- File uci/firewall.json --
-{
-       "zone": [
-               {
-                       "name": "test",
-                       "device": [ "eth0" ],
-                       "auto_helper": 0
-               }
-       ],
-       "include": [
-               {
-                       ".description": "By default, this include should be processed due to implicit enabled 1",
-                       "path": "/etc/testinclude1.nft",
-                       "type": "nftables"
-               },
-
-               {
-                       ".description": "This include should be processed due to explicit enabled 1",
-                       "path": "/etc/testinclude2.nft",
-                       "type": "nftables",
-                       "enabled": "1"
-               },
-
-               {
-                       ".description": "This include should be skipped due to explicit enabled 0",
-                       "path": "/etc/testinclude3.nft",
-                       "type": "nftables",
-                       "enabled": "0"
-               }
-       ]
-}
--- End --
-
--- Expect stderr --
-[!] Section @include[2] is disabled, ignoring section
--- End --
-
--- Expect stdout --
-table inet fw4
-flush table inet fw4
-
-table inet fw4 {
-       #
-       # Defines
-       #
-
-       define test_devices = { "eth0" }
-       define test_subnets = {  }
-
-
-       #
-       # 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"
-               iifname "eth0" jump input_test comment "!fw4: Handle test 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"
-               iifname "eth0" jump forward_test comment "!fw4: Handle test 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"
-               oifname "eth0" jump output_test comment "!fw4: Handle test IPv4/IPv6 output traffic"
-       }
-
-       chain prerouting {
-               type filter hook prerouting priority filter; policy accept;
-       }
-
-       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_test {
-               jump drop_from_test
-       }
-
-       chain output_test {
-               jump drop_to_test
-       }
-
-       chain forward_test {
-               jump drop_to_test
-       }
-
-       chain drop_from_test {
-               iifname "eth0" counter drop comment "!fw4: drop test IPv4/IPv6 traffic"
-       }
-
-       chain drop_to_test {
-               oifname "eth0" counter drop comment "!fw4: drop test 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)
-       #
-
-       chain raw_prerouting {
-               type filter hook prerouting priority raw; policy accept;
-       }
-
-       chain raw_output {
-               type filter hook output priority raw; policy accept;
-       }
-
-
-       #
-       # 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 route hook output priority mangle; policy accept;
-       }
-
-       chain mangle_forward {
-               type filter hook forward priority mangle; policy accept;
-       }
-
-       include "/etc/testinclude1.nft"
-       include "/etc/testinclude2.nft"
-}
--- End --
diff --git a/tests/05_ipsets/01_declaration b/tests/05_ipsets/01_declaration
new file mode 100644 (file)
index 0000000..88f6171
--- /dev/null
@@ -0,0 +1,168 @@
+Testing an ipset declaration.
+
+-- 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~set-entries_txt.txt --
+10.11.12.13 53
+172.16.27.1 443
+-- End --
+
+-- File uci/firewall.json --
+{
+       "ipset": [
+               {
+                       "name": "test-set",
+                       "comment": "A simple set",
+                       "counters": "1",
+                       "family": "IPv4",
+                       "match": [ "src_ip", "dest_port" ],
+                       "timeout": "600",
+                       "maxelem": "1000",
+                       "entry": [
+                               "1.2.3.4 80",
+                               "5.6.7.8 22"
+                       ],
+                       "loadfile": "set-entries.txt"
+               }
+       ]
+}
+-- End --
+
+-- Expect stdout --
+table inet fw4
+flush table inet fw4
+
+table inet fw4 {
+       #
+       # Set definitions
+       #
+
+       set test-set {
+               comment "A simple set"
+               type ipv4_addr . inet_service
+               size 1000
+               timeout 600s
+               flags timeout
+               elements = {
+                       1.2.3.4 . 80,
+                       5.6.7.8 . 22,
+                       10.11.12.13 . 53,
+                       172.16.27.1 . 443,
+               }
+       }
+
+
+       #
+       # Defines
+       #
+
+
+       #
+       # 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"
+       }
+
+       chain forward {
+               type filter hook forward priority filter; policy drop;
+
+               ct state established,related accept comment "!fw4: Allow forwarded established and related flows"
+       }
+
+       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"
+       }
+
+       chain prerouting {
+               type filter hook prerouting priority filter; policy accept;
+       }
+
+       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"
+       }
+
+
+       #
+       # 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)
+       #
+
+       chain raw_prerouting {
+               type filter hook prerouting priority raw; policy accept;
+       }
+
+       chain raw_output {
+               type filter hook output priority raw; policy accept;
+       }
+
+
+       #
+       # 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 route hook output priority mangle; policy accept;
+       }
+
+       chain mangle_forward {
+               type filter hook forward priority mangle; policy accept;
+       }
+}
+-- End --
diff --git a/tests/05_ipsets/02_usage b/tests/05_ipsets/02_usage
new file mode 100644 (file)
index 0000000..0f919af
--- /dev/null
@@ -0,0 +1,248 @@
+Test matching an ipset in rules.
+
+-- 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~_proc_version.txt --
+Linux version 5.10.113 (jow@j7) (mipsel-openwrt-linux-musl-gcc (OpenWrt GCC 11.2.0 r17858+262-2c3e8bed3f) 11.2.0, GNU ld (GNU Binutils) 2.37) #0 SMP Tue May 17 19:05:07 2022
+-- End --
+
+-- File uci/firewall.json --
+{
+       "ipset": [
+               {
+                       "name": "test-set-1",
+                       "comment": "Test set #1 with traffic direction in type declaration",
+                       "match": [ "src_ip", "dest_port" ],
+                       "entry": [
+                               "1.2.3.4 80",
+                               "5.6.7.8 22"
+                       ]
+               },
+               {
+                       "name": "test-set-2",
+                       "comment": "Test set #2 with unspecified traffic direction",
+                       "match": [ "ip", "port" ],
+                       "entry": [
+                               "1.2.3.4 80",
+                               "5.6.7.8 22"
+                       ]
+               },
+               {
+                       "name": "test-set-3",
+                       "comment": "Test set #3 with IPv6 addresses",
+                       "family": "IPv6",
+                       "match": [ "net", "net", "port" ],
+                       "entry": [
+                               "db80:1234:4567::1/64 db80:1234:abcd::1/64 80",
+                               "db80:8765:aaaa::1/64 db80:8765:ffff::1/64 22",
+                               "db80:1:2:3:4:0:0:1 0:0:0:0:0:0:0:0/0 443",
+                       ]
+               }
+       ],
+       "rule": [
+               {
+                       "name": "Rule using test set #1",
+                       "src": "*",
+                       "dest": "*",
+                       "proto": "tcp",
+                       "ipset": "test-set-1"
+               },
+               {
+                       "name": "Rule using test set #2, match direction should default to 'source'",
+                       "src": "*",
+                       "dest": "*",
+                       "proto": "tcp",
+                       "ipset": "test-set-2"
+               },
+               {
+                       "name": "Rule using test set #1, overriding match direction",
+                       "src": "*",
+                       "dest": "*",
+                       "proto": "tcp",
+                       "ipset": "test-set-1 dst src"
+               },
+               {
+                       "name": "Rule using test set #2, specifiying match direction",
+                       "src": "*",
+                       "dest": "*",
+                       "proto": "tcp",
+                       "ipset": "test-set-2 dst dst"
+               },
+               {
+                       "name": "Rule using test set #1, overriding direction and inverting match",
+                       "src": "*",
+                       "dest": "*",
+                       "proto": "tcp",
+                       "ipset": "!test-set-1 dst src"
+               },
+               {
+                       "name": "Rule using test set #3 with alternative direction notation, should inherit IPv6 family",
+                       "src": "*",
+                       "dest": "*",
+                       "proto": "tcp",
+                       "ipset": "test-set-3 src,dest,dest"
+               },
+       ]
+}
+-- End --
+
+-- Expect stdout --
+table inet fw4
+flush table inet fw4
+
+table inet fw4 {
+       #
+       # Set definitions
+       #
+
+       set test-set-1 {
+               comment "Test set #1 with traffic direction in type declaration"
+               type ipv4_addr . inet_service
+               elements = {
+                       1.2.3.4 . 80,
+                       5.6.7.8 . 22,
+               }
+       }
+
+       set test-set-2 {
+               comment "Test set #2 with unspecified traffic direction"
+               type ipv4_addr . inet_service
+               elements = {
+                       1.2.3.4 . 80,
+                       5.6.7.8 . 22,
+               }
+       }
+
+       set test-set-3 {
+               comment "Test set #3 with IPv6 addresses"
+               type ipv6_addr . ipv6_addr . inet_service
+               auto-merge
+               flags interval
+               elements = {
+                       db80:1234:4567::1/64 . db80:1234:abcd::1/64 . 80,
+                       db80:8765:aaaa::1/64 . db80:8765:ffff::1/64 . 22,
+                       db80:1:2:3:4::1/128 . ::/0 . 443,
+               }
+       }
+
+
+       #
+       # Defines
+       #
+
+
+       #
+       # 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"
+       }
+
+       chain forward {
+               type filter hook forward priority filter; policy drop;
+
+               ct state established,related accept comment "!fw4: Allow forwarded established and related flows"
+               meta nfproto ipv4 meta l4proto tcp ip saddr . tcp dport @test-set-1 counter comment "!fw4: Rule using test set #1"
+               meta nfproto ipv4 meta l4proto tcp ip saddr . tcp sport @test-set-2 counter comment "!fw4: Rule using test set #2, match direction should default to 'source'"
+               meta nfproto ipv4 meta l4proto tcp ip daddr . tcp sport @test-set-1 counter comment "!fw4: Rule using test set #1, overriding match direction"
+               meta nfproto ipv4 meta l4proto tcp ip daddr . tcp dport @test-set-2 counter comment "!fw4: Rule using test set #2, specifiying match direction"
+               meta nfproto ipv4 meta l4proto tcp ip daddr . tcp sport != @test-set-1 counter comment "!fw4: Rule using test set #1, overriding direction and inverting match"
+               meta nfproto ipv6 meta l4proto tcp ip6 saddr . ip6 daddr . tcp dport @test-set-3 counter comment "!fw4: Rule using test set #3 with alternative direction notation, should inherit IPv6 family"
+       }
+
+       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"
+       }
+
+       chain prerouting {
+               type filter hook prerouting priority filter; policy accept;
+       }
+
+       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"
+       }
+
+
+       #
+       # 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)
+       #
+
+       chain raw_prerouting {
+               type filter hook prerouting priority raw; policy accept;
+       }
+
+       chain raw_output {
+               type filter hook output priority raw; policy accept;
+       }
+
+
+       #
+       # 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 route hook output priority mangle; policy accept;
+       }
+
+       chain mangle_forward {
+               type filter hook forward priority mangle; policy accept;
+       }
+}
+-- End --
diff --git a/tests/06_includes/01_nft_includes b/tests/06_includes/01_nft_includes
new file mode 100644 (file)
index 0000000..314a428
--- /dev/null
@@ -0,0 +1,266 @@
+Testing the correct placement of potential include positions.
+
+-- 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_eth0_flags.txt --
+0x1103
+-- End --
+
+-- File fs/open~_usr_share_nftables_d_include-ruleset-start_nft.txt --
+# dummy
+-- End --
+
+-- File fs/open~_usr_share_nftables_d_include-table-start_nft.txt --
+# dummy
+-- End --
+
+-- File fs/open~_usr_share_nftables_d_include-chain-start-forward_nft.txt --
+# dummy
+-- End --
+
+-- File fs/open~_usr_share_nftables_d_include-chain-end-forward_nft.txt --
+# dummy
+-- End --
+
+-- File fs/open~_usr_share_nftables_d_include-table-end-1_nft.txt --
+# dummy
+-- End --
+
+-- File fs/open~_usr_share_nftables_d_include-table-end-2_nft.txt --
+# dummy
+-- End --
+
+-- File fs/open~_usr_share_nftables_d_include-ruleset-end_nft.txt --
+# dummy
+-- End --
+
+-- File uci/firewall.json --
+{
+       "zone": [
+               {
+                       "name": "test",
+                       "device": [ "eth0" ],
+                       "auto_helper": 0
+               }
+       ],
+       "include": [
+               {
+                       ".description": "Position 'table-pre' (or 'table-prepend') will place an include before the first chain",
+                       "path": "/usr/share/nftables.d/include-table-start.nft",
+                       "type": "nftables",
+                       "position": "table-pre"
+               },
+
+               {
+                       ".description": "Position defaults to 'table-append', means after the last chain in the table scope",
+                       "path": "/usr/share/nftables.d/include-table-end-1.nft",
+                       "type": "nftables"
+               },
+
+               {
+                       ".description": "Position 'table-post' (or 'table-postpend') may be used as alias for 'table-append'",
+                       "path": "/usr/share/nftables.d/include-table-end-2.nft",
+                       "type": "nftables",
+                       "position": "table-post"
+               },
+
+               {
+                       ".description": "Position 'ruleset-pre' (or 'ruleset-prepend') will place an include before the table declaration",
+                       "path": "/usr/share/nftables.d/include-ruleset-start.nft",
+                       "type": "nftables",
+                       "position": "ruleset-pre"
+               },
+
+               {
+                       ".description": "Position 'ruleset-post' (or 'ruleset-append') will place an include after the table declaration",
+                       "path": "/usr/share/nftables.d/include-ruleset-end.nft",
+                       "type": "nftables",
+                       "position": "ruleset-post"
+               },
+
+               {
+                       ".description": "Position 'chain-pre' (or 'chain-prepend') will place an include at the top of a specified chain",
+                       "path": "/usr/share/nftables.d/include-chain-start-forward.nft",
+                       "type": "nftables",
+                       "position": "chain-pre",
+                       "chain": "forward"
+               },
+
+               {
+                       ".description": "Position 'chain-post' (or 'chain-append') will place an include at the bottom of a specified chain",
+                       "path": "/usr/share/nftables.d/include-chain-end-forward.nft",
+                       "type": "nftables",
+                       "position": "chain-post",
+                       "chain": "forward"
+               },
+
+               {
+                       ".description": "Position 'chain-pre' or 'chain-post' without chain option will yield and error",
+                       "path": "/usr/share/nftables.d/include-chain-end-forward.nft",
+                       "type": "nftables",
+                       "position": "chain-post"
+               },
+       ]
+}
+-- End --
+
+-- Expect stderr --
+[!] Section @include[7] must specify 'chain' for position chain-append, ignoring section
+-- End --
+
+-- Expect stdout --
+table inet fw4
+flush table inet fw4
+
+include "/usr/share/nftables.d/include-ruleset-start.nft"
+
+table inet fw4 {
+       #
+       # Defines
+       #
+
+       define test_devices = { "eth0" }
+       define test_subnets = {  }
+
+
+       #
+       # User includes
+       #
+
+       include "/etc/nftables.d/*.nft"
+
+       include "/usr/share/nftables.d/include-table-start.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"
+               iifname "eth0" jump input_test comment "!fw4: Handle test IPv4/IPv6 input traffic"
+       }
+
+       chain forward {
+               type filter hook forward priority filter; policy drop;
+
+               include "/usr/share/nftables.d/include-chain-start-forward.nft"
+               ct state established,related accept comment "!fw4: Allow forwarded established and related flows"
+               iifname "eth0" jump forward_test comment "!fw4: Handle test IPv4/IPv6 forward traffic"
+               include "/usr/share/nftables.d/include-chain-end-forward.nft"
+       }
+
+       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"
+               oifname "eth0" jump output_test comment "!fw4: Handle test IPv4/IPv6 output traffic"
+       }
+
+       chain prerouting {
+               type filter hook prerouting priority filter; policy accept;
+       }
+
+       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_test {
+               jump drop_from_test
+       }
+
+       chain output_test {
+               jump drop_to_test
+       }
+
+       chain forward_test {
+               jump drop_to_test
+       }
+
+       chain drop_from_test {
+               iifname "eth0" counter drop comment "!fw4: drop test IPv4/IPv6 traffic"
+       }
+
+       chain drop_to_test {
+               oifname "eth0" counter drop comment "!fw4: drop test 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)
+       #
+
+       chain raw_prerouting {
+               type filter hook prerouting priority raw; policy accept;
+       }
+
+       chain raw_output {
+               type filter hook output priority raw; policy accept;
+       }
+
+
+       #
+       # 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 route hook output priority mangle; policy accept;
+       }
+
+       chain mangle_forward {
+               type filter hook forward priority mangle; policy accept;
+       }
+
+       include "/usr/share/nftables.d/include-table-end-1.nft"
+       include "/usr/share/nftables.d/include-table-end-2.nft"
+}
+
+include "/usr/share/nftables.d/include-ruleset-end.nft"
+-- End --
diff --git a/tests/06_includes/02_firewall.user_include b/tests/06_includes/02_firewall.user_include
new file mode 100644 (file)
index 0000000..fa398ff
--- /dev/null
@@ -0,0 +1,196 @@
+Testing that /etc/firewall.user is treated specially and requires an extra
+option to be marked compatible.
+
+-- 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_eth0_flags.txt --
+0x1103
+-- End --
+
+-- File fs/open~_etc_firewall_user.txt --
+# dummy
+-- End --
+
+-- File fs/open~_usr_share_miniupnpd_firewall_include.txt --
+# dummy
+-- End --
+
+-- File uci/firewall.json --
+{
+       "zone": [
+               {
+                       "name": "test",
+                       "device": [ "eth0" ],
+                       "auto_helper": 0
+               }
+       ],
+       "include": [
+               {
+                       ".description": "By default, this /etc/firewall.user include should be skipped with a warning",
+                       "path": "/etc/firewall.user"
+               },
+
+               {
+                       ".description": "This /etc/firewall.user include should be added due to the compatible flag",
+                       "path": "/etc/firewall.user",
+                       "fw4_compatible": 1
+               },
+
+               {
+                       ".description": "An include of another path should not require a compatible flag",
+                       "path": "/usr/share/miniupnpd/firewall.include"
+               }
+       ]
+}
+-- End --
+
+-- Expect stderr --
+[!] Section @include[0] is not marked as compatible with fw4, ignoring section
+[!] Section @include[0] requires 'option fw4_compatible 1' to be considered compatible
+-- End --
+
+-- Expect stdout --
+table inet fw4
+flush table inet fw4
+
+table inet fw4 {
+       #
+       # Defines
+       #
+
+       define test_devices = { "eth0" }
+       define test_subnets = {  }
+
+
+       #
+       # 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"
+               iifname "eth0" jump input_test comment "!fw4: Handle test 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"
+               iifname "eth0" jump forward_test comment "!fw4: Handle test 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"
+               oifname "eth0" jump output_test comment "!fw4: Handle test IPv4/IPv6 output traffic"
+       }
+
+       chain prerouting {
+               type filter hook prerouting priority filter; policy accept;
+       }
+
+       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_test {
+               jump drop_from_test
+       }
+
+       chain output_test {
+               jump drop_to_test
+       }
+
+       chain forward_test {
+               jump drop_to_test
+       }
+
+       chain drop_from_test {
+               iifname "eth0" counter drop comment "!fw4: drop test IPv4/IPv6 traffic"
+       }
+
+       chain drop_to_test {
+               oifname "eth0" counter drop comment "!fw4: drop test 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)
+       #
+
+       chain raw_prerouting {
+               type filter hook prerouting priority raw; policy accept;
+       }
+
+       chain raw_output {
+               type filter hook output priority raw; policy accept;
+       }
+
+
+       #
+       # 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 route hook output priority mangle; policy accept;
+       }
+
+       chain mangle_forward {
+               type filter hook forward priority mangle; policy accept;
+       }
+}
+-- End --
diff --git a/tests/06_includes/03_script_includes b/tests/06_includes/03_script_includes
new file mode 100644 (file)
index 0000000..3eeea31
--- /dev/null
@@ -0,0 +1,44 @@
+Testing the execution of include scripts.
+
+-- Testcase --
+{%
+       include("./root/usr/share/firewall4/main.uc", {
+               TRACE_CALLS: "stderr",
+
+               getenv: function(varname) {
+                       switch (varname) {
+                       case 'ACTION':
+                               return 'includes';
+                       }
+               }
+       })
+%}
+-- End --
+
+-- File fs/open~_var_run_fw4_state.txt --
+{
+       "includes": [
+               {
+                       "enabled": true,
+                       "path": "/usr/share/miniupnpd/firewall.include",
+                       "type": "script",
+                       "fw4_compatible": true,
+                       "position": "table-append"
+               },
+
+               {
+                       "enabled": true,
+                       "path": "/etc/example.sh",
+                       "type": "script",
+                       "fw4_compatible": true,
+                       "position": "table-append"
+               }
+       ]
+}
+-- End --
+
+-- Expect stderr --
+[call] fs.open path </var/run/fw4.state> mode <r>
+[call] system command <[ "sh", "-c", "exec 1000>&-; config() { echo \"You cannot use UCI in firewall in..." ]> timeout <30000>
+[call] system command <[ "sh", "-c", "exec 1000>&-; config() { echo \"You cannot use UCI in firewall in..." ]> timeout <30000>
+-- End --
diff --git a/tests/06_includes/04_disabled_include b/tests/06_includes/04_disabled_include
new file mode 100644 (file)
index 0000000..ac0a6c8
--- /dev/null
@@ -0,0 +1,205 @@
+Testing that include sections with `option enabled 0` are skipped.
+
+-- 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_eth0_flags.txt --
+0x1103
+-- End --
+
+-- File fs/open~_etc_testinclude1_nft.txt --
+# dummy
+-- End --
+
+-- File fs/open~_etc_testinclude2_nft.txt --
+# dummy
+-- End --
+
+-- File fs/open~_etc_testinclude3_nft.txt --
+# dummy
+-- End --
+
+-- File uci/firewall.json --
+{
+       "zone": [
+               {
+                       "name": "test",
+                       "device": [ "eth0" ],
+                       "auto_helper": 0
+               }
+       ],
+       "include": [
+               {
+                       ".description": "By default, this include should be processed due to implicit enabled 1",
+                       "path": "/etc/testinclude1.nft",
+                       "type": "nftables"
+               },
+
+               {
+                       ".description": "This include should be processed due to explicit enabled 1",
+                       "path": "/etc/testinclude2.nft",
+                       "type": "nftables",
+                       "enabled": "1"
+               },
+
+               {
+                       ".description": "This include should be skipped due to explicit enabled 0",
+                       "path": "/etc/testinclude3.nft",
+                       "type": "nftables",
+                       "enabled": "0"
+               }
+       ]
+}
+-- End --
+
+-- Expect stderr --
+[!] Section @include[2] is disabled, ignoring section
+-- End --
+
+-- Expect stdout --
+table inet fw4
+flush table inet fw4
+
+table inet fw4 {
+       #
+       # Defines
+       #
+
+       define test_devices = { "eth0" }
+       define test_subnets = {  }
+
+
+       #
+       # 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"
+               iifname "eth0" jump input_test comment "!fw4: Handle test 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"
+               iifname "eth0" jump forward_test comment "!fw4: Handle test 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"
+               oifname "eth0" jump output_test comment "!fw4: Handle test IPv4/IPv6 output traffic"
+       }
+
+       chain prerouting {
+               type filter hook prerouting priority filter; policy accept;
+       }
+
+       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_test {
+               jump drop_from_test
+       }
+
+       chain output_test {
+               jump drop_to_test
+       }
+
+       chain forward_test {
+               jump drop_to_test
+       }
+
+       chain drop_from_test {
+               iifname "eth0" counter drop comment "!fw4: drop test IPv4/IPv6 traffic"
+       }
+
+       chain drop_to_test {
+               oifname "eth0" counter drop comment "!fw4: drop test 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)
+       #
+
+       chain raw_prerouting {
+               type filter hook prerouting priority raw; policy accept;
+       }
+
+       chain raw_output {
+               type filter hook output priority raw; policy accept;
+       }
+
+
+       #
+       # 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 route hook output priority mangle; policy accept;
+       }
+
+       chain mangle_forward {
+               type filter hook forward priority mangle; policy accept;
+       }
+
+       include "/etc/testinclude1.nft"
+       include "/etc/testinclude2.nft"
+}
+-- End --