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.
import socket
import re
_IP4_FORMAT = re.compile("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
_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
# IPv6: The use of "::" indicates one or more groups of 16 bits of zeros.
# We deal with this form of wildcard using a special marker.
JOKER = "*"
while "::" in addr:
addr = addr.replace("::", ":" + JOKER + ":")
joker_pos = None
# The last part of an IPv6 address can be an IPv4 address
ipv4_bin = None
ipv4_addr = None
if "." in addr:
ipv4_addr = addr.split(":")[-1]
if _IP4_FORMAT.match(ipv4_addr) is None:
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:
# 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:
ipv4_bin = socket.inet_aton(ipv4_addr)
result += socket.inet_aton(part)
except socket.error:
raise Exception("Illegal syntax for IP address")
result = ""
parts = addr.split(":")
for part in parts:
if part == JOKER:
# Wildcard is only allowed once
if joker_pos is None:
joker_pos = len(result)
else:
raise Exception("Illegal syntax for IP address")
elif part == ipv4_addr:
result += ipv4_bin
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 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")
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 _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)
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)
......@@ -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
......
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