diff --git a/README.md b/README.md index 79a7042..02ed57f 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ sudo pip3 install argparse sudo pip3 install yamale sudo pip3 install pyyaml sudo pip3 install pyinstaller +sudo pip3 install netaddr +sudo pip3 install ipaddress ## Ensure all unittests pass. ./tests.py -d -t unittest/yaml/*.yaml diff --git a/config/interface.py b/config/interface.py index 7e79a62..de8b35f 100644 --- a/config/interface.py +++ b/config/interface.py @@ -18,6 +18,7 @@ import config.loopback as loopback import config.vxlan_tunnel as vxlan_tunnel import config.lcp as lcp import config.address as address +import config.mac as mac def get_qinx_parent_by_name(yaml, ifname): """ Returns the sub-interface which matches a QinAD or QinQ outer tag, or None,None @@ -424,6 +425,10 @@ def validate_interfaces(yaml): if not 'state' in iface: iface['state'] = 'up' + if 'mac' in iface and mac.is_multicast(iface['mac']): + msgs.append("interface %s MAC address %s cannot be multicast" % (ifname, iface['mac'])) + result = False + iface_mtu = get_mtu(yaml, ifname) iface_lcp = get_lcp(yaml, ifname) iface_address = has_address(yaml, ifname) diff --git a/config/mac.py b/config/mac.py new file mode 100644 index 0000000..ba7a05b --- /dev/null +++ b/config/mac.py @@ -0,0 +1,48 @@ +# +# 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. +# +import logging +import netaddr + +def is_valid(mac): + """ Return True if the string given in `mac` is a valid (6-byte) MAC address, + as defined by netaddr.EUI """ + try: + addr = netaddr.EUI(mac) + except: + return False + return True + +def is_local(mac): + """ Return True if a MAC address is a valid locally administered one. """ + try: + addr = netaddr.EUI(mac) + except: + return False + return bool(addr.words[0] & 0b10) + +def is_multicast(mac): + """ Return True if a MAC address is a valid multicast one. """ + try: + addr = netaddr.EUI(mac) + except: + return False + return bool(addr.words[0] & 0b01) + +def is_unicast(mac): + """ Return True if a MAC address is a valid unicast one. """ + try: + addr = netaddr.EUI(mac) + except: + return False + return not bool(addr.words[0] & 0b01) diff --git a/config/test_mac.py b/config/test_mac.py new file mode 100644 index 0000000..f9b194a --- /dev/null +++ b/config/test_mac.py @@ -0,0 +1,21 @@ +import unittest +import config.mac as mac + +class TestMACMethods(unittest.TestCase): + def test_is_valid(self): + self.assertTrue(mac.is_valid("00:01:02:03:04:05")) + self.assertTrue(mac.is_valid("00-01-02-03-04-05")) + self.assertTrue(mac.is_valid("0001.0203.0405")) + self.assertFalse(mac.is_valid("hoi")) + + def test_is_local(self): + self.assertTrue(mac.is_local("02:00:00:00:00:00")) + self.assertFalse(mac.is_local("00:00:00:00:00:00")) + + def test_is_multicast(self): + self.assertTrue(mac.is_multicast("01:00:00:00:00:00")) + self.assertFalse(mac.is_multicast("00:00:00:00:00:00")) + + def test_is_unicast(self): + self.assertFalse(mac.is_unicast("01:00:00:00:00:00")) + self.assertTrue(mac.is_unicast("00:00:00:00:00:00")) diff --git a/unittest/yaml/error-interface1.yaml b/unittest/yaml/error-interface1.yaml new file mode 100644 index 0000000..15e5cbd --- /dev/null +++ b/unittest/yaml/error-interface1.yaml @@ -0,0 +1,17 @@ +test: + description: "Interface mac addresses cannot be multicast" + errors: + expected: + - "interface .* MAC address .* cannot be multicast" + count: 1 +--- +interfaces: + GigabitEthernet3/0/0: + description: "Cool, local MACs are fine" + mac: 02:00:00:00:00:00 + GigabitEthernet3/0/1: + description: "Cool, global unicast MACs are fine" + mac: 04:00:00:00:00:00 + GigabitEthernet3/0/2: + description: "Not cool, multicast MACs" + mac: 01:00:00:00:00:00