#3 - DNS support + IPv6

This commit is contained in:
Martin Slachta
2026-08-05 17:14:04 +02:00
parent 0e639abcd2
commit b71f740b9a
13 changed files with 291 additions and 92 deletions
@@ -1,5 +1,7 @@
#pragma once
#include "io/HostResolver.hpp"
#include <arpa/inet.h>
#include <spdlog/spdlog.h>
#include <string>
@@ -7,6 +9,7 @@
#include <optional>
#include <sys/socket.h>
#include <format>
#include <tl/expected.hpp>
namespace tw::net::quicr {
@@ -38,6 +41,24 @@ public:
: m_storage(storage)
{ }
/**
* Look a host up, accepting a name where the constructor above takes only
* an address literal.
*
* `family` should be the family of the endpoint socket the address will be
* sent from, so that a dual-stack socket is handed a mapped address rather
* than a bare IPv4 one.
*/
static tl::expected<QuicrAddress, ResolutionError>
resolve(const std::string& host, int port, sa_family_t family = AF_UNSPEC) {
auto storage_r = resolve_host(host, port, family);
if(!storage_r) {
return tl::make_unexpected(storage_r.error());
}
return QuicrAddress(std::move(storage_r.value()));
}
/** Return a const pointer suitable for connect / sendto / bind. */
const struct sockaddr* sockaddr() const {
return reinterpret_cast<const struct sockaddr*>(&m_storage);
@@ -18,6 +18,7 @@ class QuicrConnectionListener;
class QuicrEndpoint {
int32_t m_socket_fd;
sa_family_t m_family;
std::unordered_map<uint64_t, std::shared_ptr<QuicrConnection>> m_connections;
std::vector<std::byte> m_inbound_buffer;
@@ -26,7 +27,7 @@ class QuicrEndpoint {
void process_datagram(std::span<std::byte> datagram, QuicrAddress from);
QuicrEndpoint(int socket_fd);
QuicrEndpoint(int socket_fd, sa_family_t family);
public:
QuicrEndpoint(const QuicrEndpoint&) = delete;
@@ -47,6 +48,12 @@ public:
return result;
}
/**
* The family the socket was opened with. Addresses have to be resolved
* into it before they can be sent to.
*/
sa_family_t family() const { return m_family; }
static tl::expected<std::unique_ptr<QuicrEndpoint>, QuicrError> create();
/**
+38 -10
View File
@@ -10,8 +10,8 @@
namespace tw::net::quicr {
QuicrEndpoint::QuicrEndpoint(int socket_fd)
: m_inbound_buffer(64 * 1024), m_socket_fd(socket_fd),
QuicrEndpoint::QuicrEndpoint(int socket_fd, sa_family_t family)
: m_inbound_buffer(64 * 1024), m_socket_fd(socket_fd), m_family(family),
m_new_connection_handler(nullptr) {
}
@@ -31,29 +31,57 @@ tl::expected<std::unique_ptr<QuicrEndpoint>, QuicrError> QuicrEndpoint::create_a
}
tl::expected<std::unique_ptr<QuicrEndpoint>, QuicrError> QuicrEndpoint::create() {
const int domain = AF_INET;
// An IPv6 socket with IPV6_V6ONLY cleared also carries IPv4 peers, which
// arrive as ::ffff: mapped addresses. A host with IPv6 switched off answers
// EAFNOSUPPORT instead, and there the endpoint stays IPv4 as it was.
sa_family_t domain = AF_INET6;
int socket_fd = socket(domain, SOCK_DGRAM, IPPROTO_UDP);
if(socket_fd < 0 && errno == EAFNOSUPPORT) {
domain = AF_INET;
socket_fd = socket(domain, SOCK_DGRAM, IPPROTO_UDP);
}
if(socket_fd < 0) {
spdlog::error("Failed to create socket: {}", strerror(errno));
return tl::make_unexpected(QuicrError::from_errno(errno));
}
if(domain == AF_INET6) {
const int v6_only = 0;
if(setsockopt(socket_fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6_only, sizeof(v6_only)) < 0) {
spdlog::error("Failed to accept IPv4 peers on the socket: {}", strerror(errno));
::close(socket_fd);
return tl::make_unexpected(QuicrError::from_errno(errno));
}
}
if(fcntl(socket_fd, F_SETFL, fcntl(socket_fd, F_GETFL, 0) | O_NONBLOCK, 1) == -1) {
spdlog::error("Failed to set non-blocking mode: {}", strerror(errno));
return tl::make_unexpected(QuicrError::from_errno(errno));
}
return std::unique_ptr<QuicrEndpoint>(new QuicrEndpoint(socket_fd));
return std::unique_ptr<QuicrEndpoint>(new QuicrEndpoint(socket_fd, domain));
}
tl::expected<void, QuicrError> QuicrEndpoint::bind(int port) {
const int domain = AF_INET;
struct sockaddr_in addr = {};
addr.sin_family = domain;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
sockaddr_storage storage = {};
socklen_t length;
if(::bind(m_socket_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
if(m_family == AF_INET6) {
auto& addr = reinterpret_cast<sockaddr_in6&>(storage);
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(port);
addr.sin6_addr = in6addr_any;
length = sizeof(sockaddr_in6);
} else {
auto& addr = reinterpret_cast<sockaddr_in&>(storage);
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
length = sizeof(sockaddr_in);
}
if(::bind(m_socket_fd, reinterpret_cast<struct sockaddr*>(&storage), length) < 0) {
spdlog::error("Failed to bind socket: {}", strerror(errno));
return tl::make_unexpected(QuicrError::from_errno(errno));
}