Add prefixlist (mixed IPv4 and IPv6, containing either IP addresses or prefixes + tests
This commit is contained in:
@ -38,6 +38,7 @@ from .interface import validate_interfaces
|
|||||||
from .bridgedomain import validate_bridgedomains
|
from .bridgedomain import validate_bridgedomains
|
||||||
from .vxlan_tunnel import validate_vxlan_tunnels
|
from .vxlan_tunnel import validate_vxlan_tunnels
|
||||||
from .tap import validate_taps
|
from .tap import validate_taps
|
||||||
|
from .prefixlist import validate_prefixlists
|
||||||
from .acl import validate_acls
|
from .acl import validate_acls
|
||||||
|
|
||||||
|
|
||||||
@ -90,6 +91,7 @@ class Validator:
|
|||||||
validate_bridgedomains,
|
validate_bridgedomains,
|
||||||
validate_vxlan_tunnels,
|
validate_vxlan_tunnels,
|
||||||
validate_taps,
|
validate_taps,
|
||||||
|
validate_prefixlists,
|
||||||
validate_acls,
|
validate_acls,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
104
vppcfg/config/prefixlist.py
Normal file
104
vppcfg/config/prefixlist.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2023 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.
|
||||||
|
#
|
||||||
|
""" A vppcfg configuration module that validates prefixlists """
|
||||||
|
import logging
|
||||||
|
import socket
|
||||||
|
import ipaddress
|
||||||
|
|
||||||
|
|
||||||
|
def get_prefixlists(yaml):
|
||||||
|
"""Return a list of all prefixlists."""
|
||||||
|
ret = []
|
||||||
|
if "prefixlists" in yaml:
|
||||||
|
for plname, _pl in yaml["prefixlists"].items():
|
||||||
|
ret.append(plname)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def get_by_name(yaml, plname):
|
||||||
|
"""Return the prefixlist by name, if it exists. Return None otherwise."""
|
||||||
|
try:
|
||||||
|
if plname in yaml["prefixlists"]:
|
||||||
|
return plname, yaml["prefixlists"][plname]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
def count(yaml, plname):
|
||||||
|
"""Return the number of IPv4 and IPv6 entries in the prefixlist.
|
||||||
|
Returns 0, 0 if it doesn't exist"""
|
||||||
|
v4, v6 = 0, 0
|
||||||
|
|
||||||
|
plname, pl = get_by_name(yaml, plname)
|
||||||
|
if not pl:
|
||||||
|
return 0, 0
|
||||||
|
for m in pl["members"]:
|
||||||
|
ipn = ipaddress.ip_network(m, strict=False)
|
||||||
|
if ipn.version == 4:
|
||||||
|
v4 += 1
|
||||||
|
elif ipn.version == 6:
|
||||||
|
v6 += 1
|
||||||
|
|
||||||
|
return v4, v6
|
||||||
|
|
||||||
|
|
||||||
|
def count_ipv4(yaml, plname):
|
||||||
|
"""Return the number of IPv4 entries in the prefixlist."""
|
||||||
|
v4, v6 = count(yaml, plname)
|
||||||
|
return v4
|
||||||
|
|
||||||
|
|
||||||
|
def count_ipv6(yaml, plname):
|
||||||
|
"""Return the number of IPv6 entries in the prefixlist."""
|
||||||
|
v4, v6 = count(yaml, plname)
|
||||||
|
return v6
|
||||||
|
|
||||||
|
|
||||||
|
def has_ipv4(yaml, plname):
|
||||||
|
"""Return True if the prefixlist has at least one IPv4 entry."""
|
||||||
|
v4, v6 = count(yaml, plname)
|
||||||
|
return v4 > 0
|
||||||
|
|
||||||
|
|
||||||
|
def has_ipv6(yaml, plname):
|
||||||
|
"""Return True if the prefixlist has at least one IPv6 entry."""
|
||||||
|
v4, v6 = count(yaml, plname)
|
||||||
|
return v6 > 0
|
||||||
|
|
||||||
|
|
||||||
|
def is_empty(yaml, plname):
|
||||||
|
"""Return True if the prefixlist has no entries."""
|
||||||
|
v4, v6 = count(yaml, plname)
|
||||||
|
return v4 + v6 == 0
|
||||||
|
|
||||||
|
|
||||||
|
def validate_prefixlists(yaml):
|
||||||
|
"""Validate the semantics of all YAML 'prefixlists' entries"""
|
||||||
|
result = True
|
||||||
|
msgs = []
|
||||||
|
logger = logging.getLogger("vppcfg.config")
|
||||||
|
logger.addHandler(logging.NullHandler())
|
||||||
|
|
||||||
|
if not "prefixlists" in yaml:
|
||||||
|
return result, msgs
|
||||||
|
|
||||||
|
for plname, pl in yaml["prefixlists"].items():
|
||||||
|
logger.debug(f"prefixlist {plname}: {pl}")
|
||||||
|
members = 0
|
||||||
|
for pl_member in pl["members"]:
|
||||||
|
members += 1
|
||||||
|
logger.debug(f"prefixlist {plname} member {members} is {pl_member}")
|
||||||
|
|
||||||
|
return result, msgs
|
77
vppcfg/config/test_prefixlist.py
Normal file
77
vppcfg/config/test_prefixlist.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#
|
||||||
|
# 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 taps """
|
||||||
|
import unittest
|
||||||
|
import yaml
|
||||||
|
from . import prefixlist
|
||||||
|
from .unittestyaml import UnitTestYaml
|
||||||
|
|
||||||
|
|
||||||
|
class TestACLMethods(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
with UnitTestYaml("test_prefixlist.yaml") as f:
|
||||||
|
self.cfg = yaml.load(f, Loader=yaml.FullLoader)
|
||||||
|
|
||||||
|
def test_get_prefixlists(self):
|
||||||
|
plist = prefixlist.get_prefixlists(self.cfg)
|
||||||
|
self.assertIsInstance(plist, list)
|
||||||
|
self.assertEqual(5, len(plist))
|
||||||
|
|
||||||
|
def test_get_by_name(self):
|
||||||
|
plname, _pl = prefixlist.get_by_name(self.cfg, "trusted")
|
||||||
|
self.assertIsNotNone(_pl)
|
||||||
|
self.assertEqual("trusted", plname)
|
||||||
|
|
||||||
|
plname, _pl = prefixlist.get_by_name(self.cfg, "pl-noexist")
|
||||||
|
self.assertIsNone(plname)
|
||||||
|
self.assertIsNone(_pl)
|
||||||
|
|
||||||
|
def test_count(self):
|
||||||
|
v4, v6 = prefixlist.count(self.cfg, "trusted")
|
||||||
|
self.assertEqual(2, v4)
|
||||||
|
self.assertEqual(2, v6)
|
||||||
|
|
||||||
|
v4, v6 = prefixlist.count(self.cfg, "empty")
|
||||||
|
self.assertEqual(0, v4)
|
||||||
|
self.assertEqual(0, v6)
|
||||||
|
|
||||||
|
v4, v6 = prefixlist.count(self.cfg, "pl-noexist")
|
||||||
|
self.assertEqual(0, v4)
|
||||||
|
self.assertEqual(0, v6)
|
||||||
|
|
||||||
|
def test_count_ipv4(self):
|
||||||
|
self.assertEqual(2, prefixlist.count_ipv4(self.cfg, "trusted"))
|
||||||
|
self.assertEqual(0, prefixlist.count_ipv4(self.cfg, "empty"))
|
||||||
|
self.assertEqual(0, prefixlist.count_ipv4(self.cfg, "pl-noexist"))
|
||||||
|
|
||||||
|
def test_count_ipv6(self):
|
||||||
|
self.assertEqual(2, prefixlist.count_ipv6(self.cfg, "trusted"))
|
||||||
|
self.assertEqual(0, prefixlist.count_ipv6(self.cfg, "empty"))
|
||||||
|
self.assertEqual(0, prefixlist.count_ipv6(self.cfg, "pl-noexist"))
|
||||||
|
|
||||||
|
def test_has_ipv4(self):
|
||||||
|
self.assertTrue(prefixlist.has_ipv4(self.cfg, "trusted"))
|
||||||
|
self.assertFalse(prefixlist.has_ipv4(self.cfg, "empty"))
|
||||||
|
self.assertFalse(prefixlist.has_ipv4(self.cfg, "pl-noexist"))
|
||||||
|
|
||||||
|
def test_has_ipv6(self):
|
||||||
|
self.assertTrue(prefixlist.has_ipv6(self.cfg, "trusted"))
|
||||||
|
self.assertFalse(prefixlist.has_ipv6(self.cfg, "empty"))
|
||||||
|
self.assertFalse(prefixlist.has_ipv6(self.cfg, "pl-noexist"))
|
||||||
|
|
||||||
|
def test_is_empty(self):
|
||||||
|
self.assertFalse(prefixlist.is_empty(self.cfg, "trusted"))
|
||||||
|
self.assertTrue(prefixlist.is_empty(self.cfg, "empty"))
|
||||||
|
self.assertTrue(prefixlist.is_empty(self.cfg, "pl-noexist"))
|
@ -118,6 +118,18 @@ taps:
|
|||||||
mtu: 1500
|
mtu: 1500
|
||||||
bridge: br1
|
bridge: br1
|
||||||
|
|
||||||
|
prefixlists:
|
||||||
|
trusted:
|
||||||
|
description: "All IPv4 and IPv6 trusted networks"
|
||||||
|
members:
|
||||||
|
- 192.0.2.1
|
||||||
|
- 192.0.2.0/24
|
||||||
|
- 2001:db8::1
|
||||||
|
- 2001:db8::/64
|
||||||
|
empty:
|
||||||
|
description: "An empty prefixlist"
|
||||||
|
members: []
|
||||||
|
|
||||||
acls:
|
acls:
|
||||||
acl01:
|
acl01:
|
||||||
description: "Test ACL"
|
description: "Test ACL"
|
||||||
|
@ -4,6 +4,7 @@ loopbacks: map(include('loopback'),key=str(matches='loop[0-9]+'),required=False)
|
|||||||
bridgedomains: map(include('bridgedomain'),key=str(matches='bd[0-9]+'),required=False)
|
bridgedomains: map(include('bridgedomain'),key=str(matches='bd[0-9]+'),required=False)
|
||||||
vxlan_tunnels: map(include('vxlan'),key=str(matches='vxlan_tunnel[0-9]+'),required=False)
|
vxlan_tunnels: map(include('vxlan'),key=str(matches='vxlan_tunnel[0-9]+'),required=False)
|
||||||
taps: map(include('tap'),key=str(matches='tap[0-9]+'),required=False)
|
taps: map(include('tap'),key=str(matches='tap[0-9]+'),required=False)
|
||||||
|
prefixlists: map(include('prefixlist'),key=str(matches='[a-z][a-z0-9\-]+',min=1,max=64),required=False)
|
||||||
acls: map(include('acl'),key=str(matches='[a-z][a-z0-9\-]+'),required=False)
|
acls: map(include('acl'),key=str(matches='[a-z][a-z0-9\-]+'),required=False)
|
||||||
---
|
---
|
||||||
vxlan:
|
vxlan:
|
||||||
@ -81,8 +82,13 @@ tap:
|
|||||||
rx-ring-size: int(min=8,max=32768,required=False)
|
rx-ring-size: int(min=8,max=32768,required=False)
|
||||||
tx-ring-size: int(min=8,max=32768,required=False)
|
tx-ring-size: int(min=8,max=32768,required=False)
|
||||||
---
|
---
|
||||||
|
prefixlist:
|
||||||
|
description: str(exclude='\'"',len=64,required=False)
|
||||||
|
members: list(any(ip_interface(),ip()))
|
||||||
|
---
|
||||||
# Valid: 80 "www" "-1024" "1024-" "1024-65535", and "any"
|
# Valid: 80 "www" "-1024" "1024-" "1024-65535", and "any"
|
||||||
acl-term-port-int-range-symbolic: any(int(min=1,max=65535),str(equals="any"),regex('^([1-9][0-9]*-|-[1-9][0-9]*|[1-9][0-9]*-[1-9][0-9]*)$'),regex('^[a-z][a-z0-9-]*$'))
|
acl-term-port-int-range-symbolic: any(int(min=1,max=65535),str(equals="any"),regex('^([1-9][0-9]*-|-[1-9][0-9]*|[1-9][0-9]*-[1-9][0-9]*)$'),regex('^[a-z][a-z0-9-]*$'))
|
||||||
|
---
|
||||||
# Valid: 80 "-245" "10-" "10-245", and "any"
|
# Valid: 80 "-245" "10-" "10-245", and "any"
|
||||||
acl-term-icmp-int-range: any(int(min=0,max=255),str(equals="any"),regex('^([0-9]+-|-[1-9][0-9]*|[0-9]*-[1-9][0-9]*)$'))
|
acl-term-icmp-int-range: any(int(min=0,max=255),str(equals="any"),regex('^([0-9]+-|-[1-9][0-9]*|[0-9]*-[1-9][0-9]*)$'))
|
||||||
---
|
---
|
||||||
|
26
vppcfg/unittest/test_prefixlist.yaml
Normal file
26
vppcfg/unittest/test_prefixlist.yaml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
prefixlists:
|
||||||
|
trusted:
|
||||||
|
description: "All IPv4 and IPv6 trusted networks"
|
||||||
|
members:
|
||||||
|
- 192.0.2.1
|
||||||
|
- 192.0.2.0/24
|
||||||
|
- 2001:db8::1
|
||||||
|
- 2001:db8::/64
|
||||||
|
deny-all:
|
||||||
|
description: "Default for IPv4 and IPv6"
|
||||||
|
members:
|
||||||
|
- 0.0.0.0/0
|
||||||
|
- ::/0
|
||||||
|
ipv4-only:
|
||||||
|
description: "Only contains IPv4"
|
||||||
|
members:
|
||||||
|
- 192.0.2.1
|
||||||
|
- 192.0.2.0/24
|
||||||
|
ipv6-only:
|
||||||
|
description: "Only contains IPv6"
|
||||||
|
members:
|
||||||
|
- 2001:db8::1
|
||||||
|
- 2001:db8::/64
|
||||||
|
empty:
|
||||||
|
description: "An empty list"
|
||||||
|
members: []
|
Reference in New Issue
Block a user