IPv6 Scoped Addresses (RFC 4007)

Issue

ipaddress module has no support for scoped IPv6 addresses which prevents the use of ipaddress.ip_address() and ipaddress.IPv6Address() with (always available by default on IPv6 systems) RFC conforming IPv6 link local addresses that specify interface scope.

https://tools.ietf.org/html/rfc4007

This is bad because interface scope is required for connect() and bind() operations on multihomed machines, and virtualized or software defined networking will make this case very common.

eg.

>>> ipaddress.IPv6Address('fe80::dead:dead:beef:ffff')
IPv6Address('fe80::dead:dead:beef:ffff')
>>> ipaddress.IPv6Address('fe80::dead:dead:beef:ffff%eth0')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/ipaddress.py", line 1900, in __init__
    self._ip = self._ip_int_from_string(addr_str)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/ipaddress.py", line 1716, in _ip_int_from_string
    raise AddressValueError("%s in %r" % (exc, ip_str)) from None
ipaddress.AddressValueError: Only hex digits permitted in 'ffff%eth0' in 'fe80::dead:dead:beef:ffff%eth0'
>>> ipaddress.IPv6Interface('fe80::dead:dead:beef:ffff%eth0')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/ipaddress.py", line 2060, in __init__
    IPv6Address.__init__(self, addr[0])
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/ipaddress.py", line 1900, in __init__
    self._ip = self._ip_int_from_string(addr_str)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/ipaddress.py", line 1716, in _ip_int_from_string
    raise AddressValueError("%s in %r" % (exc, ip_str)) from None
ipaddress.AddressValueError: Only hex digits permitted in 'ffff%eth0' in 'fe80::dead:dead:beef:ffff%eth0'

Description

Another change introduced in Python 3.9 is ability to specify scope of IPv6 addresses.

In case you are not familiar with IPv6 scopes, they are used to specify in which part of the internet is the respective IP address valid.

Scope can be specified at the end of IP address using % sign for example: 3FFE:0:0:1:200:F8FF:FE75:50DF%2, so this IP address is in scope 2 which is link-local address.

So, in case you need to deal with IPv6 addresses in Python, you can now do so like this:

from ipaddress import IPv6Address
addr = IPv6Address('ff02::fa51%1')
print(addr.scope_id)
# "1" - interface-local IP address

There is one thing you should be careful with when using IPv6 scopes though.

Two addresses with different scopes are not equal when compared using basic Python operators.