Adding network scanning
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2008-2012, David P. D. Moss. All rights reserved.
|
||||
#
|
||||
# Released under the BSD license. See the LICENSE file for details.
|
||||
#-----------------------------------------------------------------------------
|
||||
"""A Python library for manipulating IP and EUI network addresses."""
|
||||
|
||||
#: Version info (major, minor, maintenance, status)
|
||||
VERSION = (0, 7, 10)
|
||||
STATUS = ''
|
||||
__version__ = '%d.%d.%d' % VERSION[0:3] + STATUS
|
||||
|
||||
import sys as _sys
|
||||
|
||||
if _sys.version_info[0:2] < (2, 4):
|
||||
raise RuntimeError('Python 2.4.x or higher is required!')
|
||||
|
||||
from netaddr.core import AddrConversionError, AddrFormatError, \
|
||||
NotRegisteredError, ZEROFILL, Z, INET_PTON, P, NOHOST, N
|
||||
|
||||
from netaddr.ip import IPAddress, IPNetwork, IPRange, all_matching_cidrs, \
|
||||
cidr_abbrev_to_verbose, cidr_exclude, cidr_merge, iprange_to_cidrs, \
|
||||
iter_iprange, iter_unique_ips, largest_matching_cidr, \
|
||||
smallest_matching_cidr, spanning_cidr
|
||||
|
||||
from netaddr.ip.sets import IPSet
|
||||
|
||||
from netaddr.ip.glob import IPGlob, cidr_to_glob, glob_to_cidrs, \
|
||||
glob_to_iprange, glob_to_iptuple, iprange_to_globs, valid_glob
|
||||
|
||||
from netaddr.ip.nmap import valid_nmap_range, iter_nmap_range
|
||||
|
||||
from netaddr.ip.rfc1924 import base85_to_ipv6, ipv6_to_base85
|
||||
|
||||
from netaddr.eui import EUI, IAB, OUI
|
||||
|
||||
from netaddr.strategy.ipv4 import valid_str as valid_ipv4
|
||||
|
||||
from netaddr.strategy.ipv6 import valid_str as valid_ipv6, ipv6_compact, \
|
||||
ipv6_full, ipv6_verbose
|
||||
|
||||
from netaddr.strategy.eui48 import mac_eui48, mac_unix, mac_cisco, \
|
||||
mac_bare, mac_pgsql, valid_str as valid_mac
|
||||
|
||||
__all__ = [
|
||||
# Constants.
|
||||
'ZEROFILL', 'Z', 'INET_PTON', 'P', 'NOHOST', 'N',
|
||||
|
||||
# Custom Exceptions.
|
||||
'AddrConversionError', 'AddrFormatError', 'NotRegisteredError',
|
||||
|
||||
# IP classes.
|
||||
'IPAddress', 'IPNetwork', 'IPRange', 'IPSet',
|
||||
|
||||
# IPv6 dialect classes.
|
||||
'ipv6_compact', 'ipv6_full', 'ipv6_verbose',
|
||||
|
||||
# IP functions and generators.
|
||||
'all_matching_cidrs', 'cidr_abbrev_to_verbose', 'cidr_exclude',
|
||||
'cidr_merge', 'iprange_to_cidrs', 'iter_iprange', 'iter_unique_ips',
|
||||
'largest_matching_cidr', 'smallest_matching_cidr', 'spanning_cidr',
|
||||
|
||||
# IP globbing class.
|
||||
'IPGlob',
|
||||
|
||||
# IP globbing functions.
|
||||
'cidr_to_glob', 'glob_to_cidrs', 'glob_to_iprange', 'glob_to_iptuple',
|
||||
'iprange_to_globs',
|
||||
|
||||
# IEEE EUI classes.
|
||||
'EUI', 'IAB', 'OUI',
|
||||
|
||||
# EUI-48 (MAC) dialect classes.
|
||||
'mac_bare', 'mac_cisco', 'mac_eui48', 'mac_pgsql', 'mac_unix',
|
||||
|
||||
# Validation functions.
|
||||
'valid_ipv4', 'valid_ipv6', 'valid_glob', 'valid_mac',
|
||||
|
||||
# nmap-style range functions.
|
||||
'valid_nmap_range', 'iter_nmap_range',
|
||||
|
||||
# RFC 1924 functions.
|
||||
'base85_to_ipv6', 'ipv6_to_base85',
|
||||
]
|
||||
@@ -0,0 +1,93 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2008-2012, David P. D. Moss. All rights reserved.
|
||||
#
|
||||
# Released under the BSD license. See the LICENSE file for details.
|
||||
#-----------------------------------------------------------------------------
|
||||
"""
|
||||
Compatibility wrappers providing uniform behaviour for Python code required to
|
||||
run under both Python 2.x and 3.x.
|
||||
|
||||
All operations emulate 2.x behaviour where applicable.
|
||||
"""
|
||||
import sys as _sys
|
||||
|
||||
if _sys.version_info[0] == 3:
|
||||
# Python 3.x specific logic.
|
||||
_sys_maxint = _sys.maxsize
|
||||
|
||||
_int_type = int
|
||||
|
||||
_str_type = str
|
||||
|
||||
_is_str = lambda x: isinstance(x, (str, type(''.encode())))
|
||||
|
||||
_is_int = lambda x: isinstance(x, int)
|
||||
|
||||
_callable = lambda x: hasattr(x, '__call__')
|
||||
|
||||
_func_doc = lambda x: x.__doc__
|
||||
|
||||
_dict_keys = lambda x: list(x.keys())
|
||||
|
||||
_dict_items = lambda x: list(x.items())
|
||||
|
||||
_iter_dict_keys = lambda x: x.keys()
|
||||
|
||||
def _bytes_join(*args): return ''.encode().join(*args)
|
||||
|
||||
def _zip(*args): return list(zip(*args))
|
||||
|
||||
def _range(*args, **kwargs): return list(range(*args, **kwargs))
|
||||
|
||||
_iter_range = range
|
||||
|
||||
def _func_name(f, name=None):
|
||||
if name is not None: f.__name__ = name
|
||||
else: return f.__name__
|
||||
|
||||
def _func_doc(f, docstring=None):
|
||||
if docstring is not None: f.__doc__ = docstring
|
||||
else: return f.__doc__
|
||||
|
||||
elif _sys.version_info[0:2] > [2, 3]:
|
||||
# Python 2.4 or higher.
|
||||
_sys_maxint = _sys.maxint
|
||||
|
||||
_int_type = (int, long)
|
||||
|
||||
_str_type = (str, unicode)
|
||||
|
||||
# NB - not using basestring here for maximum 2.x compatibility.
|
||||
_is_str = lambda x: isinstance(x, (str, unicode))
|
||||
|
||||
_is_int = lambda x: isinstance(x, (int, long))
|
||||
|
||||
_callable = lambda x: callable(x)
|
||||
|
||||
_dict_keys = lambda x: x.keys()
|
||||
|
||||
_dict_items = lambda x: x.items()
|
||||
|
||||
_iter_dict_keys = lambda x: iter(x.keys())
|
||||
|
||||
def _bytes_join(*args): return ''.join(*args)
|
||||
|
||||
def _zip(*args): return zip(*args)
|
||||
|
||||
def _range(*args, **kwargs): return range(*args, **kwargs)
|
||||
|
||||
_iter_range = xrange
|
||||
|
||||
def _func_name(f, name=None):
|
||||
if name is not None: f.func_name = name
|
||||
else: return f.func_name
|
||||
|
||||
def _func_doc(f, docstring=None):
|
||||
if docstring is not None: f.func_doc = docstring
|
||||
else: return f.func_doc
|
||||
|
||||
else:
|
||||
# Unsupported versions.
|
||||
raise RuntimeError(
|
||||
'this module only supports Python 2.4.x or higher (including 3.x)!')
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2008-2012, David P. D. Moss. All rights reserved.
|
||||
#
|
||||
# Released under the BSD license. See the LICENSE file for details.
|
||||
#-----------------------------------------------------------------------------
|
||||
"""Common code shared between various netaddr sub modules"""
|
||||
|
||||
import sys as _sys
|
||||
import struct as _struct
|
||||
import pprint as _pprint
|
||||
|
||||
from netaddr.compat import _callable, _iter_dict_keys
|
||||
|
||||
#: True if platform is natively big endian, False otherwise.
|
||||
BIG_ENDIAN_PLATFORM = _sys.byteorder == 'big'
|
||||
|
||||
#: Use inet_pton() semantics instead of inet_aton() when parsing IPv4.
|
||||
P = INET_PTON = 1
|
||||
|
||||
#: Remove any preceding zeros from IPv4 address octets before parsing.
|
||||
Z = ZEROFILL = 2
|
||||
|
||||
#: Remove any host bits found to the right of an applied CIDR prefix.
|
||||
N = NOHOST = 4
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Custom exceptions.
|
||||
#-----------------------------------------------------------------------------
|
||||
class AddrFormatError(Exception):
|
||||
"""
|
||||
An Exception indicating a network address is not correctly formatted.
|
||||
"""
|
||||
pass
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class AddrConversionError(Exception):
|
||||
"""
|
||||
An Exception indicating a failure to convert between address types or
|
||||
notations.
|
||||
"""
|
||||
pass
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class NotRegisteredError(Exception):
|
||||
"""
|
||||
An Exception indicating that an OUI or IAB was not found in the IEEE
|
||||
Registry.
|
||||
"""
|
||||
pass
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def num_bits(int_val):
|
||||
"""
|
||||
:param int_val: an unsigned integer.
|
||||
|
||||
:return: the minimum number of bits needed to represent value provided.
|
||||
"""
|
||||
int_val = abs(int_val)
|
||||
numbits = 0
|
||||
while int_val:
|
||||
numbits += 1
|
||||
int_val >>= 1
|
||||
return numbits
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class Subscriber(object):
|
||||
"""
|
||||
An abstract class defining the interface expected by a Publisher.
|
||||
"""
|
||||
def update(self, data):
|
||||
"""
|
||||
A callback method used by a Publisher to notify this Subscriber about
|
||||
updates.
|
||||
|
||||
:param data: a Python object containing data provided by Publisher.
|
||||
"""
|
||||
raise NotImplementedError('cannot invoke virtual method!')
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class PrettyPrinter(Subscriber):
|
||||
"""
|
||||
A concrete Subscriber that employs the pprint in the standard library to
|
||||
format all data from updates received, writing them to a file-like
|
||||
object.
|
||||
|
||||
Useful as a debugging aid.
|
||||
"""
|
||||
def __init__(self, fh=_sys.stdout, write_eol=True):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
:param fh: a file-like object to write updates to.
|
||||
Default: sys.stdout.
|
||||
|
||||
|
||||
:param write_eol: if ``True`` this object will write newlines to
|
||||
output, if ``False`` it will not.
|
||||
"""
|
||||
self.fh = fh
|
||||
self.write_eol = write_eol
|
||||
|
||||
def update(self, data):
|
||||
"""
|
||||
A callback method used by a Publisher to notify this Subscriber about
|
||||
updates.
|
||||
|
||||
:param data: a Python object containing data provided by Publisher.
|
||||
"""
|
||||
self.fh.write(_pprint.pformat(data))
|
||||
if self.write_eol:
|
||||
self.fh.write("\n")
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class Publisher(object):
|
||||
"""
|
||||
A 'push' Publisher that maintains a list of Subscriber objects notifying
|
||||
them of state changes by passing them update data when it encounter events
|
||||
of interest.
|
||||
"""
|
||||
def __init__(self):
|
||||
"""Constructor"""
|
||||
self.subscribers = []
|
||||
|
||||
def attach(self, subscriber):
|
||||
"""
|
||||
Add a new subscriber.
|
||||
|
||||
:param subscriber: a new object that implements the Subscriber object
|
||||
interface.
|
||||
"""
|
||||
if hasattr(subscriber, 'update') and \
|
||||
_callable(eval('subscriber.update')):
|
||||
if subscriber not in self.subscribers:
|
||||
self.subscribers.append(subscriber)
|
||||
else:
|
||||
raise TypeError('%r does not support required interface!' \
|
||||
% subscriber)
|
||||
|
||||
def detach(self, subscriber):
|
||||
"""
|
||||
Remove an existing subscriber.
|
||||
|
||||
:param subscriber: a new object that implements the Subscriber object
|
||||
interface.
|
||||
"""
|
||||
try:
|
||||
self.subscribers.remove(subscriber)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def notify(self, data):
|
||||
"""
|
||||
Send update data to to all registered Subscribers.
|
||||
|
||||
:param data: the data to be passed to each registered Subscriber.
|
||||
"""
|
||||
for subscriber in self.subscribers:
|
||||
subscriber.update(data)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class DictDotLookup(object):
|
||||
"""
|
||||
Creates objects that behave much like a dictionaries, but allow nested
|
||||
key access using object '.' (dot) lookups.
|
||||
|
||||
Recipe 576586: Dot-style nested lookups over dictionary based data
|
||||
structures - http://code.activestate.com/recipes/576586/
|
||||
|
||||
"""
|
||||
def __init__(self, d):
|
||||
for k in d:
|
||||
if isinstance(d[k], dict):
|
||||
self.__dict__[k] = DictDotLookup(d[k])
|
||||
elif isinstance(d[k], (list, tuple)):
|
||||
l = []
|
||||
for v in d[k]:
|
||||
if isinstance(v, dict):
|
||||
l.append(DictDotLookup(v))
|
||||
else:
|
||||
l.append(v)
|
||||
self.__dict__[k] = l
|
||||
else:
|
||||
self.__dict__[k] = d[k]
|
||||
|
||||
def __getitem__(self, name):
|
||||
if name in self.__dict__:
|
||||
return self.__dict__[name]
|
||||
|
||||
def __iter__(self):
|
||||
return _iter_dict_keys(self.__dict__)
|
||||
|
||||
def __repr__(self):
|
||||
return _pprint.pformat(self.__dict__)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def dos2unix(filename):
|
||||
"""
|
||||
Replace DOS line endings (CRLF) with UNIX line endings (LF) in file.
|
||||
|
||||
"""
|
||||
fh = open(filename, "rb")
|
||||
data = fh.read()
|
||||
fh.close()
|
||||
|
||||
if '\0' in data:
|
||||
raise ValueError('file contains binary data: %s!' % filename)
|
||||
|
||||
newdata = data.replace("\r\n".encode(), "\n".encode())
|
||||
if newdata != data:
|
||||
f = open(filename, "wb")
|
||||
f.write(newdata)
|
||||
f.close()
|
||||
@@ -0,0 +1,691 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2008-2012, David P. D. Moss. All rights reserved.
|
||||
#
|
||||
# Released under the BSD license. See the LICENSE file for details.
|
||||
#-----------------------------------------------------------------------------
|
||||
"""
|
||||
Classes and functions for dealing with MAC addresses, EUI-48, EUI-64, OUI, IAB
|
||||
identifiers.
|
||||
"""
|
||||
|
||||
import sys as _sys
|
||||
import os as _os
|
||||
import os.path as _path
|
||||
import re as _re
|
||||
import csv as _csv
|
||||
|
||||
import pprint as _pprint
|
||||
|
||||
from netaddr.core import NotRegisteredError, AddrFormatError, \
|
||||
AddrConversionError, Subscriber, Publisher, DictDotLookup
|
||||
from netaddr.strategy import eui48 as _eui48, eui64 as _eui64
|
||||
from netaddr.strategy.eui48 import mac_eui48
|
||||
from netaddr.ip import IPAddress
|
||||
|
||||
from netaddr.compat import _is_int, _is_str
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class BaseIdentifier(object):
|
||||
"""Base class for all IEEE identifiers."""
|
||||
__slots__ = ('_value',)
|
||||
|
||||
def __init__(self):
|
||||
self._value = None
|
||||
|
||||
def __int__(self):
|
||||
""":return: integer value of this identifier"""
|
||||
return self._value
|
||||
|
||||
def __long__(self):
|
||||
""":return: integer value of this identifier"""
|
||||
return self._value
|
||||
|
||||
def __oct__(self):
|
||||
""":return: octal string representation of this identifier."""
|
||||
# Python 2.x only.
|
||||
if self._value == 0:
|
||||
return '0'
|
||||
return '0%o' % self._value
|
||||
|
||||
def __hex__(self):
|
||||
""":return: hexadecimal string representation of this identifier."""
|
||||
# Python 2.x only.
|
||||
return '0x%x' % self._value
|
||||
|
||||
def __index__(self):
|
||||
"""
|
||||
:return: return the integer value of this identifier when passed to
|
||||
hex(), oct() or bin().
|
||||
"""
|
||||
# Python 3.x only.
|
||||
return self._value
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
:return: ``True`` if this BaseIdentifier object is numerically the
|
||||
same as other, ``False`` otherwise.
|
||||
"""
|
||||
try:
|
||||
return (self.__class__, self._value) == (other.__class__, other._value)
|
||||
except AttributeError:
|
||||
return NotImplemented
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class OUI(BaseIdentifier):
|
||||
"""
|
||||
An individual IEEE OUI (Organisationally Unique Identifier).
|
||||
|
||||
For online details see - http://standards.ieee.org/regauth/oui/
|
||||
|
||||
"""
|
||||
__slots__ = ('records',)
|
||||
|
||||
def __init__(self, oui):
|
||||
"""
|
||||
Constructor
|
||||
|
||||
:param oui: an OUI string ``XX-XX-XX`` or an unsigned integer. \
|
||||
Also accepts and parses full MAC/EUI-48 address strings (but not \
|
||||
MAC/EUI-48 integers)!
|
||||
"""
|
||||
super(OUI, self).__init__()
|
||||
|
||||
# Lazy loading of IEEE data structures.
|
||||
from netaddr.eui import ieee
|
||||
|
||||
self.records = []
|
||||
|
||||
if isinstance(oui, str):
|
||||
#TODO: Improve string parsing here.
|
||||
#TODO: Accept full MAC/EUI-48 addressses as well as XX-XX-XX
|
||||
#TODO: and just take /16 (see IAB for details)
|
||||
self._value = int(oui.replace('-', ''), 16)
|
||||
elif _is_int(oui):
|
||||
if 0 <= oui <= 0xffffff:
|
||||
self._value = oui
|
||||
else:
|
||||
raise ValueError('OUI int outside expected range: %r' % oui)
|
||||
else:
|
||||
raise TypeError('unexpected OUI format: %r' % oui)
|
||||
|
||||
# Discover offsets.
|
||||
if self._value in ieee.OUI_INDEX:
|
||||
fh = open(ieee.OUI_REGISTRY)
|
||||
for (offset, size) in ieee.OUI_INDEX[self._value]:
|
||||
fh.seek(offset)
|
||||
data = fh.read(size)
|
||||
self._parse_data(data, offset, size)
|
||||
fh.close()
|
||||
else:
|
||||
raise NotRegisteredError('OUI %r not registered!' % oui)
|
||||
|
||||
def __getstate__(self):
|
||||
""":returns: Pickled state of an `OUI` object."""
|
||||
return self._value, self.records
|
||||
|
||||
def __setstate__(self, state):
|
||||
""":param state: data used to unpickle a pickled `OUI` object."""
|
||||
self._value, self.records = state
|
||||
|
||||
def _parse_data(self, data, offset, size):
|
||||
"""Returns a dict record from raw OUI record data"""
|
||||
record = {
|
||||
'idx': 0,
|
||||
'oui': '',
|
||||
'org': '',
|
||||
'address' : [],
|
||||
'offset': offset,
|
||||
'size': size,
|
||||
}
|
||||
|
||||
for line in data.split("\n"):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
if '(hex)' in line:
|
||||
record['idx'] = self._value
|
||||
record['org'] = ' '.join(line.split()[2:])
|
||||
record['oui'] = str(self)
|
||||
elif '(base 16)' in line:
|
||||
continue
|
||||
else:
|
||||
record['address'].append(line)
|
||||
|
||||
self.records.append(record)
|
||||
|
||||
@property
|
||||
def reg_count(self):
|
||||
"""Number of registered organisations with this OUI"""
|
||||
return len(self.records)
|
||||
|
||||
def registration(self, index=0):
|
||||
"""
|
||||
The IEEE registration details for this OUI.
|
||||
|
||||
:param index: the index of record (may contain multiple registrations)
|
||||
(Default: 0 - first registration)
|
||||
|
||||
:return: Objectified Python data structure containing registration
|
||||
details.
|
||||
"""
|
||||
return DictDotLookup(self.records[index])
|
||||
|
||||
def __str__(self):
|
||||
""":return: string representation of this OUI"""
|
||||
int_val = self._value
|
||||
words = []
|
||||
for _ in range(3):
|
||||
word = int_val & 0xff
|
||||
words.append('%02x' % word)
|
||||
int_val >>= 8
|
||||
return '-'.join(reversed(words)).upper()
|
||||
|
||||
def __repr__(self):
|
||||
""":return: executable Python string to recreate equivalent object."""
|
||||
return "OUI('%s')" % self
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class IAB(BaseIdentifier):
|
||||
"""
|
||||
An individual IEEE IAB (Individual Address Block) identifier.
|
||||
|
||||
For online details see - http://standards.ieee.org/regauth/oui/
|
||||
|
||||
"""
|
||||
__slots__ = ('record',)
|
||||
|
||||
@staticmethod
|
||||
def split_iab_mac(eui_int, strict=False):
|
||||
"""
|
||||
:param eui_int: a MAC IAB as an unsigned integer.
|
||||
|
||||
:param strict: If True, raises a ValueError if the last 12 bits of
|
||||
IAB MAC/EUI-48 address are non-zero, ignores them otherwise.
|
||||
(Default: False)
|
||||
"""
|
||||
if 0x50c2000 <= eui_int <= 0x50c2fff:
|
||||
return eui_int, 0
|
||||
|
||||
user_mask = 2 ** 12 - 1
|
||||
iab_mask = (2 ** 48 - 1) ^ user_mask
|
||||
iab_bits = eui_int >> 12
|
||||
user_bits = (eui_int | iab_mask) - iab_mask
|
||||
|
||||
if 0x50c2000 <= iab_bits <= 0x50c2fff:
|
||||
if strict and user_bits != 0:
|
||||
raise ValueError('%r is not a strict IAB!' % hex(user_bits))
|
||||
else:
|
||||
raise ValueError('%r is not an IAB address!' % hex(eui_int))
|
||||
|
||||
return iab_bits, user_bits
|
||||
|
||||
def __init__(self, iab, strict=False):
|
||||
"""
|
||||
Constructor
|
||||
|
||||
:param iab: an IAB string ``00-50-C2-XX-X0-00`` or an unsigned \
|
||||
integer. This address looks like an EUI-48 but it should not \
|
||||
have any non-zero bits in the last 3 bytes.
|
||||
|
||||
:param strict: If True, raises a ValueError if the last 12 bits \
|
||||
of IAB MAC/EUI-48 address are non-zero, ignores them otherwise. \
|
||||
(Default: False)
|
||||
"""
|
||||
super(IAB, self).__init__()
|
||||
|
||||
# Lazy loading of IEEE data structures.
|
||||
from netaddr.eui import ieee
|
||||
|
||||
self.record = {
|
||||
'idx': 0,
|
||||
'iab': '',
|
||||
'org': '',
|
||||
'address' : [],
|
||||
'offset': 0,
|
||||
'size': 0,
|
||||
}
|
||||
|
||||
if isinstance(iab, str):
|
||||
#TODO: Improve string parsing here.
|
||||
#TODO: '00-50-C2' is actually invalid.
|
||||
#TODO: Should be '00-50-C2-00-00-00' (i.e. a full MAC/EUI-48)
|
||||
int_val = int(iab.replace('-', ''), 16)
|
||||
(iab_int, user_int) = IAB.split_iab_mac(int_val, strict)
|
||||
self._value = iab_int
|
||||
elif _is_int(iab):
|
||||
(iab_int, user_int) = IAB.split_iab_mac(iab, strict)
|
||||
self._value = iab_int
|
||||
else:
|
||||
raise TypeError('unexpected IAB format: %r!' % iab)
|
||||
|
||||
# Discover offsets.
|
||||
if self._value in ieee.IAB_INDEX:
|
||||
fh = open(ieee.IAB_REGISTRY)
|
||||
(offset, size) = ieee.IAB_INDEX[self._value][0]
|
||||
self.record['offset'] = offset
|
||||
self.record['size'] = size
|
||||
fh.seek(offset)
|
||||
data = fh.read(size)
|
||||
self._parse_data(data, offset, size)
|
||||
fh.close()
|
||||
else:
|
||||
raise NotRegisteredError('IAB %r not unregistered!' % iab)
|
||||
|
||||
def __getstate__(self):
|
||||
""":returns: Pickled state of an `IAB` object."""
|
||||
return self._value, self.record
|
||||
|
||||
def __setstate__(self, state):
|
||||
""":param state: data used to unpickle a pickled `IAB` object."""
|
||||
self._value, self.record = state
|
||||
|
||||
def _parse_data(self, data, offset, size):
|
||||
"""Returns a dict record from raw IAB record data"""
|
||||
for line in data.split("\n"):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
if '(hex)' in line:
|
||||
self.record['idx'] = self._value
|
||||
self.record['org'] = ' '.join(line.split()[2:])
|
||||
self.record['iab'] = str(self)
|
||||
elif '(base 16)' in line:
|
||||
continue
|
||||
else:
|
||||
self.record['address'].append(line)
|
||||
|
||||
def registration(self):
|
||||
""" The IEEE registration details for this IAB"""
|
||||
return DictDotLookup(self.record)
|
||||
|
||||
def __str__(self):
|
||||
""":return: string representation of this IAB"""
|
||||
int_val = self._value << 12
|
||||
words = []
|
||||
for _ in range(6):
|
||||
word = int_val & 0xff
|
||||
words.append('%02x' % word)
|
||||
int_val >>= 8
|
||||
return '-'.join(reversed(words)).upper()
|
||||
|
||||
def __repr__(self):
|
||||
""":return: executable Python string to recreate equivalent object."""
|
||||
return "IAB('%s')" % self
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class EUI(BaseIdentifier):
|
||||
"""
|
||||
An IEEE EUI (Extended Unique Identifier).
|
||||
|
||||
Both EUI-48 (used for layer 2 MAC addresses) and EUI-64 are supported.
|
||||
|
||||
Input parsing for EUI-48 addresses is flexible, supporting many MAC
|
||||
variants.
|
||||
|
||||
"""
|
||||
__slots__ = ('_module', '_dialect')
|
||||
|
||||
def __init__(self, addr, version=None, dialect=None):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
:param addr: an EUI-48 (MAC) or EUI-64 address in string format or \
|
||||
an unsigned integer. May also be another EUI object (copy \
|
||||
construction).
|
||||
|
||||
:param version: (optional) the explict EUI address version. Mainly \
|
||||
used to distinguish between EUI-48 and EUI-64 identifiers \
|
||||
specified as integers which may be numerically equivalent.
|
||||
|
||||
:param dialect: (optional) the mac_* dialect to be used to configure \
|
||||
the formatting of EUI-48 (MAC) addresses.
|
||||
"""
|
||||
super(EUI, self).__init__()
|
||||
|
||||
self._module = None
|
||||
|
||||
if isinstance(addr, EUI):
|
||||
# Copy constructor.
|
||||
if version is not None and version != addr._module.version:
|
||||
raise ValueError('cannot switch EUI versions using '
|
||||
'copy constructor!')
|
||||
self._module = addr._module
|
||||
self._value = addr._value
|
||||
self.dialect = addr.dialect
|
||||
return
|
||||
|
||||
if version is not None:
|
||||
if version == 48:
|
||||
self._module = _eui48
|
||||
elif version == 64:
|
||||
self._module = _eui64
|
||||
else:
|
||||
raise ValueError('unsupported EUI version %r' % version)
|
||||
else:
|
||||
# Choose a default version when addr is an integer and version is
|
||||
# not specified.
|
||||
if _is_int(addr):
|
||||
if 0 <= addr <= 0xffffffffffff:
|
||||
self._module = _eui48
|
||||
elif 0xffffffffffff < addr <= 0xffffffffffffffff:
|
||||
self._module = _eui64
|
||||
|
||||
self.value = addr
|
||||
|
||||
# Choose a dialect for MAC formatting.
|
||||
self.dialect = dialect
|
||||
|
||||
def __getstate__(self):
|
||||
""":returns: Pickled state of an `EUI` object."""
|
||||
return self._value, self._module.version, self.dialect
|
||||
|
||||
def __setstate__(self, state):
|
||||
"""
|
||||
:param state: data used to unpickle a pickled `EUI` object.
|
||||
|
||||
"""
|
||||
value, version, dialect = state
|
||||
|
||||
self._value = value
|
||||
|
||||
if version == 48:
|
||||
self._module = _eui48
|
||||
elif version == 64:
|
||||
self._module = _eui64
|
||||
else:
|
||||
raise ValueError('unpickling failed for object state: %s' \
|
||||
% str(state))
|
||||
|
||||
self.dialect = dialect
|
||||
|
||||
def _get_value(self):
|
||||
return self._value
|
||||
|
||||
def _set_value(self, value):
|
||||
if self._module is None:
|
||||
# EUI version is implicit, detect it from value.
|
||||
for module in (_eui48, _eui64):
|
||||
try:
|
||||
self._value = module.str_to_int(value)
|
||||
self._module = module
|
||||
break
|
||||
except AddrFormatError:
|
||||
try:
|
||||
if 0 <= int(value) <= module.max_int:
|
||||
self._value = int(value)
|
||||
self._module = module
|
||||
break
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if self._module is None:
|
||||
raise AddrFormatError('failed to detect EUI version: %r'
|
||||
% value)
|
||||
else:
|
||||
# EUI version is explicit.
|
||||
if hasattr(value, 'upper'):
|
||||
try:
|
||||
self._value = self._module.str_to_int(value)
|
||||
except AddrFormatError:
|
||||
raise AddrFormatError('address %r is not an EUIv%d'
|
||||
% (value, self._module.version))
|
||||
else:
|
||||
if 0 <= int(value) <= self._module.max_int:
|
||||
self._value = int(value)
|
||||
else:
|
||||
raise AddrFormatError('bad address format: %r' % value)
|
||||
|
||||
value = property(_get_value, _set_value, None,
|
||||
'a positive integer representing the value of this EUI indentifier.')
|
||||
|
||||
def _get_dialect(self):
|
||||
return self._dialect
|
||||
|
||||
def _set_dialect(self, value):
|
||||
if value is None:
|
||||
self._dialect = mac_eui48
|
||||
else:
|
||||
if hasattr(value, 'word_size') and hasattr(value, 'word_fmt'):
|
||||
self._dialect = value
|
||||
else:
|
||||
raise TypeError('custom dialects should subclass mac_eui48!')
|
||||
|
||||
dialect = property(_get_dialect, _set_dialect, None,
|
||||
"a Python class providing support for the interpretation of "
|
||||
"various MAC\n address formats.")
|
||||
|
||||
@property
|
||||
def oui(self):
|
||||
"""The OUI (Organisationally Unique Identifier) for this EUI."""
|
||||
if self._module == _eui48:
|
||||
return OUI(self.value >> 24)
|
||||
elif self._module == _eui64:
|
||||
return OUI(self.value >> 40)
|
||||
|
||||
@property
|
||||
def ei(self):
|
||||
"""The EI (Extension Identifier) for this EUI"""
|
||||
if self._module == _eui48:
|
||||
return '-'.join(["%02x" % i for i in self[3:6]]).upper()
|
||||
elif self._module == _eui64:
|
||||
return '-'.join(["%02x" % i for i in self[3:8]]).upper()
|
||||
|
||||
def is_iab(self):
|
||||
""":return: True if this EUI is an IAB address, False otherwise"""
|
||||
return 0x50c2000 <= (self._value >> 12) <= 0x50c2fff
|
||||
|
||||
@property
|
||||
def iab(self):
|
||||
"""
|
||||
If is_iab() is True, the IAB (Individual Address Block) is returned,
|
||||
``None`` otherwise.
|
||||
"""
|
||||
if self.is_iab():
|
||||
return IAB(self._value >> 12)
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
"""The EUI version represented by this EUI object."""
|
||||
return self._module.version
|
||||
|
||||
def __getitem__(self, idx):
|
||||
"""
|
||||
:return: The integer value of the word referenced by index (both \
|
||||
positive and negative). Raises ``IndexError`` if index is out \
|
||||
of bounds. Also supports Python list slices for accessing \
|
||||
word groups.
|
||||
"""
|
||||
if _is_int(idx):
|
||||
# Indexing, including negative indexing goodness.
|
||||
num_words = self._dialect.num_words
|
||||
if not (-num_words) <= idx <= (num_words - 1):
|
||||
raise IndexError('index out range for address type!')
|
||||
return self._module.int_to_words(self._value, self._dialect)[idx]
|
||||
elif isinstance(idx, slice):
|
||||
words = self._module.int_to_words(self._value, self._dialect)
|
||||
return [words[i] for i in range(*idx.indices(len(words)))]
|
||||
else:
|
||||
raise TypeError('unsupported type %r!' % idx)
|
||||
|
||||
def __setitem__(self, idx, value):
|
||||
"""Sets the value of the word referenced by index in this address"""
|
||||
if isinstance(idx, slice):
|
||||
# TODO - settable slices.
|
||||
raise NotImplementedError('settable slices are not supported!')
|
||||
|
||||
if not _is_int(idx):
|
||||
raise TypeError('index not an integer!')
|
||||
|
||||
if not 0 <= idx <= (self._dialect.num_words - 1):
|
||||
raise IndexError('index %d outside address type boundary!' % idx)
|
||||
|
||||
if not _is_int(value):
|
||||
raise TypeError('value not an integer!')
|
||||
|
||||
if not 0 <= value <= self._dialect.max_word:
|
||||
raise IndexError('value %d outside word size maximum of %d bits!'
|
||||
% (value, self._dialect.word_size))
|
||||
|
||||
words = list(self._module.int_to_words(self._value, self._dialect))
|
||||
words[idx] = value
|
||||
self._value = self._module.words_to_int(words)
|
||||
|
||||
def __hash__(self):
|
||||
""":return: hash of this EUI object suitable for dict keys, sets etc"""
|
||||
return hash((self.version, self._value))
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
:return: ``True`` if this EUI object is numerically the same as other, \
|
||||
``False`` otherwise.
|
||||
"""
|
||||
try:
|
||||
return(self.version, self._value) == (other.version, other._value)
|
||||
except AttributeError:
|
||||
return NotImplemented
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
:return: ``False`` if this EUI object is numerically the same as the \
|
||||
other, ``True`` otherwise.
|
||||
"""
|
||||
try:
|
||||
return(self.version, self._value) != (other.version, other._value)
|
||||
except AttributeError:
|
||||
return NotImplemented
|
||||
|
||||
def __lt__(self, other):
|
||||
"""
|
||||
:return: ``True`` if this EUI object is numerically lower in value than \
|
||||
other, ``False`` otherwise.
|
||||
"""
|
||||
try:
|
||||
return (self.version, self._value) < (other.version, other._value)
|
||||
except AttributeError:
|
||||
return NotImplemented
|
||||
|
||||
def __le__(self, other):
|
||||
"""
|
||||
:return: ``True`` if this EUI object is numerically lower or equal in \
|
||||
value to other, ``False`` otherwise.
|
||||
"""
|
||||
try:
|
||||
return(self.version, self._value) <= (other.version, other._value)
|
||||
except AttributeError:
|
||||
return NotImplemented
|
||||
|
||||
def __gt__(self, other):
|
||||
"""
|
||||
:return: ``True`` if this EUI object is numerically greater in value \
|
||||
than other, ``False`` otherwise.
|
||||
"""
|
||||
try:
|
||||
return (self.version, self._value) > (other.version, other._value)
|
||||
except AttributeError:
|
||||
return NotImplemented
|
||||
|
||||
def __ge__(self, other):
|
||||
"""
|
||||
:return: ``True`` if this EUI object is numerically greater or equal \
|
||||
in value to other, ``False`` otherwise.
|
||||
"""
|
||||
try:
|
||||
return(self.version, self._value) >= (other.version, other._value)
|
||||
except AttributeError:
|
||||
return NotImplemented
|
||||
|
||||
def bits(self, word_sep=None):
|
||||
"""
|
||||
:param word_sep: (optional) the separator to insert between words. \
|
||||
Default: None - use default separator for address type.
|
||||
|
||||
:return: human-readable binary digit string of this address.
|
||||
"""
|
||||
return self._module.int_to_bits(self._value, word_sep)
|
||||
|
||||
@property
|
||||
def packed(self):
|
||||
"""The value of this EUI address as a packed binary string."""
|
||||
return self._module.int_to_packed(self._value)
|
||||
|
||||
@property
|
||||
def words(self):
|
||||
"""A list of unsigned integer octets found in this EUI address."""
|
||||
return self._module.int_to_words(self._value)
|
||||
|
||||
@property
|
||||
def bin(self):
|
||||
"""
|
||||
The value of this EUI adddress in standard Python binary
|
||||
representational form (0bxxx). A back port of the format provided by
|
||||
the builtin bin() function found in Python 2.6.x and higher.
|
||||
"""
|
||||
return self._module.int_to_bin(self._value)
|
||||
|
||||
def eui64(self):
|
||||
"""
|
||||
- If this object represents an EUI-48 it is converted to EUI-64 \
|
||||
as per the standard.
|
||||
- If this object is already and EUI-64, it just returns a new, \
|
||||
numerically equivalent object is returned instead.
|
||||
|
||||
:return: The value of this EUI object as a new 64-bit EUI object.
|
||||
"""
|
||||
if self.version == 48:
|
||||
eui64_words = ["%02x" % i for i in self[0:3]] + ['ff', 'fe'] + \
|
||||
["%02x" % i for i in self[3:6]]
|
||||
|
||||
return self.__class__('-'.join(eui64_words))
|
||||
else:
|
||||
return EUI(str(self))
|
||||
|
||||
def ipv6_link_local(self):
|
||||
"""
|
||||
.. note:: This poses security risks in certain scenarios. \
|
||||
Please read RFC 4941 for details. Reference: RFCs 4291 and 4941.
|
||||
|
||||
:return: new link local IPv6 `IPAddress` object based on this `EUI` \
|
||||
using the technique described in RFC 4291.
|
||||
"""
|
||||
int_val = 0xfe800000000000000000000000000000
|
||||
|
||||
if self.version == 48:
|
||||
eui64_tokens = ["%02x" % i for i in self[0:3]] + ['ff', 'fe'] + \
|
||||
["%02x" % i for i in self[3:6]]
|
||||
int_val += int(''.join(eui64_tokens), 16)
|
||||
else:
|
||||
int_val += self._value
|
||||
|
||||
# Modified EUI-64 format interface identifiers are formed by inverting
|
||||
# the "u" bit (universal/local bit in IEEE EUI-64 terminology) when
|
||||
# forming the interface identifier from IEEE EUI-64 identifiers. In
|
||||
# the resulting Modified EUI-64 format, the "u" bit is set to one (1)
|
||||
# to indicate universal scope, and it is set to zero (0) to indicate
|
||||
# local scope.
|
||||
int_val ^= 0x00000000000000000200000000000000
|
||||
|
||||
return IPAddress(int_val, 6)
|
||||
|
||||
@property
|
||||
def info(self):
|
||||
"""
|
||||
A record dict containing IEEE registration details for this EUI
|
||||
(MAC-48) if available, None otherwise.
|
||||
"""
|
||||
data = {'OUI': self.oui.registration()}
|
||||
if self.is_iab():
|
||||
data['IAB'] = self.iab.registration()
|
||||
|
||||
return DictDotLookup(data)
|
||||
|
||||
def __str__(self):
|
||||
""":return: EUI in representational format"""
|
||||
return self._module.int_to_str(self._value, self._dialect)
|
||||
|
||||
def __repr__(self):
|
||||
""":return: executable Python string to recreate equivalent object."""
|
||||
return "EUI('%s')" % self
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,290 @@
|
||||
#!/usr/bin/env python
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2008-2012, David P. D. Moss. All rights reserved.
|
||||
#
|
||||
# Released under the BSD license. See the LICENSE file for details.
|
||||
#-----------------------------------------------------------------------------
|
||||
#
|
||||
# DISCLAIMER
|
||||
#
|
||||
# netaddr is not sponsored nor endorsed by the IEEE.
|
||||
#
|
||||
# Use of data from the IEEE (Institute of Electrical and Electronics
|
||||
# Engineers) is subject to copyright. See the following URL for
|
||||
# details :-
|
||||
#
|
||||
# - http://www.ieee.org/web/publications/rights/legal.html
|
||||
#
|
||||
# IEEE data files included with netaddr are not modified in any way but are
|
||||
# parsed and made available to end users through an API. There is no
|
||||
# guarantee that referenced files are not out of date.
|
||||
#
|
||||
# See README file and source code for URLs to latest copies of the relevant
|
||||
# files.
|
||||
#
|
||||
#-----------------------------------------------------------------------------
|
||||
"""
|
||||
Provides access to public OUI and IAB registration data published by the IEEE.
|
||||
|
||||
More details can be found at the following URLs :-
|
||||
|
||||
- IEEE Home Page - http://www.ieee.org/
|
||||
- Registration Authority Home Page - http://standards.ieee.org/regauth/
|
||||
"""
|
||||
|
||||
import os as _os
|
||||
import sys as _sys
|
||||
import os.path as _path
|
||||
import csv as _csv
|
||||
|
||||
from netaddr.core import Subscriber, Publisher
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
#: Path to local copy of IEEE OUI Registry data file.
|
||||
OUI_REGISTRY = _path.join(_path.dirname(__file__), 'oui.txt')
|
||||
#: Path to netaddr OUI index file.
|
||||
OUI_METADATA = _path.join(_path.dirname(__file__), 'oui.idx')
|
||||
|
||||
#: OUI index lookup dictionary.
|
||||
OUI_INDEX = {}
|
||||
|
||||
#: Path to local copy of IEEE IAB Registry data file.
|
||||
IAB_REGISTRY = _path.join(_path.dirname(__file__), 'iab.txt')
|
||||
|
||||
#: Path to netaddr IAB index file.
|
||||
IAB_METADATA = _path.join(_path.dirname(__file__), 'iab.idx')
|
||||
|
||||
#: IAB index lookup dictionary.
|
||||
IAB_INDEX = {}
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class FileIndexer(Subscriber):
|
||||
"""
|
||||
A concrete Subscriber that receives OUI record offset information that is
|
||||
written to an index data file as a set of comma separated records.
|
||||
"""
|
||||
def __init__(self, index_file):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
:param index_file: a file-like object or name of index file where
|
||||
index records will be written.
|
||||
"""
|
||||
if hasattr(index_file, 'readline') and hasattr(index_file, 'tell'):
|
||||
self.fh = index_file
|
||||
else:
|
||||
self.fh = open(index_file, 'w')
|
||||
|
||||
self.writer = _csv.writer(self.fh, lineterminator="\n")
|
||||
|
||||
def update(self, data):
|
||||
"""
|
||||
Receives and writes index data to a CSV data file.
|
||||
|
||||
:param data: record containing offset record information.
|
||||
"""
|
||||
self.writer.writerow(data)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class OUIIndexParser(Publisher):
|
||||
"""
|
||||
A concrete Publisher that parses OUI (Organisationally Unique Identifier)
|
||||
records from IEEE text-based registration files
|
||||
|
||||
It notifies registered Subscribers as each record is encountered, passing
|
||||
on the record's position relative to the start of the file (offset) and
|
||||
the size of the record (in bytes).
|
||||
|
||||
The file processed by this parser is available online from this URL :-
|
||||
|
||||
- http://standards.ieee.org/regauth/oui/oui.txt
|
||||
|
||||
This is a sample of the record structure expected::
|
||||
|
||||
00-CA-FE (hex) ACME CORPORATION
|
||||
00CAFE (base 16) ACME CORPORATION
|
||||
1 MAIN STREET
|
||||
SPRINGFIELD
|
||||
UNITED STATES
|
||||
"""
|
||||
def __init__(self, ieee_file):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
:param ieee_file: a file-like object or name of file containing OUI
|
||||
records. When using a file-like object always open it in binary
|
||||
mode otherwise offsets will probably misbehave.
|
||||
"""
|
||||
super(OUIIndexParser, self).__init__()
|
||||
|
||||
if hasattr(ieee_file, 'readline') and hasattr(ieee_file, 'tell'):
|
||||
self.fh = ieee_file
|
||||
else:
|
||||
self.fh = open(ieee_file)
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
Starts the parsing process which detects records and notifies
|
||||
registered subscribers as it finds each OUI record.
|
||||
"""
|
||||
skip_header = True
|
||||
record = None
|
||||
size = 0
|
||||
|
||||
while True:
|
||||
line = self.fh.readline() # unbuffered to obtain correct offsets
|
||||
|
||||
if not line:
|
||||
break # EOF, we're done
|
||||
|
||||
if skip_header and '(hex)' in line:
|
||||
skip_header = False
|
||||
|
||||
if skip_header:
|
||||
# ignoring header section
|
||||
continue
|
||||
|
||||
if '(hex)' in line:
|
||||
# record start
|
||||
if record is not None:
|
||||
# a complete record.
|
||||
record.append(size)
|
||||
self.notify(record)
|
||||
|
||||
size = len(line)
|
||||
offset = (self.fh.tell() - len(line))
|
||||
oui = line.split()[0]
|
||||
index = int(oui.replace('-', ''), 16)
|
||||
record = [index, offset]
|
||||
else:
|
||||
# within record
|
||||
size += len(line)
|
||||
|
||||
# process final record on loop exit
|
||||
record.append(size)
|
||||
self.notify(record)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class IABIndexParser(Publisher):
|
||||
"""
|
||||
A concrete Publisher that parses IAB (Individual Address Block) records
|
||||
from IEEE text-based registration files
|
||||
|
||||
It notifies registered Subscribers as each record is encountered, passing
|
||||
on the record's position relative to the start of the file (offset) and
|
||||
the size of the record (in bytes).
|
||||
|
||||
The file processed by this parser is available online from this URL :-
|
||||
|
||||
- http://standards.ieee.org/regauth/oui/iab.txt
|
||||
|
||||
This is a sample of the record structure expected::
|
||||
|
||||
00-50-C2 (hex) ACME CORPORATION
|
||||
ABC000-ABCFFF (base 16) ACME CORPORATION
|
||||
1 MAIN STREET
|
||||
SPRINGFIELD
|
||||
UNITED STATES
|
||||
"""
|
||||
def __init__(self, ieee_file):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
:param ieee_file: a file-like object or name of file containing IAB
|
||||
records. When using a file-like object always open it in binary
|
||||
mode otherwise offsets will probably misbehave.
|
||||
"""
|
||||
super(IABIndexParser, self).__init__()
|
||||
|
||||
if hasattr(ieee_file, 'readline') and hasattr(ieee_file, 'tell'):
|
||||
self.fh = ieee_file
|
||||
else:
|
||||
self.fh = open(ieee_file)
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
Starts the parsing process which detects records and notifies
|
||||
registered subscribers as it finds each IAB record.
|
||||
"""
|
||||
skip_header = True
|
||||
record = None
|
||||
size = 0
|
||||
while True:
|
||||
line = self.fh.readline() # unbuffered
|
||||
|
||||
if not line:
|
||||
break # EOF, we're done
|
||||
|
||||
if skip_header and '(hex)' in line:
|
||||
skip_header = False
|
||||
|
||||
if skip_header:
|
||||
# ignoring header section
|
||||
continue
|
||||
|
||||
if '(hex)' in line:
|
||||
# record start
|
||||
if record is not None:
|
||||
record.append(size)
|
||||
self.notify(record)
|
||||
|
||||
offset = (self.fh.tell() - len(line))
|
||||
iab_prefix = line.split()[0]
|
||||
index = iab_prefix
|
||||
record = [index, offset]
|
||||
size = len(line)
|
||||
elif '(base 16)' in line:
|
||||
# within record
|
||||
size += len(line)
|
||||
prefix = record[0].replace('-', '')
|
||||
suffix = line.split()[0]
|
||||
suffix = suffix.split('-')[0]
|
||||
record[0] = (int(prefix + suffix, 16)) >> 12
|
||||
else:
|
||||
# within record
|
||||
size += len(line)
|
||||
|
||||
# process final record on loop exit
|
||||
record.append(size)
|
||||
self.notify(record)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def create_indices():
|
||||
"""Create indices for OUI and IAB file based lookups"""
|
||||
oui_parser = OUIIndexParser(OUI_REGISTRY)
|
||||
oui_parser.attach(FileIndexer(OUI_METADATA))
|
||||
oui_parser.parse()
|
||||
|
||||
iab_parser = IABIndexParser(IAB_REGISTRY)
|
||||
iab_parser.attach(FileIndexer(IAB_METADATA))
|
||||
iab_parser.parse()
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def load_indices():
|
||||
"""Load OUI and IAB lookup indices into memory"""
|
||||
fp = open(OUI_METADATA)
|
||||
try:
|
||||
for row in _csv.reader(fp):
|
||||
(key, offset, size) = [int(_) for _ in row]
|
||||
OUI_INDEX.setdefault(key, [])
|
||||
OUI_INDEX[key].append((offset, size))
|
||||
finally:
|
||||
fp.close()
|
||||
|
||||
fp = open(IAB_METADATA)
|
||||
try:
|
||||
for row in _csv.reader(fp):
|
||||
(key, offset, size) = [int(_) for _ in row]
|
||||
IAB_INDEX.setdefault(key, [])
|
||||
IAB_INDEX[key].append((offset, size))
|
||||
finally:
|
||||
fp.close()
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
if __name__ == '__main__':
|
||||
# Generate indices when module is executed as a script.
|
||||
create_indices()
|
||||
else:
|
||||
# On module load read indices in memory to enable lookups.
|
||||
load_indices()
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,293 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2008-2012, David P. D. Moss. All rights reserved.
|
||||
#
|
||||
# Released under the BSD license. See the LICENSE file for details.
|
||||
#-----------------------------------------------------------------------------
|
||||
"""Fallback routines for Python's standard library socket module"""
|
||||
|
||||
from struct import unpack as _unpack, pack as _pack
|
||||
|
||||
from netaddr.compat import _bytes_join
|
||||
|
||||
AF_INET = 2
|
||||
AF_INET6 = 10
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def inet_ntoa(packed_ip):
|
||||
"""
|
||||
Convert an IP address from 32-bit packed binary format to string format.
|
||||
"""
|
||||
if not hasattr(packed_ip, 'split'):
|
||||
raise TypeError('string type expected, not %s' % str(type(packed_ip)))
|
||||
|
||||
if len(packed_ip) != 4:
|
||||
raise ValueError('invalid length of packed IP address string')
|
||||
|
||||
return '%d.%d.%d.%d' % _unpack('4B', packed_ip)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def inet_aton(ip_string):
|
||||
"""
|
||||
Convert an IP address in string format (123.45.67.89) to the 32-bit packed
|
||||
binary format used in low-level network functions.
|
||||
"""
|
||||
if hasattr(ip_string, 'split'):
|
||||
invalid_addr = ValueError('illegal IP address string %r' % ip_string)
|
||||
# Support for hexadecimal and octal octets.
|
||||
tokens = []
|
||||
|
||||
base = 10
|
||||
for token in ip_string.split('.'):
|
||||
if token.startswith('0x'):
|
||||
base = 16
|
||||
elif token.startswith('0') and len(token) > 1:
|
||||
base = 8
|
||||
elif token == '':
|
||||
continue
|
||||
try:
|
||||
tokens.append(int(token, base))
|
||||
except ValueError:
|
||||
raise invalid_addr
|
||||
|
||||
# Zero fill missing octets.
|
||||
num_tokens = len(tokens)
|
||||
if num_tokens < 4:
|
||||
fill_tokens = [0] * (4 - num_tokens)
|
||||
if num_tokens > 1:
|
||||
end_token = tokens.pop()
|
||||
tokens = tokens + fill_tokens + [end_token]
|
||||
else:
|
||||
tokens = tokens + fill_tokens
|
||||
|
||||
# Pack octets.
|
||||
if len(tokens) == 4:
|
||||
words = []
|
||||
for token in tokens:
|
||||
if (token >> 8) != 0:
|
||||
raise invalid_addr
|
||||
words.append(_pack('B', token))
|
||||
return _bytes_join(words)
|
||||
else:
|
||||
raise invalid_addr
|
||||
|
||||
raise ValueError('argument should be a string, not %s' % type(ip_string))
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def _compact_ipv6_tokens(tokens):
|
||||
new_tokens = []
|
||||
|
||||
positions = []
|
||||
start_index = None
|
||||
num_tokens = 0
|
||||
|
||||
# Discover all runs of zeros.
|
||||
for idx, token in enumerate(tokens):
|
||||
if token == '0':
|
||||
if start_index is None:
|
||||
start_index = idx
|
||||
num_tokens += 1
|
||||
else:
|
||||
if num_tokens > 1:
|
||||
positions.append((num_tokens, start_index))
|
||||
start_index = None
|
||||
num_tokens = 0
|
||||
|
||||
new_tokens.append(token)
|
||||
|
||||
# Store any position not saved before loop exit.
|
||||
if num_tokens > 1:
|
||||
positions.append((num_tokens, start_index))
|
||||
|
||||
# Replace first longest run with an empty string.
|
||||
if len(positions) != 0:
|
||||
# Locate longest, left-most run of zeros.
|
||||
positions.sort(key=lambda x: x[1])
|
||||
best_position = positions[0]
|
||||
for position in positions:
|
||||
if position[0] > best_position[0]:
|
||||
best_position = position
|
||||
# Replace chosen zero run.
|
||||
(length, start_idx) = best_position
|
||||
new_tokens = new_tokens[0:start_idx] + [''] + \
|
||||
new_tokens[start_idx+length:]
|
||||
|
||||
# Add start and end blanks so join creates '::'.
|
||||
if new_tokens[0] == '':
|
||||
new_tokens.insert(0, '')
|
||||
|
||||
if new_tokens[-1] == '':
|
||||
new_tokens.append('')
|
||||
|
||||
return new_tokens
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def inet_ntop(af, packed_ip):
|
||||
"""Convert an packed IP address of the given family to string format."""
|
||||
if af == AF_INET:
|
||||
# IPv4.
|
||||
return inet_ntoa(packed_ip)
|
||||
elif af == AF_INET6:
|
||||
# IPv6.
|
||||
if len(packed_ip) != 16 or not hasattr(packed_ip, 'split'):
|
||||
raise ValueError('invalid length of packed IP address string')
|
||||
|
||||
tokens = ['%x' % i for i in _unpack('>8H', packed_ip)]
|
||||
|
||||
# Convert packed address to an integer value.
|
||||
words = list(_unpack('>8H', packed_ip))
|
||||
int_val = 0
|
||||
for i, num in enumerate(reversed(words)):
|
||||
word = num
|
||||
word = word << 16 * i
|
||||
int_val = int_val | word
|
||||
|
||||
if 0xffff < int_val <= 0xffffffff or int_val >> 32 == 0xffff:
|
||||
# IPv4 compatible / mapped IPv6.
|
||||
packed_ipv4 = _pack('>2H', *[int(i, 16) for i in tokens[-2:]])
|
||||
ipv4_str = inet_ntoa(packed_ipv4)
|
||||
tokens = tokens[0:-2] + [ipv4_str]
|
||||
|
||||
return ':'.join(_compact_ipv6_tokens(tokens))
|
||||
else:
|
||||
raise ValueError('unknown address family %d' % af)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def _inet_pton_af_inet(ip_string):
|
||||
"""
|
||||
Convert an IP address in string format (123.45.67.89) to the 32-bit packed
|
||||
binary format used in low-level network functions. Differs from inet_aton
|
||||
by only support decimal octets. Using octal or hexadecimal values will
|
||||
raise a ValueError exception.
|
||||
"""
|
||||
#TODO: optimise this ... use inet_aton with mods if available ...
|
||||
if hasattr(ip_string, 'split'):
|
||||
invalid_addr = ValueError('illegal IP address string %r' % ip_string)
|
||||
# Support for hexadecimal and octal octets.
|
||||
tokens = ip_string.split('.')
|
||||
|
||||
# Pack octets.
|
||||
if len(tokens) == 4:
|
||||
words = []
|
||||
for token in tokens:
|
||||
if token.startswith('0x') or \
|
||||
(token.startswith('0') and len(token) > 1):
|
||||
raise invalid_addr
|
||||
try:
|
||||
octet = int(token)
|
||||
except ValueError:
|
||||
raise invalid_addr
|
||||
|
||||
if (octet >> 8) != 0:
|
||||
raise invalid_addr
|
||||
words.append(_pack('B', octet))
|
||||
return _bytes_join(words)
|
||||
else:
|
||||
raise invalid_addr
|
||||
|
||||
raise ValueError('argument should be a string, not %s' % type(ip_string))
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def inet_pton(af, ip_string):
|
||||
"""
|
||||
Convert an IP address from string format to a packed string suitable for
|
||||
use with low-level network functions.
|
||||
"""
|
||||
if af == AF_INET:
|
||||
# IPv4.
|
||||
return _inet_pton_af_inet(ip_string)
|
||||
elif af == AF_INET6:
|
||||
invalid_addr = ValueError('illegal IP address string %r' % ip_string)
|
||||
# IPv6.
|
||||
values = []
|
||||
|
||||
if not hasattr(ip_string, 'split'):
|
||||
raise invalid_addr
|
||||
|
||||
if 'x' in ip_string:
|
||||
# Don't accept hextets with the 0x prefix.
|
||||
raise invalid_addr
|
||||
|
||||
if '::' in ip_string:
|
||||
if ip_string == '::':
|
||||
# Unspecified address.
|
||||
return '\x00'.encode() * 16
|
||||
# IPv6 compact mode.
|
||||
try:
|
||||
prefix, suffix = ip_string.split('::')
|
||||
except ValueError:
|
||||
raise invalid_addr
|
||||
|
||||
l_prefix = []
|
||||
l_suffix = []
|
||||
|
||||
if prefix != '':
|
||||
l_prefix = prefix.split(':')
|
||||
|
||||
if suffix != '':
|
||||
l_suffix = suffix.split(':')
|
||||
|
||||
# IPv6 compact IPv4 compatibility mode.
|
||||
if len(l_suffix) and '.' in l_suffix[-1]:
|
||||
ipv4_str = _inet_pton_af_inet(l_suffix.pop())
|
||||
l_suffix.append('%x' % _unpack('>H', ipv4_str[0:2])[0])
|
||||
l_suffix.append('%x' % _unpack('>H', ipv4_str[2:4])[0])
|
||||
|
||||
token_count = len(l_prefix) + len(l_suffix)
|
||||
|
||||
if not 0 <= token_count <= 8 - 1:
|
||||
raise invalid_addr
|
||||
|
||||
gap_size = 8 - ( len(l_prefix) + len(l_suffix) )
|
||||
|
||||
values = [_pack('>H', int(i, 16)) for i in l_prefix] \
|
||||
+ ['\x00\x00'.encode() for i in range(gap_size)] \
|
||||
+ [_pack('>H', int(i, 16)) for i in l_suffix]
|
||||
try:
|
||||
for token in l_prefix + l_suffix:
|
||||
word = int(token, 16)
|
||||
if not 0 <= word <= 0xffff:
|
||||
raise invalid_addr
|
||||
except ValueError:
|
||||
raise invalid_addr
|
||||
else:
|
||||
# IPv6 verbose mode.
|
||||
if ':' in ip_string:
|
||||
tokens = ip_string.split(':')
|
||||
|
||||
if '.' in ip_string:
|
||||
ipv6_prefix = tokens[:-1]
|
||||
if ipv6_prefix[:-1] != ['0', '0', '0', '0', '0']:
|
||||
raise invalid_addr
|
||||
|
||||
if ipv6_prefix[-1].lower() not in ('0', 'ffff'):
|
||||
raise invalid_addr
|
||||
|
||||
# IPv6 verbose IPv4 compatibility mode.
|
||||
if len(tokens) != 7:
|
||||
raise invalid_addr
|
||||
|
||||
ipv4_str = _inet_pton_af_inet(tokens.pop())
|
||||
tokens.append('%x' % _unpack('>H', ipv4_str[0:2])[0])
|
||||
tokens.append('%x' % _unpack('>H', ipv4_str[2:4])[0])
|
||||
|
||||
values = [_pack('>H', int(i, 16)) for i in tokens]
|
||||
else:
|
||||
# IPv6 verbose mode.
|
||||
if len(tokens) != 8:
|
||||
raise invalid_addr
|
||||
try:
|
||||
tokens = [int(token, 16) for token in tokens]
|
||||
for token in tokens:
|
||||
if not 0 <= token <= 0xffff:
|
||||
raise invalid_addr
|
||||
|
||||
except ValueError:
|
||||
raise invalid_addr
|
||||
|
||||
values = [_pack('>H', i) for i in tokens]
|
||||
else:
|
||||
raise invalid_addr
|
||||
|
||||
return _bytes_join(values)
|
||||
else:
|
||||
raise ValueError('Unknown address family %d' % af)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,311 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2008-2012, David P. D. Moss. All rights reserved.
|
||||
#
|
||||
# Released under the BSD license. See the LICENSE file for details.
|
||||
#-----------------------------------------------------------------------------
|
||||
"""
|
||||
Routines and classes for supporting and expressing IP address ranges using a
|
||||
glob style syntax.
|
||||
|
||||
"""
|
||||
from netaddr.core import AddrFormatError, AddrConversionError
|
||||
from netaddr.ip import IPRange, IPAddress, IPNetwork, iprange_to_cidrs
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def valid_glob(ipglob):
|
||||
"""
|
||||
:param ipglob: An IP address range in a glob-style format.
|
||||
|
||||
:return: ``True`` if IP range glob is valid, ``False`` otherwise.
|
||||
"""
|
||||
#TODO: Add support for abbreviated ipglobs.
|
||||
#TODO: e.g. 192.0.*.* == 192.0.*
|
||||
#TODO: *.*.*.* == *
|
||||
#TODO: Add strict flag to enable verbose ipglob checking.
|
||||
if not hasattr(ipglob, 'split'):
|
||||
return False
|
||||
|
||||
seen_hyphen = False
|
||||
seen_asterisk = False
|
||||
|
||||
octets = ipglob.split('.')
|
||||
|
||||
if len(octets) != 4:
|
||||
return False
|
||||
|
||||
for octet in octets:
|
||||
if '-' in octet:
|
||||
if seen_hyphen:
|
||||
return False
|
||||
seen_hyphen = True
|
||||
if seen_asterisk:
|
||||
# Asterisks cannot precede hyphenated octets.
|
||||
return False
|
||||
try:
|
||||
(octet1, octet2) = [int(i) for i in octet.split('-')]
|
||||
except ValueError:
|
||||
return False
|
||||
if octet1 >= octet2:
|
||||
return False
|
||||
if not 0 <= octet1 <= 254:
|
||||
return False
|
||||
if not 1 <= octet2 <= 255:
|
||||
return False
|
||||
elif octet == '*':
|
||||
seen_asterisk = True
|
||||
else:
|
||||
if seen_hyphen is True:
|
||||
return False
|
||||
if seen_asterisk is True:
|
||||
return False
|
||||
try:
|
||||
if not 0 <= int(octet) <= 255:
|
||||
return False
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def glob_to_iptuple(ipglob):
|
||||
"""
|
||||
A function that accepts a glob-style IP range and returns the component
|
||||
lower and upper bound IP address.
|
||||
|
||||
:param ipglob: an IP address range in a glob-style format.
|
||||
|
||||
:return: a tuple contain lower and upper bound IP objects.
|
||||
"""
|
||||
if not valid_glob(ipglob):
|
||||
raise AddrFormatError('not a recognised IP glob range: %r!' % ipglob)
|
||||
|
||||
start_tokens = []
|
||||
end_tokens = []
|
||||
|
||||
for octet in ipglob.split('.'):
|
||||
if '-' in octet:
|
||||
tokens = octet.split('-')
|
||||
start_tokens.append(tokens[0])
|
||||
end_tokens.append(tokens[1])
|
||||
elif octet == '*':
|
||||
start_tokens.append('0')
|
||||
end_tokens.append('255')
|
||||
else:
|
||||
start_tokens.append(octet)
|
||||
end_tokens.append(octet)
|
||||
|
||||
return IPAddress('.'.join(start_tokens)), IPAddress('.'.join(end_tokens))
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def glob_to_iprange(ipglob):
|
||||
"""
|
||||
A function that accepts a glob-style IP range and returns the equivalent
|
||||
IP range.
|
||||
|
||||
:param ipglob: an IP address range in a glob-style format.
|
||||
|
||||
:return: an IPRange object.
|
||||
"""
|
||||
if not valid_glob(ipglob):
|
||||
raise AddrFormatError('not a recognised IP glob range: %r!' % ipglob)
|
||||
|
||||
start_tokens = []
|
||||
end_tokens = []
|
||||
|
||||
for octet in ipglob.split('.'):
|
||||
if '-' in octet:
|
||||
tokens = octet.split('-')
|
||||
start_tokens.append(tokens[0])
|
||||
end_tokens.append(tokens[1])
|
||||
elif octet == '*':
|
||||
start_tokens.append('0')
|
||||
end_tokens.append('255')
|
||||
else:
|
||||
start_tokens.append(octet)
|
||||
end_tokens.append(octet)
|
||||
|
||||
return IPRange('.'.join(start_tokens), '.'.join(end_tokens))
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def iprange_to_globs(start, end):
|
||||
"""
|
||||
A function that accepts an arbitrary start and end IP address or subnet
|
||||
and returns one or more glob-style IP ranges.
|
||||
|
||||
:param start: the start IP address or subnet.
|
||||
|
||||
:param end: the end IP address or subnet.
|
||||
|
||||
:return: a list containing one or more IP globs.
|
||||
"""
|
||||
start = IPAddress(start)
|
||||
end = IPAddress(end)
|
||||
|
||||
if start.version != 4 and end.version != 4:
|
||||
raise AddrConversionError('IP glob ranges only support IPv4!')
|
||||
|
||||
def _iprange_to_glob(lb, ub):
|
||||
# Internal function to process individual IP globs.
|
||||
t1 = [int(_) for _ in str(lb).split('.')]
|
||||
t2 = [int(_) for _ in str(ub).split('.')]
|
||||
|
||||
tokens = []
|
||||
|
||||
seen_hyphen = False
|
||||
seen_asterisk = False
|
||||
|
||||
for i in range(4):
|
||||
if t1[i] == t2[i]:
|
||||
# A normal octet.
|
||||
tokens.append(str(t1[i]))
|
||||
elif (t1[i] == 0) and (t2[i] == 255):
|
||||
# An asterisk octet.
|
||||
tokens.append('*')
|
||||
seen_asterisk = True
|
||||
else:
|
||||
# Create a hyphenated octet - only one allowed per IP glob.
|
||||
if not seen_asterisk:
|
||||
if not seen_hyphen:
|
||||
tokens.append('%s-%s' % (t1[i], t2[i]))
|
||||
seen_hyphen = True
|
||||
else:
|
||||
raise AddrConversionError('only 1 hyphenated octet' \
|
||||
' per IP glob allowed!')
|
||||
else:
|
||||
raise AddrConversionError("asterisks are not allowed' \
|
||||
' before hyphenated octets!")
|
||||
|
||||
return '.'.join(tokens)
|
||||
|
||||
globs = []
|
||||
|
||||
try:
|
||||
# IP range can be represented by a single glob.
|
||||
ipglob = _iprange_to_glob(start, end)
|
||||
if not valid_glob(ipglob):
|
||||
#TODO: this is a workaround, it is produces non-optimal but valid
|
||||
#TODO: glob conversions. Fix inner function so that is always
|
||||
#TODO: produces a valid glob.
|
||||
raise AddrConversionError('invalid ip glob created')
|
||||
globs.append(ipglob)
|
||||
except AddrConversionError:
|
||||
# Break IP range up into CIDRs before conversion to globs.
|
||||
#
|
||||
#TODO: this is still not completely optimised but is good enough
|
||||
#TODO: for the moment.
|
||||
#
|
||||
for cidr in iprange_to_cidrs(start, end):
|
||||
ipglob = _iprange_to_glob(cidr[0], cidr[-1])
|
||||
globs.append(ipglob)
|
||||
|
||||
return globs
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def glob_to_cidrs(ipglob):
|
||||
"""
|
||||
A function that accepts a glob-style IP range and returns a list of one
|
||||
or more IP CIDRs that exactly matches it.
|
||||
|
||||
:param ipglob: an IP address range in a glob-style format.
|
||||
|
||||
:return: a list of one or more IP objects.
|
||||
"""
|
||||
return iprange_to_cidrs(*glob_to_iptuple(ipglob))
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def cidr_to_glob(cidr):
|
||||
"""
|
||||
A function that accepts an IP subnet in a glob-style format and returns
|
||||
a list of CIDR subnets that exactly matches the specified glob.
|
||||
|
||||
:param cidr: an IP object CIDR subnet.
|
||||
|
||||
:return: a list of one or more IP addresses and subnets.
|
||||
"""
|
||||
ip = IPNetwork(cidr)
|
||||
globs = iprange_to_globs(ip[0], ip[-1])
|
||||
if len(globs) != 1:
|
||||
# There should only ever be a one to one mapping between a CIDR and
|
||||
# an IP glob range.
|
||||
raise AddrConversionError('bad CIDR to IP glob conversion!')
|
||||
return globs[0]
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class IPGlob(IPRange):
|
||||
"""
|
||||
Represents an IP address range using a glob-style syntax ``x.x.x-y.*``
|
||||
|
||||
Individual octets can be represented using the following shortcuts :
|
||||
|
||||
1. ``*`` - the asterisk octet (represents values ``0`` through ``255``)
|
||||
2. ``x-y`` - the hyphenated octet (represents values ``x`` through ``y``)
|
||||
|
||||
A few basic rules also apply :
|
||||
|
||||
1. ``x`` must always be greater than ``y``, therefore :
|
||||
|
||||
- ``x`` can only be ``0`` through ``254``
|
||||
- ``y`` can only be ``1`` through ``255``
|
||||
|
||||
2. only one hyphenated octet per IP glob is allowed
|
||||
3. only asterisks are permitted after a hyphenated octet
|
||||
|
||||
Examples:
|
||||
|
||||
+------------------+------------------------------+
|
||||
| IP glob | Description |
|
||||
+==================+==============================+
|
||||
| ``192.0.2.1`` | a single address |
|
||||
+------------------+------------------------------+
|
||||
| ``192.0.2.0-31`` | 32 addresses |
|
||||
+------------------+------------------------------+
|
||||
| ``192.0.2.*`` | 256 addresses |
|
||||
+------------------+------------------------------+
|
||||
| ``192.0.2-3.*`` | 512 addresses |
|
||||
+------------------+------------------------------+
|
||||
| ``192.0-1.*.*`` | 131,072 addresses |
|
||||
+------------------+------------------------------+
|
||||
| ``*.*.*.*`` | the whole IPv4 address space |
|
||||
+------------------+------------------------------+
|
||||
|
||||
.. note :: \
|
||||
IP glob ranges are not directly equivalent to CIDR blocks. \
|
||||
They can represent address ranges that do not fall on strict bit mask \
|
||||
boundaries. They are suitable for use in configuration files, being \
|
||||
more obvious and readable than their CIDR counterparts, especially for \
|
||||
admins and end users with little or no networking knowledge or \
|
||||
experience. All CIDR addresses can always be represented as IP globs \
|
||||
but the reverse is not always true.
|
||||
"""
|
||||
__slots__ = ('_glob',)
|
||||
|
||||
def __init__(self, ipglob):
|
||||
(start, end) = glob_to_iptuple(ipglob)
|
||||
super(IPGlob, self).__init__(start, end)
|
||||
self.glob = iprange_to_globs(self._start, self._end)[0]
|
||||
|
||||
def __getstate__(self):
|
||||
""":return: Pickled state of an `IPGlob` object."""
|
||||
return super(IPGlob, self).__getstate__()
|
||||
|
||||
def __setstate__(self, state):
|
||||
""":param state: data used to unpickle a pickled `IPGlob` object."""
|
||||
super(IPGlob, self).__setstate__(state)
|
||||
self.glob = iprange_to_globs(self._start, self._end)[0]
|
||||
|
||||
def _get_glob(self):
|
||||
return self._glob
|
||||
|
||||
def _set_glob(self, ipglob):
|
||||
(self._start, self._end) = glob_to_iptuple(ipglob)
|
||||
self._glob = iprange_to_globs(self._start, self._end)[0]
|
||||
|
||||
glob = property(_get_glob, _set_glob, None,
|
||||
'an arbitrary IP address range in glob format.')
|
||||
|
||||
def __str__(self):
|
||||
""":return: IP glob in common representational format."""
|
||||
return "%s" % self.glob
|
||||
|
||||
def __repr__(self):
|
||||
""":return: Python statement to create an equivalent object"""
|
||||
return "%s('%s')" % (self.__class__.__name__, self.glob)
|
||||
@@ -0,0 +1,433 @@
|
||||
#!/usr/bin/env python
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2008-2012, David P. D. Moss. All rights reserved.
|
||||
#
|
||||
# Released under the BSD license. See the LICENSE file for details.
|
||||
#-----------------------------------------------------------------------------
|
||||
#
|
||||
# DISCLAIMER
|
||||
#
|
||||
# netaddr is not sponsored nor endorsed by IANA.
|
||||
#
|
||||
# Use of data from IANA (Internet Assigned Numbers Authority) is subject to
|
||||
# copyright and is provided with prior written permission.
|
||||
#
|
||||
# IANA data files included with netaddr are not modified in any way but are
|
||||
# parsed and made available to end users through an API.
|
||||
#
|
||||
# See README file and source code for URLs to latest copies of the relevant
|
||||
# files.
|
||||
#
|
||||
#-----------------------------------------------------------------------------
|
||||
"""
|
||||
Routines for accessing data published by IANA (Internet Assigned Numbers
|
||||
Authority).
|
||||
|
||||
More details can be found at the following URLs :-
|
||||
|
||||
- IANA Home Page - http://www.iana.org/
|
||||
- IEEE Protocols Information Home Page - http://www.iana.org/protocols/
|
||||
"""
|
||||
|
||||
import os as _os
|
||||
import os.path as _path
|
||||
import sys as _sys
|
||||
import re as _re
|
||||
|
||||
from xml.sax import make_parser, handler
|
||||
|
||||
from netaddr.core import Publisher, Subscriber, PrettyPrinter, dos2unix
|
||||
from netaddr.ip import IPAddress, IPNetwork, IPRange, \
|
||||
cidr_abbrev_to_verbose, iprange_to_cidrs
|
||||
|
||||
from netaddr.compat import _dict_items, _callable
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
#: Topic based lookup dictionary for IANA information.
|
||||
IANA_INFO = {
|
||||
'IPv4' : {},
|
||||
'IPv6' : {},
|
||||
'multicast' : {},
|
||||
}
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
class SaxRecordParser(handler.ContentHandler):
|
||||
|
||||
def __init__(self, callback=None):
|
||||
self._level = 0
|
||||
self._is_active = False
|
||||
self._record = None
|
||||
self._tag_level = None
|
||||
self._tag_payload = None
|
||||
self._tag_feeding = None
|
||||
self._callback = callback
|
||||
|
||||
def startElement(self, name, attrs):
|
||||
self._level += 1
|
||||
|
||||
if self._is_active is False:
|
||||
if name == 'record':
|
||||
self._is_active = True
|
||||
self._tag_level = self._level
|
||||
self._record = {}
|
||||
if 'date' in attrs:
|
||||
self._record['date'] = attrs['date']
|
||||
elif self._level == self._tag_level + 1:
|
||||
if name == 'xref':
|
||||
if 'type' in attrs and 'data' in attrs:
|
||||
l = self._record.setdefault(attrs['type'], [])
|
||||
l.append(attrs['data'])
|
||||
else:
|
||||
self._tag_payload = []
|
||||
self._tag_feeding = True
|
||||
else:
|
||||
self._tag_feeding = False
|
||||
|
||||
def endElement(self, name):
|
||||
if self._is_active is True:
|
||||
if name == 'record' and self._tag_level == self._level:
|
||||
self._is_active = False
|
||||
self._tag_level = None
|
||||
if _callable(self._callback):
|
||||
self._callback(self._record)
|
||||
self._record = None
|
||||
elif self._level == self._tag_level + 1:
|
||||
if name != 'xref':
|
||||
self._record[name] = ''.join(self._tag_payload)
|
||||
self._tag_payload = None
|
||||
self._tag_feeding = False
|
||||
|
||||
self._level -= 1
|
||||
|
||||
def characters(self, content):
|
||||
if self._tag_feeding is True:
|
||||
self._tag_payload.append(content)
|
||||
|
||||
|
||||
class XMLRecordParser(Publisher):
|
||||
"""
|
||||
A configurable Parser that understands how to parse XML based records.
|
||||
"""
|
||||
def __init__(self, fh, **kwargs):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
fh - a valid, open file handle to XML based record data.
|
||||
"""
|
||||
super(XMLRecordParser, self).__init__()
|
||||
|
||||
self.xmlparser = make_parser()
|
||||
self.xmlparser.setContentHandler(SaxRecordParser(self.consume_record))
|
||||
|
||||
self.fh = fh
|
||||
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
def process_record(self, rec):
|
||||
"""
|
||||
This is the callback method invoked for every record. It is usually
|
||||
over-ridden by base classes to provide specific record-based logic.
|
||||
|
||||
Any record can be vetoed (not passed to registered Subscriber objects)
|
||||
by simply returning None.
|
||||
"""
|
||||
return rec
|
||||
|
||||
def consume_record(self, rec):
|
||||
record = self.process_record(rec)
|
||||
if record is not None:
|
||||
self.notify(record)
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
Parse and normalises records, notifying registered subscribers with
|
||||
record data as it is encountered.
|
||||
"""
|
||||
self.xmlparser.parse(self.fh)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class IPv4Parser(XMLRecordParser):
|
||||
"""
|
||||
A XMLRecordParser that understands how to parse and retrieve data records
|
||||
from the IANA IPv4 address space file.
|
||||
|
||||
It can be found online here :-
|
||||
|
||||
- http://www.iana.org/assignments/ipv4-address-space/ipv4-address-space.xml
|
||||
"""
|
||||
def __init__(self, fh, **kwargs):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
fh - a valid, open file handle to an IANA IPv4 address space file.
|
||||
|
||||
kwargs - additional parser options.
|
||||
"""
|
||||
super(IPv4Parser, self).__init__(fh)
|
||||
|
||||
def process_record(self, rec):
|
||||
"""
|
||||
Callback method invoked for every record.
|
||||
|
||||
See base class method for more details.
|
||||
"""
|
||||
|
||||
record = {}
|
||||
for key in ('prefix', 'designation', 'date', 'whois', 'status'):
|
||||
record[key] = str(rec.get(key, '')).strip()
|
||||
|
||||
# Strip leading zeros from octet.
|
||||
if '/' in record['prefix']:
|
||||
(octet, prefix) = record['prefix'].split('/')
|
||||
record['prefix'] = '%d/%d' % (int(octet), int(prefix))
|
||||
|
||||
record['status'] = record['status'].capitalize()
|
||||
|
||||
return record
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class IPv6Parser(XMLRecordParser):
|
||||
"""
|
||||
A XMLRecordParser that understands how to parse and retrieve data records
|
||||
from the IANA IPv6 address space file.
|
||||
|
||||
It can be found online here :-
|
||||
|
||||
- http://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xml
|
||||
"""
|
||||
def __init__(self, fh, **kwargs):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
fh - a valid, open file handle to an IANA IPv6 address space file.
|
||||
|
||||
kwargs - additional parser options.
|
||||
"""
|
||||
super(IPv6Parser, self).__init__(fh)
|
||||
|
||||
def process_record(self, rec):
|
||||
"""
|
||||
Callback method invoked for every record.
|
||||
|
||||
See base class method for more details.
|
||||
"""
|
||||
|
||||
record = {
|
||||
'prefix': str(rec.get('prefix', '')).strip(),
|
||||
'allocation': str(rec.get('description', '')).strip(),
|
||||
'reference': str(rec.get('rfc', [''])[0]).strip(),
|
||||
}
|
||||
|
||||
return record
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class MulticastParser(XMLRecordParser):
|
||||
"""
|
||||
A XMLRecordParser that knows how to process the IANA IPv4 multicast address
|
||||
allocation file.
|
||||
|
||||
It can be found online here :-
|
||||
|
||||
- http://www.iana.org/assignments/multicast-addresses/multicast-addresses.xml
|
||||
"""
|
||||
def __init__(self, fh, **kwargs):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
fh - a valid, open file handle to an IANA IPv4 multicast address
|
||||
allocation file.
|
||||
|
||||
kwargs - additional parser options.
|
||||
"""
|
||||
super(MulticastParser, self).__init__(fh)
|
||||
|
||||
def normalise_addr(self, addr):
|
||||
"""
|
||||
Removes variations from address entries found in this particular file.
|
||||
"""
|
||||
if '-' in addr:
|
||||
(a1, a2) = addr.split('-')
|
||||
o1 = a1.strip().split('.')
|
||||
o2 = a2.strip().split('.')
|
||||
return '%s-%s' % ('.'.join([str(int(i)) for i in o1]),
|
||||
'.'.join([str(int(i)) for i in o2]))
|
||||
else:
|
||||
o1 = addr.strip().split('.')
|
||||
return '.'.join([str(int(i)) for i in o1])
|
||||
|
||||
def process_record(self, rec):
|
||||
"""
|
||||
Callback method invoked for every record.
|
||||
|
||||
See base class method for more details.
|
||||
"""
|
||||
|
||||
if 'addr' in rec:
|
||||
record = {
|
||||
'address': self.normalise_addr(str(rec['addr'])),
|
||||
'descr': str(rec.get('description', '')),
|
||||
}
|
||||
return record
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class DictUpdater(Subscriber):
|
||||
"""
|
||||
Concrete Subscriber that inserts records received from a Publisher into a
|
||||
dictionary.
|
||||
"""
|
||||
def __init__(self, dct, topic, unique_key):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
dct - lookup dict or dict like object to insert records into.
|
||||
|
||||
topic - high-level category name of data to be processed.
|
||||
|
||||
unique_key - key name in data dict that uniquely identifies it.
|
||||
"""
|
||||
self.dct = dct
|
||||
self.topic = topic
|
||||
self.unique_key = unique_key
|
||||
|
||||
def update(self, data):
|
||||
"""
|
||||
Callback function used by Publisher to notify this Subscriber about
|
||||
an update. Stores topic based information into dictionary passed to
|
||||
constructor.
|
||||
"""
|
||||
data_id = data[self.unique_key]
|
||||
|
||||
if self.topic == 'IPv4':
|
||||
cidr = IPNetwork(cidr_abbrev_to_verbose(data_id))
|
||||
self.dct[cidr] = data
|
||||
elif self.topic == 'IPv6':
|
||||
cidr = IPNetwork(cidr_abbrev_to_verbose(data_id))
|
||||
self.dct[cidr] = data
|
||||
elif self.topic == 'multicast':
|
||||
iprange = None
|
||||
if '-' in data_id:
|
||||
# See if we can manage a single CIDR.
|
||||
(first, last) = data_id.split('-')
|
||||
iprange = IPRange(first, last)
|
||||
cidrs = iprange.cidrs()
|
||||
if len(cidrs) == 1:
|
||||
iprange = cidrs[0]
|
||||
else:
|
||||
iprange = IPAddress(data_id)
|
||||
self.dct[iprange] = data
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def load_info():
|
||||
"""
|
||||
Parse and load internal IANA data lookups with the latest information from
|
||||
data files.
|
||||
"""
|
||||
PATH = _path.dirname(__file__)
|
||||
|
||||
ipv4 = IPv4Parser(open(_path.join(PATH, 'ipv4-address-space.xml')))
|
||||
ipv4.attach(DictUpdater(IANA_INFO['IPv4'], 'IPv4', 'prefix'))
|
||||
ipv4.parse()
|
||||
|
||||
ipv6 = IPv6Parser(open(_path.join(PATH, 'ipv6-address-space.xml')))
|
||||
ipv6.attach(DictUpdater(IANA_INFO['IPv6'], 'IPv6', 'prefix'))
|
||||
ipv6.parse()
|
||||
|
||||
mcast = MulticastParser(open(_path.join(PATH, 'multicast-addresses.xml')))
|
||||
mcast.attach(DictUpdater(IANA_INFO['multicast'], 'multicast', 'address'))
|
||||
mcast.parse()
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def pprint_info(fh=None):
|
||||
"""
|
||||
Pretty prints IANA information to filehandle.
|
||||
"""
|
||||
if fh is None:
|
||||
fh = _sys.stdout
|
||||
|
||||
for category in sorted(IANA_INFO):
|
||||
fh.write('-' * len(category) + "\n")
|
||||
fh.write(category + "\n")
|
||||
fh.write('-' * len(category) + "\n")
|
||||
ipranges = IANA_INFO[category]
|
||||
for iprange in sorted(ipranges):
|
||||
details = ipranges[iprange]
|
||||
fh.write('%-45r' % (iprange) + details + "\n")
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def query(ip_addr):
|
||||
"""
|
||||
Returns informational data specific to this IP address.
|
||||
"""
|
||||
info = {}
|
||||
|
||||
def within_bounds(ip, ip_range):
|
||||
# Boundary checking for multiple IP classes.
|
||||
if hasattr(ip_range, 'first'):
|
||||
# IP network or IP range.
|
||||
return ip in ip_range
|
||||
elif hasattr(ip_range, 'value'):
|
||||
# IP address.
|
||||
return ip == ip_range
|
||||
|
||||
raise Exception('Unsupported IP range or address: %r!' % ip_range)
|
||||
|
||||
if ip_addr.version == 4:
|
||||
for cidr, record in _dict_items(IANA_INFO['IPv4']):
|
||||
if within_bounds(ip_addr, cidr):
|
||||
info.setdefault('IPv4', [])
|
||||
info['IPv4'].append(record)
|
||||
|
||||
if ip_addr.is_multicast():
|
||||
for iprange, record in _dict_items(IANA_INFO['multicast']):
|
||||
if within_bounds(ip_addr, iprange):
|
||||
info.setdefault('Multicast', [])
|
||||
info['Multicast'].append(record)
|
||||
|
||||
elif ip_addr.version == 6:
|
||||
for cidr, record in _dict_items(IANA_INFO['IPv6']):
|
||||
if within_bounds(ip_addr, cidr):
|
||||
info.setdefault('IPv6', [])
|
||||
info['IPv6'].append(record)
|
||||
|
||||
return info
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def get_latest_files():
|
||||
"""Download the latest files from IANA"""
|
||||
if _sys.version_info[0] == 3:
|
||||
# Python 3.x
|
||||
from urllib.request import Request, urlopen
|
||||
else:
|
||||
# Python 2.x
|
||||
from urllib2 import Request, urlopen
|
||||
|
||||
urls = [
|
||||
'http://www.iana.org/assignments/ipv4-address-space/ipv4-address-space.xml',
|
||||
'http://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xml',
|
||||
'http://www.iana.org/assignments/multicast-addresses/multicast-addresses.xml',
|
||||
]
|
||||
|
||||
for url in urls:
|
||||
_sys.stdout.write('downloading latest copy of %s\n' % url)
|
||||
request = Request(url)
|
||||
response = urlopen(request)
|
||||
save_path = _path.dirname(__file__)
|
||||
basename = _os.path.basename(response.geturl().rstrip('/'))
|
||||
filename = _path.join(save_path, basename)
|
||||
fh = open(filename, 'wb')
|
||||
fh.write(response.read())
|
||||
fh.close()
|
||||
|
||||
# Make sure the line endings are consistent across platforms.
|
||||
dos2unix(filename)
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
if __name__ == '__main__':
|
||||
# Generate indices when module is executed as a script.
|
||||
get_latest_files()
|
||||
|
||||
# On module import, read IANA data files and populate lookups dict.
|
||||
load_info()
|
||||
@@ -0,0 +1,523 @@
|
||||
"""Immutable integer set type.
|
||||
|
||||
Integer set class.
|
||||
|
||||
Copyright (C) 2010, David Moss.
|
||||
Ported to Python 3.x.
|
||||
|
||||
Copyright (C) 2006, Heiko Wundram.
|
||||
Released under the MIT license:
|
||||
|
||||
Copyright (c) 2006, Heiko Wundram.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
# Version information
|
||||
# -------------------
|
||||
|
||||
__author__ = "Heiko Wundram <me@modelnine.org>"
|
||||
__version__ = "0.2"
|
||||
__revision__ = "7"
|
||||
__date__ = "2006-01-23"
|
||||
|
||||
|
||||
# Utility classes
|
||||
# ---------------
|
||||
|
||||
import sys as _sys
|
||||
|
||||
# Not the most efficient way of dealing with the int/long issue in Python 3.x
|
||||
# but it requires the least amount of code changes.
|
||||
|
||||
# number of code changes.
|
||||
if _sys.version_info[0] == 3:
|
||||
# Python 3.x
|
||||
_long = int
|
||||
else:
|
||||
# Python 2.x
|
||||
_long = long
|
||||
|
||||
from netaddr.compat import _func_name, _func_doc
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class _Infinity(object):
|
||||
"""Internal type used to represent infinity values."""
|
||||
|
||||
__slots__ = ["_neg"]
|
||||
|
||||
def __init__(self, neg):
|
||||
self._neg = neg
|
||||
|
||||
def __lt__(self, value):
|
||||
if not isinstance(value, (int, _long, _Infinity)):
|
||||
return NotImplemented
|
||||
return ( self._neg and
|
||||
not ( isinstance(value, _Infinity) and value._neg ) )
|
||||
|
||||
def __le__(self, value):
|
||||
if not isinstance(value, (int, _long, _Infinity)):
|
||||
return NotImplemented
|
||||
return self._neg
|
||||
|
||||
def __gt__(self, value):
|
||||
if not isinstance(value, (int, _long, _Infinity)):
|
||||
return NotImplemented
|
||||
return not ( self._neg or
|
||||
( isinstance(value, _Infinity) and not value._neg ) )
|
||||
|
||||
def __ge__(self, value):
|
||||
if not isinstance(value, (int, _long, _Infinity)):
|
||||
return NotImplemented
|
||||
return not self._neg
|
||||
|
||||
def __eq__(self, value):
|
||||
if not isinstance(value, (int, _long, _Infinity)):
|
||||
return NotImplemented
|
||||
return isinstance(value, _Infinity) and self._neg == value._neg
|
||||
|
||||
def __ne__(self, value):
|
||||
if not isinstance(value, (int, _long, _Infinity)):
|
||||
return NotImplemented
|
||||
return not isinstance(value, _Infinity) or self._neg != value._neg
|
||||
|
||||
def __repr__(self):
|
||||
return "None"
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
_MININF = _Infinity(True)
|
||||
_MAXINF = _Infinity(False)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class IntSet(object):
|
||||
"""Integer set class with efficient storage in a RLE format of ranges.
|
||||
Supports minus and plus infinity in the range."""
|
||||
|
||||
__slots__ = ["_ranges", "_min", "_max", "_hash"]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize an integer set. The constructor accepts an unlimited
|
||||
number of arguments that may either be tuples in the form of
|
||||
(start, stop) where either start or stop may be a number or None to
|
||||
represent maximum/minimum in that direction. The range specified by
|
||||
(start, stop) is always inclusive (differing from the builtin range
|
||||
operator).
|
||||
|
||||
Keyword arguments that can be passed to an integer set are min and
|
||||
max, which specify the minimum and maximum number in the set,
|
||||
respectively. You can also pass None here to represent minus or plus
|
||||
infinity, which is also the default.
|
||||
"""
|
||||
|
||||
# Special case copy constructor.
|
||||
if len(args) == 1 and isinstance(args[0], IntSet):
|
||||
if kwargs:
|
||||
raise ValueError("No keyword arguments for copy constructor.")
|
||||
self._min = args[0]._min
|
||||
self._max = args[0]._max
|
||||
self._ranges = args[0]._ranges
|
||||
self._hash = args[0]._hash
|
||||
return
|
||||
|
||||
# Initialize set.
|
||||
self._ranges = []
|
||||
|
||||
# Process keyword arguments.
|
||||
self._min = kwargs.pop("min", _MININF)
|
||||
self._max = kwargs.pop("max", _MAXINF)
|
||||
if self._min is None:
|
||||
self._min = _MININF
|
||||
if self._max is None:
|
||||
self._max = _MAXINF
|
||||
|
||||
# Check keyword arguments.
|
||||
if kwargs:
|
||||
raise ValueError("Invalid keyword argument.")
|
||||
if not ( isinstance(self._min, (int, _long)) or self._min is _MININF ):
|
||||
raise TypeError("Invalid type of min argument.")
|
||||
if not ( isinstance(self._max, (int, _long)) or self._max is _MAXINF ):
|
||||
raise TypeError("Invalid type of max argument.")
|
||||
if ( self._min is not _MININF and self._max is not _MAXINF and
|
||||
self._min > self._max ):
|
||||
raise ValueError("Minimum is not smaller than maximum.")
|
||||
if isinstance(self._max, (int, _long)):
|
||||
self._max += 1
|
||||
|
||||
# Process arguments.
|
||||
for arg in args:
|
||||
if isinstance(arg, (int, _long)):
|
||||
start, stop = arg, arg+1
|
||||
elif isinstance(arg, tuple):
|
||||
if len(arg) != 2:
|
||||
raise ValueError("Invalid tuple, must be (start,stop).")
|
||||
|
||||
# Process argument.
|
||||
start, stop = arg
|
||||
if start is None:
|
||||
start = self._min
|
||||
if stop is None:
|
||||
stop = self._max
|
||||
|
||||
# Check arguments.
|
||||
if not ( isinstance(start, (int, _long)) or start is _MININF ):
|
||||
raise TypeError("Invalid type of tuple start.")
|
||||
if not ( isinstance(stop, (int, _long)) or stop is _MAXINF ):
|
||||
raise TypeError("Invalid type of tuple stop.")
|
||||
if ( start is not _MININF and stop is not _MAXINF and
|
||||
start > stop ):
|
||||
continue
|
||||
if isinstance(stop, (int, _long)):
|
||||
stop += 1
|
||||
else:
|
||||
raise TypeError("Invalid argument.")
|
||||
|
||||
if start > self._max:
|
||||
continue
|
||||
elif start < self._min:
|
||||
start = self._min
|
||||
if stop < self._min:
|
||||
continue
|
||||
elif stop > self._max:
|
||||
stop = self._max
|
||||
self._ranges.append((start, stop))
|
||||
|
||||
# Normalize set.
|
||||
self._normalize()
|
||||
|
||||
# Utility functions for set operations
|
||||
# ------------------------------------
|
||||
|
||||
def _iterranges(self, r1, r2, minval=_MININF, maxval=_MAXINF):
|
||||
curval = minval
|
||||
curstates = {"r1":False, "r2":False}
|
||||
imax, jmax = 2*len(r1), 2*len(r2)
|
||||
i, j = 0, 0
|
||||
while i < imax or j < jmax:
|
||||
if i < imax and ( ( j < jmax and
|
||||
r1[i>>1][i&1] < r2[j>>1][j&1] ) or
|
||||
j == jmax ):
|
||||
cur_r, newname, newstate = r1[i>>1][i&1], "r1", not (i&1)
|
||||
i += 1
|
||||
else:
|
||||
cur_r, newname, newstate = r2[j>>1][j&1], "r2", not (j&1)
|
||||
j += 1
|
||||
if curval < cur_r:
|
||||
if cur_r > maxval:
|
||||
break
|
||||
yield curstates, (curval, cur_r)
|
||||
curval = cur_r
|
||||
curstates[newname] = newstate
|
||||
if curval < maxval:
|
||||
yield curstates, (curval, maxval)
|
||||
|
||||
def _normalize(self):
|
||||
self._ranges.sort()
|
||||
i = 1
|
||||
while i < len(self._ranges):
|
||||
if self._ranges[i][0] < self._ranges[i-1][1]:
|
||||
self._ranges[i-1] = (self._ranges[i-1][0],
|
||||
max(self._ranges[i-1][1],
|
||||
self._ranges[i][1]))
|
||||
del self._ranges[i]
|
||||
else:
|
||||
i += 1
|
||||
self._ranges = tuple(self._ranges)
|
||||
self._hash = hash(self._ranges)
|
||||
|
||||
def __coerce__(self, other):
|
||||
if isinstance(other, IntSet):
|
||||
return self, other
|
||||
elif isinstance(other, (int, _long, tuple)):
|
||||
try:
|
||||
return self, self.__class__(other)
|
||||
except TypeError:
|
||||
# Catch a type error, in that case the structure specified by
|
||||
# other is something we can't coerce, return NotImplemented.
|
||||
# ValueErrors are not caught, they signal that the data was
|
||||
# invalid for the constructor. This is appropriate to signal
|
||||
# as a ValueError to the caller.
|
||||
return NotImplemented
|
||||
elif isinstance(other, list):
|
||||
try:
|
||||
return self, self.__class__(*other)
|
||||
except TypeError:
|
||||
# See above.
|
||||
return NotImplemented
|
||||
return NotImplemented
|
||||
|
||||
# Set function definitions
|
||||
# ------------------------
|
||||
|
||||
def _make_function(name, type, doc, pall, pany=None):
|
||||
"""Makes a function to match two ranges. Accepts two types: either
|
||||
'set', which defines a function which returns a set with all ranges
|
||||
matching pall (pany is ignored), or 'bool', which returns True if pall
|
||||
matches for all ranges and pany matches for any one range. doc is the
|
||||
dostring to give this function. pany may be none to ignore the any
|
||||
match.
|
||||
|
||||
The predicates get a dict with two keys, 'r1', 'r2', which denote
|
||||
whether the current range is present in range1 (self) and/or range2
|
||||
(other) or none of the two, respectively."""
|
||||
|
||||
if type == "set":
|
||||
def f(self, other):
|
||||
coerced = self.__coerce__(other)
|
||||
if coerced is NotImplemented:
|
||||
return NotImplemented
|
||||
other = coerced[1]
|
||||
newset = self.__class__.__new__(self.__class__)
|
||||
newset._min = min(self._min, other._min)
|
||||
newset._max = max(self._max, other._max)
|
||||
newset._ranges = []
|
||||
for states, (start, stop) in \
|
||||
self._iterranges(self._ranges, other._ranges,
|
||||
newset._min, newset._max):
|
||||
if pall(states):
|
||||
if newset._ranges and newset._ranges[-1][1] == start:
|
||||
newset._ranges[-1] = (newset._ranges[-1][0], stop)
|
||||
else:
|
||||
newset._ranges.append((start, stop))
|
||||
newset._ranges = tuple(newset._ranges)
|
||||
newset._hash = hash(self._ranges)
|
||||
return newset
|
||||
elif type == "bool":
|
||||
def f(self, other):
|
||||
coerced = self.__coerce__(other)
|
||||
if coerced is NotImplemented:
|
||||
return NotImplemented
|
||||
other = coerced[1]
|
||||
_min = min(self._min, other._min)
|
||||
_max = max(self._max, other._max)
|
||||
found = not pany
|
||||
for states, (start, stop) in \
|
||||
self._iterranges(self._ranges, other._ranges,
|
||||
_min, _max):
|
||||
if not pall(states):
|
||||
return False
|
||||
found = found or pany(states)
|
||||
return found
|
||||
else:
|
||||
raise ValueError("Invalid type of function to create.")
|
||||
_func_name(f, name)
|
||||
_func_doc(f, doc)
|
||||
return f
|
||||
|
||||
# Intersection.
|
||||
__and__ = _make_function("__and__", "set",
|
||||
"Intersection of two sets as a new set.",
|
||||
lambda s: s["r1"] and s["r2"])
|
||||
__rand__ = _make_function("__rand__", "set",
|
||||
"Intersection of two sets as a new set.",
|
||||
lambda s: s["r1"] and s["r2"])
|
||||
intersection = _make_function("intersection", "set",
|
||||
"Intersection of two sets as a new set.",
|
||||
lambda s: s["r1"] and s["r2"])
|
||||
|
||||
# Union.
|
||||
__or__ = _make_function("__or__", "set",
|
||||
"Union of two sets as a new set.",
|
||||
lambda s: s["r1"] or s["r2"])
|
||||
__ror__ = _make_function("__ror__", "set",
|
||||
"Union of two sets as a new set.",
|
||||
lambda s: s["r1"] or s["r2"])
|
||||
union = _make_function("union", "set",
|
||||
"Union of two sets as a new set.",
|
||||
lambda s: s["r1"] or s["r2"])
|
||||
|
||||
# Difference.
|
||||
__sub__ = _make_function("__sub__", "set",
|
||||
"Difference of two sets as a new set.",
|
||||
lambda s: s["r1"] and not s["r2"])
|
||||
__rsub__ = _make_function("__rsub__", "set",
|
||||
"Difference of two sets as a new set.",
|
||||
lambda s: s["r2"] and not s["r1"])
|
||||
difference = _make_function("difference", "set",
|
||||
"Difference of two sets as a new set.",
|
||||
lambda s: s["r1"] and not s["r2"])
|
||||
|
||||
# Symmetric difference.
|
||||
__xor__ = _make_function("__xor__", "set",
|
||||
"Symmetric difference of two sets as a new set.",
|
||||
lambda s: s["r1"] ^ s["r2"])
|
||||
__rxor__ = _make_function("__rxor__", "set",
|
||||
"Symmetric difference of two sets as a new set.",
|
||||
lambda s: s["r1"] ^ s["r2"])
|
||||
symmetric_difference = _make_function("symmetric_difference", "set",
|
||||
"Symmetric difference of two sets as a new set.",
|
||||
lambda s: s["r1"] ^ s["r2"])
|
||||
|
||||
# Containership testing.
|
||||
__contains__ = _make_function("__contains__", "bool",
|
||||
"Returns true if self is superset of other.",
|
||||
lambda s: s["r1"] or not s["r2"])
|
||||
issubset = _make_function("issubset", "bool",
|
||||
"Returns true if self is subset of other.",
|
||||
lambda s: s["r2"] or not s["r1"])
|
||||
istruesubset = _make_function("istruesubset", "bool",
|
||||
"Returns true if self is true subset of other.",
|
||||
lambda s: s["r2"] or not s["r1"],
|
||||
lambda s: s["r2"] and not s["r1"])
|
||||
issuperset = _make_function("issuperset", "bool",
|
||||
"Returns true if self is superset of other.",
|
||||
lambda s: s["r1"] or not s["r2"])
|
||||
istruesuperset = _make_function("istruesuperset", "bool",
|
||||
"Returns true if self is true superset of other.",
|
||||
lambda s: s["r1"] or not s["r2"],
|
||||
lambda s: s["r1"] and not s["r2"])
|
||||
overlaps = _make_function("overlaps", "bool",
|
||||
"Returns true if self overlaps with other.",
|
||||
lambda s: True,
|
||||
lambda s: s["r1"] and s["r2"])
|
||||
|
||||
# Comparison.
|
||||
__eq__ = _make_function("__eq__", "bool",
|
||||
"Returns true if self is equal to other.",
|
||||
lambda s: not ( s["r1"] ^ s["r2"] ))
|
||||
__ne__ = _make_function("__ne__", "bool",
|
||||
"Returns true if self is different to other.",
|
||||
lambda s: True,
|
||||
lambda s: s["r1"] ^ s["r2"])
|
||||
|
||||
# Clean up namespace.
|
||||
del _make_function
|
||||
|
||||
# Define other functions.
|
||||
def inverse(self):
|
||||
"""Inverse of set as a new set."""
|
||||
|
||||
newset = self.__class__.__new__(self.__class__)
|
||||
newset._min = self._min
|
||||
newset._max = self._max
|
||||
newset._ranges = []
|
||||
laststop = self._min
|
||||
for r in self._ranges:
|
||||
if laststop < r[0]:
|
||||
newset._ranges.append((laststop, r[0]))
|
||||
laststop = r[1]
|
||||
if laststop < self._max:
|
||||
newset._ranges.append((laststop, self._max))
|
||||
return newset
|
||||
|
||||
__invert__ = inverse
|
||||
|
||||
# Hashing
|
||||
# -------
|
||||
|
||||
def __hash__(self):
|
||||
"""Returns a hash value representing this integer set. As the set is
|
||||
always stored normalized, the hash value is guaranteed to match for
|
||||
matching ranges."""
|
||||
|
||||
return self._hash
|
||||
|
||||
# Iterating
|
||||
# ---------
|
||||
|
||||
def __len__(self):
|
||||
"""Get length of this integer set. In case the length is larger than
|
||||
2**31 (including infinitely sized integer sets), it raises an
|
||||
OverflowError. This is due to len() restricting the size to
|
||||
0 <= len < 2**31."""
|
||||
|
||||
if not self._ranges:
|
||||
return 0
|
||||
if self._ranges[0][0] is _MININF or self._ranges[-1][1] is _MAXINF:
|
||||
raise OverflowError("Infinitely sized integer set.")
|
||||
rlen = 0
|
||||
for r in self._ranges:
|
||||
rlen += r[1]-r[0]
|
||||
if rlen >= 2**31:
|
||||
raise OverflowError("Integer set bigger than 2**31.")
|
||||
return rlen
|
||||
|
||||
def len(self):
|
||||
"""Returns the length of this integer set as an integer. In case the
|
||||
length is infinite, returns -1. This function exists because of a
|
||||
limitation of the builtin len() function which expects values in
|
||||
the range 0 <= len < 2**31. Use this function in case your integer
|
||||
set might be larger."""
|
||||
|
||||
if not self._ranges:
|
||||
return 0
|
||||
if self._ranges[0][0] is _MININF or self._ranges[-1][1] is _MAXINF:
|
||||
return -1
|
||||
rlen = 0
|
||||
for r in self._ranges:
|
||||
rlen += r[1]-r[0]
|
||||
return rlen
|
||||
|
||||
def __nonzero__(self):
|
||||
"""Returns true if this integer set contains at least one item."""
|
||||
# Python 2.x
|
||||
return bool(self._ranges)
|
||||
|
||||
__bool__ = __nonzero__ # Python 3.x
|
||||
|
||||
def __iter__(self):
|
||||
"""Iterate over all values in this integer set. Iteration always starts
|
||||
by iterating from lowest to highest over the ranges that are bounded.
|
||||
After processing these, all ranges that are unbounded (maximum 2) are
|
||||
yielded intermixed."""
|
||||
|
||||
ubranges = []
|
||||
for r in self._ranges:
|
||||
if r[0] is _MININF:
|
||||
if r[1] is _MAXINF:
|
||||
ubranges.extend(([0, 1], [-1, -1]))
|
||||
else:
|
||||
ubranges.append([r[1]-1, -1])
|
||||
elif r[1] is _MAXINF:
|
||||
ubranges.append([r[0], 1])
|
||||
else:
|
||||
# Little hackish, but bombs out on 32-bit platforms if using
|
||||
# xrange.
|
||||
val = r[0]
|
||||
while val < r[1]:
|
||||
yield val
|
||||
val += 1
|
||||
if ubranges:
|
||||
while True:
|
||||
for ubrange in ubranges:
|
||||
yield ubrange[0]
|
||||
ubrange[0] += ubrange[1]
|
||||
|
||||
# Printing
|
||||
# --------
|
||||
|
||||
def __repr__(self):
|
||||
"""Return a representation of this integer set. The representation is
|
||||
executable to get an equal integer set."""
|
||||
|
||||
rv = []
|
||||
for start, stop in self._ranges:
|
||||
if ( isinstance(start, (int, _long)) and \
|
||||
isinstance(stop, (int, _long))
|
||||
and stop-start == 1 ):
|
||||
rv.append("%r" % start)
|
||||
elif isinstance(stop, (int, _long)):
|
||||
rv.append("(%r,%r)" % (start, stop-1))
|
||||
else:
|
||||
rv.append("(%r,%r)" % (start, stop))
|
||||
if self._min is not _MININF:
|
||||
rv.append("min=%r" % self._min)
|
||||
if self._max is not _MAXINF:
|
||||
rv.append("max=%r" % self._max)
|
||||
return "%s(%s)" % (self.__class__.__name__, ",".join(rv))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,144 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<?xml-stylesheet type="text/xsl" href="ipv6-address-space.xsl"?>
|
||||
<?oxygen RNGSchema="ipv6-address-space.rng" type="xml"?>
|
||||
<registry xmlns="http://www.iana.org/assignments" id="ipv6-address-space">
|
||||
<title>Internet Protocol Version 6 Address Space</title>
|
||||
<updated>2012-08-02</updated>
|
||||
<note>The IPv6 address management function was formally delegated to
|
||||
IANA in December 1995 <xref type="rfc" data="rfc1881"/>. The registration procedure
|
||||
was confirmed with the IETF Chair in March 2010.</note>
|
||||
<registry id="ipv6-address-space-1">
|
||||
<registration_rule>IESG Approval</registration_rule>
|
||||
<record>
|
||||
<prefix>0000::/8</prefix>
|
||||
<description>Reserved by IETF</description>
|
||||
<xref type="rfc" data="rfc4291"/>
|
||||
<xref type="note" data="1"/>
|
||||
<xref type="note" data="5"/>
|
||||
<xref type="note" data="6"/>
|
||||
</record>
|
||||
<record>
|
||||
<prefix>0100::/8</prefix>
|
||||
<description>Reserved by IETF</description>
|
||||
<xref type="rfc" data="rfc4291"/>
|
||||
<xref type="note" data="8"/>
|
||||
</record>
|
||||
<record>
|
||||
<prefix>0200::/7</prefix>
|
||||
<description>Reserved by IETF</description>
|
||||
<xref type="rfc" data="rfc4048"/>
|
||||
<xref type="note" data="2"/>
|
||||
</record>
|
||||
<record>
|
||||
<prefix>0400::/6</prefix>
|
||||
<description>Reserved by IETF</description>
|
||||
<xref type="rfc" data="rfc4291"/>
|
||||
</record>
|
||||
<record>
|
||||
<prefix>0800::/5</prefix>
|
||||
<description>Reserved by IETF</description>
|
||||
<xref type="rfc" data="rfc4291"/>
|
||||
</record>
|
||||
<record>
|
||||
<prefix>1000::/4</prefix>
|
||||
<description>Reserved by IETF</description>
|
||||
<xref type="rfc" data="rfc4291"/>
|
||||
</record>
|
||||
<record>
|
||||
<prefix>2000::/3</prefix>
|
||||
<description>Global Unicast</description>
|
||||
<xref type="rfc" data="rfc4291"/>
|
||||
<xref type="note" data="3"/>
|
||||
</record>
|
||||
<record>
|
||||
<prefix>4000::/3</prefix>
|
||||
<description>Reserved by IETF</description>
|
||||
<xref type="rfc" data="rfc4291"/>
|
||||
</record>
|
||||
<record>
|
||||
<prefix>6000::/3</prefix>
|
||||
<description>Reserved by IETF</description>
|
||||
<xref type="rfc" data="rfc4291"/>
|
||||
</record>
|
||||
<record>
|
||||
<prefix>8000::/3</prefix>
|
||||
<description>Reserved by IETF</description>
|
||||
<xref type="rfc" data="rfc4291"/>
|
||||
</record>
|
||||
<record>
|
||||
<prefix>A000::/3</prefix>
|
||||
<description>Reserved by IETF</description>
|
||||
<xref type="rfc" data="rfc4291"/>
|
||||
</record>
|
||||
<record>
|
||||
<prefix>C000::/3</prefix>
|
||||
<description>Reserved by IETF</description>
|
||||
<xref type="rfc" data="rfc4291"/>
|
||||
</record>
|
||||
<record>
|
||||
<prefix>E000::/4</prefix>
|
||||
<description>Reserved by IETF</description>
|
||||
<xref type="rfc" data="rfc4291"/>
|
||||
</record>
|
||||
<record>
|
||||
<prefix>F000::/5</prefix>
|
||||
<description>Reserved by IETF</description>
|
||||
<xref type="rfc" data="rfc4291"/>
|
||||
</record>
|
||||
<record>
|
||||
<prefix>F800::/6</prefix>
|
||||
<description>Reserved by IETF</description>
|
||||
<xref type="rfc" data="rfc4291"/>
|
||||
</record>
|
||||
<record>
|
||||
<prefix>FC00::/7</prefix>
|
||||
<description>Unique Local Unicast</description>
|
||||
<xref type="rfc" data="rfc4193"/>
|
||||
</record>
|
||||
<record>
|
||||
<prefix>FE00::/9</prefix>
|
||||
<description>Reserved by IETF</description>
|
||||
<xref type="rfc" data="rfc4291"/>
|
||||
</record>
|
||||
<record>
|
||||
<prefix>FE80::/10</prefix>
|
||||
<description>Link Local Unicast</description>
|
||||
<xref type="rfc" data="rfc4291"/>
|
||||
</record>
|
||||
<record>
|
||||
<prefix>FEC0::/10</prefix>
|
||||
<description>Reserved by IETF</description>
|
||||
<xref type="rfc" data="rfc3879"/>
|
||||
<xref type="note" data="4"/>
|
||||
</record>
|
||||
<record>
|
||||
<prefix>FF00::/8</prefix>
|
||||
<description>Multicast</description>
|
||||
<xref type="rfc" data="rfc4291"/>
|
||||
<xref type="note" data="7"/>
|
||||
</record>
|
||||
<footnote anchor="1">The "unspecified address", the "loopback address", and the IPv6
|
||||
Addresses with Embedded IPv4 Addresses are assigned out of the
|
||||
0000::/8 address block.</footnote>
|
||||
<footnote anchor="2">0200::/7 was previously defined as an OSI NSAP-mapped prefix set
|
||||
<xref type="rfc" data="rfc4548"/>. This definition has been deprecated as of December
|
||||
2004 <xref type="rfc" data="rfc4048"/>.</footnote>
|
||||
<footnote anchor="3">The IPv6 Unicast space encompasses the entire IPv6 address range
|
||||
with the exception of FF00::/8. <xref type="rfc" data="rfc4291"/> IANA unicast address
|
||||
assignments are currently limited to the IPv6 unicast address
|
||||
range of 2000::/3. IANA assignments from this block are registered
|
||||
in the IANA registry: <xref type="registry" data="ipv6-unicast-address-assignments"/>.</footnote>
|
||||
<footnote anchor="4">FEC0::/10 was previously defined as a Site-Local scoped address
|
||||
prefix. This definition has been deprecated as of September 2004
|
||||
<xref type="rfc" data="rfc3879"/>.</footnote>
|
||||
<footnote anchor="5">0000::/96 was previously defined as the "IPv4-compatible IPv6
|
||||
address" prefix. This definition has been deprecated by <xref type="rfc" data="rfc4291"/>.</footnote>
|
||||
<footnote anchor="6">The "Well Known Prefix" 64:ff9b::/96 used in an algorithmic
|
||||
mapping between IPv4 to IPv6 addresses is defined out of the
|
||||
0000::/8 address block, per <xref type="rfc" data="rfc6052"/>.</footnote>
|
||||
<footnote anchor="7">IANA assignments from this block are registered
|
||||
in the IPv6 Multicast Address Space Registry: <xref type="registry" data="ipv6-multicast-addresses"/>.</footnote>
|
||||
<footnote anchor="8">0100::/64 is assigned as a Discard-Only Prefix for remote triggered blackhole routing as per <xref type="rfc" data="rfc6666"/>.</footnote>
|
||||
<people/>
|
||||
</registry>
|
||||
</registry>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,101 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2008-2012, David P. D. Moss. All rights reserved.
|
||||
#
|
||||
# Released under the BSD license. See the LICENSE file for details.
|
||||
#-----------------------------------------------------------------------------
|
||||
"""
|
||||
Routines for dealing with nmap-style IPv4 address ranges.
|
||||
|
||||
Based on nmap's Target Specification :-
|
||||
|
||||
http://nmap.org/book/man-target-specification.html
|
||||
"""
|
||||
|
||||
from netaddr.core import AddrFormatError
|
||||
from netaddr.ip import IPAddress
|
||||
from netaddr.compat import _iter_range, _is_str
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def _nmap_octet_target_values(spec):
|
||||
# Generates sequence of values for an individual octet as defined in the
|
||||
# nmap Target Specification.
|
||||
values = set()
|
||||
|
||||
for element in spec.split(','):
|
||||
if '-' in element:
|
||||
left, right = element.split('-', 1)
|
||||
if not left:
|
||||
left = 0
|
||||
if not right:
|
||||
right = 255
|
||||
low = int(left)
|
||||
high = int(right)
|
||||
if not ((0 <= low <= 255) and (0 <= high <= 255)):
|
||||
raise ValueError('octet value overflow for spec %s!' % spec)
|
||||
if low > high:
|
||||
raise ValueError('left side of hyphen must be < right %r' % element)
|
||||
for octet in _iter_range(low, high + 1):
|
||||
values.add(octet)
|
||||
else:
|
||||
octet = int(element)
|
||||
if not (0 <= octet <= 255):
|
||||
raise ValueError('octet value overflow for spec %s!' % spec)
|
||||
values.add(octet)
|
||||
|
||||
return sorted(values)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def _generate_nmap_octet_ranges(nmap_target_spec):
|
||||
# Generate 4 lists containing all octets defined by a given nmap Target
|
||||
# specification.
|
||||
if not _is_str(nmap_target_spec):
|
||||
raise TypeError('string expected, not %s' % type(nmap_target_spec))
|
||||
|
||||
if not nmap_target_spec:
|
||||
raise ValueError('nmap target specification cannot be blank!')
|
||||
|
||||
tokens = nmap_target_spec.split('.')
|
||||
|
||||
if len(tokens) != 4:
|
||||
raise AddrFormatError('invalid nmap range: %s' % nmap_target_spec)
|
||||
|
||||
if tokens[0] == '-':
|
||||
raise AddrFormatError('first octet cannot be a sole hyphen!')
|
||||
|
||||
return (_nmap_octet_target_values(tokens[0]),
|
||||
_nmap_octet_target_values(tokens[1]),
|
||||
_nmap_octet_target_values(tokens[2]),
|
||||
_nmap_octet_target_values(tokens[3]))
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def valid_nmap_range(nmap_target_spec):
|
||||
"""
|
||||
:param nmap_target_spec: an nmap-style IP range target specification.
|
||||
|
||||
:return: ``True`` if IP range target spec is valid, ``False`` otherwise.
|
||||
"""
|
||||
try:
|
||||
_generate_nmap_octet_ranges(nmap_target_spec)
|
||||
return True
|
||||
except (TypeError, ValueError, AddrFormatError):
|
||||
pass
|
||||
return False
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def iter_nmap_range(nmap_target_spec):
|
||||
"""
|
||||
The nmap security tool supports a custom type of IPv4 range using multiple
|
||||
hyphenated octets. This generator provides iterators yielding IP addresses
|
||||
according to this rule set.
|
||||
|
||||
:param nmap_target_spec: an nmap-style IP range target specification.
|
||||
|
||||
:return: an iterator producing IPAddress objects for each IP in the range.
|
||||
"""
|
||||
octet_ranges = _generate_nmap_octet_ranges(nmap_target_spec)
|
||||
for w in octet_ranges[0]:
|
||||
for x in octet_ranges[1]:
|
||||
for y in octet_ranges[2]:
|
||||
for z in octet_ranges[3]:
|
||||
yield IPAddress("%d.%d.%d.%d" % (w, x, y, z))
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2008-2012, David P. D. Moss. All rights reserved.
|
||||
#
|
||||
# Released under the BSD license. See the LICENSE file for details.
|
||||
#-----------------------------------------------------------------------------
|
||||
"""A basic implementation of RFC 1924 ;-)"""
|
||||
|
||||
from netaddr.core import AddrFormatError
|
||||
from netaddr.ip import IPAddress
|
||||
|
||||
from netaddr.compat import _zip
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def chr_range(low, high):
|
||||
"""Returns all characters between low and high chars."""
|
||||
return [chr(i) for i in range(ord(low), ord(high)+1)]
|
||||
|
||||
#: Base 85 integer index to character lookup table.
|
||||
BASE_85 = chr_range('0', '9') + chr_range('A', 'Z') + chr_range('a', 'z') + \
|
||||
['!', '#', '$', '%', '&', '(',')', '*', '+', '-',';', '<', '=', '>',
|
||||
'?', '@', '^', '_','`', '{', '|', '}', '~']
|
||||
|
||||
#: Base 85 digit to integer lookup table.
|
||||
BASE_85_DICT = dict(_zip(BASE_85, range(0, 86)))
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def ipv6_to_base85(addr):
|
||||
"""Convert a regular IPv6 address to base 85."""
|
||||
ip = IPAddress(addr)
|
||||
int_val = int(ip)
|
||||
|
||||
remainder = []
|
||||
while int_val > 0:
|
||||
remainder.append(int_val % 85)
|
||||
int_val //= 85
|
||||
|
||||
return ''.join([BASE_85[w] for w in reversed(remainder)])
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def base85_to_ipv6(addr):
|
||||
"""
|
||||
Convert a base 85 IPv6 address to its hexadecimal format.
|
||||
"""
|
||||
tokens = list(addr)
|
||||
|
||||
if len(tokens) != 20:
|
||||
raise AddrFormatError('Invalid base 85 IPv6 addess: %r' % addr)
|
||||
|
||||
result = 0
|
||||
for i, num in enumerate(reversed(tokens)):
|
||||
num = BASE_85_DICT[num]
|
||||
result += (num * 85 ** i)
|
||||
|
||||
ip = IPAddress(result, 6)
|
||||
|
||||
return str(ip)
|
||||
@@ -0,0 +1,535 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2008-2012, David P. D. Moss. All rights reserved.
|
||||
#
|
||||
# Released under the BSD license. See the LICENSE file for details.
|
||||
#-----------------------------------------------------------------------------
|
||||
"""Set based operations for IP addresses and subnets."""
|
||||
|
||||
import sys as _sys
|
||||
import itertools as _itertools
|
||||
|
||||
from netaddr.strategy import ipv4 as _ipv4, ipv6 as _ipv6
|
||||
from netaddr.ip.intset import IntSet as _IntSet
|
||||
|
||||
from netaddr.ip import IPNetwork, IPAddress, cidr_merge, cidr_exclude, \
|
||||
iprange_to_cidrs
|
||||
|
||||
from netaddr.compat import _zip, _sys_maxint, _dict_keys, _int_type
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def partition_ips(iterable):
|
||||
"""
|
||||
Takes a sequence of IP addresses and networks splitting them into two
|
||||
separate sequences by IP version.
|
||||
|
||||
:param iterable: a sequence or iterator contain IP addresses and networks.
|
||||
|
||||
:return: a two element tuple (ipv4_list, ipv6_list).
|
||||
"""
|
||||
# Start off using set as we'll remove any duplicates at the start.
|
||||
if not hasattr(iterable, '__iter__'):
|
||||
raise ValueError('A sequence or iterator is expected!')
|
||||
|
||||
ipv4 = []
|
||||
ipv6 = []
|
||||
|
||||
for ip in iterable:
|
||||
if not hasattr(ip, 'version'):
|
||||
raise TypeError('IPAddress or IPNetwork expected!')
|
||||
|
||||
if ip.version == 4:
|
||||
ipv4.append(ip)
|
||||
else:
|
||||
ipv6.append(ip)
|
||||
|
||||
return ipv4, ipv6
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class IPSet(object):
|
||||
"""
|
||||
Represents an unordered collection (set) of unique IP addresses and
|
||||
subnets.
|
||||
|
||||
"""
|
||||
__slots__ = ('_cidrs',)
|
||||
|
||||
def __init__(self, iterable=None, flags=0):
|
||||
"""
|
||||
Constructor.
|
||||
|
||||
:param iterable: (optional) an iterable containing IP addresses and
|
||||
subnets.
|
||||
|
||||
:param flags: decides which rules are applied to the interpretation
|
||||
of the addr value. See the netaddr.core namespace documentation
|
||||
for supported constant values.
|
||||
|
||||
"""
|
||||
self._cidrs = {}
|
||||
if iterable is not None:
|
||||
mergeable = []
|
||||
for addr in iterable:
|
||||
if isinstance(addr, _int_type):
|
||||
addr = IPAddress(addr, flags=flags)
|
||||
mergeable.append(addr)
|
||||
|
||||
for cidr in cidr_merge(mergeable):
|
||||
self._cidrs[cidr] = True
|
||||
|
||||
def __getstate__(self):
|
||||
""":return: Pickled state of an ``IPSet`` object."""
|
||||
return tuple([cidr.__getstate__() for cidr in self._cidrs])
|
||||
|
||||
def __setstate__(self, state):
|
||||
"""
|
||||
:param state: data used to unpickle a pickled ``IPSet`` object.
|
||||
|
||||
"""
|
||||
#TODO: this needs to be optimised.
|
||||
self._cidrs = {}
|
||||
for cidr_tuple in state:
|
||||
value, prefixlen, version = cidr_tuple
|
||||
|
||||
if version == 4:
|
||||
module = _ipv4
|
||||
elif version == 6:
|
||||
module = _ipv6
|
||||
else:
|
||||
raise ValueError('unpickling failed for object state %s' \
|
||||
% str(state))
|
||||
|
||||
if 0 <= prefixlen <= module.width:
|
||||
cidr = IPNetwork((value, prefixlen), version=module.version)
|
||||
self._cidrs[cidr] = True
|
||||
else:
|
||||
raise ValueError('unpickling failed for object state %s' \
|
||||
% str(state))
|
||||
|
||||
def compact(self):
|
||||
"""
|
||||
Compact internal list of `IPNetwork` objects using a CIDR merge.
|
||||
"""
|
||||
cidrs = cidr_merge(list(self._cidrs))
|
||||
self._cidrs = dict(_zip(cidrs, [True] * len(cidrs)))
|
||||
|
||||
def __hash__(self):
|
||||
"""
|
||||
Raises ``TypeError`` if this method is called.
|
||||
|
||||
.. note:: IPSet objects are not hashable and cannot be used as \
|
||||
dictionary keys or as members of other sets. \
|
||||
"""
|
||||
raise TypeError('IP sets are unhashable!')
|
||||
|
||||
def __contains__(self, ip):
|
||||
"""
|
||||
:param ip: An IP address or subnet.
|
||||
|
||||
:return: ``True`` if IP address or subnet is a member of this IP set.
|
||||
"""
|
||||
ip = IPNetwork(ip)
|
||||
for cidr in self._cidrs:
|
||||
if ip in cidr:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
:return: an iterator over the IP addresses within this IP set.
|
||||
"""
|
||||
return _itertools.chain(*sorted(self._cidrs))
|
||||
|
||||
def iter_cidrs(self):
|
||||
"""
|
||||
:return: an iterator over individual IP subnets within this IP set.
|
||||
"""
|
||||
return sorted(self._cidrs)
|
||||
|
||||
def add(self, addr, flags=0):
|
||||
"""
|
||||
Adds an IP address or subnet to this IP set. Has no effect if it is
|
||||
already present.
|
||||
|
||||
Note that where possible the IP address or subnet is merged with other
|
||||
members of the set to form more concise CIDR blocks.
|
||||
|
||||
:param addr: An IP address or subnet.
|
||||
|
||||
:param flags: decides which rules are applied to the interpretation
|
||||
of the addr value. See the netaddr.core namespace documentation
|
||||
for supported constant values.
|
||||
|
||||
"""
|
||||
if isinstance(addr, _int_type):
|
||||
addr = IPAddress(addr, flags=flags)
|
||||
else:
|
||||
addr = IPNetwork(addr)
|
||||
self._cidrs[addr] = True
|
||||
self.compact()
|
||||
|
||||
def remove(self, addr, flags=0):
|
||||
"""
|
||||
Removes an IP address or subnet from this IP set. Does nothing if it
|
||||
is not already a member.
|
||||
|
||||
Note that this method behaves more like discard() found in regular
|
||||
Python sets because it doesn't raise KeyError exceptions if the
|
||||
IP address or subnet is question does not exist. It doesn't make sense
|
||||
to fully emulate that behaviour here as IP sets contain groups of
|
||||
individual IP addresses as individual set members using IPNetwork
|
||||
objects.
|
||||
|
||||
:param addr: An IP address or subnet.
|
||||
|
||||
:param flags: decides which rules are applied to the interpretation
|
||||
of the addr value. See the netaddr.core namespace documentation
|
||||
for supported constant values.
|
||||
|
||||
"""
|
||||
if isinstance(addr, _int_type):
|
||||
addr = IPAddress(addr, flags=flags)
|
||||
else:
|
||||
addr = IPNetwork(addr)
|
||||
|
||||
# This add() is required for address blocks provided that are larger
|
||||
# than blocks found within the set but have overlaps. e.g. :-
|
||||
#
|
||||
# >>> IPSet(['192.0.2.0/24']).remove('192.0.2.0/23')
|
||||
# IPSet([])
|
||||
#
|
||||
self.add(addr)
|
||||
|
||||
remainder = None
|
||||
matching_cidr = None
|
||||
|
||||
# Search for a matching CIDR and exclude IP from it.
|
||||
for cidr in self._cidrs:
|
||||
if addr in cidr:
|
||||
remainder = cidr_exclude(cidr, addr)
|
||||
matching_cidr = cidr
|
||||
break
|
||||
|
||||
# Replace matching CIDR with remaining CIDR elements.
|
||||
if remainder is not None:
|
||||
del self._cidrs[matching_cidr]
|
||||
for cidr in remainder:
|
||||
self._cidrs[cidr] = True
|
||||
self.compact()
|
||||
|
||||
def pop(self):
|
||||
"""
|
||||
Removes and returns an arbitrary IP address or subnet from this IP
|
||||
set.
|
||||
|
||||
:return: An IP address or subnet.
|
||||
"""
|
||||
return self._cidrs.popitem()[0]
|
||||
|
||||
def isdisjoint(self, other):
|
||||
"""
|
||||
:param other: an IP set.
|
||||
|
||||
:return: ``True`` if this IP set has no elements (IP addresses
|
||||
or subnets) in common with other. Intersection *must* be an
|
||||
empty set.
|
||||
"""
|
||||
result = self.intersection(other)
|
||||
if result == IPSet():
|
||||
return True
|
||||
return False
|
||||
|
||||
def copy(self):
|
||||
""":return: a shallow copy of this IP set."""
|
||||
obj_copy = self.__class__()
|
||||
obj_copy._cidrs.update(self._cidrs)
|
||||
return obj_copy
|
||||
|
||||
def update(self, iterable, flags=0):
|
||||
"""
|
||||
Update the contents of this IP set with the union of itself and
|
||||
other IP set.
|
||||
|
||||
:param iterable: an iterable containing IP addresses and subnets.
|
||||
|
||||
:param flags: decides which rules are applied to the interpretation
|
||||
of the addr value. See the netaddr.core namespace documentation
|
||||
for supported constant values.
|
||||
|
||||
"""
|
||||
if not hasattr(iterable, '__iter__'):
|
||||
raise TypeError('an iterable was expected!')
|
||||
|
||||
if hasattr(iterable, '_cidrs'):
|
||||
# Another IP set.
|
||||
for ip in cidr_merge(_dict_keys(self._cidrs)
|
||||
+ _dict_keys(iterable._cidrs)):
|
||||
self._cidrs[ip] = True
|
||||
else:
|
||||
# An iterable contain IP addresses or subnets.
|
||||
mergeable = []
|
||||
for addr in iterable:
|
||||
if isinstance(addr, _int_type):
|
||||
addr = IPAddress(addr, flags=flags)
|
||||
mergeable.append(addr)
|
||||
|
||||
for cidr in cidr_merge(_dict_keys(self._cidrs) + mergeable):
|
||||
self._cidrs[cidr] = True
|
||||
|
||||
self.compact()
|
||||
|
||||
def clear(self):
|
||||
"""Remove all IP addresses and subnets from this IP set."""
|
||||
self._cidrs = {}
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
:param other: an IP set
|
||||
|
||||
:return: ``True`` if this IP set is equivalent to the ``other`` IP set,
|
||||
``False`` otherwise.
|
||||
"""
|
||||
try:
|
||||
return self._cidrs == other._cidrs
|
||||
except AttributeError:
|
||||
return NotImplemented
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
:param other: an IP set
|
||||
|
||||
:return: ``False`` if this IP set is equivalent to the ``other`` IP set,
|
||||
``True`` otherwise.
|
||||
"""
|
||||
try:
|
||||
return self._cidrs != other._cidrs
|
||||
except AttributeError:
|
||||
return NotImplemented
|
||||
|
||||
def __lt__(self, other):
|
||||
"""
|
||||
:param other: an IP set
|
||||
|
||||
:return: ``True`` if this IP set is less than the ``other`` IP set,
|
||||
``False`` otherwise.
|
||||
"""
|
||||
if not hasattr(other, '_cidrs'):
|
||||
return NotImplemented
|
||||
|
||||
return len(self) < len(other) and self.issubset(other)
|
||||
|
||||
def issubset(self, other):
|
||||
"""
|
||||
:param other: an IP set.
|
||||
|
||||
:return: ``True`` if every IP address and subnet in this IP set
|
||||
is found within ``other``.
|
||||
"""
|
||||
if not hasattr(other, '_cidrs'):
|
||||
return NotImplemented
|
||||
|
||||
l_ipv4, l_ipv6 = partition_ips(self._cidrs)
|
||||
r_ipv4, r_ipv6 = partition_ips(other._cidrs)
|
||||
|
||||
l_ipv4_iset = _IntSet(*[(c.first, c.last) for c in l_ipv4])
|
||||
r_ipv4_iset = _IntSet(*[(c.first, c.last) for c in r_ipv4])
|
||||
|
||||
l_ipv6_iset = _IntSet(*[(c.first, c.last) for c in l_ipv6])
|
||||
r_ipv6_iset = _IntSet(*[(c.first, c.last) for c in r_ipv6])
|
||||
|
||||
ipv4 = l_ipv4_iset.issubset(r_ipv4_iset)
|
||||
ipv6 = l_ipv6_iset.issubset(r_ipv6_iset)
|
||||
|
||||
return ipv4 and ipv6
|
||||
|
||||
__le__ = issubset
|
||||
|
||||
def __gt__(self, other):
|
||||
"""
|
||||
:param other: an IP set.
|
||||
|
||||
:return: ``True`` if this IP set is greater than the ``other`` IP set,
|
||||
``False`` otherwise.
|
||||
"""
|
||||
if not hasattr(other, '_cidrs'):
|
||||
return NotImplemented
|
||||
|
||||
return len(self) > len(other) and self.issuperset(other)
|
||||
|
||||
def issuperset(self, other):
|
||||
"""
|
||||
:param other: an IP set.
|
||||
|
||||
:return: ``True`` if every IP address and subnet in other IP set
|
||||
is found within this one.
|
||||
"""
|
||||
if not hasattr(other, '_cidrs'):
|
||||
return NotImplemented
|
||||
|
||||
l_ipv4, l_ipv6 = partition_ips(self._cidrs)
|
||||
r_ipv4, r_ipv6 = partition_ips(other._cidrs)
|
||||
|
||||
l_ipv4_iset = _IntSet(*[(c.first, c.last) for c in l_ipv4])
|
||||
r_ipv4_iset = _IntSet(*[(c.first, c.last) for c in r_ipv4])
|
||||
|
||||
l_ipv6_iset = _IntSet(*[(c.first, c.last) for c in l_ipv6])
|
||||
r_ipv6_iset = _IntSet(*[(c.first, c.last) for c in r_ipv6])
|
||||
|
||||
ipv4 = l_ipv4_iset.issuperset(r_ipv4_iset)
|
||||
ipv6 = l_ipv6_iset.issuperset(r_ipv6_iset)
|
||||
|
||||
return ipv4 and ipv6
|
||||
|
||||
__ge__ = issuperset
|
||||
|
||||
def union(self, other):
|
||||
"""
|
||||
:param other: an IP set.
|
||||
|
||||
:return: the union of this IP set and another as a new IP set
|
||||
(combines IP addresses and subnets from both sets).
|
||||
"""
|
||||
ip_set = self.copy()
|
||||
ip_set.update(other)
|
||||
ip_set.compact()
|
||||
return ip_set
|
||||
|
||||
__or__ = union
|
||||
|
||||
def intersection(self, other):
|
||||
"""
|
||||
:param other: an IP set.
|
||||
|
||||
:return: the intersection of this IP set and another as a new IP set.
|
||||
(IP addresses and subnets common to both sets).
|
||||
"""
|
||||
cidr_list = []
|
||||
|
||||
# Separate IPv4 from IPv6.
|
||||
l_ipv4, l_ipv6 = partition_ips(self._cidrs)
|
||||
r_ipv4, r_ipv6 = partition_ips(other._cidrs)
|
||||
|
||||
# Process IPv4.
|
||||
l_ipv4_iset = _IntSet(*[(c.first, c.last) for c in l_ipv4])
|
||||
r_ipv4_iset = _IntSet(*[(c.first, c.last) for c in r_ipv4])
|
||||
|
||||
ipv4_result = l_ipv4_iset & r_ipv4_iset
|
||||
|
||||
for start, end in list(ipv4_result._ranges):
|
||||
cidrs = iprange_to_cidrs(IPAddress(start, 4), IPAddress(end-1, 4))
|
||||
cidr_list.extend(cidrs)
|
||||
|
||||
# Process IPv6.
|
||||
l_ipv6_iset = _IntSet(*[(c.first, c.last) for c in l_ipv6])
|
||||
r_ipv6_iset = _IntSet(*[(c.first, c.last) for c in r_ipv6])
|
||||
|
||||
ipv6_result = l_ipv6_iset & r_ipv6_iset
|
||||
|
||||
for start, end in list(ipv6_result._ranges):
|
||||
cidrs = iprange_to_cidrs(IPAddress(start, 6), IPAddress(end-1, 6))
|
||||
cidr_list.extend(cidrs)
|
||||
|
||||
return IPSet(cidr_list)
|
||||
|
||||
__and__ = intersection
|
||||
|
||||
def symmetric_difference(self, other):
|
||||
"""
|
||||
:param other: an IP set.
|
||||
|
||||
:return: the symmetric difference of this IP set and another as a new
|
||||
IP set (all IP addresses and subnets that are in exactly one
|
||||
of the sets).
|
||||
"""
|
||||
cidr_list = []
|
||||
|
||||
# Separate IPv4 from IPv6.
|
||||
l_ipv4, l_ipv6 = partition_ips(self._cidrs)
|
||||
r_ipv4, r_ipv6 = partition_ips(other._cidrs)
|
||||
|
||||
# Process IPv4.
|
||||
l_ipv4_iset = _IntSet(*[(c.first, c.last) for c in l_ipv4])
|
||||
r_ipv4_iset = _IntSet(*[(c.first, c.last) for c in r_ipv4])
|
||||
|
||||
ipv4_result = l_ipv4_iset ^ r_ipv4_iset
|
||||
|
||||
for start, end in list(ipv4_result._ranges):
|
||||
cidrs = iprange_to_cidrs(IPAddress(start, 4), IPAddress(end-1, 4))
|
||||
cidr_list.extend(cidrs)
|
||||
|
||||
# Process IPv6.
|
||||
l_ipv6_iset = _IntSet(*[(c.first, c.last) for c in l_ipv6])
|
||||
r_ipv6_iset = _IntSet(*[(c.first, c.last) for c in r_ipv6])
|
||||
|
||||
ipv6_result = l_ipv6_iset ^ r_ipv6_iset
|
||||
|
||||
for start, end in list(ipv6_result._ranges):
|
||||
cidrs = iprange_to_cidrs(IPAddress(start, 6), IPAddress(end-1, 6))
|
||||
cidr_list.extend(cidrs)
|
||||
|
||||
return IPSet(cidr_list)
|
||||
|
||||
__xor__ = symmetric_difference
|
||||
|
||||
def difference(self, other):
|
||||
"""
|
||||
:param other: an IP set.
|
||||
|
||||
:return: the difference between this IP set and another as a new IP
|
||||
set (all IP addresses and subnets that are in this IP set but
|
||||
not found in the other.)
|
||||
"""
|
||||
cidr_list = []
|
||||
|
||||
# Separate IPv4 from IPv6.
|
||||
l_ipv4, l_ipv6 = partition_ips(self._cidrs)
|
||||
r_ipv4, r_ipv6 = partition_ips(other._cidrs)
|
||||
|
||||
# Process IPv4.
|
||||
l_ipv4_iset = _IntSet(*[(c.first, c.last) for c in l_ipv4])
|
||||
r_ipv4_iset = _IntSet(*[(c.first, c.last) for c in r_ipv4])
|
||||
|
||||
ipv4_result = l_ipv4_iset - r_ipv4_iset
|
||||
|
||||
for start, end in list(ipv4_result._ranges):
|
||||
cidrs = iprange_to_cidrs(IPAddress(start, 4), IPAddress(end-1, 4))
|
||||
cidr_list.extend(cidrs)
|
||||
|
||||
# Process IPv6.
|
||||
l_ipv6_iset = _IntSet(*[(c.first, c.last) for c in l_ipv6])
|
||||
r_ipv6_iset = _IntSet(*[(c.first, c.last) for c in r_ipv6])
|
||||
|
||||
ipv6_result = l_ipv6_iset - r_ipv6_iset
|
||||
|
||||
for start, end in list(ipv6_result._ranges):
|
||||
cidrs = iprange_to_cidrs(IPAddress(start, 6), IPAddress(end-1, 6))
|
||||
cidr_list.extend(cidrs)
|
||||
|
||||
return IPSet(cidr_list)
|
||||
|
||||
__sub__ = difference
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
:return: the cardinality of this IP set (i.e. sum of individual IP \
|
||||
addresses). Raises ``IndexError`` if size > maxint (a Python \
|
||||
limitation). Use the .size property for subnets of any size.
|
||||
"""
|
||||
size = self.size
|
||||
if size > _sys.maxint:
|
||||
raise IndexError("range contains greater than %d (maxint) " \
|
||||
"IP addresses! Use the .size property instead." % _sys_maxint)
|
||||
return size
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
"""
|
||||
The cardinality of this IP set (based on the number of individual IP
|
||||
addresses including those implicitly defined in subnets).
|
||||
"""
|
||||
return sum([cidr.size for cidr in self._cidrs])
|
||||
|
||||
def __repr__(self):
|
||||
""":return: Python statement to create an equivalent object"""
|
||||
return 'IPSet(%r)' % [str(c) for c in sorted(self._cidrs)]
|
||||
|
||||
__str__ = __repr__
|
||||
@@ -0,0 +1,273 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2008-2012, 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
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
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 hasattr(bits, 'replace'):
|
||||
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 is not '':
|
||||
# Check custom separator.
|
||||
if not hasattr(word_sep, 'join'):
|
||||
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 hasattr(bin_val, 'startswith'):
|
||||
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)
|
||||
@@ -0,0 +1,291 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2008-2012, 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.strategy import BYTES_TO_BITS as _BYTES_TO_BITS, \
|
||||
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_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) + '$',
|
||||
'^' + '\.'.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 hasattr(addr, 'upper'):
|
||||
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)
|
||||
@@ -0,0 +1,184 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2008-2012, David P. D. Moss. All rights reserved.
|
||||
#
|
||||
# Released under the BSD license. See the LICENSE file for details.
|
||||
#-----------------------------------------------------------------------------
|
||||
"""
|
||||
IEEE 64-bit EUI (Extended Unique Indentifier) logic.
|
||||
"""
|
||||
import struct as _struct
|
||||
import re as _re
|
||||
|
||||
# This is a fake constant that doesn't really exist. Here for completeness.
|
||||
AF_EUI64 = 64
|
||||
|
||||
from netaddr.core import AddrFormatError
|
||||
from netaddr.strategy import BYTES_TO_BITS as _BYTES_TO_BITS, \
|
||||
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 = 64
|
||||
|
||||
#: 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 = '%.2X'
|
||||
|
||||
#: The separator character used between each word.
|
||||
word_sep = '-'
|
||||
|
||||
#: 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 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
|
||||
|
||||
#: Compiled regular expression for detecting value EUI-64 identifiers.
|
||||
RE_EUI64_FORMAT = _re.compile('^' + '-'.join(['([0-9A-F]{1,2})'] * 8) + '$',
|
||||
_re.IGNORECASE)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def valid_str(addr):
|
||||
"""
|
||||
:param addr: An IEEE EUI-64 indentifier in string form.
|
||||
|
||||
:return: ``True`` if EUI-64 indentifier is valid, ``False`` otherwise.
|
||||
"""
|
||||
try:
|
||||
match_result = RE_EUI64_FORMAT.findall(addr)
|
||||
if len(match_result) != 0:
|
||||
return True
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def str_to_int(addr):
|
||||
"""
|
||||
:param addr: An IEEE EUI-64 indentifier in string form.
|
||||
|
||||
:return: An unsigned integer that is equivalent to value represented
|
||||
by EUI-64 string identifier.
|
||||
"""
|
||||
words = []
|
||||
|
||||
try:
|
||||
match_result = RE_EUI64_FORMAT.findall(addr)
|
||||
if not match_result:
|
||||
raise TypeError
|
||||
except TypeError:
|
||||
raise AddrFormatError('invalid IEEE EUI-64 identifier: %r!' % addr)
|
||||
|
||||
words = match_result[0]
|
||||
|
||||
if len(words) != num_words:
|
||||
raise AddrFormatError('bad word count for EUI-64 identifier: %r!' \
|
||||
% addr)
|
||||
|
||||
return int(''.join(['%.2x' % int(w, 16) for w in words]), 16)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def int_to_str(int_val, dialect=None):
|
||||
"""
|
||||
:param int_val: An unsigned integer.
|
||||
|
||||
:param dialect: (optional) a Python class defining formatting options
|
||||
(Please Note - not currently in use).
|
||||
|
||||
:return: An IEEE EUI-64 identifier that is equivalent to unsigned integer.
|
||||
"""
|
||||
words = int_to_words(int_val)
|
||||
tokens = [word_fmt % i for i in words]
|
||||
addr = 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):
|
||||
return _valid_words(words, word_size, num_words)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def int_to_words(int_val, dialect=None):
|
||||
return _int_to_words(int_val, word_size, num_words)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def words_to_int(words, dialect=None):
|
||||
return _words_to_int(words, word_size, num_words)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def valid_bits(bits, dialect=None):
|
||||
return _valid_bits(bits, width, word_sep)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def bits_to_int(bits, dialect=None):
|
||||
return _bits_to_int(bits, width, word_sep)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def int_to_bits(int_val, dialect=None):
|
||||
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)
|
||||
@@ -0,0 +1,294 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# 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)
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2008-2012, 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:
|
||||
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 BYTES_TO_BITS as _BYTES_TO_BITS, \
|
||||
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)
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2008-2012, David P. D. Moss. All rights reserved.
|
||||
#
|
||||
# Released under the BSD license. See the LICENSE file for details.
|
||||
#-----------------------------------------------------------------------------
|
||||
"""Runs all netaddr unit tests."""
|
||||
|
||||
from os.path import abspath, basename, dirname, join as pathjoin
|
||||
import sys
|
||||
import glob
|
||||
import doctest
|
||||
import unittest
|
||||
|
||||
sys.path.insert(0, abspath(pathjoin(dirname(__file__), '..', '..')))
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def test_suite_all():
|
||||
|
||||
test_dirs = [
|
||||
'ip',
|
||||
'eui',
|
||||
'strategy',
|
||||
'core'
|
||||
]
|
||||
|
||||
base_path = abspath(pathjoin(dirname(__file__), '..'))
|
||||
|
||||
# Select tests based on the version of the Python interpreter.
|
||||
py_ver_dir = '2.x'
|
||||
if sys.version_info[0] == 3:
|
||||
py_ver_dir = '3.x'
|
||||
|
||||
# Gather list of files containing tests.
|
||||
test_files = []
|
||||
for entry in test_dirs:
|
||||
test_path = pathjoin(base_path, "tests", py_ver_dir, entry, "*.txt")
|
||||
files = glob.glob(test_path)
|
||||
test_files.extend(files)
|
||||
|
||||
sys.stdout.write('testdir: %s\n' % '\n'.join(test_files))
|
||||
|
||||
# Add anything to the skiplist that we want to leave out.
|
||||
skiplist = []
|
||||
|
||||
# Drop platform specific tests for other platforms.
|
||||
platform_tests = ['platform_darwin.txt', 'platform_linux2.txt', 'platform_win32.txt']
|
||||
for platform_test in platform_tests:
|
||||
if not sys.platform in platform_test:
|
||||
skiplist.append(platform_test)
|
||||
|
||||
# Exclude any entries from the skip list.
|
||||
test_files = [t for t in test_files if basename(t) not in skiplist]
|
||||
|
||||
# Build and return a complete unittest test suite.
|
||||
suite = unittest.TestSuite()
|
||||
|
||||
for test_file in test_files:
|
||||
doctest_suite = doctest.DocFileSuite(test_file,
|
||||
optionflags=doctest.ELLIPSIS, module_relative=False)
|
||||
suite.addTest(doctest_suite)
|
||||
|
||||
return suite
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
def run():
|
||||
runner = unittest.TextTestRunner()
|
||||
runner.run(test_suite_all())
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/python
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2008-2012, David P. D. Moss. All rights reserved.
|
||||
#
|
||||
# Released under the BSD license. See the LICENSE file for details.
|
||||
#-----------------------------------------------------------------------------
|
||||
"""an interactive shell for the netaddr library"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import netaddr
|
||||
from netaddr import *
|
||||
|
||||
# aliases to save some typing ...
|
||||
from netaddr import IPAddress as IP, IPNetwork as CIDR
|
||||
from netaddr import EUI as MAC
|
||||
|
||||
argv = sys.argv[1:]
|
||||
|
||||
banner = "\nnetaddr shell %s - %s\n" % (netaddr.__version__, __doc__)
|
||||
exit_msg = "\nShare and enjoy!"
|
||||
rc_override = None
|
||||
|
||||
try:
|
||||
try:
|
||||
# ipython >= 0.11
|
||||
from IPython.frontend.terminal.embed import InteractiveShellEmbed
|
||||
ipshell = InteractiveShellEmbed(banner1=banner, exit_msg=exit_msg)
|
||||
except ImportError:
|
||||
# ipython < 0.11
|
||||
from IPython.Shell import IPShellEmbed
|
||||
ipshell = IPShellEmbed(argv, banner, exit_msg, rc_override)
|
||||
except ImportError:
|
||||
sys.stderr.write('IPython (http://ipython.scipy.org/) not found!\n')
|
||||
sys.exit(1)
|
||||
|
||||
ipshell()
|
||||
Reference in New Issue
Block a user