#----------------------------------------------------------------------------- # Copyright (c) 2008-2012, 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 import socket as _socket # 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 _socket import inet_aton as _inet_aton, inet_ntoa as _inet_ntoa from netaddr.fbsocket import inet_pton as _inet_pton, AF_INET else: # All other cases, attempt to use all functions from the socket module. try: # A common bug on older implementations of the socket module. _socket.inet_aton('255.255.255.255') from _socket import inet_aton as _inet_aton, inet_ntoa as _inet_ntoa, \ inet_pton as _inet_pton, AF_INET except: # Use the fallback socket code. from netaddr.fbsocket import inet_aton as _inet_aton, \ inet_ntoa as _inet_ntoa, \ 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 netaddr.core docs for details. :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: 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 netaddr.core docs 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: 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 if '.' in addr: tokens = ['%d' % int(o) for o in addr.split('.')] else: try: 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)