Skip to content

gh-156680: Raise the documented error from IPv6Network.next_network() - #156681

Merged
orsenthil merged 3 commits into
python:mainfrom
fedonman:fix-ipaddress-next-network-ipv6
Sep 10, 2026
Merged

gh-156680: Raise the documented error from IPv6Network.next_network()#156681
orsenthil merged 3 commits into
python:mainfrom
fedonman:fix-ipaddress-next-network-ipv6

Conversation

@fedonman

@fedonman fedonman commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Range-check next_ip against _ALL_ONES in next_network() instead of catching OverflowError, which only the IPv4 path raises. The IPv6 path went through _BaseV6._string_from_ip_int(), whose ValueError escaped uncaught.

next_network() is new in 3.16 and unreleased, so this is folded into the entry the method landed with and needs no NEWS fragment.

testNextNetworkOutOfAddressSpace (test.test_ipaddress.IpaddrUnitTest.testNextNetworkOutOfAddressSpace) ... ok
Total tests: run=1 (filtered)
Result: SUCCESS
Total tests: run=215
Result: SUCCESS

…work()

next_network() guarded address-space exhaustion with except
OverflowError, which only int.to_bytes() on the IPv4 path raises.
_BaseV6._string_from_ip_int() raises ValueError instead, so the
handler never ran for IPv6 and the internal 'IPv6 address is too
large' message escaped.

Range-check next_ip against _ALL_ONES before formatting it, which
decides the outcome for both address families before either path
runs.
@StanFromIreland

StanFromIreland commented Aug 31, 2026

Copy link
Copy Markdown
Member

Thanks! Can you also please fix the What's New entry (and news entry) for these:

image

@StanFromIreland

StanFromIreland commented Aug 31, 2026

Copy link
Copy Markdown
Member

Also, reviewing fadb785 please fix the docstring of next_network, the sentence detailing the arguments is missing a period and there's no raises section.

@StanFromIreland

StanFromIreland commented Aug 31, 2026

Copy link
Copy Markdown
Member

Oh and one more thing. I don't quite understand why we're doing this little dance with _string_from_ip_int, when we can create an instance directly.

Apologies for all the changes I've asked you to make, indeed it grew to be quite a long list. As such, here's a patch instead:

--- a/Lib/ipaddress.py
+++ b/Lib/ipaddress.py
@@ -1124,11 +1124,15 @@ def next_network(self, next_prefix=None):
 
         Args:
             next_prefix: The desired next prefix length, if not specified the
-            same self.prefixlen will be used
+            same self.prefixlen will be used.
 
         Returns:
             An IPv(4|6) Network object of the next closest network.
 
+        Raises:
+            ValueError: If next_prefix is outside the range of valid prefix
+            lengths, or if no further network of that size exists.
+
         """
         if next_prefix is None:
             next_prefix = self.prefixlen
@@ -1150,15 +1154,13 @@ def next_network(self, next_prefix=None):
             ((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:
+        if next_ip > self._ALL_ONES:
             raise ValueError(
                 f"out of address space, cannot make another /{next_prefix} "
                 "network"
-            ) from None
+            )
+
+        return self.__class__((next_ip, next_prefix))

Finish the next_network() docstring: a period on the argument sentence
and a Raises section for the two ValueError cases.

Build the result from an (address, prefix) tuple rather than formatting
and reparsing a string.

Give the What's New and NEWS entries explicit link titles so the
IPv4Network and IPv6Network methods no longer both render as
next_network().
@fedonman
fedonman requested a review from AA-Turner as a code owner September 4, 2026 14:45
@read-the-docs-community

Copy link
Copy Markdown

Documentation build overview

📚 cpython-previews | 🛠️ Build #34394344 | 📁 Comparing e9745c3 against main (1860130)

  🔍 Preview build  

2 files changed
± whatsnew/3.16.html
± whatsnew/changelog.html

@fedonman

fedonman commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

@StanFromIreland Thanks for the comments! Implemented all.

@StanFromIreland StanFromIreland left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this looks good to me. @orsenthil can you please take a look as well?

Comment thread Lib/ipaddress.py
) from None
)

return self.__class__((next_ip, next_prefix))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While technically this style can be used self.__class__((next_ip, next_prefix)) m because IPv4Network and IPv6Network do the _split_addr_prefix, we do not advertise or mention about this the value of the address argument. This styled tripped me a bit.

I would prefer the previous style.

return self.__class__(
                f"{self._string_from_ip_int(next_ip)}/{next_prefix}"
            )

Because of what we say address can be in the doc strings of the class.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize that public docs document that.
https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv4Network
https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv6Network

The change might simply be a follow up doc string patch on here.

cpython/Lib/ipaddress.py

Lines 2301 to 2319 in a6d25db

address: A string or integer representing the IPv6 network or the
IP and prefix/netmask.
'2001:db8::/128'
'2001:db8:0000:0000:0000:0000:0000:0000/128'
'2001:db8::'
are all functionally the same in IPv6. That is to say,
failing to provide a subnetmask will create an object with
a mask of /128.
Additionally, an integer can be passed, so
IPv6Network('2001:db8::') ==
IPv6Network(42540766411282592856903984951653826560)
or, more generally
IPv6Network(int(IPv6Network('2001:db8::'))) ==
IPv6Network('2001:db8::')
strict: A boolean. If true, ensure that we have been passed
A true network address, eg, 2001:db8::1000/124 and not an
IP address on a network, eg, 2001:db8::1/124.

@orsenthil orsenthil left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@orsenthil

Copy link
Copy Markdown
Member

Thanks for the patch @fedonman and review and ping @StanFromIreland .

@orsenthil
orsenthil merged commit 69a6612 into python:main Sep 10, 2026
55 checks passed
@orsenthil

Copy link
Copy Markdown
Member

@StanFromIreland - could you please review this follow up PR #157280

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants