taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

commit f3e3171efc1edd5e1470f2562eb9d7b899a32ad6
parent 3ce4de773233fad0b30fa5f8cba4efd0bf8dff8f
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat, 31 Oct 2026 13:10:25 +0100

support dual-stack properly

Diffstat:
MCargo.lock | 1+
Mcommon/taler-api/Cargo.toml | 1+
Mcommon/taler-api/src/lib.rs | 30+++++++++++++++++++++++++++++-
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),