diff --git a/vppcfg/config/address.py b/vppcfg/config/address.py index 2afa3d9..2e1fb48 100644 --- a/vppcfg/config/address.py +++ b/vppcfg/config/address.py @@ -113,3 +113,33 @@ def is_allowed(yaml, ifname, iface_addresses, ip_interface): return False return True + + +def get_canonical(iface_address): + """Returns the canonical form of an interface address, which can be either an address + '2001:db8::1' or a prefix '2001:db8::1/64' for either IPv4 of IPv6. + + This function the string representation of the canonical address.""" + + is_prefix = "/" in iface_address + if is_prefix: + iface_address, prefixlen = iface_address.split("/") + + ip_address = ipaddress.ip_address(iface_address) + if is_prefix: + return str(ip_address) + "/" + prefixlen + return str(ip_address) + + +def is_canonical(iface_address): + """Checks to see that the interface address is written in a canonical form, useful to + ensure that IPv6 addresses are written in such a way that they don't trigger spurious + diffs when comparing to VPP. As an example, '2001:DB8:0:0::1/64' is fine, but VPP will + show this as '2001:db8::1/64'. + + Input can be either an address 2001:db8::1 or a prefix 2001:db8::1/128 + + This function returns False if the iface_address isn't canonical, and True otherwise. + """ + can = get_canonical(iface_address) + return can == iface_address diff --git a/vppcfg/config/interface.py b/vppcfg/config/interface.py index 4755a93..5d73560 100644 --- a/vppcfg/config/interface.py +++ b/vppcfg/config/interface.py @@ -13,6 +13,7 @@ # """ A vppcfg configuration module that validates interfaces """ import logging +import ipaddress from . import bondethernet from . import bridgedomain from . import loopback @@ -500,6 +501,12 @@ def validate_interfaces(yaml): f"interface {ifname} IP address {addr} conflicts with another" ) result = False + if not address.is_canonical(addr): + canonical = address.get_canonical(addr) + msgs.append( + f"interface {ifname} IP address {addr} is not canonical, use {canonical}" + ) + result = False if "l2xc" in iface: if has_sub(yaml, ifname): diff --git a/vppcfg/config/test_address.py b/vppcfg/config/test_address.py new file mode 100644 index 0000000..dd4c354 --- /dev/null +++ b/vppcfg/config/test_address.py @@ -0,0 +1,44 @@ +# +# Copyright (c) 2022 Pim van Pelt +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# -*- coding: utf-8 -*- +""" Unit tests for addresses """ +import unittest +import yaml +from . import address + + +class TestAddressMethods(unittest.TestCase): + def test_get_canonical(self): + self.assertEqual(address.get_canonical("0.0.0.0"), "0.0.0.0") + self.assertEqual(address.get_canonical("0.0.0.0/0"), "0.0.0.0/0") + self.assertEqual(address.get_canonical("192.168.1.1"), "192.168.1.1") + self.assertEqual(address.get_canonical("192.168.1.1/32"), "192.168.1.1/32") + self.assertEqual(address.get_canonical("2001:db8::1"), "2001:db8::1") + self.assertEqual(address.get_canonical("2001:db8::1/64"), "2001:db8::1/64") + + self.assertEqual(address.get_canonical("2001:dB8::1/128"), "2001:db8::1/128") + self.assertEqual(address.get_canonical("2001:db8:0::1/128"), "2001:db8::1/128") + self.assertEqual(address.get_canonical("2001:db8::0:1"), "2001:db8::1") + + def test_is_canonical(self): + self.assertTrue(address.is_canonical("0.0.0.0")) + self.assertTrue(address.is_canonical("0.0.0.0/0")) + self.assertTrue(address.is_canonical("192.168.1.1")) + self.assertTrue(address.is_canonical("192.168.1.1/32")) + self.assertTrue(address.is_canonical("2001:db8::1")) + self.assertTrue(address.is_canonical("2001:db8::1/64")) + + self.assertFalse(address.is_canonical("2001:dB8::1/128")) # Capitals + self.assertFalse(address.is_canonical("2001:db8:0::1/128")) # Spurious 0 + self.assertFalse(address.is_canonical("2001:db8::0:1")) # Spurious 0 diff --git a/vppcfg/example.yaml b/vppcfg/example.yaml index 6df2d61..a5990d8 100644 --- a/vppcfg/example.yaml +++ b/vppcfg/example.yaml @@ -20,7 +20,7 @@ interfaces: lcp: "ice12-0-0" mac: f2:01:00:12:00:00 mtu: 9000 - addresses: [ 192.0.2.17/30, 2001:db8:3::1/64 ] + addresses: [ 192.0.2.17/30, 2001:DB8:3::1/64 ] sub-interfaces: 1234: mtu: 1200 diff --git a/vppcfg/unittest/yaml/correct-example1.yaml b/vppcfg/unittest/yaml/correct-example1.yaml index 41b8057..19214cf 100644 --- a/vppcfg/unittest/yaml/correct-example1.yaml +++ b/vppcfg/unittest/yaml/correct-example1.yaml @@ -13,7 +13,7 @@ interfaces: GigabitEthernet1/0/0: description: "Infra: nikhef-core-1.nl.switch.coloclue.net e1/34" lcp: e0-0 - addresses: [ 94.142.244.85/24, 2A02:898::146:1/64 ] + addresses: [ 94.142.244.85/24, 2a02:898::146:1/64 ] sub-interfaces: 100: description: "Cust: hvn0.nlams0.ipng.ch" diff --git a/vppcfg/unittest/yaml/error-address1.yaml b/vppcfg/unittest/yaml/error-address1.yaml index daf8701..b9645bd 100644 --- a/vppcfg/unittest/yaml/error-address1.yaml +++ b/vppcfg/unittest/yaml/error-address1.yaml @@ -5,7 +5,8 @@ test: - "interface .* IP address .* conflicts with another" - "sub-interface .* IP address .* conflicts with another" - "loopback .* IP address .* conflicts with another" - count: 14 + - "interface .* IP address .* is not canonical, use .*" + count: 15 --- interfaces: GigabitEthernet1/0/0: @@ -15,7 +16,7 @@ interfaces: GigabitEthernet1/0/1: lcp: e1-0-1 - addresses: [ 192.0.2.1/29, 2001:db8:1::1/64 ] + addresses: [ 192.0.2.1/29, 2001:DB8:1::1/64 ] sub-interfaces: 100: description: "These addresses overlap with Gi1/0/1" @@ -23,6 +24,9 @@ interfaces: 101: description: "These addresses overlap with loop0" addresses: [ 192.0.2.10/29, 2001:db8:2::2/64 ] + 102: + description: "This address is not canonical" + addresses: [ 2001:DB8:5::1/64 ] GigabitEthernet1/0/2: lcp: e0-2