Skip to content
Open
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
71 changes: 49 additions & 22 deletions src/net/dns.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@

#include <arpa/nameser.h>
#include <chrono>
#include <cstring>
#include <memory>
#include <concepts>
#include <netdb.h>
Expand Down Expand Up @@ -370,19 +371,8 @@ dns_resolver::impl::impl(network_stack& stack, const options& opts)
a_opts.flags = ARES_FLAG_USEVC | ARES_FLAG_PRIMARY;
flags |= ARES_OPT_FLAGS;
}
std::vector<in_addr> addr_tmp;
if (opts.servers) {
std::transform(opts.servers->begin(), opts.servers->end(), std::back_inserter(addr_tmp), [](const inet_address& a) {
if (a.in_family() != inet_address::family::INET) {
throw std::invalid_argument("Servers must be ipv4 addresses");
}
in_addr in = a;
return in;
});
a_opts.servers = addr_tmp.data();
a_opts.nservers = int(addr_tmp.size());
flags |= ARES_OPT_SERVERS;
}
// ARES_OPT_SERVERS carries in_addr only, so servers are installed after
// ares_init_options instead.
std::vector<const char *> dom_tmp;
if (opts.domains) {
std::transform(opts.domains->begin(), opts.domains->end(), std::back_inserter(dom_tmp), [](const sstring& s) {
Expand All @@ -403,6 +393,18 @@ dns_resolver::impl::impl(network_stack& stack, const options& opts)

check_ares_error(ares_init_options(&_channel, &a_opts, flags));

if (opts.servers) {
// Without a port a server inherits ARES_OPT_UDP_PORT/ARES_OPT_TCP_PORT.
sstring csv;
for (auto& server : *opts.servers) {
if (!csv.empty()) {
csv += ",";
}
csv += fmt::format("{}", server);
}
check_ares_error(ares_set_servers_csv(_channel, csv.c_str()));
}

// Set up custom socket functions to integrate with Seastar's networking stack
// Note: These work together with ARES_OPT_SOCK_STATE_CB (when available)
#if ARES_VERSION >= 0x012200 // ares_set_socket_functions_ex available since 1.34.0
Expand Down Expand Up @@ -1112,8 +1114,8 @@ dns_resolver::impl::do_socket(int af, int type, int protocol) {
dns_log.trace("Created tcp socket {}", fd);
break;
case SOCK_DGRAM:
_sockets.emplace(fd, _stack.make_unbound_datagram_channel(AF_INET));
dns_log.trace("Created udp socket {}", fd);
_sockets.emplace(fd, _stack.make_unbound_datagram_channel(af));
dns_log.trace("Created udp socket {} (family {})", fd, af);
break;
default: return -1;
}
Expand Down Expand Up @@ -1168,11 +1170,29 @@ dns_resolver::impl::do_close(ares_socket_t fd) {

socket_address
dns_resolver::impl::sock_addr(const sockaddr * addr, socklen_t len) {
if (addr->sa_family != AF_INET) {
throw std::invalid_argument("No ipv6 yet");
// Copy instead of casting: the object c-ares passes need not be a
// sockaddr_in or sockaddr_in6, so reading it through one is UB (#2288).
switch (addr->sa_family) {
case AF_INET: {
::sockaddr_in in;
if (len < static_cast<socklen_t>(sizeof(in))) {
throw std::invalid_argument("Truncated AF_INET address");
}
std::memcpy(&in, addr, sizeof(in));
return in;
}
case AF_INET6: {
::sockaddr_in6 in6;
if (len < static_cast<socklen_t>(sizeof(in6))) {
throw std::invalid_argument("Truncated AF_INET6 address");
}
std::memcpy(&in6, addr, sizeof(in6));
return in6;
}
default:
throw std::invalid_argument(
format("Unsupported address family {}", addr->sa_family));
}
auto in = reinterpret_cast<const sockaddr_in *>(addr);
return *in;
}

int
Expand Down Expand Up @@ -1306,10 +1326,17 @@ dns_resolver::impl::do_recvfrom(ares_socket_t fd, void * dst, size_t len, int fl
dns_log.trace("Read {}. {} bytes available from {}", fd, available, udp.in->get_src());

if (from != nullptr) {
*from = socket_address(udp.in->get_src()).as_posix_sockaddr();
// from_len is value-result: it bounds the caller's
// buffer on entry and reports the source size on exit,
// which for AF_INET6 exceeds a sockaddr.
auto src = socket_address(udp.in->get_src());
auto capacity = from_len != nullptr
? *from_len
: static_cast<socklen_t>(sizeof(sockaddr));
std::memcpy(from, &src.as_posix_sockaddr(),
std::min(src.length(), capacity));
if (from_len != nullptr) {
// TODO: ipvv6
*from_len = sizeof(sockaddr_in);
*from_len = src.length();
}
}

Expand Down
66 changes: 65 additions & 1 deletion tests/unit/dns_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
*/
#include <vector>
#include <algorithm>
#include <span>

#include <seastar/core/seastar.hh>
#include <seastar/core/sleep.hh>
Expand All @@ -34,6 +35,8 @@
#include <seastar/net/dns.hh>
#include <seastar/net/inet_address.hh>

#include "ipv6_support.hh"

using namespace seastar;
using namespace seastar::net;

Expand All @@ -55,7 +58,7 @@ static void write_be32(std::vector<char>& out, uint32_t v) {
out.push_back(char(v));
}

static std::vector<char> make_tcp_dns_a_response(const temporary_buffer<char>& query) {
static std::vector<char> make_dns_a_response(const temporary_buffer<char>& query) {
BOOST_REQUIRE_GE(query.size(), 12);
BOOST_REQUIRE_EQUAL(read_be16(query.get() + 4), 1);

Expand Down Expand Up @@ -88,12 +91,29 @@ static std::vector<char> make_tcp_dns_a_response(const temporary_buffer<char>& q
msg.push_back(char(0));
msg.push_back(char(42));

return msg;
}

static std::vector<char> make_tcp_dns_a_response(const temporary_buffer<char>& query) {
auto msg = make_dns_a_response(query);
std::vector<char> tcp_response;
write_be16(tcp_response, msg.size());
tcp_response.insert(tcp_response.end(), msg.begin(), msg.end());
return tcp_response;
}

// Answers exactly one query, over whatever family the channel is bound to.
static future<> serve_udp_dns_response(net::datagram_channel& chan) {
auto dg = co_await chan.receive();
auto bufs = dg.get_buffers();
BOOST_REQUIRE(!bufs.empty());
// A DNS query for a single name fits one buffer.
auto query = temporary_buffer<char>(bufs.front().get(), bufs.front().size());
auto response = make_dns_a_response(query);
auto out = temporary_buffer<char>(response.data(), response.size());
co_await chan.send(dg.get_src(), std::span<temporary_buffer<char>>(&out, 1));
}

static future<> serve_split_tcp_dns_response(server_socket& listener) {
auto ar = co_await listener.accept();
auto socket = std::move(ar.connection);
Expand Down Expand Up @@ -326,6 +346,50 @@ SEASTAR_TEST_CASE(test_tcp_server_closes_connection) {
});
}

// The resolver must be able to *talk to* an IPv6 nameserver. The record it
// asks for is still an A record: nameserver transport family and answer
// family are independent, and only the former was broken.
SEASTAR_TEST_CASE(test_resolve_udp_ipv6_nameserver) {
if (!seastar::testing::ipv6_available_or_skip()) {
co_return;
}

auto chan = make_bound_datagram_channel(socket_address(ipv6_addr{"::1", 0}));
auto server = serve_udp_dns_response(chan);

dns_resolver::options opts;
opts.servers = std::vector<inet_address>({ inet_address("::1") });
opts.udp_port = chan.local_address().port();
opts.timeout = std::chrono::seconds(30);

auto d = ::make_lw_shared<dns_resolver>(engine().net(), opts);

std::exception_ptr ex;
try {
auto h = co_await with_timeout(timer<>::clock::now() + std::chrono::seconds(5),
d->get_host_by_name("v6ns.seastar.test", inet_address::family::INET));
BOOST_REQUIRE_EQUAL(h.addr_entries.size(), 1);
BOOST_REQUIRE_EQUAL(h.addr_entries.front().addr, inet_address("127.0.0.42"));
} catch (...) {
ex = std::current_exception();
}

co_await d->close();
chan.shutdown_input();
try {
co_await std::move(server);
} catch (...) {
if (!ex) {
ex = std::current_exception();
}
}
chan.close();

if (ex) {
std::rethrow_exception(ex);
}
}

SEASTAR_TEST_CASE(test_resolve_tcp,
*enable_if_with_networking()) {
dns_resolver::options opts;
Expand Down
Loading