Skip to content
Snippets Groups Projects
Commit 67ee2f3b authored by Guillaume Valadon's avatar Guillaume Valadon Committed by GitHub
Browse files

Merge pull request #495 from p-l-/fix-inet6addr

inet_{pton,ntop}: reorganize, cleanup and improve tests
parents d2f96009 01110e50
No related branches found
No related tags found
No related merge requests found
...@@ -13,96 +13,116 @@ without IPv6 support, on Windows for instance. ...@@ -13,96 +13,116 @@ without IPv6 support, on Windows for instance.
import socket import socket
import re import re
_IP4_FORMAT = re.compile("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
_IP6_ZEROS = re.compile('(?::|^)(0(?::0)+)(?::|$)') _IP6_ZEROS = re.compile('(?::|^)(0(?::0)+)(?::|$)')
_INET6_PTON_EXC = socket.error("illegal IP address string passed to inet_pton")
def inet_pton(af, addr): def _inet6_pton(addr):
"""Convert an IP address from text representation into binary form""" """Convert an IPv6 address from text representation into binary form,
if af == socket.AF_INET: used when socket.inet_pton is not available.
return socket.inet_aton(addr)
elif af == socket.AF_INET6: """
# Use inet_pton if available joker_pos = None
try: result = ""
return socket.inet_pton(af, addr) if addr == '::':
except AttributeError: return '\x00' * 16
pass if addr.startswith('::'):
addr = addr[1:]
# IPv6: The use of "::" indicates one or more groups of 16 bits of zeros. if addr.endswith('::'):
# We deal with this form of wildcard using a special marker. addr = addr[:-1]
JOKER = "*" parts = addr.split(":")
while "::" in addr: nparts = len(parts)
addr = addr.replace("::", ":" + JOKER + ":") for i, part in enumerate(parts):
joker_pos = None if not part:
# "::" indicates one or more groups of 2 null bytes
# The last part of an IPv6 address can be an IPv4 address if joker_pos is None:
ipv4_bin = None joker_pos = len(result)
ipv4_addr = None else:
if "." in addr: # Wildcard is only allowed once
ipv4_addr = addr.split(":")[-1] raise _INET6_PTON_EXC
if _IP4_FORMAT.match(ipv4_addr) is None: elif i + 1 == nparts and '.' in part:
raise Exception("Illegal syntax for IP address") # The last part of an IPv6 address can be an IPv4 address
if part.count('.') != 3:
# we have to do this since socket.inet_aton('1.2') ==
# '\x01\x00\x00\x02'
raise _INET6_PTON_EXC
try: try:
ipv4_bin = socket.inet_aton(ipv4_addr) result += socket.inet_aton(part)
except socket.error: except socket.error:
raise Exception("Illegal syntax for IP address") raise _INET6_PTON_EXC
else:
result = "" # Each part must be 16bit. Add missing zeroes before decoding.
parts = addr.split(":") try:
for part in parts: result += part.rjust(4, "0").decode("hex")
if part == JOKER: except TypeError:
# Wildcard is only allowed once raise _INET6_PTON_EXC
if joker_pos is None: # If there's a wildcard, fill up with zeros to reach 128bit (16 bytes)
joker_pos = len(result) if joker_pos is not None:
else: if len(result) == 16:
raise Exception("Illegal syntax for IP address") raise _INET6_PTON_EXC
elif part == ipv4_addr: result = (result[:joker_pos] + "\x00" * (16 - len(result))
result += ipv4_bin + result[joker_pos:])
else: if len(result) != 16:
# Each part must be 16bit. Add missing zeroes before decoding. raise _INET6_PTON_EXC
try: return result
result += part.rjust(4, "0").decode("hex")
except TypeError:
raise Exception("Illegal syntax for IP address")
# If there's a wildcard, fill up with zeros to reach 128bit (16 bytes)
if JOKER in addr:
result = (result[:joker_pos] + "\x00" * (16 - len(result))
+ result[joker_pos:])
if len(result) != 16:
raise Exception("Illegal syntax for IP address")
return result
else:
raise Exception("Address family not supported")
def inet_ntop(af, addr): _INET_PTON = {
"""Convert an IP address from binary form into text representation""" socket.AF_INET: socket.inet_aton,
if af == socket.AF_INET: socket.AF_INET6: _inet6_pton,
return socket.inet_ntoa(addr) }
elif af == socket.AF_INET6:
# Use inet_ntop if available
def inet_pton(af, addr):
"""Convert an IP address from text representation into binary form."""
# Use inet_pton if available
try:
return socket.inet_pton(af, addr)
except AttributeError:
try: try:
return socket.inet_ntop(af, addr) return _INET_PTON[af](addr)
except AttributeError: except KeyError:
return _ipv6_bin_to_str(addr) raise socket.error("Address family not supported by protocol")
else:
raise Exception("Address family not supported yet")
def _inet6_ntop(addr):
"""Convert an IPv6 address from binary form into text representation,
used when socket.inet_pton is not available.
def _ipv6_bin_to_str(addr): """
# IPv6 addresses have 128bits (16 bytes) # IPv6 addresses have 128bits (16 bytes)
if len(addr) != 16: if len(addr) != 16:
raise ValueError("invalid length of packed IP address string") raise ValueError("invalid length of packed IP address string")
# Decode to hex representation # Decode to hex representation
address = ":".join(addr[idx:idx + 2].encode('hex').lstrip('0') or '0' for idx in xrange(0, 16, 2)) address = ":".join(addr[idx:idx + 2].encode('hex').lstrip('0') or '0'
for idx in xrange(0, 16, 2))
try: try:
# Get the longest set of zero blocks # Get the longest set of zero blocks. We need to take a look
# Actually we need to take a look at group 1 regarding the length as 0:0:1:0:0:2:3:4 would have two matches: # at group 1 regarding the length, as 0:0:1:0:0:2:3:4 would
# 0:0: and :0:0: where the latter is longer, though the first one should be taken. Group 1 is in both cases 0:0. # have two matches: 0:0: and :0:0: where the latter is longer,
match = max(_IP6_ZEROS.finditer(address), key=lambda m: m.end(1) - m.start(1)) # though the first one should be taken. Group 1 is in both
# cases 0:0.
match = max(_IP6_ZEROS.finditer(address),
key=lambda m: m.end(1) - m.start(1))
return '{}::{}'.format(address[:match.start()], address[match.end():]) return '{}::{}'.format(address[:match.start()], address[match.end():])
except ValueError: except ValueError:
return address return address
_INET_NTOP = {
socket.AF_INET: socket.inet_ntoa,
socket.AF_INET6: _inet6_ntop,
}
def inet_ntop(af, addr):
"""Convert an IP address from binary form into text representation."""
# Use inet_ntop if available
try:
return socket.inet_ntop(af, addr)
except AttributeError:
try:
return _INET_NTOP[af](addr)
except KeyError:
raise ValueError("unknown address family %d" % af)
...@@ -7253,78 +7253,55 @@ assert(re.match(r'^.*bit 2.*$', x) is not None) ...@@ -7253,78 +7253,55 @@ assert(re.match(r'^.*bit 2.*$', x) is not None)
############ ############
+ Test correct conversion from binary to string of IPv6 addresses + Test correct conversion from binary to string of IPv6 addresses
= IPv6 bin to string conversion - all zero bytes = IPv6 bin to string conversion
from scapy.pton_ntop import _ipv6_bin_to_str, inet_ntop from scapy.pton_ntop import _inet6_ntop, inet_ntop
import socket import socket
address=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # All zero for binfrm, address in [
compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address) ('\x00' * 16, '::'),
assert(compressed1 == compressed2 == '::') ('\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88',
'1111:2222:3333:4444:5555:6666:7777:8888'),
= IPv6 bin to string conversion - non-compressable ('\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x00\x00\x00\x00\x00\x00',
from scapy.pton_ntop import _ipv6_bin_to_str, inet_ntop '1111:2222:3333:4444:5555::'),
import socket ('\x00\x00\x00\x00\x00\x00\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88',
address=b'\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88' # Not compressable '::4444:5555:6666:7777:8888'),
compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address) ('\x00\x00\x00\x00\x33\x33\x44\x44\x00\x00\x00\x00\x00\x00\x88\x88',
assert(compressed1 == compressed2 == '1111:2222:3333:4444:5555:6666:7777:8888') '0:0:3333:4444::8888'),
('\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
= IPv6 bin to string conversion - Zero-block right '1::'),
from scapy.pton_ntop import _ipv6_bin_to_str, inet_ntop ('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01',
import socket '::1'),
address=b'\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x00\x00\x00\x00\x00\x00' # Zeroblock right ('\x11\x11\x00\x00\x00\x00\x44\x44\x00\x00\x00\x00\x77\x77\x88\x88',
compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address) '1111::4444:0:0:7777:8888'),
assert(compressed1 == compressed2 == '1111:2222:3333:4444:5555::') ('\x10\x00\x02\x00\x00\x30\x00\x04\x00\x05\x00\x60\x07\x00\x80\x00',
'1000:200:30:4:5:60:700:8000'),
= IPv6 bin to string conversion - Zero-block left ]:
from scapy.pton_ntop import _ipv6_bin_to_str, inet_ntop addr1 = inet_ntop(socket.AF_INET6, binfrm)
import socket addr2 = _inet6_ntop(binfrm)
address=b'\x00\x00\x00\x00\x00\x00\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88' # Zeroblock left assert address == addr1 == addr2
compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address)
assert(compressed1 == compressed2 == '::4444:5555:6666:7777:8888')
= IPv6 bin to string conversion - Two zero-block with different length
from scapy.pton_ntop import _ipv6_bin_to_str, inet_ntop
import socket
address=b'\x00\x00\x00\x00\x33\x33\x44\x44\x00\x00\x00\x00\x00\x00\x88\x88' # Short and long zero block
compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address)
assert(compressed1 == compressed2 == '0:0:3333:4444::8888')
= IPv6 bin to string conversion - Address 1::
from scapy.pton_ntop import _ipv6_bin_to_str, inet_ntop
import socket
address=b'\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # only 1 on the left
compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address)
assert(compressed1 == compressed2 == '1::')
= IPv6 bin to string conversion - Address ::1
from scapy.pton_ntop import _ipv6_bin_to_str, inet_ntop
import socket
address=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' # loopback
compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address)
assert(compressed1 == compressed2 == '::1')
= IPv6 bin to string conversion - Zero-block of length 1 = IPv6 bin to string conversion - Zero-block of length 1
from scapy.pton_ntop import _ipv6_bin_to_str, inet_ntop binfrm = '\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x00\x00\x88\x88'
import socket addr1, addr2 = inet_ntop(socket.AF_INET6, binfrm), _inet6_ntop(binfrm)
address=b'\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x00\x00\x88\x88' # only one zero block # On Mac OS socket.inet_ntop is not fully compliant with RFC 5952 and
compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address) # shortens the single zero block to '::'. This is a valid IPv6 address
assert(compressed1 == '1111:2222:3333:4444:5555:6666:0:8888') # representation anyway.
# On Mac OS socket.inet_ntop is not fully compliant with RFC 5952 and shortens the single zero block to '::'. Still assert(addr1 in ['1111:2222:3333:4444:5555:6666:0:8888',
# this is a valid IPv6 address representation. '1111:2222:3333:4444:5555:6666::8888'])
assert(compressed2 in ('1111:2222:3333:4444:5555:6666:0:8888', '1111:2222:3333:4444:5555:6666::8888')) assert(addr2 == '1111:2222:3333:4444:5555:6666:0:8888')
= IPv6 bin to string conversion - Two zero-blocks with equal length = IPv6 bin to string conversion - Illegal sizes
from scapy.pton_ntop import _ipv6_bin_to_str, inet_ntop for binfrm in ["\x00" * 15, "\x00" * 17]:
import socket rc = False
address=b'\x11\x11\x00\x00\x00\x00\x44\x44\x00\x00\x00\x00\x77\x77\x88\x88' # two zero blocks of equal length try:
compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address) inet_ntop(socket.AF_INET6, binfrm)
assert(compressed1 == compressed2 == '1111::4444:0:0:7777:8888') except Exception as exc1:
rc = True
= IPv6 bin to string conversion - Leading zero suppression assert rc
from scapy.pton_ntop import _ipv6_bin_to_str, inet_ntop try:
import socket _inet6_ntop(binfrm)
address=b'\x10\x00\x02\x00\x00\x30\x00\x04\x00\x05\x00\x60\x07\x00\x80\x00' # Leading zero suppression except Exception as exc2:
compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address) rc = isinstance(exc2, type(exc1))
assert(compressed1 == compressed2 == '1000:200:30:4:5:60:700:8000') assert rc
############ ############
...@@ -7466,31 +7443,46 @@ in6_getscope("::1") == IPV6_ADDR_LOOPBACK ...@@ -7466,31 +7443,46 @@ in6_getscope("::1") == IPV6_ADDR_LOOPBACK
= inet_pton() = inet_pton()
from scapy.pton_ntop import _inet6_pton, inet_pton
import socket import socket
ip6_bad_addrs = ["fe80::2e67:ef2d:7eca::ed8a", ip6_bad_addrs = ["fe80::2e67:ef2d:7eca::ed8a",
"fe80:1234:abcd::192.168.40.12:abcd", "fe80:1234:abcd::192.168.40.12:abcd",
"fe80:1234:abcd::192.168.40", "fe80:1234:abcd::192.168.40",
"fe80:1234:abcd::192.168.400.12"] "fe80:1234:abcd::192.168.400.12",
"1234:5678:9abc:def0:1234:5678:9abc:def0:",
"1234:5678:9abc:def0:1234:5678:9abc:def0:1234"]
for ip6 in ip6_bad_addrs: for ip6 in ip6_bad_addrs:
rc = False rc = False
try: try:
inet_pton(socket.AF_INET6, ip6) res1 = inet_pton(socket.AF_INET6, ip6)
except Exception, e: except Exception as exc1:
rc = True rc = True
assert rc assert rc
rc = False
ip6_good_addrs = ["fe80:1234:abcd::192.168.40.12",
"fe80:1234:abcd::fe06",
"fe80::2e67:ef2d:7ece:ed8a"]
for ip6 in ip6_good_addrs:
rc = True
try: try:
inet_pton(socket.AF_INET6, ip6) res2 = _inet6_pton(ip6)
except Exception, e: except Exception as exc2:
rc = False rc = isinstance(exc2, type(exc1))
assert rc assert rc
ip6_good_addrs = [("fe80:1234:abcd::192.168.40.12",
'\xfe\x80\x124\xab\xcd\x00\x00\x00\x00\x00\x00\xc0\xa8(\x0c'),
("fe80:1234:abcd::fe06",
'\xfe\x80\x124\xab\xcd\x00\x00\x00\x00\x00\x00\x00\x00\xfe\x06'),
("fe80::2e67:ef2d:7ece:ed8a",
'\xfe\x80\x00\x00\x00\x00\x00\x00.g\xef-~\xce\xed\x8a'),
("::ffff",
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff'),
("ffff::",
'\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'),
('::', '\x00' * 16)]
for ip6, res in ip6_good_addrs:
res1 = inet_pton(socket.AF_INET6, ip6)
res2 = _inet6_pton(ip6)
assert res == res1 == res2
############ ############
############ ############
+ Test Route class + Test Route class
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment