From 426599e510f803ed4c517fe14614f19832554587 Mon Sep 17 00:00:00 2001
From: Pim van Pelt <pim@ipng.nl>
Date: Sun, 7 Apr 2024 15:19:57 +0200
Subject: [PATCH] Also allow loopback interfaces to be unnumbered

---
 vppcfg/config/loopback.py                    | 52 ++++++++++++++++++++
 vppcfg/schema.yaml                           |  1 +
 vppcfg/unittest/yaml/correct-unnumbered.yaml | 12 +++++
 vppcfg/unittest/yaml/error-unnumbered1.yaml  | 14 ++++--
 vppcfg/unittest/yaml/error-unnumbered2.yaml  | 12 ++++-
 vppcfg/unittest/yaml/error-unnumbered3.yaml  |  7 ++-
 6 files changed, 92 insertions(+), 6 deletions(-)

diff --git a/vppcfg/config/loopback.py b/vppcfg/config/loopback.py
index 4bf6294..4e28b45 100644
--- a/vppcfg/config/loopback.py
+++ b/vppcfg/config/loopback.py
@@ -16,6 +16,7 @@ import logging
 from . import lcp
 from . import address
 from . import mac
+from . import interface
 
 
 def get_loopbacks(yaml):
@@ -53,6 +54,32 @@ def is_loopback(yaml, ifname):
     return iface is not None
 
 
+def get_unnumbered_loopbacks(yaml):
+    """Returns a list of all loopbacks that are unnumbered"""
+    ret = []
+    if not "loopbacks" in yaml:
+        return ret
+    for ifname, iface in yaml["loopbacks"].items():
+        if "unnumbered" in iface:
+            ret.append(ifname)
+
+    return ret
+
+
+def is_unnumbered(yaml, ifname):
+    """Returns True if the loopback exists and is unnumbered"""
+    return ifname in get_unnumbered_loopbacks(yaml)
+
+
+def has_address(yaml, ifname):
+    """Returns True if this loopback has one or more addresses"""
+
+    ifname, iface = get_by_name(yaml, ifname)
+    if not iface:
+        return False
+    return "addresses" in iface
+
+
 def validate_loopbacks(yaml):
     """Validate the semantics of all YAML 'loopbacks' entries"""
     result = True
@@ -76,6 +103,31 @@ def validate_loopbacks(yaml):
                 f"loopback {ifname} does not have a unique LCP name {iface['lcp']}"
             )
             result = False
+        if "unnumbered" in iface:
+            target = iface["unnumbered"]
+            _, target_iface = get_by_name(yaml, target)
+            if not target_iface:
+                _, target_iface = interface.get_by_name(yaml, target)
+            if not target_iface:
+                msgs.append(
+                    f"loopback {ifname} unnumbered target {target} does not exist"
+                )
+                result = False
+            if is_unnumbered(yaml, target):
+                msgs.append(
+                    f"loopback {ifname} unnumbered target {target} cannot also be unnumbered"
+                )
+                result = False
+            if ifname == target:
+                msgs.append(
+                    f"loopback {ifname} unnumbered target cannot point to itself"
+                )
+                result = False
+            if has_address(yaml, ifname):
+                msgs.append(
+                    f"loopback {ifname} cannot also have addresses when it is unnumbered"
+                )
+                result = False
         if "addresses" in iface:
             for addr in iface["addresses"]:
                 if not address.is_allowed(yaml, ifname, iface["addresses"], addr):
diff --git a/vppcfg/schema.yaml b/vppcfg/schema.yaml
index 8787d6d..7e60b2f 100644
--- a/vppcfg/schema.yaml
+++ b/vppcfg/schema.yaml
@@ -35,6 +35,7 @@ loopback:
   lcp: str(max=15,matches='[a-z]+[a-z0-9-]*',required=False)
   mtu: int(min=128,max=9216,required=False)
   addresses: list(ip_interface(),min=1,max=6,required=False)
+  unnumbered: str(required=False)
   mpls: bool(required=False)
 ---
 bondethernet:
diff --git a/vppcfg/unittest/yaml/correct-unnumbered.yaml b/vppcfg/unittest/yaml/correct-unnumbered.yaml
index 1433232..693ba45 100644
--- a/vppcfg/unittest/yaml/correct-unnumbered.yaml
+++ b/vppcfg/unittest/yaml/correct-unnumbered.yaml
@@ -7,6 +7,18 @@ loopbacks:
   loop0:
     mtu: 9216
     addresses: [ 192.0.2.1/32, 2001:db8:1::1/128 ]
+  loop1:
+    mtu: 1500
+    addresses: [ 192.0.2.17/32, 2001:db8:10::1/128 ]
+  loop2:
+    mtu: 1500
+    unnumbered: loop1
+  loop3:
+    mtu: 1500
+    unnumbered: GigabitEthernet1/0/0
+  loop4:
+    mtu: 1500
+    unnumbered: GigabitEthernet3/0/0.100
 
 interfaces:
   GigabitEthernet1/0/0:
diff --git a/vppcfg/unittest/yaml/error-unnumbered1.yaml b/vppcfg/unittest/yaml/error-unnumbered1.yaml
index fc9966e..e529b7d 100644
--- a/vppcfg/unittest/yaml/error-unnumbered1.yaml
+++ b/vppcfg/unittest/yaml/error-unnumbered1.yaml
@@ -2,21 +2,27 @@ test:
   description: "Nonexistent unnumbered target"
   errors:
     expected:
-     - "^sub-interface .* unnumbered target .* does not exist"
-     - "^interface .* unnumbered target .* does not exist"
-    count: 4
+     - "(sub-)?interface .* unnumbered target .* does not exist"
+     - "loopback .* unnumbered target .* does not exist"
+    count: 6
 ---
 loopbacks:
   loop0:
     mtu: 9216
     addresses: [ 192.0.2.1/32, 2001:db8:1::1/128 ]
+  loop1:
+    mtu: 9216
+    unnumbered: loop2
+  loop3:
+    mtu: 9216
+    unnumbered: GigabitEthernet0/0/0
 
 interfaces:
   GigabitEthernet1/0/0:
     sub-interfaces:
       101:
         description: "Error: non existent loopback device"
-        unnumbered: loop1
+        unnumbered: loop2
 
   GigabitEthernet2/0/0:
     addresses: [ 192.0.2.5/30, 2001:db8:2::1/64 ]
diff --git a/vppcfg/unittest/yaml/error-unnumbered2.yaml b/vppcfg/unittest/yaml/error-unnumbered2.yaml
index 3009921..d17e20c 100644
--- a/vppcfg/unittest/yaml/error-unnumbered2.yaml
+++ b/vppcfg/unittest/yaml/error-unnumbered2.yaml
@@ -4,8 +4,18 @@ test:
     expected:
       - "(sub-)?interface .* unnumbered target cannot point to itself"
       - "(sub-)?interface .* unnumbered target .* cannot also be unnumbered"
-    count: 8
+      - "loopback .* unnumbered target cannot point to itself"
+      - "loopback .* unnumbered target .* cannot also be unnumbered"
+    count: 12
 ---
+loopbacks:
+  loop0:
+    unnumbered: loop1
+  loop1:
+    unnumbered: loop0
+  loop2:
+    unnumbered: loop2
+
 interfaces:
   GigabitEthernet1/0/0:
     description: "Cannot point to the same interface"
diff --git a/vppcfg/unittest/yaml/error-unnumbered3.yaml b/vppcfg/unittest/yaml/error-unnumbered3.yaml
index cbbe8b1..a22d639 100644
--- a/vppcfg/unittest/yaml/error-unnumbered3.yaml
+++ b/vppcfg/unittest/yaml/error-unnumbered3.yaml
@@ -3,12 +3,17 @@ test:
   errors:
     expected:
       - "(sub-)?interface .* cannot also have addresses when it is unnumbered"
-    count: 3
+      - "loopback .* cannot also have addresses when it is unnumbered"
+    count: 4
 ---
 loopbacks:
   loop0:
     mtu: 9216
     addresses: [ 192.0.2.1/32, 2001:db8:1::1/128 ]
+  loop1:
+    mtu: 9216
+    unnumbered: loop0
+    addresses: [ 192.0.2.13/32, 2001:db8:4::1/128 ]
 
 interfaces:
   GigabitEthernet1/0/0: