commit f3e3171efc1edd5e1470f2562eb9d7b899a32ad6
parent 3ce4de773233fad0b30fa5f8cba4efd0bf8dff8f
Author: Christian Grothoff <christian@grothoff.org>
Date: Sat, 31 Oct 2026 13:10:25 +0100
support dual-stack properly
Diffstat:
3 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -2642,6 +2642,7 @@ dependencies = [
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
+ "socket2",
"sqlx",
"taler-common",
"taler-macros",
diff --git a/common/taler-api/Cargo.toml b/common/taler-api/Cargo.toml
@@ -13,6 +13,7 @@ doctest = false
[dependencies]
listenfd = "1.0.0"
+socket2 = "0.6"
dashmap = "6.1"
http-body-util.workspace = true
zlib-rs = "0.6.3"
diff --git a/common/taler-api/src/lib.rs b/common/taler-api/src/lib.rs
@@ -17,6 +17,7 @@
use std::{fs::Permissions, io::ErrorKind, net::SocketAddr, os::unix::fs::PermissionsExt as _};
use listenfd::ListenFd;
+use socket2::{Domain, Protocol, Socket, Type};
use tokio::net::{TcpListener, UnixListener};
use tracing::info;
@@ -48,7 +49,7 @@ impl Serve {
match self {
Serve::Tcp(socket_addr) => {
info!(target: "api", "Server listening on {socket_addr}");
- let listener = std::net::TcpListener::bind(socket_addr)?;
+ let listener = bind_tcp(socket_addr)?;
listener.set_nonblocking(true)?;
Ok(Listener::Tcp(TcpListener::from_std(listener)?))
}
@@ -92,6 +93,33 @@ impl Serve {
}
}
+/// Listening backlog, the same value `std::net::TcpListener::bind` uses.
+const LISTEN_BACKLOG: i32 = 128;
+
+/// Bind a listening TCP socket to `addr`.
+///
+/// Like `std::net::TcpListener::bind`, except that a socket for an IPv6
+/// address gets `IPV6_V6ONLY` cleared explicitly, so that the wildcard address
+/// `::` serves IPv4 clients as well (as v4-mapped addresses) instead of
+/// leaving that to the `net.ipv6.bindv6only` sysctl. Without this a host whose
+/// resolver returns the IPv6 address of a name first -- which every dual-stack
+/// `localhost` does -- can only reach us if the operator happened to pick the
+/// matching address family in `BIND_TO`. The C components have no such
+/// asymmetry: they resolve `BIND_TO` with `getaddrinfo()` and `AI_PASSIVE` and
+/// listen on every address it returns.
+fn bind_tcp(addr: &SocketAddr) -> Result<std::net::TcpListener, std::io::Error> {
+ let domain = Domain::for_address(*addr);
+ let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))?;
+ if domain == Domain::IPV6 {
+ socket.set_only_v6(false)?;
+ }
+ // std sets this on unix too, keep the behaviour on restart
+ socket.set_reuse_address(true)?;
+ socket.bind(&(*addr).into())?;
+ socket.listen(LISTEN_BACKLOG)?;
+ Ok(socket.into())
+}
+
enum Listener {
Tcp(TcpListener),
Unix(UnixListener),