diff options
Diffstat (limited to '')
-rw-r--r-- | netaddr/strategy/__init__.py | 273 | ||||
-rw-r--r-- | netaddr/strategy/eui48.py | 296 | ||||
-rw-r--r-- | netaddr/strategy/eui64.py | 273 | ||||
-rw-r--r-- | netaddr/strategy/ipv4.py | 283 | ||||
-rw-r--r-- | netaddr/strategy/ipv6.py | 259 |
5 files changed, 1384 insertions, 0 deletions
diff --git a/netaddr/strategy/__init__.py b/netaddr/strategy/__init__.py new file mode 100644 index 0000000..7b5861d --- /dev/null +++ b/netaddr/strategy/__init__.py @@ -0,0 +1,273 @@ +#----------------------------------------------------------------------------- +# Copyright (c) 2008 by David P. D. Moss. All rights reserved. +# +# Released under the BSD license. See the LICENSE file for details. +#----------------------------------------------------------------------------- +""" +Shared logic for various address types. +""" +import re as _re + +from netaddr.compat import _range, _is_str + + +def bytes_to_bits(): + """ + :return: A 256 element list containing 8-bit binary digit strings. The + list index value is equivalent to its bit string value. + """ + lookup = [] + bits_per_byte = _range(7, -1, -1) + for num in range(256): + bits = 8 * [None] + for i in bits_per_byte: + bits[i] = '01'[num & 1] + num >>= 1 + lookup.append(''.join(bits)) + return lookup + +#: A lookup table of 8-bit integer values to their binary digit bit strings. +BYTES_TO_BITS = bytes_to_bits() + + +def valid_words(words, word_size, num_words): + """ + :param words: A sequence of unsigned integer word values. + + :param word_size: Width (in bits) of each unsigned integer word value. + + :param num_words: Number of unsigned integer words expected. + + :return: ``True`` if word sequence is valid for this address type, + ``False`` otherwise. + """ + if not hasattr(words, '__iter__'): + return False + + if len(words) != num_words: + return False + + max_word = 2 ** word_size - 1 + + for i in words: + if not 0 <= i <= max_word: + return False + + return True + + +def int_to_words(int_val, word_size, num_words): + """ + :param int_val: Unsigned integer to be divided into words of equal size. + + :param word_size: Width (in bits) of each unsigned integer word value. + + :param num_words: Number of unsigned integer words expected. + + :return: A tuple contain unsigned integer word values split according + to provided arguments. + """ + max_int = 2 ** (num_words * word_size) - 1 + + if not 0 <= int_val <= max_int: + raise IndexError('integer out of bounds: %r!' % hex(int_val)) + + max_word = 2 ** word_size - 1 + + words = [] + for _ in range(num_words): + word = int_val & max_word + words.append(int(word)) + int_val >>= word_size + + return tuple(reversed(words)) + + +def words_to_int(words, word_size, num_words): + """ + :param words: A sequence of unsigned integer word values. + + :param word_size: Width (in bits) of each unsigned integer word value. + + :param num_words: Number of unsigned integer words expected. + + :return: An unsigned integer that is equivalent to value represented + by word sequence. + """ + if not valid_words(words, word_size, num_words): + raise ValueError('invalid integer word sequence: %r!' % (words,)) + + int_val = 0 + for i, num in enumerate(reversed(words)): + word = num + word = word << word_size * i + int_val = int_val | word + + return int_val + + +def valid_bits(bits, width, word_sep=''): + """ + :param bits: A network address in a delimited binary string format. + + :param width: Maximum width (in bits) of a network address (excluding + delimiters). + + :param word_sep: (optional) character or string used to delimit word + groups (default: '', no separator). + + :return: ``True`` if network address is valid, ``False`` otherwise. + """ + if not _is_str(bits): + return False + + if word_sep != '': + bits = bits.replace(word_sep, '') + + if len(bits) != width: + return False + + max_int = 2 ** width - 1 + + try: + if 0 <= int(bits, 2) <= max_int: + return True + except ValueError: + pass + + return False + + +def bits_to_int(bits, width, word_sep=''): + """ + :param bits: A network address in a delimited binary string format. + + :param width: Maximum width (in bits) of a network address (excluding + delimiters). + + :param word_sep: (optional) character or string used to delimit word + groups (default: '', no separator). + + :return: An unsigned integer that is equivalent to value represented + by network address in readable binary form. + """ + if not valid_bits(bits, width, word_sep): + raise ValueError('invalid readable binary string: %r!' % (bits,)) + + if word_sep != '': + bits = bits.replace(word_sep, '') + + return int(bits, 2) + + +def int_to_bits(int_val, word_size, num_words, word_sep=''): + """ + :param int_val: An unsigned integer. + + :param word_size: Width (in bits) of each unsigned integer word value. + + :param num_words: Number of unsigned integer words expected. + + :param word_sep: (optional) character or string used to delimit word + groups (default: '', no separator). + + :return: A network address in a delimited binary string format that is + equivalent in value to unsigned integer. + """ + bit_words = [] + + for word in int_to_words(int_val, word_size, num_words): + bits = [] + while word: + bits.append(BYTES_TO_BITS[word & 255]) + word >>= 8 + bits.reverse() + bit_str = ''.join(bits) or '0' * word_size + bits = ('0' * word_size + bit_str)[-word_size:] + bit_words.append(bits) + + if word_sep != '': + # Check custom separator. + if not _is_str(word_sep): + raise ValueError('word separator is not a string: %r!' % (word_sep,)) + + return word_sep.join(bit_words) + + +def valid_bin(bin_val, width): + """ + :param bin_val: A network address in Python's binary representation format + ('0bxxx'). + + :param width: Maximum width (in bits) of a network address (excluding + delimiters). + + :return: ``True`` if network address is valid, ``False`` otherwise. + """ + if not _is_str(bin_val): + return False + + if not bin_val.startswith('0b'): + return False + + bin_val = bin_val.replace('0b', '') + + if len(bin_val) > width: + return False + + max_int = 2 ** width - 1 + + try: + if 0 <= int(bin_val, 2) <= max_int: + return True + except ValueError: + pass + + return False + + +def int_to_bin(int_val, width): + """ + :param int_val: An unsigned integer. + + :param width: Maximum allowed width (in bits) of a unsigned integer. + + :return: Equivalent string value in Python's binary representation format + ('0bxxx'). + """ + bin_tokens = [] + + try: + # Python 2.6.x and upwards. + bin_val = bin(int_val) + except NameError: + # Python 2.4.x and 2.5.x + i = int_val + while i > 0: + word = i & 0xff + bin_tokens.append(BYTES_TO_BITS[word]) + i >>= 8 + + bin_tokens.reverse() + bin_val = '0b' + _re.sub(r'^[0]+([01]+)$', r'\1', ''.join(bin_tokens)) + + if len(bin_val[2:]) > width: + raise IndexError('binary string out of bounds: %s!' % (bin_val,)) + + return bin_val + + +def bin_to_int(bin_val, width): + """ + :param bin_val: A string containing an unsigned integer in Python's binary + representation format ('0bxxx'). + + :param width: Maximum allowed width (in bits) of a unsigned integer. + + :return: An unsigned integer that is equivalent to value represented + by Python binary string format. + """ + if not valid_bin(bin_val, width): + raise ValueError('not a valid Python binary string: %r!' % (bin_val,)) + + return int(bin_val.replace('0b', ''), 2) diff --git a/netaddr/strategy/eui48.py b/netaddr/strategy/eui48.py new file mode 100644 index 0000000..5685e34 --- /dev/null +++ b/netaddr/strategy/eui48.py @@ -0,0 +1,296 @@ +#----------------------------------------------------------------------------- +# Copyright (c) 2008 by David P. D. Moss. All rights reserved. +# +# Released under the BSD license. See the LICENSE file for details. +#----------------------------------------------------------------------------- +""" +IEEE 48-bit EUI (MAC address) logic. + +Supports numerous MAC string formats including Cisco's triple hextet as well +as bare MACs containing no delimiters. +""" +import struct as _struct +import re as _re + +# Check whether we need to use fallback code or not. +try: + from socket import AF_LINK +except ImportError: + AF_LINK = 48 + +from netaddr.core import AddrFormatError +from netaddr.compat import _is_str +from netaddr.strategy import ( + valid_words as _valid_words, int_to_words as _int_to_words, + words_to_int as _words_to_int, valid_bits as _valid_bits, + bits_to_int as _bits_to_int, int_to_bits as _int_to_bits, + valid_bin as _valid_bin, int_to_bin as _int_to_bin, + bin_to_int as _bin_to_int) + +#: The width (in bits) of this address type. +width = 48 + +#: The AF_* constant value of this address type. +family = AF_LINK + +#: A friendly string name address type. +family_name = 'MAC' + +#: The version of this address type. +version = 48 + +#: The maximum integer value that can be represented by this address type. +max_int = 2 ** width - 1 + +#----------------------------------------------------------------------------- +# Dialect classes. +#----------------------------------------------------------------------------- + +class mac_eui48(object): + """A standard IEEE EUI-48 dialect class.""" + #: The individual word size (in bits) of this address type. + word_size = 8 + + #: The number of words in this address type. + num_words = width // word_size + + #: The maximum integer value for an individual word in this address type. + max_word = 2 ** word_size - 1 + + #: The separator character used between each word. + word_sep = '-' + + #: The format string to be used when converting words to string values. + word_fmt = '%.2X' + + #: The number base to be used when interpreting word values as integers. + word_base = 16 + + +class mac_unix(mac_eui48): + """A UNIX-style MAC address dialect class.""" + word_size = 8 + num_words = width // word_size + word_sep = ':' + word_fmt = '%x' + word_base = 16 + + +class mac_unix_expanded(mac_unix): + """A UNIX-style MAC address dialect class with leading zeroes.""" + word_fmt = '%.2x' + + +class mac_cisco(mac_eui48): + """A Cisco 'triple hextet' MAC address dialect class.""" + word_size = 16 + num_words = width // word_size + word_sep = '.' + word_fmt = '%.4x' + word_base = 16 + + +class mac_bare(mac_eui48): + """A bare (no delimiters) MAC address dialect class.""" + word_size = 48 + num_words = width // word_size + word_sep = '' + word_fmt = '%.12X' + word_base = 16 + + +class mac_pgsql(mac_eui48): + """A PostgreSQL style (2 x 24-bit words) MAC address dialect class.""" + word_size = 24 + num_words = width // word_size + word_sep = ':' + word_fmt = '%.6x' + word_base = 16 + +#: The default dialect to be used when not specified by the user. +DEFAULT_DIALECT = mac_eui48 + +#----------------------------------------------------------------------------- +#: Regular expressions to match all supported MAC address formats. +RE_MAC_FORMATS = ( + # 2 bytes x 6 (UNIX, Windows, EUI-48) + '^' + ':'.join(['([0-9A-F]{1,2})'] * 6) + '$', + '^' + '-'.join(['([0-9A-F]{1,2})'] * 6) + '$', + + # 4 bytes x 3 (Cisco) + '^' + ':'.join(['([0-9A-F]{1,4})'] * 3) + '$', + '^' + '-'.join(['([0-9A-F]{1,4})'] * 3) + '$', + '^' + r'\.'.join(['([0-9A-F]{1,4})'] * 3) + '$', + + # 6 bytes x 2 (PostgreSQL) + '^' + '-'.join(['([0-9A-F]{5,6})'] * 2) + '$', + '^' + ':'.join(['([0-9A-F]{5,6})'] * 2) + '$', + + # 12 bytes (bare, no delimiters) + '^(' + ''.join(['[0-9A-F]'] * 12) + ')$', + '^(' + ''.join(['[0-9A-F]'] * 11) + ')$', +) +# For efficiency, each string regexp converted in place to its compiled +# counterpart. +RE_MAC_FORMATS = [_re.compile(_, _re.IGNORECASE) for _ in RE_MAC_FORMATS] + + +def valid_str(addr): + """ + :param addr: An IEEE EUI-48 (MAC) address in string form. + + :return: ``True`` if MAC address string is valid, ``False`` otherwise. + """ + for regexp in RE_MAC_FORMATS: + try: + match_result = regexp.findall(addr) + if len(match_result) != 0: + return True + except TypeError: + pass + + return False + + +def str_to_int(addr): + """ + :param addr: An IEEE EUI-48 (MAC) address in string form. + + :return: An unsigned integer that is equivalent to value represented + by EUI-48/MAC string address formatted according to the dialect + settings. + """ + words = [] + if _is_str(addr): + found_match = False + for regexp in RE_MAC_FORMATS: + match_result = regexp.findall(addr) + if len(match_result) != 0: + found_match = True + if isinstance(match_result[0], tuple): + words = match_result[0] + else: + words = (match_result[0],) + break + if not found_match: + raise AddrFormatError('%r is not a supported MAC format!' % (addr,)) + else: + raise TypeError('%r is not str() or unicode()!' % (addr,)) + + int_val = None + + if len(words) == 6: + # 2 bytes x 6 (UNIX, Windows, EUI-48) + int_val = int(''.join(['%.2x' % int(w, 16) for w in words]), 16) + elif len(words) == 3: + # 4 bytes x 3 (Cisco) + int_val = int(''.join(['%.4x' % int(w, 16) for w in words]), 16) + elif len(words) == 2: + # 6 bytes x 2 (PostgreSQL) + int_val = int(''.join(['%.6x' % int(w, 16) for w in words]), 16) + elif len(words) == 1: + # 12 bytes (bare, no delimiters) + int_val = int('%012x' % int(words[0], 16), 16) + else: + raise AddrFormatError('unexpected word count in MAC address %r!' % (addr,)) + + return int_val + + +def int_to_str(int_val, dialect=None): + """ + :param int_val: An unsigned integer. + + :param dialect: (optional) a Python class defining formatting options. + + :return: An IEEE EUI-48 (MAC) address string that is equivalent to + unsigned integer formatted according to the dialect settings. + """ + if dialect is None: + dialect = mac_eui48 + + words = int_to_words(int_val, dialect) + tokens = [dialect.word_fmt % i for i in words] + addr = dialect.word_sep.join(tokens) + + return addr + + +def int_to_packed(int_val): + """ + :param int_val: the integer to be packed. + + :return: a packed string that is equivalent to value represented by an + unsigned integer. + """ + return _struct.pack(">HI", int_val >> 32, int_val & 0xffffffff) + + +def packed_to_int(packed_int): + """ + :param packed_int: a packed string containing an unsigned integer. + It is assumed that string is packed in network byte order. + + :return: An unsigned integer equivalent to value of network address + represented by packed binary string. + """ + words = list(_struct.unpack('>6B', packed_int)) + + int_val = 0 + for i, num in enumerate(reversed(words)): + word = num + word = word << 8 * i + int_val = int_val | word + + return int_val + + +def valid_words(words, dialect=None): + if dialect is None: + dialect = DEFAULT_DIALECT + return _valid_words(words, dialect.word_size, dialect.num_words) + + +def int_to_words(int_val, dialect=None): + if dialect is None: + dialect = DEFAULT_DIALECT + return _int_to_words(int_val, dialect.word_size, dialect.num_words) + + +def words_to_int(words, dialect=None): + if dialect is None: + dialect = DEFAULT_DIALECT + return _words_to_int(words, dialect.word_size, dialect.num_words) + + +def valid_bits(bits, dialect=None): + if dialect is None: + dialect = DEFAULT_DIALECT + return _valid_bits(bits, width, dialect.word_sep) + + +def bits_to_int(bits, dialect=None): + if dialect is None: + dialect = DEFAULT_DIALECT + return _bits_to_int(bits, width, dialect.word_sep) + + +def int_to_bits(int_val, dialect=None): + if dialect is None: + dialect = DEFAULT_DIALECT + return _int_to_bits( + int_val, dialect.word_size, dialect.num_words, dialect.word_sep) + + +def valid_bin(bin_val, dialect=None): + if dialect is None: + dialect = DEFAULT_DIALECT + return _valid_bin(bin_val, width) + + +def int_to_bin(int_val): + return _int_to_bin(int_val, width) + + +def bin_to_int(bin_val): + return _bin_to_int(bin_val, width) diff --git a/netaddr/strategy/eui64.py b/netaddr/strategy/eui64.py new file mode 100644 index 0000000..9b57ee3 --- /dev/null +++ b/netaddr/strategy/eui64.py @@ -0,0 +1,273 @@ +#----------------------------------------------------------------------------- +# Copyright (c) 2008 by David P. D. Moss. All rights reserved. +# +# Released under the BSD license. See the LICENSE file for details. +#----------------------------------------------------------------------------- +""" +IEEE 64-bit EUI (Extended Unique Identifier) logic. +""" +import struct as _struct +import re as _re + +from netaddr.core import AddrFormatError +from netaddr.strategy import ( + valid_words as _valid_words, int_to_words as _int_to_words, + words_to_int as _words_to_int, valid_bits as _valid_bits, + bits_to_int as _bits_to_int, int_to_bits as _int_to_bits, + valid_bin as _valid_bin, int_to_bin as _int_to_bin, + bin_to_int as _bin_to_int) + + +# This is a fake constant that doesn't really exist. Here for completeness. +AF_EUI64 = 64 + +#: The width (in bits) of this address type. +width = 64 + +#: The AF_* constant value of this address type. +family = AF_EUI64 + +#: A friendly string name address type. +family_name = 'EUI-64' + +#: The version of this address type. +version = 64 + +#: The maximum integer value that can be represented by this address type. +max_int = 2 ** width - 1 + +#----------------------------------------------------------------------------- +# Dialect classes. +#----------------------------------------------------------------------------- + +class eui64_base(object): + """A standard IEEE EUI-64 dialect class.""" + #: The individual word size (in bits) of this address type. + word_size = 8 + + #: The number of words in this address type. + num_words = width // word_size + + #: The maximum integer value for an individual word in this address type. + max_word = 2 ** word_size - 1 + + #: The separator character used between each word. + word_sep = '-' + + #: The format string to be used when converting words to string values. + word_fmt = '%.2X' + + #: The number base to be used when interpreting word values as integers. + word_base = 16 + + +class eui64_unix(eui64_base): + """A UNIX-style MAC address dialect class.""" + word_size = 8 + num_words = width // word_size + word_sep = ':' + word_fmt = '%x' + word_base = 16 + + +class eui64_unix_expanded(eui64_unix): + """A UNIX-style MAC address dialect class with leading zeroes.""" + word_fmt = '%.2x' + + +class eui64_cisco(eui64_base): + """A Cisco 'triple hextet' MAC address dialect class.""" + word_size = 16 + num_words = width // word_size + word_sep = '.' + word_fmt = '%.4x' + word_base = 16 + + +class eui64_bare(eui64_base): + """A bare (no delimiters) MAC address dialect class.""" + word_size = 64 + num_words = width // word_size + word_sep = '' + word_fmt = '%.16X' + word_base = 16 + + +#: The default dialect to be used when not specified by the user. + +DEFAULT_EUI64_DIALECT = eui64_base + +#----------------------------------------------------------------------------- +#: Regular expressions to match all supported MAC address formats. +RE_EUI64_FORMATS = ( + # 2 bytes x 8 (UNIX, Windows, EUI-64) + '^' + ':'.join(['([0-9A-F]{1,2})'] * 8) + '$', + '^' + '-'.join(['([0-9A-F]{1,2})'] * 8) + '$', + + # 4 bytes x 4 (Cisco like) + '^' + ':'.join(['([0-9A-F]{1,4})'] * 4) + '$', + '^' + '-'.join(['([0-9A-F]{1,4})'] * 4) + '$', + '^' + r'\.'.join(['([0-9A-F]{1,4})'] * 4) + '$', + + # 16 bytes (bare, no delimiters) + '^(' + ''.join(['[0-9A-F]'] * 16) + ')$', +) +# For efficiency, each string regexp converted in place to its compiled +# counterpart. +RE_EUI64_FORMATS = [_re.compile(_, _re.IGNORECASE) for _ in RE_EUI64_FORMATS] + + +def _get_match_result(address, formats): + for regexp in formats: + match = regexp.findall(address) + if match: + return match[0] + + +def valid_str(addr): + """ + :param addr: An IEEE EUI-64 identifier in string form. + + :return: ``True`` if EUI-64 identifier is valid, ``False`` otherwise. + """ + try: + if _get_match_result(addr, RE_EUI64_FORMATS): + return True + except TypeError: + pass + + return False + + +def str_to_int(addr): + """ + :param addr: An IEEE EUI-64 identifier in string form. + + :return: An unsigned integer that is equivalent to value represented + by EUI-64 string address formatted according to the dialect + """ + words = [] + + try: + words = _get_match_result(addr, RE_EUI64_FORMATS) + if not words: + raise TypeError + except TypeError: + raise AddrFormatError('invalid IEEE EUI-64 identifier: %r!' % (addr,)) + + if isinstance(words, tuple): + pass + else: + words = (words,) + + if len(words) == 8: + # 2 bytes x 8 (UNIX, Windows, EUI-48) + int_val = int(''.join(['%.2x' % int(w, 16) for w in words]), 16) + elif len(words) == 4: + # 4 bytes x 4 (Cisco like) + int_val = int(''.join(['%.4x' % int(w, 16) for w in words]), 16) + elif len(words) == 1: + # 16 bytes (bare, no delimiters) + int_val = int('%016x' % int(words[0], 16), 16) + else: + raise AddrFormatError( + 'bad word count for EUI-64 identifier: %r!' % addr) + + return int_val + + +def int_to_str(int_val, dialect=None): + """ + :param int_val: An unsigned integer. + + :param dialect: (optional) a Python class defining formatting options + + :return: An IEEE EUI-64 identifier that is equivalent to unsigned integer. + """ + if dialect is None: + dialect = eui64_base + words = int_to_words(int_val, dialect) + tokens = [dialect.word_fmt % i for i in words] + addr = dialect.word_sep.join(tokens) + return addr + + +def int_to_packed(int_val): + """ + :param int_val: the integer to be packed. + + :return: a packed string that is equivalent to value represented by an + unsigned integer. + """ + words = int_to_words(int_val) + return _struct.pack('>8B', *words) + + +def packed_to_int(packed_int): + """ + :param packed_int: a packed string containing an unsigned integer. + It is assumed that string is packed in network byte order. + + :return: An unsigned integer equivalent to value of network address + represented by packed binary string. + """ + words = list(_struct.unpack('>8B', packed_int)) + + int_val = 0 + for i, num in enumerate(reversed(words)): + word = num + word = word << 8 * i + int_val = int_val | word + + return int_val + + +def valid_words(words, dialect=None): + if dialect is None: + dialect = DEFAULT_EUI64_DIALECT + return _valid_words(words, dialect.word_size, dialect.num_words) + + +def int_to_words(int_val, dialect=None): + if dialect is None: + dialect = DEFAULT_EUI64_DIALECT + return _int_to_words(int_val, dialect.word_size, dialect.num_words) + + +def words_to_int(words, dialect=None): + if dialect is None: + dialect = DEFAULT_EUI64_DIALECT + return _words_to_int(words, dialect.word_size, dialect.num_words) + + +def valid_bits(bits, dialect=None): + if dialect is None: + dialect = DEFAULT_EUI64_DIALECT + return _valid_bits(bits, width, dialect.word_sep) + + +def bits_to_int(bits, dialect=None): + if dialect is None: + dialect = DEFAULT_EUI64_DIALECT + return _bits_to_int(bits, width, dialect.word_sep) + + +def int_to_bits(int_val, dialect=None): + if dialect is None: + dialect = DEFAULT_EUI64_DIALECT + return _int_to_bits( + int_val, dialect.word_size, dialect.num_words, dialect.word_sep) + + +def valid_bin(bin_val, dialect=None): + if dialect is None: + dialect = DEFAULT_EUI64_DIALECT + return _valid_bin(bin_val, width) + + +def int_to_bin(int_val): + return _int_to_bin(int_val, width) + + +def bin_to_int(bin_val): + return _bin_to_int(bin_val, width) diff --git a/netaddr/strategy/ipv4.py b/netaddr/strategy/ipv4.py new file mode 100644 index 0000000..f42fa07 --- /dev/null +++ b/netaddr/strategy/ipv4.py @@ -0,0 +1,283 @@ +#----------------------------------------------------------------------------- +# Copyright (c) 2008 by David P. D. Moss. All rights reserved. +# +# Released under the BSD license. See the LICENSE file for details. +#----------------------------------------------------------------------------- +"""IPv4 address logic.""" + +import sys as _sys +import struct as _struct + +from socket import inet_aton as _inet_aton +# Check whether we need to use fallback code or not. +if _sys.platform in ('win32', 'cygwin'): + # inet_pton() not available on Windows. inet_pton() under cygwin + # behaves exactly like inet_aton() and is therefore highly unreliable. + from netaddr.fbsocket import inet_pton as _inet_pton, AF_INET +else: + # All other cases, use all functions from the socket module. + from socket import inet_pton as _inet_pton, AF_INET + +from netaddr.core import AddrFormatError, ZEROFILL, INET_PTON + +from netaddr.strategy import ( + valid_words as _valid_words, valid_bits as _valid_bits, + bits_to_int as _bits_to_int, int_to_bits as _int_to_bits, + valid_bin as _valid_bin, int_to_bin as _int_to_bin, + bin_to_int as _bin_to_int) + +from netaddr.compat import _str_type + +#: The width (in bits) of this address type. +width = 32 + +#: The individual word size (in bits) of this address type. +word_size = 8 + +#: The format string to be used when converting words to string values. +word_fmt = '%d' + +#: The separator character used between each word. +word_sep = '.' + +#: The AF_* constant value of this address type. +family = AF_INET + +#: A friendly string name address type. +family_name = 'IPv4' + +#: The version of this address type. +version = 4 + +#: The number base to be used when interpreting word values as integers. +word_base = 10 + +#: The maximum integer value that can be represented by this address type. +max_int = 2 ** width - 1 + +#: The number of words in this address type. +num_words = width // word_size + +#: The maximum integer value for an individual word in this address type. +max_word = 2 ** word_size - 1 + +#: A dictionary mapping IPv4 CIDR prefixes to the equivalent netmasks. +prefix_to_netmask = dict( + [(i, max_int ^ (2 ** (width - i) - 1)) for i in range(0, width + 1)]) + +#: A dictionary mapping IPv4 netmasks to their equivalent CIDR prefixes. +netmask_to_prefix = dict( + [(max_int ^ (2 ** (width - i) - 1), i) for i in range(0, width + 1)]) + +#: A dictionary mapping IPv4 CIDR prefixes to the equivalent hostmasks. +prefix_to_hostmask = dict( + [(i, (2 ** (width - i) - 1)) for i in range(0, width + 1)]) + +#: A dictionary mapping IPv4 hostmasks to their equivalent CIDR prefixes. +hostmask_to_prefix = dict( + [((2 ** (width - i) - 1), i) for i in range(0, width + 1)]) + + +def valid_str(addr, flags=0): + """ + :param addr: An IPv4 address in presentation (string) format. + + :param flags: decides which rules are applied to the interpretation of the + addr value. Supported constants are INET_PTON and ZEROFILL. See the + :class:`IPAddress` documentation for details. + + .. versionchanged:: 0.10.1 + ``flags`` is scheduled to default to :data:`INET_PTON` instead of :data:`INET_ATON` + in the future. + + :return: ``True`` if IPv4 address is valid, ``False`` otherwise. + """ + if addr == '': + raise AddrFormatError('Empty strings are not supported!') + + validity = True + + if flags & ZEROFILL: + addr = '.'.join(['%d' % int(i) for i in addr.split('.')]) + + try: + if flags & INET_PTON: + _inet_pton(AF_INET, addr) + else: + _inet_aton(addr) + except Exception: + validity = False + + return validity + + +def str_to_int(addr, flags=0): + """ + :param addr: An IPv4 dotted decimal address in string form. + + :param flags: decides which rules are applied to the interpretation of the + addr value. Supported constants are INET_PTON and ZEROFILL. See the + :class:`IPAddress` documentation for details. + + :return: The equivalent unsigned integer for a given IPv4 address. + """ + if flags & ZEROFILL: + addr = '.'.join(['%d' % int(i) for i in addr.split('.')]) + + try: + if flags & INET_PTON: + return _struct.unpack('>I', _inet_pton(AF_INET, addr))[0] + else: + return _struct.unpack('>I', _inet_aton(addr))[0] + except Exception: + raise AddrFormatError('%r is not a valid IPv4 address string!' % (addr,)) + + +def int_to_str(int_val, dialect=None): + """ + :param int_val: An unsigned integer. + + :param dialect: (unused) Any value passed in is ignored. + + :return: The IPv4 presentation (string) format address equivalent to the + unsigned integer provided. + """ + if 0 <= int_val <= max_int: + return '%d.%d.%d.%d' % ( + int_val >> 24, + (int_val >> 16) & 0xff, + (int_val >> 8) & 0xff, + int_val & 0xff) + else: + raise ValueError('%r is not a valid 32-bit unsigned integer!' % (int_val,)) + + +def int_to_arpa(int_val): + """ + :param int_val: An unsigned integer. + + :return: The reverse DNS lookup for an IPv4 address in network byte + order integer form. + """ + words = ["%d" % i for i in int_to_words(int_val)] + words.reverse() + words.extend(['in-addr', 'arpa', '']) + return '.'.join(words) + + +def int_to_packed(int_val): + """ + :param int_val: the integer to be packed. + + :return: a packed string that is equivalent to value represented by an + unsigned integer. + """ + return _struct.pack('>I', int_val) + + +def packed_to_int(packed_int): + """ + :param packed_int: a packed string containing an unsigned integer. + It is assumed that string is packed in network byte order. + + :return: An unsigned integer equivalent to value of network address + represented by packed binary string. + """ + return _struct.unpack('>I', packed_int)[0] + + +def valid_words(words): + return _valid_words(words, word_size, num_words) + + +def int_to_words(int_val): + """ + :param int_val: An unsigned integer. + + :return: An integer word (octet) sequence that is equivalent to value + represented by an unsigned integer. + """ + if not 0 <= int_val <= max_int: + raise ValueError('%r is not a valid integer value supported by' + 'this address type!' % (int_val,)) + return ( int_val >> 24, + (int_val >> 16) & 0xff, + (int_val >> 8) & 0xff, + int_val & 0xff) + + +def words_to_int(words): + """ + :param words: A list or tuple containing integer octets. + + :return: An unsigned integer that is equivalent to value represented + by word (octet) sequence. + """ + if not valid_words(words): + raise ValueError('%r is not a valid octet list for an IPv4 address!' % (words,)) + return _struct.unpack('>I', _struct.pack('4B', *words))[0] + + +def valid_bits(bits): + return _valid_bits(bits, width, word_sep) + + +def bits_to_int(bits): + return _bits_to_int(bits, width, word_sep) + + +def int_to_bits(int_val, word_sep=None): + if word_sep is None: + word_sep = globals()['word_sep'] + return _int_to_bits(int_val, word_size, num_words, word_sep) + + +def valid_bin(bin_val): + return _valid_bin(bin_val, width) + + +def int_to_bin(int_val): + return _int_to_bin(int_val, width) + + +def bin_to_int(bin_val): + return _bin_to_int(bin_val, width) + + +def expand_partial_address(addr): + """ + Expands a partial IPv4 address into a full 4-octet version. + + :param addr: an partial or abbreviated IPv4 address + + :return: an expanded IP address in presentation format (x.x.x.x) + + """ + tokens = [] + + error = AddrFormatError('invalid partial IPv4 address: %r!' % addr) + + if isinstance(addr, _str_type): + if ':' in addr: + # Ignore IPv6 ... + raise error + + try: + if '.' in addr: + tokens = ['%d' % int(o) for o in addr.split('.')] + else: + tokens = ['%d' % int(addr)] + except ValueError: + raise error + + if 1 <= len(tokens) <= 4: + for i in range(4 - len(tokens)): + tokens.append('0') + else: + raise error + + if not tokens: + raise error + + return '%s.%s.%s.%s' % tuple(tokens) + diff --git a/netaddr/strategy/ipv6.py b/netaddr/strategy/ipv6.py new file mode 100644 index 0000000..de2a935 --- /dev/null +++ b/netaddr/strategy/ipv6.py @@ -0,0 +1,259 @@ +#----------------------------------------------------------------------------- +# Copyright (c) 2008 by David P. D. Moss. All rights reserved. +# +# Released under the BSD license. See the LICENSE file for details. +#----------------------------------------------------------------------------- +""" +IPv6 address logic. +""" +import struct as _struct + +OPT_IMPORTS = False + +# Check whether we need to use fallback code or not. +try: + import socket as _socket + # These might all generate exceptions on different platforms. + if not _socket.has_ipv6: + raise Exception('IPv6 disabled') + _socket.inet_pton + _socket.AF_INET6 + from _socket import (inet_pton as _inet_pton, inet_ntop as _inet_ntop, + AF_INET6) + OPT_IMPORTS = True +except Exception: + from netaddr.fbsocket import (inet_pton as _inet_pton, inet_ntop as _inet_ntop, + AF_INET6) + +from netaddr.core import AddrFormatError +from netaddr.strategy import ( + valid_words as _valid_words, int_to_words as _int_to_words, + words_to_int as _words_to_int, valid_bits as _valid_bits, + bits_to_int as _bits_to_int, int_to_bits as _int_to_bits, + valid_bin as _valid_bin, int_to_bin as _int_to_bin, + bin_to_int as _bin_to_int) + +#: The width (in bits) of this address type. +width = 128 + +#: The individual word size (in bits) of this address type. +word_size = 16 + +#: The separator character used between each word. +word_sep = ':' + +#: The AF_* constant value of this address type. +family = AF_INET6 + +#: A friendly string name address type. +family_name = 'IPv6' + +#: The version of this address type. +version = 6 + +#: The number base to be used when interpreting word values as integers. +word_base = 16 + +#: The maximum integer value that can be represented by this address type. +max_int = 2 ** width - 1 + +#: The number of words in this address type. +num_words = width // word_size + +#: The maximum integer value for an individual word in this address type. +max_word = 2 ** word_size - 1 + +#: A dictionary mapping IPv6 CIDR prefixes to the equivalent netmasks. +prefix_to_netmask = dict( + [(i, max_int ^ (2 ** (width - i) - 1)) for i in range(0, width+1)]) + +#: A dictionary mapping IPv6 netmasks to their equivalent CIDR prefixes. +netmask_to_prefix = dict( + [(max_int ^ (2 ** (width - i) - 1), i) for i in range(0, width+1)]) + +#: A dictionary mapping IPv6 CIDR prefixes to the equivalent hostmasks. +prefix_to_hostmask = dict( + [(i, (2 ** (width - i) - 1)) for i in range(0, width+1)]) + +#: A dictionary mapping IPv6 hostmasks to their equivalent CIDR prefixes. +hostmask_to_prefix = dict( + [((2 ** (width - i) - 1), i) for i in range(0, width+1)]) + +#----------------------------------------------------------------------------- +# Dialect classes. +#----------------------------------------------------------------------------- + +class ipv6_compact(object): + """An IPv6 dialect class - compact form.""" + #: The format string used to converting words into string values. + word_fmt = '%x' + + #: Boolean flag indicating if IPv6 compaction algorithm should be used. + compact = True + +class ipv6_full(ipv6_compact): + """An IPv6 dialect class - 'all zeroes' form.""" + + #: Boolean flag indicating if IPv6 compaction algorithm should be used. + compact = False + +class ipv6_verbose(ipv6_compact): + """An IPv6 dialect class - extra wide 'all zeroes' form.""" + + #: The format string used to converting words into string values. + word_fmt = '%.4x' + + #: Boolean flag indicating if IPv6 compaction algorithm should be used. + compact = False + + +def valid_str(addr, flags=0): + """ + :param addr: An IPv6 address in presentation (string) format. + + :param flags: decides which rules are applied to the interpretation of the + addr value. Future use - currently has no effect. + + :return: ``True`` if IPv6 address is valid, ``False`` otherwise. + """ + if addr == '': + raise AddrFormatError('Empty strings are not supported!') + + try: + _inet_pton(AF_INET6, addr) + except: + return False + return True + + +def str_to_int(addr, flags=0): + """ + :param addr: An IPv6 address in string form. + + :param flags: decides which rules are applied to the interpretation of the + addr value. Future use - currently has no effect. + + :return: The equivalent unsigned integer for a given IPv6 address. + """ + try: + packed_int = _inet_pton(AF_INET6, addr) + return packed_to_int(packed_int) + except Exception: + raise AddrFormatError('%r is not a valid IPv6 address string!' % (addr,)) + + +def int_to_str(int_val, dialect=None): + """ + :param int_val: An unsigned integer. + + :param dialect: (optional) a Python class defining formatting options. + + :return: The IPv6 presentation (string) format address equivalent to the + unsigned integer provided. + """ + if dialect is None: + dialect = ipv6_compact + + addr = None + + try: + packed_int = int_to_packed(int_val) + if dialect.compact: + # Default return value. + addr = _inet_ntop(AF_INET6, packed_int) + else: + # Custom return value. + words = list(_struct.unpack('>8H', packed_int)) + tokens = [dialect.word_fmt % word for word in words] + addr = word_sep.join(tokens) + except Exception: + raise ValueError('%r is not a valid 128-bit unsigned integer!' % (int_val,)) + + return addr + + +def int_to_arpa(int_val): + """ + :param int_val: An unsigned integer. + + :return: The reverse DNS lookup for an IPv6 address in network byte + order integer form. + """ + addr = int_to_str(int_val, ipv6_verbose) + tokens = list(addr.replace(':', '')) + tokens.reverse() + # We won't support ip6.int here - see RFC 3152 for details. + tokens = tokens + ['ip6', 'arpa', ''] + return '.'.join(tokens) + + +def int_to_packed(int_val): + """ + :param int_val: the integer to be packed. + + :return: a packed string that is equivalent to value represented by an + unsigned integer. + """ + words = int_to_words(int_val, 4, 32) + return _struct.pack('>4I', *words) + + +def packed_to_int(packed_int): + """ + :param packed_int: a packed string containing an unsigned integer. + It is assumed that string is packed in network byte order. + + :return: An unsigned integer equivalent to value of network address + represented by packed binary string. + """ + words = list(_struct.unpack('>4I', packed_int)) + + int_val = 0 + for i, num in enumerate(reversed(words)): + word = num + word = word << 32 * i + int_val = int_val | word + + return int_val + + +def valid_words(words): + return _valid_words(words, word_size, num_words) + + +def int_to_words(int_val, num_words=None, word_size=None): + if num_words is None: + num_words = globals()['num_words'] + if word_size is None: + word_size = globals()['word_size'] + return _int_to_words(int_val, word_size, num_words) + + +def words_to_int(words): + return _words_to_int(words, word_size, num_words) + + +def valid_bits(bits): + return _valid_bits(bits, width, word_sep) + + +def bits_to_int(bits): + return _bits_to_int(bits, width, word_sep) + + +def int_to_bits(int_val, word_sep=None): + if word_sep is None: + word_sep = globals()['word_sep'] + return _int_to_bits(int_val, word_size, num_words, word_sep) + + +def valid_bin(bin_val): + return _valid_bin(bin_val, width) + + +def int_to_bin(int_val): + return _int_to_bin(int_val, width) + + +def bin_to_int(bin_val): + return _bin_to_int(bin_val, width) |