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

@ -217,11 +217,20 @@ write code that handles both IP versions correctly. Address objects are
:RFC:`4291` for details. For example, :RFC:`4291` for details. For example,
``"0000:0000:0000:0000:0000:0abc:0007:0def"`` can be compressed to ``"0000:0000:0000:0000:0000:0abc:0007:0def"`` can be compressed to
``"::abc:7:def"``. ``"::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 ``%``.
See :RFC:`4007` for details.
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. 2. An integer that fits into 128 bits.
3. An integer packed into a :class:`bytes` object of length 16, big-endian. 3. An integer packed into a :class:`bytes` object of length 16, big-endian.
>>> ipaddress.IPv6Address('2001:db8::1000') >>> ipaddress.IPv6Address('2001:db8::1000')
IPv6Address('2001:db8::1000') IPv6Address('2001:db8::1000')
>>> ipaddress.IPv6Address('ff02::5678%1')
IPv6Address('ff02::5678%1')
.. attribute:: compressed .. attribute:: compressed
@ -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. ``::FFFF/96``), this property will report the embedded IPv4 address.
For any other address, this property will be ``None``. 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 .. attribute:: sixtofour
For addresses that appear to be 6to4 addresses (starting with 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')) >>> int(ipaddress.IPv6Address('::1'))
1 1
Note that IPv6 scoped addresses are converted to integers without scope zone ID.
Operators Operators
^^^^^^^^^ ^^^^^^^^^
@ -311,8 +328,9 @@ IPv6).
Comparison operators Comparison operators
"""""""""""""""""""" """"""""""""""""""""
Address objects can be compared with the usual set of comparison operators. Some Address objects can be compared with the usual set of comparison operators.
examples:: Same IPv6 addresses with different scope zone IDs are not equal.
Some examples::
>>> IPv4Address('127.0.0.2') > IPv4Address('127.0.0.1') >>> IPv4Address('127.0.0.2') > IPv4Address('127.0.0.1')
True True
@ -320,6 +338,10 @@ examples::
False False
>>> IPv4Address('127.0.0.2') != IPv4Address('127.0.0.1') >>> IPv4Address('127.0.0.2') != IPv4Address('127.0.0.1')
True True
>>> IPv6Address('fe80::1234') == IPv6Address('fe80::1234%1')
False
>>> IPv6Address('fe80::1234%1') != IPv6Address('fe80::1234%2')
True
Arithmetic operators Arithmetic operators

View file

@ -78,15 +78,15 @@ created. Socket addresses are represented as follows:
Python programs. Python programs.
- For :const:`AF_INET6` address family, a four-tuple ``(host, port, flowinfo, - For :const:`AF_INET6` address family, a four-tuple ``(host, port, flowinfo,
scopeid)`` is used, where *flowinfo* and *scopeid* represent the ``sin6_flowinfo`` scope_id)`` is used, where *flowinfo* and *scope_id* represent the ``sin6_flowinfo``
and ``sin6_scope_id`` members in :const:`struct sockaddr_in6` in C. For and ``sin6_scope_id`` members in :const:`struct sockaddr_in6` in C. For
:mod:`socket` module methods, *flowinfo* and *scopeid* can be omitted just for :mod:`socket` module methods, *flowinfo* and *scope_id* can be omitted just for
backward compatibility. Note, however, omission of *scopeid* can cause problems backward compatibility. Note, however, omission of *scope_id* can cause problems
in manipulating scoped IPv6 addresses. in manipulating scoped IPv6 addresses.
.. versionchanged:: 3.7 .. versionchanged:: 3.7
For multicast addresses (with *scopeid* meaningful) *address* may not contain For multicast addresses (with *scope_id* meaningful) *address* may not contain
``%scope`` (or ``zone id``) part. This information is superfluous and may ``%scope_id`` (or ``zone id``) part. This information is superfluous and may
be safely omitted (recommended). be safely omitted (recommended).
- :const:`AF_NETLINK` sockets are represented as pairs ``(pid, groups)``. - :const:`AF_NETLINK` sockets are represented as pairs ``(pid, groups)``.
@ -738,7 +738,7 @@ The :mod:`socket` module also offers various network-related services:
:const:`AI_CANONNAME` is part of the *flags* argument; else *canonname* :const:`AI_CANONNAME` is part of the *flags* argument; else *canonname*
will be empty. *sockaddr* is a tuple describing a socket address, whose will be empty. *sockaddr* is a tuple describing a socket address, whose
format depends on the returned *family* (a ``(address, port)`` 2-tuple for format depends on the returned *family* (a ``(address, port)`` 2-tuple for
:const:`AF_INET`, a ``(address, port, flow info, scope id)`` 4-tuple for :const:`AF_INET`, a ``(address, port, flowinfo, scope_id)`` 4-tuple for
:const:`AF_INET6`), and is meant to be passed to the :meth:`socket.connect` :const:`AF_INET6`), and is meant to be passed to the :meth:`socket.connect`
method. method.
@ -759,7 +759,7 @@ The :mod:`socket` module also offers various network-related services:
.. versionchanged:: 3.7 .. versionchanged:: 3.7
for IPv6 multicast addresses, string representing an address will not for IPv6 multicast addresses, string representing an address will not
contain ``%scope`` part. contain ``%scope_id`` part.
.. function:: getfqdn([name]) .. function:: getfqdn([name])
@ -827,8 +827,8 @@ The :mod:`socket` module also offers various network-related services:
or numeric address representation in *host*. Similarly, *port* can contain a or numeric address representation in *host*. Similarly, *port* can contain a
string port name or a numeric port number. string port name or a numeric port number.
For IPv6 addresses, ``%scope`` is appended to the host part if *sockaddr* For IPv6 addresses, ``%scope_id`` is appended to the host part if *sockaddr*
contains meaningful *scopeid*. Usually this happens for multicast addresses. contains meaningful *scope_id*. Usually this happens for multicast addresses.
For more information about *flags* you can consult :manpage:`getnameinfo(3)`. For more information about *flags* you can consult :manpage:`getnameinfo(3)`.
@ -1354,7 +1354,7 @@ to sockets.
.. versionchanged:: 3.7 .. versionchanged:: 3.7
For multicast IPv6 address, first item of *address* does not contain For multicast IPv6 address, first item of *address* does not contain
``%scope`` part anymore. In order to get full IPv6 address use ``%scope_id`` part anymore. In order to get full IPv6 address use
:func:`getnameinfo`. :func:`getnameinfo`.
.. method:: socket.recvmsg(bufsize[, ancbufsize[, flags]]) .. method:: socket.recvmsg(bufsize[, ancbufsize[, flags]])

View file

@ -147,6 +147,8 @@ library/ipaddress,,:db8,>>> ipaddress.IPv6Address('2001:db8::1000')
library/ipaddress,,::,>>> ipaddress.IPv6Address('2001:db8::1000') library/ipaddress,,::,>>> ipaddress.IPv6Address('2001:db8::1000')
library/ipaddress,,:db8,IPv6Address('2001:db8::1000') library/ipaddress,,:db8,IPv6Address('2001:db8::1000')
library/ipaddress,,::,IPv6Address('2001:db8::1000') library/ipaddress,,::,IPv6Address('2001:db8::1000')
library/ipaddress,,::,IPv6Address('ff02::5678%1')
library/ipaddress,,::,fe80::1234
library/ipaddress,,:db8,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" library/ipaddress,,:db8,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer"
library/ipaddress,,::,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer" library/ipaddress,,::,">>> ipaddress.ip_address(""2001:db8::1"").reverse_pointer"
library/ipaddress,,::,"""::abc:7:def""" library/ipaddress,,::,"""::abc:7:def"""

1 c-api/arg :ref PyArg_ParseTuple(args, "O|O:ref", &object, &callback)
147 library/ipaddress :: >>> ipaddress.IPv6Address('2001:db8::1000')
148 library/ipaddress :db8 IPv6Address('2001:db8::1000')
149 library/ipaddress :: IPv6Address('2001:db8::1000')
150 library/ipaddress :: IPv6Address('ff02::5678%1')
151 library/ipaddress :: fe80::1234
152 library/ipaddress :db8 >>> ipaddress.ip_address("2001:db8::1").reverse_pointer
153 library/ipaddress :: >>> ipaddress.ip_address("2001:db8::1").reverse_pointer
154 library/ipaddress :: "::abc:7:def"

View file

@ -213,6 +213,15 @@ now raises :exc:`ImportError` instead of :exc:`ValueError` for invalid relative
import attempts. import attempts.
(Contributed by Ngalim Siregar in :issue:`37444`.) (Contributed by Ngalim Siregar in :issue:`37444`.)
ipaddress
---------
:mod:`ipaddress` now supports IPv6 Scoped Addresses (IPv6 address with suffix ``%<scope_id>``).
Scoped IPv6 addresses can be parsed using :class:`ipaddress.IPv6Address`.
If present, scope zone ID is available through the :attr:`~ipaddress.IPv6Address.scope_id` attribute.
(Contributed by Oleksandr Pavliuk in :issue:`34788`.)
math math
---- ----

View file

@ -1836,6 +1836,26 @@ class _BaseV6:
reverse_chars = self.exploded[::-1].replace(':', '') reverse_chars = self.exploded[::-1].replace(':', '')
return '.'.join(reverse_chars) + '.ip6.arpa' 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 @property
def max_prefixlen(self): def max_prefixlen(self):
return self._max_prefixlen return self._max_prefixlen
@ -1849,7 +1869,7 @@ class IPv6Address(_BaseV6, _BaseAddress):
"""Represent and manipulate single IPv6 Addresses.""" """Represent and manipulate single IPv6 Addresses."""
__slots__ = ('_ip', '__weakref__') __slots__ = ('_ip', '_scope_id', '__weakref__')
def __init__(self, address): def __init__(self, address):
"""Instantiate a new IPv6 address object. """Instantiate a new IPv6 address object.
@ -1872,12 +1892,14 @@ class IPv6Address(_BaseV6, _BaseAddress):
if isinstance(address, int): if isinstance(address, int):
self._check_int_address(address) self._check_int_address(address)
self._ip = address self._ip = address
self._scope_id = None
return return
# Constructing from a packed address # Constructing from a packed address
if isinstance(address, bytes): if isinstance(address, bytes):
self._check_packed_address(address, 16) self._check_packed_address(address, 16)
self._ip = int.from_bytes(address, 'big') self._ip = int.from_bytes(address, 'big')
self._scope_id = None
return return
# Assume input argument to be string or any object representation # Assume input argument to be string or any object representation
@ -1885,8 +1907,37 @@ class IPv6Address(_BaseV6, _BaseAddress):
addr_str = str(address) addr_str = str(address)
if '/' in addr_str: if '/' in addr_str:
raise AddressValueError("Unexpected '/' in %r" % address) 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) 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 @property
def packed(self): def packed(self):
"""The binary representation of this address.""" """The binary representation of this address."""
@ -2040,7 +2091,7 @@ class IPv6Interface(IPv6Address):
return self.network.hostmask return self.network.hostmask
def __str__(self): def __str__(self):
return '%s/%d' % (self._string_from_ip_int(self._ip), return '%s/%d' % (super().__str__(),
self._prefixlen) self._prefixlen)
def __eq__(self, other): def __eq__(self, other):

View file

@ -170,6 +170,15 @@ class CommonTestMixin_v6(CommonTestMixin):
assertBadLength(15) assertBadLength(15)
assertBadLength(17) assertBadLength(17)
def test_blank_scope_id(self):
address = ('::1%')
with self.assertAddressError('Invalid IPv6 address: "%r"', address):
self.factory(address)
def test_invalid_scope_id_with_percent(self):
address = ('::1%scope%')
with self.assertAddressError('Invalid IPv6 address: "%r"', address):
self.factory(address)
class AddressTestCase_v4(BaseTestCase, CommonTestMixin_v4): class AddressTestCase_v4(BaseTestCase, CommonTestMixin_v4):
factory = ipaddress.IPv4Address factory = ipaddress.IPv4Address
@ -328,24 +337,30 @@ class AddressTestCase_v6(BaseTestCase, CommonTestMixin_v6):
self.assertEqual(txt, format(v6, fmt)) self.assertEqual(txt, format(v6, fmt))
def test_network_passed_as_address(self): def test_network_passed_as_address(self):
addr = "::1/24" def assertBadSplit(addr):
with self.assertAddressError("Unexpected '/' in %r", addr): msg = "Unexpected '/' in %r"
with self.assertAddressError(msg, addr):
ipaddress.IPv6Address(addr) ipaddress.IPv6Address(addr)
assertBadSplit("::1/24")
assertBadSplit("::1%scope_id/24")
def test_bad_address_split_v6_not_enough_parts(self): def test_bad_address_split_v6_not_enough_parts(self):
def assertBadSplit(addr): def assertBadSplit(addr):
msg = "At least 3 parts expected in %r" msg = "At least 3 parts expected in %r"
with self.assertAddressError(msg, addr): with self.assertAddressError(msg, addr.split('%')[0]):
ipaddress.IPv6Address(addr) ipaddress.IPv6Address(addr)
assertBadSplit(":") assertBadSplit(":")
assertBadSplit(":1") assertBadSplit(":1")
assertBadSplit("FEDC:9878") assertBadSplit("FEDC:9878")
assertBadSplit(":%scope")
assertBadSplit(":1%scope")
assertBadSplit("FEDC:9878%scope")
def test_bad_address_split_v6_too_many_colons(self): def test_bad_address_split_v6_too_many_colons(self):
def assertBadSplit(addr): def assertBadSplit(addr):
msg = "At most 8 colons permitted in %r" msg = "At most 8 colons permitted in %r"
with self.assertAddressError(msg, addr): with self.assertAddressError(msg, addr.split('%')[0]):
ipaddress.IPv6Address(addr) ipaddress.IPv6Address(addr)
assertBadSplit("9:8:7:6:5:4:3::2:1") assertBadSplit("9:8:7:6:5:4:3::2:1")
@ -355,10 +370,17 @@ class AddressTestCase_v6(BaseTestCase, CommonTestMixin_v6):
# A trailing IPv4 address is two parts # A trailing IPv4 address is two parts
assertBadSplit("10:9:8:7:6:5:4:3:42.42.42.42") assertBadSplit("10:9:8:7:6:5:4:3:42.42.42.42")
assertBadSplit("9:8:7:6:5:4:3::2:1%scope")
assertBadSplit("10:9:8:7:6:5:4:3:2:1%scope")
assertBadSplit("::8:7:6:5:4:3:2:1%scope")
assertBadSplit("8:7:6:5:4:3:2:1::%scope")
# A trailing IPv4 address is two parts
assertBadSplit("10:9:8:7:6:5:4:3:42.42.42.42%scope")
def test_bad_address_split_v6_too_many_parts(self): def test_bad_address_split_v6_too_many_parts(self):
def assertBadSplit(addr): def assertBadSplit(addr):
msg = "Exactly 8 parts expected without '::' in %r" msg = "Exactly 8 parts expected without '::' in %r"
with self.assertAddressError(msg, addr): with self.assertAddressError(msg, addr.split('%')[0]):
ipaddress.IPv6Address(addr) ipaddress.IPv6Address(addr)
assertBadSplit("3ffe:0:0:0:0:0:0:0:1") assertBadSplit("3ffe:0:0:0:0:0:0:0:1")
@ -368,18 +390,26 @@ class AddressTestCase_v6(BaseTestCase, CommonTestMixin_v6):
assertBadSplit("9:8:7:6:5:4:3:42.42.42.42") assertBadSplit("9:8:7:6:5:4:3:42.42.42.42")
assertBadSplit("7:6:5:4:3:42.42.42.42") assertBadSplit("7:6:5:4:3:42.42.42.42")
assertBadSplit("3ffe:0:0:0:0:0:0:0:1%scope")
assertBadSplit("9:8:7:6:5:4:3:2:1%scope")
assertBadSplit("7:6:5:4:3:2:1%scope")
# A trailing IPv4 address is two parts
assertBadSplit("9:8:7:6:5:4:3:42.42.42.42%scope")
assertBadSplit("7:6:5:4:3:42.42.42.42%scope")
def test_bad_address_split_v6_too_many_parts_with_double_colon(self): def test_bad_address_split_v6_too_many_parts_with_double_colon(self):
def assertBadSplit(addr): def assertBadSplit(addr):
msg = "Expected at most 7 other parts with '::' in %r" msg = "Expected at most 7 other parts with '::' in %r"
with self.assertAddressError(msg, addr): with self.assertAddressError(msg, addr.split('%')[0]):
ipaddress.IPv6Address(addr) ipaddress.IPv6Address(addr)
assertBadSplit("1:2:3:4::5:6:7:8") assertBadSplit("1:2:3:4::5:6:7:8")
assertBadSplit("1:2:3:4::5:6:7:8%scope")
def test_bad_address_split_v6_repeated_double_colon(self): def test_bad_address_split_v6_repeated_double_colon(self):
def assertBadSplit(addr): def assertBadSplit(addr):
msg = "At most one '::' permitted in %r" msg = "At most one '::' permitted in %r"
with self.assertAddressError(msg, addr): with self.assertAddressError(msg, addr.split('%')[0]):
ipaddress.IPv6Address(addr) ipaddress.IPv6Address(addr)
assertBadSplit("3ffe::1::1") assertBadSplit("3ffe::1::1")
@ -393,10 +423,21 @@ class AddressTestCase_v6(BaseTestCase, CommonTestMixin_v6):
assertBadSplit(":::") assertBadSplit(":::")
assertBadSplit('2001:db8:::1') assertBadSplit('2001:db8:::1')
assertBadSplit("3ffe::1::1%scope")
assertBadSplit("1::2::3::4:5%scope")
assertBadSplit("2001::db:::1%scope")
assertBadSplit("3ffe::1::%scope")
assertBadSplit("::3ffe::1%scope")
assertBadSplit(":3ffe::1::1%scope")
assertBadSplit("3ffe::1::1:%scope")
assertBadSplit(":3ffe::1::1:%scope")
assertBadSplit(":::%scope")
assertBadSplit('2001:db8:::1%scope')
def test_bad_address_split_v6_leading_colon(self): def test_bad_address_split_v6_leading_colon(self):
def assertBadSplit(addr): def assertBadSplit(addr):
msg = "Leading ':' only permitted as part of '::' in %r" msg = "Leading ':' only permitted as part of '::' in %r"
with self.assertAddressError(msg, addr): with self.assertAddressError(msg, addr.split('%')[0]):
ipaddress.IPv6Address(addr) ipaddress.IPv6Address(addr)
assertBadSplit(":2001:db8::1") assertBadSplit(":2001:db8::1")
@ -404,10 +445,15 @@ class AddressTestCase_v6(BaseTestCase, CommonTestMixin_v6):
assertBadSplit(":1:2:3:4:5:6:") assertBadSplit(":1:2:3:4:5:6:")
assertBadSplit(":6:5:4:3:2:1::") assertBadSplit(":6:5:4:3:2:1::")
assertBadSplit(":2001:db8::1%scope")
assertBadSplit(":1:2:3:4:5:6:7%scope")
assertBadSplit(":1:2:3:4:5:6:%scope")
assertBadSplit(":6:5:4:3:2:1::%scope")
def test_bad_address_split_v6_trailing_colon(self): def test_bad_address_split_v6_trailing_colon(self):
def assertBadSplit(addr): def assertBadSplit(addr):
msg = "Trailing ':' only permitted as part of '::' in %r" msg = "Trailing ':' only permitted as part of '::' in %r"
with self.assertAddressError(msg, addr): with self.assertAddressError(msg, addr.split('%')[0]):
ipaddress.IPv6Address(addr) ipaddress.IPv6Address(addr)
assertBadSplit("2001:db8::1:") assertBadSplit("2001:db8::1:")
@ -415,9 +461,14 @@ class AddressTestCase_v6(BaseTestCase, CommonTestMixin_v6):
assertBadSplit("::1.2.3.4:") assertBadSplit("::1.2.3.4:")
assertBadSplit("::7:6:5:4:3:2:") assertBadSplit("::7:6:5:4:3:2:")
assertBadSplit("2001:db8::1:%scope")
assertBadSplit("1:2:3:4:5:6:7:%scope")
assertBadSplit("::1.2.3.4:%scope")
assertBadSplit("::7:6:5:4:3:2:%scope")
def test_bad_v4_part_in(self): def test_bad_v4_part_in(self):
def assertBadAddressPart(addr, v4_error): def assertBadAddressPart(addr, v4_error):
with self.assertAddressError("%s in %r", v4_error, addr): with self.assertAddressError("%s in %r", v4_error, addr.split('%')[0]):
ipaddress.IPv6Address(addr) ipaddress.IPv6Address(addr)
assertBadAddressPart("3ffe::1.net", "Expected 4 octets in '1.net'") assertBadAddressPart("3ffe::1.net", "Expected 4 octets in '1.net'")
@ -431,9 +482,20 @@ class AddressTestCase_v6(BaseTestCase, CommonTestMixin_v6):
"Only decimal digits permitted in 'net' " "Only decimal digits permitted in 'net' "
"in '1.1.1.net'") "in '1.1.1.net'")
assertBadAddressPart("3ffe::1.net%scope", "Expected 4 octets in '1.net'")
assertBadAddressPart("3ffe::127.0.1%scope",
"Expected 4 octets in '127.0.1'")
assertBadAddressPart("::1.2.3%scope",
"Expected 4 octets in '1.2.3'")
assertBadAddressPart("::1.2.3.4.5%scope",
"Expected 4 octets in '1.2.3.4.5'")
assertBadAddressPart("3ffe::1.1.1.net%scope",
"Only decimal digits permitted in 'net' "
"in '1.1.1.net'")
def test_invalid_characters(self): def test_invalid_characters(self):
def assertBadPart(addr, part): def assertBadPart(addr, part):
msg = "Only hex digits permitted in %r in %r" % (part, addr) msg = "Only hex digits permitted in %r in %r" % (part, addr.split('%')[0])
with self.assertAddressError(re.escape(msg)): with self.assertAddressError(re.escape(msg)):
ipaddress.IPv6Address(addr) ipaddress.IPv6Address(addr)
@ -444,10 +506,17 @@ class AddressTestCase_v6(BaseTestCase, CommonTestMixin_v6):
assertBadPart("1.2.3.4::", "1.2.3.4") assertBadPart("1.2.3.4::", "1.2.3.4")
assertBadPart('1234:axy::b', "axy") assertBadPart('1234:axy::b', "axy")
assertBadPart("3ffe::goog%scope", "goog")
assertBadPart("3ffe::-0%scope", "-0")
assertBadPart("3ffe::+0%scope", "+0")
assertBadPart("3ffe::-1%scope", "-1")
assertBadPart("1.2.3.4::%scope", "1.2.3.4")
assertBadPart('1234:axy::b%scope', "axy")
def test_part_length(self): def test_part_length(self):
def assertBadPart(addr, part): def assertBadPart(addr, part):
msg = "At most 4 characters permitted in %r in %r" msg = "At most 4 characters permitted in %r in %r"
with self.assertAddressError(msg, part, addr): with self.assertAddressError(msg, part, addr.split('%')[0]):
ipaddress.IPv6Address(addr) ipaddress.IPv6Address(addr)
assertBadPart("::00000", "00000") assertBadPart("::00000", "00000")
@ -455,11 +524,17 @@ class AddressTestCase_v6(BaseTestCase, CommonTestMixin_v6):
assertBadPart("02001:db8::", "02001") assertBadPart("02001:db8::", "02001")
assertBadPart('2001:888888::1', "888888") assertBadPart('2001:888888::1', "888888")
assertBadPart("::00000%scope", "00000")
assertBadPart("3ffe::10000%scope", "10000")
assertBadPart("02001:db8::%scope", "02001")
assertBadPart('2001:888888::1%scope', "888888")
def test_pickle(self): def test_pickle(self):
self.pickle_test('2001:db8::') self.pickle_test('2001:db8::')
def test_weakref(self): def test_weakref(self):
weakref.ref(self.factory('2001:db8::')) weakref.ref(self.factory('2001:db8::'))
weakref.ref(self.factory('2001:db8::%scope'))
class NetmaskTestMixin_v4(CommonTestMixin_v4): class NetmaskTestMixin_v4(CommonTestMixin_v4):
@ -617,11 +692,20 @@ class NetmaskTestMixin_v6(CommonTestMixin_v6):
# IPv6Network has prefixlen, but IPv6Interface doesn't. # IPv6Network has prefixlen, but IPv6Interface doesn't.
# Should we add it to IPv4Interface too? (bpo-36392) # Should we add it to IPv4Interface too? (bpo-36392)
scoped_net = self.factory('::1%scope')
self.assertEqual(str(scoped_net), '::1%scope/128')
self.assertEqual(str(scoped_net.netmask), 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff')
self.assertEqual(str(scoped_net.hostmask), '::')
def test_split_netmask(self): def test_split_netmask(self):
addr = "cafe:cafe::/128/190" addr = "cafe:cafe::/128/190"
with self.assertAddressError("Only one '/' permitted in %r" % addr): with self.assertAddressError("Only one '/' permitted in %r" % addr):
self.factory(addr) self.factory(addr)
scoped_addr = "cafe:cafe::%scope/128/190"
with self.assertAddressError("Only one '/' permitted in %r" % scoped_addr):
self.factory(scoped_addr)
def test_address_errors(self): def test_address_errors(self):
def assertBadAddress(addr, details): def assertBadAddress(addr, details):
with self.assertAddressError(details): with self.assertAddressError(details):
@ -634,6 +718,13 @@ class NetmaskTestMixin_v6(CommonTestMixin_v6):
assertBadAddress("10/8", "At least 3 parts") assertBadAddress("10/8", "At least 3 parts")
assertBadAddress("1234:axy::b", "Only hex digits") assertBadAddress("1234:axy::b", "Only hex digits")
assertBadAddress("/%scope", "Address cannot be empty")
assertBadAddress("/%scope8", "Address cannot be empty")
assertBadAddress("google.com%scope", "At least 3 parts")
assertBadAddress("1.2.3.4%scope", "At least 3 parts")
assertBadAddress("10%scope/8", "At least 3 parts")
assertBadAddress("1234:axy::b%scope", "Only hex digits")
def test_valid_netmask(self): def test_valid_netmask(self):
# We only support CIDR for IPv6, because expanded netmasks are not # We only support CIDR for IPv6, because expanded netmasks are not
# standard notation. # standard notation.
@ -645,6 +736,14 @@ class NetmaskTestMixin_v6(CommonTestMixin_v6):
# Zero prefix is treated as decimal. # Zero prefix is treated as decimal.
self.assertEqual(str(self.factory('::/0%d' % i)), net_str) self.assertEqual(str(self.factory('::/0%d' % i)), net_str)
self.assertEqual(str(self.factory('2001:db8::%scope/32')), '2001:db8::%scope/32')
for i in range(0, 129):
# Generate and re-parse the CIDR format (trivial).
net_str = '::/%d' % i
self.assertEqual(str(self.factory(net_str)), net_str)
# Zero prefix is treated as decimal.
self.assertEqual(str(self.factory('::/0%d' % i)), net_str)
def test_netmask_errors(self): def test_netmask_errors(self):
def assertBadNetmask(addr, netmask): def assertBadNetmask(addr, netmask):
msg = "%r is not a valid netmask" % netmask msg = "%r is not a valid netmask" % netmask
@ -663,6 +762,8 @@ class NetmaskTestMixin_v6(CommonTestMixin_v6):
assertBadNetmask("::1", "pudding") assertBadNetmask("::1", "pudding")
assertBadNetmask("::", "::") assertBadNetmask("::", "::")
assertBadNetmask("::1%scope", "pudding")
def test_netmask_in_tuple_errors(self): def test_netmask_in_tuple_errors(self):
def assertBadNetmask(addr, netmask): def assertBadNetmask(addr, netmask):
msg = "%r is not a valid netmask" % netmask msg = "%r is not a valid netmask" % netmask
@ -670,12 +771,15 @@ class NetmaskTestMixin_v6(CommonTestMixin_v6):
self.factory((addr, netmask)) self.factory((addr, netmask))
assertBadNetmask("::1", -1) assertBadNetmask("::1", -1)
assertBadNetmask("::1", 129) assertBadNetmask("::1", 129)
assertBadNetmask("::1%scope", 129)
def test_pickle(self): def test_pickle(self):
self.pickle_test('2001:db8::1000/124') self.pickle_test('2001:db8::1000/124')
self.pickle_test('2001:db8::1000/127') # IPV6LENGTH - 1 self.pickle_test('2001:db8::1000/127') # IPV6LENGTH - 1
self.pickle_test('2001:db8::1000') # IPV6LENGTH self.pickle_test('2001:db8::1000') # IPV6LENGTH
self.pickle_test('2001:db8::1000%scope') # IPV6LENGTH
class InterfaceTestCase_v6(BaseTestCase, NetmaskTestMixin_v6): class InterfaceTestCase_v6(BaseTestCase, NetmaskTestMixin_v6):
factory = ipaddress.IPv6Interface factory = ipaddress.IPv6Interface
@ -702,6 +806,13 @@ class NetworkTestCase_v6(BaseTestCase, NetmaskTestMixin_v6):
self.factory('2000:aaa::/48').subnet_of( self.factory('2000:aaa::/48').subnet_of(
self.factory('2000:aaa::/56'))) self.factory('2000:aaa::/56')))
self.assertFalse(
self.factory('2000:999::%scope/56').subnet_of(
self.factory('2000:aaa::%scope/48')))
self.assertTrue(
self.factory('2000:aaa::%scope/56').subnet_of(
self.factory('2000:aaa::%scope/48')))
def test_supernet_of(self): def test_supernet_of(self):
# containee left of container # containee left of container
self.assertFalse( self.assertFalse(
@ -748,13 +859,19 @@ class ComparisonTests(unittest.TestCase):
v6addr = ipaddress.IPv6Address(1) v6addr = ipaddress.IPv6Address(1)
v6net = ipaddress.IPv6Network(1) v6net = ipaddress.IPv6Network(1)
v6intf = ipaddress.IPv6Interface(1) v6intf = ipaddress.IPv6Interface(1)
v6addr_scoped = ipaddress.IPv6Address('::1%scope')
v6net_scoped= ipaddress.IPv6Network('::1%scope')
v6intf_scoped= ipaddress.IPv6Interface('::1%scope')
v4_addresses = [v4addr, v4intf] v4_addresses = [v4addr, v4intf]
v4_objects = v4_addresses + [v4net] v4_objects = v4_addresses + [v4net]
v6_addresses = [v6addr, v6intf] v6_addresses = [v6addr, v6intf]
v6_objects = v6_addresses + [v6net] v6_objects = v6_addresses + [v6net]
v6_scoped_addresses = [v6addr_scoped, v6intf_scoped]
v6_scoped_objects = v6_scoped_addresses + [v6net_scoped]
objects = v4_objects + v6_objects objects = v4_objects + v6_objects
objects_with_scoped = objects + v6_scoped_objects
v4addr2 = ipaddress.IPv4Address(2) v4addr2 = ipaddress.IPv4Address(2)
v4net2 = ipaddress.IPv4Network(2) v4net2 = ipaddress.IPv4Network(2)
@ -762,11 +879,14 @@ class ComparisonTests(unittest.TestCase):
v6addr2 = ipaddress.IPv6Address(2) v6addr2 = ipaddress.IPv6Address(2)
v6net2 = ipaddress.IPv6Network(2) v6net2 = ipaddress.IPv6Network(2)
v6intf2 = ipaddress.IPv6Interface(2) v6intf2 = ipaddress.IPv6Interface(2)
v6addr2_scoped = ipaddress.IPv6Address('::2%scope')
v6net2_scoped = ipaddress.IPv6Network('::2%scope')
v6intf2_scoped = ipaddress.IPv6Interface('::2%scope')
def test_foreign_type_equality(self): def test_foreign_type_equality(self):
# __eq__ should never raise TypeError directly # __eq__ should never raise TypeError directly
other = object() other = object()
for obj in self.objects: for obj in self.objects_with_scoped:
self.assertNotEqual(obj, other) self.assertNotEqual(obj, other)
self.assertFalse(obj == other) self.assertFalse(obj == other)
self.assertEqual(obj.__eq__(other), NotImplemented) self.assertEqual(obj.__eq__(other), NotImplemented)
@ -781,8 +901,17 @@ class ComparisonTests(unittest.TestCase):
continue continue
self.assertNotEqual(lhs, rhs) self.assertNotEqual(lhs, rhs)
def test_scoped_ipv6_equality(self):
for lhs, rhs in zip(self.v6_objects, self.v6_scoped_objects):
self.assertNotEqual(lhs, rhs)
def test_v4_with_v6_scoped_equality(self):
for lhs in self.v4_objects:
for rhs in self.v6_scoped_objects:
self.assertNotEqual(lhs, rhs)
def test_same_type_equality(self): def test_same_type_equality(self):
for obj in self.objects: for obj in self.objects_with_scoped:
self.assertEqual(obj, obj) self.assertEqual(obj, obj)
self.assertLessEqual(obj, obj) self.assertLessEqual(obj, obj)
self.assertGreaterEqual(obj, obj) self.assertGreaterEqual(obj, obj)
@ -795,6 +924,9 @@ class ComparisonTests(unittest.TestCase):
(self.v6addr, self.v6addr2), (self.v6addr, self.v6addr2),
(self.v6net, self.v6net2), (self.v6net, self.v6net2),
(self.v6intf, self.v6intf2), (self.v6intf, self.v6intf2),
(self.v6addr_scoped, self.v6addr2_scoped),
(self.v6net_scoped, self.v6net2_scoped),
(self.v6intf_scoped, self.v6intf2_scoped),
): ):
self.assertNotEqual(lhs, rhs) self.assertNotEqual(lhs, rhs)
self.assertLess(lhs, rhs) self.assertLess(lhs, rhs)
@ -809,16 +941,21 @@ class ComparisonTests(unittest.TestCase):
def test_containment(self): def test_containment(self):
for obj in self.v4_addresses: for obj in self.v4_addresses:
self.assertIn(obj, self.v4net) self.assertIn(obj, self.v4net)
for obj in self.v6_addresses: for obj in self.v6_addresses + self.v6_scoped_addresses:
self.assertIn(obj, self.v6net) self.assertIn(obj, self.v6net)
for obj in self.v4_objects + [self.v6net]: for obj in self.v6_addresses + self.v6_scoped_addresses:
self.assertIn(obj, self.v6net_scoped)
for obj in self.v4_objects + [self.v6net, self.v6net_scoped]:
self.assertNotIn(obj, self.v6net) self.assertNotIn(obj, self.v6net)
for obj in self.v6_objects + [self.v4net]: for obj in self.v4_objects + [self.v6net, self.v6net_scoped]:
self.assertNotIn(obj, self.v6net_scoped)
for obj in self.v6_objects + self.v6_scoped_objects + [self.v4net]:
self.assertNotIn(obj, self.v4net) self.assertNotIn(obj, self.v4net)
def test_mixed_type_ordering(self): def test_mixed_type_ordering(self):
for lhs in self.objects: for lhs in self.objects_with_scoped:
for rhs in self.objects: for rhs in self.objects_with_scoped:
if isinstance(lhs, type(rhs)) or isinstance(rhs, type(lhs)): if isinstance(lhs, type(rhs)) or isinstance(rhs, type(lhs)):
continue continue
self.assertRaises(TypeError, lambda: lhs < rhs) self.assertRaises(TypeError, lambda: lhs < rhs)
@ -828,7 +965,7 @@ class ComparisonTests(unittest.TestCase):
def test_foreign_type_ordering(self): def test_foreign_type_ordering(self):
other = object() other = object()
for obj in self.objects: for obj in self.objects_with_scoped:
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
obj < other obj < other
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
@ -850,14 +987,18 @@ class ComparisonTests(unittest.TestCase):
# with get_mixed_type_key, you can sort addresses and network. # with get_mixed_type_key, you can sort addresses and network.
v4_ordered = [self.v4addr, self.v4net, self.v4intf] v4_ordered = [self.v4addr, self.v4net, self.v4intf]
v6_ordered = [self.v6addr, self.v6net, self.v6intf] v6_ordered = [self.v6addr, self.v6net, self.v6intf]
v6_scoped_ordered = [self.v6addr_scoped, self.v6net_scoped, self.v6intf_scoped]
self.assertEqual(v4_ordered, self.assertEqual(v4_ordered,
sorted(self.v4_objects, sorted(self.v4_objects,
key=ipaddress.get_mixed_type_key)) key=ipaddress.get_mixed_type_key))
self.assertEqual(v6_ordered, self.assertEqual(v6_ordered,
sorted(self.v6_objects, sorted(self.v6_objects,
key=ipaddress.get_mixed_type_key)) key=ipaddress.get_mixed_type_key))
self.assertEqual(v4_ordered + v6_ordered, self.assertEqual(v6_scoped_ordered,
sorted(self.objects, sorted(self.v6_scoped_objects,
key=ipaddress.get_mixed_type_key))
self.assertEqual(v4_ordered + v6_scoped_ordered,
sorted(self.v4_objects + self.v6_scoped_objects,
key=ipaddress.get_mixed_type_key)) key=ipaddress.get_mixed_type_key))
self.assertEqual(NotImplemented, ipaddress.get_mixed_type_key(object)) self.assertEqual(NotImplemented, ipaddress.get_mixed_type_key(object))
@ -867,6 +1008,8 @@ class ComparisonTests(unittest.TestCase):
v4net = ipaddress.ip_network('1.1.1.1') v4net = ipaddress.ip_network('1.1.1.1')
v6addr = ipaddress.ip_address('::1') v6addr = ipaddress.ip_address('::1')
v6net = ipaddress.ip_network('::1') v6net = ipaddress.ip_network('::1')
v6addr_scoped = ipaddress.ip_address('::1%scope')
v6net_scoped = ipaddress.ip_network('::1%scope')
self.assertRaises(TypeError, v4addr.__lt__, v6addr) self.assertRaises(TypeError, v4addr.__lt__, v6addr)
self.assertRaises(TypeError, v4addr.__gt__, v6addr) self.assertRaises(TypeError, v4addr.__gt__, v6addr)
@ -878,6 +1021,16 @@ class ComparisonTests(unittest.TestCase):
self.assertRaises(TypeError, v6net.__lt__, v4net) self.assertRaises(TypeError, v6net.__lt__, v4net)
self.assertRaises(TypeError, v6net.__gt__, v4net) self.assertRaises(TypeError, v6net.__gt__, v4net)
self.assertRaises(TypeError, v4addr.__lt__, v6addr_scoped)
self.assertRaises(TypeError, v4addr.__gt__, v6addr_scoped)
self.assertRaises(TypeError, v4net.__lt__, v6net_scoped)
self.assertRaises(TypeError, v4net.__gt__, v6net_scoped)
self.assertRaises(TypeError, v6addr_scoped.__lt__, v4addr)
self.assertRaises(TypeError, v6addr_scoped.__gt__, v4addr)
self.assertRaises(TypeError, v6net_scoped.__lt__, v4net)
self.assertRaises(TypeError, v6net_scoped.__gt__, v4net)
class IpaddrUnitTest(unittest.TestCase): class IpaddrUnitTest(unittest.TestCase):
@ -891,12 +1044,19 @@ class IpaddrUnitTest(unittest.TestCase):
self.ipv6_interface = ipaddress.IPv6Interface( self.ipv6_interface = ipaddress.IPv6Interface(
'2001:658:22a:cafe:200:0:0:1/64') '2001:658:22a:cafe:200:0:0:1/64')
self.ipv6_network = ipaddress.IPv6Network('2001:658:22a:cafe::/64') self.ipv6_network = ipaddress.IPv6Network('2001:658:22a:cafe::/64')
self.ipv6_scoped_address = ipaddress.IPv6Interface(
'2001:658:22a:cafe:200:0:0:1%scope')
self.ipv6_scoped_interface = ipaddress.IPv6Interface(
'2001:658:22a:cafe:200:0:0:1%scope/64')
self.ipv6_scoped_network = ipaddress.IPv6Network('2001:658:22a:cafe::%scope/64')
def testRepr(self): def testRepr(self):
self.assertEqual("IPv4Interface('1.2.3.4/32')", self.assertEqual("IPv4Interface('1.2.3.4/32')",
repr(ipaddress.IPv4Interface('1.2.3.4'))) repr(ipaddress.IPv4Interface('1.2.3.4')))
self.assertEqual("IPv6Interface('::1/128')", self.assertEqual("IPv6Interface('::1/128')",
repr(ipaddress.IPv6Interface('::1'))) repr(ipaddress.IPv6Interface('::1')))
self.assertEqual("IPv6Interface('::1%scope/128')",
repr(ipaddress.IPv6Interface('::1%scope')))
# issue #16531: constructing IPv4Network from an (address, mask) tuple # issue #16531: constructing IPv4Network from an (address, mask) tuple
def testIPv4Tuple(self): def testIPv4Tuple(self):
@ -983,6 +1143,8 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertEqual(ipaddress.IPv6Network((ip, '96')), self.assertEqual(ipaddress.IPv6Network((ip, '96')),
net) net)
ip_scoped = ipaddress.IPv6Address('2001:db8::%scope')
# strict=True and host bits set # strict=True and host bits set
ip = ipaddress.IPv6Address('2001:db8::1') ip = ipaddress.IPv6Address('2001:db8::1')
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
@ -1011,6 +1173,13 @@ class IpaddrUnitTest(unittest.TestCase):
(42540766411282592856903984951653826561, '96')), (42540766411282592856903984951653826561, '96')),
ipaddress.IPv6Interface('2001:db8::1/96')) ipaddress.IPv6Interface('2001:db8::1/96'))
ip_scoped = ipaddress.IPv6Address('2001:db8::1%scope')
with self.assertRaises(ValueError):
ipaddress.IPv6Network(('2001:db8::1%scope', 96))
with self.assertRaises(ValueError):
ipaddress.IPv6Network((ip_scoped, 96))
# strict=False and host bits set
# issue57 # issue57
def testAddressIntMath(self): def testAddressIntMath(self):
self.assertEqual(ipaddress.IPv4Address('1.1.1.1') + 255, self.assertEqual(ipaddress.IPv4Address('1.1.1.1') + 255,
@ -1021,6 +1190,10 @@ class IpaddrUnitTest(unittest.TestCase):
ipaddress.IPv6Address('::ffff')) ipaddress.IPv6Address('::ffff'))
self.assertEqual(ipaddress.IPv6Address('::ffff') - (2**16 - 2), self.assertEqual(ipaddress.IPv6Address('::ffff') - (2**16 - 2),
ipaddress.IPv6Address('::1')) ipaddress.IPv6Address('::1'))
self.assertNotEqual(ipaddress.IPv6Address('::1%scope') + (2**16 - 2),
ipaddress.IPv6Address('::ffff%scope'))
self.assertNotEqual(ipaddress.IPv6Address('::ffff%scope') - (2**16 - 2),
ipaddress.IPv6Address('::1%scope'))
def testInvalidIntToBytes(self): def testInvalidIntToBytes(self):
self.assertRaises(ValueError, ipaddress.v4_int_to_packed, -1) self.assertRaises(ValueError, ipaddress.v4_int_to_packed, -1)
@ -1053,6 +1226,12 @@ class IpaddrUnitTest(unittest.TestCase):
'2001:658:22a:cafe::') '2001:658:22a:cafe::')
self.assertEqual(str(self.ipv6_network.hostmask), self.assertEqual(str(self.ipv6_network.hostmask),
'::ffff:ffff:ffff:ffff') '::ffff:ffff:ffff:ffff')
self.assertEqual(int(self.ipv6_scoped_network.network_address),
42540616829182469433403647294022090752)
self.assertEqual(str(self.ipv6_scoped_network.network_address),
'2001:658:22a:cafe::%scope')
self.assertEqual(str(self.ipv6_scoped_network.hostmask),
'::ffff:ffff:ffff:ffff')
def testIpFromInt(self): def testIpFromInt(self):
self.assertEqual(self.ipv4_interface._ip, self.assertEqual(self.ipv4_interface._ip,
@ -1060,17 +1239,23 @@ class IpaddrUnitTest(unittest.TestCase):
ipv4 = ipaddress.ip_network('1.2.3.4') ipv4 = ipaddress.ip_network('1.2.3.4')
ipv6 = ipaddress.ip_network('2001:658:22a:cafe:200:0:0:1') ipv6 = ipaddress.ip_network('2001:658:22a:cafe:200:0:0:1')
ipv6_scoped = ipaddress.ip_network('2001:658:22a:cafe:200:0:0:1%scope')
self.assertEqual(ipv4, ipaddress.ip_network(int(ipv4.network_address))) self.assertEqual(ipv4, ipaddress.ip_network(int(ipv4.network_address)))
self.assertEqual(ipv6, ipaddress.ip_network(int(ipv6.network_address))) self.assertEqual(ipv6, ipaddress.ip_network(int(ipv6.network_address)))
self.assertNotEqual(ipv6_scoped, ipaddress.ip_network(int(ipv6_scoped.network_address)))
v6_int = 42540616829182469433547762482097946625 v6_int = 42540616829182469433547762482097946625
self.assertEqual(self.ipv6_interface._ip, self.assertEqual(self.ipv6_interface._ip,
ipaddress.IPv6Interface(v6_int)._ip) ipaddress.IPv6Interface(v6_int)._ip)
self.assertEqual(self.ipv6_scoped_interface._ip,
ipaddress.IPv6Interface(v6_int)._ip)
self.assertEqual(ipaddress.ip_network(self.ipv4_address._ip).version, self.assertEqual(ipaddress.ip_network(self.ipv4_address._ip).version,
4) 4)
self.assertEqual(ipaddress.ip_network(self.ipv6_address._ip).version, self.assertEqual(ipaddress.ip_network(self.ipv6_address._ip).version,
6) 6)
self.assertEqual(ipaddress.ip_network(self.ipv6_scoped_address._ip).version,
6)
def testIpFromPacked(self): def testIpFromPacked(self):
address = ipaddress.ip_address address = ipaddress.ip_address
@ -1096,6 +1281,24 @@ class IpaddrUnitTest(unittest.TestCase):
42540616829182469433547762482097946625) 42540616829182469433547762482097946625)
self.assertEqual(str(self.ipv6_interface.ip), self.assertEqual(str(self.ipv6_interface.ip),
'2001:658:22a:cafe:200::1') '2001:658:22a:cafe:200::1')
self.assertEqual(int(self.ipv6_scoped_interface.ip),
42540616829182469433547762482097946625)
self.assertEqual(str(self.ipv6_scoped_interface.ip),
'2001:658:22a:cafe:200::1')
def testGetScopeId(self):
self.assertEqual(self.ipv6_address.scope_id,
None)
self.assertEqual(str(self.ipv6_scoped_address.scope_id),
'scope')
self.assertEqual(self.ipv6_interface.scope_id,
None)
self.assertEqual(str(self.ipv6_scoped_interface.scope_id),
'scope')
self.assertEqual(self.ipv6_network.network_address.scope_id,
None)
self.assertEqual(str(self.ipv6_scoped_network.network_address.scope_id),
'scope')
def testGetNetmask(self): def testGetNetmask(self):
self.assertEqual(int(self.ipv4_network.netmask), 4294967040) self.assertEqual(int(self.ipv4_network.netmask), 4294967040)
@ -1103,6 +1306,9 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertEqual(int(self.ipv6_network.netmask), self.assertEqual(int(self.ipv6_network.netmask),
340282366920938463444927863358058659840) 340282366920938463444927863358058659840)
self.assertEqual(self.ipv6_network.prefixlen, 64) self.assertEqual(self.ipv6_network.prefixlen, 64)
self.assertEqual(int(self.ipv6_scoped_network.netmask),
340282366920938463444927863358058659840)
self.assertEqual(self.ipv6_scoped_network.prefixlen, 64)
def testZeroNetmask(self): def testZeroNetmask(self):
ipv4_zero_netmask = ipaddress.IPv4Interface('1.2.3.4/0') ipv4_zero_netmask = ipaddress.IPv4Interface('1.2.3.4/0')
@ -1113,6 +1319,10 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertEqual(int(ipv6_zero_netmask.network.netmask), 0) self.assertEqual(int(ipv6_zero_netmask.network.netmask), 0)
self.assertEqual(ipv6_zero_netmask._prefix_from_prefix_string('0'), 0) self.assertEqual(ipv6_zero_netmask._prefix_from_prefix_string('0'), 0)
ipv6_scoped_zero_netmask = ipaddress.IPv6Interface('::1%scope/0')
self.assertEqual(int(ipv6_scoped_zero_netmask.network.netmask), 0)
self.assertEqual(ipv6_scoped_zero_netmask._prefix_from_prefix_string('0'), 0)
def testIPv4Net(self): def testIPv4Net(self):
net = ipaddress.IPv4Network('127.0.0.0/0.0.0.255') net = ipaddress.IPv4Network('127.0.0.0/0.0.0.255')
self.assertEqual(net.prefixlen, 24) self.assertEqual(net.prefixlen, 24)
@ -1126,9 +1336,15 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertEqual(str(self.ipv6_network.broadcast_address), self.assertEqual(str(self.ipv6_network.broadcast_address),
'2001:658:22a:cafe:ffff:ffff:ffff:ffff') '2001:658:22a:cafe:ffff:ffff:ffff:ffff')
self.assertEqual(int(self.ipv6_scoped_network.broadcast_address),
42540616829182469451850391367731642367)
self.assertEqual(str(self.ipv6_scoped_network.broadcast_address),
'2001:658:22a:cafe:ffff:ffff:ffff:ffff')
def testGetPrefixlen(self): def testGetPrefixlen(self):
self.assertEqual(self.ipv4_interface.network.prefixlen, 24) self.assertEqual(self.ipv4_interface.network.prefixlen, 24)
self.assertEqual(self.ipv6_interface.network.prefixlen, 64) self.assertEqual(self.ipv6_interface.network.prefixlen, 64)
self.assertEqual(self.ipv6_scoped_interface.network.prefixlen, 64)
def testGetSupernet(self): def testGetSupernet(self):
self.assertEqual(self.ipv4_network.supernet().prefixlen, 23) self.assertEqual(self.ipv4_network.supernet().prefixlen, 23)
@ -1143,6 +1359,9 @@ class IpaddrUnitTest(unittest.TestCase):
'2001:658:22a:cafe::') '2001:658:22a:cafe::')
self.assertEqual(ipaddress.IPv6Interface('::0/0').network.supernet(), self.assertEqual(ipaddress.IPv6Interface('::0/0').network.supernet(),
ipaddress.IPv6Network('::0/0')) ipaddress.IPv6Network('::0/0'))
self.assertEqual(self.ipv6_scoped_network.supernet().prefixlen, 63)
self.assertEqual(str(self.ipv6_scoped_network.supernet().network_address),
'2001:658:22a:cafe::')
def testGetSupernet3(self): def testGetSupernet3(self):
self.assertEqual(self.ipv4_network.supernet(3).prefixlen, 21) self.assertEqual(self.ipv4_network.supernet(3).prefixlen, 21)
@ -1152,6 +1371,9 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertEqual(self.ipv6_network.supernet(3).prefixlen, 61) self.assertEqual(self.ipv6_network.supernet(3).prefixlen, 61)
self.assertEqual(str(self.ipv6_network.supernet(3).network_address), self.assertEqual(str(self.ipv6_network.supernet(3).network_address),
'2001:658:22a:caf8::') '2001:658:22a:caf8::')
self.assertEqual(self.ipv6_scoped_network.supernet(3).prefixlen, 61)
self.assertEqual(str(self.ipv6_scoped_network.supernet(3).network_address),
'2001:658:22a:caf8::')
def testGetSupernet4(self): def testGetSupernet4(self):
self.assertRaises(ValueError, self.ipv4_network.supernet, self.assertRaises(ValueError, self.ipv4_network.supernet,
@ -1167,6 +1389,12 @@ class IpaddrUnitTest(unittest.TestCase):
new_prefix=65) new_prefix=65)
self.assertEqual(self.ipv6_network.supernet(prefixlen_diff=2), self.assertEqual(self.ipv6_network.supernet(prefixlen_diff=2),
self.ipv6_network.supernet(new_prefix=62)) self.ipv6_network.supernet(new_prefix=62))
self.assertRaises(ValueError, self.ipv6_scoped_network.supernet,
prefixlen_diff=2, new_prefix=1)
self.assertRaises(ValueError, self.ipv6_scoped_network.supernet,
new_prefix=65)
self.assertEqual(self.ipv6_scoped_network.supernet(prefixlen_diff=2),
self.ipv6_scoped_network.supernet(new_prefix=62))
def testHosts(self): def testHosts(self):
hosts = list(self.ipv4_network.hosts()) hosts = list(self.ipv4_network.hosts())
@ -1180,6 +1408,12 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertEqual(ipaddress.IPv6Address('2001:658:22a:cafe::1'), hosts[0]) self.assertEqual(ipaddress.IPv6Address('2001:658:22a:cafe::1'), hosts[0])
self.assertEqual(ipaddress.IPv6Address('2001:658:22a:cafe::ff'), hosts[-1]) self.assertEqual(ipaddress.IPv6Address('2001:658:22a:cafe::ff'), hosts[-1])
ipv6_scoped_network = ipaddress.IPv6Network('2001:658:22a:cafe::%scope/120')
hosts = list(ipv6_scoped_network.hosts())
self.assertEqual(255, len(hosts))
self.assertEqual(ipaddress.IPv6Address('2001:658:22a:cafe::1'), hosts[0])
self.assertEqual(ipaddress.IPv6Address('2001:658:22a:cafe::ff'), hosts[-1])
# special case where only 1 bit is left for address # special case where only 1 bit is left for address
addrs = [ipaddress.IPv4Address('2.0.0.0'), addrs = [ipaddress.IPv4Address('2.0.0.0'),
ipaddress.IPv4Address('2.0.0.1')] ipaddress.IPv4Address('2.0.0.1')]
@ -1198,6 +1432,14 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertEqual(addrs, list(ipaddress.ip_network(tpl_args).hosts())) self.assertEqual(addrs, list(ipaddress.ip_network(tpl_args).hosts()))
self.assertEqual(list(ipaddress.ip_network(str_args).hosts()), self.assertEqual(list(ipaddress.ip_network(str_args).hosts()),
list(ipaddress.ip_network(tpl_args).hosts())) list(ipaddress.ip_network(tpl_args).hosts()))
addrs = [ipaddress.IPv6Address('2001:658:22a:cafe::'),
ipaddress.IPv6Address('2001:658:22a:cafe::1')]
str_args = '2001:658:22a:cafe::/127'
tpl_args = ('2001:658:22a:cafe::', 127)
self.assertEqual(addrs, list(ipaddress.ip_network(str_args).hosts()))
self.assertEqual(addrs, list(ipaddress.ip_network(tpl_args).hosts()))
self.assertEqual(list(ipaddress.ip_network(str_args).hosts()),
list(ipaddress.ip_network(tpl_args).hosts()))
def testFancySubnetting(self): def testFancySubnetting(self):
self.assertEqual(sorted(self.ipv4_network.subnets(prefixlen_diff=3)), self.assertEqual(sorted(self.ipv4_network.subnets(prefixlen_diff=3)),
@ -1214,6 +1456,13 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertRaises(ValueError, list, self.assertRaises(ValueError, list,
self.ipv6_network.subnets(prefixlen_diff=4, self.ipv6_network.subnets(prefixlen_diff=4,
new_prefix=68)) new_prefix=68))
self.assertEqual(sorted(self.ipv6_scoped_network.subnets(prefixlen_diff=4)),
sorted(self.ipv6_scoped_network.subnets(new_prefix=68)))
self.assertRaises(ValueError, list,
self.ipv6_scoped_network.subnets(new_prefix=63))
self.assertRaises(ValueError, list,
self.ipv6_scoped_network.subnets(prefixlen_diff=4,
new_prefix=68))
def testGetSubnets(self): def testGetSubnets(self):
self.assertEqual(list(self.ipv4_network.subnets())[0].prefixlen, 25) self.assertEqual(list(self.ipv4_network.subnets())[0].prefixlen, 25)
@ -1225,6 +1474,7 @@ class IpaddrUnitTest(unittest.TestCase):
'1.2.3.128') '1.2.3.128')
self.assertEqual(list(self.ipv6_network.subnets())[0].prefixlen, 65) self.assertEqual(list(self.ipv6_network.subnets())[0].prefixlen, 65)
self.assertEqual(list(self.ipv6_scoped_network.subnets())[0].prefixlen, 65)
def testGetSubnetForSingle32(self): def testGetSubnetForSingle32(self):
ip = ipaddress.IPv4Network('1.2.3.4/32') ip = ipaddress.IPv4Network('1.2.3.4/32')
@ -1240,6 +1490,12 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertEqual(subnets1, ['::1/128']) self.assertEqual(subnets1, ['::1/128'])
self.assertEqual(subnets1, subnets2) self.assertEqual(subnets1, subnets2)
ip_scoped = ipaddress.IPv6Network('::1%scope/128')
subnets1 = [str(x) for x in ip_scoped.subnets()]
subnets2 = [str(x) for x in ip_scoped.subnets(2)]
self.assertEqual(subnets1, ['::1%scope/128'])
self.assertEqual(subnets1, subnets2)
def testSubnet2(self): def testSubnet2(self):
ips = [str(x) for x in self.ipv4_network.subnets(2)] ips = [str(x) for x in self.ipv4_network.subnets(2)]
self.assertEqual( self.assertEqual(
@ -1283,12 +1539,18 @@ class IpaddrUnitTest(unittest.TestCase):
self.ipv6_interface.network.subnets(65)) self.ipv6_interface.network.subnets(65))
self.assertRaises(ValueError, list, self.assertRaises(ValueError, list,
self.ipv6_network.subnets(65)) self.ipv6_network.subnets(65))
self.assertRaises(ValueError, list,
self.ipv6_scoped_interface.network.subnets(65))
self.assertRaises(ValueError, list,
self.ipv6_scoped_network.subnets(65))
def testSupernetFailsForLargeCidrDiff(self): def testSupernetFailsForLargeCidrDiff(self):
self.assertRaises(ValueError, self.assertRaises(ValueError,
self.ipv4_interface.network.supernet, 25) self.ipv4_interface.network.supernet, 25)
self.assertRaises(ValueError, self.assertRaises(ValueError,
self.ipv6_interface.network.supernet, 65) self.ipv6_interface.network.supernet, 65)
self.assertRaises(ValueError,
self.ipv6_scoped_interface.network.supernet, 65)
def testSubnetFailsForNegativeCidrDiff(self): def testSubnetFailsForNegativeCidrDiff(self):
self.assertRaises(ValueError, list, self.assertRaises(ValueError, list,
@ -1299,6 +1561,10 @@ class IpaddrUnitTest(unittest.TestCase):
self.ipv6_interface.network.subnets(-1)) self.ipv6_interface.network.subnets(-1))
self.assertRaises(ValueError, list, self.assertRaises(ValueError, list,
self.ipv6_network.subnets(-1)) self.ipv6_network.subnets(-1))
self.assertRaises(ValueError, list,
self.ipv6_scoped_interface.network.subnets(-1))
self.assertRaises(ValueError, list,
self.ipv6_scoped_network.subnets(-1))
def testGetNum_Addresses(self): def testGetNum_Addresses(self):
self.assertEqual(self.ipv4_network.num_addresses, 256) self.assertEqual(self.ipv4_network.num_addresses, 256)
@ -1311,6 +1577,11 @@ class IpaddrUnitTest(unittest.TestCase):
9223372036854775808) 9223372036854775808)
self.assertEqual(self.ipv6_network.supernet().num_addresses, self.assertEqual(self.ipv6_network.supernet().num_addresses,
36893488147419103232) 36893488147419103232)
self.assertEqual(self.ipv6_scoped_network.num_addresses, 18446744073709551616)
self.assertEqual(list(self.ipv6_scoped_network.subnets())[0].num_addresses,
9223372036854775808)
self.assertEqual(self.ipv6_scoped_network.supernet().num_addresses,
36893488147419103232)
def testContains(self): def testContains(self):
self.assertIn(ipaddress.IPv4Interface('1.2.3.128/25'), self.assertIn(ipaddress.IPv4Interface('1.2.3.128/25'),
@ -1332,6 +1603,9 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertEqual(str(self.ipv6_network[5]), self.assertEqual(str(self.ipv6_network[5]),
'2001:658:22a:cafe::5') '2001:658:22a:cafe::5')
self.assertRaises(IndexError, self.ipv6_network.__getitem__, 1 << 64) self.assertRaises(IndexError, self.ipv6_network.__getitem__, 1 << 64)
self.assertEqual(str(self.ipv6_scoped_network[5]),
'2001:658:22a:cafe::5')
self.assertRaises(IndexError, self.ipv6_scoped_network.__getitem__, 1 << 64)
def testGetitem(self): def testGetitem(self):
# http://code.google.com/p/ipaddr-py/issues/detail?id=15 # http://code.google.com/p/ipaddr-py/issues/detail?id=15
@ -1351,6 +1625,8 @@ class IpaddrUnitTest(unittest.TestCase):
ipaddress.IPv4Interface('1.2.3.4/23')) ipaddress.IPv4Interface('1.2.3.4/23'))
self.assertFalse(self.ipv4_interface == self.assertFalse(self.ipv4_interface ==
ipaddress.IPv6Interface('::1.2.3.4/24')) ipaddress.IPv6Interface('::1.2.3.4/24'))
self.assertFalse(self.ipv4_interface ==
ipaddress.IPv6Interface('::1.2.3.4%scope/24'))
self.assertFalse(self.ipv4_interface == '') self.assertFalse(self.ipv4_interface == '')
self.assertFalse(self.ipv4_interface == []) self.assertFalse(self.ipv4_interface == [])
self.assertFalse(self.ipv4_interface == 2) self.assertFalse(self.ipv4_interface == 2)
@ -1365,6 +1641,20 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertFalse(self.ipv6_interface == []) self.assertFalse(self.ipv6_interface == [])
self.assertFalse(self.ipv6_interface == 2) self.assertFalse(self.ipv6_interface == 2)
self.assertTrue(self.ipv6_scoped_interface ==
ipaddress.IPv6Interface('2001:658:22a:cafe:200::1%scope/64'))
self.assertFalse(self.ipv6_scoped_interface ==
ipaddress.IPv6Interface('2001:658:22a:cafe:200::1%scope/63'))
self.assertFalse(self.ipv6_scoped_interface ==
ipaddress.IPv6Interface('2001:658:22a:cafe:200::1/64'))
self.assertFalse(self.ipv6_scoped_interface ==
ipaddress.IPv6Interface('2001:658:22a:cafe:200::1/63'))
self.assertFalse(self.ipv6_scoped_interface ==
ipaddress.IPv4Interface('1.2.3.4/23'))
self.assertFalse(self.ipv6_scoped_interface == '')
self.assertFalse(self.ipv6_scoped_interface == [])
self.assertFalse(self.ipv6_scoped_interface == 2)
def testNotEqual(self): def testNotEqual(self):
self.assertFalse(self.ipv4_interface != self.assertFalse(self.ipv4_interface !=
ipaddress.IPv4Interface('1.2.3.4/24')) ipaddress.IPv4Interface('1.2.3.4/24'))
@ -1372,6 +1662,8 @@ class IpaddrUnitTest(unittest.TestCase):
ipaddress.IPv4Interface('1.2.3.4/23')) ipaddress.IPv4Interface('1.2.3.4/23'))
self.assertTrue(self.ipv4_interface != self.assertTrue(self.ipv4_interface !=
ipaddress.IPv6Interface('::1.2.3.4/24')) ipaddress.IPv6Interface('::1.2.3.4/24'))
self.assertTrue(self.ipv4_interface !=
ipaddress.IPv6Interface('::1.2.3.4%scope/24'))
self.assertTrue(self.ipv4_interface != '') self.assertTrue(self.ipv4_interface != '')
self.assertTrue(self.ipv4_interface != []) self.assertTrue(self.ipv4_interface != [])
self.assertTrue(self.ipv4_interface != 2) self.assertTrue(self.ipv4_interface != 2)
@ -1398,6 +1690,26 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertTrue(self.ipv6_address != []) self.assertTrue(self.ipv6_address != [])
self.assertTrue(self.ipv6_address != 2) self.assertTrue(self.ipv6_address != 2)
self.assertFalse(self.ipv6_scoped_interface !=
ipaddress.IPv6Interface('2001:658:22a:cafe:200::1%scope/64'))
self.assertTrue(self.ipv6_scoped_interface !=
ipaddress.IPv6Interface('2001:658:22a:cafe:200::1%scope/63'))
self.assertTrue(self.ipv6_scoped_interface !=
ipaddress.IPv6Interface('2001:658:22a:cafe:200::1/64'))
self.assertTrue(self.ipv6_scoped_interface !=
ipaddress.IPv6Interface('2001:658:22a:cafe:200::1/63'))
self.assertTrue(self.ipv6_scoped_interface !=
ipaddress.IPv4Interface('1.2.3.4/23'))
self.assertTrue(self.ipv6_scoped_interface != '')
self.assertTrue(self.ipv6_scoped_interface != [])
self.assertTrue(self.ipv6_scoped_interface != 2)
self.assertTrue(self.ipv6_scoped_address !=
ipaddress.IPv4Address('1.2.3.4'))
self.assertTrue(self.ipv6_scoped_address != '')
self.assertTrue(self.ipv6_scoped_address != [])
self.assertTrue(self.ipv6_scoped_address != 2)
def testSlash32Constructor(self): def testSlash32Constructor(self):
self.assertEqual(str(ipaddress.IPv4Interface( self.assertEqual(str(ipaddress.IPv4Interface(
'1.2.3.4/255.255.255.255')), '1.2.3.4/32') '1.2.3.4/255.255.255.255')), '1.2.3.4/32')
@ -1405,6 +1717,8 @@ class IpaddrUnitTest(unittest.TestCase):
def testSlash128Constructor(self): def testSlash128Constructor(self):
self.assertEqual(str(ipaddress.IPv6Interface('::1/128')), self.assertEqual(str(ipaddress.IPv6Interface('::1/128')),
'::1/128') '::1/128')
self.assertEqual(str(ipaddress.IPv6Interface('::1%scope/128')),
'::1%scope/128')
def testSlash0Constructor(self): def testSlash0Constructor(self):
self.assertEqual(str(ipaddress.IPv4Interface('1.2.3.4/0.0.0.0')), self.assertEqual(str(ipaddress.IPv4Interface('1.2.3.4/0.0.0.0')),
@ -1476,6 +1790,13 @@ class IpaddrUnitTest(unittest.TestCase):
collapsed = ipaddress.collapse_addresses([ip1, ip2, ip3]) collapsed = ipaddress.collapse_addresses([ip1, ip2, ip3])
self.assertEqual(list(collapsed), [ip3]) self.assertEqual(list(collapsed), [ip3])
ip1 = ipaddress.IPv6Network('2001::%scope/100')
ip2 = ipaddress.IPv6Network('2001::%scope/120')
ip3 = ipaddress.IPv6Network('2001::%scope/96')
# test that ipv6 addresses are subsumed properly.
collapsed = ipaddress.collapse_addresses([ip1, ip2, ip3])
self.assertEqual(list(collapsed), [ip3])
# the toejam test # the toejam test
addr_tuples = [ addr_tuples = [
(ipaddress.ip_address('1.1.1.1'), (ipaddress.ip_address('1.1.1.1'),
@ -1489,6 +1810,18 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertRaises(TypeError, ipaddress.collapse_addresses, self.assertRaises(TypeError, ipaddress.collapse_addresses,
[ip1, ip2]) [ip1, ip2])
addr_tuples = [
(ipaddress.ip_address('1.1.1.1'),
ipaddress.ip_address('::1%scope')),
(ipaddress.IPv4Network('1.1.0.0/24'),
ipaddress.IPv6Network('2001::%scope/120')),
(ipaddress.IPv4Network('1.1.0.0/32'),
ipaddress.IPv6Network('2001::%scope/128')),
]
for ip1, ip2 in addr_tuples:
self.assertRaises(TypeError, ipaddress.collapse_addresses,
[ip1, ip2])
def testSummarizing(self): def testSummarizing(self):
#ip = ipaddress.ip_address #ip = ipaddress.ip_address
#ipnet = ipaddress.ip_network #ipnet = ipaddress.ip_network
@ -1508,6 +1841,8 @@ class IpaddrUnitTest(unittest.TestCase):
# test that a summary over ip4 & ip6 fails # test that a summary over ip4 & ip6 fails
self.assertRaises(TypeError, list, self.assertRaises(TypeError, list,
summarize(ip1, ipaddress.IPv6Address('::1'))) summarize(ip1, ipaddress.IPv6Address('::1')))
self.assertRaises(TypeError, list,
summarize(ip1, ipaddress.IPv6Address('::1%scope')))
# test a /24 is summarized properly # test a /24 is summarized properly
self.assertEqual(list(summarize(ip1, ip2))[0], self.assertEqual(list(summarize(ip1, ip2))[0],
ipaddress.ip_network('1.1.1.0/24')) ipaddress.ip_network('1.1.1.0/24'))
@ -1533,6 +1868,17 @@ class IpaddrUnitTest(unittest.TestCase):
[ipaddress.ip_network('1::/16'), [ipaddress.ip_network('1::/16'),
ipaddress.ip_network('2::/128')]) ipaddress.ip_network('2::/128')])
ip1 = ipaddress.ip_address('1::%scope')
ip2 = ipaddress.ip_address('1:ffff:ffff:ffff:ffff:ffff:ffff:ffff%scope')
# test an IPv6 is summarized properly
self.assertEqual(list(summarize(ip1, ip2))[0],
ipaddress.ip_network('1::/16'))
# test an IPv6 range that isn't on a network byte boundary
ip2 = ipaddress.ip_address('2::%scope')
self.assertEqual(list(summarize(ip1, ip2)),
[ipaddress.ip_network('1::/16'),
ipaddress.ip_network('2::/128')])
# test exception raised when first is greater than last # test exception raised when first is greater than last
self.assertRaises(ValueError, list, self.assertRaises(ValueError, list,
summarize(ipaddress.ip_address('1.1.1.0'), summarize(ipaddress.ip_address('1.1.1.0'),
@ -1558,6 +1904,10 @@ class IpaddrUnitTest(unittest.TestCase):
ipaddress.ip_address('::1')) ipaddress.ip_address('::1'))
self.assertTrue(ipaddress.ip_address('::1') <= self.assertTrue(ipaddress.ip_address('::1') <=
ipaddress.ip_address('::2')) ipaddress.ip_address('::2'))
self.assertTrue(ipaddress.ip_address('::1%scope') <=
ipaddress.ip_address('::1%scope'))
self.assertTrue(ipaddress.ip_address('::1%scope') <=
ipaddress.ip_address('::2%scope'))
def testInterfaceComparison(self): def testInterfaceComparison(self):
self.assertTrue(ipaddress.ip_interface('1.1.1.1/24') == self.assertTrue(ipaddress.ip_interface('1.1.1.1/24') ==
@ -1590,6 +1940,52 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertTrue(ipaddress.ip_interface('::1/64') > self.assertTrue(ipaddress.ip_interface('::1/64') >
ipaddress.ip_interface('::2/48')) ipaddress.ip_interface('::2/48'))
self.assertTrue(ipaddress.ip_interface('::1%scope/64') ==
ipaddress.ip_interface('::1%scope/64'))
self.assertTrue(ipaddress.ip_interface('::1%scope/64') <
ipaddress.ip_interface('::1%scope/80'))
self.assertTrue(ipaddress.ip_interface('::1%scope/64') <
ipaddress.ip_interface('::2%scope/64'))
self.assertTrue(ipaddress.ip_interface('::2%scope/48') <
ipaddress.ip_interface('::1%scope/64'))
self.assertTrue(ipaddress.ip_interface('::1%scope/80') >
ipaddress.ip_interface('::1%scope/64'))
self.assertTrue(ipaddress.ip_interface('::2%scope/64') >
ipaddress.ip_interface('::1%scope/64'))
self.assertTrue(ipaddress.ip_interface('::1%scope/64') >
ipaddress.ip_interface('::2%scope/48'))
self.assertFalse(ipaddress.ip_interface('::1%scope/64') ==
ipaddress.ip_interface('::1/64'))
self.assertTrue(ipaddress.ip_interface('::1%scope/64') <
ipaddress.ip_interface('::1/80'))
self.assertTrue(ipaddress.ip_interface('::1%scope/64') <
ipaddress.ip_interface('::2/64'))
self.assertTrue(ipaddress.ip_interface('::2%scope/48') <
ipaddress.ip_interface('::1/64'))
self.assertTrue(ipaddress.ip_interface('::1%scope/80') >
ipaddress.ip_interface('::1/64'))
self.assertTrue(ipaddress.ip_interface('::2%scope/64') >
ipaddress.ip_interface('::1/64'))
self.assertTrue(ipaddress.ip_interface('::1%scope/64') >
ipaddress.ip_interface('::2/48'))
self.assertFalse(ipaddress.ip_interface('::1/64') ==
ipaddress.ip_interface('::1%scope/64'))
self.assertTrue(ipaddress.ip_interface('::1/64') <
ipaddress.ip_interface('::1%scope/80'))
self.assertTrue(ipaddress.ip_interface('::1/64') <
ipaddress.ip_interface('::2%scope/64'))
self.assertTrue(ipaddress.ip_interface('::2/48') <
ipaddress.ip_interface('::1%scope/64'))
self.assertTrue(ipaddress.ip_interface('::1/80') >
ipaddress.ip_interface('::1%scope/64'))
self.assertTrue(ipaddress.ip_interface('::2/64') >
ipaddress.ip_interface('::1%scope/64'))
self.assertTrue(ipaddress.ip_interface('::1/64') >
ipaddress.ip_interface('::2%scope/48'))
def testNetworkComparison(self): def testNetworkComparison(self):
# ip1 and ip2 have the same network address # ip1 and ip2 have the same network address
ip1 = ipaddress.IPv4Network('1.1.1.0/24') ip1 = ipaddress.IPv4Network('1.1.1.0/24')
@ -1669,6 +2065,7 @@ class IpaddrUnitTest(unittest.TestCase):
ipaddress.ip_network('1.1.1.2')) ipaddress.ip_network('1.1.1.2'))
self.assertFalse(ipaddress.ip_network('1.1.1.2') <= self.assertFalse(ipaddress.ip_network('1.1.1.2') <=
ipaddress.ip_network('1.1.1.1')) ipaddress.ip_network('1.1.1.1'))
self.assertTrue(ipaddress.ip_network('::1') <= self.assertTrue(ipaddress.ip_network('::1') <=
ipaddress.ip_network('::1')) ipaddress.ip_network('::1'))
self.assertTrue(ipaddress.ip_network('::1') <= self.assertTrue(ipaddress.ip_network('::1') <=
@ -1679,6 +2076,7 @@ class IpaddrUnitTest(unittest.TestCase):
def testStrictNetworks(self): def testStrictNetworks(self):
self.assertRaises(ValueError, ipaddress.ip_network, '192.168.1.1/24') self.assertRaises(ValueError, ipaddress.ip_network, '192.168.1.1/24')
self.assertRaises(ValueError, ipaddress.ip_network, '::1/120') self.assertRaises(ValueError, ipaddress.ip_network, '::1/120')
self.assertRaises(ValueError, ipaddress.ip_network, '::1%scope/120')
def testOverlaps(self): def testOverlaps(self):
other = ipaddress.IPv4Network('1.2.3.0/30') other = ipaddress.IPv4Network('1.2.3.0/30')
@ -1707,13 +2105,28 @@ class IpaddrUnitTest(unittest.TestCase):
self.assertEqual(ipaddress.ip_address('FFFF::192.0.2.1'), self.assertEqual(ipaddress.ip_address('FFFF::192.0.2.1'),
ipaddress.ip_address('FFFF::c000:201')) ipaddress.ip_address('FFFF::c000:201'))
self.assertEqual(ipaddress.ip_address('::FFFF:192.0.2.1%scope'),
ipaddress.ip_address('::FFFF:c000:201%scope'))
self.assertEqual(ipaddress.ip_address('FFFF::192.0.2.1%scope'),
ipaddress.ip_address('FFFF::c000:201%scope'))
self.assertNotEqual(ipaddress.ip_address('::FFFF:192.0.2.1%scope'),
ipaddress.ip_address('::FFFF:c000:201'))
self.assertNotEqual(ipaddress.ip_address('FFFF::192.0.2.1%scope'),
ipaddress.ip_address('FFFF::c000:201'))
self.assertNotEqual(ipaddress.ip_address('::FFFF:192.0.2.1'),
ipaddress.ip_address('::FFFF:c000:201%scope'))
self.assertNotEqual(ipaddress.ip_address('FFFF::192.0.2.1'),
ipaddress.ip_address('FFFF::c000:201%scope'))
def testIPVersion(self): def testIPVersion(self):
self.assertEqual(self.ipv4_address.version, 4) self.assertEqual(self.ipv4_address.version, 4)
self.assertEqual(self.ipv6_address.version, 6) self.assertEqual(self.ipv6_address.version, 6)
self.assertEqual(self.ipv6_scoped_address.version, 6)
def testMaxPrefixLength(self): def testMaxPrefixLength(self):
self.assertEqual(self.ipv4_interface.max_prefixlen, 32) self.assertEqual(self.ipv4_interface.max_prefixlen, 32)
self.assertEqual(self.ipv6_interface.max_prefixlen, 128) self.assertEqual(self.ipv6_interface.max_prefixlen, 128)
self.assertEqual(self.ipv6_scoped_interface.max_prefixlen, 128)
def testPacked(self): def testPacked(self):
self.assertEqual(self.ipv4_address.packed, self.assertEqual(self.ipv4_address.packed,
@ -1728,6 +2141,14 @@ class IpaddrUnitTest(unittest.TestCase):
+ b'\x00' * 6) + b'\x00' * 6)
self.assertEqual(ipaddress.IPv6Interface('::1:0:0:0:0').packed, self.assertEqual(ipaddress.IPv6Interface('::1:0:0:0:0').packed,
b'\x00' * 6 + b'\x00\x01' + b'\x00' * 8) b'\x00' * 6 + b'\x00\x01' + b'\x00' * 8)
self.assertEqual(self.ipv6_scoped_address.packed,
b'\x20\x01\x06\x58\x02\x2a\xca\xfe'
b'\x02\x00\x00\x00\x00\x00\x00\x01')
self.assertEqual(ipaddress.IPv6Interface('ffff:2:3:4:ffff::%scope').packed,
b'\xff\xff\x00\x02\x00\x03\x00\x04\xff\xff'
+ b'\x00' * 6)
self.assertEqual(ipaddress.IPv6Interface('::1:0:0:0:0%scope').packed,
b'\x00' * 6 + b'\x00\x01' + b'\x00' * 8)
def testIpType(self): def testIpType(self):
ipv4net = ipaddress.ip_network('1.2.3.4') ipv4net = ipaddress.ip_network('1.2.3.4')

View file

@ -0,0 +1,2 @@
Add support for scoped IPv6 addresses to :mod:`ipaddress`. Patch by Oleksandr
Pavliuk.