From 5904a4163d004561b8cb51ad76212a841ce85832 Mon Sep 17 00:00:00 2001
From: Christian Breunig <christian@breunig.cc>
Date: Sun, 10 Dec 2023 21:13:54 +0100
Subject: srv6: T591: initial implementation to support locator definition

VyOS CLI

set protocols segment-routing srv6 locator bar prefix '2001:b::/64'
set protocols segment-routing srv6 locator foo behavior-usid
set protocols segment-routing srv6 locator foo prefix '2001:a::/64'

Will generate in FRR

segment-routing
 srv6
  locators
   locator bar
    prefix 2001:b::/64 block-len 40 node-len 24 func-bits 16
   exit
   !
   locator foo
    prefix 2001:a::/64 block-len 40 node-len 24 func-bits 16
    behavior usid
   exit
   !
  exit
  !
 exit
 !
exit

(cherry picked from commit ca301cdd4746187f96ff84e411fda6a84e33f237)
---
 data/configd-include.json                          |  1 +
 data/templates/frr/zebra.segment_routing.frr.j2    | 23 ++++++
 .../protocols-segment-routing.xml.in               | 89 ++++++++++++++++++++++
 op-mode-definitions/show-segment-routing.xml.in    | 27 +++++++
 .../scripts/cli/test_protocols_segment_routing.py  | 70 +++++++++++++++++
 src/conf_mode/protocols_segment_routing.py         | 74 ++++++++++++++++++
 6 files changed, 284 insertions(+)
 create mode 100644 data/templates/frr/zebra.segment_routing.frr.j2
 create mode 100644 interface-definitions/protocols-segment-routing.xml.in
 create mode 100644 op-mode-definitions/show-segment-routing.xml.in
 create mode 100755 smoketest/scripts/cli/test_protocols_segment_routing.py
 create mode 100755 src/conf_mode/protocols_segment_routing.py

diff --git a/data/configd-include.json b/data/configd-include.json
index a762a6d4c..92d3863ce 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -53,6 +53,7 @@
 "protocols_rip.py",
 "protocols_ripng.py",
 "protocols_rpki.py",
+"protocols_segment_routing.py",
 "protocols_static.py",
 "protocols_static_multicast.py",
 "qos.py",
diff --git a/data/templates/frr/zebra.segment_routing.frr.j2 b/data/templates/frr/zebra.segment_routing.frr.j2
new file mode 100644
index 000000000..7b12fcdd0
--- /dev/null
+++ b/data/templates/frr/zebra.segment_routing.frr.j2
@@ -0,0 +1,23 @@
+!
+{% if srv6.locator is vyos_defined %}
+segment-routing
+ srv6
+  locators
+{%     for locator, locator_config in srv6.locator.items() %}
+   locator {{ locator }}
+{%         if locator_config.prefix is vyos_defined %}
+    prefix {{ locator_config.prefix }} block-len {{ locator_config.block_len }} node-len {{ locator_config.node_len }} func-bits {{ locator_config.func_bits }}
+{%         endif %}
+{%         if locator_config.behavior_usid is vyos_defined %}
+    behavior usid
+{%         endif %}
+    exit
+    !
+{%     endfor %}
+  exit
+  !
+exit
+!
+exit
+!
+{% endif %}
diff --git a/interface-definitions/protocols-segment-routing.xml.in b/interface-definitions/protocols-segment-routing.xml.in
new file mode 100644
index 000000000..d461e9c5d
--- /dev/null
+++ b/interface-definitions/protocols-segment-routing.xml.in
@@ -0,0 +1,89 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+  <node name="protocols">
+    <children>
+       <node name="segment-routing" owner="${vyos_conf_scripts_dir}/protocols_segment_routing.py">
+        <properties>
+          <help>Segment Routing</help>
+          <priority>900</priority>
+        </properties>
+        <children>
+          <node name="srv6">
+            <properties>
+              <help>Segment-Routing SRv6 configuration</help>
+            </properties>
+            <children>
+              <tagNode name="locator">
+                <properties>
+                  <help>Segment Routing SRv6 locator</help>
+                  <constraint>
+                    #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
+                  </constraint>
+                </properties>
+                <children>
+                  <leafNode name="behavior-usid">
+                    <properties>
+                      <help>Set SRv6 behavior uSID</help>
+                      <valueless/>
+                    </properties>
+                  </leafNode>
+                  <leafNode name="prefix">
+                    <properties>
+                      <help>SRv6 locator prefix</help>
+                      <valueHelp>
+                        <format>ipv6net</format>
+                        <description>SRv6 locator prefix</description>
+                      </valueHelp>
+                      <constraint>
+                        <validator name="ipv6-prefix"/>
+                      </constraint>
+                    </properties>
+                  </leafNode>
+                  <leafNode name="block-len">
+                    <properties>
+                      <help>Configure SRv6 locator block length in bits</help>
+                      <valueHelp>
+                        <format>u32:16-64</format>
+                        <description>Specify SRv6 locator block length in bits</description>
+                      </valueHelp>
+                      <constraint>
+                        <validator name="numeric" argument="--range 16-64"/>
+                      </constraint>
+                    </properties>
+                    <defaultValue>40</defaultValue>
+                  </leafNode>
+                  <leafNode name="func-bits">
+                    <properties>
+                      <help>Configure SRv6 locator function length in bits</help>
+                      <valueHelp>
+                        <format>u32:0-64</format>
+                        <description>Specify SRv6 locator function length in bits</description>
+                      </valueHelp>
+                      <constraint>
+                        <validator name="numeric" argument="--range 0-64"/>
+                      </constraint>
+                    </properties>
+                    <defaultValue>16</defaultValue>
+                  </leafNode>
+                  <leafNode name="node-len">
+                    <properties>
+                      <help>Configure SRv6 locator node length in bits</help>
+                      <valueHelp>
+                        <format>u32:16-64</format>
+                        <description>Configure SRv6 locator node length in bits</description>
+                      </valueHelp>
+                      <constraint>
+                        <validator name="numeric" argument="--range 16-64"/>
+                      </constraint>
+                    </properties>
+                    <defaultValue>24</defaultValue>
+                  </leafNode>
+                </children>
+              </tagNode>
+            </children>
+          </node>
+        </children>
+      </node>
+    </children>
+  </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/show-segment-routing.xml.in b/op-mode-definitions/show-segment-routing.xml.in
new file mode 100644
index 000000000..ebdb51a61
--- /dev/null
+++ b/op-mode-definitions/show-segment-routing.xml.in
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+  <node name="show">
+    <children>
+      <node name="segment-routing">
+        <properties>
+          <help>Show Segment Routing</help>
+        </properties>
+        <children>
+          <node name="srv6">
+            <properties>
+              <help>Segment Routing SRv6</help>
+            </properties>
+            <children>
+              <node name="locator">
+                <properties>
+                  <help>Locator Information</help>
+                </properties>
+                <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+              </node>
+            </children>
+          </node>
+        </children>
+      </node>
+    </children>
+  </node>
+</interfaceDefinition>
diff --git a/smoketest/scripts/cli/test_protocols_segment_routing.py b/smoketest/scripts/cli/test_protocols_segment_routing.py
new file mode 100755
index 000000000..81d42b925
--- /dev/null
+++ b/smoketest/scripts/cli/test_protocols_segment_routing.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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 os
+import unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.configsession import ConfigSessionError
+from vyos.utils.process import cmd
+from vyos.utils.process import process_named_running
+
+base_path = ['protocols', 'segment-routing']
+PROCESS_NAME = 'zebra'
+
+class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        # call base-classes classmethod
+        super(TestProtocolsSegmentRouting, cls).setUpClass()
+        # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same
+        cls.daemon_pid = process_named_running(PROCESS_NAME)
+        # ensure we can also run this test on a live system - so lets clean
+        # out the current configuration :)
+        cls.cli_delete(cls, base_path)
+
+    def tearDown(self):
+        self.cli_delete(base_path)
+        self.cli_commit()
+
+        # check process health and continuity
+        self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME))
+
+    def test_srv6(self):
+        locators = {
+            'foo' : { 'prefix' : '2001:a::/64' },
+            'foo' : { 'prefix' : '2001:b::/64', 'usid' : {} },
+        }
+
+        for locator, locator_config in locators.items():
+            self.cli_set(base_path + ['srv6', 'locator', locator, 'prefix', locator_config['prefix']])
+            if 'usid' in locator_config:
+                self.cli_set(base_path + ['srv6', 'locator', locator, 'behavior-usid'])
+
+        self.cli_commit()
+
+        frrconfig = self.getFRRconfig(f'segment-routing', daemon='zebra')
+        self.assertIn(f'segment-routing', frrconfig)
+        self.assertIn(f' srv6', frrconfig)
+        self.assertIn(f'  locators', frrconfig)
+        for locator, locator_config in locators.items():
+            self.assertIn(f'   locator {locator}', frrconfig)
+            self.assertIn(f'    prefix {locator_config["prefix"]} block-len 40 node-len 24 func-bits 16', frrconfig)
+
+
+if __name__ == '__main__':
+    unittest.main(verbosity=2)
diff --git a/src/conf_mode/protocols_segment_routing.py b/src/conf_mode/protocols_segment_routing.py
new file mode 100755
index 000000000..eb1653212
--- /dev/null
+++ b/src/conf_mode/protocols_segment_routing.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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 os
+
+from sys import exit
+
+from vyos.config import Config
+from vyos.template import render_to_string
+from vyos import ConfigError
+from vyos import frr
+from vyos import airbag
+airbag.enable()
+
+def get_config(config=None):
+    if config:
+        conf = config
+    else:
+        conf = Config()
+
+    base = ['protocols', 'segment-routing']
+    sr = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=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)
+
+    return sr
+
+def verify(static):
+    return None
+
+def generate(static):
+    if not static:
+        return None
+
+    static['new_frr_config'] = render_to_string('frr/zebra.segment_routing.frr.j2', static)
+    return None
+
+def apply(static):
+    zebra_daemon = 'zebra'
+
+    # 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'])
+    frr_cfg.commit_configuration(zebra_daemon)
+
+    return None
+
+if __name__ == '__main__':
+    try:
+        c = get_config()
+        verify(c)
+        generate(c)
+        apply(c)
+    except ConfigError as e:
+        print(e)
+        exit(1)
-- 
cgit v1.2.3