Page MenuHomeVyOS Platform

No OneTemporary

Size
35 KB
Referenced Files
None
Subscribers
None
diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py
index 6207a1b41..a3090ee41 100755
--- a/smoketest/scripts/cli/test_vrf.py
+++ b/smoketest/scripts/cli/test_vrf.py
@@ -1,497 +1,500 @@
#!/usr/bin/env python3
#
# 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
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
import os
import json
import unittest
from netifaces import interfaces
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Interface
from vyos.ifconfig import Section
from vyos.template import is_ipv4
from vyos.utils.process import cmd
from vyos.utils.file import read_file
from vyos.utils.network import get_interface_config
from vyos.utils.network import is_intf_addr_assigned
from vyos.utils.system import sysctl_read
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 = []
@classmethod
def setUpClass(cls):
# we need to filter out VLAN interfaces identified by a dot (.)
# in their name - just in case!
if 'TEST_ETH' in os.environ:
tmp = os.environ['TEST_ETH'].split()
cls._interfaces = tmp
else:
for tmp in Section.interfaces('ethernet', vlan=False):
cls._interfaces.append(tmp)
# call base-classes classmethod
super(VRFTest, cls).setUpClass()
+ def setUp(self):
+ # VRF strict_most ist always enabled
+ tmp = read_file('/proc/sys/net/vrf/strict_mode')
+ self.assertEqual(tmp, '1')
+
def tearDown(self):
# delete all VRFs
self.cli_delete(base_path)
self.cli_commit()
for vrf in vrfs:
self.assertNotIn(vrf, interfaces())
- # If there is no VRF defined, strict_mode should be off
- self.assertEqual(sysctl_read('net.vrf.strict_mode'), '0')
def test_vrf_vni_and_table_id(self):
base_table = '1000'
table = base_table
for vrf in vrfs:
base = base_path + ['name', vrf]
description = f'VyOS-VRF-{vrf}'
self.cli_set(base + ['description', description])
# check validate() - a table ID is mandatory
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(base + ['table', table])
self.cli_set(base + ['vni', table])
if vrf == 'green':
self.cli_set(base + ['disable'])
table = str(int(table) + 1)
# commit changes
self.cli_commit()
# Verify VRF configuration
table = base_table
iproute2_config = read_file('/etc/iproute2/rt_tables.d/vyos-vrf.conf')
for vrf in vrfs:
description = f'VyOS-VRF-{vrf}'
self.assertTrue(vrf in interfaces())
vrf_if = Interface(vrf)
# validate proper interface description
self.assertEqual(vrf_if.get_alias(), description)
# validate admin up/down state of VRF
state = 'up'
if vrf == 'green':
state = 'down'
self.assertEqual(vrf_if.get_admin_state(), state)
# Test the iproute2 lookup file, syntax is as follows:
#
# # id vrf name comment
# 1000 red # VyOS-VRF-red
# 1001 green # VyOS-VRF-green
# ...
regex = f'{table}\s+{vrf}\s+#\s+{description}'
self.assertTrue(re.findall(regex, iproute2_config))
frrconfig = self.getFRRconfig(f'vrf {vrf}')
self.assertIn(f' vni {table}', frrconfig)
tmp = get_interface_config(vrf)
self.assertEqual(int(table), tmp['linkinfo']['info_data']['table'])
# Increment table ID for the next run
table = str(int(table) + 1)
def test_vrf_loopbacks_ips(self):
table = '2000'
for vrf in vrfs:
base = base_path + ['name', vrf]
self.cli_set(base + ['table', str(table)])
table = str(int(table) + 1)
# commit changes
self.cli_commit()
# Verify VRF configuration
loopbacks = ['127.0.0.1', '::1']
for vrf in vrfs:
# Ensure VRF was created
self.assertIn(vrf, interfaces())
# Verify IP forwarding is 1 (enabled)
self.assertEqual(sysctl_read(f'net.ipv4.conf.{vrf}.forwarding'), '1')
self.assertEqual(sysctl_read(f'net.ipv6.conf.{vrf}.forwarding'), '1')
# Test for proper loopback IP assignment
for addr in loopbacks:
self.assertTrue(is_intf_addr_assigned(vrf, addr))
def test_vrf_bind_all(self):
table = '2000'
for vrf in vrfs:
base = base_path + ['name', vrf]
self.cli_set(base + ['table', str(table)])
table = str(int(table) + 1)
self.cli_set(base_path + ['bind-to-all'])
# commit changes
self.cli_commit()
# Verify VRF configuration
self.assertEqual(sysctl_read('net.ipv4.tcp_l3mdev_accept'), '1')
self.assertEqual(sysctl_read('net.ipv4.udp_l3mdev_accept'), '1')
# If there is any VRF defined, strict_mode should be on
self.assertEqual(sysctl_read('net.vrf.strict_mode'), '1')
def test_vrf_table_id_is_unalterable(self):
# Linux Kernel prohibits the change of a VRF table on the fly.
# VRF must be deleted and recreated!
table = '1000'
vrf = vrfs[0]
base = base_path + ['name', vrf]
self.cli_set(base + ['table', table])
# commit changes
self.cli_commit()
# Check if VRF has been created
self.assertTrue(vrf in interfaces())
table = str(int(table) + 1)
self.cli_set(base + ['table', table])
# check validate() - table ID can not be altered!
with self.assertRaises(ConfigSessionError):
self.cli_commit()
def test_vrf_assign_interface(self):
vrf = vrfs[0]
table = '5000'
self.cli_set(['vrf', 'name', vrf, 'table', table])
for interface in self._interfaces:
section = Section.section(interface)
self.cli_set(['interfaces', section, interface, 'vrf', vrf])
# commit changes
self.cli_commit()
# Verify VRF assignmant
for interface in self._interfaces:
tmp = get_interface_config(interface)
self.assertEqual(vrf, tmp['master'])
# cleanup
section = Section.section(interface)
self.cli_delete(['interfaces', section, interface, 'vrf'])
def test_vrf_static_route(self):
base_table = '100'
table = base_table
for vrf in vrfs:
next_hop = f'192.0.{table}.1'
prefix = f'10.0.{table}.0/24'
base = base_path + ['name', vrf]
self.cli_set(base + ['vni', table])
# check validate() - a table ID is mandatory
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(base + ['table', table])
self.cli_set(base + ['protocols', 'static', 'route', prefix, 'next-hop', next_hop])
table = str(int(table) + 1)
# commit changes
self.cli_commit()
# Verify VRF configuration
table = base_table
for vrf in vrfs:
next_hop = f'192.0.{table}.1'
prefix = f'10.0.{table}.0/24'
self.assertTrue(vrf in interfaces())
frrconfig = self.getFRRconfig(f'vrf {vrf}')
self.assertIn(f' vni {table}', frrconfig)
self.assertIn(f' ip route {prefix} {next_hop}', frrconfig)
# Increment table ID for the next run
table = str(int(table) + 1)
def test_vrf_link_local_ip_addresses(self):
# Testcase for issue T4331
table = '100'
vrf = 'orange'
interface = 'dum9998'
addresses = ['192.0.2.1/26', '2001:db8:9998::1/64', 'fe80::1/64']
for address in addresses:
self.cli_set(['interfaces', 'dummy', interface, 'address', address])
# Create dummy interfaces
self.cli_commit()
# ... and verify IP addresses got assigned
for address in addresses:
self.assertTrue(is_intf_addr_assigned(interface, address))
# Move interface to VRF
self.cli_set(base_path + ['name', vrf, 'table', table])
self.cli_set(['interfaces', 'dummy', interface, 'vrf', vrf])
# Apply VRF config
self.cli_commit()
# Ensure VRF got created
self.assertIn(vrf, interfaces())
# ... and IP addresses are still assigned
for address in addresses:
self.assertTrue(is_intf_addr_assigned(interface, address))
# Verify VRF table ID
tmp = get_interface_config(vrf)
self.assertEqual(int(table), tmp['linkinfo']['info_data']['table'])
# Verify interface is assigned to VRF
tmp = get_interface_config(interface)
self.assertEqual(vrf, tmp['master'])
# Delete Interface
self.cli_delete(['interfaces', 'dummy', interface])
self.cli_commit()
def test_vrf_disable_forwarding(self):
table = '2000'
for vrf in vrfs:
base = base_path + ['name', vrf]
self.cli_set(base + ['table', table])
self.cli_set(base + ['ip', 'disable-forwarding'])
self.cli_set(base + ['ipv6', 'disable-forwarding'])
table = str(int(table) + 1)
# commit changes
self.cli_commit()
# Verify VRF configuration
loopbacks = ['127.0.0.1', '::1']
for vrf in vrfs:
# Ensure VRF was created
self.assertIn(vrf, interfaces())
# Verify IP forwarding is 0 (disabled)
self.assertEqual(sysctl_read(f'net.ipv4.conf.{vrf}.forwarding'), '0')
self.assertEqual(sysctl_read(f'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)
# Delete route-maps
for vrf in vrfs:
base = base_path + ['name', vrf]
self.cli_delete(['policy', 'route-map', f'route-map-{vrf}-{protocol}'])
self.cli_delete(base + ['ip', 'protocol'])
self.cli_commit()
# Verify route-map properly is removed from FRR
for vrf in vrfs:
frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra')
self.assertNotIn(f'vrf {vrf}', 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)
# Delete route-maps
for vrf in vrfs:
base = base_path + ['name', vrf]
self.cli_delete(['policy', 'route-map', f'route-map-{vrf}-{protocol}'])
self.cli_delete(base + ['ipv6', 'protocol'])
self.cli_commit()
# Verify route-map properly is removed from FRR
for vrf in vrfs:
frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra')
self.assertNotIn(f'vrf {vrf}', frrconfig)
def test_vrf_vni_duplicates(self):
base_table = '6300'
table = base_table
for vrf in vrfs:
base = base_path + ['name', vrf]
self.cli_set(base + ['table', str(table)])
self.cli_set(base + ['vni', '100'])
table = str(int(table) + 1)
# L3VNIs can only be used once
with self.assertRaises(ConfigSessionError):
self.cli_commit()
table = base_table
for vrf in vrfs:
base = base_path + ['name', vrf]
self.cli_set(base + ['vni', str(table)])
table = str(int(table) + 1)
# commit changes
self.cli_commit()
# Verify VRF configuration
table = base_table
for vrf in vrfs:
self.assertTrue(vrf in interfaces())
frrconfig = self.getFRRconfig(f'vrf {vrf}')
self.assertIn(f' vni {table}', frrconfig)
# Increment table ID for the next run
table = str(int(table) + 1)
def test_vrf_vni_add_change_remove(self):
base_table = '6300'
table = base_table
for vrf in vrfs:
base = base_path + ['name', vrf]
self.cli_set(base + ['table', str(table)])
self.cli_set(base + ['vni', str(table)])
table = str(int(table) + 1)
# commit changes
self.cli_commit()
# Verify VRF configuration
table = base_table
for vrf in vrfs:
self.assertTrue(vrf in interfaces())
frrconfig = self.getFRRconfig(f'vrf {vrf}')
self.assertIn(f' vni {table}', frrconfig)
# Increment table ID for the next run
table = str(int(table) + 1)
# Now change all L3VNIs (increment 2)
# We must also change the base_table number as we probably could get
# duplicate VNI's during the test as VNIs are applied 1:1 to FRR
base_table = '5000'
table = base_table
for vrf in vrfs:
base = base_path + ['name', vrf]
self.cli_set(base + ['vni', str(table)])
table = str(int(table) + 2)
# commit changes
self.cli_commit()
# Verify VRF configuration
table = base_table
for vrf in vrfs:
self.assertTrue(vrf in interfaces())
frrconfig = self.getFRRconfig(f'vrf {vrf}')
self.assertIn(f' vni {table}', frrconfig)
# Increment table ID for the next run
table = str(int(table) + 2)
# Now delete all the VNIs
for vrf in vrfs:
base = base_path + ['name', vrf]
self.cli_delete(base + ['vni'])
# commit changes
self.cli_commit()
# Verify no VNI is defined
for vrf in vrfs:
self.assertTrue(vrf in interfaces())
frrconfig = self.getFRRconfig(f'vrf {vrf}')
self.assertNotIn('vni', frrconfig)
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index f2c544aa6..a2f4956be 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -1,333 +1,319 @@
#!/usr/bin/env python3
#
# Copyright (C) 2020-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
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
from sys import exit
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.utils.dict import dict_search
-from vyos.utils.kernel import check_kmod
from vyos.utils.network import get_interface_config
from vyos.utils.network import get_vrf_members
from vyos.utils.network import interface_exists
from vyos.utils.process import call
from vyos.utils.process import cmd
from vyos.utils.system import sysctl_write
from vyos import ConfigError
from vyos import frr
from vyos import airbag
airbag.enable()
config_file = '/etc/iproute2/rt_tables.d/vyos-vrf.conf'
k_mod = ['vrf']
def has_rule(af : str, priority : int, table : str=None):
"""
Check if a given ip rule exists
$ ip --json -4 rule show
[{'l3mdev': None, 'priority': 1000, 'src': 'all'},
{'action': 'unreachable', 'l3mdev': None, 'priority': 2000, 'src': 'all'},
{'priority': 32765, 'src': 'all', 'table': 'local'},
{'priority': 32766, 'src': 'all', 'table': 'main'},
{'priority': 32767, 'src': 'all', 'table': 'default'}]
"""
if af not in ['-4', '-6']:
raise ValueError()
command = f'ip --detail --json {af} rule show'
for tmp in loads(cmd(command)):
if 'priority' in tmp and 'table' in tmp:
if tmp['priority'] == priority and tmp['table'] == table:
return True
elif 'priority' in tmp and table in tmp:
# l3mdev table has a different layout
if tmp['priority'] == priority:
return True
return False
def vrf_interfaces(c, match):
matched = []
old_level = c.get_level()
c.set_level(['interfaces'])
section = c.get_config_dict([], get_first_key=True)
for type in section:
interfaces = section[type]
for name in interfaces:
interface = interfaces[name]
if 'vrf' in interface:
v = interface.get('vrf', '')
if v == match:
matched.append(name)
c.set_level(old_level)
return matched
def vrf_routing(c, match):
matched = []
old_level = c.get_level()
c.set_level(['protocols', 'vrf'])
if match in c.list_nodes([]):
matched.append(match)
c.set_level(old_level)
return matched
def get_config(config=None):
if config:
conf = config
else:
conf = Config()
base = ['vrf']
vrf = conf.get_config_dict(base, key_mangling=('-', '_'),
no_tag_node_value_mangle=True, get_first_key=True)
# determine which VRF has been removed
for name in node_changed(conf, base + ['name']):
if 'vrf_remove' not in vrf:
vrf.update({'vrf_remove' : {}})
vrf['vrf_remove'][name] = {}
# get VRF bound interfaces
interfaces = vrf_interfaces(conf, name)
if interfaces: vrf['vrf_remove'][name]['interface'] = interfaces
# get VRF bound routing instances
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)}}
# L3VNI setup is done via vrf_vni.py as it must be de-configured (on node
# deletetion prior to the BGP process. Tell the Jinja2 template no VNI
# setup is needed
vrf.update({'no_vni' : ''})
# Merge policy dict into "regular" config dict
vrf = dict_merge(tmp, vrf)
return vrf
def verify(vrf):
# ensure VRF is not assigned to any interface
if 'vrf_remove' in vrf:
for name, config in vrf['vrf_remove'].items():
if 'interface' in config:
raise ConfigError(f'Can not remove VRF "{name}", it still has '\
f'member interfaces!')
if 'route' in config:
raise ConfigError(f'Can not remove VRF "{name}", it still has '\
f'static routes installed!')
if 'name' in vrf:
reserved_names = ["add", "all", "broadcast", "default", "delete", "dev",
"get", "inet", "mtu", "link", "type", "vrf"]
table_ids = []
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 vrf_config:
raise ConfigError(f'VRF "{name}" table id is mandatory!')
# routing table id can't be changed - OS restriction
if interface_exists(name):
tmp = str(dict_search('linkinfo.info_data.table', get_interface_config(name)))
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 'table' in vrf_config and vrf_config['table'] in table_ids:
raise ConfigError(f'VRF "{name}" table id is not unique!')
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 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
#
# - https://github.com/torvalds/linux/blob/master/Documentation/networking/vrf.txt
# - https://github.com/Mellanox/mlxsw/wiki/Virtual-Routing-and-Forwarding-(VRF)
# - https://github.com/Mellanox/mlxsw/wiki/L3-Tunneling
# - https://netdevconf.info/1.1/proceedings/slides/ahern-vrf-tutorial.pdf
# - https://netdevconf.info/1.2/slides/oct6/02_ahern_what_is_l3mdev_slides.pdf
# set the default VRF global behaviour
bind_all = '0'
if 'bind_to_all' in vrf:
bind_all = '1'
sysctl_write('net.ipv4.tcp_l3mdev_accept', bind_all)
sysctl_write('net.ipv4.udp_l3mdev_accept', bind_all)
for tmp in (dict_search('vrf_remove', vrf) or []):
if interface_exists(tmp):
# T5492: deleting a VRF instance may leafe processes running
# (e.g. dhclient) as there is a depedency ordering issue in the CLI.
# We need to ensure that we stop the dhclient processes first so
# a proper DHCLP RELEASE message is sent
for interface in get_vrf_members(tmp):
vrf_iface = Interface(interface)
vrf_iface.set_dhcp(False)
vrf_iface.set_dhcpv6(False)
# Remove nftables conntrack zone map item
nft_del_element = f'delete element inet vrf_zones ct_iface_map {{ "{tmp}" }}'
cmd(f'nft {nft_del_element}')
# Delete the VRF Kernel interface
call(f'ip link delete dev {tmp}')
- # Enable/Disable VRF strict mode
- # When net.vrf.strict_mode=0 (default) it is possible to associate multiple
- # VRF devices to the same table. Conversely, when net.vrf.strict_mode=1 a
- # table can be associated to a single VRF device.
- #
- # A VRF table can be used by the VyOS CLI only once (ensured by verify()),
- # this simply adds an additional Kernel safety net
- strict_mode = '0'
- # Set to 1 if any VRF is defined
- if 'name' in vrf: strict_mode = '1'
- sysctl_write('net.vrf.strict_mode', strict_mode)
-
if 'name' in vrf:
# Linux routing uses rules to find tables - routing targets are then
# looked up in those tables. If the lookup got a matching route, the
# process ends.
#
# TL;DR; first table with a matching entry wins!
#
# You can see your routing table lookup rules using "ip rule", sadly the
# local lookup is hit before any VRF lookup. Pinging an addresses from the
# VRF will usually find a hit in the local table, and never reach the VRF
# routing table - this is usually not what you want. Thus we will
# re-arrange the tables and move the local lookup further down once VRFs
# are enabled.
#
# Thanks to https://stbuehler.de/blog/article/2020/02/29/using_vrf__virtual_routing_and_forwarding__on_linux.html
for afi in ['-4', '-6']:
# move lookup local to pref 32765 (from 0)
if not has_rule(afi, 32765, 'local'):
call(f'ip {afi} rule add pref 32765 table local')
if has_rule(afi, 0, 'local'):
call(f'ip {afi} rule del pref 0')
# make sure that in VRFs after failed lookup in the VRF specific table
# nothing else is reached
if not has_rule(afi, 1000, 'l3mdev'):
# this should be added by the kernel when a VRF is created
# add it here for completeness
call(f'ip {afi} rule add pref 1000 l3mdev protocol kernel')
# add another rule with an unreachable target which only triggers in VRF context
# if a route could not be reached
if not has_rule(afi, 2000, 'l3mdev'):
call(f'ip {afi} rule add pref 2000 l3mdev unreachable')
for name, config in vrf['name'].items():
table = config['table']
if not interface_exists(name):
# For each VRF apart from your default context create a VRF
# interface with a separate routing table
call(f'ip link add {name} type vrf table {table}')
# set VRF description for e.g. SNMP monitoring
vrf_if = Interface(name)
# We also should add proper loopback IP addresses to the newly added
# VRF for services bound to the loopback address (SNMP, NTP)
vrf_if.add_addr('127.0.0.1/8')
vrf_if.add_addr('::1/128')
# add VRF description if available
vrf_if.set_alias(config.get('description', ''))
# Enable/Disable IPv4 forwarding
tmp = dict_search('ip.disable_forwarding', config)
value = '0' if (tmp != None) else '1'
vrf_if.set_ipv4_forwarding(value)
# Enable/Disable IPv6 forwarding
tmp = dict_search('ipv6.disable_forwarding', config)
value = '0' if (tmp != None) else '1'
vrf_if.set_ipv6_forwarding(value)
# Enable/Disable of an interface must always be done at the end of the
# derived class to make use of the ref-counting set_admin_state()
# function. We will only enable the interface if 'up' was called as
# often as 'down'. This is required by some interface implementations
# as certain parameters can only be changed when the interface is
# in admin-down state. This ensures the link does not flap during
# reconfiguration.
state = 'down' if 'disable' in config else 'up'
vrf_if.set_admin_state(state)
# Add nftables conntrack zone map item
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 None
if __name__ == '__main__':
try:
- check_kmod(k_mod)
c = get_config()
verify(c)
generate(c)
apply(c)
except ConfigError as e:
print(e)
exit(1)
diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf
index 6291be5f0..c9b8ef8fe 100644
--- a/src/etc/sysctl.d/30-vyos-router.conf
+++ b/src/etc/sysctl.d/30-vyos-router.conf
@@ -1,110 +1,112 @@
#
# VyOS specific sysctl settings, see sysctl.conf (5) for information.
#
# Panic on OOPS
kernel.panic_on_oops=1
# Timeout before rebooting on panic
kernel.panic=60
# Send all core files to /var/core/core.program.pid.time
kernel.core_pattern=/var/core/core-%e-%p-%t
# ARP configuration
# arp_filter - allow multiple network interfaces on same subnet
# arp_announce - avoid local addresses no on target's subnet
# arp_ignore - reply only if target IP is local_address on the interface
# arp_filter defaults to 1 so set all to 0 so vrrp interfaces can override it.
net.ipv4.conf.all.arp_filter=0
# https://vyos.dev/T300
net.ipv4.conf.all.arp_ignore=0
net.ipv4.conf.all.arp_announce=2
# Enable packet forwarding for IPv4
net.ipv4.ip_forward=1
# Enable directed broadcast forwarding feature described in rfc1812#section-5.3.5.2 and rfc2644.
# Note that setting the 'all' entry to 1 doesn't enable directed broadcast forwarding on all interfaces.
# To enable directed broadcast forwarding on an interface, both the 'all' entry and the input interface entry should be set to 1.
net.ipv4.conf.all.bc_forwarding=1
net.ipv4.conf.default.bc_forwarding=0
# if a primary address is removed from an interface promote the
# secondary address if available
net.ipv4.conf.all.promote_secondaries=1
# Ignore ICMP broadcasts sent to broadcast/multicast
net.ipv4.icmp_echo_ignore_broadcasts=1
# Ignore bogus ICMP errors
net.ipv4.icmp_ignore_bogus_error_responses=1
# Send ICMP responses with primary address of exiting interface
net.ipv4.icmp_errors_use_inbound_ifaddr=1
# Log packets with impossible addresses to kernel log
net.ipv4.conf.all.log_martians=1
# Do not ignore all ICMP ECHO requests by default
net.ipv4.icmp_echo_ignore_all=0
# Disable source validation by default
net.ipv4.conf.all.rp_filter=0
net.ipv4.conf.default.rp_filter=0
# Enable tcp syn-cookies by default
net.ipv4.tcp_syncookies=1
# Disable accept_redirects by default for any interface
net.ipv4.conf.all.accept_redirects=0
net.ipv4.conf.default.accept_redirects=0
net.ipv6.conf.all.accept_redirects=0
net.ipv6.conf.default.accept_redirects=0
# Disable accept_source_route by default
net.ipv4.conf.all.accept_source_route=0
net.ipv4.conf.default.accept_source_route=0
net.ipv6.conf.all.accept_source_route=0
net.ipv6.conf.default.accept_source_route=0
# Enable send_redirects by default
net.ipv4.conf.all.send_redirects=1
net.ipv4.conf.default.send_redirects=1
# Increase size of buffer for netlink
net.core.rmem_max=2097152
# Remove IPv4 and IPv6 routes from forward information base when link goes down
net.ipv4.conf.all.ignore_routes_with_linkdown=1
net.ipv4.conf.default.ignore_routes_with_linkdown=1
net.ipv6.conf.all.ignore_routes_with_linkdown=1
net.ipv6.conf.default.ignore_routes_with_linkdown=1
# Enable packet forwarding for IPv6
net.ipv6.conf.all.forwarding=1
# Increase route table limit
net.ipv6.route.max_size = 262144
# Do not forget IPv6 addresses when a link goes down
net.ipv6.conf.default.keep_addr_on_down=1
net.ipv6.conf.all.keep_addr_on_down=1
net.ipv6.route.skip_notify_on_dev_down=1
# Default value of 20 seems to interfere with larger OSPF and VRRP setups
net.ipv4.igmp_max_memberships = 512
# Enable global RFS (Receive Flow Steering) configuration. RFS is inactive
# until explicitly configured at the interface level
net.core.rps_sock_flow_entries = 32768
# Congestion control
net.core.default_qdisc=fq_codel
net.ipv4.tcp_congestion_control=bbr
# Disable IPv6 Segment Routing packets by default
net.ipv6.conf.all.seg6_enabled = 0
net.ipv6.conf.default.seg6_enabled = 0
+
+net.vrf.strict_mode = 1

File Metadata

Mime Type
text/x-diff
Expires
Sat, Sep 26, 11:57 AM (1 d, 20 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4285082
Default Alt Text
(35 KB)

Event Timeline