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
# Definition of the Ranges for IPv4 IPs
# this should include www.iana.org/assignments/ipv4-address-space
# and www.iana.org/assignments/multicast-addresses
'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
# 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/
'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
if sys.version_info >= (3,):
STR_TYPES = (str, unicode)
"""Handling of IP addresses returning integers.
Use class IP instead because some features are not implemented for
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'))
>>> print(IP('127.0.0.0/255.0.0.0'))
>>> print(IP('127.0.0.0-127.255.255.255'))
>>> print(IP('127.0.0.1/255.0.0.0', make_net=True))
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
# handling of non string values in constructor
if isinstance(data, INT_TYPES):
if self.ip <= MAX_IPV4_ADDRESS:
if self.ip > MAX_IPV4_ADDRESS:
raise ValueError("IPv4 Address can't be larger than %x: %x" % (MAX_IPV4_ADDRESS, self.ip))
if self.ip > MAX_IPV6_ADDRESS:
raise ValueError("IPv6 Address can't be larger than %x: %x" % (MAX_IPV6_ADDRESS, self.ip))
raise ValueError("only IPv4 and IPv6 supported")
self._ipversion = ipversion
self._prefixlen = prefixlen
# handle IP instance as an parameter
elif isinstance(data, IPint):
self._ipversion = data._ipversion
self._prefixlen = data._prefixlen
elif isinstance(data, STR_TYPES):
# splitting of a string into IP and prefixlen et. al.
# a.b.c.0-a.b.c.255 specification ?
(self.ip, parsedVersion) = parseAddress(ip)
raise ValueError("first-last notation only allowed for IPv4")
(last, lastversion) = parseAddress(last)
raise ValueError("last address should be IPv4, too")
raise ValueError("last address should be larger than first")
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)
# if no prefix is given use defaults
raise ValueError("only one '/' allowed in IP Address")
if prefixlen.find('.') != -1:
# check if the user might have used a netmask like
(netmask, vers) = parseAddress(prefixlen)
raise ValueError("netmask must be IPv4")
prefixlen = _netmaskToPrefixlen(netmask)
raise ValueError("only one '-' allowed in IP Address")
raise ValueError("can't parse")
(self.ip, parsedVersion) = parseAddress(ip, ipversion)
ipversion = parsedVersion
bits = _ipVersionToLen(ipversion)
prefixlen = bits - netbits
self._ipversion = ipversion
self._prefixlen = int(prefixlen)
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))
raise TypeError("Unsupported data type: %s" % type(data))
"""Return the first / base / network addess as an (long) integer.
>>> "%X" % IP('10.0.0.0/8').int()
"""Return the IP version of this Object.
>>> IP('10.0.0.0/8').version()
"""Returns Network Prefixlen.
>>> IP('10.0.0.0/8').prefixlen()
Return the base (first) address of a network as an (long) integer.
Return the broadcast (last) address of a network as an (long) integer.
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):
if self.NoPrefixForSingleIp:
want = self.WantPrefixLen
# this should work with IP and IPint
if not isinstance(netmask, INT_TYPES):
return "/%s" % (intToIp(netmask, self._ipversion))
return "-%s" % (intToIp(self.ip + self.len() - 1, self._ipversion))
return "/%d" % (self._prefixlen)
# 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
def strBin(self, wantprefixlen = None):
"""Return a string representation as a binary value.
>>> print(IP('127.0.0.1').strBin())
01111111000000000000000000000001
>>> print(IP('2001:0658:022a:cafe:0200::1').strBin())
00100000000000010000011001011000000000100010101011001010111111100000001000000000000000000000000000000000000000000000000000000001
bits = _ipVersionToLen(self._ipversion)
if self.WantPrefixLen == None and wantprefixlen == None:
return '0' * (bits - len(ret)) + ret + self._printPrefix(wantprefixlen)
def strCompressed(self, wantprefixlen = None):
"""Return a string representation in compressed format using '::' Notation.
>>> IP('127.0.0.1').strCompressed()
>>> 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:
return self.strFullsize(wantprefixlen)
if self.ip >> 32 == 0xffff:
ipv4 = intToIp(self.ip & MAX_IPV4_ADDRESS, 4)
text = "::ffff:" + ipv4 + self._printPrefix(wantprefixlen)
# 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 corresponding element of hextets
for i in xrange(len(hextets)):
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, '')
if compressionpos + 1 >= len(hextets):
return ':'.join(hextets) + self._printPrefix(wantprefixlen)
return self.strNormal(0) + self._printPrefix(wantprefixlen)
def strNormal(self, wantprefixlen = None):
"""Return a string representation in the usual format.
>>> print(IP('127.0.0.1').strNormal())
>>> print(IP('2001:0658:022a:cafe:0200::1').strNormal())
2001:658:22a:cafe:200:0:0:1
if self.WantPrefixLen == None and wantprefixlen == None:
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(':')]])
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.
>>> print(IP('127.0.0.1').strFullsize())
>>> print(IP('2001:0658:022a:cafe:0200::1').strFullsize())
2001:0658:022a:cafe:0200:0000:0000:0001
if self.WantPrefixLen == None and wantprefixlen == None:
return intToIp(self.ip, self._ipversion) + self._printPrefix(wantprefixlen)
def strHex(self, wantprefixlen = None):
"""Return a string representation in hex format in lower case.
>>> print(IP('127.0.0.1').strHex())
>>> print(IP('2001:0658:022a:cafe:0200::1').strHex())
0x20010658022acafe0200000000000001
if self.WantPrefixLen == None and wantprefixlen == None:
return x + self._printPrefix(wantprefixlen)
def strDec(self, wantprefixlen = None):
"""Return a string representation in decimal format.
>>> print(IP('127.0.0.1').strDec())
>>> print(IP('2001:0658:022a:cafe:0200::1').strDec())
42540616829182469433547762482097946625
if self.WantPrefixLen == None and wantprefixlen == None:
return x + self._printPrefix(wantprefixlen)
"""Return a description of the IP type ('PRIVATE', 'RESERVED', etc).
>>> print(IP('127.0.0.1').iptype())
>>> print(IP('192.168.1.1').iptype())
>>> print(IP('195.185.1.2').iptype())
>>> print(IP('::1').iptype())
>>> print(IP('2001:0658:022a:cafe:0200::1').iptype())
The type information for IPv6 is out of sync with reality.
# this could be greatly improved
elif self._ipversion == 6:
raise ValueError("only IPv4 and IPv6 supported")