Add address.get_canonical() and is_canonical()
These functions will take either an IPv4/IPv6 address, or an IPv4/IPv6 prefix, and cast them to their canonical form. Notably for IPv6 addresses, this means lower case and with the 0-tuples correctly formatted: 2001:DB8::1 becomes 2001:db8::1 2001:db8:0:0::1 becomes 2001:db8::1 This avoids spurious diffs in vppcfg when comparing to the output of the VPP dataplane.
This commit is contained in:
@ -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
|
||||
|
@ -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):
|
||||
|
44
vppcfg/config/test_address.py
Normal file
44
vppcfg/config/test_address.py
Normal file
@ -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
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user