/* SPDX-License-Identifier: GPL-3.0-or-later */ // ss::relay::Resolver against a local DNS server that answers after a delay: // A/AAAA/NXDOMAIN complete without blocking the loop, the query pool bounds // the number in flight, and shutdown reports pending queries as failed. #include #include #include #include #include #include #include "event/loop.hpp" #include "relay/resolver.hpp" #include "test_helpers.hpp" extern "C" { int verbose = 0; } using namespace ss; namespace { struct Answered { unsigned called = 0; int family = 0; bool missing = false; bool ok = false; }; int expected_family = AF_INET; unsigned completed = 0; event::Loop *test_loop = nullptr; unsigned stop_after = 0; void on_answer(const relay::ResolveCallback &cb, const sockaddr *address) noexcept { auto *r = static_cast(cb.ctx); ++r->called; if (address != nullptr) { r->family = address->sa_family; r->ok = address->sa_family == AF_INET ? reinterpret_cast(address)->sin_port == htons(443) : reinterpret_cast(address)->sin6_port == htons(443); } if (++completed == stop_after) { test_loop->stop(); } } // Answers every question 50 ms later: names starting with 'm' do not exist. class FakeDns { public: void on_tick() noexcept { for (auto &p : pending_) { (void)sendto(poll_.fd(), p.bytes, p.length, 0, reinterpret_cast(&p.peer), p.peer_len); } pending_.clear(); } event::Flow on_question(int) noexcept { Packet p{}; p.peer_len = sizeof(p.peer); ssize_t n = recvfrom(poll_.fd(), p.bytes, sizeof(p.bytes), 0, reinterpret_cast(&p.peer), &p.peer_len); if (n < 17) { return event::Flow::proceed; } std::size_t end = 12; while (end < static_cast(n) && p.bytes[end] != 0) { end += p.bytes[end] + 1; } end += 5; unsigned type = p.bytes[end - 3]; bool missing = p.bytes[13] == 'm'; p.bytes[2] = 0x81; p.bytes[3] = missing ? 0x83 : 0x80; p.bytes[6] = 0; p.bytes[7] = missing ? 0 : 1; std::memset(p.bytes + 8, 0, 4); p.length = end; if (!missing) { unsigned char answer[] = { 0xc0, 0x0c, 0, static_cast(type), 0, 1, 0, 0, 0, 1, 0, static_cast(type == 1 ? 4 : 16) }; std::memcpy(p.bytes + end, answer, sizeof(answer)); p.length += sizeof(answer); std::memset(p.bytes + p.length, 0, 16); if (type == 1) { p.bytes[p.length] = 127; p.bytes[p.length + 3] = 1; } else { p.bytes[p.length + 15] = 1; } p.length += type == 1 ? 4 : 16; } pending_.push_back(p); timer_.start(50); return event::Flow::proceed; } void open(event::Loop &loop, int fd) { SS_CHECK(poll_.open(loop, *this, fd)); SS_CHECK(poll_.want(event::readable)); SS_CHECK(timer_.open(loop, *this)); } void close(event::CloseGroup &g) { poll_.close(g); timer_.close(g); } private: struct Packet { sockaddr_storage peer; socklen_t peer_len; unsigned char bytes[512]; std::size_t length; }; std::vector pending_; event::Poll poll_; event::Timer timer_; }; void drain(event::Loop &loop, bool &flag) { while (!flag) { loop.run_nowait(); } } void test_answers_and_limits() { for (int ipv6 = 0; ipv6 < 2; ++ipv6) { event::Loop loop; SS_CHECK(loop.init()); test_loop = &loop; int fd = socket(AF_INET, SOCK_DGRAM, 0); SS_CHECK(fd >= 0); sockaddr_in a{}; a.sin_family = AF_INET; a.sin_addr.s_addr = htonl(INADDR_LOOPBACK); SS_CHECK(bind(fd, reinterpret_cast(&a), sizeof(a)) == 0); socklen_t len = sizeof(a); SS_CHECK(getsockname(fd, reinterpret_cast(&a), &len) == 0); char nameserver[64]; std::snprintf(nameserver, sizeof(nameserver), "127.0.0.1:%u", ntohs(a.sin_port)); FakeDns dns; dns.open(loop, fd); relay::Resolver resolver; SS_CHECK(resolver.init(loop, nameserver, ipv6 != 0, 33)); expected_family = ipv6 ? AF_INET6 : AF_INET; completed = 0; stop_after = 33; Answered results[34]; results[32].missing = true; for (unsigned i = 0; i < 34; ++i) { char host[64]; std::snprintf(host, sizeof(host), "%s-%u.invalid", i == 32 ? "missing" : "resolved", i); relay::ResolveCallback cb; cb.fn = &on_answer; cb.ctx = &results[i]; auto st = resolver.resolve(host, htons(443), cb); // The pool holds 33 queries; the 34th is refused up front. SS_CHECK(i < 33 ? static_cast(st) : (!st && st.error().code == Errc::exhausted)); } SS_CHECK(completed == 0 && resolver.in_flight() == 33); loop.run(); SS_CHECK(completed == 33 && resolver.in_flight() == 0); for (unsigned i = 0; i < 33; ++i) { SS_CHECK(results[i].called == 1); if (results[i].missing) { SS_CHECK(!results[i].ok && results[i].family == 0); } else { SS_CHECK(results[i].ok && results[i].family == expected_family); } } SS_CHECK(results[33].called == 0); // Shutting down with queries in flight reports them as failed. Answered late; relay::ResolveCallback cb; cb.fn = &on_answer; cb.ctx = ⪭ SS_CHECK(resolver.resolve("resolved-late.invalid", htons(443), cb)); static bool closed; closed = false; resolver.shutdown(CloseCompletion([](void *, std::uint32_t) { closed = true; }, nullptr, 0)); drain(loop, closed); SS_CHECK(late.called == 1 && !late.ok); event::CloseGroup g; static bool dns_closed; dns_closed = false; g.arm(CloseCompletion([](void *, std::uint32_t) { dns_closed = true; }, nullptr, 0)); dns.close(g); g.seal(); drain(loop, dns_closed); SS_CHECK(loop.shutdown()); } } } // namespace int main() { SS_RUN(test_answers_and_limits); return 0; }