summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2024-08-04 09:07:15 +0200
committerGitHub <noreply@github.com>2024-08-04 09:07:15 +0200
commit15c77978f30bebe7c6d4f4e9a87c56e12e1382cd (patch)
treeb27bd1e95b512a341a6591ef1435b73ff9531865
parent998df24dc4ed0c1ccd572d09c438d96fe6b79ba8 (diff)
parentc33cd6157ebc5c08dc1e3ff1aa36f2d2fbb9ca83 (diff)
downloadvyos-1x-15c77978f30bebe7c6d4f4e9a87c56e12e1382cd.tar.gz
vyos-1x-15c77978f30bebe7c6d4f4e9a87c56e12e1382cd.zip
Merge pull request #3901 from nicolas-fort/T4072-extend-bridge-fwall
T4072: firewall extend bridge firewall
-rw-r--r--data/templates/firewall/nftables-bridge.j285
-rw-r--r--data/templates/firewall/nftables-defines.j210
-rw-r--r--data/templates/firewall/nftables.j2101
-rw-r--r--data/templates/firewall/sysctl-firewall.conf.j28
-rw-r--r--interface-definitions/firewall.xml.in3
-rw-r--r--interface-definitions/include/firewall/address-inet.xml.i63
-rw-r--r--interface-definitions/include/firewall/address-mask-inet.xml.i19
-rw-r--r--interface-definitions/include/firewall/bridge-custom-name.xml.i6
-rw-r--r--interface-definitions/include/firewall/bridge-hook-forward.xml.i6
-rw-r--r--interface-definitions/include/firewall/bridge-hook-input.xml.i40
-rw-r--r--interface-definitions/include/firewall/bridge-hook-output.xml.i40
-rw-r--r--interface-definitions/include/firewall/bridge-hook-prerouting.xml.i37
-rw-r--r--interface-definitions/include/firewall/common-rule-bridge.xml.i34
-rw-r--r--interface-definitions/include/firewall/global-options.xml.i19
-rw-r--r--interface-definitions/include/firewall/set-packet-modifications.xml.i96
-rw-r--r--interface-definitions/include/firewall/source-destination-group-inet.xml.i50
-rw-r--r--interface-definitions/include/policy/route-common.xml.i95
-rw-r--r--python/vyos/firewall.py56
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py33
-rwxr-xr-xsrc/conf_mode/firewall.py163
-rw-r--r--src/etc/sysctl.d/30-vyos-router.conf5
21 files changed, 773 insertions, 196 deletions
diff --git a/data/templates/firewall/nftables-bridge.j2 b/data/templates/firewall/nftables-bridge.j2
index dec027bf9..1975fb9b0 100644
--- a/data/templates/firewall/nftables-bridge.j2
+++ b/data/templates/firewall/nftables-bridge.j2
@@ -1,9 +1,13 @@
+{% import 'firewall/nftables-defines.j2' as group_tmpl %}
{% macro bridge(bridge) %}
{% set ns = namespace(sets=[]) %}
{% if bridge.forward is vyos_defined %}
{% for prior, conf in bridge.forward.items() %}
chain VYOS_FORWARD_{{ prior }} {
type filter hook forward priority {{ prior }}; policy accept;
+{% if global_options.state_policy is vyos_defined %}
+ jump VYOS_STATE_POLICY
+{% endif %}
{% if conf.rule is vyos_defined %}
{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
{{ rule_conf | nft_rule('FWD', prior, rule_id, 'bri') }}
@@ -17,6 +21,46 @@
{% endfor %}
{% endif %}
+{% if bridge.input is vyos_defined %}
+{% for prior, conf in bridge.input.items() %}
+ chain VYOS_INPUT_{{ prior }} {
+ type filter hook input priority {{ prior }}; policy accept;
+{% if global_options.state_policy is vyos_defined %}
+ jump VYOS_STATE_POLICY
+{% endif %}
+{% if conf.rule is vyos_defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
+ {{ rule_conf | nft_rule('INP', prior, rule_id, 'bri') }}
+{% if rule_conf.recent is vyos_defined %}
+{% set ns.sets = ns.sets + ['INP_' + prior + '_' + rule_id] %}
+{% endif %}
+{% endfor %}
+{% endif %}
+ {{ conf | nft_default_rule('INP-filter', 'bri') }}
+ }
+{% endfor %}
+{% endif %}
+
+{% if bridge.output is vyos_defined %}
+{% for prior, conf in bridge.output.items() %}
+ chain VYOS_OUTUT_{{ prior }} {
+ type filter hook output priority {{ prior }}; policy accept;
+{% if global_options.state_policy is vyos_defined %}
+ jump VYOS_STATE_POLICY
+{% endif %}
+{% if conf.rule is vyos_defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
+ {{ rule_conf | nft_rule('OUT', prior, rule_id, 'bri') }}
+{% if rule_conf.recent is vyos_defined %}
+{% set ns.sets = ns.sets + ['OUT_' + prior + '_' + rule_id] %}
+{% endif %}
+{% endfor %}
+{% endif %}
+ {{ conf | nft_default_rule('OUT-filter', 'bri') }}
+ }
+{% endfor %}
+{% endif %}
+
{% if bridge.name is vyos_defined %}
{% for name_text, conf in bridge.name.items() %}
chain NAME_{{ name_text }} {
@@ -32,4 +76,45 @@
}
{% endfor %}
{% endif %}
+
+{% for set_name in ns.sets %}
+ set RECENT_{{ set_name }} {
+ type ipv4_addr
+ size 65535
+ flags dynamic
+ }
+{% endfor %}
+{% for set_name in ip_fqdn %}
+ set FQDN_{{ set_name }} {
+ type ipv4_addr
+ flags interval
+ }
+{% endfor %}
+{% if geoip_updated.name is vyos_defined %}
+{% for setname in geoip_updated.name %}
+ set {{ setname }} {
+ type ipv4_addr
+ flags interval
+ }
+{% endfor %}
+{% endif %}
+
+{{ group_tmpl.groups(group, False, True) }}
+{{ group_tmpl.groups(group, True, True) }}
+
+{% if global_options.state_policy is vyos_defined %}
+ chain VYOS_STATE_POLICY {
+{% if global_options.state_policy.established is vyos_defined %}
+ {{ global_options.state_policy.established | nft_state_policy('established') }}
+{% endif %}
+{% if global_options.state_policy.invalid is vyos_defined %}
+ {{ global_options.state_policy.invalid | nft_state_policy('invalid') }}
+{% endif %}
+{% if global_options.state_policy.related is vyos_defined %}
+ {{ global_options.state_policy.related | nft_state_policy('related') }}
+{% endif %}
+ return
+ }
+{% endif %}
+
{% endmacro %}
diff --git a/data/templates/firewall/nftables-defines.j2 b/data/templates/firewall/nftables-defines.j2
index 8a75ab2d6..fa6cd74c0 100644
--- a/data/templates/firewall/nftables-defines.j2
+++ b/data/templates/firewall/nftables-defines.j2
@@ -1,7 +1,7 @@
{% macro groups(group, is_ipv6, is_l3) %}
{% if group is vyos_defined %}
{% set ip_type = 'ipv6_addr' if is_ipv6 else 'ipv4_addr' %}
-{% if group.address_group is vyos_defined and not is_ipv6 and is_l3 %}
+{% if group.address_group is vyos_defined and not is_ipv6 %}
{% for group_name, group_conf in group.address_group.items() %}
{% set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
set A_{{ group_name }} {
@@ -14,7 +14,7 @@
}
{% endfor %}
{% endif %}
-{% if group.ipv6_address_group is vyos_defined and is_ipv6 and is_l3 %}
+{% if group.ipv6_address_group is vyos_defined and is_ipv6 %}
{% for group_name, group_conf in group.ipv6_address_group.items() %}
{% set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
set A6_{{ group_name }} {
@@ -46,7 +46,7 @@
}
{% endfor %}
{% endif %}
-{% if group.network_group is vyos_defined and not is_ipv6 and is_l3 %}
+{% if group.network_group is vyos_defined and not is_ipv6 %}
{% for group_name, group_conf in group.network_group.items() %}
{% set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
set N_{{ group_name }} {
@@ -59,7 +59,7 @@
}
{% endfor %}
{% endif %}
-{% if group.ipv6_network_group is vyos_defined and is_ipv6 and is_l3 %}
+{% if group.ipv6_network_group is vyos_defined and is_ipv6 %}
{% for group_name, group_conf in group.ipv6_network_group.items() %}
{% set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
set N6_{{ group_name }} {
@@ -72,7 +72,7 @@
}
{% endfor %}
{% endif %}
-{% if group.port_group is vyos_defined and is_l3 %}
+{% if group.port_group is vyos_defined %}
{% for group_name, group_conf in group.port_group.items() %}
{% set includes = group_conf.include if group_conf.include is vyos_defined else [] %}
set P_{{ group_name }} {
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index 68a3bfd87..82dcefac0 100644
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -339,7 +339,104 @@ table ip6 vyos_filter {
delete table bridge vyos_filter
{% endif %}
table bridge vyos_filter {
-{{ bridge_tmpl.bridge(bridge) }}
+{% if bridge is vyos_defined %}
+{% if bridge.forward is vyos_defined %}
+{% for prior, conf in bridge.forward.items() %}
+ chain VYOS_FORWARD_{{ prior }} {
+ type filter hook forward priority {{ prior }}; policy accept;
+{% if global_options.state_policy is vyos_defined %}
+ jump VYOS_STATE_POLICY
+{% endif %}
+{% if conf.rule is vyos_defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
+ {{ rule_conf | nft_rule('FWD', prior, rule_id, 'bri') }}
+{% endfor %}
+{% endif %}
+ {{ conf | nft_default_rule('FWD-' + prior, 'bri') }}
+ }
+{% endfor %}
+{% endif %}
+
+{% if bridge.input is vyos_defined %}
+{% for prior, conf in bridge.input.items() %}
+ chain VYOS_INPUT_{{ prior }} {
+ type filter hook input priority {{ prior }}; policy accept;
+{% if global_options.state_policy is vyos_defined %}
+ jump VYOS_STATE_POLICY
+{% endif %}
+{% if conf.rule is vyos_defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
+ {{ rule_conf | nft_rule('INP', prior, rule_id, 'bri') }}
+{% endfor %}
+{% endif %}
+ {{ conf | nft_default_rule('INP-' + prior, 'bri') }}
+ }
+{% endfor %}
+{% endif %}
+
+{% if bridge.output is vyos_defined %}
+{% for prior, conf in bridge.output.items() %}
+ chain VYOS_OUTUT_{{ prior }} {
+ type filter hook output priority {{ prior }}; policy accept;
+{% if global_options.state_policy is vyos_defined %}
+ jump VYOS_STATE_POLICY
+{% endif %}
+{% if conf.rule is vyos_defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
+ {{ rule_conf | nft_rule('OUT', prior, rule_id, 'bri') }}
+{% endfor %}
+{% endif %}
+ {{ conf | nft_default_rule('OUT-' + prior, 'bri') }}
+ }
+{% endfor %}
+{% endif %}
+
+{% if bridge.prerouting is vyos_defined %}
+{% for prior, conf in bridge.prerouting.items() %}
+ chain VYOS_PREROUTING_{{ prior }} {
+ type filter hook prerouting priority {{ prior }}; policy accept;
+{% if conf.rule is vyos_defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
+ {{ rule_conf | nft_rule('PRE', prior, rule_id, 'bri') }}
+{% endfor %}
+{% endif %}
+ {{ conf | nft_default_rule('PRE-' + prior, 'bri') }}
+ }
+{% endfor %}
+{% endif %}
+
+{% if bridge.name is vyos_defined %}
+{% for name_text, conf in bridge.name.items() %}
+ chain NAME_{{ name_text }} {
+{% if conf.rule is vyos_defined %}
+{% for rule_id, rule_conf in conf.rule.items() if rule_conf.disable is not vyos_defined %}
+ {{ rule_conf | nft_rule('NAM', name_text, rule_id, 'bri') }}
+{% if rule_conf.recent is vyos_defined %}
+{% set ns.sets = ns.sets + ['NAM_' + name_text + '_' + rule_id] %}
+{% endif %}
+{% endfor %}
+{% endif %}
+ {{ conf | nft_default_rule(name_text, 'bri') }}
+ }
+{% endfor %}
+{% endif %}
+
+{% endif %}
{{ group_tmpl.groups(group, False, False) }}
+{{ group_tmpl.groups(group, True, False) }}
-}
+{% if global_options.state_policy is vyos_defined %}
+ chain VYOS_STATE_POLICY {
+{% if global_options.state_policy.established is vyos_defined %}
+ {{ global_options.state_policy.established | nft_state_policy('established') }}
+{% endif %}
+{% if global_options.state_policy.invalid is vyos_defined %}
+ {{ global_options.state_policy.invalid | nft_state_policy('invalid') }}
+{% endif %}
+{% if global_options.state_policy.related is vyos_defined %}
+ {{ global_options.state_policy.related | nft_state_policy('related') }}
+{% endif %}
+ return
+ }
+{% endif %}
+} \ No newline at end of file
diff --git a/data/templates/firewall/sysctl-firewall.conf.j2 b/data/templates/firewall/sysctl-firewall.conf.j2
index b9c3311e2..6c33ffdc8 100644
--- a/data/templates/firewall/sysctl-firewall.conf.j2
+++ b/data/templates/firewall/sysctl-firewall.conf.j2
@@ -13,6 +13,14 @@ net.ipv4.conf.*.send_redirects = {{ 1 if global_options.send_redirects == 'enabl
net.ipv4.tcp_syncookies = {{ 1 if global_options.syn_cookies == 'enable' else 0 }}
net.ipv4.tcp_rfc1337 = {{ 1 if global_options.twa_hazards_protection == 'enable' else 0 }}
+{% if global_options.apply_to_bridged_traffic is vyos_defined %}
+net.bridge.bridge-nf-call-iptables = {{ 1 if global_options.apply_to_bridged_traffic.ipv4 is vyos_defined else 0 }}
+net.bridge.bridge-nf-call-ip6tables = {{ 1 if global_options.apply_to_bridged_traffic.ipv6 is vyos_defined else 0 }}
+{% else %}
+net.bridge.bridge-nf-call-iptables = 0
+net.bridge.bridge-nf-call-ip6tables = 0
+{% endif %}
+
## Timeout values:
net.netfilter.nf_conntrack_icmp_timeout = {{ global_options.timeout.icmp }}
net.netfilter.nf_conntrack_generic_timeout = {{ global_options.timeout.other }}
diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in
index dc4625af0..816dd1855 100644
--- a/interface-definitions/firewall.xml.in
+++ b/interface-definitions/firewall.xml.in
@@ -367,6 +367,9 @@
</properties>
<children>
#include <include/firewall/bridge-hook-forward.xml.i>
+ #include <include/firewall/bridge-hook-input.xml.i>
+ #include <include/firewall/bridge-hook-output.xml.i>
+ #include <include/firewall/bridge-hook-prerouting.xml.i>
#include <include/firewall/bridge-custom-name.xml.i>
</children>
</node>
diff --git a/interface-definitions/include/firewall/address-inet.xml.i b/interface-definitions/include/firewall/address-inet.xml.i
new file mode 100644
index 000000000..02ed8f6e4
--- /dev/null
+++ b/interface-definitions/include/firewall/address-inet.xml.i
@@ -0,0 +1,63 @@
+<!-- include start from firewall/address-inet.xml.i -->
+<leafNode name="address">
+ <properties>
+ <help>IP address, subnet, or range</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address to match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>IPv4 prefix to match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv4range</format>
+ <description>IPv4 address range to match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!ipv4</format>
+ <description>Match everything except the specified address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!ipv4net</format>
+ <description>Match everything except the specified prefix</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!ipv4range</format>
+ <description>Match everything except the specified range</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>Subnet to match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6range</format>
+ <description>IP range to match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!ipv6</format>
+ <description>Match everything except the specified address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!ipv6net</format>
+ <description>Match everything except the specified prefix</description>
+ </valueHelp>
+ <valueHelp>
+ <format>!ipv6range</format>
+ <description>Match everything except the specified range</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv4-prefix"/>
+ <validator name="ipv4-range"/>
+ <validator name="ipv4-address-exclude"/>
+ <validator name="ipv4-prefix-exclude"/>
+ <validator name="ipv4-range-exclude"/>
+ <validator name="ipv6"/>
+ <validator name="ipv6-exclude"/>
+ <validator name="ipv6-range"/>
+ <validator name="ipv6-range-exclude"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/address-mask-inet.xml.i b/interface-definitions/include/firewall/address-mask-inet.xml.i
new file mode 100644
index 000000000..e2a5927ab
--- /dev/null
+++ b/interface-definitions/include/firewall/address-mask-inet.xml.i
@@ -0,0 +1,19 @@
+<!-- include start from firewall/address-mask-inet.xml.i -->
+<leafNode name="address-mask">
+ <properties>
+ <help>IP mask</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 mask to apply</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IP mask to apply</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv6"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/bridge-custom-name.xml.i b/interface-definitions/include/firewall/bridge-custom-name.xml.i
index 654493c0e..9a2a829d0 100644
--- a/interface-definitions/include/firewall/bridge-custom-name.xml.i
+++ b/interface-definitions/include/firewall/bridge-custom-name.xml.i
@@ -32,6 +32,12 @@
</properties>
<children>
#include <include/firewall/common-rule-bridge.xml.i>
+ #include <include/firewall/action-l2.xml.i>
+ #include <include/firewall/connection-mark.xml.i>
+ #include <include/firewall/connection-status.xml.i>
+ #include <include/firewall/state.xml.i>
+ #include <include/firewall/inbound-interface.xml.i>
+ #include <include/firewall/outbound-interface.xml.i>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/include/firewall/bridge-hook-forward.xml.i b/interface-definitions/include/firewall/bridge-hook-forward.xml.i
index 99f66ec77..fcc981925 100644
--- a/interface-definitions/include/firewall/bridge-hook-forward.xml.i
+++ b/interface-definitions/include/firewall/bridge-hook-forward.xml.i
@@ -26,6 +26,12 @@
</properties>
<children>
#include <include/firewall/common-rule-bridge.xml.i>
+ #include <include/firewall/action-l2.xml.i>
+ #include <include/firewall/connection-mark.xml.i>
+ #include <include/firewall/connection-status.xml.i>
+ #include <include/firewall/state.xml.i>
+ #include <include/firewall/inbound-interface.xml.i>
+ #include <include/firewall/outbound-interface.xml.i>
</children>
</tagNode>
</children>
diff --git a/interface-definitions/include/firewall/bridge-hook-input.xml.i b/interface-definitions/include/firewall/bridge-hook-input.xml.i
new file mode 100644
index 000000000..f6a11f8da
--- /dev/null
+++ b/interface-definitions/include/firewall/bridge-hook-input.xml.i
@@ -0,0 +1,40 @@
+<!-- include start from firewall/bridge-hook-input.xml.i -->
+<node name="input">
+ <properties>
+ <help>Bridge input firewall</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>Bridge firewall input filter</help>
+ </properties>
+ <children>
+ #include <include/firewall/default-action-base-chains.xml.i>
+ #include <include/firewall/default-log.xml.i>
+ #include <include/generic-description.xml.i>
+ <tagNode name="rule">
+ <properties>
+ <help>Bridge Firewall input filter rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number for this firewall rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/firewall/common-rule-bridge.xml.i>
+ #include <include/firewall/action-l2.xml.i>
+ #include <include/firewall/connection-mark.xml.i>
+ #include <include/firewall/connection-status.xml.i>
+ #include <include/firewall/state.xml.i>
+ #include <include/firewall/inbound-interface.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/bridge-hook-output.xml.i b/interface-definitions/include/firewall/bridge-hook-output.xml.i
new file mode 100644
index 000000000..38b8b08ca
--- /dev/null
+++ b/interface-definitions/include/firewall/bridge-hook-output.xml.i
@@ -0,0 +1,40 @@
+<!-- include start from firewall/bridge-hook-output.xml.i -->
+<node name="output">
+ <properties>
+ <help>Bridge output firewall</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>Bridge firewall output filter</help>
+ </properties>
+ <children>
+ #include <include/firewall/default-action-base-chains.xml.i>
+ #include <include/firewall/default-log.xml.i>
+ #include <include/generic-description.xml.i>
+ <tagNode name="rule">
+ <properties>
+ <help>Bridge Firewall output filter rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number for this firewall rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/firewall/common-rule-bridge.xml.i>
+ #include <include/firewall/action-l2.xml.i>
+ #include <include/firewall/connection-mark.xml.i>
+ #include <include/firewall/connection-status.xml.i>
+ #include <include/firewall/state.xml.i>
+ #include <include/firewall/outbound-interface.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/bridge-hook-prerouting.xml.i b/interface-definitions/include/firewall/bridge-hook-prerouting.xml.i
new file mode 100644
index 000000000..ea567644f
--- /dev/null
+++ b/interface-definitions/include/firewall/bridge-hook-prerouting.xml.i
@@ -0,0 +1,37 @@
+<!-- include start from firewall/bridge-hook-prerouting.xml.i -->
+<node name="prerouting">
+ <properties>
+ <help>Bridge prerouting firewall</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>Bridge firewall prerouting filter</help>
+ </properties>
+ <children>
+ #include <include/firewall/default-action-base-chains.xml.i>
+ #include <include/firewall/default-log.xml.i>
+ #include <include/generic-description.xml.i>
+ <tagNode name="rule">
+ <properties>
+ <help>Bridge firewall prerouting filter rule number</help>
+ <valueHelp>
+ <format>u32:1-999999</format>
+ <description>Number for this firewall rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>Firewall rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/firewall/common-rule-bridge.xml.i>
+ #include <include/firewall/action-and-notrack.xml.i>
+ #include <include/firewall/inbound-interface.xml.i>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/common-rule-bridge.xml.i b/interface-definitions/include/firewall/common-rule-bridge.xml.i
index dcdd970ac..9ae28f7be 100644
--- a/interface-definitions/include/firewall/common-rule-bridge.xml.i
+++ b/interface-definitions/include/firewall/common-rule-bridge.xml.i
@@ -1,15 +1,36 @@
<!-- include start from firewall/common-rule-bridge.xml.i -->
-#include <include/firewall/action-l2.xml.i>
+#include <include/generic-description.xml.i>
+#include <include/generic-disable-node.xml.i>
+#include <include/firewall/dscp.xml.i>
+#include <include/firewall/firewall-mark.xml.i>
+#include <include/firewall/fragment.xml.i>
+#include <include/firewall/hop-limit.xml.i>
+#include <include/firewall/icmp.xml.i>
+#include <include/firewall/icmpv6.xml.i>
+#include <include/firewall/limit.xml.i>
+#include <include/firewall/log.xml.i>
+#include <include/firewall/log-options.xml.i>
+#include <include/firewall/match-ipsec.xml.i>
+#include <include/firewall/match-vlan.xml.i>
#include <include/firewall/nft-queue.xml.i>
+#include <include/firewall/packet-options.xml.i>
+#include <include/firewall/protocol.xml.i>
+#include <include/firewall/tcp-flags.xml.i>
+#include <include/firewall/tcp-mss.xml.i>
+#include <include/firewall/time.xml.i>
+#include <include/firewall/ttl.xml.i>
<node name="destination">
<properties>
<help>Destination parameters</help>
</properties>
<children>
#include <include/firewall/mac-address.xml.i>
+ #include <include/firewall/address-inet.xml.i>
+ #include <include/firewall/address-mask-inet.xml.i>
+ #include <include/firewall/port.xml.i>
+ #include <include/firewall/source-destination-group-inet.xml.i>
</children>
</node>
-#include <include/generic-disable-node.xml.i>
<leafNode name="jump-target">
<properties>
<help>Set jump target. Action jump must be defined to use this setting</help>
@@ -18,17 +39,16 @@
</completionHelp>
</properties>
</leafNode>
-#include <include/firewall/log.xml.i>
-#include <include/firewall/log-options.xml.i>
<node name="source">
<properties>
<help>Source parameters</help>
</properties>
<children>
#include <include/firewall/mac-address.xml.i>
+ #include <include/firewall/address-inet.xml.i>
+ #include <include/firewall/address-mask-inet.xml.i>
+ #include <include/firewall/port.xml.i>
+ #include <include/firewall/source-destination-group-inet.xml.i>
</children>
</node>
-#include <include/firewall/inbound-interface.xml.i>
-#include <include/firewall/outbound-interface.xml.i>
-#include <include/firewall/match-vlan.xml.i>
<!-- include end -->
diff --git a/interface-definitions/include/firewall/global-options.xml.i b/interface-definitions/include/firewall/global-options.xml.i
index 9039b76fd..cee8f1854 100644
--- a/interface-definitions/include/firewall/global-options.xml.i
+++ b/interface-definitions/include/firewall/global-options.xml.i
@@ -44,6 +44,25 @@
</properties>
<defaultValue>disable</defaultValue>
</leafNode>
+ <node name="apply-to-bridged-traffic">
+ <properties>
+ <help>Apply configured firewall rules to traffic switched by bridges</help>
+ </properties>
+ <children>
+ <leafNode name="ipv4">
+ <properties>
+ <help>Apply configured IPv4 firewall rules</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ipv6">
+ <properties>
+ <help>Apply configured IPv6 firewall rules</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
<leafNode name="directed-broadcast">
<properties>
<help>Policy for handling IPv4 directed broadcast forwarding on all interfaces</help>
diff --git a/interface-definitions/include/firewall/set-packet-modifications.xml.i b/interface-definitions/include/firewall/set-packet-modifications.xml.i
new file mode 100644
index 000000000..ee019b64e
--- /dev/null
+++ b/interface-definitions/include/firewall/set-packet-modifications.xml.i
@@ -0,0 +1,96 @@
+<!-- include start from firewall/set-packet-modifications.xml.i -->
+<node name="set">
+ <properties>
+ <help>Packet modifications</help>
+ </properties>
+ <children>
+ <leafNode name="connection-mark">
+ <properties>
+ <help>Set connection mark</help>
+ <valueHelp>
+ <format>u32:0-2147483647</format>
+ <description>Connection mark</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-2147483647"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="dscp">
+ <properties>
+ <help>Set DSCP (Packet Differentiated Services Codepoint) bits</help>
+ <valueHelp>
+ <format>u32:0-63</format>
+ <description>DSCP number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-63"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="mark">
+ <properties>
+ <help>Set packet mark</help>
+ <valueHelp>
+ <format>u32:1-2147483647</format>
+ <description>Packet mark</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-2147483647"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="table">
+ <properties>
+ <help>Set the routing table for matched packets</help>
+ <valueHelp>
+ <format>u32:1-200</format>
+ <description>Table number</description>
+ </valueHelp>
+ <valueHelp>
+ <format>main</format>
+ <description>Main table</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-200"/>
+ <regex>(main)</regex>
+ </constraint>
+ <completionHelp>
+ <list>main</list>
+ <path>protocols static table</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="vrf">
+ <properties>
+ <help>VRF to forward packet with</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>VRF instance name</description>
+ </valueHelp>
+ <valueHelp>
+ <format>default</format>
+ <description>Forward into default global VRF</description>
+ </valueHelp>
+ <completionHelp>
+ <list>default</list>
+ <path>vrf name</path>
+ </completionHelp>
+ #include <include/constraint/vrf.xml.i>
+ </properties>
+ </leafNode>
+ <leafNode name="tcp-mss">
+ <properties>
+ <help>Set TCP Maximum Segment Size</help>
+ <valueHelp>
+ <format>u32:500-1460</format>
+ <description>Explicitly set TCP MSS value</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 500-1460"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/include/firewall/source-destination-group-inet.xml.i b/interface-definitions/include/firewall/source-destination-group-inet.xml.i
new file mode 100644
index 000000000..174051624
--- /dev/null
+++ b/interface-definitions/include/firewall/source-destination-group-inet.xml.i
@@ -0,0 +1,50 @@
+<!-- include start from firewall/source-destination-group-inet.xml.i -->
+<node name="group">
+ <properties>
+ <help>Group</help>
+ </properties>
+ <children>
+ <leafNode name="ipv4-address-group">
+ <properties>
+ <help>Group of IPv4 addresses</help>
+ <completionHelp>
+ <path>firewall group address-group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="ipv6-address-group">
+ <properties>
+ <help>Group of IPv6 addresses</help>
+ <completionHelp>
+ <path>firewall group ipv6-address-group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ #include <include/firewall/mac-group.xml.i>
+ <leafNode name="ipv4-network-group">
+ <properties>
+ <help>Group of IPv4 networks</help>
+ <completionHelp>
+ <path>firewall group network-group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="ipv6-network-group">
+ <properties>
+ <help>Group of IPv6 networks</help>
+ <completionHelp>
+ <path>firewall group ipv6-network-group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="port-group">
+ <properties>
+ <help>Group of ports</help>
+ <completionHelp>
+ <path>firewall group port-group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/policy/route-common.xml.i b/interface-definitions/include/policy/route-common.xml.i
index 203be73e7..19ffc0506 100644
--- a/interface-definitions/include/policy/route-common.xml.i
+++ b/interface-definitions/include/policy/route-common.xml.i
@@ -66,100 +66,7 @@
</leafNode>
</children>
</node>
-<node name="set">
- <properties>
- <help>Packet modifications</help>
- </properties>
- <children>
- <leafNode name="connection-mark">
- <properties>
- <help>Connection marking</help>
- <valueHelp>
- <format>u32:0-2147483647</format>
- <description>Connection marking</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-2147483647"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="dscp">
- <properties>
- <help>Packet Differentiated Services Codepoint (DSCP)</help>
- <valueHelp>
- <format>u32:0-63</format>
- <description>DSCP number</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-63"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="mark">
- <properties>
- <help>Packet marking</help>
- <valueHelp>
- <format>u32:1-2147483647</format>
- <description>Packet marking</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-2147483647"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="table">
- <properties>
- <help>Routing table to forward packet with</help>
- <valueHelp>
- <format>u32:1-200</format>
- <description>Table number</description>
- </valueHelp>
- <valueHelp>
- <format>main</format>
- <description>Main table</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-200"/>
- <regex>(main)</regex>
- </constraint>
- <completionHelp>
- <list>main</list>
- <path>protocols static table</path>
- </completionHelp>
- </properties>
- </leafNode>
- <leafNode name="vrf">
- <properties>
- <help>VRF to forward packet with</help>
- <valueHelp>
- <format>txt</format>
- <description>VRF instance name</description>
- </valueHelp>
- <valueHelp>
- <format>default</format>
- <description>Forward into default global VRF</description>
- </valueHelp>
- <completionHelp>
- <list>default</list>
- <path>vrf name</path>
- </completionHelp>
- #include <include/constraint/vrf.xml.i>
- </properties>
- </leafNode>
- <leafNode name="tcp-mss">
- <properties>
- <help>TCP Maximum Segment Size</help>
- <valueHelp>
- <format>u32:500-1460</format>
- <description>Explicitly set TCP MSS value</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 500-1460"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
-</node>
+#include <include/firewall/set-packet-modifications.xml.i>
#include <include/firewall/state.xml.i>
#include <include/firewall/tcp-flags.xml.i>
#include <include/firewall/tcp-mss.xml.i>
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index facd498ca..cac6d2953 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -167,7 +167,10 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
if address_mask:
operator = '!=' if exclude else '=='
operator = f'& {address_mask} {operator} '
- output.append(f'{ip_name} {prefix}addr {operator}{suffix}')
+ if is_ipv4(suffix):
+ output.append(f'ip {prefix}addr {operator}{suffix}')
+ else:
+ output.append(f'ip6 {prefix}addr {operator}{suffix}')
if 'fqdn' in side_conf:
fqdn = side_conf['fqdn']
@@ -236,22 +239,38 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
if 'group' in side_conf:
group = side_conf['group']
- if 'address_group' in group:
- group_name = group['address_group']
- operator = ''
- exclude = group_name[0] == "!"
- if exclude:
- operator = '!='
- group_name = group_name[1:]
- if address_mask:
- operator = '!=' if exclude else '=='
- operator = f'& {address_mask} {operator}'
- output.append(f'{ip_name} {prefix}addr {operator} @A{def_suffix}_{group_name}')
- elif 'dynamic_address_group' in group:
+ for ipvx_address_group in ['address_group', 'ipv4_address_group', 'ipv6_address_group']:
+ if ipvx_address_group in group:
+ group_name = group[ipvx_address_group]
+ operator = ''
+ exclude = group_name[0] == "!"
+ if exclude:
+ operator = '!='
+ group_name = group_name[1:]
+ if address_mask:
+ operator = '!=' if exclude else '=='
+ operator = f'& {address_mask} {operator}'
+ # for bridge, change ip_name
+ if ip_name == 'bri':
+ ip_name = 'ip' if ipvx_address_group == 'ipv4_address_group' else 'ip6'
+ def_suffix = '6' if ipvx_address_group == 'ipv6_address_group' else ''
+ output.append(f'{ip_name} {prefix}addr {operator} @A{def_suffix}_{group_name}')
+ for ipvx_network_group in ['network_group', 'ipv4_network_group', 'ipv6_network_group']:
+ if ipvx_network_group in group:
+ group_name = group[ipvx_network_group]
+ operator = ''
+ if group_name[0] == "!":
+ operator = '!='
+ group_name = group_name[1:]
+ # for bridge, change ip_name
+ if ip_name == 'bri':
+ ip_name = 'ip' if ipvx_network_group == 'ipv4_network_group' else 'ip6'
+ def_suffix = '6' if ipvx_network_group == 'ipv6_network_group' else ''
+ output.append(f'{ip_name} {prefix}addr {operator} @N{def_suffix}_{group_name}')
+ if 'dynamic_address_group' in group:
group_name = group['dynamic_address_group']
operator = ''
- exclude = group_name[0] == "!"
- if exclude:
+ if group_name[0] == "!":
operator = '!='
group_name = group_name[1:]
output.append(f'{ip_name} {prefix}addr {operator} @DA{def_suffix}_{group_name}')
@@ -263,13 +282,6 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
operator = '!='
group_name = group_name[1:]
output.append(f'{ip_name} {prefix}addr {operator} @D_{group_name}')
- elif 'network_group' in group:
- group_name = group['network_group']
- operator = ''
- if group_name[0] == '!':
- operator = '!='
- group_name = group_name[1:]
- output.append(f'{ip_name} {prefix}addr {operator} @N{def_suffix}_{group_name}')
if 'mac_group' in group:
group_name = group['mac_group']
operator = ''
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index e6317050c..551f8ce65 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -695,13 +695,21 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK')
self.verify_nftables_chain([['return']], 'ip6 vyos_conntrack', 'FW_CONNTRACK')
- def test_bridge_basic_rules(self):
+ def test_bridge_firewall(self):
name = 'smoketest'
interface_in = 'eth0'
mac_address = '00:53:00:00:00:01'
vlan_id = '12'
vlan_prior = '3'
+ # Check bridge-nf-call-iptables default value: 0
+ self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-iptables'), '0')
+ self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-ip6tables'), '0')
+
+ self.cli_set(['firewall', 'group', 'ipv6-address-group', 'AGV6', 'address', '2001:db1::1'])
+ self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'action', 'accept'])
+ self.cli_set(['firewall', 'global-options', 'apply-to-bridged-traffic', 'ipv4'])
+
self.cli_set(['firewall', 'bridge', 'name', name, 'default-action', 'accept'])
self.cli_set(['firewall', 'bridge', 'name', name, 'default-log'])
self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'action', 'accept'])
@@ -718,20 +726,41 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'jump-target', name])
self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'vlan', 'priority', vlan_prior])
+ self.cli_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'action', 'accept'])
+ self.cli_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'inbound-interface', 'name', interface_in])
+ self.cli_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'source', 'address', '192.0.2.2'])
+ self.cli_set(['firewall', 'bridge', 'input', 'filter', 'rule', '1', 'state', 'new'])
+
+ self.cli_set(['firewall', 'bridge', 'prerouting', 'filter', 'rule', '1', 'action', 'notrack'])
+ self.cli_set(['firewall', 'bridge', 'prerouting', 'filter', 'rule', '1', 'destination', 'group', 'ipv6-address-group', 'AGV6'])
+
self.cli_commit()
nftables_search = [
+ ['set A6_AGV6'],
+ ['type ipv6_addr'],
+ ['elements', '2001:db1::1'],
['chain VYOS_FORWARD_filter'],
['type filter hook forward priority filter; policy accept;'],
+ ['jump VYOS_STATE_POLICY'],
[f'vlan id {vlan_id}', 'accept'],
[f'vlan pcp {vlan_prior}', f'jump NAME_{name}'],
['log prefix "[bri-FWD-filter-default-D]"', 'drop', 'FWD-filter default-action drop'],
[f'chain NAME_{name}'],
[f'ether saddr {mac_address}', f'iifname "{interface_in}"', f'log prefix "[bri-NAM-{name}-1-A]" log level crit', 'accept'],
- ['accept', f'{name} default-action accept']
+ ['accept', f'{name} default-action accept'],
+ ['chain VYOS_INPUT_filter'],
+ ['type filter hook input priority filter; policy accept;'],
+ ['ct state new', 'ip saddr 192.0.2.2', f'iifname "{interface_in}"', 'accept'],
+ ['chain VYOS_PREROUTING_filter'],
+ ['type filter hook prerouting priority filter; policy accept;'],
+ ['ip6 daddr @A6_AGV6', 'notrack']
]
self.verify_nftables(nftables_search, 'bridge vyos_filter')
+ ## Check bridge-nf-call-iptables is set to 1, and for ipv6 remains on default 0
+ self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-iptables'), '1')
+ self.assertEqual(get_sysctl('net.bridge.bridge-nf-call-ip6tables'), '0')
def test_source_validation(self):
# Strict
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 352d5cbb1..02bf00bcc 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -36,6 +36,7 @@ from vyos.utils.process import cmd
from vyos.utils.process import rc_cmd
from vyos import ConfigError
from vyos import airbag
+from subprocess import run as subp_run
airbag.enable()
@@ -47,7 +48,12 @@ valid_groups = [
'domain_group',
'network_group',
'port_group',
- 'interface_group'
+ 'interface_group',
+ ## Added for group ussage in bridge firewall
+ 'ipv4_address_group',
+ 'ipv6_address_group',
+ 'ipv4_network_group',
+ 'ipv6_network_group'
]
nested_group_types = [
@@ -128,41 +134,32 @@ def get_config(config=None):
return firewall
-def verify_jump_target(firewall, root_chain, jump_target, ipv6, recursive=False):
+def verify_jump_target(firewall, hook, jump_target, family, recursive=False):
targets_seen = []
targets_pending = [jump_target]
while targets_pending:
target = targets_pending.pop()
- if not ipv6:
- if target not in dict_search_args(firewall, 'ipv4', 'name'):
- raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system')
- target_rules = dict_search_args(firewall, 'ipv4', 'name', target, 'rule')
- else:
- if target not in dict_search_args(firewall, 'ipv6', 'name'):
- raise ConfigError(f'Invalid jump-target. Firewall ipv6 name {target} does not exist on the system')
- target_rules = dict_search_args(firewall, 'ipv6', 'name', target, 'rule')
+ if 'name' not in firewall[family]:
+ raise ConfigError(f'Invalid jump-target. Firewall {family} name {target} does not exist on the system')
+ elif target not in dict_search_args(firewall, family, 'name'):
+ raise ConfigError(f'Invalid jump-target. Firewall {family} name {target} does not exist on the system')
- no_ipsec_in = root_chain in ('output', )
+ target_rules = dict_search_args(firewall, family, 'name', target, 'rule')
+ no_ipsec_in = hook in ('output', )
if target_rules:
for target_rule_conf in target_rules.values():
# Output hook types will not tolerate 'meta ipsec exists' matches even in jump targets:
if no_ipsec_in and (dict_search_args(target_rule_conf, 'ipsec', 'match_ipsec_in') is not None \
or dict_search_args(target_rule_conf, 'ipsec', 'match_none_in') is not None):
- if not ipv6:
- raise ConfigError(f'Invalid jump-target for {root_chain}. Firewall name {target} rules contain incompatible ipsec inbound matches')
- else:
- raise ConfigError(f'Invalid jump-target for {root_chain}. Firewall ipv6 name {target} rules contain incompatible ipsec inbound matches')
+ raise ConfigError(f'Invalid jump-target for {hook}. Firewall {family} name {target} rules contain incompatible ipsec inbound matches')
# Make sure we're not looping back on ourselves somewhere:
if recursive and 'jump_target' in target_rule_conf:
child_target = target_rule_conf['jump_target']
if child_target in targets_seen:
- if not ipv6:
- raise ConfigError(f'Loop detected in jump-targets, firewall name {target} refers to previously traversed name {child_target}')
- else:
- raise ConfigError(f'Loop detected in jump-targets, firewall ipv6 name {target} refers to previously traversed ipv6 name {child_target}')
+ raise ConfigError(f'Loop detected in jump-targets, firewall {family} name {target} refers to previously traversed {family} name {child_target}')
targets_pending.append(child_target)
if len(targets_seen) == 7:
path_txt = ' -> '.join(targets_seen)
@@ -170,7 +167,7 @@ def verify_jump_target(firewall, root_chain, jump_target, ipv6, recursive=False)
targets_seen.append(target)
-def verify_rule(firewall, chain_name, rule_conf, ipv6):
+def verify_rule(firewall, family, hook, priority, rule_id, rule_conf):
if 'action' not in rule_conf:
raise ConfigError('Rule action must be defined')
@@ -181,10 +178,10 @@ def verify_rule(firewall, chain_name, rule_conf, ipv6):
if 'jump' not in rule_conf['action']:
raise ConfigError('jump-target defined, but action jump needed and it is not defined')
target = rule_conf['jump_target']
- if chain_name != 'name': # This is a bit clumsy, but consolidates a chunk of code.
- verify_jump_target(firewall, chain_name, target, ipv6, recursive=True)
+ if hook != 'name': # This is a bit clumsy, but consolidates a chunk of code.
+ verify_jump_target(firewall, hook, target, family, recursive=True)
else:
- verify_jump_target(firewall, chain_name, target, ipv6, recursive=False)
+ verify_jump_target(firewall, hook, target, family, recursive=False)
if rule_conf['action'] == 'offload':
if 'offload_target' not in rule_conf:
@@ -246,9 +243,9 @@ def verify_rule(firewall, chain_name, rule_conf, ipv6):
raise ConfigError(f'Cannot match a tcp flag as set and not set')
if 'protocol' in rule_conf:
- if rule_conf['protocol'] == 'icmp' and ipv6:
+ if rule_conf['protocol'] == 'icmp' and family == 'ipv6':
raise ConfigError(f'Cannot match IPv4 ICMP protocol on IPv6, use ipv6-icmp')
- if rule_conf['protocol'] == 'ipv6-icmp' and not ipv6:
+ if rule_conf['protocol'] == 'ipv6-icmp' and family == 'ipv4':
raise ConfigError(f'Cannot match IPv6 ICMP protocol on IPv4, use icmp')
for side in ['destination', 'source']:
@@ -266,7 +263,18 @@ def verify_rule(firewall, chain_name, rule_conf, ipv6):
if group in side_conf['group']:
group_name = side_conf['group'][group]
- fw_group = f'ipv6_{group}' if ipv6 and group in ['address_group', 'network_group'] else group
+ if family == 'ipv6' and group in ['address_group', 'network_group']:
+ fw_group = f'ipv6_{group}'
+ elif family == 'bridge':
+ if group =='ipv4_address_group':
+ fw_group = 'address_group'
+ elif group == 'ipv4_network_group':
+ fw_group = 'network_group'
+ else:
+ fw_group = group
+ else:
+ fw_group = group
+
error_group = fw_group.replace("_", "-")
if group in ['address_group', 'network_group', 'domain_group']:
@@ -302,7 +310,7 @@ def verify_rule(firewall, chain_name, rule_conf, ipv6):
raise ConfigError(f'Dynamic address group must be defined.')
else:
target = rule_conf['add_address_to_group'][type]['address_group']
- fwall_group = 'ipv6_address_group' if ipv6 else 'address_group'
+ fwall_group = 'ipv6_address_group' if family == 'ipv6' else 'address_group'
group_obj = dict_search_args(firewall, 'group', 'dynamic_group', fwall_group, target)
if group_obj is None:
raise ConfigError(f'Invalid dynamic address group on firewall rule')
@@ -379,43 +387,25 @@ def verify(firewall):
for group_name, group in groups.items():
verify_nested_group(group_name, group, groups, [])
- if 'ipv4' in firewall:
- for name in ['name','forward','input','output', 'prerouting']:
- if name in firewall['ipv4']:
- for name_id, name_conf in firewall['ipv4'][name].items():
- if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf:
- raise ConfigError('default-action set to jump, but no default-jump-target specified')
- if 'default_jump_target' in name_conf:
- target = name_conf['default_jump_target']
- if 'jump' not in name_conf['default_action']:
- raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined')
- if name_conf['default_jump_target'] == name_id:
- raise ConfigError(f'Loop detected on default-jump-target.')
- verify_jump_target(firewall, name, target, False, recursive=True)
-
- if 'rule' in name_conf:
- for rule_id, rule_conf in name_conf['rule'].items():
- verify_rule(firewall, name, rule_conf, False)
-
- if 'ipv6' in firewall:
- for name in ['name','forward','input','output', 'prerouting']:
- if name in firewall['ipv6']:
- for name_id, name_conf in firewall['ipv6'][name].items():
- if 'jump' in name_conf['default_action'] and 'default_jump_target' not in name_conf:
- raise ConfigError('default-action set to jump, but no default-jump-target specified')
- if 'default_jump_target' in name_conf:
- target = name_conf['default_jump_target']
- if 'jump' not in name_conf['default_action']:
- raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined')
- if name_conf['default_jump_target'] == name_id:
- raise ConfigError(f'Loop detected on default-jump-target.')
- verify_jump_target(firewall, name, target, True, recursive=True)
-
- if 'rule' in name_conf:
- for rule_id, rule_conf in name_conf['rule'].items():
- verify_rule(firewall, name, rule_conf, True)
-
- #### ZONESSSS
+ for family in ['ipv4', 'ipv6', 'bridge']:
+ if family in firewall:
+ for chain in ['name','forward','input','output', 'prerouting']:
+ if chain in firewall[family]:
+ for priority, priority_conf in firewall[family][chain].items():
+ if 'jump' in priority_conf['default_action'] and 'default_jump_target' not in priority_conf:
+ raise ConfigError('default-action set to jump, but no default-jump-target specified')
+ if 'default_jump_target' in priority_conf:
+ target = priority_conf['default_jump_target']
+ if 'jump' not in priority_conf['default_action']:
+ raise ConfigError('default-jump-target defined, but default-action jump needed and it is not defined')
+ if priority_conf['default_jump_target'] == priority:
+ raise ConfigError(f'Loop detected on default-jump-target.')
+ if target not in dict_search_args(firewall[family], 'name'):
+ raise ConfigError(f'Invalid jump-target. Firewall name {target} does not exist on the system')
+ if 'rule' in priority_conf:
+ for rule_id, rule_conf in priority_conf['rule'].items():
+ verify_rule(firewall, family, chain, priority, rule_id, rule_conf)
+
local_zone = False
zone_interfaces = []
@@ -495,8 +485,53 @@ def generate(firewall):
render(sysctl_file, 'firewall/sysctl-firewall.conf.j2', firewall)
return None
+def parse_firewall_error(output):
+ # Define the regex patterns to extract the error message and the comment
+ error_pattern = re.compile(r'Error:\s*(.*?)\n')
+ comment_pattern = re.compile(r'comment\s+"([^"]+)"')
+ error_output = []
+
+ # Find all error messages in the output
+ error_matches = error_pattern.findall(output)
+ # Find all comment matches in the output
+ comment_matches = comment_pattern.findall(output)
+
+ if not error_matches or not comment_matches:
+ raise ConfigError(f'Unknown firewall error detected: {output}')
+
+ error_output.append('Fail to apply firewall')
+ # Loop over the matches and process them
+ for error_message, comment in zip(error_matches, comment_matches):
+ # Parse the comment
+ parsed_entries = comment.split('-')
+ family = 'bridge' if parsed_entries[0] == 'bri' else parsed_entries[0]
+ if parsed_entries[1] == 'NAM':
+ chain = 'name'
+ elif parsed_entries[1] == 'FWD':
+ chain = 'forward'
+ elif parsed_entries[1] == 'INP':
+ chain = 'input'
+ elif parsed_entries[1] == 'OUT':
+ chain = 'output'
+ elif parsed_entries[1] == 'PRE':
+ chain = 'prerouting'
+ error_output.append(f'Error found on: firewall {family} {chain} {parsed_entries[2]} rule {parsed_entries[3]}')
+ error_output.append(f'\tError message: {error_message.strip()}')
+
+ raise ConfigError('\n'.join(error_output))
+
def apply(firewall):
+ # Use nft -c option to check current configuration file
+ completed_process = subp_run(['nft', '-c', '--file', nftables_conf], capture_output=True)
+ install_result = completed_process.returncode
+ if install_result == 1:
+ # We need to handle firewall error
+ output = completed_process.stderr
+ parse_firewall_error(output.decode())
+
+ # No error detected during check, we can apply the new configuration
install_result, output = rc_cmd(f'nft --file {nftables_conf}')
+ # Double check just in case
if install_result == 1:
raise ConfigError(f'Failed to apply firewall: {output}')
diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf
index c9b8ef8fe..76be41ddc 100644
--- a/src/etc/sysctl.d/30-vyos-router.conf
+++ b/src/etc/sysctl.d/30-vyos-router.conf
@@ -110,3 +110,8 @@ net.ipv6.conf.all.seg6_enabled = 0
net.ipv6.conf.default.seg6_enabled = 0
net.vrf.strict_mode = 1
+
+# https://vyos.dev/T6570
+# By default, do not forward traffic from bridge to IPvX layer
+net.bridge.bridge-nf-call-iptables = 0
+net.bridge.bridge-nf-call-ip6tables = 0 \ No newline at end of file