Skip to content

Commit

Permalink
Merge pull request #4180 from nicolas-fort/zbf-vrfaware
Browse files Browse the repository at this point in the history
T6841: firewall: improve config parsing for ZBF when using VRFs and interfaces attached to VRFs
  • Loading branch information
c-po authored Jan 6, 2025
2 parents 9c091f0 + dda428f commit 5ae3f05
Show file tree
Hide file tree
Showing 9 changed files with 261 additions and 73 deletions.
56 changes: 47 additions & 9 deletions data/templates/firewall/nftables-zone.j2
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@
{% endif %}
{% for zone_name, zone_conf in zone.items() %}
{% if 'local_zone' not in zone_conf %}
oifname { {{ zone_conf.interface | join(',') }} } counter jump VZONE_{{ zone_name }}
{% if 'interface' in zone_conf.member %}
oifname { {{ zone_conf.member.interface | join(',') }} } counter jump VZONE_{{ zone_name }}
{% endif %}
{% if 'vrf' in zone_conf.member %}
{% for vrf_name in zone_conf.member.vrf %}
oifname { {{ zone_conf['vrf_interfaces'][vrf_name] }} } counter jump VZONE_{{ zone_name }}
{% endfor %}
{% endif %}
{% endif %}
{% endfor %}
}
Expand Down Expand Up @@ -40,8 +47,15 @@
iifname lo counter return
{% if zone_conf.from is vyos_defined %}
{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall[fw_name] is vyos_defined %}
iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
iifname { {{ zone[from_zone].interface | join(",") }} } counter return

{% if 'interface' in zone[from_zone].member %}
iifname { {{ zone[from_zone].member.interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
iifname { {{ zone[from_zone].member.interface | join(",") }} } counter return
{% endif %}
{% if 'vrf' in zone[from_zone].member %}
iifname { {{ zone[from_zone].member.vrf | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
iifname { {{ zone[from_zone].member.vrf | join(",") }} } counter return
{% endif %}
{% endfor %}
{% endif %}
{{ zone_conf | nft_default_rule('zone_' + zone_name, family) }}
Expand All @@ -50,23 +64,47 @@
oifname lo counter return
{% if zone_conf.from_local is vyos_defined %}
{% for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall[fw_name] is vyos_defined %}
oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
oifname { {{ zone[from_zone].interface | join(",") }} } counter return
{% if 'interface' in zone[from_zone].member %}
oifname { {{ zone[from_zone].member.interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
oifname { {{ zone[from_zone].member.interface | join(",") }} } counter return
{% endif %}
{% if 'vrf' in zone[from_zone].member %}
{% for vrf_name in zone[from_zone].member.vrf %}
oifname { {{ zone[from_zone]['vrf_interfaces'][vrf_name] }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
oifname { {{ zone[from_zone]['vrf_interfaces'][vrf_name] }} } counter return
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
{{ zone_conf | nft_default_rule('zone_' + zone_name, family) }}
}
{% else %}
chain VZONE_{{ zone_name }} {
iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6) }}
{% if 'interface' in zone_conf.member %}
iifname { {{ zone_conf.member.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6) }}
{% endif %}
{% if 'vrf' in zone_conf.member %}
iifname { {{ zone_conf.member.vrf | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6) }}
{% endif %}
{% if zone_conf.intra_zone_filtering is vyos_defined %}
iifname { {{ zone_conf.interface | join(",") }} } counter return
{% if 'interface' in zone_conf.member %}
iifname { {{ zone_conf.member.interface | join(",") }} } counter return
{% endif %}
{% if 'vrf' in zone_conf.member %}
iifname { {{ zone_conf.member.vrf | join(",") }} } counter return
{% endif %}
{% endif %}
{% if zone_conf.from is vyos_defined %}
{% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall[fw_name] is vyos_defined %}
{% if zone[from_zone].local_zone is not defined %}
iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
iifname { {{ zone[from_zone].interface | join(",") }} } counter return
{% if 'interface' in zone[from_zone].member %}
iifname { {{ zone[from_zone].member.interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
iifname { {{ zone[from_zone].member.interface | join(",") }} } counter return
{% endif %}
{% if 'vrf' in zone[from_zone].member %}
iifname { {{ zone[from_zone].member.vrf | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }}
iifname { {{ zone[from_zone].member.vrf | join(",") }} } counter return
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
Expand Down
53 changes: 20 additions & 33 deletions interface-definitions/firewall.xml.in
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,7 @@
</properties>
<children>
#include <include/generic-description.xml.i>
<leafNode name="interface">
<properties>
<help>Interfaces to use this flowtable</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<multi/>
</properties>
</leafNode>
#include <include/generic-interface-multi.xml.i>
<leafNode name="offload">
<properties>
<help>Offloading method</help>
Expand Down Expand Up @@ -155,15 +147,7 @@
<constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage>
</properties>
<children>
<leafNode name="interface">
<properties>
<help>Interface-group member</help>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
<multi/>
</properties>
</leafNode>
#include <include/generic-interface-multi.xml.i>
<leafNode name="include">
<properties>
<help>Include another interface-group</help>
Expand Down Expand Up @@ -464,24 +448,27 @@
</node>
</children>
</tagNode>
<leafNode name="interface">
<node name="member">
<properties>
<help>Interface associated with zone</help>
<valueHelp>
<format>txt</format>
<description>Interface associated with zone</description>
</valueHelp>
<valueHelp>
<format>vrf</format>
<description>VRF associated with zone</description>
</valueHelp>
<completionHelp>
<script>${vyos_completion_dir}/list_interfaces</script>
<path>vrf name</path>
</completionHelp>
<multi/>
</properties>
</leafNode>
<children>
#include <include/generic-interface-multi.xml.i>
<leafNode name="vrf">
<properties>
<help>VRF associated with zone</help>
<valueHelp>
<format>vrf</format>
<description>VRF associated with zone</description>
</valueHelp>
<completionHelp>
<path>vrf name</path>
</completionHelp>
<multi/>
</properties>
</leafNode>
</children>
</node>
<node name="intra-zone-filtering">
<properties>
<help>Intra-zone filtering</help>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<!-- include start from include/version/firewall-version.xml.i -->
<syntaxVersion component='firewall' version='17'></syntaxVersion>
<syntaxVersion component='firewall' version='18'></syntaxVersion>
<!-- include end -->
4 changes: 3 additions & 1 deletion python/vyos/utils/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ def get_vrf_members(vrf: str) -> list:
answer = json.loads(output)
for data in answer:
if 'ifname' in data:
interfaces.append(data.get('ifname'))
# Skip PIM interfaces which appears in VRF
if 'pim' not in data.get('ifname'):
interfaces.append(data.get('ifname'))
except:
pass
return interfaces
Expand Down
96 changes: 94 additions & 2 deletions smoketest/scripts/cli/test_firewall.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
# Copyright (C) 2021-2023 VyOS maintainers and contributors
# Copyright (C) 2021-2024 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
Expand Down Expand Up @@ -906,7 +906,7 @@ def test_timeout_sysctl(self):
def test_zone_basic(self):
self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'default-action', 'drop'])
self.cli_set(['firewall', 'ipv6', 'name', 'smoketestv6', 'default-action', 'drop'])
self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'interface', 'eth0'])
self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'member', 'interface', 'eth0'])
self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'from', 'smoketest-local', 'firewall', 'name', 'smoketest'])
self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'intra-zone-filtering', 'firewall', 'ipv6-name', 'smoketestv6'])
self.cli_set(['firewall', 'zone', 'smoketest-local', 'local-zone'])
Expand Down Expand Up @@ -964,6 +964,98 @@ def test_zone_basic(self):
self.verify_nftables(nftables_search, 'ip vyos_filter')
self.verify_nftables(nftables_search_v6, 'ip6 vyos_filter')

def test_zone_with_vrf(self):
self.cli_set(['firewall', 'ipv4', 'name', 'ZONE1-to-LOCAL', 'default-action', 'accept'])
self.cli_set(['firewall', 'ipv4', 'name', 'ZONE2_to_ZONE1', 'default-action', 'continue'])
self.cli_set(['firewall', 'ipv6', 'name', 'LOCAL_to_ZONE2_v6', 'default-action', 'drop'])
self.cli_set(['firewall', 'zone', 'LOCAL', 'from', 'ZONE1', 'firewall', 'name', 'ZONE1-to-LOCAL'])
self.cli_set(['firewall', 'zone', 'LOCAL', 'local-zone'])
self.cli_set(['firewall', 'zone', 'ZONE1', 'from', 'ZONE2', 'firewall', 'name', 'ZONE2_to_ZONE1'])
self.cli_set(['firewall', 'zone', 'ZONE1', 'member', 'interface', 'eth1'])
self.cli_set(['firewall', 'zone', 'ZONE1', 'member', 'interface', 'eth2'])
self.cli_set(['firewall', 'zone', 'ZONE1', 'member', 'vrf', 'VRF-1'])
self.cli_set(['firewall', 'zone', 'ZONE2', 'from', 'LOCAL', 'firewall', 'ipv6-name', 'LOCAL_to_ZONE2_v6'])
self.cli_set(['firewall', 'zone', 'ZONE2', 'member', 'interface', 'vtun66'])
self.cli_set(['firewall', 'zone', 'ZONE2', 'member', 'vrf', 'VRF-2'])

self.cli_set(['vrf', 'name', 'VRF-1', 'table', '101'])
self.cli_set(['vrf', 'name', 'VRF-2', 'table', '102'])
self.cli_set(['interfaces', 'ethernet', 'eth0', 'vrf', 'VRF-1'])
self.cli_set(['interfaces', 'vti', 'vti1', 'vrf', 'VRF-2'])

self.cli_commit()

nftables_search = [
['chain NAME_ZONE1-to-LOCAL'],
['counter', 'accept', 'comment "NAM-ZONE1-to-LOCAL default-action accept"'],
['chain NAME_ZONE2_to_ZONE1'],
['counter', 'continue', 'comment "NAM-ZONE2_to_ZONE1 default-action continue"'],
['chain VYOS_ZONE_FORWARD'],
['type filter hook forward priority filter + 1'],
['oifname { "eth1", "eth2" }', 'counter packets', 'jump VZONE_ZONE1'],
['oifname "eth0"', 'counter packets', 'jump VZONE_ZONE1'],
['oifname "vtun66"', 'counter packets', 'jump VZONE_ZONE2'],
['oifname "vti1"', 'counter packets', 'jump VZONE_ZONE2'],
['chain VYOS_ZONE_LOCAL'],
['type filter hook input priority filter + 1'],
['counter packets', 'jump VZONE_LOCAL_IN'],
['chain VYOS_ZONE_OUTPUT'],
['type filter hook output priority filter + 1'],
['counter packets', 'jump VZONE_LOCAL_OUT'],
['chain VZONE_LOCAL_IN'],
['iifname { "eth1", "eth2" }', 'counter packets', 'jump NAME_ZONE1-to-LOCAL'],
['iifname "VRF-1"', 'counter packets', 'jump NAME_ZONE1-to-LOCAL'],
['counter packets', 'drop', 'comment "zone_LOCAL default-action drop"'],
['chain VZONE_LOCAL_OUT'],
['counter packets', 'drop', 'comment "zone_LOCAL default-action drop"'],
['chain VZONE_ZONE1'],
['iifname { "eth1", "eth2" }', 'counter packets', 'return'],
['iifname "VRF-1"', 'counter packets', 'return'],
['iifname "vtun66"', 'counter packets', 'jump NAME_ZONE2_to_ZONE1'],
['iifname "vtun66"', 'counter packets', 'return'],
['iifname "VRF-2"', 'counter packets', 'jump NAME_ZONE2_to_ZONE1'],
['iifname "VRF-2"', 'counter packets', 'return'],
['counter packets', 'drop', 'comment "zone_ZONE1 default-action drop"'],
['chain VZONE_ZONE2'],
['iifname "vtun66"', 'counter packets', 'return'],
['iifname "VRF-2"', 'counter packets', 'return'],
['counter packets', 'drop', 'comment "zone_ZONE2 default-action drop"']
]

nftables_search_v6 = [
['chain NAME6_LOCAL_to_ZONE2_v6'],
['counter', 'drop', 'comment "NAM-LOCAL_to_ZONE2_v6 default-action drop"'],
['chain VYOS_ZONE_FORWARD'],
['type filter hook forward priority filter + 1'],
['oifname { "eth1", "eth2" }', 'counter packets', 'jump VZONE_ZONE1'],
['oifname "eth0"', 'counter packets', 'jump VZONE_ZONE1'],
['oifname "vtun66"', 'counter packets', 'jump VZONE_ZONE2'],
['oifname "vti1"', 'counter packets', 'jump VZONE_ZONE2'],
['chain VYOS_ZONE_LOCAL'],
['type filter hook input priority filter + 1'],
['counter packets', 'jump VZONE_LOCAL_IN'],
['chain VYOS_ZONE_OUTPUT'],
['type filter hook output priority filter + 1'],
['counter packets', 'jump VZONE_LOCAL_OUT'],
['chain VZONE_LOCAL_IN'],
['counter packets', 'drop', 'comment "zone_LOCAL default-action drop"'],
['chain VZONE_LOCAL_OUT'],
['oifname "vtun66"', 'counter packets', 'jump NAME6_LOCAL_to_ZONE2_v6'],
['oifname "vti1"', 'counter packets', 'jump NAME6_LOCAL_to_ZONE2_v6'],
['counter packets', 'drop', 'comment "zone_LOCAL default-action drop"'],
['chain VZONE_ZONE1'],
['iifname { "eth1", "eth2" }', 'counter packets', 'return'],
['iifname "VRF-1"', 'counter packets', 'return'],
['counter packets', 'drop', 'comment "zone_ZONE1 default-action drop"'],
['chain VZONE_ZONE2'],
['iifname "vtun66"', 'counter packets', 'return'],
['iifname "VRF-2"', 'counter packets', 'return'],
['counter packets', 'drop', 'comment "zone_ZONE2 default-action drop"']
]

self.verify_nftables(nftables_search, 'ip vyos_filter')
self.verify_nftables(nftables_search_v6, 'ip6 vyos_filter')

def test_flow_offload(self):
self.cli_set(['interfaces', 'ethernet', 'eth0', 'vif', '10'])
self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0.10'])
Expand Down
Loading

0 comments on commit 5ae3f05

Please sign in to comment.