From 39e8c70957c795bf0c12f04299170ae86c6efdf8 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Sat, 7 Jan 2023 17:00:18 +0100 Subject: [PATCH] fw4: fix handling the ipset "comment" option 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 Signed-off-by: Jo-Philipp Wich --- .../share/firewall4/templates/mangle-rule.uc | 93 ++++ root/usr/share/firewall4/templates/ruleset.uc | 3 + .../share/firewall4/templates/ruleset.uc.orig | 449 ++++++++++++++++++ .../share/firewall4/templates/ruleset.uc.rej | 12 + root/usr/share/ucode/fw4.uc | 2 +- tests/05_ipsets/01_declaration | 168 +++++++ tests/05_ipsets/02_usage | 248 ++++++++++ .../01_nft_includes | 0 .../02_firewall.user_include | 0 .../03_script_includes | 0 .../04_disabled_include | 0 11 files changed, 974 insertions(+), 1 deletion(-) create mode 100644 root/usr/share/firewall4/templates/mangle-rule.uc create mode 100644 root/usr/share/firewall4/templates/ruleset.uc.orig create mode 100644 root/usr/share/firewall4/templates/ruleset.uc.rej create mode 100644 tests/05_ipsets/01_declaration create mode 100644 tests/05_ipsets/02_usage rename tests/{05_includes => 06_includes}/01_nft_includes (100%) rename tests/{05_includes => 06_includes}/02_firewall.user_include (100%) rename tests/{05_includes => 06_includes}/03_script_includes (100%) rename tests/{05_includes => 06_includes}/04_disabled_include (100%) diff --git a/root/usr/share/firewall4/templates/mangle-rule.uc b/root/usr/share/firewall4/templates/mangle-rule.uc new file mode 100644 index 0000000..90637bb --- /dev/null +++ b/root/usr/share/firewall4/templates/mangle-rule.uc @@ -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) }} diff --git a/root/usr/share/firewall4/templates/ruleset.uc b/root/usr/share/firewall4/templates/ruleset.uc index d6eedfd..9537ba2 100644 --- a/root/usr/share/firewall4/templates/ruleset.uc +++ b/root/usr/share/firewall4/templates/ruleset.uc @@ -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 index 0000000..2e33d5d --- /dev/null +++ b/root/usr/share/firewall4/templates/ruleset.uc.orig @@ -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 index 0000000..35e8dea --- /dev/null +++ b/root/usr/share/firewall4/templates/ruleset.uc.rej @@ -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): %} diff --git a/root/usr/share/ucode/fw4.uc b/root/usr/share/ucode/fw4.uc index 5dce90d..0393508 100644 --- a/root/usr/share/ucode/fw4.uc +++ b/root/usr/share/ucode/fw4.uc @@ -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_ipsets/01_declaration b/tests/05_ipsets/01_declaration new file mode 100644 index 0000000..88f6171 --- /dev/null +++ b/tests/05_ipsets/01_declaration @@ -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 index 0000000..0f919af --- /dev/null +++ b/tests/05_ipsets/02_usage @@ -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/05_includes/01_nft_includes b/tests/06_includes/01_nft_includes similarity index 100% rename from tests/05_includes/01_nft_includes rename to tests/06_includes/01_nft_includes diff --git a/tests/05_includes/02_firewall.user_include b/tests/06_includes/02_firewall.user_include similarity index 100% rename from tests/05_includes/02_firewall.user_include rename to tests/06_includes/02_firewall.user_include diff --git a/tests/05_includes/03_script_includes b/tests/06_includes/03_script_includes similarity index 100% rename from tests/05_includes/03_script_includes rename to tests/06_includes/03_script_includes diff --git a/tests/05_includes/04_disabled_include b/tests/06_includes/04_disabled_include similarity index 100% rename from tests/05_includes/04_disabled_include rename to tests/06_includes/04_disabled_include -- 2.30.2