#-----------------------------------------------------------------------------# Copyright (c) 2008 by 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 aglob style syntax."""fromnetaddr.coreimportAddrFormatError,AddrConversionErrorfromnetaddr.ipimportIPRange,IPAddress,IPNetwork,iprange_to_cidrsfromnetaddr.compatimport_is_str
[docs]defvalid_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.ifnot_is_str(ipglob):returnFalseseen_hyphen=Falseseen_asterisk=Falseoctets=ipglob.split('.')iflen(octets)!=4:returnFalseforoctetinoctets:if'-'inoctet:ifseen_hyphen:returnFalseseen_hyphen=Trueifseen_asterisk:# Asterisks cannot precede hyphenated octets.returnFalsetry:(octet1,octet2)=[int(i)foriinoctet.split('-')]exceptValueError:returnFalseifoctet1>=octet2:returnFalseifnot0<=octet1<=254:returnFalseifnot1<=octet2<=255:returnFalseelifoctet=='*':seen_asterisk=Trueelse:ifseen_hyphenisTrue:returnFalseifseen_asteriskisTrue:returnFalsetry:ifnot0<=int(octet)<=255:returnFalseexceptValueError:returnFalsereturnTrue
[docs]defglob_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. """ifnotvalid_glob(ipglob):raiseAddrFormatError('not a recognised IP glob range: %r!'%(ipglob,))start_tokens=[]end_tokens=[]foroctetinipglob.split('.'):if'-'inoctet:tokens=octet.split('-')start_tokens.append(tokens[0])end_tokens.append(tokens[1])elifoctet=='*':start_tokens.append('0')end_tokens.append('255')else:start_tokens.append(octet)end_tokens.append(octet)returnIPAddress('.'.join(start_tokens)),IPAddress('.'.join(end_tokens))
[docs]defglob_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. """ifnotvalid_glob(ipglob):raiseAddrFormatError('not a recognised IP glob range: %r!'%(ipglob,))start_tokens=[]end_tokens=[]foroctetinipglob.split('.'):if'-'inoctet:tokens=octet.split('-')start_tokens.append(tokens[0])end_tokens.append(tokens[1])elifoctet=='*':start_tokens.append('0')end_tokens.append('255')else:start_tokens.append(octet)end_tokens.append(octet)returnIPRange('.'.join(start_tokens),'.'.join(end_tokens))
[docs]defiprange_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)ifstart.version!=4andend.version!=4:raiseAddrConversionError('IP glob ranges only support IPv4!')def_iprange_to_glob(lb,ub):# Internal function to process individual IP globs.t1=[int(_)for_instr(lb).split('.')]t2=[int(_)for_instr(ub).split('.')]tokens=[]seen_hyphen=Falseseen_asterisk=Falseforiinrange(4):ift1[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=Trueelse:# Create a hyphenated octet - only one allowed per IP glob.ifnotseen_asterisk:ifnotseen_hyphen:tokens.append('%s-%s'%(t1[i],t2[i]))seen_hyphen=Trueelse:raiseAddrConversionError('only 1 hyphenated octet per IP glob allowed!')else:raiseAddrConversionError("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)ifnotvalid_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.raiseAddrConversionError('invalid ip glob created')globs.append(ipglob)exceptAddrConversionError:# 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.#forcidriniprange_to_cidrs(start,end):ipglob=_iprange_to_glob(cidr[0],cidr[-1])globs.append(ipglob)returnglobs
[docs]defglob_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. """returniprange_to_cidrs(*glob_to_iptuple(ipglob))
[docs]defcidr_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])iflen(globs)!=1:# There should only ever be a one to one mapping between a CIDR and# an IP glob range.raiseAddrConversionError('bad CIDR to IP glob conversion!')returnglobs[0]
[docs]classIPGlob(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 less 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__getstate__(self):""":return: Pickled state of an `IPGlob` object."""returnsuper(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):returnself._globdef_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.globdef__repr__(self):""":return: Python statement to create an equivalent object"""return"%s('%s')"%(self.__class__.__name__,self.glob)