From 20cade1050a6d464de4158641719d5555751b084 Mon Sep 17 00:00:00 2001 From: David Yu Date: Thu, 24 Sep 2026 22:57:35 -0700 Subject: [PATCH] net: listen on an unspecified address binds any family, not just IPv4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A default-constructed socket_address has been AF_UNSPEC since 453d531b0, but posix_network_stack::listen still turned it into 0.0.0.0, so listen(socket_address{}) — "any address, any port" — was unreachable on an IPv6-only host, with no way to ask for any family. Bind [::] instead where the stack has IPv6, with IPV6_V6ONLY off so IPv4 peers still arrive, and keep 0.0.0.0 on a host without IPv6. A caller who wants exactly one family passes that family's any-address (inet_address(family), ipv4_addr(port), ipv6_addr(port)) or sets listen_options::ipv6_only, which is kept here if it was set explicitly. Tested by listening on socket_address{} and connecting from 127.0.0.1: the listener is AF_INET6 and on the any-address, and the peer reads as IPv4 after unmapping. --- src/net/posix-stack.cc | 22 ++++++++++++++++++---- tests/unit/ipv6_test.cc | 19 +++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/net/posix-stack.cc b/src/net/posix-stack.cc index bb7302bf109..307b8709c9f 100644 --- a/src/net/posix-stack.cc +++ b/src/net/posix-stack.cc @@ -914,9 +914,16 @@ posix_network_stack::posix_network_stack(const program_options::option_group& op server_socket posix_network_stack::listen(socket_address sa, listen_options opt) { using server_socket = seastar::server_socket; - // allow unspecified bind address -> default to ipv4 wildcard + // An unspecified address listens on both families where the host has + // IPv6 ([::] with IPV6_V6ONLY off), and on 0.0.0.0 where it does not. A + // listen_options::ipv6_only the caller set is kept. if (sa.is_unspecified()) { - sa = inet_address(inet_address::family::INET); + if (supports_ipv6()) { + sa = inet_address(inet_address::family::INET6); + opt.ipv6_only = opt.ipv6_only.value_or(false); + } else { + sa = inet_address(inet_address::family::INET); + } } if (sa.is_af_unix()) { return server_socket(std::make_unique(0, sa, internal::posix_listen(sa, opt), opt.lba, opt.fixed_cpu, opt.proxy_protocol, _allocator)); @@ -940,9 +947,16 @@ posix_ap_network_stack::posix_ap_network_stack(const program_options::option_gro server_socket posix_ap_network_stack::listen(socket_address sa, listen_options opt) { using server_socket = seastar::server_socket; - // allow unspecified bind address -> default to ipv4 wildcard + // An unspecified address listens on both families where the host has + // IPv6 ([::] with IPV6_V6ONLY off), and on 0.0.0.0 where it does not. A + // listen_options::ipv6_only the caller set is kept. if (sa.is_unspecified()) { - sa = inet_address(inet_address::family::INET); + if (supports_ipv6()) { + sa = inet_address(inet_address::family::INET6); + opt.ipv6_only = opt.ipv6_only.value_or(false); + } else { + sa = inet_address(inet_address::family::INET); + } } if (sa.is_af_unix()) { return server_socket(std::make_unique(0, sa, _allocator)); diff --git a/tests/unit/ipv6_test.cc b/tests/unit/ipv6_test.cc index f23c791f802..f38967d8657 100644 --- a/tests/unit/ipv6_test.cc +++ b/tests/unit/ipv6_test.cc @@ -161,6 +161,25 @@ SEASTAR_TEST_CASE(dual_stack_listen_test) { } } +// listen() with no address at all listens for both families where it can, +// instead of quietly picking IPv4 and being unreachable on an IPv6-only host. +SEASTAR_TEST_CASE(listen_unspecified_address_test) { + if (!check_ipv6_support()) { + co_return; + } + auto ss = server_socket(engine().net().listen(socket_address(), {})); + BOOST_REQUIRE_EQUAL(ss.local_address().family(), AF_INET6); + BOOST_REQUIRE(ss.local_address().addr().is_addr_any()); + + auto accepted = ss.accept(); + auto cs = co_await connect(ipv4_addr("127.0.0.1", ss.local_address().port())); + auto ar = co_await std::move(accepted); + // an IPv4 peer arrives, and (per the v4-mapped normalisation) reads as IPv4 + BOOST_REQUIRE_EQUAL(ar.remote_address.unmapped().addr(), net::inet_address("127.0.0.1")); + cs.shutdown_output(); + ar.connection.shutdown_output(); +} + // ipv6_addr compares and hashes like ipv4_addr. SEASTAR_TEST_CASE(ipv6_addr_value_type_test) { const ipv6_addr a("2001:db8::1", 9092);