Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions include/ylt/coro_io/listen_endpoint.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#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 {

// Resolve a string address into a TCP endpoint.
// - If `address` is a numeric IP literal (e.g. "::", "0.0.0.0", "127.0.0.1"),
// it is parsed directly via `asio::ip::make_address` to avoid the ambiguity
// of `getaddrinfo()` on different glibc versions / container environments.
// - Otherwise (e.g. "localhost"), falls back to `asio::ip::tcp::resolver`.
//
// On success `ec` is cleared and the resulting endpoint is returned.
// On failure `ec` carries the resolver error and `std::nullopt` is returned.
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();
}

// Disable IPV6_V6ONLY on a TCP acceptor so that an IPv6 socket also accepts
// IPv4-mapped connections (i.e. dual-stack listening).
//
// Only applied when the endpoint protocol is IPv6; for IPv4 endpoints this is
// a no-op. The returned `error_code` is non-zero only when the socket option
// cannot be set (e.g. on platforms that disallow toggling V6ONLY at runtime).
// Callers are expected to log a warning on failure rather than abort, since
// the listen socket can still serve IPv6 connections.
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::detail
19 changes: 10 additions & 9 deletions include/ylt/coro_io/server_acceptor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#include <string>
#include <system_error>

#include "asio/ip/address.hpp"
#include "listen_endpoint.hpp"
#include "socket_wrapper.hpp"
#include "ylt/coro_io/coro_io.hpp"
#include "ylt/coro_io/io_context_pool.hpp"
Expand Down Expand Up @@ -81,27 +81,28 @@ struct tcp_server_acceptor : public server_acceptor_base {
ELOG_INFO << "begin to listen";
using asio::ip::tcp;
asio::error_code ec;
asio::ip::tcp::resolver::query query(address_, std::to_string(port_));
asio::ip::tcp::resolver resolver(acceptor_->get_executor());
asio::ip::tcp::resolver::iterator it = resolver.resolve(query, ec);

asio::ip::tcp::resolver::iterator it_end;
if (ec || it == it_end) {
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;
}

auto endpoint = it->endpoint();
acceptor_->open(endpoint.protocol(), ec);
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
acceptor_->bind(endpoint, ec);
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);
if (ec) {
ELOG_ERROR << "bind port " << port_ << " error: " << ec.message();
acceptor_->cancel(ec);
Expand Down
22 changes: 12 additions & 10 deletions include/ylt/standalone/cinatra/coro_http_server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "ylt/coro_io/coro_file.hpp"
#include "ylt/coro_io/coro_io.hpp"
#include "ylt/coro_io/io_context_pool.hpp"
#include "ylt/coro_io/listen_endpoint.hpp"
#include "ylt/coro_io/load_balancer.hpp"

namespace cinatra {
Expand Down Expand Up @@ -736,22 +737,18 @@ class coro_http_server {
using asio::ip::tcp;
asio::error_code ec;

asio::ip::tcp::resolver::query query(address_, std::to_string(port_));
asio::ip::tcp::resolver resolver(acceptor_.get_executor());
asio::ip::tcp::resolver::iterator it = resolver.resolve(query, ec);

asio::ip::tcp::resolver::iterator it_end;
if (ec || it == it_end) {
CINATRA_LOG_ERROR << "bad address: " << address_
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);
}

auto endpoint = it->endpoint();
acceptor_.open(endpoint.protocol(), ec);
acceptor_.open(endpoint->protocol(), ec);
if (ec) {
CINATRA_LOG_ERROR << "acceptor open failed"
<< " error: " << ec.message();
Expand All @@ -760,7 +757,12 @@ class coro_http_server {
#ifdef __GNUC__
acceptor_.set_option(tcp::acceptor::reuse_address(true), ec);
#endif
acceptor_.bind(endpoint, ec);
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);
if (ec) {
CINATRA_LOG_ERROR << "bind port: " << port_ << " error: " << ec.message();
std::error_code ignore_ec;
Expand Down
Loading