Commit 91dc64ba authored by Cheryl Sabella's avatar Cheryl Sabella Committed by Antoine Pitrou

bpo-20825: Containment test for ip_network in ip_network.

parent 04e36af9
...@@ -543,6 +543,28 @@ so to avoid duplication they are only documented for :class:`IPv4Network`. ...@@ -543,6 +543,28 @@ so to avoid duplication they are only documented for :class:`IPv4Network`.
>>> ip_network('192.0.2.0/24').supernet(new_prefix=20) >>> ip_network('192.0.2.0/24').supernet(new_prefix=20)
IPv4Network('192.0.0.0/20') IPv4Network('192.0.0.0/20')
.. method:: subnet_of(other)
Returns *True* if this network is a subnet of *other*.
>>> a = ip_network('192.168.1.0/24')
>>> b = ip_network('192.168.1.128/30')
>>> b.subnet_of(a)
True
.. versionadded:: 3.7
.. method:: supernet_of(other)
Returns *True* if this network is a supernet of *other*.
>>> a = ip_network('192.168.1.0/24')
>>> b = ip_network('192.168.1.128/30')
>>> a.supernet_of(b)
True
.. versionadded:: 3.7
.. method:: compare_networks(other) .. method:: compare_networks(other)
Compare this network to *other*. In this comparison only the network Compare this network to *other*. In this comparison only the network
...@@ -621,6 +643,8 @@ so to avoid duplication they are only documented for :class:`IPv4Network`. ...@@ -621,6 +643,8 @@ so to avoid duplication they are only documented for :class:`IPv4Network`.
.. method:: address_exclude(network) .. method:: address_exclude(network)
.. method:: subnets(prefixlen_diff=1, new_prefix=None) .. method:: subnets(prefixlen_diff=1, new_prefix=None)
.. method:: supernet(prefixlen_diff=1, new_prefix=None) .. method:: supernet(prefixlen_diff=1, new_prefix=None)
.. method:: subnet_of(other)
.. method:: supernet_of(other)
.. method:: compare_networks(other) .. method:: compare_networks(other)
Refer to the corresponding attribute documentation in Refer to the corresponding attribute documentation in
......
...@@ -776,8 +776,7 @@ class _BaseNetwork(_IPAddressBase): ...@@ -776,8 +776,7 @@ class _BaseNetwork(_IPAddressBase):
if not isinstance(other, _BaseNetwork): if not isinstance(other, _BaseNetwork):
raise TypeError("%s is not a network object" % other) raise TypeError("%s is not a network object" % other)
if not (other.network_address >= self.network_address and if not other.subnet_of(self):
other.broadcast_address <= self.broadcast_address):
raise ValueError('%s not contained in %s' % (other, self)) raise ValueError('%s not contained in %s' % (other, self))
if other == self: if other == self:
return return
...@@ -788,12 +787,10 @@ class _BaseNetwork(_IPAddressBase): ...@@ -788,12 +787,10 @@ class _BaseNetwork(_IPAddressBase):
s1, s2 = self.subnets() s1, s2 = self.subnets()
while s1 != other and s2 != other: while s1 != other and s2 != other:
if (other.network_address >= s1.network_address and if other.subnet_of(s1):
other.broadcast_address <= s1.broadcast_address):
yield s2 yield s2
s1, s2 = s1.subnets() s1, s2 = s1.subnets()
elif (other.network_address >= s2.network_address and elif other.subnet_of(s2):
other.broadcast_address <= s2.broadcast_address):
yield s1 yield s1
s1, s2 = s2.subnets() s1, s2 = s2.subnets()
else: else:
...@@ -975,6 +972,26 @@ class _BaseNetwork(_IPAddressBase): ...@@ -975,6 +972,26 @@ class _BaseNetwork(_IPAddressBase):
return (self.network_address.is_multicast and return (self.network_address.is_multicast and
self.broadcast_address.is_multicast) self.broadcast_address.is_multicast)
@staticmethod
def _is_subnet_of(a, b):
try:
# Always false if one is v4 and the other is v6.
if a._version != b._version:
raise TypeError(f"{a} and {b} are not of the same version")
return (b.network_address <= a.network_address and
b.broadcast_address >= a.broadcast_address)
except AttributeError:
raise TypeError(f"Unable to test subnet containment "
f"between {a} and {b}")
def subnet_of(self, other):
"""Return True if this network is a subnet of other."""
return self._is_subnet_of(self, other)
def supernet_of(self, other):
"""Return True if this network is a supernet of other."""
return self._is_subnet_of(other, self)
@property @property
def is_reserved(self): def is_reserved(self):
"""Test if the address is otherwise IETF reserved. """Test if the address is otherwise IETF reserved.
......
...@@ -92,7 +92,6 @@ class CommonTestMixin: ...@@ -92,7 +92,6 @@ class CommonTestMixin:
y = pickle.loads(pickle.dumps(x, proto)) y = pickle.loads(pickle.dumps(x, proto))
self.assertEqual(y, x) self.assertEqual(y, x)
class CommonTestMixin_v4(CommonTestMixin): class CommonTestMixin_v4(CommonTestMixin):
def test_leading_zeros(self): def test_leading_zeros(self):
...@@ -477,6 +476,56 @@ class InterfaceTestCase_v4(BaseTestCase, NetmaskTestMixin_v4): ...@@ -477,6 +476,56 @@ class InterfaceTestCase_v4(BaseTestCase, NetmaskTestMixin_v4):
class NetworkTestCase_v4(BaseTestCase, NetmaskTestMixin_v4): class NetworkTestCase_v4(BaseTestCase, NetmaskTestMixin_v4):
factory = ipaddress.IPv4Network factory = ipaddress.IPv4Network
def test_subnet_of(self):
# containee left of container
self.assertFalse(
self.factory('10.0.0.0/30').subnet_of(
self.factory('10.0.1.0/24')))
# containee inside container
self.assertTrue(
self.factory('10.0.0.0/30').subnet_of(
self.factory('10.0.0.0/24')))
# containee right of container
self.assertFalse(
self.factory('10.0.0.0/30').subnet_of(
self.factory('10.0.1.0/24')))
# containee larger than container
self.assertFalse(
self.factory('10.0.1.0/24').subnet_of(
self.factory('10.0.0.0/30')))
def test_supernet_of(self):
# containee left of container
self.assertFalse(
self.factory('10.0.0.0/30').supernet_of(
self.factory('10.0.1.0/24')))
# containee inside container
self.assertFalse(
self.factory('10.0.0.0/30').supernet_of(
self.factory('10.0.0.0/24')))
# containee right of container
self.assertFalse(
self.factory('10.0.0.0/30').supernet_of(
self.factory('10.0.1.0/24')))
# containee larger than container
self.assertTrue(
self.factory('10.0.0.0/24').supernet_of(
self.factory('10.0.0.0/30')))
def test_subnet_of_mixed_types(self):
with self.assertRaises(TypeError):
ipaddress.IPv4Network('10.0.0.0/30').supernet_of(
ipaddress.IPv6Network('::1/128'))
with self.assertRaises(TypeError):
ipaddress.IPv6Network('::1/128').supernet_of(
ipaddress.IPv4Network('10.0.0.0/30'))
with self.assertRaises(TypeError):
ipaddress.IPv4Network('10.0.0.0/30').subnet_of(
ipaddress.IPv6Network('::1/128'))
with self.assertRaises(TypeError):
ipaddress.IPv6Network('::1/128').subnet_of(
ipaddress.IPv4Network('10.0.0.0/30'))
class NetmaskTestMixin_v6(CommonTestMixin_v6): class NetmaskTestMixin_v6(CommonTestMixin_v6):
"""Input validation on interfaces and networks is very similar""" """Input validation on interfaces and networks is very similar"""
...@@ -540,6 +589,42 @@ class InterfaceTestCase_v6(BaseTestCase, NetmaskTestMixin_v6): ...@@ -540,6 +589,42 @@ class InterfaceTestCase_v6(BaseTestCase, NetmaskTestMixin_v6):
class NetworkTestCase_v6(BaseTestCase, NetmaskTestMixin_v6): class NetworkTestCase_v6(BaseTestCase, NetmaskTestMixin_v6):
factory = ipaddress.IPv6Network factory = ipaddress.IPv6Network
def test_subnet_of(self):
# containee left of container
self.assertFalse(
self.factory('2000:999::/56').subnet_of(
self.factory('2000:aaa::/48')))
# containee inside container
self.assertTrue(
self.factory('2000:aaa::/56').subnet_of(
self.factory('2000:aaa::/48')))
# containee right of container
self.assertFalse(
self.factory('2000:bbb::/56').subnet_of(
self.factory('2000:aaa::/48')))
# containee larger than container
self.assertFalse(
self.factory('2000:aaa::/48').subnet_of(
self.factory('2000:aaa::/56')))
def test_supernet_of(self):
# containee left of container
self.assertFalse(
self.factory('2000:999::/56').supernet_of(
self.factory('2000:aaa::/48')))
# containee inside container
self.assertFalse(
self.factory('2000:aaa::/56').supernet_of(
self.factory('2000:aaa::/48')))
# containee right of container
self.assertFalse(
self.factory('2000:bbb::/56').supernet_of(
self.factory('2000:aaa::/48')))
# containee larger than container
self.assertTrue(
self.factory('2000:aaa::/48').supernet_of(
self.factory('2000:aaa::/56')))
class FactoryFunctionErrors(BaseTestCase): class FactoryFunctionErrors(BaseTestCase):
......
Add `subnet_of` and `superset_of` containment tests to
:class:`ipaddress.IPv6Network` and :class:`ipaddress.IPv4Network`.
Patch by Michel Albert and Cheryl Sabella.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment