feat(tcp): ListenerTCP binds the address it is given, at port 0 if asked
A new constructor takes a numeric bind address (127.0.0.1, ::1, 0.0.0.0, ::) and accepts port 0, and Port() reports the port the listener got. Until now every listener bound 0.0.0.0 at a port the caller chose, so a helper meant for this machine's own processes was reachable from the network, and the caller had to guess a free port. A name such as `localhost` is refused instead of bound to whichever address the resolver ranks first. The port-only constructor still listens on every IPv4 interface. `running` is atomic now: Stop() runs on another thread than the accept loop that reads it. The accept loops take a sockaddr_storage, so an IPv6 peer's address is no longer cut short. New: ShouldBindLoopbackEphemeral. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
78853033e8
commit
d6f8f4edcb
5 changed files with 235 additions and 40 deletions
|
|
@ -12,6 +12,7 @@ module;
|
|||
#include <sys/time.h>
|
||||
#include <sys/wait.h>
|
||||
#include <strings.h>
|
||||
#include <netdb.h>
|
||||
#include <cerrno>
|
||||
|
||||
module Crafter.Network:ListenerTCP_impl;
|
||||
|
|
@ -21,14 +22,30 @@ import Crafter.Thread;
|
|||
|
||||
using namespace Crafter;
|
||||
|
||||
ListenerTCP::ListenerTCP(std::uint16_t port, std::function<void(ClientTCP*)> connectCallback, std::uint32_t concurrentClientLimit, std::uint32_t totalClientLimit) : connectCallback(connectCallback), concurrentClientLimit(concurrentClientLimit), totalClientLimit(totalClientLimit) {
|
||||
sockaddr_in servAddr;
|
||||
bzero((char*)&servAddr, sizeof(servAddr));
|
||||
servAddr.sin_family = AF_INET;
|
||||
servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
servAddr.sin_port = htons(port);
|
||||
ListenerTCP::ListenerTCP(std::uint16_t port, std::function<void(ClientTCP*)> connectCallback, std::uint32_t concurrentClientLimit, std::uint32_t totalClientLimit)
|
||||
: ListenerTCP("0.0.0.0", port, std::move(connectCallback), concurrentClientLimit, totalClientLimit) {}
|
||||
|
||||
s = socket(AF_INET, SOCK_STREAM, 0);
|
||||
ListenerTCP::ListenerTCP(std::string_view bindAddress, std::uint16_t port, std::function<void(ClientTCP*)> connectCallback, std::uint32_t concurrentClientLimit, std::uint32_t totalClientLimit) : connectCallback(connectCallback), concurrentClientLimit(concurrentClientLimit), totalClientLimit(totalClientLimit) {
|
||||
// AI_NUMERICHOST: no lookup, so `localhost` is refused here rather than
|
||||
// bound to whichever of ::1 and 127.0.0.1 the resolver ranks first.
|
||||
addrinfo hints{};
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST | AI_NUMERICSERV;
|
||||
const std::string host(bindAddress);
|
||||
const std::string service = std::to_string(port);
|
||||
addrinfo* resolved = nullptr;
|
||||
const int status = getaddrinfo(host.c_str(), service.c_str(), &hints, &resolved);
|
||||
if (status != 0 || resolved == nullptr) {
|
||||
throw std::runtime_error("Could not bind " + host + ": not a numeric IPv4 or IPv6 address (" + gai_strerror(status) + ")");
|
||||
}
|
||||
sockaddr_storage servAddr {};
|
||||
std::memcpy(&servAddr, resolved->ai_addr, resolved->ai_addrlen);
|
||||
const socklen_t servAddrLength = resolved->ai_addrlen;
|
||||
const int family = resolved->ai_family;
|
||||
freeaddrinfo(resolved);
|
||||
|
||||
s = socket(family, SOCK_STREAM, 0);
|
||||
if(s < 0)
|
||||
{
|
||||
throw std::runtime_error("Error establishing the server socket");
|
||||
|
|
@ -39,7 +56,7 @@ ListenerTCP::ListenerTCP(std::uint16_t port, std::function<void(ClientTCP*)> con
|
|||
int reuse = 1;
|
||||
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
||||
|
||||
if(bind(s, (struct sockaddr*) &servAddr, sizeof(servAddr)) < 0) {
|
||||
if(bind(s, reinterpret_cast<const sockaddr*>(&servAddr), servAddrLength) < 0) {
|
||||
// Ignoring this left the listener silently accepting nothing at
|
||||
// all, with no hint as to why.
|
||||
const std::string reason = std::strerror(errno);
|
||||
|
|
@ -55,6 +72,18 @@ ListenerTCP::ListenerTCP(std::uint16_t port, std::function<void(ClientTCP*)> con
|
|||
}
|
||||
}
|
||||
|
||||
std::uint16_t ListenerTCP::Port() const {
|
||||
sockaddr_storage address {};
|
||||
socklen_t length = sizeof(address);
|
||||
if (getsockname(s, reinterpret_cast<sockaddr*>(&address), &length) == -1) {
|
||||
throw std::runtime_error(std::string("Could not read the listening port: ") + std::strerror(errno));
|
||||
}
|
||||
if (address.ss_family == AF_INET6) {
|
||||
return ntohs(reinterpret_cast<const sockaddr_in6*>(&address)->sin6_port);
|
||||
}
|
||||
return ntohs(reinterpret_cast<const sockaddr_in*>(&address)->sin_port);
|
||||
}
|
||||
|
||||
void ListenerTCP::Stop() {
|
||||
running = false;
|
||||
shutdown(s, SHUT_RDWR);
|
||||
|
|
@ -64,7 +93,7 @@ void ListenerTCP::Stop() {
|
|||
|
||||
void ListenerTCP::ListenSyncSync() {
|
||||
while (running && totalClientCounter < totalClientLimit) {
|
||||
sockaddr_in newSockAddr;
|
||||
sockaddr_storage newSockAddr;
|
||||
socklen_t newSockAddrSize = sizeof(newSockAddr);
|
||||
int client = accept(s, (sockaddr*)&newSockAddr, &newSockAddrSize);
|
||||
if (client > 0) {
|
||||
|
|
@ -81,7 +110,7 @@ void ListenerTCP::ListenSyncSync() {
|
|||
|
||||
void ListenerTCP::ListenSyncAsync() {
|
||||
while (running && totalClientCounter < totalClientLimit) {
|
||||
sockaddr_in newSockAddr;
|
||||
sockaddr_storage newSockAddr;
|
||||
socklen_t newSockAddrSize = sizeof(newSockAddr);
|
||||
int client = accept(s, (sockaddr*)&newSockAddr, &newSockAddrSize);
|
||||
if (client > 0) {
|
||||
|
|
@ -97,7 +126,7 @@ void ListenerTCP::ListenSyncAsync() {
|
|||
void ListenerTCP::ListenAsyncSync() {
|
||||
ThreadPool::Enqueue([this]() {
|
||||
while (running && totalClientCounter < totalClientLimit) {
|
||||
sockaddr_in newSockAddr;
|
||||
sockaddr_storage newSockAddr;
|
||||
socklen_t newSockAddrSize = sizeof(newSockAddr);
|
||||
int client = accept(s, (sockaddr*)&newSockAddr, &newSockAddrSize);
|
||||
if (client > 0) {
|
||||
|
|
@ -114,7 +143,7 @@ void ListenerTCP::ListenAsyncSync() {
|
|||
void ListenerTCP::ListenAsyncAsync() {
|
||||
ThreadPool::Enqueue([this]() {
|
||||
while (running && totalClientCounter < totalClientLimit) {
|
||||
sockaddr_in newSockAddr;
|
||||
sockaddr_storage newSockAddr;
|
||||
socklen_t newSockAddrSize = sizeof(newSockAddr);
|
||||
int client = accept(s, (sockaddr*)&newSockAddr, &newSockAddrSize);
|
||||
if (client > 0) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue