From c17f259d09abd2bf632d09400fe8deb4c2781d32 Mon Sep 17 00:00:00 2001
From: Mark Royds <github@mnr.cc>
Date: Sun, 23 May 2021 11:20:56 +0100
Subject: router-advert: T3561: add support for specific routes

Co-authored-by: Mark Royds <mark.royds@vitaminit.co.uk>
---
 data/templates/router-advert/radvd.conf.tmpl       | 13 +++++
 interface-definitions/service_router-advert.xml.in | 66 ++++++++++++++++++++++
 src/conf_mode/service_router-advert.py             | 12 +++-
 src/migration-scripts/interfaces/5-to-6            | 10 ++++
 4 files changed, 99 insertions(+), 2 deletions(-)

diff --git a/data/templates/router-advert/radvd.conf.tmpl b/data/templates/router-advert/radvd.conf.tmpl
index 2fde78fec..9cc237512 100644
--- a/data/templates/router-advert/radvd.conf.tmpl
+++ b/data/templates/router-advert/radvd.conf.tmpl
@@ -30,6 +30,19 @@ interface {{ iface }} {
     AdvOtherConfigFlag {{ 'on' if interface[iface].other_config_flag is defined else 'off' }};
     AdvRetransTimer {{ interface[iface].retrans_timer }};
     AdvCurHopLimit {{ interface[iface].hop_limit }};
+{%     if interface[iface].route is defined %}
+{%       for route in interface[iface].route %}
+    route {{ route }} {
+{%         if interface[iface].route[route].valid_lifetime is defined %}
+        AdvRouteLifetime {{ interface[iface].route[route].valid_lifetime }};
+{%         endif %}
+{%         if interface[iface].route[route].route_preference is defined %}
+        AdvRoutePreference {{ interface[iface].route[route].route_preference }};
+{%         endif %}
+        RemoveRoute {{ 'off' if interface[iface].route[route].no_remove_route is defined else 'on' }};
+    };
+{%       endfor %}
+{%     endif %}
 {%     for prefix in interface[iface].prefix %}
     prefix {{ prefix }} {
         AdvAutonomous {{ 'off' if interface[iface].prefix[prefix].no_autonomous_flag is defined else 'on' }};
diff --git a/interface-definitions/service_router-advert.xml.in b/interface-definitions/service_router-advert.xml.in
index 8c525aed2..750ae314c 100644
--- a/interface-definitions/service_router-advert.xml.in
+++ b/interface-definitions/service_router-advert.xml.in
@@ -154,6 +154,72 @@
                   <valueless/>
                 </properties>
               </leafNode>
+              <tagNode name="route">
+                <properties>
+                  <help>IPv6 route to be advertised in Router Advertisements (RAs)</help>
+                  <valueHelp>
+                    <format>ipv6net</format>
+                    <description>IPv6 route to be advertized</description>
+                  </valueHelp>
+                  <constraint>
+                    <validator name="ipv6-prefix"/>
+                  </constraint>
+                </properties>
+                <children>
+                  <leafNode name="valid-lifetime">
+                    <properties>
+                      <help>Time in seconds that the route will remain valid (default: 1800 seconds)</help>
+                      <completionHelp>
+                        <list>infinity</list>
+                      </completionHelp>
+                      <valueHelp>
+                        <format>1-4294967295</format>
+                        <description>Time in seconds that the route will remain valid</description>
+                      </valueHelp>
+                      <valueHelp>
+                        <format>infinity</format>
+                        <description>Route will remain preferred forever</description>
+                      </valueHelp>
+                      <constraint>
+                        <validator name="numeric" argument="--range 0-4294967295"/>
+                        <regex>^(infinity)$</regex>
+                      </constraint>
+                    </properties>
+                    <defaultValue>1800</defaultValue>
+                  </leafNode>
+                  <leafNode name="route-preference">
+                    <properties>
+                      <help>Preference associated with the route,</help>
+                      <completionHelp>
+                        <list>low medium high</list>
+                      </completionHelp>
+                      <valueHelp>
+                        <format>low</format>
+                        <description>Route has low preference</description>
+                      </valueHelp>
+                      <valueHelp>
+                        <format>medium</format>
+                        <description>Route has medium preference (default)</description>
+                      </valueHelp>
+                      <valueHelp>
+                        <format>high</format>
+                        <description>Route has high preference</description>
+                      </valueHelp>
+                      <constraint>
+                        <regex>^(low|medium|high)$</regex>
+                      </constraint>
+                      <constraintErrorMessage>Route preference must be low, medium or high</constraintErrorMessage>
+                    </properties>
+                    <defaultValue>medium</defaultValue>
+                  </leafNode>
+                  <leafNode name="no-remove-route">
+                    <properties>
+                      <help>Do not announce this route with a zero second lifetime upon shutdown</help>
+                      <valueless/>
+                    </properties>
+                  </leafNode>
+                </children>
+              </tagNode>
               <tagNode name="prefix">
                 <properties>
                   <help>IPv6 prefix to be advertised in Router Advertisements (RAs)</help>
diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py
index 65eb11ce3..1f6d90fb4 100755
--- a/src/conf_mode/service_router-advert.py
+++ b/src/conf_mode/service_router-advert.py
@@ -40,11 +40,14 @@ def get_config(config=None):
     # We have gathered the dict representation of the CLI, but there are default
     # options which we need to update into the dictionary retrived.
     default_interface_values = defaults(base + ['interface'])
-    # we deal with prefix defaults later on
+    # we deal with prefix, route defaults later on
     if 'prefix' in default_interface_values:
         del default_interface_values['prefix']
-
+    if 'route' in default_interface_values:
+        del default_interface_values['route']
+        
     default_prefix_values = defaults(base + ['interface', 'prefix'])
+    default_route_values = defaults(base + ['interface', 'route'])
 
     if 'interface' in rtradv:
         for interface in rtradv['interface']:
@@ -55,6 +58,11 @@ def get_config(config=None):
                 for prefix in rtradv['interface'][interface]['prefix']:
                     rtradv['interface'][interface]['prefix'][prefix] = dict_merge(
                         default_prefix_values, rtradv['interface'][interface]['prefix'][prefix])
+                        
+            if 'route' in rtradv['interface'][interface]:
+                for route in rtradv['interface'][interface]['route']:
+                    rtradv['interface'][interface]['route'][route] = dict_merge(
+                        default_route_values, rtradv['interface'][interface]['route'][route])
 
             if 'name_server' in rtradv['interface'][interface]:
                 # always use a list when dealing with nameservers - eases the template generation
diff --git a/src/migration-scripts/interfaces/5-to-6 b/src/migration-scripts/interfaces/5-to-6
index 1291751d8..8ae34c208 100755
--- a/src/migration-scripts/interfaces/5-to-6
+++ b/src/migration-scripts/interfaces/5-to-6
@@ -55,6 +55,16 @@ def copy_rtradv(c, old_base, interface):
                 min_max = interval.split('-')[0]
                 c.set(new_base + ['interval', min_max], value=tmp)
 
+		# cleanup boolean nodes in individual route
+		route_base = new_base + ['route']
+		if c.exists(route_base):
+		    for route in config.list_nodes(route_base):
+			    if c.exists(route_base + [route, 'remove-route']):
+				    tmp = c.return_value(route_base + [route, 'remove-route'])
+					c.delete(route_base + [route, 'remove-route'])
+					if tmp == 'false':
+					   c.set(route_base + [route, 'no-remove-route'])
+					   
         # cleanup boolean nodes in individual prefix
         prefix_base = new_base + ['prefix']
         if c.exists(prefix_base):
-- 
cgit v1.2.3