bpo-34788: Add support for scoped IPv6 addresses (GH-13772)

Automerge-Triggered-By: @asvetlov
This commit is contained in:
opavlyuk 2020-02-26 16:33:57 +02:00 committed by GitHub
parent be7ead62db
commit 21da76d1f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 544 additions and 37 deletions

View file

@ -1836,6 +1836,26 @@ class _BaseV6:
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 @@ class IPv6Address(_BaseV6, _BaseAddress):
"""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 @@ class IPv6Address(_BaseV6, _BaseAddress):
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 @@ class IPv6Address(_BaseV6, _BaseAddress):
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 @@ class IPv6Interface(IPv6Address):
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):