1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2024-12-26 08:05:22 +00:00
webvirtcloud/vrtManager/IPy.py

1658 lines
60 KiB
Python
Raw Normal View History

2015-02-27 08:53:51 +00:00
"""
IPy - class and tools for handling of IPv4 and IPv6 addresses and networks.
See README file for learn how to use IPy.
Further Information might be available at:
https://github.com/haypo/python-ipy
2015-02-27 08:53:51 +00:00
"""
__version__ = '1.01'
2015-02-27 08:53:51 +00:00
import bisect
2015-02-27 08:53:51 +00:00
import types
try:
import collections.abc as collections_abc
except ImportError:
import collections as collections_abc
2015-02-27 08:53:51 +00:00
# Definition of the Ranges for IPv4 IPs
# 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
'0110010001': 'CARRIER_GRADE_NAT', #100.64/10
'01111111': 'LOOPBACK', # 127.0/8
'1': 'PUBLIC', # fall back
'1010100111111110': 'PRIVATE', # 169.254/16
'101011000001': 'PRIVATE', # 172.16/12
'1100000010101000': 'PRIVATE', # 192.168/16
'111': 'RESERVED', # 224/3
}
2015-02-27 08:53:51 +00:00
# Definition of the Ranges for IPv6 IPs
# http://www.iana.org/assignments/ipv6-address-space/
# http://www.iana.org/assignments/ipv6-unicast-address-assignments/
# http://www.iana.org/assignments/ipv6-multicast-addresses/
2015-02-27 08:53:51 +00:00
IPv6ranges = {
'00000000' : 'RESERVED', # ::/8
'0' * 96 : 'RESERVED', # ::/96 Formerly IPV4COMP [RFC4291]
'0' * 128 : 'UNSPECIFIED', # ::/128
'0' * 127 + '1' : 'LOOPBACK', # ::1/128
'0' * 80 + '1' * 16 : 'IPV4MAP', # ::ffff:0:0/96
'00000000011001001111111110011011' + '0' * 64 : 'WKP46TRANS', # 0064:ff9b::/96 Well-Known-Prefix [RFC6052]
'00000001' : 'UNASSIGNED', # 0100::/8
'0000001' : 'RESERVED', # 0200::/7 Formerly NSAP [RFC4048]
'0000010' : 'RESERVED', # 0400::/7 Formerly IPX [RFC3513]
'0000011' : 'RESERVED', # 0600::/7
'00001' : 'RESERVED', # 0800::/5
'0001' : 'RESERVED', # 1000::/4
'001' : 'GLOBAL-UNICAST', # 2000::/3 [RFC4291]
'00100000000000010000000' : 'SPECIALPURPOSE', # 2001::/23 [RFC4773]
'00100000000000010000000000000000' : 'TEREDO', # 2001::/32 [RFC4380]
'00100000000000010000000000000010' + '0' * 16 : 'BMWG', # 2001:0002::/48 Benchmarking [RFC5180]
'0010000000000001000000000001' : 'ORCHID', # 2001:0010::/28 (Temp until 2014-03-21) [RFC4843]
'00100000000000010000001' : 'ALLOCATED APNIC', # 2001:0200::/23
'00100000000000010000010' : 'ALLOCATED ARIN', # 2001:0400::/23
'00100000000000010000011' : 'ALLOCATED RIPE NCC', # 2001:0600::/23
'00100000000000010000100' : 'ALLOCATED RIPE NCC', # 2001:0800::/23
'00100000000000010000101' : 'ALLOCATED RIPE NCC', # 2001:0a00::/23
'00100000000000010000110' : 'ALLOCATED APNIC', # 2001:0c00::/23
'00100000000000010000110110111000' : 'DOCUMENTATION', # 2001:0db8::/32 [RFC3849]
'00100000000000010000111' : 'ALLOCATED APNIC', # 2001:0e00::/23
'00100000000000010001001' : 'ALLOCATED LACNIC', # 2001:1200::/23
'00100000000000010001010' : 'ALLOCATED RIPE NCC', # 2001:1400::/23
'00100000000000010001011' : 'ALLOCATED RIPE NCC', # 2001:1600::/23
'00100000000000010001100' : 'ALLOCATED ARIN', # 2001:1800::/23
'00100000000000010001101' : 'ALLOCATED RIPE NCC', # 2001:1a00::/23
'0010000000000001000111' : 'ALLOCATED RIPE NCC', # 2001:1c00::/22
'00100000000000010010' : 'ALLOCATED RIPE NCC', # 2001:2000::/20
'001000000000000100110' : 'ALLOCATED RIPE NCC', # 2001:3000::/21
'0010000000000001001110' : 'ALLOCATED RIPE NCC', # 2001:3800::/22
'0010000000000001001111' : 'RESERVED', # 2001:3c00::/22 Possible future allocation to RIPE NCC
'00100000000000010100000' : 'ALLOCATED RIPE NCC', # 2001:4000::/23
'00100000000000010100001' : 'ALLOCATED AFRINIC', # 2001:4200::/23
'00100000000000010100010' : 'ALLOCATED APNIC', # 2001:4400::/23
'00100000000000010100011' : 'ALLOCATED RIPE NCC', # 2001:4600::/23
'00100000000000010100100' : 'ALLOCATED ARIN', # 2001:4800::/23
'00100000000000010100101' : 'ALLOCATED RIPE NCC', # 2001:4a00::/23
'00100000000000010100110' : 'ALLOCATED RIPE NCC', # 2001:4c00::/23
'00100000000000010101' : 'ALLOCATED RIPE NCC', # 2001:5000::/20
'0010000000000001100' : 'ALLOCATED APNIC', # 2001:8000::/19
'00100000000000011010' : 'ALLOCATED APNIC', # 2001:a000::/20
'00100000000000011011' : 'ALLOCATED APNIC', # 2001:b000::/20
'0010000000000010' : '6TO4', # 2002::/16 "6to4" [RFC3056]
'001000000000001100' : 'ALLOCATED RIPE NCC', # 2003::/18
'001001000000' : 'ALLOCATED APNIC', # 2400::/12
'001001100000' : 'ALLOCATED ARIN', # 2600::/12
'00100110000100000000000' : 'ALLOCATED ARIN', # 2610::/23
'00100110001000000000000' : 'ALLOCATED ARIN', # 2620::/23
'001010000000' : 'ALLOCATED LACNIC', # 2800::/12
'001010100000' : 'ALLOCATED RIPE NCC', # 2a00::/12
'001011000000' : 'ALLOCATED AFRINIC', # 2c00::/12
'00101101' : 'RESERVED', # 2d00::/8
'0010111' : 'RESERVED', # 2e00::/7
'0011' : 'RESERVED', # 3000::/4
'010' : 'RESERVED', # 4000::/3
'011' : 'RESERVED', # 6000::/3
'100' : 'RESERVED', # 8000::/3
'101' : 'RESERVED', # a000::/3
'110' : 'RESERVED', # c000::/3
'1110' : 'RESERVED', # e000::/4
'11110' : 'RESERVED', # f000::/5
'111110' : 'RESERVED', # f800::/6
'1111110' : 'ULA', # fc00::/7 [RFC4193]
'111111100' : 'RESERVED', # fe00::/9
'1111111010' : 'LINKLOCAL', # fe80::/10
'1111111011' : 'RESERVED', # fec0::/10 Formerly SITELOCAL [RFC4291]
'11111111' : 'MULTICAST', # ff00::/8
'1111111100000001' : 'NODE-LOCAL MULTICAST', # ff01::/16
'1111111100000010' : 'LINK-LOCAL MULTICAST', # ff02::/16
'1111111100000100' : 'ADMIN-LOCAL MULTICAST', # ff04::/16
'1111111100000101' : 'SITE-LOCAL MULTICAST', # ff05::/16
'1111111100001000' : 'ORG-LOCAL MULTICAST', # ff08::/16
'1111111100001110' : 'GLOBAL MULTICAST', # ff0e::/16
'1111111100001111' : 'RESERVED MULTICAST', # ff0f::/16
'111111110011' : 'PREFIX-BASED MULTICAST', # ff30::/12 [RFC3306]
'111111110111' : 'RP-EMBEDDED MULTICAST', # ff70::/12 [RFC3956]
}
MAX_IPV4_ADDRESS = 0xffffffff
MAX_IPV6_ADDRESS = 0xffffffffffffffffffffffffffffffff
IPV6_TEST_MAP = 0xffffffffffffffffffffffff00000000
IPV6_MAP_MASK = 0x00000000000000000000ffff00000000
try:
INT_TYPES = (int, long)
STR_TYPES = (str, unicode)
xrange
except NameError:
INT_TYPES = (int,)
STR_TYPES = (str,)
xrange = range
2015-02-27 08:53:51 +00:00
class IPint(object):
2015-02-27 08:53:51 +00:00
"""Handling of IP addresses returning integers.
Use class IP instead because some features are not implemented for
IPint."""
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
/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
/decimal netmask a.b.c.d/255.255.255.0 not supported for IPv6
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'))
2015-02-27 08:53:51 +00:00
127.0.0.0/8
>>> print(IP('127.0.0.0/255.0.0.0'))
2015-02-27 08:53:51 +00:00
127.0.0.0/8
>>> print(IP('127.0.0.0-127.255.255.255'))
2015-02-27 08:53:51 +00:00
127.0.0.0/8
>>> print(IP('127.0.0.1/255.0.0.0', make_net=True))
2015-02-27 08:53:51 +00:00
127.0.0.0/8
See module documentation for more examples.
"""
# 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 isinstance(data, INT_TYPES):
self.ip = int(data)
2015-02-27 08:53:51 +00:00
if ipversion == 0:
ipversion = 4 if self.ip <= MAX_IPV4_ADDRESS else 6
2015-02-27 08:53:51 +00:00
if ipversion == 4:
if self.ip > MAX_IPV4_ADDRESS:
raise ValueError("IPv4 Address can't be larger than %x: %x" % (MAX_IPV4_ADDRESS, self.ip))
2015-02-27 08:53:51 +00:00
prefixlen = 32
elif ipversion == 6:
if self.ip > MAX_IPV6_ADDRESS:
raise ValueError("IPv6 Address can't be larger than %x: %x" % (MAX_IPV6_ADDRESS, self.ip))
2015-02-27 08:53:51 +00:00
prefixlen = 128
else:
raise ValueError("only IPv4 and IPv6 supported")
2015-02-27 08:53:51 +00:00
self._ipversion = ipversion
self._prefixlen = prefixlen
# handle IP instance as an parameter
elif isinstance(data, IPint):
self._ipversion = data._ipversion
self._prefixlen = data._prefixlen
self.ip = data.ip
elif isinstance(data, STR_TYPES):
2015-02-27 08:53:51 +00:00
# TODO: refactor me!
# splitting of a string into IP and prefixlen et. al.
x = data.split('-')
2015-02-27 08:53:51 +00:00
if len(x) == 2:
# a.b.c.0-a.b.c.255 specification ?
(ip, last) = x
(self.ip, parsedVersion) = parseAddress(ip)
if parsedVersion != 4:
raise ValueError("first-last notation only allowed for IPv4")
2015-02-27 08:53:51 +00:00
(last, lastversion) = parseAddress(last)
if lastversion != 4:
raise ValueError("last address should be IPv4, too")
2015-02-27 08:53:51 +00:00
if last < self.ip:
raise ValueError("last address should be larger than first")
2015-02-27 08:53:51 +00:00
size = last - self.ip
netbits = _count1Bits(size)
# 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)
2015-02-27 08:53:51 +00:00
elif len(x) == 1:
x = data.split('/')
2015-02-27 08:53:51 +00:00
# if no prefix is given use defaults
if len(x) == 1:
ip = x[0]
prefixlen = -1
elif len(x) > 2:
raise ValueError("only one '/' allowed in IP Address")
2015-02-27 08:53:51 +00:00
else:
(ip, prefixlen) = x
if prefixlen.find('.') != -1:
2015-02-27 08:53:51 +00:00
# check if the user might have used a netmask like
# a.b.c.d/255.255.255.0
(netmask, vers) = parseAddress(prefixlen)
if vers != 4:
raise ValueError("netmask must be IPv4")
2015-02-27 08:53:51 +00:00
prefixlen = _netmaskToPrefixlen(netmask)
elif len(x) > 2:
raise ValueError("only one '-' allowed in IP Address")
2015-02-27 08:53:51 +00:00
else:
raise ValueError("can't parse")
2015-02-27 08:53:51 +00:00
2019-09-09 13:53:52 +00:00
(self.ip, parsedVersion) = parseAddress(ip, ipversion)
2015-02-27 08:53:51 +00:00
if ipversion == 0:
ipversion = parsedVersion
if prefixlen == -1:
bits = _ipVersionToLen(ipversion)
prefixlen = bits - netbits
2015-02-27 08:53:51 +00:00
self._ipversion = ipversion
self._prefixlen = int(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))
else:
raise TypeError("Unsupported data type: %s" % type(data))
2015-02-27 08:53:51 +00:00
def int(self):
"""Return the first / base / network addess as an (long) integer.
The same as IP[0].
>>> "%X" % IP('10.0.0.0/8').int()
'A000000'
"""
return self.ip
def version(self):
"""Return the IP version of this Object.
>>> IP('10.0.0.0/8').version()
4
>>> IP('::1').version()
6
"""
return self._ipversion
def prefixlen(self):
"""Returns Network Prefixlen.
>>> IP('10.0.0.0/8').prefixlen()
8
"""
return self._prefixlen
def net(self):
"""
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.
The same as IP[-1]."""
return self.int() + self.len() - 1
def _printPrefix(self, want):
"""Prints Prefixlen/Netmask.
Not really. In fact it is our universal Netmask/Prefixlen printer.
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
want == 2 /netmask 1.2.3.0/255.255.255.0
want == 3 -lastip 1.2.3.0-1.2.3.255
"""
if (self._ipversion == 4 and self._prefixlen == 32) or \
(self._ipversion == 6 and self._prefixlen == 128):
2015-02-27 08:53:51 +00:00
if self.NoPrefixForSingleIp:
want = 0
if want == None:
2015-02-27 08:53:51 +00:00
want = self.WantPrefixLen
if want == None:
2015-02-27 08:53:51 +00:00
want = 1
if want:
if want == 2:
# this should work with IP and IPint
netmask = self.netmask()
if not isinstance(netmask, INT_TYPES):
2015-02-27 08:53:51 +00:00
netmask = netmask.int()
return "/%s" % (intToIp(netmask, self._ipversion))
elif want == 3:
return "-%s" % (intToIp(self.ip + self.len() - 1, self._ipversion))
else:
# default
return "/%d" % (self._prefixlen)
else:
return ''
2015-02-27 08:53:51 +00:00
# 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 0x7F000001 0x20010658022ACAFE0200C0FFFE8D08FA
# strDec 2130706433 42540616829182469433547974687817795834
2015-02-27 08:53:51 +00:00
def strBin(self, wantprefixlen = None):
2015-02-27 08:53:51 +00:00
"""Return a string representation as a binary value.
>>> print(IP('127.0.0.1').strBin())
2015-02-27 08:53:51 +00:00
01111111000000000000000000000001
>>> print(IP('2001:0658:022a:cafe:0200::1').strBin())
00100000000000010000011001011000000000100010101011001010111111100000001000000000000000000000000000000000000000000000000000000001
2015-02-27 08:53:51 +00:00
"""
bits = _ipVersionToLen(self._ipversion)
if self.WantPrefixLen == None and wantprefixlen == None:
2015-02-27 08:53:51 +00:00
wantprefixlen = 0
ret = _intToBin(self.ip)
return '0' * (bits - len(ret)) + ret + self._printPrefix(wantprefixlen)
2015-02-27 08:53:51 +00:00
def strCompressed(self, wantprefixlen = None):
2015-02-27 08:53:51 +00:00
"""Return a string representation in compressed format using '::' Notation.
>>> 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:
2015-02-27 08:53:51 +00:00
wantprefixlen = 1
if self._ipversion == 4:
return self.strFullsize(wantprefixlen)
else:
if self.ip >> 32 == 0xffff:
ipv4 = intToIp(self.ip & MAX_IPV4_ADDRESS, 4)
2015-02-27 08:53:51 +00:00
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(':')]
2015-02-27 08:53:51 +00:00
# every element of followingzeros will contain the number of zeros
# following the corresponding element of hextets
followingzeros = [0] * 8
for i in xrange(len(hextets)):
2015-02-27 08:53:51 +00:00
followingzeros[i] = _countFollowingZeros(hextets[i:])
# compressionpos is the position where we can start removing zeros
compressionpos = followingzeros.index(max(followingzeros))
if max(followingzeros) > 1:
# genererate string with the longest number of zeros cut out
# now we need hextets as strings
hextets = [x for x in self.strNormal(0).split(':')]
while compressionpos < len(hextets) and hextets[compressionpos] == '0':
del(hextets[compressionpos])
hextets.insert(compressionpos, '')
2015-02-27 08:53:51 +00:00
if compressionpos + 1 >= len(hextets):
hextets.append('')
2015-02-27 08:53:51 +00:00
if compressionpos == 0:
hextets = [''] + hextets
return ':'.join(hextets) + self._printPrefix(wantprefixlen)
2015-02-27 08:53:51 +00:00
else:
return self.strNormal(0) + self._printPrefix(wantprefixlen)
def strNormal(self, wantprefixlen = None):
2015-02-27 08:53:51 +00:00
"""Return a string representation in the usual format.
>>> print(IP('127.0.0.1').strNormal())
2015-02-27 08:53:51 +00:00
127.0.0.1
>>> print(IP('2001:0658:022a:cafe:0200::1').strNormal())
2015-02-27 08:53:51 +00:00
2001:658:22a:cafe:200:0:0:1
"""
if self.WantPrefixLen == None and wantprefixlen == None:
2015-02-27 08:53:51 +00:00
wantprefixlen = 1
if self._ipversion == 4:
ret = self.strFullsize(0)
elif self._ipversion == 6:
ret = ':'.join(["%x" % x for x in [int(x, 16) for x in self.strFullsize(0).split(':')]])
2015-02-27 08:53:51 +00:00
else:
raise ValueError("only IPv4 and IPv6 supported")
2015-02-27 08:53:51 +00:00
2015-02-27 08:53:51 +00:00
return ret + self._printPrefix(wantprefixlen)
def strFullsize(self, wantprefixlen = None):
2015-02-27 08:53:51 +00:00
"""Return a string representation in the non-mangled format.
>>> print(IP('127.0.0.1').strFullsize())
2015-02-27 08:53:51 +00:00
127.0.0.1
>>> print(IP('2001:0658:022a:cafe:0200::1').strFullsize())
2015-02-27 08:53:51 +00:00
2001:0658:022a:cafe:0200:0000:0000:0001
"""
if self.WantPrefixLen == None and wantprefixlen == None:
2015-02-27 08:53:51 +00:00
wantprefixlen = 1
return intToIp(self.ip, self._ipversion) + self._printPrefix(wantprefixlen)
2015-02-27 08:53:51 +00:00
def strHex(self, wantprefixlen = None):
2015-02-27 08:53:51 +00:00
"""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
2015-02-27 08:53:51 +00:00
"""
if self.WantPrefixLen == None and wantprefixlen == None:
2015-02-27 08:53:51 +00:00
wantprefixlen = 0
x = '0x%x' % self.ip
return x + self._printPrefix(wantprefixlen)
2015-02-27 08:53:51 +00:00
def strDec(self, wantprefixlen = None):
2015-02-27 08:53:51 +00:00
"""Return a string representation in decimal format.
>>> print(IP('127.0.0.1').strDec())
2015-02-27 08:53:51 +00:00
2130706433
>>> print(IP('2001:0658:022a:cafe:0200::1').strDec())
2015-02-27 08:53:51 +00:00
42540616829182469433547762482097946625
"""
if self.WantPrefixLen == None and wantprefixlen == None:
2015-02-27 08:53:51 +00:00
wantprefixlen = 0
x = '%d' % self.ip
2015-02-27 08:53:51 +00:00
return x + self._printPrefix(wantprefixlen)
def iptype(self):
"""Return a description of the IP type ('PRIVATE', 'RESERVED', etc).
2015-02-27 08:53:51 +00:00
>>> print(IP('127.0.0.1').iptype())
2019-09-09 13:53:52 +00:00
LOOPBACK
>>> print(IP('192.168.1.1').iptype())
2015-02-27 08:53:51 +00:00
PRIVATE
>>> print(IP('195.185.1.2').iptype())
2015-02-27 08:53:51 +00:00
PUBLIC
>>> print(IP('::1').iptype())
2015-02-27 08:53:51 +00:00
LOOPBACK
>>> print(IP('2001:0658:022a:cafe:0200::1').iptype())
ALLOCATED RIPE NCC
2015-02-27 08:53:51 +00:00
The type information for IPv6 is out of sync with reality.
"""
# this could be greatly improved
if self._ipversion == 4:
iprange = IPv4ranges
elif self._ipversion == 6:
iprange = IPv6ranges
else:
raise ValueError("only IPv4 and IPv6 supported")
2015-02-27 08:53:51 +00:00
bits = self.strBin()
for i in xrange(len(bits), 0, -1):
if bits[:i] in iprange:
2015-02-27 08:53:51 +00:00
return iprange[bits[:i]]
return "unknown"
2015-02-27 08:53:51 +00:00
def netmask(self):
"""Return netmask as an integer.
>>> "%X" % IP('195.185.0.0/16').netmask().int()
'FFFF0000'
"""
# TODO: unify with prefixlenToNetmask?
bits = _ipVersionToLen(self._ipversion)
locallen = bits - self._prefixlen
2015-02-27 08:53:51 +00:00
return ((2 ** self._prefixlen) - 1) << locallen
2015-02-27 08:53:51 +00:00
2015-02-27 08:53:51 +00:00
def strNetmask(self):
"""Return netmask as an string. Mostly useful for IPv6.
>>> print(IP('195.185.0.0/16').strNetmask())
2015-02-27 08:53:51 +00:00
255.255.0.0
>>> print(IP('2001:0658:022a:cafe::0/64').strNetmask())
2015-02-27 08:53:51 +00:00
/64
"""
# TODO: unify with prefixlenToNetmask?
# Note: call to _ipVersionToLen() also validates version is 4 or 6
bits = _ipVersionToLen(self._ipversion)
2015-02-27 08:53:51 +00:00
if self._ipversion == 4:
locallen = bits - self._prefixlen
return intToIp(((2 ** self._prefixlen) - 1) << locallen, 4)
2015-02-27 08:53:51 +00:00
elif self._ipversion == 6:
return "/%d" % self._prefixlen
def len(self):
"""Return the length of a subnet.
>>> print(IP('195.185.1.0/28').len())
2015-02-27 08:53:51 +00:00
16
>>> print(IP('195.185.1.0/24').len())
2015-02-27 08:53:51 +00:00
256
"""
bits = _ipVersionToLen(self._ipversion)
locallen = bits - self._prefixlen
return 2 ** locallen
2015-02-27 08:53:51 +00:00
2015-02-27 08:53:51 +00:00
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 True
2015-02-27 08:53:51 +00:00
2019-09-09 13:53:52 +00:00
def __bool__(self):
return self.__nonzero__()
2015-02-27 08:53:51 +00:00
def __len__(self):
"""
Return the length of a subnet.
2015-02-27 08:53:51 +00:00
Called to implement the built-in function len().
It will break with large IPv6 Networks.
Use the object's len() instead.
"""
return self.len()
2015-02-27 08:53:51 +00:00
def __add__(self, other):
"""Emulate numeric objects through network aggregation"""
if self._ipversion != other._ipversion:
raise ValueError("Only networks with the same IP version can be added.")
if self._prefixlen != other._prefixlen:
raise ValueError("Only networks with the same prefixlen can be added.")
if self._prefixlen < 1:
raise ValueError("Networks with a prefixlen longer than /1 can't be added.")
if self > other:
# fixed by Skinny Puppy <skin_pup-IPy@happypoo.com>
return other.__add__(self)
if other.int() - self[-1].int() != 1:
raise ValueError("Only adjacent networks can be added together.")
ret = IP(self.int(), ipversion=self._ipversion)
ret._prefixlen = self.prefixlen() - 1
if not _checkNetaddrWorksWithPrefixlen(ret.ip, ret._prefixlen,
ret._ipversion):
raise ValueError("The resulting %s has invalid prefix length (%s)"
% (repr(ret), ret._prefixlen))
return ret
def __sub__(self, other):
"""Return the prefixes that are in this IP but not in the other"""
return _remove_subprefix(self, other)
2015-02-27 08:53:51 +00:00
def __getitem__(self, key):
"""Called to implement evaluation of self[key].
>>> ip=IP('127.0.0.0/30')
>>> for x in ip:
... print(repr(x))
2015-02-27 08:53:51 +00:00
...
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 isinstance(key, slice):
return [self.ip + int(x) for x in xrange(*key.indices(len(self)))]
if not isinstance(key, INT_TYPES):
2015-02-27 08:53:51 +00:00
raise TypeError
if key < 0:
if abs(key) <= self.len():
key = self.len() - abs(key)
else:
raise IndexError
else:
if key >= self.len():
raise IndexError
return self.ip + int(key)
2015-02-27 08:53:51 +00:00
2015-02-27 08:53:51 +00:00
def __contains__(self, item):
"""Called to implement membership test operators.
Should return true if item is in self, false otherwise. Item
can be other IP-objects, strings or ints.
>>> IP('195.185.1.1').strHex()
'0xc3b90101'
>>> 0xC3B90101 in IP('195.185.1.0/24')
True
2015-02-27 08:53:51 +00:00
>>> '127.0.0.1' in IP('127.0.0.0/24')
True
2015-02-27 08:53:51 +00:00
>>> IP('127.0.0.0/24') in IP('127.0.0.0/25')
False
2015-02-27 08:53:51 +00:00
"""
if isinstance(item, IP):
if item._ipversion != self._ipversion:
return False
else:
item = IP(item)
2015-02-27 08:53:51 +00:00
if item.ip >= self.ip and item.ip < self.ip + self.len() - item.len() + 1:
return True
2015-02-27 08:53:51 +00:00
else:
return False
2015-02-27 08:53:51 +00:00
2015-02-27 08:53:51 +00:00
def overlaps(self, item):
"""Check if two IP address ranges overlap.
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')
1
>>> IP('192.168.0.0/23').overlaps('192.168.1.255')
1
>>> IP('192.168.0.0/23').overlaps('192.168.2.0')
0
>>> IP('192.168.1.0/24').overlaps('192.168.0.0/23')
-1
"""
if not isinstance(item, IP):
item = IP(item)
2015-02-27 08:53:51 +00:00
if item.ip >= self.ip and item.ip < self.ip + self.len():
return 1
elif self.ip >= item.ip and self.ip < item.ip + item.len():
return -1
else:
return 0
2015-02-27 08:53:51 +00:00
def __str__(self):
"""Dispatch to the prefered String Representation.
Used to implement str(IP)."""
return self.strCompressed()
2015-02-27 08:53:51 +00:00
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 wantprefixlen stuff - see
module docstring.
>>> print(repr(IP('10.0.0.0/24')))
2015-02-27 08:53:51 +00:00
IP('10.0.0.0/24')
"""
return("IPint('%s')" % (self.strCompressed(1)))
2015-02-27 08:53:51 +00:00
def __cmp__(self, other):
"""Called by comparison operations.
Should return a negative integer if self < other, zero if self
== other, a positive integer if self > other.
Order is first determined by the address family. IPv4 addresses
are always smaller than IPv6 addresses:
>>> IP('10.0.0.0') < IP('2001:db8::')
2015-02-27 08:53:51 +00:00
1
Then the first address is compared. Lower addresses are
always smaller:
>>> IP('10.0.0.0') > IP('10.0.0.1')
0
>>> IP('10.0.0.0/24') > IP('10.0.0.1')
2015-02-27 08:53:51 +00:00
0
>>> IP('10.0.1.0') > IP('10.0.0.0/24')
2015-02-27 08:53:51 +00:00
1
>>> IP('10.0.1.0/24') > IP('10.0.0.0/24')
1
>>> IP('10.0.1.0/24') > IP('10.0.0.0')
1
Then the prefix length is compared. Shorter prefixes are
considered smaller than longer prefixes:
>>> IP('10.0.0.0/24') > IP('10.0.0.0')
0
>>> IP('10.0.0.0/24') > IP('10.0.0.0/25')
2015-02-27 08:53:51 +00:00
0
>>> IP('10.0.0.0/24') > IP('10.0.0.0/23')
1
2015-02-27 08:53:51 +00:00
"""
if not isinstance(other, IPint):
raise TypeError
# Lower version -> lower result
if self._ipversion != other._ipversion:
return self._ipversion < other._ipversion and -1 or 1
# Lower start address -> lower result
if self.ip != other.ip:
return self.ip < other.ip and -1 or 1
# Shorter prefix length -> lower result
if self._prefixlen != other._prefixlen:
return self._prefixlen < other._prefixlen and -1 or 1
# No differences found
return 0
2015-02-27 08:53:51 +00:00
def __eq__(self, other):
if not isinstance(other, IPint):
return False
return self.__cmp__(other) == 0
def __ne__(self, other):
return not self.__eq__(other)
2015-02-27 08:53:51 +00:00
def __lt__(self, other):
return self.__cmp__(other) < 0
2015-02-27 08:53:51 +00:00
2019-09-09 13:53:52 +00:00
def __le__(self, other):
return self.__cmp__(other) <= 0
2015-02-27 08:53:51 +00:00
def __hash__(self):
"""Called for the key object for dictionary operations, and by
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
>>> IP('10.0.0.0/24').__hash__()
-167772185
"""
thehash = int(-1)
ip = self.ip
while ip > 0:
thehash = thehash ^ (ip & 0x7fffffff)
2015-02-27 08:53:51 +00:00
ip = ip >> 32
thehash = thehash ^ self._prefixlen
return int(thehash)
class IP(IPint):
"""Class for handling IP addresses and networks."""
def net(self):
"""Return the base (first) address of a network as an IP object.
The same as IP[0].
>>> IP('10.0.0.0/8').net()
IP('10.0.0.0')
"""
return IP(IPint.net(self), ipversion=self._ipversion)
def broadcast(self):
"""Return the broadcast (last) address of a network as an IP object.
The same as IP[-1].
>>> IP('10.0.0.0/8').broadcast()
IP('10.255.255.255')
"""
return IP(IPint.broadcast(self))
def netmask(self):
"""Return netmask as an IP object.
>>> IP('10.0.0.0/8').netmask()
IP('255.0.0.0')
"""
return IP(IPint.netmask(self), ipversion=self._ipversion)
def _getIPv4Map(self):
if self._ipversion != 6:
return None
if (self.ip >> 32) != 0xffff:
return None
ipv4 = self.ip & MAX_IPV4_ADDRESS
if self._prefixlen != 128:
ipv4 = '%s/%s' % (ipv4, 32-(128-self._prefixlen))
return IP(ipv4, ipversion=4)
2015-02-27 08:53:51 +00:00
def reverseNames(self):
"""Return a list with values forming the reverse lookup.
>>> IP('213.221.113.87/32').reverseNames()
['87.113.221.213.in-addr.arpa.']
>>> IP('213.221.112.224/30').reverseNames()
['224.112.221.213.in-addr.arpa.', '225.112.221.213.in-addr.arpa.', '226.112.221.213.in-addr.arpa.', '227.112.221.213.in-addr.arpa.']
>>> IP('127.0.0.0/24').reverseNames()
['0.0.127.in-addr.arpa.']
>>> IP('127.0.0.0/23').reverseNames()
['0.0.127.in-addr.arpa.', '1.0.127.in-addr.arpa.']
>>> IP('127.0.0.0/16').reverseNames()
['0.127.in-addr.arpa.']
>>> IP('127.0.0.0/15').reverseNames()
['0.127.in-addr.arpa.', '1.127.in-addr.arpa.']
>>> IP('128.0.0.0/8').reverseNames()
['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 = []
# TODO: Refactor. Add support for IPint objects
if self.len() < 2**8:
2015-02-27 08:53:51 +00:00
for x in self:
ret.append(x.reverseName())
elif self.len() < 2**16:
for i in xrange(0, self.len(), 2**8):
2015-02-27 08:53:51 +00:00
ret.append(self[i].reverseName()[2:])
elif self.len() < 2**24:
for i in xrange(0, self.len(), 2**16):
2015-02-27 08:53:51 +00:00
ret.append(self[i].reverseName()[4:])
else:
for i in xrange(0, self.len(), 2**24):
2015-02-27 08:53:51 +00:00
ret.append(self[i].reverseName()[6:])
return ret
elif self._ipversion == 6:
ipv4 = self._getIPv4Map()
if ipv4 is not None:
return ipv4.reverseNames()
s = "%x" % self.ip
2015-02-27 08:53:51 +00:00
if self._prefixlen % 4 != 0:
raise NotImplementedError("can't create IPv6 reverse names at sub nibble level")
2015-02-27 08:53:51 +00:00
s = list(s)
s.reverse()
s = '.'.join(s)
first_nibble_index = int(32 - (self._prefixlen // 4)) * 2
2015-02-27 08:53:51 +00:00
return ["%s.ip6.arpa." % s[first_nibble_index:]]
else:
raise ValueError("only IPv4 and IPv6 supported")
2015-02-27 08:53:51 +00:00
def reverseName(self):
"""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 achieve that.
>>> print(IP('195.185.1.1').reverseName())
2015-02-27 08:53:51 +00:00
1.1.185.195.in-addr.arpa.
>>> print(IP('195.185.1.0/28').reverseName())
2015-02-27 08:53:51 +00:00
0-15.1.185.195.in-addr.arpa.
>>> IP('::1:2').reverseName()
'2.0.0.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.'
>>> IP('ff02::/64').reverseName()
'0.0.0.0.0.0.0.0.0.0.0.0.2.0.f.f.ip6.arpa.'
2015-02-27 08:53:51 +00:00
"""
if self._ipversion == 4:
s = self.strFullsize(0)
s = s.split('.')
2015-02-27 08:53:51 +00:00
s.reverse()
first_byte_index = int(4 - (self._prefixlen // 8))
2015-02-27 08:53:51 +00:00
if self._prefixlen % 8 != 0:
nibblepart = "%s-%s" % (s[3-(self._prefixlen // 8)], intToIp(self.ip + self.len() - 1, 4).split('.')[-1])
nibblepart += '.'
2015-02-27 08:53:51 +00:00
else:
nibblepart = ""
s = '.'.join(s[first_byte_index:])
2015-02-27 08:53:51 +00:00
return "%s%s.in-addr.arpa." % (nibblepart, s)
elif self._ipversion == 6:
ipv4 = self._getIPv4Map()
if ipv4 is not None:
return ipv4.reverseName()
s = '%032x' % self.ip
2015-02-27 08:53:51 +00:00
if self._prefixlen % 4 != 0:
nibblepart = "%s-%x" % (s[self._prefixlen:], self.ip + self.len() - 1)
nibblepart += '.'
2015-02-27 08:53:51 +00:00
else:
nibblepart = ""
s = list(s)
s.reverse()
s = '.'.join(s)
first_nibble_index = int(32 - (self._prefixlen // 4)) * 2
2015-02-27 08:53:51 +00:00
return "%s%s.ip6.arpa." % (nibblepart, s[first_nibble_index:])
else:
raise ValueError("only IPv4 and IPv6 supported")
2015-02-27 08:53:51 +00:00
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'))
2015-02-27 08:53:51 +00:00
127.0.0.0/8
"""
if '/' in str(netmask):
raise ValueError("invalid netmask (%s)" % netmask)
return IP('%s/%s' % (self, netmask), make_net=True)
2015-02-27 08:53:51 +00:00
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))
2015-02-27 08:53:51 +00:00
...
127.0.0.0
127.0.0.1
127.0.0.2
127.0.0.3
>>> print(str(ip[2]))
2015-02-27 08:53:51 +00:00
127.0.0.2
>>> print(str(ip[-1]))
2015-02-27 08:53:51 +00:00
127.0.0.3
"""
if isinstance(key, slice):
return [IP(IPint.__getitem__(self, x), ipversion=self._ipversion) for x in xrange(*key.indices(len(self)))]
return IP(IPint.__getitem__(self, key), ipversion=self._ipversion)
2015-02-27 08:53:51 +00:00
def __repr__(self):
"""Print a representation of the Object.
>>> IP('10.0.0.0/8')
IP('10.0.0.0/8')
"""
return("IP('%s')" % (self.strCompressed(1)))
2015-02-27 08:53:51 +00:00
def get_mac(self):
"""
Get the 802.3 MAC address from IPv6 RFC 2464 address, in lower case.
Return None if the address is an IPv4 or not a IPv6 RFC 2464 address.
>>> IP('fe80::f66d:04ff:fe47:2fae').get_mac()
'f4:6d:04:47:2f:ae'
"""
if self._ipversion != 6:
return None
if (self.ip & 0x20000ffff000000) != 0x20000fffe000000:
return None
return '%02x:%02x:%02x:%02x:%02x:%02x' % (
(((self.ip >> 56) & 0xff) & 0xfd),
(self.ip >> 48) & 0xff,
(self.ip >> 40) & 0xff,
(self.ip >> 16) & 0xff,
(self.ip >> 8) & 0xff,
self.ip & 0xff,
)
def v46map(self):
"""
Returns the IPv6 mapped address of an IPv4 address, or the corresponding
IPv4 address if the IPv6 address is in the appropriate range.
Raises a ValueError if the IPv6 address is not translatable. See RFC 4291.
>>> IP('192.168.1.1').v46map()
IP('::ffff:192.168.1.1')
>>> IP('::ffff:192.168.1.1').v46map()
IP('192.168.1.1')
"""
if self._ipversion == 4:
return IP(str(IPV6_MAP_MASK + self.ip) +
"/%s" % (self._prefixlen + 96))
2015-02-27 08:53:51 +00:00
else:
if self.ip & IPV6_TEST_MAP == IPV6_MAP_MASK:
return IP(str(self.ip - IPV6_MAP_MASK) +
"/%s" % (self._prefixlen - 96))
raise ValueError("%s cannot be converted to an IPv4 address."
% repr(self))
class IPSet(collections_abc.MutableSet):
def __init__(self, iterable=[]):
# Make sure it's iterable, otherwise wrap
if not isinstance(iterable, collections_abc.Iterable):
raise TypeError("'%s' object is not iterable" % type(iterable).__name__)
# Make sure we only accept IP objects
for prefix in iterable:
if not isinstance(prefix, IP):
raise ValueError('Only IP objects can be added to an IPSet')
# Store and optimize
self.prefixes = iterable[:]
self.optimize()
def __contains__(self, ip):
valid_masks = self.prefixtable.keys()
if isinstance(ip, IP):
#Don't dig through more-specific ranges
ip_mask = ip._prefixlen
valid_masks = [x for x in valid_masks if x <= ip_mask]
for mask in sorted(valid_masks):
i = bisect.bisect(self.prefixtable[mask], ip)
# Because of sorting order, a match can only occur in the prefix
# that comes before the result of the search.
if i and ip in self.prefixtable[mask][i - 1]:
return True
def __iter__(self):
for prefix in self.prefixes:
yield prefix
def __len__(self):
return self.len()
def __add__(self, other):
return IPSet(self.prefixes + other.prefixes)
def __sub__(self, other):
new = IPSet(self.prefixes)
for prefix in other:
new.discard(prefix)
return new
def __and__(self, other):
left = iter(self.prefixes)
right = iter(other.prefixes)
result = []
try:
l = next(left)
r = next(right)
while True:
# iterate over prefixes in order, keeping the smaller of the
# two if they overlap
if l in r:
result.append(l)
l = next(left)
continue
elif r in l:
result.append(r)
r = next(right)
continue
if l < r:
l = next(left)
else:
r = next(right)
except StopIteration:
return IPSet(result)
def __repr__(self):
return '%s([' % self.__class__.__name__ + ', '.join(map(repr, self.prefixes)) + '])'
def len(self):
return sum(prefix.len() for prefix in self.prefixes)
def add(self, value):
# Make sure it's iterable, otherwise wrap
if not isinstance(value, collections_abc.Iterable):
value = [value]
# Check type
for prefix in value:
if not isinstance(prefix, IP):
raise ValueError('Only IP objects can be added to an IPSet')
# Append and optimize
self.prefixes.extend(value)
self.optimize()
def discard(self, value):
# Make sure it's iterable, otherwise wrap
if not isinstance(value, collections_abc.Iterable):
value = [value]
# This is much faster than iterating over the addresses
if isinstance(value, IPSet):
value = value.prefixes
# Remove
for del_prefix in value:
if not isinstance(del_prefix, IP):
raise ValueError('Only IP objects can be removed from an IPSet')
# First check if this prefix contains anything in our list
found = False
d = 0
for i in range(len(self.prefixes)):
if self.prefixes[i - d] in del_prefix:
self.prefixes.pop(i - d)
d = d + 1
found = True
if found:
# If the prefix was bigger than an existing prefix, then it's
# certainly not a subset of one, so skip the rest
continue
# Maybe one of our prefixes contains this prefix
found = False
for i in range(len(self.prefixes)):
if del_prefix in self.prefixes[i]:
self.prefixes[i:i+1] = self.prefixes[i] - del_prefix
break
self.optimize()
def isdisjoint(self, other):
left = iter(self.prefixes)
right = iter(other.prefixes)
try:
l = next(left)
r = next(right)
while True:
if l in r or r in l:
return False
if l < r:
l = next(left)
else:
r = next(right)
except StopIteration:
return True
def optimize(self):
# The algorithm below *depends* on the sort order
self.prefixes.sort()
# First eliminate all values that are a subset of other values
addrlen = len(self.prefixes)
i = 0
while i < addrlen:
# Everything that might be inside this prefix follows
# directly behind it
j = i+1
while j < addrlen and self.prefixes[j] in self.prefixes[i]:
# Mark for deletion by overwriting with None
self.prefixes[j] = None
j += 1
# Continue where we left off
i = j
# Try to merge as many prefixes as possible
run_again = True
while run_again:
# Filter None values. This happens when a subset is eliminated
# above, or when two prefixes are merged below
self.prefixes = [a for a in self.prefixes if a is not None]
# We'll set run_again to True when we make changes that require
# re-evaluation of the whole list
run_again = False
# We can merge two prefixes that have the same version, same
# prefix length and differ only on the last bit of the prefix
addrlen = len(self.prefixes)
i = 0
while i < addrlen-1:
j = i + 1
try:
# The next line will throw an exception when merging
# is not possible
self.prefixes[i] += self.prefixes[j]
self.prefixes[j] = None
i = j + 1
run_again = True
except ValueError:
# Can't be merged, see if position j can be merged
i = j
# O(n) insertion now by prefix means faster searching on __contains__
# when lots of ranges with the same length exist
self.prefixtable = {}
for address in self.prefixes:
try:
self.prefixtable[address._prefixlen].append(address)
except KeyError:
self.prefixtable[address._prefixlen] = [address]
2015-02-27 08:53:51 +00:00
def _parseAddressIPv6(ipstr):
"""
Internal function used by parseAddress() to parse IPv6 address with ':'.
>>> print(_parseAddressIPv6('::'))
0
>>> print(_parseAddressIPv6('::1'))
1
>>> print(_parseAddressIPv6('0:0:0:0:0:0:0:1'))
1
>>> print(_parseAddressIPv6('0:0:0::0:0:1'))
1
>>> print(_parseAddressIPv6('0:0:0:0:0:0:0:0'))
0
>>> print(_parseAddressIPv6('0:0:0::0:0:0'))
0
>>> print(_parseAddressIPv6('FEDC:BA98:7654:3210:FEDC:BA98:7654:3210'))
338770000845734292534325025077361652240
>>> print(_parseAddressIPv6('1080:0000:0000:0000:0008:0800:200C:417A'))
21932261930451111902915077091070067066
>>> print(_parseAddressIPv6('1080:0:0:0:8:800:200C:417A'))
21932261930451111902915077091070067066
>>> print(_parseAddressIPv6('1080:0::8:800:200C:417A'))
21932261930451111902915077091070067066
>>> print(_parseAddressIPv6('1080::8:800:200C:417A'))
21932261930451111902915077091070067066
>>> print(_parseAddressIPv6('FF01:0:0:0:0:0:0:43'))
338958331222012082418099330867817087043
>>> print(_parseAddressIPv6('FF01:0:0::0:0:43'))
338958331222012082418099330867817087043
>>> print(_parseAddressIPv6('FF01::43'))
338958331222012082418099330867817087043
>>> print(_parseAddressIPv6('0:0:0:0:0:0:13.1.68.3'))
218186755
>>> print(_parseAddressIPv6('::13.1.68.3'))
218186755
>>> print(_parseAddressIPv6('0:0:0:0:0:FFFF:129.144.52.38'))
281472855454758
>>> print(_parseAddressIPv6('::FFFF:129.144.52.38'))
281472855454758
>>> print(_parseAddressIPv6('1080:0:0:0:8:800:200C:417A'))
21932261930451111902915077091070067066
>>> print(_parseAddressIPv6('1080::8:800:200C:417A'))
21932261930451111902915077091070067066
>>> print(_parseAddressIPv6('::1:2:3:4:5:6'))
1208962713947218704138246
>>> print(_parseAddressIPv6('1:2:3:4:5:6::'))
5192455318486707404433266432802816
2015-02-27 08:53:51 +00:00
"""
# Split string into a list, example:
# '1080:200C::417A' => ['1080', '200C', '417A'] and fill_pos=2
2015-02-27 08:53:51 +00:00
# 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(':')
2015-02-27 08:53:51 +00:00
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] == "::":
2015-02-27 08:53:51 +00:00
index += pos
else:
index += pos+1
2015-02-27 08:53:51 +00:00
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]:
2015-02-27 08:53:51 +00:00
# IPv6 ending with IPv4 like '::ffff:192.168.0.1'
if (fill_pos is not None) and not (fill_pos <= len(items)-1):
2015-02-27 08:53:51 +00:00
# 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)]
2015-02-27 08:53:51 +00:00
# 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:]
2015-02-27 08:53:51 +00:00
# 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 = 0
2015-02-27 08:53:51 +00:00
index = 0
for item in items:
try:
item = int(item, 16)
error = not(0 <= item <= 0xffff)
2015-02-27 08:53:51 +00:00
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
2019-09-09 13:53:52 +00:00
def parseAddress(ipstr, ipversion=0):
2015-02-27 08:53:51 +00:00
"""
Parse a string and return the corresponding IP address (as integer)
and a guess of the IP version.
Following address formats are recognized:
>>> def testParseAddress(address):
... ip, version = parseAddress(address)
... print(("%s (IPv%s)" % (ip, version)))
...
>>> testParseAddress('0x0123456789abcdef') # IPv4 if <= 0xffffffff else IPv6
81985529216486895 (IPv6)
>>> testParseAddress('123.123.123.123') # IPv4
2071690107 (IPv4)
>>> testParseAddress('123.123') # 0-padded IPv4
2071658496 (IPv4)
>>> testParseAddress('127')
2130706432 (IPv4)
>>> testParseAddress('255')
4278190080 (IPv4)
>>> testParseAddress('256')
256 (IPv4)
>>> testParseAddress('108000000000000000080800200C417A')
21932261930451111902915077091070067066 (IPv6)
>>> testParseAddress('0x108000000000000000080800200C417A')
21932261930451111902915077091070067066 (IPv6)
>>> testParseAddress('1080:0000:0000:0000:0008:0800:200C:417A')
21932261930451111902915077091070067066 (IPv6)
>>> testParseAddress('1080:0:0:0:8:800:200C:417A')
21932261930451111902915077091070067066 (IPv6)
>>> testParseAddress('1080:0::8:800:200C:417A')
21932261930451111902915077091070067066 (IPv6)
>>> testParseAddress('::1')
1 (IPv6)
>>> testParseAddress('::')
0 (IPv6)
>>> testParseAddress('0:0:0:0:0:FFFF:129.144.52.38')
281472855454758 (IPv6)
>>> testParseAddress('::13.1.68.3')
218186755 (IPv6)
>>> testParseAddress('::FFFF:129.144.52.38')
281472855454758 (IPv6)
2015-02-27 08:53:51 +00:00
"""
try:
hexval = int(ipstr, 16)
except ValueError:
hexval = None
try:
intval = int(ipstr, 10)
except ValueError:
intval = None
if ipstr.startswith('0x') and hexval is not None:
if hexval > MAX_IPV6_ADDRESS:
raise ValueError("IP Address can't be larger than %x: %x" % (MAX_IPV6_ADDRESS, hexval))
if hexval <= MAX_IPV4_ADDRESS:
return (hexval, 4)
2015-02-27 08:53:51 +00:00
else:
return (hexval, 6)
2015-02-27 08:53:51 +00:00
if ipstr.find(':') != -1:
2015-02-27 08:53:51 +00:00
return (_parseAddressIPv6(ipstr), 6)
elif len(ipstr) == 32 and hexval is not None:
2015-02-27 08:53:51 +00:00
# assume IPv6 in pure hexadecimal notation
return (hexval, 6)
2015-02-27 08:53:51 +00:00
elif ipstr.find('.') != -1 or (intval is not None and intval < 256 and ipversion != 6):
2015-02-27 08:53:51 +00:00
# assume IPv4 ('127' gets interpreted as '127.0.0.0')
bytes = ipstr.split('.')
2015-02-27 08:53:51 +00:00
if len(bytes) > 4:
raise ValueError("IPv4 Address with more than 4 bytes")
bytes += ['0'] * (4 - len(bytes))
bytes = [int(x) for x in bytes]
2015-02-27 08:53:51 +00:00
for x in bytes:
if x > 255 or x < 0:
raise ValueError("%r: single byte must be 0 <= byte < 256" % (ipstr))
2015-02-27 08:53:51 +00:00
return ((bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3], 4)
elif intval is not None:
2015-02-27 08:53:51 +00:00
# we try to interprete it as a decimal digit -
# this ony works for numbers > 255 ... others
# will be interpreted as IPv4 first byte
if intval > MAX_IPV6_ADDRESS:
raise ValueError("IP Address can't be larger than %x: %x" % (MAX_IPV6_ADDRESS, intval))
2019-09-09 13:53:52 +00:00
if intval <= MAX_IPV4_ADDRESS and ipversion != 6:
return (intval, 4)
2015-02-27 08:53:51 +00:00
else:
return (intval, 6)
raise ValueError("IP Address format was invalid: %s" % ipstr)
2015-02-27 08:53:51 +00:00
def intToIp(ip, version):
"""Transform an integer string into an IP address."""
# just to be sure and hoping for Python 2.2
ip = int(ip)
2015-02-27 08:53:51 +00:00
if ip < 0:
raise ValueError("IPs can't be negative: %d" % (ip))
2015-02-27 08:53:51 +00:00
ret = ''
2015-02-27 08:53:51 +00:00
if version == 4:
if ip > MAX_IPV4_ADDRESS:
raise ValueError("IPv4 Address can't be larger than %x: %x" % (MAX_IPV4_ADDRESS, ip))
for l in xrange(4):
ret = str(ip & 0xff) + '.' + ret
2015-02-27 08:53:51 +00:00
ip = ip >> 8
ret = ret[:-1]
elif version == 6:
if ip > MAX_IPV6_ADDRESS:
raise ValueError("IPv6 Address can't be larger than %x: %x" % (MAX_IPV6_ADDRESS, ip))
l = "%032x" % ip
for x in xrange(1, 33):
2015-02-27 08:53:51 +00:00
ret = l[-x] + ret
if x % 4 == 0:
ret = ':' + ret
2015-02-27 08:53:51 +00:00
ret = ret[1:]
else:
raise ValueError("only IPv4 and IPv6 supported")
2015-02-27 08:53:51 +00:00
return ret
def _ipVersionToLen(version):
"""Return number of bits in address for a certain IP version.
>>> _ipVersionToLen(4)
32
>>> _ipVersionToLen(6)
128
>>> _ipVersionToLen(5)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "IPy.py", line 1076, in _ipVersionToLen
raise ValueError("only IPv4 and IPv6 supported")
2015-02-27 08:53:51 +00:00
ValueError: only IPv4 and IPv6 supported
"""
if version == 4:
return 32
elif version == 6:
return 128
else:
raise ValueError("only IPv4 and IPv6 supported")
2015-02-27 08:53:51 +00:00
def _countFollowingZeros(l):
"""Return number of elements containing 0 at the beginning of the list."""
if len(l) == 0:
return 0
elif l[0] != 0:
return 0
else:
return 1 + _countFollowingZeros(l[1:])
_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'}
2015-02-27 08:53:51 +00:00
def _intToBin(val):
"""Return the binary representation of an integer as string."""
if val < 0:
raise ValueError("Only positive values allowed")
s = "%x" % val
ret = ''
for x in s:
2015-02-27 08:53:51 +00:00
ret += _BitTable[x]
# remove leading zeros
while ret[0] == '0' and len(ret) > 1:
2015-02-27 08:53:51 +00:00
ret = ret[1:]
return ret
def _count1Bits(num):
"""Find the highest bit set to 1 in an integer."""
ret = 0
while num > 0:
num = num >> 1
ret += 1
return ret
def _count0Bits(num):
"""Find the highest bit set to 0 in an integer."""
# this could be so easy if _count1Bits(~int(num)) would work as excepted
num = int(num)
2015-02-27 08:53:51 +00:00
if num < 0:
raise ValueError("Only positive Numbers please: %s" % (num))
2015-02-27 08:53:51 +00:00
ret = 0
while num > 0:
if num & 1 == 1:
break
num = num >> 1
ret += 1
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.
>>> _checkPrefix(0x7f000000, 24, 4)
2015-02-27 08:53:51 +00:00
1
>>> _checkPrefix(0x7f000001, 24, 4)
2015-02-27 08:53:51 +00:00
0
>>> repr(_checkPrefix(0x7f000001, -1, 4))
2015-02-27 08:53:51 +00:00
'None'
>>> repr(_checkPrefix(0x7f000001, 33, 4))
2015-02-27 08:53:51 +00:00
'None'
"""
# TODO: unify this v4/v6/invalid code in a function
bits = _ipVersionToLen(version)
if prefixlen < 0 or prefixlen > bits:
return None
if ip == 0:
zbits = bits + 1
else:
zbits = _count0Bits(ip)
if zbits < bits - prefixlen:
2015-02-27 08:53:51 +00:00
return 0
else:
return 1
def _checkNetmask(netmask, masklen):
"""Checks if a netmask is expressable as a prefixlen."""
num = int(netmask)
2015-02-27 08:53:51 +00:00
bits = masklen
# remove zero bits at the end
while (num & 1) == 0 and bits != 0:
num = num >> 1
bits -= 1
if bits == 0:
break
# now check if the rest consists only of ones
while bits > 0:
if (num & 1) == 0:
raise ValueError("Netmask 0x%x can't be expressed as an prefix." % netmask)
2015-02-27 08:53:51 +00:00
num = num >> 1
bits -= 1
def _checkNetaddrWorksWithPrefixlen(net, prefixlen, version):
"""Check if a base addess of a network is compatible with a prefixlen"""
try:
return (net & _prefixlenToNetmask(prefixlen, version) == net)
except ValueError:
return False
2015-02-27 08:53:51 +00:00
def _netmaskToPrefixlen(netmask):
"""Convert an Integer representing a netmask to a prefixlen.
E.g. 0xffffff00 (255.255.255.0) returns 24
"""
netlen = _count0Bits(netmask)
masklen = _count1Bits(netmask)
_checkNetmask(netmask, masklen)
return masklen - netlen
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
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66517
"""
if prefixlen == 0:
return 0
elif prefixlen < 0:
raise ValueError("Prefixlen must be > 0")
return ((2<<prefixlen-1)-1) << (_ipVersionToLen(version) - prefixlen)
def _remove_subprefix(prefix, subprefix):
if prefix in subprefix:
# Nothing left
return IPSet()
if subprefix not in prefix:
# That prefix isn't even in here
return IPSet([IP(prefix)])
# Start cutting in half, recursively
prefixes = [
IP('%s/%d' % (prefix[0], prefix._prefixlen + 1)),
IP('%s/%d' % (prefix[int(prefix.len() / 2)], prefix._prefixlen + 1)),
]
if subprefix in prefixes[0]:
return _remove_subprefix(prefixes[0], subprefix) + IPSet([prefixes[1]])
else:
return IPSet([prefixes[0]]) + _remove_subprefix(prefixes[1], subprefix)
2015-02-27 08:53:51 +00:00
if __name__ == "__main__":
import doctest
failure, nbtest = doctest.testmod()
if failure:
import sys
sys.exit(1)