summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDamien Claisse <d.claisse@criteo.com>2018-01-23 15:34:22 +0100
committerJakub Stasiak <jakub@stasiak.at>2020-06-21 02:12:59 +0200
commit2984c0a40a706ffadd6f22a7686f3f4f056e7f51 (patch)
tree71721ec0e89ddd54e80d124c12ab7c68be2525b3
parent94b7fb677516255874f8a70b10bedca70c66a01d (diff)
downloadnetaddr-pick-191.tar.gz
Handle RFC 6164 IPv6 addressespick-191
Like RFC 3021, IPv6 defines point-to-point subnets that must be handled separately, i.e. don't reserve first IP address. This patch aims to implement this, while refactoring code in iter_host function to reduce code duplication. Tests for this feature also added to ensure there is no regression. Signed-off-by: Damien Claisse <d.claisse@criteo.com>
-rw-r--r--netaddr/ip/__init__.py46
-rw-r--r--netaddr/tests/ip/test_ip_v4.py6
-rw-r--r--netaddr/tests/ip/test_ip_v6.py19
3 files changed, 43 insertions, 28 deletions
diff --git a/netaddr/ip/__init__.py b/netaddr/ip/__init__.py
index b61717b..edcbaf1 100644
--- a/netaddr/ip/__init__.py
+++ b/netaddr/ip/__init__.py
@@ -1004,7 +1004,7 @@ class IPNetwork(BaseIP, IPListMixin):
@property
def broadcast(self):
"""The broadcast address of this `IPNetwork` object"""
- if self._module.version == 4 and (self._module.width - self._prefixlen) <= 1:
+ if (self._module.width - self._prefixlen) <= 1:
return None
else:
return IPAddress(self._value | self._hostmask_int, self._module.version)
@@ -1306,36 +1306,38 @@ class IPNetwork(BaseIP, IPListMixin):
A generator that provides all the IP addresses that can be assigned
to hosts within the range of this IP object's subnet.
- - for IPv4, the network and broadcast addresses are always excluded. \
- for subnets that contains less than 4 IP addresses /31 and /32 \
- report in a manner per RFC 3021
+ - for IPv4, the network and broadcast addresses are excluded, excepted \
+ when using /31 or /32 subnets as per RFC 3021.
- - for IPv6, only the unspecified address '::' or Subnet-Router anycast \
- address (first address in the network) is excluded.
+ - for IPv6, only Subnet-Router anycast address (first address in the \
+ network) is excluded as per RFC 4291 section 2.6.1, excepted when using \
+ /127 or /128 subnets as per RFC 6164.
:return: an IPAddress iterator
"""
it_hosts = iter([])
+ # Common logic, first IP is always reserved.
+ first_usable_address = self.first + 1
if self._module.version == 4:
- # IPv4 logic.
- if self.size >= 4:
- it_hosts = iter_iprange(
- IPAddress(self.first + 1, self._module.version),
- IPAddress(self.last - 1, self._module.version))
- else:
- it_hosts = iter_iprange(
- IPAddress(self.first, self._module.version),
- IPAddress(self.last, self._module.version))
+ # IPv4 logic, last address is reserved for broadcast.
+ last_usable_address = self.last - 1
+ else:
+ # IPv6 logic, no broadcast address reserved.
+ last_usable_address = self.last
+
+ # If subnet has a size of less than 4, then it is a /31, /32, /127 or /128.
+ # Handle them as per RFC 3021 (IPv4) or RFC 6164 (IPv6), and don't reserve
+ # first or last IP address.
+ if self.size >= 4:
+ it_hosts = iter_iprange(
+ IPAddress(first_usable_address, self._module.version),
+ IPAddress(last_usable_address, self._module.version))
else:
- # IPv6 logic.
- # RFC 4291 section 2.6.1 says that the first IP in the network is
- # the Subnet-Router anycast address. This address cannot be
- # assigned to a host, so use self.first+1.
- if self.size >= 2:
- it_hosts = iter_iprange(
- IPAddress(self.first + 1, self._module.version),
+ it_hosts = iter_iprange(
+ IPAddress(self.first, self._module.version),
IPAddress(self.last, self._module.version))
+
return it_hosts
def __str__(self):
diff --git a/netaddr/tests/ip/test_ip_v4.py b/netaddr/tests/ip/test_ip_v4.py
index f6d774e..46e22b3 100644
--- a/netaddr/tests/ip/test_ip_v4.py
+++ b/netaddr/tests/ip/test_ip_v4.py
@@ -260,8 +260,6 @@ def test_iterhosts_v4():
IPAddress('192.168.0.1'),
]
- assert list(IPNetwork("1234::/128")) == [IPAddress('1234::')]
- assert list(IPNetwork("1234::/128").iter_hosts()) == []
assert list(IPNetwork("192.168.0.0/31").iter_hosts()) == [IPAddress('192.168.0.0'),IPAddress('192.168.0.1')]
assert list(IPNetwork("192.168.0.0/32").iter_hosts()) == [IPAddress('192.168.0.0')]
@@ -509,10 +507,6 @@ def test_rfc3021_subnets():
assert IPNetwork('192.0.2.0/32').broadcast is None
assert list(IPNetwork('192.0.2.0/32').iter_hosts()) == [IPAddress('192.0.2.0')]
- # IPv6 must not be affected
- assert IPNetwork('abcd::/127').broadcast is not None
- assert IPNetwork('abcd::/128').broadcast is not None
-
def test_ipnetwork_change_prefixlen():
ip = IPNetwork('192.168.0.0/16')
diff --git a/netaddr/tests/ip/test_ip_v6.py b/netaddr/tests/ip/test_ip_v6.py
index aa9c5c1..5b5486a 100644
--- a/netaddr/tests/ip/test_ip_v6.py
+++ b/netaddr/tests/ip/test_ip_v6.py
@@ -141,3 +141,22 @@ def test_ipv6_unicast_address_allocation_info():
assert ip.info.IPv6_unicast[0].description == 'LACNIC'
assert ip.info.IPv6_unicast[0].whois == 'whois.lacnic.net'
assert ip.info.IPv6_unicast[0].status == 'ALLOCATED'
+
+def test_rfc6164_subnets():
+ # Tests for /127 subnet
+ assert list(IPNetwork('1234::/127')) == [
+ IPAddress('1234::'),
+ IPAddress('1234::1'),
+ ]
+ assert list(IPNetwork('1234::/127').iter_hosts()) == [
+ IPAddress('1234::'),
+ IPAddress('1234::1'),
+ ]
+ assert IPNetwork('1234::/127').network == IPAddress('1234::')
+ assert IPNetwork('1234::').broadcast is None
+
+ # Tests for /128 subnet
+ assert IPNetwork("1234::/128").network == IPAddress('1234::')
+ assert IPNetwork("1234::/128").broadcast is None
+ assert list(IPNetwork("1234::/128")) == [IPAddress('1234::')]
+ assert list(IPNetwork("1234::/128").iter_hosts()) == [IPAddress('1234::')]