From f25f641513540845470ce34c489bea1cd8fecb93 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Sat, 31 Dec 2016 14:46:52 +0100 Subject: [PATCH 1/4] Fix issue #359 Handle ipv6 addresses according to RFC 5952 Add regression tests --- scapy/pton_ntop.py | 70 +++++++++++++++++++++++++++++++-------------- test/regression.uts | 63 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 21 deletions(-) diff --git a/scapy/pton_ntop.py b/scapy/pton_ntop.py index 25f7ef98eff..64a5af41592 100644 --- a/scapy/pton_ntop.py +++ b/scapy/pton_ntop.py @@ -10,7 +10,7 @@ without IPv6 support, on Windows for instance. """ -import socket,struct +import socket,struct,re def inet_pton(af, addr): """Convert an IP address from text representation into binary form""" @@ -77,25 +77,53 @@ def inet_ntop(af, addr): except AttributeError: pass - # IPv6 addresses have 128bits (16 bytes) - if len(addr) != 16: + return _ipv6_bin_to_str(addr) + else: + raise Exception("Address family not supported yet") + + +def _ipv6_bin_to_str(addr): + # 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 = [] - 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 + parts.append(hexstr.lower()) + + address = ":".join(parts) + + # Find all consecutive zero blocks + matches = re.findall('(?::|^)(0(?::0)+)(?::|$)', address) + if matches: + # If multiple consecutive blocks have the same length, take the leftmost + match = max(matches) + if address.startswith(match): + leftidx = 0 + else: + leftidx = address.find(':' + match) + 1 + left = address[:leftidx] + rightidx = leftidx + len(match) + + # Adrress is like abcd:ef01:: + if len(address) == rightidx: + compact_address = left + ":" + + # Adrress is like ::abcd:ef01 + elif leftidx == 0: + compact_address = ":" + address[rightidx:] + + # Adrress is like abcd::ef01 + else: + compact_address = left + address[rightidx:] + + # Special case: address full of zeros + if compact_address == ":": + compact_address = "::" else: - raise Exception("Address family not supported yet") + compact_address = address + return compact_address diff --git a/test/regression.uts b/test/regression.uts index 41c640a052f..a8cee7f9ee1 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -6897,3 +6897,66 @@ 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 +address=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # All zero +compressed = _ipv6_bin_to_str(address) +assert(compressed == '::') + += IPv6 bin to string conversion - non-compressable +from scapy.pton_ntop import _ipv6_bin_to_str +address=b'\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88' # Not compressable +compressed = _ipv6_bin_to_str(address) +assert(compressed == '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 +address=b'\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x00\x00\x00\x00\x00\x00' # Zeroblock right +compressed = _ipv6_bin_to_str(address) +assert(compressed == '1111:2222:3333:4444:5555::') + += IPv6 bin to string conversion - Zero-block left +from scapy.pton_ntop import _ipv6_bin_to_str +address=b'\x00\x00\x00\x00\x00\x00\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88' # Zeroblock left +compressed = _ipv6_bin_to_str(address) +assert(compressed == '::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 +address=b'\x00\x00\x00\x00\x33\x33\x44\x44\x00\x00\x00\x00\x00\x00\x88\x88' # Short and long zero block +compressed = _ipv6_bin_to_str(address) +assert(compressed == '0:0:3333:4444::8888') + += IPv6 bin to string conversion - Address 1:: +from scapy.pton_ntop import _ipv6_bin_to_str +address=b'\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' # only 1 on the left +compressed = _ipv6_bin_to_str(address) +assert(compressed == '1::') + += IPv6 bin to string conversion - Address ::1 +from scapy.pton_ntop import _ipv6_bin_to_str +address=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' # loopback +compressed = _ipv6_bin_to_str(address) +assert(compressed == '::1') + += IPv6 bin to string conversion - Zero-block of length 1 +from scapy.pton_ntop import _ipv6_bin_to_str +address=b'\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x00\x00\x88\x88' # only one zero block +compressed = _ipv6_bin_to_str(address) +assert(compressed == '1111:2222:3333:4444:5555:6666:0:8888') + += IPv6 bin to string conversion - Two zero-blocks with equal length +from scapy.pton_ntop import _ipv6_bin_to_str +address=b'\x11\x11\x00\x00\x00\x00\x44\x44\x00\x00\x00\x00\x77\x77\x88\x88' # two zero blocks of equal length +compressed = _ipv6_bin_to_str(address) +assert(compressed == '1111::4444:0:0:7777:8888') + += IPv6 bin to string conversion - Leading zero suppression +from scapy.pton_ntop import _ipv6_bin_to_str +address=b'\x10\x00\x02\x00\x00\x30\x00\x04\x00\x05\x00\x60\x07\x00\x80\x00' # Leading zero suppression +compressed = _ipv6_bin_to_str(address) +assert(compressed == '1000:200:30:4:5:60:700:8000') \ No newline at end of file From d2b43f66aea5f0ead63f2047d23a89c4d8a99c9f Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 3 Jan 2017 18:54:22 +0100 Subject: [PATCH 2/4] Code optimization Better message for exception --- scapy/pton_ntop.py | 61 ++++++++++++---------------------------------- 1 file changed, 16 insertions(+), 45 deletions(-) diff --git a/scapy/pton_ntop.py b/scapy/pton_ntop.py index 64a5af41592..5506a89e23e 100644 --- a/scapy/pton_ntop.py +++ b/scapy/pton_ntop.py @@ -12,6 +12,8 @@ import socket,struct,re +_IP6_ZEROS = re.compile('(?::|^)(0(?::0)+)(?::|$)') + def inet_pton(af, addr): """Convert an IP address from text representation into binary form""" if af == socket.AF_INET: @@ -75,9 +77,7 @@ def inet_ntop(af, addr): try: return socket.inet_ntop(af, addr) except AttributeError: - pass - - return _ipv6_bin_to_str(addr) + return _ipv6_bin_to_str(addr) else: raise Exception("Address family not supported yet") @@ -85,45 +85,16 @@ def inet_ntop(af, addr): def _ipv6_bin_to_str(addr): # 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.lower()) - - address = ":".join(parts) - - # Find all consecutive zero blocks - matches = re.findall('(?::|^)(0(?::0)+)(?::|$)', address) - if matches: - # If multiple consecutive blocks have the same length, take the leftmost - match = max(matches) - if address.startswith(match): - leftidx = 0 - else: - leftidx = address.find(':' + match) + 1 - left = address[:leftidx] - rightidx = leftidx + len(match) - - # Adrress is like abcd:ef01:: - if len(address) == rightidx: - compact_address = left + ":" - - # Adrress is like ::abcd:ef01 - elif leftidx == 0: - compact_address = ":" + address[rightidx:] - - # Adrress is like abcd::ef01 - else: - compact_address = left + address[rightidx:] - - # Special case: address full of zeros - if compact_address == ":": - compact_address = "::" - else: - compact_address = address - return compact_address + 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 From 3f710fbbe2178778b3ff078e2d019a5cd6dbcdd5 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Fri, 6 Jan 2017 11:07:05 +0100 Subject: [PATCH 3/4] Improve regression tests PEP-8 compliance --- scapy/pton_ntop.py | 3 +- test/regression.uts | 70 ++++++++++++++++++++++++++------------------- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/scapy/pton_ntop.py b/scapy/pton_ntop.py index 5506a89e23e..30512923eb4 100644 --- a/scapy/pton_ntop.py +++ b/scapy/pton_ntop.py @@ -10,7 +10,8 @@ without IPv6 support, on Windows for instance. """ -import socket,struct,re +import socket +import re _IP6_ZEROS = re.compile('(?::|^)(0(?::0)+)(?::|$)') diff --git a/test/regression.uts b/test/regression.uts index a8cee7f9ee1..3921b4d8a38 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -6902,61 +6902,71 @@ 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 +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 -compressed = _ipv6_bin_to_str(address) -assert(compressed == '::') +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 +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 -compressed = _ipv6_bin_to_str(address) -assert(compressed == '1111:2222:3333:4444:5555:6666:7777:8888') +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 +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 -compressed = _ipv6_bin_to_str(address) -assert(compressed == '1111:2222:3333:4444:5555::') +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 +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 -compressed = _ipv6_bin_to_str(address) -assert(compressed == '::4444:5555:6666:7777:8888') +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 +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 -compressed = _ipv6_bin_to_str(address) -assert(compressed == '0:0:3333:4444::8888') +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 +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 -compressed = _ipv6_bin_to_str(address) -assert(compressed == '1::') +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 +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 -compressed = _ipv6_bin_to_str(address) -assert(compressed == '::1') +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 +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 -compressed = _ipv6_bin_to_str(address) -assert(compressed == '1111:2222:3333:4444:5555:6666:0:8888') +compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address) +assert(compressed1 == compressed2 == '1111:2222:3333:4444:5555:6666:0:8888') = IPv6 bin to string conversion - Two zero-blocks with equal length -from scapy.pton_ntop import _ipv6_bin_to_str +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 -compressed = _ipv6_bin_to_str(address) -assert(compressed == '1111::4444:0:0:7777:8888') +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 +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 -compressed = _ipv6_bin_to_str(address) -assert(compressed == '1000:200:30:4:5:60:700:8000') \ No newline at end of file +compressed1, compressed2 = _ipv6_bin_to_str(address), inet_ntop(socket.AF_INET6, address) +assert(compressed1 == compressed2 == '1000:200:30:4:5:60:700:8000') From c459de935845b32b7c61af22223c6ad6cd516547 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 10 Jan 2017 19:57:53 +0100 Subject: [PATCH 4/4] Modify regression test as socket.inet_pton is not RFC 5952 compliant on Mac OS --- test/regression.uts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/regression.uts b/test/regression.uts index 3921b4d8a38..7962638b730 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -6955,7 +6955,10 @@ 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 == compressed2 == '1111:2222:3333:4444:5555:6666:0:8888') +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