Skip to content
Merged
34 changes: 33 additions & 1 deletion Lib/ipaddress.py
Original file line number Diff line number Diff line change
Expand Up @@ -1923,8 +1923,40 @@ def __init__(self, address):

self._ip = self._ip_int_from_string(addr_str)

def _explode_shorthand_ip_string(self):
ipv4_mapped = self.ipv4_mapped
if ipv4_mapped is None:
long_form = super()._explode_shorthand_ip_string()
else:
prefix_len = 30
raw_exploded_str = super()._explode_shorthand_ip_string()
long_form = "%s%s" % (raw_exploded_str[:prefix_len], str(ipv4_mapped))
return long_form

def _ipv4_mapped_ipv6_to_str(self):
"""Return convenient text representation of IPv4-mapped IPv6 address

See RFC 4291 2.5.5.2, 2.2 p.3 for details.

Returns:
A string, 'x:x:x:x:x:x:d.d.d.d', where the 'x's are the hexadecimal values of
the six high-order 16-bit pieces of the address, and the 'd's are
the decimal values of the four low-order 8-bit pieces of the
address (standard IPv4 representation) as defined in RFC 4291 2.2 p.3.

"""
ipv4_mapped = self.ipv4_mapped
if ipv4_mapped is None:
raise AddressValueError("Can not apply to non-IPv4-mapped IPv6 address %s" % str(self))
high_order_bits = self._ip >> 32
return "%s:%s" % (self._string_from_ip_int(high_order_bits), str(ipv4_mapped))

def __str__(self):
ip_str = super().__str__()
ipv4_mapped = self.ipv4_mapped
if ipv4_mapped is None:
ip_str = super().__str__()
else:
ip_str = self._ipv4_mapped_ipv6_to_str()
return ip_str + '%' + self._scope_id if self._scope_id else ip_str

def __hash__(self):
Expand Down
11 changes: 11 additions & 0 deletions Lib/test/test_ipaddress.py
Original file line number Diff line number Diff line change
Expand Up @@ -1321,6 +1321,17 @@ def testGetIp(self):
self.assertEqual(str(self.ipv6_scoped_interface.ip),
'2001:658:22a:cafe:200::1')

def testIPv6IPv4MappedStringRepresentation(self):
long_prefix = '0000:0000:0000:0000:0000:ffff:'
short_prefix = '::ffff:'
ipv4 = '1.2.3.4'
ipv6_ipv4_mapped_str = '%s%s' % (short_prefix, ipv4)
ipv6_ipv4_mapped_address = ipaddress.IPv6Address(ipv6_ipv4_mapped_str)
ipv6_ipv4_mapped_interface = ipaddress.IPv6Interface(ipv6_ipv4_mapped_str)
self.assertEqual(str(ipv6_ipv4_mapped_address), ipv6_ipv4_mapped_str)
self.assertEqual(ipv6_ipv4_mapped_address.exploded, long_prefix + ipv4)
self.assertEqual(str(ipv6_ipv4_mapped_interface.ip), ipv6_ipv4_mapped_str)

def testGetScopeId(self):
self.assertEqual(self.ipv6_address.scope_id,
None)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve the textual representation of IPv4-mapped IPv6 addresses (:rfc:`4291` Sections 2.2, 2.5.5.2) in :mod:`ipaddress.IPv6Address`. Patch by Oleksandr Pavliuk.