diff --git a/src/virtManager/IPy.py b/src/virtManager/IPy.py index 2af618be4..9fd80ece4 100644 --- a/src/virtManager/IPy.py +++ b/src/virtManager/IPy.py @@ -1,138 +1,16 @@ -# -# As per docs on download site: http://c0re.23.nu/c0de/IPy/ -# -# redistribution: If you think you need a license for anything -# you might think of this as beeing unter a "BSD Style License". -# +""" +IPy - class and tools for handling of IPv4 and IPv6 addresses and networks. +See README file for learn how to use IPy. -""" IPy - class and tools for handling of IPv4 and IPv6 Addresses and Networks. - -$HeadURL: http://svn.23.nu/svn/repos/IPy/trunk/IPy.py $ - -$Id: IPy.py 671 2004-08-22 21:02:29Z md $ - -The IP class allows a comfortable parsing and handling for most -notations in use for IPv4 and IPv6 Addresses and Networks. It was -greatly inspired bei RIPE's Perl module NET::IP's interface but -doesn't share the Implementation. It doesn't share non-CIDR netmasks, -so funky stuff lixe a netmask 0xffffff0f can't be done here. - - >>> ip = IP('127.0.0.0/30') - >>> for x in ip: - ... print x - ... - 127.0.0.0 - 127.0.0.1 - 127.0.0.2 - 127.0.0.3 - >>> ip2 = IP('0x7f000000/30') - >>> ip == ip2 - 1 - >>> ip.reverseNames() - ['0.0.0.127.in-addr.arpa.', '1.0.0.127.in-addr.arpa.', '2.0.0.127.in-addr.arpa.', '3.0.0.127.in-addr.arpa.'] - >>> ip.reverseName() - '0-3.0.0.127.in-addr.arpa.' - >>> ip.iptype() - 'PRIVATE' - -It can detect about a dozen different ways of expressing IP addresses -and networks, parse them and distinguish between IPv4 and IPv6 addresses. - - >>> IP('10.0.0.0/8').version() - 4 - >>> IP('::1').version() - 6 - >>> print IP(0x7f000001) - 127.0.0.1 - >>> print IP('0x7f000001') - 127.0.0.1 - >>> print IP('127.0.0.1') - 127.0.0.1 - >>> print IP('10') - 10.0.0.0 - >>> print IP('1080:0:0:0:8:800:200C:417A') - 1080:0000:0000:0000:0008:0800:200c:417a - >>> print IP('1080::8:800:200C:417A') - 1080:0000:0000:0000:0008:0800:200c:417a - >>> print IP('::1') - 0000:0000:0000:0000:0000:0000:0000:0001 - >>> print IP('::13.1.68.3') - 0000:0000:0000:0000:0000:0000:0d01:4403 - >>> print IP('127.0.0.0/8') - 127.0.0.0/8 - >>> print IP('127.0.0.0/255.0.0.0') - 127.0.0.0/8 - >>> print IP('127.0.0.0-127.255.255.255') - 127.0.0.0/8 - -Nearly all class methods which return a string have an optional -parameter 'wantprefixlen' which controlles if the prefixlen or netmask -is printed. Per default the prefilen is always shown if the net -contains more than one address. - -wantprefixlen == 0 / None don't return anything 1.2.3.0 -wantprefixlen == 1 /prefix 1.2.3.0/24 -wantprefixlen == 2 /netmask 1.2.3.0/255.255.255.0 -wantprefixlen == 3 -lastip 1.2.3.0-1.2.3.255 - -You can also change the defaults on an per-object basis by fiddeling with the class members - -NoPrefixForSingleIp -WantPrefixLen - - >>> IP('10.0.0.0/32').strNormal() - '10.0.0.0' - >>> IP('10.0.0.0/24').strNormal() - '10.0.0.0/24' - >>> IP('10.0.0.0/24').strNormal(0) - '10.0.0.0' - >>> IP('10.0.0.0/24').strNormal(1) - '10.0.0.0/24' - >>> IP('10.0.0.0/24').strNormal(2) - '10.0.0.0/255.255.255.0' - >>> IP('10.0.0.0/24').strNormal(3) - '10.0.0.0-10.0.0.255' - >>> ip = IP('10.0.0.0') - >>> print ip - 10.0.0.0 - >>> ip.NoPrefixForSingleIp = None - >>> print ip - 10.0.0.0/32 - >>> ip.WantPrefixLen = 3 - >>> print ip - 10.0.0.0-10.0.0.0 - - -Further Information might be available at http://c0re.jp/c0de/IPy/ - -Hacked 2001 by drt@un.bewaff.net - -TODO: - * better comparison (__cmp__ and friends) - * tests for __cmp__ - * always write hex values lowercase - * interpret 2001:1234:5678:1234/64 as 2001:1234:5678:1234::/64 - * move size in bits into class variables to get rid of some "if self._ipversion ..." - * support for base85 encoding - * support for output of IPv6 encoded IPv4 Addresses - * update address type tables - * first-last notation should be allowed for IPv6 - * add IPv6 docstring examples - * check better for negative parameters - * add addition / aggregation - * move reverse name stuff out of the classes and refactor it - * support for aggregation of more than two nets at once - * support for aggregation with "holes" - * support for finding common prefix - * '>>' and '<<' for prefix manipulation - * add our own exceptions instead ValueError all the time - * rename checkPrefix to checkPrefixOk - * add more documentation and doctests - * refactor +Further Information might be available at: +http://software.inl.fr/trac/trac.cgi/wiki/IPy """ -__rcsid__ = '$Id: IPy.py 671 2004-08-22 21:02:29Z md $' -__version__ = '0.42' +# $HeadURL: https://svn.inl.fr/inl-svn/src/tools/ipy/tags/IPy-0.70/IPy.py $ +# $Id: IPy.py 19309 2009-10-29 10:21:13Z haypo $ + +__rcsid__ = '$Id: IPy.py 19309 2009-10-29 10:21:13Z haypo $' +__version__ = '0.70' import types @@ -140,15 +18,16 @@ import types # this should include www.iana.org/assignments/ipv4-address-space # and www.iana.org/assignments/multicast-addresses IPv4ranges = { - '0' : 'PUBLIC', # fall back - '00000000' : 'PRIVATE', # 0/8 - '00001010' : 'PRIVATE', # 10/8 - '01111111' : 'PRIVATE', # 127.0/8 - '1' : 'PUBLIC', # fall back - '101011000001' : 'PRIVATE', # 172.16/12 - '1100000010101000' : 'PRIVATE', # 192.168/16 - '11011111' : 'RESERVED', # 223/8 - '111' : 'RESERVED' # 224/3 + '0': 'PUBLIC', # fall back + '00000000': 'PRIVATE', # 0/8 + '00001010': 'PRIVATE', # 10/8 + '01111111': 'PRIVATE', # 127.0/8 + '1': 'PUBLIC', # fall back + '1010100111111110': 'PRIVATE', # 169.254/16 + '101011000001': 'PRIVATE', # 172.16/12 + '1100000010101000': 'PRIVATE', # 192.168/16 + '11011111': 'RESERVED', # 223/8 + '111': 'RESERVED' # 224/3 } # Definition of the Ranges for IPv6 IPs @@ -161,7 +40,7 @@ IPv6ranges = { '00000001' : 'UNASSIGNED', # 100::/8 '0000001' : 'NSAP', # 200::/7 '0000010' : 'IPX', # 400::/7 - '0000011' : 'UNASSIGNED', # 600::/7 + '0000011' : 'UNASSIGNED', # 600::/7 '00001' : 'UNASSIGNED', # 800::/5 '0001' : 'UNASSIGNED', # 1000::/4 '0010000000000000' : 'RESERVED', # 2000::/16 Reserved @@ -173,7 +52,7 @@ IPv6ranges = { '0010000000000010' : '6TO4', # 2002::/16 "6to4" [RFC3056] '0011111111111110' : '6BONE', # 3FFE::/16 6bone Testing [RFC2471] '0011111111111111' : 'RESERVED', # 3FFF::/16 Reserved - '010' : 'GLOBAL-UNICAST', # 4000::/3 + '010' : 'GLOBAL-UNICAST', # 4000::/3 '011' : 'UNASSIGNED', # 6000::/3 '100' : 'GEO-UNICAST', # 8000::/3 '101' : 'UNASSIGNED', # A000::/3 @@ -198,13 +77,13 @@ class IPint: Use class IP instead because some features are not implemented for IPint.""" - - def __init__(self, data, ipversion = 0): + + def __init__(self, data, ipversion=0, make_net=0): """Create an instance of an IP object. - + Data can be a network specification or a single IP. IP - Addresses can be specified in all forms understood by - parseAddress.() the size of a network can be specified as + addresses can be specified in all forms understood by + parseAddress(). The size of a network can be specified as /prefixlen a.b.c.0/24 2001:658:22a:cafe::/64 -lastIP a.b.c.0-a.b.c.255 2001:658:22a:cafe::-2001:658:22a:cafe:ffff:ffff:ffff:ffff @@ -213,22 +92,30 @@ class IPint: If no size specification is given a size of 1 address (/32 for IPv4 and /128 for IPv6) is assumed. + If make_net is True, an IP address will be transformed into the network + address by applying the specified netmask. + >>> print IP('127.0.0.0/8') 127.0.0.0/8 >>> print IP('127.0.0.0/255.0.0.0') 127.0.0.0/8 >>> print IP('127.0.0.0-127.255.255.255') 127.0.0.0/8 + >>> print IP('127.0.0.1/255.0.0.0', make_net=True) + 127.0.0.0/8 See module documentation for more examples. """ - self.NoPrefixForSingleIp = 1 # Print no Prefixlen for /32 and /128 - self.WantPrefixLen = None # Do we want prefix printed by default? see _printPrefix() - + # Print no Prefixlen for /32 and /128 + self.NoPrefixForSingleIp = 1 + + # Do we want prefix printed by default? see _printPrefix() + self.WantPrefixLen = None + netbits = 0 prefixlen = -1 - + # handling of non string values in constructor if type(data) == types.IntType or type(data) == types.LongType: self.ip = long(data) @@ -267,7 +154,13 @@ class IPint: raise ValueError, "last address should be larger than first" size = last - self.ip netbits = _count1Bits(size) - elif len(x) == 1: + # make sure the broadcast is the same as the last ip + # otherwise it will return /16 for something like: + # 192.168.0.0-192.168.191.255 + if IP('%s/%s' % (ip, 32-netbits)).broadcast().int() != last: + raise ValueError, \ + "the range %s is not on a network boundary." % data + elif len(x) == 1: x = data.split('/') # if no prefix is given use defaults if len(x) == 1: @@ -283,7 +176,7 @@ class IPint: (netmask, vers) = parseAddress(prefixlen) if vers != 4: raise ValueError, "netmask must be IPv4" - prefixlen = _netmaskToPrefixlen(netmask) + prefixlen = _netmaskToPrefixlen(netmask) elif len(x) > 2: raise ValueError, "only one '-' allowed in IP Address" else: @@ -302,17 +195,20 @@ class IPint: self._ipversion = ipversion self._prefixlen = int(prefixlen) - if not _checkNetaddrWorksWithPrefixlen(self.ip, self._prefixlen, self._ipversion): - raise ValueError, "%s goes not well with prefixlen %d" % (hex(self.ip), self._prefixlen) - + if make_net: + self.ip = self.ip & _prefixlenToNetmask(self._prefixlen, self._ipversion) + + if not _checkNetaddrWorksWithPrefixlen(self.ip, + self._prefixlen, self._ipversion): + raise ValueError, "%s has invalid prefix length (%s)" % (repr(self), self._prefixlen) def int(self): """Return the first / base / network addess as an (long) integer. The same as IP[0]. - >>> hex(IP('10.0.0.0/8').int()) - '0xA000000L' + >>> "%X" % IP('10.0.0.0/8').int() + 'A000000' """ return self.ip @@ -335,12 +231,14 @@ class IPint: return self._prefixlen def net(self): - """Return the base (first) address of a network as an (long) integer.""" - + """ + Return the base (first) address of a network as an (long) integer. + """ return self.int() def broadcast(self): - """Return the broadcast (last) address of a network as an (long) integer. + """ + Return the broadcast (last) address of a network as an (long) integer. The same as IP[-1].""" return self.int() + self.len() - 1 @@ -349,7 +247,7 @@ class IPint: """Prints Prefixlen/Netmask. Not really. In fact it is our universal Netmask/Prefixlen printer. - This is considered an internel function. + This is considered an internal function. want == 0 / None don't return anything 1.2.3.0 want == 1 /prefix 1.2.3.0/24 @@ -358,7 +256,7 @@ class IPint: """ if (self._ipversion == 4 and self._prefixlen == 32) or \ - (self._ipversion == 6 and self._prefixlen == 128): + (self._ipversion == 6 and self._prefixlen == 128): if self.NoPrefixForSingleIp: want = 0 if want == None: @@ -367,9 +265,10 @@ class IPint: want = 1 if want: if want == 2: - # this should work wit IP and IPint + # this should work with IP and IPint netmask = self.netmask() - if type(netmask) != types.IntType and type(netmask) != types.LongType: + if type(netmask) != types.IntType \ + and type(netmask) != types.LongType: netmask = netmask.int() return "/%s" % (intToIp(netmask, self._ipversion)) elif want == 3: @@ -380,14 +279,14 @@ class IPint: else: return '' - # We have different Favours to convert to: + # We have different flavours to convert to: # strFullsize 127.0.0.1 2001:0658:022a:cafe:0200:c0ff:fe8d:08fa # strNormal 127.0.0.1 2001:658:22a:cafe:200:c0ff:fe8d:08fa # strCompressed 127.0.0.1 2001:658:22a:cafe::1 # strHex 0x7F000001L 0x20010658022ACAFE0200C0FFFE8D08FA # strDec 2130706433 42540616829182469433547974687817795834 - def strBin(self, wantprefixlen = None): + def strBin(self, wantprefixlen = None): """Return a string representation as a binary value. >>> print IP('127.0.0.1').strBin() @@ -396,7 +295,7 @@ class IPint: if self._ipversion == 4: - bits = 32 + bits = 32 elif self._ipversion == 6: bits = 128 else: @@ -410,22 +309,28 @@ class IPint: def strCompressed(self, wantprefixlen = None): """Return a string representation in compressed format using '::' Notation. - >>> print IP('127.0.0.1').strCompressed() - 127.0.0.1 - >>> print IP('2001:0658:022a:cafe:0200::1').strCompressed() - 2001:658:22a:cafe:200::1 + >>> IP('127.0.0.1').strCompressed() + '127.0.0.1' + >>> IP('2001:0658:022a:cafe:0200::1').strCompressed() + '2001:658:22a:cafe:200::1' + >>> IP('ffff:ffff:ffff:ffff:ffff:f:f:fffc/127').strCompressed() + 'ffff:ffff:ffff:ffff:ffff:f:f:fffc/127' """ - + if self.WantPrefixLen == None and wantprefixlen == None: wantprefixlen = 1 - + if self._ipversion == 4: return self.strFullsize(wantprefixlen) else: + if self.ip >> 32 == 0xffff: + ipv4 = intToIp(self.ip & 0xffffffff, 4) + text = "::ffff:" + ipv4 + self._printPrefix(wantprefixlen) + return text # find the longest sequence of '0' hextets = [int(x, 16) for x in self.strFullsize(0).split(':')] # every element of followingzeros will contain the number of zeros - # following the corrospondending element of hextetes + # following the corresponding element of hextets followingzeros = [0] * 8 for i in range(len(hextets)): followingzeros[i] = _countFollowingZeros(hextets[i:]) @@ -444,7 +349,7 @@ class IPint: hextets = [''] + hextets return ':'.join(hextets) + self._printPrefix(wantprefixlen) else: - return self.strNormal() + self._printPrefix(wantprefixlen) + return self.strNormal(0) + self._printPrefix(wantprefixlen) def strNormal(self, wantprefixlen = None): """Return a string representation in the usual format. @@ -457,20 +362,20 @@ class IPint: if self.WantPrefixLen == None and wantprefixlen == None: wantprefixlen = 1 - + if self._ipversion == 4: - ret = self.strFullsize(0) + ret = self.strFullsize(0) elif self._ipversion == 6: ret = ':'.join([hex(x)[2:] for x in [int(x, 16) for x in self.strFullsize(0).split(':')]]) else: raise ValueError, "only IPv4 and IPv6 supported" - - + + return ret + self._printPrefix(wantprefixlen) def strFullsize(self, wantprefixlen = None): - """Return a string representation in the non mangled format. + """Return a string representation in the non-mangled format. >>> print IP('127.0.0.1').strFullsize() 127.0.0.1 @@ -480,16 +385,16 @@ class IPint: if self.WantPrefixLen == None and wantprefixlen == None: wantprefixlen = 1 - + return intToIp(self.ip, self._ipversion).lower() + self._printPrefix(wantprefixlen) def strHex(self, wantprefixlen = None): - """Return a string representation in hex format. + """Return a string representation in hex format in lower case. - >>> print IP('127.0.0.1').strHex() - 0x7F000001 - >>> print IP('2001:0658:022a:cafe:0200::1').strHex() - 0x20010658022ACAFE0200000000000001 + >>> IP('127.0.0.1').strHex() + '0x7f000001' + >>> IP('2001:0658:022a:cafe:0200::1').strHex() + '0x20010658022acafe0200000000000001' """ if self.WantPrefixLen == None and wantprefixlen == None: @@ -498,7 +403,7 @@ class IPint: x = hex(self.ip) if x[-1] == 'L': x = x[:-1] - return x + self._printPrefix(wantprefixlen) + return x.lower() + self._printPrefix(wantprefixlen) def strDec(self, wantprefixlen = None): """Return a string representation in decimal format. @@ -537,9 +442,9 @@ class IPint: # this could be greatly improved if self._ipversion == 4: - iprange = IPv4ranges + iprange = IPv4ranges elif self._ipversion == 6: - iprange = IPv6ranges + iprange = IPv6ranges else: raise ValueError, "only IPv4 and IPv6 supported" @@ -553,8 +458,8 @@ class IPint: def netmask(self): """Return netmask as an integer. - >>> print hex(IP('195.185.0.0/16').netmask().int()) - 0xFFFF0000L + >>> "%X" % IP('195.185.0.0/16').netmask().int() + 'FFFF0000' """ # TODO: unify with prefixlenToNetmask? @@ -588,7 +493,7 @@ class IPint: raise ValueError, "only IPv4 and IPv6 supported" def len(self): - """Return the length of an subnet. + """Return the length of a subnet. >>> print IP('195.185.1.0/28').len() 16 @@ -603,48 +508,61 @@ class IPint: else: raise ValueError, "only IPv4 and IPv6 supported" - return 2L ** locallen + return 2L ** locallen + + + def __nonzero__(self): + """All IPy objects should evaluate to true in boolean context. + Ordinarily they do, but if handling a default route expressed as + 0.0.0.0/0, the __len__() of the object becomes 0, which is used + as the boolean value of the object. + """ + return 1 def __len__(self): - """Return the length of an subnet. + """Return the length of a subnet. Called to implement the built-in function len(). It breaks with IPv6 Networks. Anybody knows how to fix this.""" # Python < 2.2 has this silly restriction which breaks IPv6 - # how about Python >= 2.2 ... ouch - it presists! - + # how about Python >= 2.2 ... ouch - it persists! + return int(self.len()) def __getitem__(self, key): """Called to implement evaluation of self[key]. - + >>> ip=IP('127.0.0.0/30') >>> for x in ip: - ... print hex(x.int()) + ... print repr(x) ... - 0x7F000000L - 0x7F000001L - 0x7F000002L - 0x7F000003L - >>> hex(ip[2].int()) - '0x7F000002L' - >>> hex(ip[-1].int()) - '0x7F000003L' + IP('127.0.0.0') + IP('127.0.0.1') + IP('127.0.0.2') + IP('127.0.0.3') + >>> ip[2] + IP('127.0.0.2') + >>> ip[-1] + IP('127.0.0.3') """ if type(key) != types.IntType and type(key) != types.LongType: raise TypeError - if abs(key) >= self.len(): - raise IndexError if key < 0: - key = self.len() - abs(key) + if abs(key) <= self.len(): + key = self.len() - abs(key) + else: + raise IndexError + else: + if key >= self.len(): + raise IndexError return self.ip + long(key) - + def __contains__(self, item): """Called to implement membership test operators. @@ -652,8 +570,8 @@ class IPint: Should return true if item is in self, false otherwise. Item can be other IP-objects, strings or ints. - >>> print IP('195.185.1.1').strHex() - 0xC3B90101 + >>> IP('195.185.1.1').strHex() + '0xc3b90101' >>> 0xC3B90101L in IP('195.185.1.0/24') 1 >>> '127.0.0.1' in IP('127.0.0.0/24') @@ -672,7 +590,7 @@ class IPint: def overlaps(self, item): """Check if two IP address ranges overlap. - Returns 0 if the two ranged don't overlap, 1 if the given + Returns 0 if the two ranges don't overlap, 1 if the given range overlaps at the end and -1 if it does at the beginning. >>> IP('192.168.0.0/23').overlaps('192.168.1.0/24') @@ -693,27 +611,27 @@ class IPint: else: return 0 - + def __str__(self): """Dispatch to the prefered String Representation. Used to implement str(IP).""" - return self.strFullsize() + return self.strCompressed() def __repr__(self): """Print a representation of the Object. Used to implement repr(IP). Returns a string which evaluates - to an identical Object (without the wnatprefixlen stuff - see + to an identical Object (without the wantprefixlen stuff - see module docstring. >>> print repr(IP('10.0.0.0/24')) IP('10.0.0.0/24') """ - return("IPint('%s')" % (self.strCompressed(1))) + return("IPint('%s')" % (self.strCompressed(1))) def __cmp__(self, other): @@ -724,10 +642,10 @@ class IPint: Networks with different prefixlen are considered non-equal. Networks with the same prefixlen and differing addresses are - considered non equal but are compared by thair base address + considered non equal but are compared by their base address integer value to aid sorting of IP objects. - The Version of Objects is not put into consideration. + The version of Objects is not put into consideration. >>> IP('10.0.0.0/24') > IP('10.0.0.0') 1 @@ -742,11 +660,11 @@ class IPint: # Im not really sure if this is "the right thing to do" if self._prefixlen < other.prefixlen(): - return (other.prefixlen() - self._prefixlen) + return (other.prefixlen() - self._prefixlen) elif self._prefixlen > other.prefixlen(): # Fixed bySamuel Krempp : - + # The bug is quite obvious really (as 99% bugs are once # spotted, isn't it ? ;-) Because of precedence of # multiplication by -1 over the substraction, prefixlen @@ -759,26 +677,29 @@ class IPint: # a=IP("1.0.0.0/24"); b=IP("2.0.0.0/16");) thus, anything # could happen when launching a sort algorithm.. # everything's in order with the trivial, attached patch. - + return (self._prefixlen - other.prefixlen()) * -1 else: if self.ip < other.ip: - return -1 + return -1 elif self.ip > other.ip: return 1 + elif self._ipversion != other._ipversion: + # IP('0.0.0.0'), IP('::/0') + return cmp(self._ipversion, other._ipversion) else: return 0 - - + + def __hash__(self): """Called for the key object for dictionary operations, and by - the built-in function hash() Should return a 32-bit integer + the built-in function hash(). Should return a 32-bit integer usable as a hash value for dictionary operations. The only required property is that objects which compare equal have the same hash value - >>> hex(IP('10.0.0.0/24').__hash__()) - '0xf5ffffe7' + >>> IP('10.0.0.0/24').__hash__() + -167772185 """ thehash = int(-1) @@ -791,7 +712,7 @@ class IPint: class IP(IPint): - """Class for handling IP Addresses and Networks.""" + """Class for handling IP addresses and networks.""" def net(self): """Return the base (first) address of a network as an IP object. @@ -801,7 +722,7 @@ class IP(IPint): >>> IP('10.0.0.0/8').net() IP('10.0.0.0') """ - return IP(IPint.net(self)) + return IP(IPint.net(self), ipversion=self._ipversion) def broadcast(self): """Return the broadcast (last) address of a network as an IP object. @@ -841,11 +762,12 @@ class IP(IPint): ['128.in-addr.arpa.'] >>> IP('128.0.0.0/7').reverseNames() ['128.in-addr.arpa.', '129.in-addr.arpa.'] - + >>> IP('::1:2').reverseNames() + ['2.0.0.0.1.ip6.arpa.'] """ if self._ipversion == 4: - ret =[] + ret = [] # TODO: Refactor. Add support for IPint objects if self.len() < 2**8: for x in self: @@ -870,30 +792,32 @@ class IP(IPint): s.reverse() s = '.'.join(s) first_nibble_index = int(32 - (self._prefixlen / 4)) * 2 - return ["%s.ip6.int." % s[first_nibble_index:]] + return ["%s.ip6.arpa." % s[first_nibble_index:]] else: raise ValueError, "only IPv4 and IPv6 supported" - - + + def reverseName(self): - """Return the value for reverse lookup/PTR records as RfC 2317 look alike. + """Return the value for reverse lookup/PTR records as RFC 2317 look alike. - RfC 2317 is an ugly hack which only works for sub-/24 e.g. not - for /23. Do not use it. Better set up a Zone for every - address. See reverseName for a way to arcive that. + RFC 2317 is an ugly hack which only works for sub-/24 e.g. not + for /23. Do not use it. Better set up a zone for every + address. See reverseName for a way to achieve that. >>> print IP('195.185.1.1').reverseName() 1.1.185.195.in-addr.arpa. >>> print IP('195.185.1.0/28').reverseName() 0-15.1.185.195.in-addr.arpa. + >>> IP('::1:2').reverseName() + '2.0.0.0.1.ip6.arpa.' """ if self._ipversion == 4: s = self.strFullsize(0) s = s.split('.') s.reverse() - first_byte_index = int(4 - (self._prefixlen / 8)) + first_byte_index = int(4 - (self._prefixlen / 8)) if self._prefixlen % 8 != 0: nibblepart = "%s-%s" % (s[3-(self._prefixlen / 8)], intToIp(self.ip + self.len() - 1, 4).split('.')[-1]) if nibblepart[-1] == 'l': @@ -920,13 +844,26 @@ class IP(IPint): s.reverse() s = '.'.join(s) first_nibble_index = int(32 - (self._prefixlen / 4)) * 2 - return "%s%s.ip6.int." % (nibblepart, s[first_nibble_index:]) + return "%s%s.ip6.arpa." % (nibblepart, s[first_nibble_index:]) else: raise ValueError, "only IPv4 and IPv6 supported" + def make_net(self, netmask): + """Transform a single IP address into a network specification by + applying the given netmask. + + Returns a new IP instance. + + >>> print IP('127.0.0.1').make_net('255.0.0.0') + 127.0.0.0/8 + """ + if '/' in str(netmask): + raise ValueError, "invalid netmask (%s)" % netmask + return IP('%s/%s' % (self, netmask), make_net=True) + def __getitem__(self, key): """Called to implement evaluation of self[key]. - + >>> ip=IP('127.0.0.0/30') >>> for x in ip: ... print str(x) @@ -949,7 +886,7 @@ class IP(IPint): IP('10.0.0.0/8') """ - return("IP('%s')" % (self.strCompressed(1))) + return("IP('%s')" % (self.strCompressed(1))) def __add__(self, other): """Emulate numeric objects through network aggregation""" @@ -964,27 +901,161 @@ class IP(IPint): return other.__add__(self) else: ret = IP(self.int()) - ret._prefixlen = self.prefixlen() - 1 + ret._prefixlen = self.prefixlen() - 1 return ret -def parseAddress(ipstr): - """Parse a string and return the corrospondending IPaddress and the a guess of the IP version. - Following Forms ar recorgnized: - 0x0123456789abcdef # IPv4 if <= 0xffffffff else IPv6 - 123.123.123.123 # IPv4 - 123.123 # 0-padded IPv4 - 1080:0000:0000:0000:0008:0800:200C:417A - 1080:0:0:0:8:800:200C:417A - 1080:0::8:800:200C:417A - ::1 - :: - 0:0:0:0:0:FFFF:129.144.52.38 - ::13.1.68.3 - ::FFFF:129.144.52.38 +def _parseAddressIPv6(ipstr): + """ + Internal function used by parseAddress() to parse IPv6 address with ':'. + + >>> _parseAddressIPv6('::') + 0L + >>> _parseAddressIPv6('::1') + 1L + >>> _parseAddressIPv6('0:0:0:0:0:0:0:1') + 1L + >>> _parseAddressIPv6('0:0:0::0:0:1') + 1L + >>> _parseAddressIPv6('0:0:0:0:0:0:0:0') + 0L + >>> _parseAddressIPv6('0:0:0::0:0:0') + 0L + + >>> _parseAddressIPv6('FEDC:BA98:7654:3210:FEDC:BA98:7654:3210') + 338770000845734292534325025077361652240L + >>> _parseAddressIPv6('1080:0000:0000:0000:0008:0800:200C:417A') + 21932261930451111902915077091070067066L + >>> _parseAddressIPv6('1080:0:0:0:8:800:200C:417A') + 21932261930451111902915077091070067066L + >>> _parseAddressIPv6('1080:0::8:800:200C:417A') + 21932261930451111902915077091070067066L + >>> _parseAddressIPv6('1080::8:800:200C:417A') + 21932261930451111902915077091070067066L + >>> _parseAddressIPv6('FF01:0:0:0:0:0:0:43') + 338958331222012082418099330867817087043L + >>> _parseAddressIPv6('FF01:0:0::0:0:43') + 338958331222012082418099330867817087043L + >>> _parseAddressIPv6('FF01::43') + 338958331222012082418099330867817087043L + >>> _parseAddressIPv6('0:0:0:0:0:0:13.1.68.3') + 218186755L + >>> _parseAddressIPv6('::13.1.68.3') + 218186755L + >>> _parseAddressIPv6('0:0:0:0:0:FFFF:129.144.52.38') + 281472855454758L + >>> _parseAddressIPv6('::FFFF:129.144.52.38') + 281472855454758L + >>> _parseAddressIPv6('1080:0:0:0:8:800:200C:417A') + 21932261930451111902915077091070067066L + >>> _parseAddressIPv6('1080::8:800:200C:417A') + 21932261930451111902915077091070067066L + >>> _parseAddressIPv6('::1:2:3:4:5:6') + 1208962713947218704138246L + >>> _parseAddressIPv6('1:2:3:4:5:6::') + 5192455318486707404433266432802816L + """ + + # Split string into a list, example: + # '1080:200C::417A' => ['1080', '200C', '417A'] and fill_pos=2 + # and fill_pos is the position of '::' in the list + items = [] + index = 0 + fill_pos = None + while index < len(ipstr): + text = ipstr[index:] + if text.startswith("::"): + if fill_pos is not None: + # Invalid IPv6, eg. '1::2::' + raise ValueError("%r: Invalid IPv6 address: more than one '::'" % ipstr) + fill_pos = len(items) + index += 2 + continue + pos = text.find(':') + if pos == 0: + # Invalid IPv6, eg. '1::2:' + raise ValueError("%r: Invalid IPv6 address" % ipstr) + if pos != -1: + items.append(text[:pos]) + if text[pos:pos+2] == "::": + index += pos + else: + index += pos+1 + + if index == len(ipstr): + # Invalid IPv6, eg. '1::2:' + raise ValueError("%r: Invalid IPv6 address" % ipstr) + else: + items.append(text) + break + + if items and '.' in items[-1]: + # IPv6 ending with IPv4 like '::ffff:192.168.0.1' + if not (fill_pos <= len(items)-1): + # Invalid IPv6: 'ffff:192.168.0.1::' + raise ValueError("%r: Invalid IPv6 address: '::' after IPv4" % ipstr) + value = parseAddress(items[-1])[0] + items = items[:-1] + ["%04x" % (value >> 16), "%04x" % (value & 0xffff)] + + # Expand fill_pos to fill with '0' + # ['1','2'] with fill_pos=1 => ['1', '0', '0', '0', '0', '0', '0', '2'] + if fill_pos is not None: + diff = 8 - len(items) + if diff <= 0: + raise ValueError("%r: Invalid IPv6 address: '::' is not needed" % ipstr) + items = items[:fill_pos] + ['0']*diff + items[fill_pos:] + + # Here we have a list of 8 strings + if len(items) != 8: + # Invalid IPv6, eg. '1:2:3' + raise ValueError("%r: Invalid IPv6 address: should have 8 hextets" % ipstr) + + # Convert strings to long integer + value = 0L + index = 0 + for item in items: + try: + item = int(item, 16) + error = not(0 <= item <= 0xFFFF) + except ValueError: + error = True + if error: + raise ValueError("%r: Invalid IPv6 address: invalid hexlet %r" % (ipstr, item)) + value = (value << 16) + item + index += 1 + return value + +def parseAddress(ipstr): + """ + Parse a string and return the corresponding IP address (as integer) + and a guess of the IP version. + + Following address formats are recognized: + + >>> parseAddress('0x0123456789abcdef') # IPv4 if <= 0xffffffff else IPv6 + (81985529216486895L, 6) + >>> parseAddress('123.123.123.123') # IPv4 + (2071690107L, 4) + >>> parseAddress('123.123') # 0-padded IPv4 + (2071658496L, 4) + >>> parseAddress('1080:0000:0000:0000:0008:0800:200C:417A') + (21932261930451111902915077091070067066L, 6) + >>> parseAddress('1080:0:0:0:8:800:200C:417A') + (21932261930451111902915077091070067066L, 6) + >>> parseAddress('1080:0::8:800:200C:417A') + (21932261930451111902915077091070067066L, 6) + >>> parseAddress('::1') + (1L, 6) + >>> parseAddress('::') + (0L, 6) + >>> parseAddress('0:0:0:0:0:FFFF:129.144.52.38') + (281472855454758L, 6) + >>> parseAddress('::13.1.68.3') + (218186755L, 6) + >>> parseAddress('::FFFF:129.144.52.38') + (281472855454758L, 6) """ - # TODO: refactor me! if ipstr.startswith('0x'): ret = long(ipstr[2:], 16) if ret > 0xffffffffffffffffffffffffffffffffL: @@ -993,51 +1064,14 @@ def parseAddress(ipstr): return (ret, 4) else: return (ret, 6) - + if ipstr.find(':') != -1: - # assume IPv6 - if ipstr.find(':::') != -1: - raise ValueError, "%r: IPv6 Address can't contain ':::'" % (ipstr) - hextets = ipstr.split(':') - if ipstr.find('.') != -1: - # this might be a mixed address like '0:0:0:0:0:0:13.1.68.3' - (v4, foo) = parseAddress(hextets[-1]) - assert foo == 4 - del(hextets[-1]) - hextets.append(hex(v4 >> 16)[2:-1]) - hextets.append(hex(v4 & 0xffff)[2:-1]) - if len(hextets) > 8: - raise ValueError, "%r: IPv6 Address with more than 8 hexletts" % (ipstr) - if len(hextets) < 8: - if '' not in hextets: - raise ValueError, "%r IPv6 Address with less than 8 hexletts and without '::'" % (ipstr) - # catch :: at the beginning or end - if hextets.index('') < len(hextets) - 1 and hextets[hextets.index('')+1] == '': - hextets.remove('') - # catch '::' - if hextets.index('') < len(hextets) - 1 and hextets[hextets.index('')+1] == '': - hextets.remove('') - - for foo in range(9-len(hextets)): - hextets.insert(hextets.index(''), '0') - hextets.remove('') - if '' in hextets: - raise ValueError, "%r IPv6 Address may contain '::' only once" % (ipstr) - if '' in hextets: - raise ValueError, "%r IPv6 Address may contain '::' only if it has less than 8 hextets" % (ipstr) - num = '' - for x in hextets: - if len(x) < 4: - x = ((4 - len(x)) * '0') + x - if int(x, 16) < 0 or int(x, 16) > 0xffff: - raise ValueError, "%r: single hextet must be 0 <= hextet <= 0xffff which isn't true for %s" % (ipstr, x) - num += x - return (long(num, 16), 6) + return (_parseAddressIPv6(ipstr), 6) elif len(ipstr) == 32: # assume IPv6 in pure hexadecimal notation return (long(ipstr, 16), 6) - + elif ipstr.find('.') != -1 or (len(ipstr) < 4 and int(ipstr) < 256): # assume IPv4 ('127' gets interpreted as '127.0.0.0') bytes = ipstr.split('.') @@ -1054,9 +1088,9 @@ def parseAddress(ipstr): # we try to interprete it as a decimal digit - # this ony works for numbers > 255 ... others # will be interpreted as IPv4 first byte - ret = long(ipstr) + ret = long(ipstr, 10) if ret > 0xffffffffffffffffffffffffffffffffL: - raise ValueError, "IP Address cant be bigger than 2^128" + raise ValueError, "IP Address can't be bigger than 2^128" if ret <= 0xffffffffL: return (ret, 4) else: @@ -1071,28 +1105,28 @@ def intToIp(ip, version): if ip < 0: raise ValueError, "IPs can't be negative: %d" % (ip) - + ret = '' - if version == 4: + if version == 4: if ip > 0xffffffffL: raise ValueError, "IPv4 Addresses can't be larger than 0xffffffff: %s" % (hex(ip)) for l in range(4): ret = str(ip & 0xffL) + '.' + ret - ip = ip >> 8; + ip = ip >> 8 ret = ret[:-1] elif version == 6: if ip > 0xffffffffffffffffffffffffffffffffL: raise ValueError, "IPv6 Addresses can't be larger than 0xffffffffffffffffffffffffffffffff: %s" % (hex(ip)) l = '0' * 32 + hex(ip)[2:-1] - for x in range(1,33): + for x in range(1, 33): ret = l[-x] + ret if x % 4 == 0: ret = ':' + ret ret = ret[1:] else: raise ValueError, "only IPv4 and IPv6 supported" - - return ret; + + return ret def _ipVersionToLen(version): """Return number of bits in address for a certain IP version. @@ -1118,7 +1152,7 @@ def _ipVersionToLen(version): def _countFollowingZeros(l): - """Return Nr. of elements containing 0 at the beginning th the list.""" + """Return number of elements containing 0 at the beginning of the list.""" if len(l) == 0: return 0 elif l[0] != 0: @@ -1131,12 +1165,12 @@ _BitTable = {'0': '0000', '1': '0001', '2': '0010', '3': '0011', '4': '0100', '5': '0101', '6': '0110', '7': '0111', '8': '1000', '9': '1001', 'a': '1010', 'b': '1011', 'c': '1100', 'd': '1101', 'e': '1110', 'f': '1111'} - + def _intToBin(val): """Return the binary representation of an integer as string.""" if val < 0: - raise ValueError, "Only positive Values allowed" + raise ValueError, "Only positive values allowed" s = hex(val).lower() ret = '' if s[-1] == 'l': @@ -1149,7 +1183,7 @@ def _intToBin(val): # remove leading zeros while ret[0] == '0' and len(ret) > 1: ret = ret[1:] - return ret + return ret def _count1Bits(num): """Find the highest bit set to 1 in an integer.""" @@ -1172,12 +1206,12 @@ def _count0Bits(num): break num = num >> 1 ret += 1 - return ret + return ret + - def _checkPrefix(ip, prefixlen, version): """Check the validity of a prefix - + Checks if the variant part of a prefix only has 0s, and the length is correct. @@ -1193,11 +1227,11 @@ def _checkPrefix(ip, prefixlen, version): # TODO: unify this v4/v6/invalid code in a function bits = _ipVersionToLen(version) - + if prefixlen < 0 or prefixlen > bits: return None - if ip == 0: + if ip == 0: zbits = bits + 1 else: zbits = _count0Bits(ip) @@ -1208,13 +1242,13 @@ def _checkPrefix(ip, prefixlen, version): def _checkNetmask(netmask, masklen): - """Checks if a netmask is expressable as e prefixlen.""" + """Checks if a netmask is expressable as a prefixlen.""" num = long(netmask) bits = masklen - + # remove zero bits at the end - while (num & 1) == 0: + while (num & 1) == 0 and bits != 0: num = num >> 1 bits -= 1 if bits == 0: @@ -1228,15 +1262,15 @@ def _checkNetmask(netmask, masklen): def _checkNetaddrWorksWithPrefixlen(net, prefixlen, version): - """Check if a base addess of e network is compatible with a prefixlen""" + """Check if a base addess of a network is compatible with a prefixlen""" if net & _prefixlenToNetmask(prefixlen, version) == net: return 1 else: return 0 - + def _netmaskToPrefixlen(netmask): - """Convert an Integer reprsenting a Netmask to an prefixlen. + """Convert an Integer representing a netmask to a prefixlen. E.g. 0xffffff00 (255.255.255.0) returns 24 """ @@ -1250,7 +1284,8 @@ def _netmaskToPrefixlen(netmask): def _prefixlenToNetmask(prefixlen, version): """Return a mask of n bits as a long integer. - From 'IP address conversion functions with the builtin socket module' by Alex Martelli + From 'IP address conversion functions with the builtin socket module' + by Alex Martelli http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66517 """ if prefixlen == 0: @@ -1258,17 +1293,12 @@ def _prefixlenToNetmask(prefixlen, version): elif prefixlen < 0: raise ValueError, "Prefixlen must be > 0" return ((2L<