Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion Doc/library/ipaddress.rst
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@ dictionaries.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
raise ValueError(
ValueError: next prefix must be between 1 and 32
ValueError: prefix must be between 1 and 32
>>> IPv4Network('255.255.255.0/24').next_network()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
Expand All @@ -748,6 +748,36 @@ dictionaries.

.. versionadded:: 3.16

.. method:: prev_network(prev_prefix=None)

Finds the previous closest network of prefix size *prev_prefix*. If
*prev_prefix=None*, then the current network prefix will be used.
Returns a single network object.

Raises :exc:`ValueError` if *prev_prefix* is out of range or no further
network of the requested size exists.

>>> IPv4Network('192.0.2.0/24').prev_network()
IPv4Network('192.0.1.0/24')
>>> IPv4Network('192.0.2.0/24').prev_network(prev_prefix=25)
IPv4Network('192.0.1.128/25')
>>> IPv4Network('192.0.2.0/24').prev_network(prev_prefix=23)
IPv4Network('192.0.0.0/23')
>>> IPv4Network('192.0.80.0/22').prev_network(prev_prefix=18)
IPv4Network('192.0.0.0/18')
>>> IPv4Network('192.0.80.0/22').prev_network(prev_prefix=50)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
raise ValueError(
ValueError: prefix must be between 1 and 32
>>> IPv4Network('0.0.0.0/24').prev_network()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
raise ValueError(
ValueError: out of address space, cannot make another /24 network

.. versionadded:: 3.16

.. class:: IPv6Network(address, strict=True)

Construct an IPv6 network definition. *address* can be one of the following:
Expand Down Expand Up @@ -820,6 +850,7 @@ dictionaries.
.. method:: subnet_of(other)
.. method:: supernet_of(other)
.. method:: next_network(next_prefix=None)
.. method:: prev_network(prev_prefix=None)
.. method:: compare_networks(other)

Refer to the corresponding attribute documentation in
Expand Down
3 changes: 3 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,9 @@ ipaddress
Add :meth:`~ipaddress.IPv4Network.next_network` and
:meth:`~ipaddress.IPv6Network.next_network` methods to find the next nearest
network with a specific prefix size.
Add :meth:`~ipaddress.IPv4Network.prev_network` and
:meth:`~ipaddress.IPv6Network.prev_network` methods to find the previous nearest
network with a specific prefix size.


logging
Expand Down
84 changes: 60 additions & 24 deletions Lib/ipaddress.py
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,50 @@ def is_loopback(self):
return (self.network_address.is_loopback and
self.broadcast_address.is_loopback)

def _network_by_offset(self, prefix, offset):
"""Get the network offset from this network.

Args:
prefix: Target prefix length.
offset: Number of networks to move (+1 or -1).

Returns:
An IPv(4|6) Network object.

"""
if prefix < 1 or prefix > self.max_prefixlen:
raise ValueError(
f"prefix must be between 1 and {self.max_prefixlen}"
)
new_netmask, _ = self._make_netmask(prefix)
error = f"out of address space, cannot make another /{prefix} network"

if prefix > self.prefixlen:
if offset < 0:
new_ip = self.network_address._ip - 1
else:
new_ip = self.broadcast_address._ip + 1
if new_ip < 0 or new_ip > self._ALL_ONES:
raise ValueError(error)

new_ip &= new_netmask._ip
else:
bit_shift = self.max_prefixlen - prefix
new_ip = (
((new_netmask._ip & self.network_address._ip) >> bit_shift)
+ offset
) << bit_shift

if new_ip < 0:
raise ValueError(error)

try:
return self.__class__(
f"{self._string_from_ip_int(new_ip)}/{prefix}"
)
except OverflowError:
raise ValueError(error) from None

def next_network(self, next_prefix=None):
"""Get the next closest network with a specific prefix.

Expand All @@ -1132,33 +1176,25 @@ def next_network(self, next_prefix=None):
"""
if next_prefix is None:
next_prefix = self.prefixlen
new_netmask = self.netmask
else:
if next_prefix < 1 or next_prefix > self.max_prefixlen:
raise ValueError(
f"next prefix must be between 1 and {self.max_prefixlen}"
)
new_netmask, _ = self._make_netmask(next_prefix)

bit_shift = (
self.max_prefixlen - next_prefix
if next_prefix <= self.prefixlen
else self.max_prefixlen - self.prefixlen
)
return self._network_by_offset(next_prefix, 1)

next_ip = (
((new_netmask._ip & self.network_address._ip) >> bit_shift) + 1
) << bit_shift

try:
return self.__class__(
f"{self._string_from_ip_int(next_ip)}/{next_prefix}"
)
except OverflowError:
raise ValueError(
f"out of address space, cannot make another /{next_prefix} "
"network"
) from None
def prev_network(self, prev_prefix=None):
"""Get the previous closest network with a specific prefix.

Args:
prev_prefix: The desired previous prefix length, if not specified the
same self.prefixlen will be used

Returns:
An IPv(4|6) Network object of the previous closest network.

"""
if prev_prefix is None:
prev_prefix = self.prefixlen

return self._network_by_offset(prev_prefix, -1)


class _BaseConstants:
Expand Down
49 changes: 49 additions & 0 deletions Lib/test/test_ipaddress.py
Original file line number Diff line number Diff line change
Expand Up @@ -1597,8 +1597,57 @@ def testNextNetworkWithBadPrefix(self):
def testNextNetworkOutOfAddressSpace(self):
ipv4 = ipaddress.IPv4Network('255.255.255.0/24')
self.assertRaises(ValueError, ipv4.next_network)
self.assertRaises(ValueError, ipv4.next_network, 25)
ipv6 = ipaddress.IPv6Network('ffff:ffff:ffff:ffff:ffff:ffff:ffff:0/112')
self.assertRaises(ValueError, ipv6.next_network)
self.assertRaises(ValueError, ipv4.next_network, 112)

def testPrevNetwork(self):
ipv4 = ipaddress.IPv4Network('1.2.3.0/24')
self.assertEqual(
ipv4.prev_network(),
ipaddress.IPv4Network('1.2.2.0/24'),
)
self.assertEqual(
ipv4.prev_network(prev_prefix=16),
ipaddress.IPv4Network('1.1.0.0/16'),
)
self.assertEqual(
ipv4.prev_network(prev_prefix=25),
ipaddress.IPv4Network('1.2.2.128/25'),
)
self.assertEqual(
ipv4.prev_network(prev_prefix=23),
ipaddress.IPv4Network('1.2.0.0/23'),
)

ipv6 = ipaddress.IPv6Network('2001:dbb8:aaaa:aaaa::/64')
self.assertEqual(
ipv6.prev_network(),
ipaddress.IPv6Network('2001:dbb8:aaaa:aaa9::/64'),
)
self.assertEqual(
ipv6.prev_network(prev_prefix=48),
ipaddress.IPv6Network('2001:dbb8:aaa9::/48'),
)
self.assertEqual(
ipv6.prev_network(prev_prefix=88),
ipaddress.IPv6Network('2001:dbb8:aaaa:aaa9:ffff:ff00::/88'),
)

def testPrevNetworkWithBadPrefix(self):
self.assertRaises(ValueError, self.ipv4_network.prev_network, 0)
self.assertRaises(ValueError, self.ipv4_network.prev_network, 35)
self.assertRaises(ValueError, self.ipv6_network.prev_network, 0)
self.assertRaises(ValueError, self.ipv6_network.prev_network, 150)

def testPrevNetworkOutOfAddressSpace(self):
ipv4 = ipaddress.IPv4Network('0.0.0.0/24')
self.assertRaises(ValueError, ipv4.prev_network)
self.assertRaises(ValueError, ipv4.prev_network, 25)
ipv6 = ipaddress.IPv6Network('::/112')
self.assertRaises(ValueError, ipv6.prev_network)
self.assertRaises(ValueError, ipv6.prev_network, 112)

def testFancySubnetting(self):
self.assertEqual(sorted(self.ipv4_network.subnets(prefixlen_diff=3)),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
Add :meth:`~ipaddress.IPv4Network.next_network` and
:meth:`~ipaddress.IPv6Network.next_network`. Patch by Faisal Mahmood.
Add :meth:`~ipaddress.IPv4Network.next_network`,
:meth:`~ipaddress.IPv6Network.next_network`,
:meth:`~ipaddress.IPv4Network.prev_network` and
:meth:`~ipaddress.IPv6Network.prev_network`. Patch by Faisal Mahmood.
Loading