Skip to content
Closed
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
70 changes: 47 additions & 23 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>

Expand Down Expand Up @@ -115,7 +116,9 @@ class ares_error_category : public std::error_category {
case ARES_EBADNAME: return "Bad name";
case ARES_EBADFAMILY: return "Bad family";
case ARES_EBADRESP: return "Bad response";
case ARES_ECONNREFUSED :return "Connection refused";
// c-ares raises this when no nameserver could be reached at
// all, not for a refused TCP connect; use its own wording.
case ARES_ECONNREFUSED: return "Could not contact DNS servers";
case ARES_ETIMEOUT: return "Timeout";
case ARES_EOF: return "EOF";
case ARES_EFILE: return "File error";
Expand Down Expand Up @@ -370,19 +373,9 @@ 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;
}
// opts.servers is installed after ares_init_options, below:
// ARES_OPT_SERVERS carries in_addr only, so it cannot express an IPv6
// nameserver.
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 +396,19 @@ dns_resolver::impl::impl(network_stack& stack, const options& opts)

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

if (opts.servers) {
// A server given without a port inherits the channel's udp/tcp
// defaults, so ARES_OPT_UDP_PORT/ARES_OPT_TCP_PORT above still apply.
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 +1118,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 +1174,21 @@ 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");
switch (addr->sa_family) {
case AF_INET:
if (len < static_cast<socklen_t>(sizeof(sockaddr_in))) {
throw std::invalid_argument("Truncated AF_INET address");
}
return *reinterpret_cast<const sockaddr_in *>(addr);
case AF_INET6:
if (len < static_cast<socklen_t>(sizeof(sockaddr_in6))) {
throw std::invalid_argument("Truncated AF_INET6 address");
}
return *reinterpret_cast<const sockaddr_in6 *>(addr);
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 @@ -1303,10 +1319,18 @@ 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();
// sockaddr is only wide enough for AF_INET, so an
// AF_INET6 source has to be copied by its real length.
// from_len is value-result: on entry it bounds the
// caller's buffer, on exit it reports the source size.
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
65 changes: 64 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 Down Expand Up @@ -54,7 +55,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 @@ -87,12 +88,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 @@ -276,6 +294,51 @@ SEASTAR_TEST_CASE(test_resolve_tcp_split_response) {
}
}

// 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 (!engine().net().supports_ipv6()) {
BOOST_TEST_MESSAGE("No ipv6 support, skipping test");
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