Source code for xcp.net.mac

# Copyright (c) 2013, Citrix Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""
Mac address object for manipulation and comparison.
"""

__version__ = "1.0.1"
__author__  = "Andrew Cooper"

import re
import six

VALID_COLON_MAC = re.compile(r"^([\da-fA-F]{1,2}:){5}[\da-fA-F]{1,2}$")
VALID_DASH_MAC = re.compile(r"^([\da-fA-F]{1,2}-){5}[\da-fA-F]{1,2}$")
VALID_DOTQUAD_MAC = re.compile(r"^([\da-fA-F]{1,4}\.){2}[\da-fA-F]{1,4}$")

[docs] @six.python_2_unicode_compatible class MAC(object): """ Mac address object for manipulation and comparison """
[docs] @classmethod def is_valid(cls, addr): """ Static method to assertain whether addr is a recognised MAC address or not """ try: MAC(addr) except Exception: return False return True
def __init__(self, addr): """Constructor""" self.octets = [] self.integer = -1 if not isinstance(addr, six.string_types): raise TypeError("String expected") res = VALID_COLON_MAC.match(addr) if res: self._set_from_str_octets(addr.split(":")) return res = VALID_DASH_MAC.match(addr) if res: self._set_from_str_octets(addr.split("-")) return res = VALID_DOTQUAD_MAC.match(addr) if res: self._set_from_str_quads(addr.split(".")) return raise ValueError("Unrecognised MAC address '%s'" % addr) def _set_from_str_octets(self, octets): """Private helper""" if len(octets) != 6: raise ValueError("Expected 6 octets, got %d" % len(octets)) self.octets = [ int(i, 16) for i in octets ] # See:https://diveintopython3.net/porting-code-to-python-3-with-2to3.html#xrange # False positive from pylint --py3k: pylint: disable=range-builtin-not-iterating self.integer = sum(t[0] << t[1] for t in zip(self.octets, range(40, -1, -8))) def _set_from_str_quads(self, quads): """Private helper""" if len(quads) != 3: raise ValueError("Expected 3 quads, got %d" % len(quads)) self.octets = [] for quad in ( int(i, 16) for i in quads ): self.octets.extend([(quad >> 8) & 0xff, quad & 0xff]) # False positive from pylint --py3k: pylint: disable=range-builtin-not-iterating self.integer = sum(t[0] << t[1] for t in zip(self.octets, range(40, -1, -8)))
[docs] def is_unicast(self): """is this a unicast address?""" return (self.integer & 1 << 40) == 0
[docs] def is_multicast(self): """is this a multicast address?""" return (self.integer & 1 << 40) != 0
[docs] def is_global(self): """is this a globally administered address?""" return (self.integer & 1 << 41) == 0
[docs] def is_local(self): """is this a locally administered address?""" return (self.integer & 1 << 41) != 0
def __str__(self): return ':'.join([ "%0.2x" % x for x in self.octets]) def __repr__(self): return "<MAC %s>" % ':'.join([ "%0.2x" % x for x in self.octets])
[docs] def as_string(self, sep = ".", upper = False): """Get a string representation of this MAC address""" res = "" if sep == ".": # this is a hack but I cant think of an easy way of # manipulating self.octetes res = "%0.4x.%0.4x.%0.4x" % ( (self.integer >> 32) & 0xffff, (self.integer >> 16) & 0xffff, (self.integer ) & 0xffff ) elif sep == "-": res = '-'.join([ "%0.2x" % o for o in self.octets]) elif sep == ":": res = ':'.join([ "%0.2x" % o for o in self.octets]) else: raise ValueError("'%s' is not a valid seperator" % sep) if upper: return res.upper() return res
def __eq__(self, rhs): if hasattr(rhs, "integer"): return self.integer == rhs.integer elif MAC.is_valid(rhs): return self.integer == MAC(rhs).integer else: return NotImplemented def __ne__(self, rhs): if hasattr(rhs, "integer"): return self.integer != rhs.integer elif MAC.is_valid(rhs): return self.integer != MAC(rhs).integer else: return NotImplemented def __hash__(self): return self.__str__().__hash__() def __lt__(self, rhs): if hasattr(rhs, "integer"): return self.integer < rhs.integer elif MAC.is_valid(rhs): return self.integer < MAC(rhs).integer else: return NotImplemented def __le__(self, rhs): if hasattr(rhs, "integer"): return self.integer <= rhs.integer elif MAC.is_valid(rhs): return self.integer <= MAC(rhs).integer else: return NotImplemented def __gt__(self, rhs): if hasattr(rhs, "integer"): return self.integer > rhs.integer elif MAC.is_valid(rhs): return self.integer > MAC(rhs).integer else: return NotImplemented def __ge__(self, rhs): if hasattr(rhs, "integer"): return self.integer >= rhs.integer elif MAC.is_valid(rhs): return self.integer >= MAC(rhs).integer else: return NotImplemented