diff --git a/scapy/pton_ntop.py b/scapy/pton_ntop.py index 25f7ef98eff..30512923eb4 100644 --- a/scapy/pton_ntop.py +++ b/scapy/pton_ntop.py @@ -10,7 +10,10 @@ without IPv6 support, on Windows for instance. """ -import socket,struct +import socket +import re + +_IP6_ZEROS = re.compile('(?::|^)(0(?::0)+)(?::|$)') def inet_pton(af, addr): """Convert an IP address from text representation into binary form""" @@ -75,27 +78,24 @@ def inet_ntop(af, addr): try: return socket.inet_ntop(af, addr) except AttributeError: - pass - - # IPv6 addresses have 128bits (16 bytes) - if len(addr) != 16: - raise Exception("Illegal syntax for IP address") - parts = [] - for left in [0, 2, 4, 6, 8, 10, 12, 14]: - try: - value = struct.unpack("!H", addr[left:left+2])[0] - hexstr = hex(value)[2:] - except TypeError: - raise Exception("Illegal syntax for IP address") - parts.append(hexstr.lstrip("0").lower()) - result = ":".join(parts) - while ":::" in result: - result = result.replace(":::", "::") - # Leaving out leading and trailing zeros is only allowed with :: - if result.endswith(":") and not result.endswith("::"): - result = result + "0" - if result.startswith(":") and not result.startswith("::"): - result = "0" + result - return result + return _ipv6_bin_to_str(addr) else: - raise Exception("Address family not supported yet") + raise Exception("Address family not supported yet") + + +def _ipv6_bin_to_str(addr): + # IPv6 addresses have 128bits (16 bytes) + if len(addr) != 16: + raise ValueError("invalid length of packed IP address string") + + # Decode to hex representation + address = ":".join(addr[idx:idx + 2].encode('hex').lstrip('0') or '0' for idx in xrange(0, 16, 2)) + + try: + # Get the longest set of zero blocks + # 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: + # 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. + match = max(_IP6_ZEROS.finditer(address), key=lambda m: m.end(1) - m.start(1)) + return '{}::{}'.format(address[:match.start()], address[match.end():]) + except ValueError: + return address diff --git a/test/regression.uts b/test/regression.uts index 41c640a052f..7962638b730 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -6897,3 +6897,79 @@ x = f.i2repr(mp, {'*', '+', 'bit 2'}) assert(re.match(r'^.*Star \(\*\).*$', x) is not None) assert(re.match(r'^.*Plus \(\+\).*$', x) is not None) assert(re.match(r'^.*bit 2.*$', x) is not None) + +########################################################################################################### ++ Test correct conversion from binary to string of IPv6 addresses + += IPv6 bin to string conversion - all zero bytes +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\x00' # All zero +compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address) +assert(compressed1 == compressed2 == '::') + += IPv6 bin to string conversion - non-compressable +from scapy.pton_ntop import _ipv6_bin_to_str, inet_ntop +import socket +address=b'\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88' # Not compressable +compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address) +assert(compressed1 == compressed2 == '1111:2222:3333:4444:5555:6666:7777:8888') + += IPv6 bin to string conversion - Zero-block right +from scapy.pton_ntop import _ipv6_bin_to_str, inet_ntop +import socket +address=b'\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x00\x00\x00\x00\x00\x00' # Zeroblock right +compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address) +assert(compressed1 == compressed2 == '1111:2222:3333:4444:5555::') + += IPv6 bin to string conversion - Zero-block left +from scapy.pton_ntop import _ipv6_bin_to_str, inet_ntop +import socket +address=b'\x00\x00\x00\x00\x00\x00\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88' # Zeroblock left +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 +from scapy.pton_ntop import _ipv6_bin_to_str, inet_ntop +import socket +address=b'\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x00\x00\x88\x88' # only one zero block +compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address) +assert(compressed1 == '1111:2222:3333:4444:5555:6666:0:8888') +# On Mac OS socket.inet_ntop is not fully compliant with RFC 5952 and shortens the single zero block to '::'. Still +# this is a valid IPv6 address representation. +assert(compressed2 in ('1111:2222:3333:4444:5555:6666:0:8888', '1111:2222:3333:4444:5555:6666::8888')) + += IPv6 bin to string conversion - Two zero-blocks with equal length +from scapy.pton_ntop import _ipv6_bin_to_str, inet_ntop +import socket +address=b'\x11\x11\x00\x00\x00\x00\x44\x44\x00\x00\x00\x00\x77\x77\x88\x88' # two zero blocks of equal length +compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address) +assert(compressed1 == compressed2 == '1111::4444:0:0:7777:8888') + += IPv6 bin to string conversion - Leading zero suppression +from scapy.pton_ntop import _ipv6_bin_to_str, inet_ntop +import socket +address=b'\x10\x00\x02\x00\x00\x30\x00\x04\x00\x05\x00\x60\x07\x00\x80\x00' # Leading zero suppression +compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address) +assert(compressed1 == compressed2 == '1000:200:30:4:5:60:700:8000')