Skip to content

Commit

Permalink
Fix issue #359: Bin to str conversion of IPv6 addresses (#431)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xb35c authored and p-l- committed Jan 18, 2017
1 parent 5322d88 commit fcb62e7
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 24 deletions.
48 changes: 24 additions & 24 deletions scapy/pton_ntop.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down Expand Up @@ -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
76 changes: 76 additions & 0 deletions test/regression.uts
Original file line number Diff line number Diff line change
Expand Up @@ -7047,3 +7047,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')

0 comments on commit fcb62e7

Please sign in to comment.