From 01110e502e84852930d896c73c9d0bb5cb14abdf Mon Sep 17 00:00:00 2001 From: Pierre LALET <pierre.lalet@cea.fr> Date: Sun, 29 Jan 2017 14:25:42 +0100 Subject: [PATCH] inet_{pton,ntop}: reorganize, cleanup and improve tests --- scapy/pton_ntop.py | 154 +++++++++++++++++++++++++++----------------- test/regression.uts | 154 +++++++++++++++++++++----------------------- 2 files changed, 168 insertions(+), 140 deletions(-) diff --git a/scapy/pton_ntop.py b/scapy/pton_ntop.py index 76e2dc87..8e6e8203 100644 --- a/scapy/pton_ntop.py +++ b/scapy/pton_ntop.py @@ -14,79 +14,115 @@ import socket import re _IP6_ZEROS = re.compile('(?::|^)(0(?::0)+)(?::|$)') +_INET6_PTON_EXC = socket.error("illegal IP address string passed to inet_pton") -def inet_pton(af, addr): - """Convert an IP address from text representation into binary form""" - if af == socket.AF_INET: - return socket.inet_aton(addr) - elif af == socket.AF_INET6: - # Use inet_pton if available - try: - return socket.inet_pton(af, addr) - except AttributeError: - pass - joker_pos = None - result = "" - parts = addr.split(":") - nparts = len(parts) - for i, part in enumerate(parts): - if not part: - # "::" indicates one or more groups of 2 null bytes - if joker_pos is None: - joker_pos = len(result) - else: - # Wildcard is only allowed once - raise Exception("Illegal syntax for IP address") - elif i + 1 == nparts and '.' in part: - # The last part of an IPv6 address can be an IPv4 address - try: - result += socket.inet_aton(part) - except socket.error: - raise Exception("Illegal syntax for IP address") +def _inet6_pton(addr): + """Convert an IPv6 address from text representation into binary form, +used when socket.inet_pton is not available. + + """ + joker_pos = None + result = "" + if addr == '::': + return '\x00' * 16 + if addr.startswith('::'): + addr = addr[1:] + if addr.endswith('::'): + addr = addr[:-1] + parts = addr.split(":") + nparts = len(parts) + for i, part in enumerate(parts): + if not part: + # "::" indicates one or more groups of 2 null bytes + if joker_pos is None: + joker_pos = len(result) else: - # Each part must be 16bit. Add missing zeroes before decoding. - try: - 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_pos is not None: - 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") + # Wildcard is only allowed once + raise _INET6_PTON_EXC + elif i + 1 == nparts and '.' in part: + # 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: + result += socket.inet_aton(part) + except socket.error: + raise _INET6_PTON_EXC + else: + # Each part must be 16bit. Add missing zeroes before decoding. + try: + result += part.rjust(4, "0").decode("hex") + except TypeError: + raise _INET6_PTON_EXC + # If there's a wildcard, fill up with zeros to reach 128bit (16 bytes) + if joker_pos is not None: + if len(result) == 16: + raise _INET6_PTON_EXC + result = (result[:joker_pos] + "\x00" * (16 - len(result)) + + result[joker_pos:]) + if len(result) != 16: + raise _INET6_PTON_EXC + return result -def inet_ntop(af, addr): - """Convert an IP address from binary form into text representation""" - if af == socket.AF_INET: - return socket.inet_ntoa(addr) - elif af == socket.AF_INET6: - # Use inet_ntop if available +_INET_PTON = { + socket.AF_INET: socket.inet_aton, + socket.AF_INET6: _inet6_pton, +} + + +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: - return socket.inet_ntop(af, addr) - except AttributeError: - return _ipv6_bin_to_str(addr) - else: - raise Exception("Address family not supported yet") + return _INET_PTON[af](addr) + except KeyError: + raise socket.error("Address family not supported by protocol") -def _ipv6_bin_to_str(addr): +def _inet6_ntop(addr): + """Convert an IPv6 address from binary form into text representation, +used when socket.inet_pton is not available. + + """ # 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)) + 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)) + # Get the longest set of zero blocks. 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 + + +_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) diff --git a/test/regression.uts b/test/regression.uts index 36d2fef2..c74e5379 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -7253,78 +7253,55 @@ 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 += IPv6 bin to string conversion +from scapy.pton_ntop import _inet6_ntop, 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') +for binfrm, address in [ + ('\x00' * 16, '::'), + ('\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88', + '1111:2222:3333:4444:5555:6666:7777:8888'), + ('\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x00\x00\x00\x00\x00\x00', + '1111:2222:3333:4444:5555::'), + ('\x00\x00\x00\x00\x00\x00\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88', + '::4444:5555:6666:7777:8888'), + ('\x00\x00\x00\x00\x33\x33\x44\x44\x00\x00\x00\x00\x00\x00\x88\x88', + '0:0:3333:4444::8888'), + ('\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + '1::'), + ('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01', + '::1'), + ('\x11\x11\x00\x00\x00\x00\x44\x44\x00\x00\x00\x00\x77\x77\x88\x88', + '1111::4444:0:0:7777:8888'), + ('\x10\x00\x02\x00\x00\x30\x00\x04\x00\x05\x00\x60\x07\x00\x80\x00', + '1000:200:30:4:5:60:700:8000'), +]: + addr1 = inet_ntop(socket.AF_INET6, binfrm) + addr2 = _inet6_ntop(binfrm) + assert address == addr1 == addr2 = 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') +binfrm = '\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x00\x00\x88\x88' +addr1, addr2 = inet_ntop(socket.AF_INET6, binfrm), _inet6_ntop(binfrm) +# On Mac OS socket.inet_ntop is not fully compliant with RFC 5952 and +# shortens the single zero block to '::'. This is a valid IPv6 address +# representation anyway. +assert(addr1 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 - Illegal sizes +for binfrm in ["\x00" * 15, "\x00" * 17]: + rc = False + try: + inet_ntop(socket.AF_INET6, binfrm) + except Exception as exc1: + rc = True + assert rc + try: + _inet6_ntop(binfrm) + except Exception as exc2: + rc = isinstance(exc2, type(exc1)) + assert rc ############ @@ -7466,31 +7443,46 @@ in6_getscope("::1") == IPV6_ADDR_LOOPBACK = inet_pton() +from scapy.pton_ntop import _inet6_pton, inet_pton import socket ip6_bad_addrs = ["fe80::2e67:ef2d:7eca::ed8a", "fe80:1234:abcd::192.168.40.12:abcd", "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: rc = False try: - inet_pton(socket.AF_INET6, ip6) - except Exception, e: + res1 = inet_pton(socket.AF_INET6, ip6) + except Exception as exc1: rc = True assert rc - -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 + rc = False try: - inet_pton(socket.AF_INET6, ip6) - except Exception, e: - rc = False + res2 = _inet6_pton(ip6) + except Exception as exc2: + rc = isinstance(exc2, type(exc1)) 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 -- GitLab