Skip to content

Commit

Permalink
T5150: initial VRF support for Kernel/Zebra route-map filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
c-po committed Apr 13, 2023
1 parent f9aa4c6 commit b454ddc
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 127 deletions.
1 change: 0 additions & 1 deletion data/configd-include.json
Expand Up @@ -86,5 +86,4 @@
"vpn_pptp.py",
"vpn_sstp.py",
"vrf.py",
"vrf_vni.py"
]
9 changes: 0 additions & 9 deletions data/templates/frr/vrf-vni.frr.j2

This file was deleted.

26 changes: 7 additions & 19 deletions data/templates/frr/zebra.route-map.frr.j2
@@ -1,21 +1,9 @@
!
{% if vrf is vyos_defined %}
vrf {{ vrf }}
{% if protocol is vyos_defined %}
{% for prot, prot_config in protocol.items() %}
{{ afi }} protocol {{ protocol }} route-map {{ prot_config.route_map }}
{% endfor %}
{% endif %}
exit-vrf
!
{% else %}
{% if protocol is vyos_defined %}
{% for prot, prot_config in protocol.items() %}
{% if prot is vyos_defined('ospfv3') %}
{% set prot = 'ospf6' %}
{% endif %}
{{ afi }} protocol {{ prot }} route-map {{ prot_config.route_map }}
{% endfor %}
{% endif %}
{% if protocol is vyos_defined %}
{% for protocol_name, protocol_config in protocol.items() %}
{% if protocol_name is vyos_defined('ospfv3') %}
{% set protocol_name = 'ospf6' %}
{% endif %}
{{ afi }} protocol {{ protocol_name }} route-map {{ protocol_config.route_map }}
{% endfor %}
{% endif %}
!
24 changes: 24 additions & 0 deletions data/templates/frr/zebra.vrf.route-map.frr.j2
@@ -0,0 +1,24 @@
!
{% if name is vyos_defined %}
{% for vrf, vrf_config in name.items() %}
vrf {{ vrf }}
{% if vrf_config.ip.protocol is vyos_defined %}
{% for protocol_name, protocol_config in vrf_config.ip.protocol.items() %}
ip protocol {{ protocol_name }} route-map {{ protocol_config.route_map }}
{% endfor %}
{% endif %}
{% if vrf_config.ipv6.protocol is vyos_defined %}
{% for protocol_name, protocol_config in vrf_config.ipv6.protocol.items() %}
{% if protocol_name is vyos_defined('ospfv3') %}
{% set protocol_name = 'ospf6' %}
{% endif %}
ipv6 protocol {{ protocol_name }} route-map {{ protocol_config.route_map }}
{% endfor %}
{% endif %}
{% if vrf_config.vni is vyos_defined %}
vni {{ vrf_config.vni }}
{% endif %}
{% endfor %}
exit-vrf
!
{% endif %}
15 changes: 1 addition & 14 deletions interface-definitions/vrf.xml.in
Expand Up @@ -121,20 +121,7 @@
<constraintErrorMessage>VRF routing table must be in range from 100 to 65535</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="vni" owner="${vyos_conf_scripts_dir}/vrf_vni.py">
<properties>
<help>Virtual Network Identifier</help>
<!-- priority must be after BGP -->
<priority>822</priority>
<valueHelp>
<format>u32:0-16777214</format>
<description>VXLAN virtual network identifier</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 0-16777214"/>
</constraint>
</properties>
</leafNode>
#include <include/vni.xml.i>
</children>
</tagNode>
</children>
Expand Down
11 changes: 0 additions & 11 deletions smoketest/scripts/cli/test_protocols_bgp.py
Expand Up @@ -809,7 +809,6 @@ def test_bgp_10_vrf_simple(self):
self.cli_set(vrf_base + ['table', table])
self.cli_set(vrf_base + ['protocols', 'bgp', 'system-as', ASN])
self.cli_set(vrf_base + ['protocols', 'bgp', 'parameters', 'router-id', router_id])
self.cli_set(vrf_base + ['protocols', 'bgp', 'route-map', route_map_in])
table = str(int(table) + 1000)

# import VRF routes do main RIB
Expand All @@ -822,7 +821,6 @@ def test_bgp_10_vrf_simple(self):
self.assertIn(f'router bgp {ASN}', frrconfig)
self.assertIn(f' address-family ipv6 unicast', frrconfig)


for vrf in vrfs:
self.assertIn(f' import vrf {vrf}', frrconfig)

Expand All @@ -831,15 +829,6 @@ def test_bgp_10_vrf_simple(self):
self.assertIn(f'router bgp {ASN} vrf {vrf}', frr_vrf_config)
self.assertIn(f' bgp router-id {router_id}', frr_vrf_config)

# XXX: Currently this is not working as FRR() class does not support
# route-maps for multiple vrfs because the modify_section() only works
# on lines and not text blocks.
#
# vrfconfig = self.getFRRconfig(f'vrf {vrf}')
# zebra_route_map = f' ip protocol bgp route-map {route_map_in}'
# self.assertIn(zebra_route_map, vrfconfig)


def test_bgp_11_confederation(self):
router_id = '127.10.10.2'
confed_id = str(int(ASN) + 1)
Expand Down
80 changes: 79 additions & 1 deletion smoketest/scripts/cli/test_vrf.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
# Copyright (C) 2020-2022 VyOS maintainers and contributors
# Copyright (C) 2020-2023 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 @@ -33,6 +33,8 @@

base_path = ['vrf']
vrfs = ['red', 'green', 'blue', 'foo-bar', 'baz_foo']
v4_protocols = ['any', 'babel', 'bgp', 'connected', 'eigrp', 'isis', 'kernel', 'ospf', 'rip', 'static', 'table']
v6_protocols = ['any', 'babel', 'bgp', 'connected', 'isis', 'kernel', 'ospfv3', 'ripng', 'static', 'table']

class VRFTest(VyOSUnitTestSHIM.TestCase):
_interfaces = []
Expand Down Expand Up @@ -291,5 +293,81 @@ def test_vrf_disable_forwarding(self):
self.assertEqual(read_file(f'/proc/sys/net/ipv4/conf/{vrf}/forwarding'), '0')
self.assertEqual(read_file(f'/proc/sys/net/ipv6/conf/{vrf}/forwarding'), '0')

def test_vrf_ip_protocol_route_map(self):
table = '6000'

for vrf in vrfs:
base = base_path + ['name', vrf]
self.cli_set(base + ['table', table])

for protocol in v4_protocols:
self.cli_set(['policy', 'route-map', f'route-map-{vrf}-{protocol}', 'rule', '10', 'action', 'permit'])
self.cli_set(base + ['ip', 'protocol', protocol, 'route-map', f'route-map-{vrf}-{protocol}'])

table = str(int(table) + 1)

self.cli_commit()

# Verify route-map properly applied to FRR
for vrf in vrfs:
frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra')
self.assertIn(f'vrf {vrf}', frrconfig)
for protocol in v4_protocols:
self.assertIn(f' ip protocol {protocol} route-map route-map-{vrf}-{protocol}', frrconfig)

def test_vrf_ip_ipv6_protocol_non_existing_route_map(self):
table = '6100'
non_existing = 'non-existing'

for vrf in vrfs:
base = base_path + ['name', vrf]
self.cli_set(base + ['table', table])
for protocol in v4_protocols:
self.cli_set(base + ['ip', 'protocol', protocol, 'route-map', f'v4-{non_existing}'])
for protocol in v6_protocols:
self.cli_set(base + ['ipv6', 'protocol', protocol, 'route-map', f'v6-{non_existing}'])

table = str(int(table) + 1)

# Both v4 and v6 route-maps do not exist yet
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(['policy', 'route-map', f'v4-{non_existing}', 'rule', '10', 'action', 'deny'])

# v6 route-map does not exist yet
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(['policy', 'route-map', f'v6-{non_existing}', 'rule', '10', 'action', 'deny'])

# Commit again
self.cli_commit()

def test_vrf_ipv6_protocol_route_map(self):
table = '6200'

for vrf in vrfs:
base = base_path + ['name', vrf]
self.cli_set(base + ['table', table])

for protocol in v6_protocols:
route_map = f'route-map-{vrf}-{protocol.replace("ospfv3", "ospf6")}'
self.cli_set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit'])
self.cli_set(base + ['ipv6', 'protocol', protocol, 'route-map', route_map])

table = str(int(table) + 1)

self.cli_commit()

# Verify route-map properly applied to FRR
for vrf in vrfs:
frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra')
self.assertIn(f'vrf {vrf}', frrconfig)
for protocol in v6_protocols:
# VyOS and FRR use a different name for OSPFv3 (IPv6)
if protocol == 'ospfv3':
protocol = 'ospf6'
route_map = f'route-map-{vrf}-{protocol}'
self.assertIn(f' ipv6 protocol {protocol} route-map {route_map}', frrconfig)

if __name__ == '__main__':
unittest.main(verbosity=2)
2 changes: 1 addition & 1 deletion src/conf_mode/protocols_static.py
Expand Up @@ -112,7 +112,7 @@ def apply(static):

if 'vrf' in static:
vrf = static['vrf']
frr_cfg.modify_section(f'^vrf {vrf}', stop_pattern='^exit', remove_stop_mark=True)
frr_cfg.modify_section(f'^vrf {vrf}', stop_pattern='^exit-vrf', remove_stop_mark=True)
else:
frr_cfg.modify_section(r'^ip route .*')
frr_cfg.modify_section(r'^ipv6 route .*')
Expand Down
49 changes: 43 additions & 6 deletions src/conf_mode/vrf.py
Expand Up @@ -20,9 +20,12 @@
from json import loads

from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.configverify import verify_route_map
from vyos.ifconfig import Interface
from vyos.template import render
from vyos.template import render_to_string
from vyos.util import call
from vyos.util import cmd
from vyos.util import dict_search
Expand Down Expand Up @@ -99,6 +102,14 @@ def get_config(config=None):
routes = vrf_routing(conf, name)
if routes: vrf['vrf_remove'][name]['route'] = routes

# We also need the route-map information from the config
#
# XXX: one MUST always call this without the key_mangling() option! See
# vyos.configverify.verify_common_route_maps() for more information.
tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'],
get_first_key=True)}}
# Merge policy dict into "regular" config dict
vrf = dict_merge(tmp, vrf)
return vrf

def verify(vrf):
Expand All @@ -116,35 +127,50 @@ def verify(vrf):
reserved_names = ["add", "all", "broadcast", "default", "delete", "dev", "get", "inet", "mtu", "link", "type",
"vrf"]
table_ids = []
for name, config in vrf['name'].items():
for name, vrf_config in vrf['name'].items():
# Reserved VRF names
if name in reserved_names:
raise ConfigError(f'VRF name "{name}" is reserved and connot be used!')

# table id is mandatory
if 'table' not in config:
if 'table' not in vrf_config:
raise ConfigError(f'VRF "{name}" table id is mandatory!')

# routing table id can't be changed - OS restriction
if os.path.isdir(f'/sys/class/net/{name}'):
tmp = str(dict_search('linkinfo.info_data.table', get_interface_config(name)))
if tmp and tmp != config['table']:
if tmp and tmp != vrf_config['table']:
raise ConfigError(f'VRF "{name}" table id modification not possible!')

# VRf routing table ID must be unique on the system
if config['table'] in table_ids:
if vrf_config['table'] in table_ids:
raise ConfigError(f'VRF "{name}" table id is not unique!')
table_ids.append(config['table'])
table_ids.append(vrf_config['table'])

tmp = dict_search('ip.protocol', vrf_config)
if tmp != None:
for protocol, protocol_options in tmp.items():
if 'route_map' in protocol_options:
verify_route_map(protocol_options['route_map'], vrf)

tmp = dict_search('ipv6.protocol', vrf_config)
if tmp != None:
for protocol, protocol_options in tmp.items():
if 'route_map' in protocol_options:
verify_route_map(protocol_options['route_map'], vrf)

return None


def generate(vrf):
# Render iproute2 VR helper names
render(config_file, 'iproute2/vrf.conf.j2', vrf)
# Render nftables zones config
render(nft_vrf_config, 'firewall/nftables-vrf-zones.j2', vrf)
return None
# Render VRF Kernel/Zebra route-map filters
vrf['frr_zebra_config'] = render_to_string('frr/zebra.vrf.route-map.frr.j2', vrf)

return None

def apply(vrf):
# Documentation
Expand Down Expand Up @@ -249,6 +275,17 @@ def apply(vrf):
nft_add_element = f'add element inet vrf_zones ct_iface_map {{ "{name}" : {table} }}'
cmd(f'nft {nft_add_element}')

# Apply FRR filters
zebra_daemon = 'zebra'
# Save original configuration prior to starting any commit actions
frr_cfg = frr.FRRConfig()

# The route-map used for the FIB (zebra) is part of the zebra daemon
frr_cfg.load_configuration(zebra_daemon)
frr_cfg.modify_section(f'^vrf .+', stop_pattern='^exit-vrf', remove_stop_mark=True)
if 'frr_zebra_config' in vrf:
frr_cfg.add_before(frr.default_add_before, vrf['frr_zebra_config'])
frr_cfg.commit_configuration(zebra_daemon)

# return to default lookup preference when no VRF is configured
if 'name' not in vrf:
Expand Down

0 comments on commit b454ddc

Please sign in to comment.