fix(windows): enable dual-stack IPv6 sockets by default

On Windows, IPV6_V6ONLY defaults to true, so binding to [::] only
accepts IPv6 connections. Set it to false so that IPv6 listeners also
accept IPv4 traffic, matching the default Linux behavior.
This commit is contained in:
Romain Roffé 2026-03-27 09:58:08 +01:00
parent b9d3adfa4d
commit 314e625010
2 changed files with 16 additions and 0 deletions

View File

@ -2,6 +2,7 @@
## Unreleased
- Enable dual-stack IPv6 sockets on Windows so that binding to `[::]` also accepts IPv4 connections.
- Panic when calling `Route::to()` or `Route::service()` after `Route::wrap()` to prevent silently dropping route middleware. [#3944]
- Fix `HttpRequest::{match_pattern,match_name}` reporting path-only matches when route guards disambiguate overlapping resources. [#3346]
- Fix `Readlines` handling of lines split across payload chunks so combined line limits are enforced and complete lines are yielded.

View File

@ -445,6 +445,13 @@ where
/// Using a bind address of `0.0.0.0`, which signals to use all interfaces, may also multiple
/// the number of instantiations in a similar way.
///
/// # Dual-Stack IPv6
///
/// On Windows, binding to an IPv6 address (e.g., `[::]:8080`) automatically enables dual-stack
/// mode, allowing the socket to accept both IPv4 and IPv6 connections. On Linux and macOS,
/// dual-stack is typically already the OS default. If you need IPv6-only behavior on Windows,
/// create the listener manually and pass it to [`listen()`](Self::listen()).
///
/// # Typical Usage
///
/// In general, use `127.0.0.1:<port>` when testing locally and `0.0.0.0:<port>` when deploying
@ -1259,6 +1266,14 @@ fn create_tcp_listener(addr: net::SocketAddr, backlog: u32) -> io::Result<net::T
{
socket.set_reuse_address(true)?;
}
// On Windows, IPV6_V6ONLY defaults to true, preventing IPv6 sockets from accepting IPv4
// connections. Set it to false so that binding to [::] also accepts IPv4 traffic.
#[cfg(windows)]
if addr.is_ipv6() {
if let Err(err) = socket.set_only_v6(false) {
log::warn!("failed to set IPV6_V6ONLY=false: {err}");
}
}
socket.bind(&addr.into())?;
// clamp backlog to max u32 that fits in i32 range
let backlog = cmp::min(backlog, i32::MAX as u32) as i32;