diff options
author | Daniil Baturin <daniil@vyos.io> | 2023-12-21 15:32:36 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-21 15:32:36 +0000 |
commit | c9b71b0669924da41dc50632bdcaed710f03d4d7 (patch) | |
tree | 30a3eada0c894fb0ff07f83b360f9535c5d43627 | |
parent | 5c91424daafbee512f7c87caa418cd05f0bc4633 (diff) | |
parent | 0ee2f8285c81878687a9f92e6a3b0f10c4d75584 (diff) | |
download | vyos-1x-c9b71b0669924da41dc50632bdcaed710f03d4d7.tar.gz vyos-1x-c9b71b0669924da41dc50632bdcaed710f03d4d7.zip |
Merge pull request #2674 from vyos/mergify/bp/sagitta/pr-2663
srv6: T591: enable SR enabled packet processing on defined interfaces (backport #2663)
-rw-r--r-- | interface-definitions/protocols-segment-routing.xml.in | 48 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_protocols_segment_routing.py | 42 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_vrf.py | 21 | ||||
-rwxr-xr-x | src/conf_mode/protocols_segment_routing.py | 66 | ||||
-rwxr-xr-x | src/conf_mode/vrf.py | 12 | ||||
-rw-r--r-- | src/etc/sysctl.d/30-vyos-router.conf | 11 |
6 files changed, 173 insertions, 27 deletions
diff --git a/interface-definitions/protocols-segment-routing.xml.in b/interface-definitions/protocols-segment-routing.xml.in index d461e9c5d..4308f0c91 100644 --- a/interface-definitions/protocols-segment-routing.xml.in +++ b/interface-definitions/protocols-segment-routing.xml.in @@ -8,6 +8,54 @@ <priority>900</priority> </properties> <children> + <tagNode name="interface"> + <properties> + <help>Interface specific Segment Routing options</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Interface name</description> + </valueHelp> + <constraint> + #include <include/constraint/interface-name.xml.i> + </constraint> + </properties> + <children> + <node name="srv6"> + <properties> + <help>Accept SR-enabled IPv6 packets on this interface</help> + </properties> + <children> + <leafNode name="hmac"> + <properties> + <help>Define HMAC policy for ingress SR-enabled packets on this interface</help> + <completionHelp> + <list>accept drop ignore</list> + </completionHelp> + <valueHelp> + <format>accept</format> + <description>Accept packets without HMAC, validate packets with HMAC</description> + </valueHelp> + <valueHelp> + <format>drop</format> + <description>Drop packets without HMAC, validate packets with HMAC</description> + </valueHelp> + <valueHelp> + <format>ignore</format> + <description>Ignore HMAC field.</description> + </valueHelp> + <constraint> + <regex>(accept|drop|ignore)</regex> + </constraint> + </properties> + <defaultValue>accept</defaultValue> + </leafNode> + </children> + </node> + </children> + </tagNode> <node name="srv6"> <properties> <help>Segment-Routing SRv6 configuration</help> diff --git a/smoketest/scripts/cli/test_protocols_segment_routing.py b/smoketest/scripts/cli/test_protocols_segment_routing.py index 81d42b925..403c05924 100755 --- a/smoketest/scripts/cli/test_protocols_segment_routing.py +++ b/smoketest/scripts/cli/test_protocols_segment_routing.py @@ -20,8 +20,10 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section from vyos.utils.process import cmd from vyos.utils.process import process_named_running +from vyos.utils.system import sysctl_read base_path = ['protocols', 'segment-routing'] PROCESS_NAME = 'zebra' @@ -45,6 +47,7 @@ class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase): self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME)) def test_srv6(self): + interfaces = Section.interfaces('ethernet', vlan=False) locators = { 'foo' : { 'prefix' : '2001:a::/64' }, 'foo' : { 'prefix' : '2001:b::/64', 'usid' : {} }, @@ -55,8 +58,18 @@ class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase): if 'usid' in locator_config: self.cli_set(base_path + ['srv6', 'locator', locator, 'behavior-usid']) + # verify() - SRv6 should be enabled on at least one interface! + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'srv6']) + self.cli_commit() + for interface in interfaces: + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '0') # default + frrconfig = self.getFRRconfig(f'segment-routing', daemon='zebra') self.assertIn(f'segment-routing', frrconfig) self.assertIn(f' srv6', frrconfig) @@ -65,6 +78,35 @@ class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase): self.assertIn(f' locator {locator}', frrconfig) self.assertIn(f' prefix {locator_config["prefix"]} block-len 40 node-len 24 func-bits 16', frrconfig) + def test_srv6_sysctl(self): + interfaces = Section.interfaces('ethernet', vlan=False) + + # HMAC accept + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'srv6']) + self.cli_set(base_path + ['interface', interface, 'srv6', 'hmac', 'ignore']) + self.cli_commit() + + for interface in interfaces: + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '-1') # ignore + + # HMAC drop + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'srv6']) + self.cli_set(base_path + ['interface', interface, 'srv6', 'hmac', 'drop']) + self.cli_commit() + + for interface in interfaces: + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') + self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '1') # drop + + # Disable SRv6 on first interface + first_if = interfaces[-1] + self.cli_delete(base_path + ['interface', first_if]) + self.cli_commit() + + self.assertEqual(sysctl_read(f'net.ipv6.conf.{first_if}.seg6_enabled'), '0') if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py index bb91eddea..6207a1b41 100755 --- a/smoketest/scripts/cli/test_vrf.py +++ b/smoketest/scripts/cli/test_vrf.py @@ -30,6 +30,7 @@ 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'] @@ -58,6 +59,8 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): 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' @@ -130,8 +133,9 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): # Ensure VRF was created self.assertIn(vrf, interfaces()) # Verify IP forwarding is 1 (enabled) - self.assertEqual(read_file(f'/proc/sys/net/ipv4/conf/{vrf}/forwarding'), '1') - self.assertEqual(read_file(f'/proc/sys/net/ipv6/conf/{vrf}/forwarding'), '1') + 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)) @@ -149,10 +153,11 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify VRF configuration - tmp = read_file('/proc/sys/net/ipv4/tcp_l3mdev_accept') - self.assertIn(tmp, '1') - tmp = read_file('/proc/sys/net/ipv4/udp_l3mdev_accept') - self.assertIn(tmp, '1') + 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. @@ -290,8 +295,8 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): # Ensure VRF was created self.assertIn(vrf, interfaces()) # Verify IP forwarding is 0 (disabled) - 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') + 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' diff --git a/src/conf_mode/protocols_segment_routing.py b/src/conf_mode/protocols_segment_routing.py index eb1653212..d865c2ac0 100755 --- a/src/conf_mode/protocols_segment_routing.py +++ b/src/conf_mode/protocols_segment_routing.py @@ -19,7 +19,10 @@ import os from sys import exit from vyos.config import Config +from vyos.configdict import node_changed from vyos.template import render_to_string +from vyos.utils.dict import dict_search +from vyos.utils.system import sysctl_write from vyos import ConfigError from vyos import frr from vyos import airbag @@ -32,33 +35,74 @@ def get_config(config=None): conf = Config() base = ['protocols', 'segment-routing'] - sr = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) + sr = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_recursive_defaults=True) - # We have gathered the dict representation of the CLI, but there are default - # options which we need to update into the dictionary retrived. - sr = conf.merge_defaults(sr, recursive=True) + # FRR has VRF support for different routing daemons. As interfaces belong + # to VRFs - or the global VRF, we need to check for changed interfaces so + # that they will be properly rendered for the FRR config. Also this eases + # removal of interfaces from the running configuration. + interfaces_removed = node_changed(conf, base + ['interface']) + if interfaces_removed: + sr['interface_removed'] = list(interfaces_removed) + import pprint + pprint.pprint(sr) return sr -def verify(static): +def verify(sr): + if 'srv6' in sr: + srv6_enable = False + if 'interface' in sr: + for interface, interface_config in sr['interface'].items(): + if 'srv6' in interface_config: + srv6_enable = True + break + if not srv6_enable: + raise ConfigError('SRv6 should be enabled on at least one interface!') return None -def generate(static): - if not static: +def generate(sr): + if not sr: return None - static['new_frr_config'] = render_to_string('frr/zebra.segment_routing.frr.j2', static) + sr['new_frr_config'] = render_to_string('frr/zebra.segment_routing.frr.j2', sr) return None -def apply(static): +def apply(sr): zebra_daemon = 'zebra' + if 'interface_removed' in sr: + for interface in sr['interface_removed']: + # Disable processing of IPv6-SR packets + sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '0') + + if 'interface' in sr: + for interface, interface_config in sr['interface'].items(): + # Accept or drop SR-enabled IPv6 packets on this interface + if 'srv6' in interface_config: + sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '1') + # Define HMAC policy for ingress SR-enabled packets on this interface + # It's a redundant check as HMAC has a default value - but better safe + # then sorry + tmp = dict_search('srv6.hmac', interface_config) + if tmp == 'accept': + sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '0') + elif tmp == 'drop': + sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '1') + elif tmp == 'ignore': + sysctl_write(f'net.ipv6.conf.{interface}.seg6_require_hmac', '-1') + else: + sysctl_write(f'net.ipv6.conf.{interface}.seg6_enabled', '0') + # Save original configuration prior to starting any commit actions frr_cfg = frr.FRRConfig() frr_cfg.load_configuration(zebra_daemon) frr_cfg.modify_section(r'^segment-routing') - if 'new_frr_config' in static: - frr_cfg.add_before(frr.default_add_before, static['new_frr_config']) + if 'new_frr_config' in sr: + frr_cfg.add_before(frr.default_add_before, sr['new_frr_config']) frr_cfg.commit_configuration(zebra_daemon) return None diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py index 37625142c..9b1b6355f 100755 --- a/src/conf_mode/vrf.py +++ b/src/conf_mode/vrf.py @@ -214,6 +214,18 @@ def apply(vrf): # 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: # Separate VRFs in conntrack table # check if table already exists diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf index 67d96969e..6291be5f0 100644 --- a/src/etc/sysctl.d/30-vyos-router.conf +++ b/src/etc/sysctl.d/30-vyos-router.conf @@ -105,11 +105,6 @@ net.core.rps_sock_flow_entries = 32768 net.core.default_qdisc=fq_codel net.ipv4.tcp_congestion_control=bbr -# VRF - Virtual routing and forwarding -# 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 -net.vrf.strict_mode=1 +# Disable IPv6 Segment Routing packets by default +net.ipv6.conf.all.seg6_enabled = 0 +net.ipv6.conf.default.seg6_enabled = 0 |