From cafd8840dfeda5cac5f723716c0b234935284856 Mon Sep 17 00:00:00 2001
From: Christian Breunig <christian@breunig.cc>
Date: Sat, 10 Feb 2024 10:02:47 +0100
Subject: srv6: T5849: add segment support to "protocols static route6"

* set protocols static route6 <prefix> next-hop <address> segments 'x:x::x:x/y:y::y/z::z'
* set protocols static route6 <prefix> interface <interface> segments 'x:x::x:x/y:y::y/z::z'

(cherry picked from commit b84f7de453f3951945298d95a8a27345ba7d28c3)
---
 data/templates/frr/static_routes_macro.j2          |  6 +--
 .../include/static/static-route-reject.xml.i       |  1 -
 .../include/static/static-route-segments.xml.i     | 14 +++++
 .../include/static/static-route6.xml.i             |  5 +-
 smoketest/scripts/cli/test_protocols_static.py     | 62 ++++++++++++++++------
 src/validators/ipv6-srv6-segments                  | 13 +++++
 6 files changed, 79 insertions(+), 22 deletions(-)
 create mode 100644 interface-definitions/include/static/static-route-segments.xml.i
 create mode 100755 src/validators/ipv6-srv6-segments

diff --git a/data/templates/frr/static_routes_macro.j2 b/data/templates/frr/static_routes_macro.j2
index 8afd4a68a..cf8046968 100644
--- a/data/templates/frr/static_routes_macro.j2
+++ b/data/templates/frr/static_routes_macro.j2
@@ -13,17 +13,17 @@
 {% endif %}
 {% if prefix_config.interface is vyos_defined %}
 {%     for interface, interface_config in prefix_config.interface.items() if interface_config.disable is not defined %}
-{{ ip_ipv6 }} route {{ prefix }} {{ interface }} {{ interface_config.distance if interface_config.distance is vyos_defined }} {{ 'nexthop-vrf ' ~ interface_config.vrf if interface_config.vrf is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined }}
+{{ ip_ipv6 }} route {{ prefix }} {{ interface }} {{ interface_config.distance if interface_config.distance is vyos_defined }} {{ 'nexthop-vrf ' ~ interface_config.vrf if interface_config.vrf is vyos_defined }} {{ 'segments ' ~ interface_config.segments if interface_config.segments is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined }}
 {%     endfor %}
 {% endif %}
 {% if prefix_config.next_hop is vyos_defined and prefix_config.next_hop is not none %}
 {%     for next_hop, next_hop_config in prefix_config.next_hop.items() if next_hop_config.disable is not defined %}
-{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ next_hop_config.interface if next_hop_config.interface is vyos_defined }} {{ next_hop_config.distance if next_hop_config.distance is vyos_defined }} {{ 'nexthop-vrf ' ~ next_hop_config.vrf if next_hop_config.vrf is vyos_defined }} {{ 'bfd profile ' ~ next_hop_config.bfd.profile if next_hop_config.bfd.profile is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined }} 
+{{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ next_hop_config.interface if next_hop_config.interface is vyos_defined }} {{ next_hop_config.distance if next_hop_config.distance is vyos_defined }} {{ 'nexthop-vrf ' ~ next_hop_config.vrf if next_hop_config.vrf is vyos_defined }} {{ 'bfd profile ' ~ next_hop_config.bfd.profile if next_hop_config.bfd.profile is vyos_defined }} {{ 'segments ' ~ next_hop_config.segments if next_hop_config.segments is vyos_defined }} {{ 'table ' ~ table if table is vyos_defined }}
 {%         if next_hop_config.bfd.multi_hop.source is vyos_defined %}
 {%             for source, source_config in next_hop_config.bfd.multi_hop.source.items() %}
 {{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} bfd multi-hop source {{ source }} profile {{ source_config.profile }}
 {%             endfor %}
-{%         endif %} 
+{%         endif %}
 {%     endfor %}
 {% endif %}
 {% endmacro %}
diff --git a/interface-definitions/include/static/static-route-reject.xml.i b/interface-definitions/include/static/static-route-reject.xml.i
index 81d4f9afd..ef713ac85 100644
--- a/interface-definitions/include/static/static-route-reject.xml.i
+++ b/interface-definitions/include/static/static-route-reject.xml.i
@@ -9,4 +9,3 @@
   </children>
 </node>
 <!-- include end -->
-
diff --git a/interface-definitions/include/static/static-route-segments.xml.i b/interface-definitions/include/static/static-route-segments.xml.i
new file mode 100644
index 000000000..2068b1ab4
--- /dev/null
+++ b/interface-definitions/include/static/static-route-segments.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from static/static-route-segments.xml.i -->
+<leafNode name="segments">
+    <properties>
+      <help>SRv6 segments</help>
+      <valueHelp>
+        <format>txt</format>
+        <description>Segs (SIDs)</description>
+      </valueHelp>
+      <constraint>
+        <validator name="ipv6-srv6-segments"/>
+      </constraint>
+    </properties>
+  </leafNode>
+  <!-- include end -->
diff --git a/interface-definitions/include/static/static-route6.xml.i b/interface-definitions/include/static/static-route6.xml.i
index a83cc230b..4468c8025 100644
--- a/interface-definitions/include/static/static-route6.xml.i
+++ b/interface-definitions/include/static/static-route6.xml.i
@@ -31,6 +31,7 @@
       <children>
         #include <include/generic-disable-node.xml.i>
         #include <include/static/static-route-distance.xml.i>
+        #include <include/static/static-route-segments.xml.i>
         #include <include/static/static-route-vrf.xml.i>
       </children>
     </tagNode>
@@ -47,13 +48,13 @@
       </properties>
       <children>
         #include <include/generic-disable-node.xml.i>
+        #include <include/static/static-route-bfd.xml.i>
         #include <include/static/static-route-distance.xml.i>
         #include <include/static/static-route-interface.xml.i>
+        #include <include/static/static-route-segments.xml.i>
         #include <include/static/static-route-vrf.xml.i>
-        #include <include/static/static-route-bfd.xml.i>
       </children>
     </tagNode>
   </children>
 </tagNode>
 <!-- include end -->
-
diff --git a/smoketest/scripts/cli/test_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py
index abf1080ab..c5cf2aab6 100755
--- a/smoketest/scripts/cli/test_protocols_static.py
+++ b/smoketest/scripts/cli/test_protocols_static.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2021-2023 VyOS maintainers and contributors
+# Copyright (C) 2021-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
@@ -55,13 +55,13 @@ routes = {
         'blackhole' : { 'distance' : '90' },
     },
     '100.64.0.0/16' : {
-        'blackhole' : { },
+        'blackhole' : {},
     },
     '100.65.0.0/16' : {
         'reject'    : { 'distance' : '10', 'tag' : '200' },
     },
     '100.66.0.0/16' : {
-        'blackhole' : { },
+        'blackhole' : {},
         'reject'    : { 'distance' : '10', 'tag' : '200' },
     },
     '2001:db8:100::/40' : {
@@ -88,8 +88,28 @@ routes = {
     '2001:db8:300::/40' : {
         'reject'    : { 'distance' : '250', 'tag' : '500' },
     },
+    '2001:db8:400::/40' : {
+        'next_hop' : {
+            '2001:db8::400' : { 'segments' : '2001:db8:aaaa::400/2002::400/2003::400/2004::400' },
+        },
+    },
+    '2001:db8:500::/40' : {
+        'next_hop' : {
+            '2001:db8::500' : { 'segments' : '2001:db8:aaaa::500/2002::500/2003::500/2004::500' },
+        },
+    },
+    '2001:db8:600::/40' : {
+        'interface' : {
+            'eth0'  : { 'segments' : '2001:db8:aaaa::600/2002::600' },
+        },
+    },
+    '2001:db8:700::/40' : {
+        'interface' : {
+            'eth1'  : { 'segments' : '2001:db8:aaaa::700' },
+        },
+    },
     '2001:db8::/32' : {
-        'blackhole' : { 'distance' : '200', 'tag' : '600' },
+        'blackhole' : { 'distance' : '200', 'tag' : '600' }
     },
 }
 
@@ -108,18 +128,14 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
         super(TestProtocolsStatic, cls).tearDownClass()
 
     def tearDown(self):
-        for route, route_config in routes.items():
-            route_type = 'route'
-            if is_ipv6(route):
-                route_type = 'route6'
-            self.cli_delete(base_path + [route_type, route])
-
-        for table in tables:
-            self.cli_delete(base_path + ['table', table])
-
-        tmp = self.getFRRconfig('', end='')
+        self.cli_delete(base_path)
         self.cli_commit()
 
+        v4route = self.getFRRconfig('ip route', end='')
+        self.assertFalse(v4route)
+        v6route = self.getFRRconfig('ipv6 route', end='')
+        self.assertFalse(v6route)
+
     def test_01_static(self):
         bfd_profile = 'vyos-test'
         for route, route_config in routes.items():
@@ -142,7 +158,8 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
                         self.cli_set(base + ['next-hop', next_hop, 'bfd', 'profile', bfd_profile ])
                     if 'bfd_source' in next_hop_config:
                         self.cli_set(base + ['next-hop', next_hop, 'bfd', 'multi-hop', 'source', next_hop_config['bfd_source'], 'profile', bfd_profile])
-
+                    if 'segments' in next_hop_config:
+                        self.cli_set(base + ['next-hop', next_hop, 'segments', next_hop_config['segments']])
 
             if 'interface' in route_config:
                 for interface, interface_config in route_config['interface'].items():
@@ -153,6 +170,8 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
                         self.cli_set(base + ['interface', interface, 'distance', interface_config['distance']])
                     if 'vrf' in interface_config:
                         self.cli_set(base + ['interface', interface, 'vrf', interface_config['vrf']])
+                    if 'segments' in interface_config:
+                        self.cli_set(base + ['interface', interface, 'segments', interface_config['segments']])
 
             if 'blackhole' in route_config:
                 self.cli_set(base + ['blackhole'])
@@ -200,6 +219,8 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
                         tmp += ' bfd profile ' + bfd_profile
                     if 'bfd_source' in next_hop_config:
                         tmp += ' bfd multi-hop source ' + next_hop_config['bfd_source'] + ' profile ' + bfd_profile
+                    if 'segments' in next_hop_config:
+                        tmp += ' segments ' + next_hop_config['segments']
 
                     if 'disable' in next_hop_config:
                         self.assertNotIn(tmp, frrconfig)
@@ -215,6 +236,8 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
                         tmp += ' ' + interface_config['distance']
                     if 'vrf' in interface_config:
                         tmp += ' nexthop-vrf ' + interface_config['vrf']
+                    if 'segments' in interface_config:
+                        tmp += ' segments ' + interface_config['segments']
 
                     if 'disable' in interface_config:
                         self.assertNotIn(tmp, frrconfig)
@@ -369,7 +392,8 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
                             self.cli_set(route_base_path + ['next-hop', next_hop, 'interface', next_hop_config['interface']])
                         if 'vrf' in next_hop_config:
                             self.cli_set(route_base_path + ['next-hop', next_hop, 'vrf', next_hop_config['vrf']])
-
+                        if 'segments' in next_hop_config:
+                            self.cli_set(route_base_path + ['next-hop', next_hop, 'segments', next_hop_config['segments']])
 
                 if 'interface' in route_config:
                     for interface, interface_config in route_config['interface'].items():
@@ -380,6 +404,8 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
                             self.cli_set(route_base_path + ['interface', interface, 'distance', interface_config['distance']])
                         if 'vrf' in interface_config:
                             self.cli_set(route_base_path + ['interface', interface, 'vrf', interface_config['vrf']])
+                        if 'segments' in interface_config:
+                            self.cli_set(route_base_path + ['interface', interface, 'segments', interface_config['segments']])
 
                 if 'blackhole' in route_config:
                     self.cli_set(route_base_path + ['blackhole'])
@@ -417,6 +443,8 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
                             tmp += ' ' + next_hop_config['distance']
                         if 'vrf' in next_hop_config:
                             tmp += ' nexthop-vrf ' + next_hop_config['vrf']
+                        if 'segments' in next_hop_config:
+                            tmp += ' segments ' + next_hop_config['segments']
 
                         if 'disable' in next_hop_config:
                             self.assertNotIn(tmp, frrconfig)
@@ -432,6 +460,8 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase):
                             tmp += ' ' + interface_config['distance']
                         if 'vrf' in interface_config:
                             tmp += ' nexthop-vrf ' + interface_config['vrf']
+                        if 'segments' in interface_config:
+                            tmp += ' segments ' + interface_config['segments']
 
                         if 'disable' in interface_config:
                             self.assertNotIn(tmp, frrconfig)
diff --git a/src/validators/ipv6-srv6-segments b/src/validators/ipv6-srv6-segments
new file mode 100755
index 000000000..e72a4f90f
--- /dev/null
+++ b/src/validators/ipv6-srv6-segments
@@ -0,0 +1,13 @@
+#!/bin/sh
+segments="$1"
+export IFS="/"
+
+for ipv6addr in $segments; do
+    ipaddrcheck --is-ipv6-single $ipv6addr
+    if [ $? -gt 0 ]; then
+        echo "Error: $1 is not a valid IPv6 address"
+        exit 1
+    fi
+done
+exit 0
+
-- 
cgit v1.2.3