From d242923cc6a90b5d597394f2cae4035a0d267526 Mon Sep 17 00:00:00 2001 From: Michael Hanselmann <hansmi@google.com> Date: Mon, 3 Dec 2012 17:08:42 +0100 Subject: [PATCH] utils.text: Function to verify MAC address prefix The network management code needs to verify a MAC address prefix. Instead of (ab)using NormalizeAndValidateMac, clean code should be used. Unit tests for NormalizeAndValidateMac are updated and new ones for NormalizeAndValidateThreeOctetMacPrefix are added. Signed-off-by: Michael Hanselmann <hansmi@google.com> Reviewed-by: Guido Trotter <ultrotter@google.com> --- lib/utils/text.py | 82 ++++++++++++++++++++++++------ test/ganeti.utils.text_unittest.py | 18 ++++++- 2 files changed, 82 insertions(+), 18 deletions(-) diff --git a/lib/utils/text.py b/lib/utils/text.py index 84de0bb99..6174caf88 100644 --- a/lib/utils/text.py +++ b/lib/utils/text.py @@ -37,15 +37,15 @@ _PARSEUNIT_REGEX = re.compile(r"^([.\d]+)\s*([a-zA-Z]+)?$") #: Characters which don't need to be quoted for shell commands _SHELL_UNQUOTED_RE = re.compile("^[-.,=:/_+@A-Za-z0-9]+$") -#: MAC checker regexp -_MAC_CHECK_RE = re.compile("^([0-9a-f]{2}:){5}[0-9a-f]{2}$", re.I) - #: Shell param checker regexp _SHELLPARAM_REGEX = re.compile(r"^[-a-zA-Z0-9._+/:%@]+$") #: ASCII equivalent of unicode character 'HORIZONTAL ELLIPSIS' (U+2026) _ASCII_ELLIPSIS = "..." +#: MAC address octet +_MAC_ADDR_OCTET_RE = r"[0-9a-f]{2}" + def MatchNameComponent(key, name_list, case_sensitive=True): """Try to match a name against a list. @@ -301,25 +301,75 @@ def GenerateSecret(numbytes=20): return os.urandom(numbytes).encode("hex") -def NormalizeAndValidateMac(mac): - """Normalizes and check if a MAC address is valid. +def _MakeMacAddrRegexp(octets): + """Builds a regular expression for verifying MAC addresses. - Checks whether the supplied MAC address is formally correct, only - accepts colon separated format. Normalize it to all lower. + @type octets: integer + @param octets: How many octets to expect (1-6) + @return: Compiled regular expression - @type mac: str - @param mac: the MAC to be validated - @rtype: str - @return: returns the normalized and validated MAC. + """ + assert octets > 0 + assert octets <= 6 + + return re.compile("^%s$" % ":".join([_MAC_ADDR_OCTET_RE] * octets), + re.I) + + +#: Regular expression for full MAC address +_MAC_CHECK_RE = _MakeMacAddrRegexp(6) + +#: Regular expression for half a MAC address +_MAC_PREFIX_CHECK_RE = _MakeMacAddrRegexp(3) + + +def _MacAddressCheck(check_re, mac, msg): + """Checks a MAC address using a regular expression. + + @param check_re: Compiled regular expression as returned by C{re.compile} + @type mac: string + @param mac: MAC address to be validated + @type msg: string + @param msg: Error message (%s will be replaced with MAC address) + + """ + if check_re.match(mac): + return mac.lower() + + raise errors.OpPrereqError(msg % mac, errors.ECODE_INVAL) - @raise errors.OpPrereqError: If the MAC isn't valid + +def NormalizeAndValidateMac(mac): + """Normalizes and check if a MAC address is valid and contains six octets. + + Checks whether the supplied MAC address is formally correct. Accepts + colon-separated format only. Normalize it to all lower case. + + @type mac: string + @param mac: MAC address to be validated + @rtype: string + @return: Normalized and validated MAC address + @raise errors.OpPrereqError: If the MAC address isn't valid """ - if not _MAC_CHECK_RE.match(mac): - raise errors.OpPrereqError("Invalid MAC address '%s'" % mac, - errors.ECODE_INVAL) + return _MacAddressCheck(_MAC_CHECK_RE, mac, "Invalid MAC address '%s'") - return mac.lower() + +def NormalizeAndValidateThreeOctetMacPrefix(mac): + """Normalizes a potential MAC address prefix (three octets). + + Checks whether the supplied string is a valid MAC address prefix consisting + of three colon-separated octets. The result is normalized to all lower case. + + @type mac: string + @param mac: Prefix to be validated + @rtype: string + @return: Normalized and validated prefix + @raise errors.OpPrereqError: If the MAC address prefix isn't valid + + """ + return _MacAddressCheck(_MAC_PREFIX_CHECK_RE, mac, + "Invalid MAC address prefix '%s'") def SafeEncode(text): diff --git a/test/ganeti.utils.text_unittest.py b/test/ganeti.utils.text_unittest.py index 181e2f6cf..c01bcf2fe 100755 --- a/test/ganeti.utils.text_unittest.py +++ b/test/ganeti.utils.text_unittest.py @@ -356,14 +356,28 @@ class TestShellWriter(unittest.TestCase): class TestNormalizeAndValidateMac(unittest.TestCase): def testInvalid(self): - self.assertRaises(errors.OpPrereqError, - utils.NormalizeAndValidateMac, "xxx") + for i in ["xxx", "00:11:22:33:44:55:66", "zz:zz:zz:zz:zz:zz"]: + self.assertRaises(errors.OpPrereqError, utils.NormalizeAndValidateMac, i) def testNormalization(self): for mac in ["aa:bb:cc:dd:ee:ff", "00:AA:11:bB:22:cc"]: self.assertEqual(utils.NormalizeAndValidateMac(mac), mac.lower()) +class TestNormalizeAndValidateThreeOctetMacPrefix(unittest.TestCase): + def testInvalid(self): + for i in ["xxx", "00:11:22:33:44:55:66", "zz:zz:zz:zz:zz:zz", + "aa:bb:cc:dd:ee:ff", "00:AA:11:bB:22:cc", + "00:11:"]: + self.assertRaises(errors.OpPrereqError, + utils.NormalizeAndValidateThreeOctetMacPrefix, i) + + def testNormalization(self): + for mac in ["aa:bb:cc", "00:AA:11"]: + self.assertEqual(utils.NormalizeAndValidateThreeOctetMacPrefix(mac), + mac.lower()) + + class TestSafeEncode(unittest.TestCase): """Test case for SafeEncode""" -- GitLab