#----------------------------------------------------------------------------- # Copyright (c) 2008-2015, 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)