#3 - DNS support + IPv6
This commit is contained in:
@@ -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();
|
||||
|
||||
/**
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user