diff options
| author | Cheeze-It <16260577+Cheeze-It@users.noreply.github.com> | 2026-04-30 06:25:49 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-30 15:25:49 +0300 |
| commit | cf5d2b87e184c50b02638442cbc0691e6433dc4c (patch) | |
| tree | d0f2d3336182eee3cf197529a9f84760a20cd0e7 | |
| parent | defc2a20adfef3cfa2018c061ab5bc92881203d4 (diff) | |
| download | vyos-1x-cf5d2b87e184c50b02638442cbc0691e6433dc4c.tar.gz vyos-1x-cf5d2b87e184c50b02638442cbc0691e6433dc4c.zip | |
T6750: sr-te: Adding initial Segment Routing Traffic Engineering portion of FRR (#4994)
* sr-te: T6750: Adding Segment Routing Traffic Engineering portion of FRR
---------
Co-authored-by: Cheeze_It <none@none.com>
Co-authored-by: Christian Breunig <christian@breunig.cc>
| -rw-r--r-- | data/templates/frr/daemons.frr.tmpl | 4 | ||||
| -rw-r--r-- | data/templates/frr/zebra.segment_routing.frr.j2 | 39 | ||||
| -rw-r--r-- | interface-definitions/include/segment-routing/algorithm.xml.i | 20 | ||||
| -rw-r--r-- | interface-definitions/protocols_segment-routing.xml.in | 200 | ||||
| -rw-r--r-- | op-mode-definitions/show-segment-routing.xml.in | 17 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_protocols_segment-routing.py | 97 | ||||
| -rwxr-xr-x | src/conf_mode/protocols_segment-routing.py | 68 |
7 files changed, 434 insertions, 11 deletions
diff --git a/data/templates/frr/daemons.frr.tmpl b/data/templates/frr/daemons.frr.tmpl index fa39ae9d0..32ef1079b 100644 --- a/data/templates/frr/daemons.frr.tmpl +++ b/data/templates/frr/daemons.frr.tmpl @@ -12,7 +12,7 @@ # eigrp # sharpd # fabricd -# pathd +# # # The zebra, mgmtd and staticd daemons are always started and can not be disabled # @@ -37,7 +37,7 @@ pbrd=no bfdd=yes fabricd=yes vrrpd=no -pathd=no +pathd=yes # # Define defaults for all services even those who shall be kept disabled. diff --git a/data/templates/frr/zebra.segment_routing.frr.j2 b/data/templates/frr/zebra.segment_routing.frr.j2 index a0bcc9210..b3f2a1fbb 100644 --- a/data/templates/frr/zebra.segment_routing.frr.j2 +++ b/data/templates/frr/zebra.segment_routing.frr.j2 @@ -1,6 +1,43 @@ ! -{% if srv6 is vyos_defined %} segment-routing +{% if traffic_engineering is vyos_defined %} + traffic-eng + mpls-te on +{% if traffic_engineering.database_import_protocol is vyos_defined %} +{% for protocol, protocol_config in traffic_engineering.database_import_protocol.items() %} + mpls-te import {{ protocol | replace('ospf', 'ospfv2') }} +{% endfor %} +{% endif %} +{% if traffic_engineering.segment_list is vyos_defined %} +{% for segment_list, segment_list_config in traffic_engineering.segment_list.items() %} + segment-list {{ segment_list }} +{% if segment_list_config.index.items() is vyos_defined %} +{% for index, index_config in segment_list_config.index.items() %} +{% if index_config.mpls.label is vyos_defined %} + index {{ index }} mpls label {{ index_config.mpls.label }} +{% endif %} +{% if index_config.nai is vyos_defined %} +{% if index_config.nai.adjacency is vyos_defined %} +{% for address_family, address_family_options in index_config.nai.adjacency.items() %} + index {{ index }} nai adjacency {{ address_family_options.source_identifier }} {{ address_family_options.destination_identifier }} +{% endfor %} +{% endif %} +{% if index_config.nai.prefix is vyos_defined %} +{% for address_family, address_family_options in index_config.nai.prefix.items() %} +{% for prefix, prefix_options in address_family_options.prefix_identifier.items() %} + index {{ index }} nai prefix {{ prefix }} {{ 'algorithm 0' if prefix_options.algorithm.spf is vyos_defined }} {{ 'algorithm 1' if prefix_options.algorithm.strict_spf is vyos_defined }} +{% endfor %} +{% endfor %} +{% endif %} +{% endif %} +{% endfor %} +{% endif %} +{% endfor %} +{% endif %} + exit +{% endif %} +! +{% if srv6 is vyos_defined %} srv6 {% if srv6.encapsulation is vyos_defined %} encapsulation diff --git a/interface-definitions/include/segment-routing/algorithm.xml.i b/interface-definitions/include/segment-routing/algorithm.xml.i new file mode 100644 index 000000000..dae019b22 --- /dev/null +++ b/interface-definitions/include/segment-routing/algorithm.xml.i @@ -0,0 +1,20 @@ +<!-- include start from segment-routing/algorithm.xml.i --> +<node name="algorithm"> + <properties> + <help>IGP prefix algorithm style</help> + </properties> + <children> + <leafNode name="spf"> + <properties> + <help>Shortest Path First (SPF)</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="strict-spf"> + <properties> + <help>Strict Shortest Path First (SPF) - ignore any possible local policy overriding the SPF along the path</help> + <valueless/> + </properties> + </leafNode> + </children> +</node>
\ No newline at end of file diff --git a/interface-definitions/protocols_segment-routing.xml.in b/interface-definitions/protocols_segment-routing.xml.in index 32e61ebfc..cab1e6945 100644 --- a/interface-definitions/protocols_segment-routing.xml.in +++ b/interface-definitions/protocols_segment-routing.xml.in @@ -157,6 +157,206 @@ </tagNode> </children> </node> + <node name="traffic-engineering"> + <properties> + <help>SR Traffic Engineering (TE) configuration</help> + </properties> + <children> + <node name="database-import-protocol"> + <properties> + <help>Traffic Engineering Database (TED) IGP import protocol</help> + </properties> + <children> + <leafNode name="isis"> + <properties> + <help>IS-IS originated Traffic Engineering (TE) database</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="ospf"> + <properties> + <help>OSPF originated Traffic Engineering (TE) database</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + <tagNode name="segment-list"> + <properties> + <help>Segment List</help> + <valueHelp> + <format>txt</format> + <description>Segment List Name</description> + </valueHelp> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i> + </constraint> + </properties> + <children> + <tagNode name="index"> + <properties> + <help>Traffic engineering index value for segment list</help> + <valueHelp> + <format>u32</format> + <description>Segment list index value</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295"/> + </constraint> + </properties> + <children> + <node name="mpls"> + <properties> + <help>MPLS label for index</help> + </properties> + <children> + <leafNode name="label"> + <properties> + <help>MPLS label value for index</help> + <valueHelp> + <format>u32:16-1048575</format> + <description>MPLS label value for index</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 16-1048575"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="nai"> + <properties> + <help>Node or Adjacency identifier (NAI) for index</help> + </properties> + <children> + <node name="adjacency"> + <properties> + <help>Adjacency identifier for index</help> + </properties> + <children> + <node name="ipv4"> + <properties> + <help>IPv4 address</help> + </properties> + <children> + <leafNode name="source-identifier"> + <properties> + <help>Adjacency source address identifier for index</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 adjacency source address identifier</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="destination-identifier"> + <properties> + <help>Adjacency destination address identifier for index</help> + <valueHelp> + <format>ipv4</format> + <description>IPv4 adjacency destination address identifier</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + <node name="ipv6"> + <properties> + <help>IPv6 address</help> + </properties> + <children> + <leafNode name="source-identifier"> + <properties> + <help>Adjacency source address identifier for index</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 adjacency source address identifier</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> + <leafNode name="destination-identifier"> + <properties> + <help>Adjacency destination address identifier for index</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 adjacency destination address identifier</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + <node name="prefix"> + <properties> + <help>IGP prefix identifier for index</help> + </properties> + <children> + <node name="ipv4"> + <properties> + <help>IPv4 address</help> + </properties> + <children> + <tagNode name="prefix-identifier"> + <properties> + <help>IPv4 IGP prefix address identifier for index</help> + <valueHelp> + <format>ipv4net</format> + <description>IPv4 adjacency source address identifier</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> + </properties> + <children> + #include <include/segment-routing/algorithm.xml.i> + </children> + </tagNode> + </children> + </node> + <node name="ipv6"> + <properties> + <help>IPv6 address</help> + </properties> + <children> + <tagNode name="prefix-identifier"> + <properties> + <help>IPv6 IGP prefix address identifier for index</help> + <valueHelp> + <format>ipv6net</format> + <description>IPv6 adjacency source address identifier</description> + </valueHelp> + <constraint> + <validator name="ipv6-prefix"/> + </constraint> + </properties> + <children> + #include <include/segment-routing/algorithm.xml.i> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> + </children> + </tagNode> + </children> + </tagNode> + </children> + </node> </children> </node> </children> diff --git a/op-mode-definitions/show-segment-routing.xml.in b/op-mode-definitions/show-segment-routing.xml.in index fc17e2f39..0f7fa5d76 100644 --- a/op-mode-definitions/show-segment-routing.xml.in +++ b/op-mode-definitions/show-segment-routing.xml.in @@ -4,12 +4,12 @@ <children> <node name="segment-routing"> <properties> - <help>Show Segment Routing</help> + <help>Show Segment Routing Information</help> </properties> <children> <node name="srv6"> <properties> - <help>Segment Routing SRv6</help> + <help>Segment Routing over IPv6 information</help> </properties> <children> <node name="locator"> @@ -26,6 +26,19 @@ </node> </children> </node> + <node name="traffic-engineering"> + <properties> + <help>Traffic Engineering information</help> + </properties> + <children> + <leafNode name="database"> + <properties> + <help>Show Traffic Engineering database</help> + </properties> + <command>vtysh -c "show pathd ted database verbose"</command> + </leafNode> + </children> + </node> </children> </node> </children> diff --git a/smoketest/scripts/cli/test_protocols_segment-routing.py b/smoketest/scripts/cli/test_protocols_segment-routing.py index 7be76ee0c..09d32445a 100755 --- a/smoketest/scripts/cli/test_protocols_segment-routing.py +++ b/smoketest/scripts/cli/test_protocols_segment-routing.py @@ -220,5 +220,102 @@ class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase): self.assertEqual(sysctl_read(['net', 'ipv6', 'conf', first_if, 'seg6_enabled']), '0') + def test_srte_database(self): + for protocol in ['isis', 'ospf']: + self.cli_set(base_path + ['traffic-engineering', 'database-import-protocol', protocol]) + # IS-IS and OSPF are mutually exclusive + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # Add database-import-protocol for isis and check the config + for protocol in ['isis', 'ospf']: + self.cli_delete(base_path) + self.cli_set(base_path + ['traffic-engineering', 'database-import-protocol', protocol]) + self.cli_commit() + + frrconfig = self.getFRRconfig(f'segment-routing', stop_section='^exit') + self.assertIn('segment-routing', frrconfig) + self.assertIn(' traffic-eng', frrconfig) + self.assertIn(' mpls-te on', frrconfig) + self.assertIn(f' mpls-te import {protocol}', frrconfig) + + def test_srte_mpls_label(self): + # Add segment-list with an mpls value + mpls_label = '500' + segment_list = 'smoketest-mpls-only-segment-list' + index_value = '0' + + self.cli_set(base_path + ['traffic-engineering', 'segment-list', segment_list, + 'index', index_value, 'mpls', 'label', mpls_label]) + self.cli_commit() + + frrconfig = self.getFRRconfig(f'segment-routing', stop_section='^exit') + self.assertIn('segment-routing', frrconfig) + self.assertIn(' traffic-eng', frrconfig) + self.assertIn(' mpls-te on', frrconfig) + self.assertIn(f' segment-list {segment_list}', frrconfig) + self.assertIn(f' index {index_value} mpls label {mpls_label}', frrconfig) + + def test_srte_mpls_label_and_adjacency(self): + # Add segment-list with an mpls value and adjacency + mpls_label = '1000' + segment_list = 'smoketest-mpls-with-adjacency-segment-list' + index_value = '0' + + addresses = {'ipv4' : {'source_identifier' : '192.168.255.1', 'destination_identifier' : '192.168.255.2'}, + 'ipv6' : {'source_identifier' : '2003::1', 'destination_identifier' : '2003::2'}} + + test_path = base_path + ['traffic-engineering', 'segment-list', segment_list, 'index', index_value] + for address_family in ['ipv4', 'ipv6']: + source_identifier = addresses[address_family]['source_identifier'] + destination_identifier = addresses[address_family]['destination_identifier'] + # Make testcase re-entrant for next for loop + self.cli_delete(test_path) + + self.cli_set(test_path + ['mpls', 'label', mpls_label]) + self.cli_set(test_path + ['nai', 'adjacency', address_family, 'source-identifier', source_identifier]) + self.cli_set(test_path + ['nai', 'adjacency', address_family, 'destination-identifier', destination_identifier]) + self.cli_commit() + + frrconfig = self.getFRRconfig(f'segment-routing', stop_section='^exit') + self.assertIn(f'segment-routing', frrconfig) + self.assertIn(f' traffic-eng', frrconfig) + self.assertIn(f' mpls-te on', frrconfig) + self.assertIn(f' segment-list {segment_list}', frrconfig) + self.assertIn(f' index {index_value} mpls label {mpls_label}', frrconfig) + self.assertIn(f' index {index_value} nai adjacency {source_identifier} {destination_identifier}', frrconfig) + + def test_srte_mpls_label_and_prefix(self): + # Add segment-list with an mpls value and prefix + mpls_label = '1500' + segment_list = 'smoketest-mpls-with-prefix-segment-list' + index_value = '0' + + prefixes = {'ipv4' : {'prefix' : '192.168.255.0/24'}, + 'ipv6' : {'prefix' : '2003::/120'}} + + test_path = base_path + ['traffic-engineering', 'segment-list', segment_list, 'index', index_value] + for address_family in ['ipv4', 'ipv6']: + for algorithm_type in ['spf', 'strict-spf']: + prefix = prefixes[address_family]['prefix'] + # Make testcase re-entrant for next for loop + self.cli_delete(test_path) + + self.cli_set(test_path + ['mpls', 'label', mpls_label]) + self.cli_set(test_path + ['nai', 'prefix', address_family, 'prefix-identifier', prefix, 'algorithm', algorithm_type]) + self.cli_commit() + + frrconfig = self.getFRRconfig(f'segment-routing', stop_section='^exit') + self.assertIn(f'segment-routing', frrconfig) + self.assertIn(f' traffic-eng', frrconfig) + self.assertIn(f' mpls-te on', frrconfig) + self.assertIn(f' segment-list {segment_list}', frrconfig) + if algorithm_type == 'spf': + self.assertIn(f' index {index_value} mpls label {mpls_label}', frrconfig) + self.assertIn(f' index {index_value} nai prefix {prefix} algorithm 0', frrconfig) + elif algorithm_type == 'strict-spf': + self.assertIn(f' index {index_value} mpls label {mpls_label}', frrconfig) + self.assertIn(f' index {index_value} nai prefix {prefix} algorithm 1', frrconfig) + if __name__ == '__main__': unittest.main(verbosity=2, failfast=VyOSUnitTestSHIM.TestCase.debug_on()) diff --git a/src/conf_mode/protocols_segment-routing.py b/src/conf_mode/protocols_segment-routing.py index 8a4d12bd8..b9689557e 100755 --- a/src/conf_mode/protocols_segment-routing.py +++ b/src/conf_mode/protocols_segment-routing.py @@ -15,6 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from sys import exit +from sys import argv from vyos.config import Config from vyos.configdict import list_diff @@ -35,7 +36,7 @@ def get_config(config=None): else: conf = Config() - return get_frrender_dict(conf) + return get_frrender_dict(conf, argv) def verify(config_dict): if not has_frr_protocol_in_dict(config_dict, 'segment_routing'): @@ -45,13 +46,68 @@ def verify(config_dict): 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 + for _, interface_config in dict_search('interface', sr, {}).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!') + + # Check for database import having more than one protocol + if tmp := dict_search('traffic_engineering.database_import_protocol', sr): + if {'isis', 'ospf'} <= set(tmp.keys()): + raise ConfigError('SR-TE database import: IS-IS and OSPF are mutually exclusive!') + + for segment_list in dict_search('traffic_engineering.segment_list', sr, []): + sl_data = dict_search(f'traffic_engineering.segment_list.{segment_list}', sr) + indices = sl_data.get('index') if sl_data else None + + if indices is None: + raise ConfigError(f'SR-TE segment list "{segment_list}": '\ + 'at least one index is required!') + + for index, index_data in indices.items(): + error_msg = f'SR-TE segment list "{segment_list}", index "{index}"' + nai = index_data.get('nai') + mpls = index_data.get('mpls') + if not nai and not mpls: + raise ConfigError(f'{error_msg}: "mpls" or "nai" is required!') + + if nai: + if 'adjacency' in nai and 'prefix' in nai: + raise ConfigError(f'{error_msg}: "prefix" and "adjacency" are mutually exclusive!') + + for nai_type in ('adjacency', 'prefix'): + nai_data = nai.get(nai_type) + if not nai_data: + continue + + if 'ipv4' in nai_data and 'ipv6' in nai_data: + raise ConfigError(f'{error_msg}, nai {nai_type}: "ipv4" and "ipv6" are ' + 'mutually exclusive!') + + for af, af_config in nai_data.items(): + af_ctx = f'{error_msg}, nai {nai_type} {af}' + if nai_type == 'adjacency': + has_src = 'source_identifier' in af_config + has_dst = 'destination_identifier' in af_config + if has_src != has_dst: + missing = 'destination-identifier' if has_src else 'source-identifier' + raise ConfigError(f'{af_ctx}: "{missing}" is required!') + else: + if 'prefix_identifier' not in af_config: + raise ConfigError(f'{af_ctx}: "prefix-identifier" is required!') + + for pfx, pfx_data in af_config['prefix_identifier'].items(): + pfx_ctx = f'{af_ctx}, prefix "{pfx}"' + if 'algorithm' not in pfx_data: + raise ConfigError(f'{pfx_ctx}: "algorithm" is required!') + + if alg := pfx_data.get('algorithm'): + if {'spf', 'strict_spf'} <= set(alg.keys()): + raise ConfigError(f'{pfx_ctx}: "spf" and "strict-spf" ' + 'are mutually exclusive!') + return None def generate(config_dict): |
