--- /dev/null
+{%+ 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) }}
{% 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 }}
--- /dev/null
+{%
+ 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') %}
--- /dev/null
+--- 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): %}
enabled: [ "bool", "1" ],
reload_set: [ "bool" ],
counters: [ "bool" ],
- comment: [ "bool" ],
+ comment: [ "string" ],
name: [ "string", null, REQUIRED ],
family: [ "family", "4" ],
+++ /dev/null
-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 --
+++ /dev/null
-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 --
+++ /dev/null
-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 --
+++ /dev/null
-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 --
--- /dev/null
+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 --
--- /dev/null
+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 --
--- /dev/null
+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 --
--- /dev/null
+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 --
--- /dev/null
+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 --
--- /dev/null
+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 --