From 2984c0a40a706ffadd6f22a7686f3f4f056e7f51 Mon Sep 17 00:00:00 2001 From: Damien Claisse Date: Tue, 23 Jan 2018 15:34:22 +0100 Subject: Handle RFC 6164 IPv6 addresses 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 --- netaddr/ip/__init__.py | 46 ++++++++++++++++++++++-------------------- netaddr/tests/ip/test_ip_v4.py | 6 ------ netaddr/tests/ip/test_ip_v6.py | 19 +++++++++++++++++ 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::')] -- cgit v1.2.1