bpo-34788: Add support for scoped IPv6 addresses (GH-13772)
Automerge-Triggered-By: @asvetlov
diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py
index 7d80a52..9c47405 100644
--- a/Lib/ipaddress.py
+++ b/Lib/ipaddress.py
@@ -1836,6 +1836,26 @@
reverse_chars = self.exploded[::-1].replace(':', '')
return '.'.join(reverse_chars) + '.ip6.arpa'
+ @staticmethod
+ def _split_scope_id(ip_str):
+ """Helper function to parse IPv6 string address with scope id.
+
+ See RFC 4007 for details.
+
+ Args:
+ ip_str: A string, the IPv6 address.
+
+ Returns:
+ (addr, scope_id) tuple.
+
+ """
+ addr, sep, scope_id = ip_str.partition('%')
+ if not sep:
+ scope_id = None
+ elif not scope_id or '%' in scope_id:
+ raise AddressValueError('Invalid IPv6 address: "%r"' % ip_str)
+ return addr, scope_id
+
@property
def max_prefixlen(self):
return self._max_prefixlen
@@ -1849,7 +1869,7 @@
"""Represent and manipulate single IPv6 Addresses."""
- __slots__ = ('_ip', '__weakref__')
+ __slots__ = ('_ip', '_scope_id', '__weakref__')
def __init__(self, address):
"""Instantiate a new IPv6 address object.
@@ -1872,12 +1892,14 @@
if isinstance(address, int):
self._check_int_address(address)
self._ip = address
+ self._scope_id = None
return
# Constructing from a packed address
if isinstance(address, bytes):
self._check_packed_address(address, 16)
self._ip = int.from_bytes(address, 'big')
+ self._scope_id = None
return
# Assume input argument to be string or any object representation
@@ -1885,8 +1907,37 @@
addr_str = str(address)
if '/' in addr_str:
raise AddressValueError("Unexpected '/' in %r" % address)
+ addr_str, self._scope_id = self._split_scope_id(addr_str)
+
self._ip = self._ip_int_from_string(addr_str)
+ def __str__(self):
+ ip_str = super().__str__()
+ return ip_str + '%' + self._scope_id if self._scope_id else ip_str
+
+ def __hash__(self):
+ return hash((self._ip, self._scope_id))
+
+ def __eq__(self, other):
+ address_equal = super().__eq__(other)
+ if address_equal is NotImplemented:
+ return NotImplemented
+ if not address_equal:
+ return False
+ return self._scope_id == getattr(other, '_scope_id', None)
+
+ @property
+ def scope_id(self):
+ """Identifier of a particular zone of the address's scope.
+
+ See RFC 4007 for details.
+
+ Returns:
+ A string identifying the zone of the address if specified, else None.
+
+ """
+ return self._scope_id
+
@property
def packed(self):
"""The binary representation of this address."""
@@ -2040,7 +2091,7 @@
return self.network.hostmask
def __str__(self):
- return '%s/%d' % (self._string_from_ip_int(self._ip),
+ return '%s/%d' % (super().__str__(),
self._prefixlen)
def __eq__(self, other):