fix: enable dual-stack IPv4/IPv6 listening on Linux#1186
Conversation
When binding to an IPv6 address (e.g. "::"), explicitly set IPV6_V6ONLY=false so the socket also accepts IPv4 connections. This fixes the issue where Linux systems with net.ipv6.bindv6only=1 would reject IPv4-mapped connections. Additionally, for IP literal addresses, use asio::ip::make_address() directly instead of going through the resolver, which avoids ambiguous results from getaddrinfo() on different glibc versions. Falls back to resolver for hostname addresses (e.g. "localhost"). Fixes alibaba#1185 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6e7e886 to
d47ab9c
Compare
|
for detail, goto summary download Artifacts |
|
Hi @qicosmos, thanks for maintaining this great project! I've addressed the CLA issue and all CI checks are passing now. Would you have a chance to review this PR when convenient? No rush at all — just wanted to make sure it's on your radar. Happy to make any changes if needed. Thanks! |
Thanks for your PR, i will review it later. |
|
建议新增文件,消除重复代码: // include/ylt/coro_io/listen_endpoint.hpp
#pragma once
#include <cstdint>
#include <optional>
#include <string>
#include "asio/error_code.hpp"
#include "asio/ip/address.hpp"
#include "asio/ip/tcp.hpp"
#include "asio/ip/v6_only.hpp"
namespace coro_io::detail {
template <typename Executor>
inline std::optional<asio::ip::tcp::endpoint>
resolve_listen_endpoint(
const Executor& executor, const std::string& address, uint16_t port,
asio::error_code& ec) {
using asio::ip::tcp;
auto addr = asio::ip::make_address(address, ec);
if (!ec) {
return tcp::endpoint(addr, port);
}
ec.clear();
tcp::resolver resolver(executor);
auto it = resolver.resolve(
tcp::resolver::query(address, std::to_string(port)), ec);
if (ec || it == tcp::resolver::iterator{}) {
return std::nullopt;
}
return it->endpoint();
}
inline asio::error_code set_ipv6_only_false(
asio::ip::tcp::acceptor& acceptor,
const asio::ip::tcp::endpoint& endpoint) {
asio::error_code ec;
if (endpoint.protocol() == asio::ip::tcp::v6()) {
acceptor.set_option(asio::ip::v6_only(false), ec);
}
return ec;
}
} // namespace coro_io::detailserver_acceptor.hpp 里: #include "listen_endpoint.hpp"
//然后 listen 里替换成:
auto endpoint = detail::resolve_listen_endpoint(
acceptor_->get_executor(), address_, port_, ec);
if (!endpoint) {
ELOG_ERROR << "resolve address " << address_
<< " error: " << ec.message();
return listen_errc::bad_address;
}
acceptor_->open(endpoint->protocol(), ec);
if (ec) {
ELOG_ERROR << "open failed, error: " << ec.message();
return listen_errc::open_error;
}
#ifdef __GNUC__
acceptor_->set_option(tcp::acceptor::reuse_address(true), ec);
#endif
if (auto opt_ec =
detail::set_ipv6_only_false(*acceptor_, *endpoint); opt_ec) {
ELOG_WARN << "set v6_only(false) failed: " << opt_ec.message();
}
acceptor_->bind(*endpoint, ec);coro_http_server.hpp 里: #include "ylt/coro_io/listen_endpoint.hpp"
// 然后 listen 里替换成:
auto endpoint =
coro_io::detail::resolve_listen_endpoint(
acceptor_.get_executor(), address_, port_, ec);
if (!endpoint) {
CINATRA_LOG_ERROR << "invalid address: " << address_
<< " error: " << ec.message();
if (ec) {
return ec;
}
return std::make_error_code(std::errc::address_not_available);
}
acceptor_.open(endpoint->protocol(), ec);
if (ec) {
CINATRA_LOG_ERROR << "acceptor open failed"
<< " error: " << ec.message();
return ec;
}
#ifdef __GNUC__
acceptor_.set_option(tcp::acceptor::reuse_address(true), ec);
#endif
if (auto opt_ec =
coro_io::detail::set_ipv6_only_false(acceptor_, *endpoint);
opt_ec) {
CINATRA_LOG_WARNING << "set v6_only(false) failed:" << opt_ec.message();
}
acceptor_.bind(*endpoint, ec);同时可以删掉两个原文件里直接引入的 asio/ip/ v6_only.hpp 和 asio/ip/address.hpp,因为都在listen_endpoint.hpp 了。这样解析 endpoint 和设置v6_only(false) 都集中,错误日志和返回策略仍然留在各自模块里。 |
|
OK,谢谢你的提醒,这是我的疏忽,我现在做出相应的修改 |
Move resolve_listen_endpoint() and set_ipv6_only_false() into a dedicated header so that server_acceptor.hpp and coro_http_server.hpp reuse the same implementation instead of duplicating it.
|
Done. I extracted resolve_listen_endpoint() and set_ipv6_only_false() into include/ylt/coro_io/listen_endpoint.hpp, and both server_acceptor.hpp and coro_http_server.hpp now call into it instead of duplicating the logic. Tested locally on MSVC -- all existing test suites pass without regression. |
|
for detail, goto summary download Artifacts |
|
CI workflows are waiting for approval to run. Could you approve them when you get a chance? Thanks.@qicosmos |
done |
|
for detail, goto summary download Artifacts |
|
for detail, goto summary download Artifacts |
|
All checks should be green once CI is approved. Ready to merge from my side — let me know if anything else needs to be changed. |
Summary
Fixes #1185
Linux下绑定IPv6地址(如
::)时,由于两个原因导致IPv4客户端连不上:getaddrinfo解析字面量IP在不同glibc版本/容器环境下结果不稳定(AI_ADDRCONFIG标志的影响)IPV6_V6ONLY=false,在net.ipv6.bindv6only=1的环境下IPv6 socket拒绝IPv4连接Changes
include/ylt/coro_io/listen_endpoint.hpp,提取公共的地址解析和dual-stack设置逻辑resolve_listen_endpoint():字面量IP用make_address直接解析,hostname才走resolverset_ipv6_only_false():IPv6 endpoint时关闭v6_only,失败只warn不abortserver_acceptor.hpp和coro_http_server.hpp改为调用上述公共函数,消除重复代码Testing
Windows MSVC 14.50 本地全量回归: