Fix: Support IPv4/IPv6 dual-stack listening on Windows (Backport from CPython)#43
Fix: Support IPv4/IPv6 dual-stack listening on Windows (Backport from CPython)#43coolermzb3 wants to merge 1 commit intodanvk:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR backports CPython’s Windows dual-stack listening behavior into RangeHTTPServer’s CLI entrypoint to prevent IPv4 from being unintentionally disabled when IPv6 is involved (notably on Windows).
Changes:
- Add a custom
DualStackServerthat attempts to disableIPV6_V6ONLYto enable dual-stack sockets. - Wire the custom server into
python -m RangeHTTPServerby passingServerClass=DualStackServertoSimpleHTTPServer.test(...).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| class DualStackServer(SimpleHTTPServer.ThreadingHTTPServer): | ||
| def server_bind(self): | ||
| # suppress exception when protocol is IPv4 | ||
| with contextlib.suppress(Exception): | ||
| self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) | ||
| return super().server_bind() |
There was a problem hiding this comment.
DualStackServer only tweaks IPV6_V6ONLY but doesn’t set address_family = socket.AF_INET6. As a result, if a user passes an IPv6 bind address like --bind ::, the underlying HTTPServer/ThreadingHTTPServer will still create an AF_INET socket and the bind will fail. To match the CPython dual-stack fix, set the server’s address_family appropriately (and ideally only use the dual-stack server when IPv6 is available via socket.has_dualstack_ipv6()).
|
|
||
| args = parser.parse_args() | ||
| SimpleHTTPServer.test(HandlerClass=RangeRequestHandler, port=args.port, bind=args.bind) | ||
| SimpleHTTPServer.test(HandlerClass=RangeRequestHandler, ServerClass=DualStackServer, port=args.port, bind=args.bind) |
There was a problem hiding this comment.
SimpleHTTPServer.test(..., ServerClass=DualStackServer, ...) always forces the custom server class even when dual-stack IPv6 isn’t supported on the host. Consider selecting the ServerClass conditionally (e.g., use the dual-stack variant only when socket.has_dualstack_ipv6() is true and the bind address is unspecified/IPv6), and otherwise fall back to the default server class to avoid startup failures on IPv4-only environments.
| # suppress exception when protocol is IPv4 | ||
| with contextlib.suppress(Exception): |
There was a problem hiding this comment.
contextlib.suppress(Exception) is overly broad here and can hide real bugs (e.g., programming errors or unexpected socket state). Narrow the suppression to the specific exception types expected from this setsockopt call (typically OSError / AttributeError, depending on platform) so genuine failures still surface.
| # suppress exception when protocol is IPv4 | |
| with contextlib.suppress(Exception): | |
| # suppress expected platform/socket capability errors when protocol is IPv4 | |
| with contextlib.suppress(OSError, AttributeError): |
This PR fixes an issue where the server fails to listen on IPv4 when an IPv6 address is present, particularly on Windows environments. The solution is a backport of the official fixes from the CPython upstream:
cpython#17854
cpython#17865
This is a fresh implementation that resolves conflicts in the inactive PR #25.
Tests passed on Python 3.8 and 3.12 (WSL). While the native Windows test suite encountered environment-specific CRLF mismatches, I have manually verified the functional correctness of the socket binding logic on Windows to ensure it resolves the original issue.