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 False
|
||||||
|
|
||||||
return True
|
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 """
|
""" A vppcfg configuration module that validates interfaces """
|
||||||
import logging
|
import logging
|
||||||
|
import ipaddress
|
||||||
from . import bondethernet
|
from . import bondethernet
|
||||||
from . import bridgedomain
|
from . import bridgedomain
|
||||||
from . import loopback
|
from . import loopback
|
||||||
@ -500,6 +501,12 @@ def validate_interfaces(yaml):
|
|||||||
f"interface {ifname} IP address {addr} conflicts with another"
|
f"interface {ifname} IP address {addr} conflicts with another"
|
||||||
)
|
)
|
||||||
result = False
|
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 "l2xc" in iface:
|
||||||
if has_sub(yaml, ifname):
|
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"
|
lcp: "ice12-0-0"
|
||||||
mac: f2:01:00:12:00:00
|
mac: f2:01:00:12:00:00
|
||||||
mtu: 9000
|
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:
|
sub-interfaces:
|
||||||
1234:
|
1234:
|
||||||
mtu: 1200
|
mtu: 1200
|
||||||
|
@ -13,7 +13,7 @@ interfaces:
|
|||||||
GigabitEthernet1/0/0:
|
GigabitEthernet1/0/0:
|
||||||
description: "Infra: nikhef-core-1.nl.switch.coloclue.net e1/34"
|
description: "Infra: nikhef-core-1.nl.switch.coloclue.net e1/34"
|
||||||
lcp: e0-0
|
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:
|
sub-interfaces:
|
||||||
100:
|
100:
|
||||||
description: "Cust: hvn0.nlams0.ipng.ch"
|
description: "Cust: hvn0.nlams0.ipng.ch"
|
||||||
|
@ -5,7 +5,8 @@ test:
|
|||||||
- "interface .* IP address .* conflicts with another"
|
- "interface .* IP address .* conflicts with another"
|
||||||
- "sub-interface .* IP address .* conflicts with another"
|
- "sub-interface .* IP address .* conflicts with another"
|
||||||
- "loopback .* IP address .* conflicts with another"
|
- "loopback .* IP address .* conflicts with another"
|
||||||
count: 14
|
- "interface .* IP address .* is not canonical, use .*"
|
||||||
|
count: 15
|
||||||
---
|
---
|
||||||
interfaces:
|
interfaces:
|
||||||
GigabitEthernet1/0/0:
|
GigabitEthernet1/0/0:
|
||||||
@ -15,7 +16,7 @@ interfaces:
|
|||||||
|
|
||||||
GigabitEthernet1/0/1:
|
GigabitEthernet1/0/1:
|
||||||
lcp: e1-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:
|
sub-interfaces:
|
||||||
100:
|
100:
|
||||||
description: "These addresses overlap with Gi1/0/1"
|
description: "These addresses overlap with Gi1/0/1"
|
||||||
@ -23,6 +24,9 @@ interfaces:
|
|||||||
101:
|
101:
|
||||||
description: "These addresses overlap with loop0"
|
description: "These addresses overlap with loop0"
|
||||||
addresses: [ 192.0.2.10/29, 2001:db8:2::2/64 ]
|
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:
|
GigabitEthernet1/0/2:
|
||||||
lcp: e0-2
|
lcp: e0-2
|
||||||
|
Reference in New Issue
Block a user