-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
bpo-34788: Add support for scoped IPv6 addresses #13772
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
eb4a407
8a5c94f
a841e9a
9a3f7ec
a17538a
6da269e
35750de
681cfa5
fb99b6b
d8bb674
ccd83ed
dc540e9
c03d4a5
c12e533
033f031
27c5cf4
3b495c0
1de1ae6
b0a4fe9
ac19fad
25d3ac7
66db707
064e019
ef91d9c
d0eb91b
0735d3f
fb47e34
b8dffe5
fc2425f
f9d38c0
d8972af
0b37c30
41ce543
7f3fae8
6c5a845
e569cd5
3982d78
6b5198f
97a7707
6339732
eff04f6
06e66e9
c25a327
84f4b3d
c6695bf
d30ea41
e971130
22c310e
5882d06
2c6fd54
0ec1572
5530a79
2f7de77
ef42a2d
641bc6b
34728d2
af90771
e041c2e
0e250bf
2c4d3d5
555f59f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -217,11 +217,20 @@ write code that handles both IP versions correctly. Address objects are | |
:RFC:`4291` for details. For example, | ||
``"0000:0000:0000:0000:0000:0abc:0007:0def"`` can be compressed to | ||
``"::abc:7:def"``. | ||
|
||
Optionally, the string may also have a scope zone ID, expressed | ||
with a suffix ``%scope_id``. If present, the scope ID must be non-empty, | ||
and may not contain ``%``. | ||
opavlyuk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
See :RFC:`4007` for details. | ||
opavlyuk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
For example, ``fe80::1234%1`` might identify address ``fe80::1234`` on the first link of the node. | ||
2. An integer that fits into 128 bits. | ||
3. An integer packed into a :class:`bytes` object of length 16, big-endian. | ||
|
||
|
||
>>> ipaddress.IPv6Address('2001:db8::1000') | ||
IPv6Address('2001:db8::1000') | ||
>>> ipaddress.IPv6Address('ff02::5678%1') | ||
IPv6Address('ff02::5678%1') | ||
|
||
.. attribute:: compressed | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this documentation on (Noticed this while skimming the doc just now to answer the question about where to make this feature visible with examples.) |
||
|
@@ -268,6 +277,12 @@ write code that handles both IP versions correctly. Address objects are | |
``::FFFF/96``), this property will report the embedded IPv4 address. | ||
For any other address, this property will be ``None``. | ||
|
||
.. attribute:: scope_id | ||
|
||
For scoped addresses as defined by :RFC:`4007`, this property identifies | ||
the particular zone of the address's scope that the address belongs to, | ||
as a string. When no scope zone is specified, this property will be ``None``. | ||
|
||
.. attribute:: sixtofour | ||
|
||
For addresses that appear to be 6to4 addresses (starting with | ||
|
@@ -299,6 +314,8 @@ the :func:`str` and :func:`int` builtin functions:: | |
>>> int(ipaddress.IPv6Address('::1')) | ||
1 | ||
|
||
Note that IPv6 scoped addresses are converted to integers without scope zone ID. | ||
|
||
|
||
Operators | ||
^^^^^^^^^ | ||
|
@@ -311,15 +328,20 @@ IPv6). | |
Comparison operators | ||
"""""""""""""""""""" | ||
|
||
Address objects can be compared with the usual set of comparison operators. Some | ||
examples:: | ||
Address objects can be compared with the usual set of comparison operators. | ||
Same IPv6 addresses with different scope zone IDs are not equal. | ||
Some examples:: | ||
|
||
>>> IPv4Address('127.0.0.2') > IPv4Address('127.0.0.1') | ||
True | ||
>>> IPv4Address('127.0.0.2') == IPv4Address('127.0.0.1') | ||
False | ||
>>> IPv4Address('127.0.0.2') != IPv4Address('127.0.0.1') | ||
True | ||
>>> IPv6Address('fe80::1234') == IPv6Address('fe80::1234%1') | ||
False | ||
>>> IPv6Address('fe80::1234%1') != IPv6Address('fe80::1234%2') | ||
True | ||
|
||
|
||
Arithmetic operators | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1836,6 +1836,26 @@ def _reverse_pointer(self): | |
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. | ||
asvetlov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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) | ||
opavlyuk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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,21 +1892,52 @@ def __init__(self, address): | |
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 | ||
# which converts into a formatted IP string. | ||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So this adds the scoped syntax, with Are there places where that's likely to wind up in a URL? The RFC in section 11.7 expressed concern about this syntax's interaction with URLs -- in particular, the I think if that causes a problem, it's a bug in the code that made the URL; so we can't do everything to prevent it. But:
... OK, it looks like nothing else in the stdlib refers to this module, so that takes care of point 2. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you clarify you suggestion, please? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure -- the request is this bit:
Concretely that means something like:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gnprice , great thanks, really very helpful. In progress. |
||
|
||
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. | ||
opavlyuk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
See RFC 4007 for details. | ||
|
||
Returns: | ||
A string identifying the zone of the address if specified, else None. | ||
|
||
""" | ||
opavlyuk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return self._scope_id | ||
|
||
@property | ||
def packed(self): | ||
"""The binary representation of this address.""" | ||
|
@@ -2040,7 +2091,7 @@ def hostmask(self): | |
return self.network.hostmask | ||
|
||
def __str__(self): | ||
return '%s/%d' % (self._string_from_ip_int(self._ip), | ||
return '%s/%d' % (super().__str__(), | ||
opavlyuk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self._prefixlen) | ||
|
||
def __eq__(self, other): | ||
|
Uh oh!
There was an error while loading. Please reload this page.