netwatch.py/netaddr-0.7.10/netaddr/ip/glob.py

312 lines
11 KiB
Python

#-----------------------------------------------------------------------------
# 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)