mirror of https://github.com/fafhrd91/actix-web
fix(windows): enable dual-stack IPv6 sockets by default (#3988)
* 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. * chore: add test --------- Co-authored-by: Romain Roffé <r2@kyber.media> Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
This commit is contained in:
parent
25c5b5c4cf
commit
4fcde646b9
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Enable dual-stack IPv6 sockets on Windows when possible so that Actix-created listeners bound to `[::]` also accept IPv4 connections.
|
||||||
- Panic when calling `Route::to()` or `Route::service()` after `Route::wrap()` to prevent silently dropping route middleware. [#3944]
|
- 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 `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.
|
- Fix `Readlines` handling of lines split across payload chunks so combined line limits are enforced and complete lines are yielded.
|
||||||
|
|
|
||||||
|
|
@ -445,6 +445,14 @@ where
|
||||||
/// Using a bind address of `0.0.0.0`, which signals to use all interfaces, may also multiple
|
/// 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.
|
/// the number of instantiations in a similar way.
|
||||||
///
|
///
|
||||||
|
/// # Dual-Stack IPv6
|
||||||
|
///
|
||||||
|
/// On Windows, when this method creates an IPv6 listener (e.g., for `[::]:8080`), this
|
||||||
|
/// attempts to enable dual-stack mode so the socket can 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
|
/// # Typical Usage
|
||||||
///
|
///
|
||||||
/// In general, use `127.0.0.1:<port>` when testing locally and `0.0.0.0:<port>` when deploying
|
/// In general, use `127.0.0.1:<port>` when testing locally and `0.0.0.0:<port>` when deploying
|
||||||
|
|
@ -1259,6 +1267,14 @@ fn create_tcp_listener(addr: net::SocketAddr, backlog: u32) -> io::Result<net::T
|
||||||
{
|
{
|
||||||
socket.set_reuse_address(true)?;
|
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())?;
|
socket.bind(&addr.into())?;
|
||||||
// clamp backlog to max u32 that fits in i32 range
|
// clamp backlog to max u32 that fits in i32 range
|
||||||
let backlog = cmp::min(backlog, i32::MAX as u32) as i32;
|
let backlog = cmp::min(backlog, i32::MAX as u32) as i32;
|
||||||
|
|
|
||||||
|
|
@ -217,3 +217,48 @@ async fn test_tcp_nodelay_enabled() {
|
||||||
async fn test_tcp_nodelay_disabled() {
|
async fn test_tcp_nodelay_disabled() {
|
||||||
assert_tcp_nodelay_config(false).await;
|
assert_tcp_nodelay_config(false).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
#[cfg(windows)]
|
||||||
|
async fn test_dual_stack_ipv6_on_windows() {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
actix_rt::System::new()
|
||||||
|
.block_on(async {
|
||||||
|
let srv = HttpServer::new(|| {
|
||||||
|
App::new().service(
|
||||||
|
web::resource("/")
|
||||||
|
.route(web::to(|| async { HttpResponse::Ok().body("test") })),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.workers(1)
|
||||||
|
.disable_signals()
|
||||||
|
.bind("[::]:0")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let port = srv.addrs()[0].port();
|
||||||
|
let srv = srv.run();
|
||||||
|
|
||||||
|
tx.send((srv.handle(), port)).unwrap();
|
||||||
|
srv.await
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
let (srv, port) = rx.recv().unwrap();
|
||||||
|
|
||||||
|
let client = awc::Client::builder()
|
||||||
|
.connector(awc::Connector::new().timeout(Duration::from_secs(1)))
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.get(format!("http://127.0.0.1:{port}"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
srv.stop(false).await;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue