Skip to content

Commit 5e07582

Browse files
Jorge Lopez Silvameta-codesync[bot]
authored andcommitted
Test c-ares active query and socket sampling
Summary: Add `CAresResolver` tests that hold a DNS query open long enough to observe c-ares active queries and open sockets, and cover the app-timeout case where c-ares still owns a live query. Reviewed By: HsiehYuho Differential Revision: D106669276 fbshipit-source-id: 58d8e2480fd6c2c3e98c381cc14b143348a14af4
1 parent c88a624 commit 5e07582

1 file changed

Lines changed: 296 additions & 0 deletions

File tree

proxygen/lib/dns/test/CAresResolverTest.cpp

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@
66
* LICENSE file in the root directory of this source tree.
77
*/
88

9+
#include <algorithm>
10+
#include <array>
911
#include <atomic>
12+
#include <condition_variable>
1013
#include <cstring>
14+
#include <mutex>
1115
#include <thread>
1216

1317
#include <folly/portability/GMock.h>
@@ -415,6 +419,207 @@ class LoopTerminatingCallback : public CAresResolver::ResolutionCallback {
415419
folly::EventBase& evb_;
416420
};
417421

422+
class RecordingStatsCollector : public DNSResolver::StatsCollector {
423+
public:
424+
void recordSuccess(const std::vector<DNSResolver::Answer>&,
425+
std::chrono::milliseconds) noexcept override {
426+
}
427+
428+
void recordError(const folly::exception_wrapper&,
429+
std::chrono::milliseconds) noexcept override {
430+
}
431+
432+
void recordQueryResult(uint8_t) noexcept override {
433+
}
434+
435+
void recordCAresActiveQueries(size_t count) noexcept override {
436+
activeQueries_.push_back(count);
437+
}
438+
439+
void recordCAresOpenSockets(size_t count) noexcept override {
440+
openSockets_.push_back(count);
441+
}
442+
void recordCAresSocketOpen() noexcept override {
443+
++socketOpens_;
444+
}
445+
void recordCAresSocketClose() noexcept override {
446+
++socketCloses_;
447+
}
448+
449+
const std::vector<size_t>& activeQueries() const {
450+
return activeQueries_;
451+
}
452+
453+
const std::vector<size_t>& openSockets() const {
454+
return openSockets_;
455+
}
456+
457+
size_t socketOpens() const {
458+
return socketOpens_;
459+
}
460+
461+
size_t socketCloses() const {
462+
return socketCloses_;
463+
}
464+
465+
private:
466+
std::vector<size_t> activeQueries_;
467+
std::vector<size_t> openSockets_;
468+
size_t socketOpens_{0};
469+
size_t socketCloses_{0};
470+
};
471+
472+
class DelayedDnsServer {
473+
public:
474+
DelayedDnsServer() {
475+
serverSock_ = socket(AF_INET, SOCK_DGRAM, 0);
476+
EXPECT_GE(serverSock_, 0);
477+
478+
struct timeval tv{};
479+
tv.tv_sec = 1;
480+
tv.tv_usec = 0;
481+
setsockopt(serverSock_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
482+
483+
struct sockaddr_in serverAddr{};
484+
serverAddr.sin_family = AF_INET;
485+
serverAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
486+
serverAddr.sin_port = 0;
487+
EXPECT_EQ(
488+
bind(serverSock_, (struct sockaddr*)&serverAddr, sizeof(serverAddr)),
489+
0);
490+
491+
socklen_t addrLen = sizeof(serverAddr);
492+
EXPECT_EQ(getsockname(serverSock_, (struct sockaddr*)&serverAddr, &addrLen),
493+
0);
494+
port_ = ntohs(serverAddr.sin_port);
495+
496+
thread_ = std::thread([this] { run(); });
497+
}
498+
499+
~DelayedDnsServer() {
500+
release(false);
501+
}
502+
503+
uint16_t port() const {
504+
return port_;
505+
}
506+
507+
bool waitForQuery(std::chrono::milliseconds timeout) {
508+
std::unique_lock<std::mutex> lock(mutex_);
509+
return cv_.wait_for(lock, timeout, [&] { return queryReceived_; });
510+
}
511+
512+
void respondAndJoin() {
513+
release(true);
514+
}
515+
516+
void stopAndJoin() {
517+
release(false);
518+
}
519+
520+
private:
521+
void release(bool sendResponse) {
522+
{
523+
std::lock_guard<std::mutex> lock(mutex_);
524+
if (released_) {
525+
return;
526+
}
527+
released_ = true;
528+
sendResponse_ = sendResponse;
529+
}
530+
cv_.notify_all();
531+
if (thread_.joinable()) {
532+
thread_.join();
533+
}
534+
if (serverSock_ >= 0) {
535+
close(serverSock_);
536+
serverSock_ = -1;
537+
}
538+
}
539+
540+
void run() {
541+
struct sockaddr_in clientAddr{};
542+
socklen_t clientAddrLen = sizeof(clientAddr);
543+
std::array<unsigned char, 512> query;
544+
ssize_t n = recvfrom(serverSock_,
545+
query.data(),
546+
query.size(),
547+
0,
548+
(struct sockaddr*)&clientAddr,
549+
&clientAddrLen);
550+
if (n <= 0) {
551+
return;
552+
}
553+
554+
size_t questionEnd = findQuestionEnd(query, static_cast<size_t>(n));
555+
{
556+
std::lock_guard<std::mutex> lock(mutex_);
557+
queryReceived_ = true;
558+
}
559+
cv_.notify_all();
560+
561+
std::unique_lock<std::mutex> lock(mutex_);
562+
cv_.wait(lock, [&] { return released_; });
563+
bool sendResponse = sendResponse_;
564+
lock.unlock();
565+
566+
if (!sendResponse) {
567+
return;
568+
}
569+
570+
std::array<unsigned char, 512> resp;
571+
memcpy(resp.data(), query.data(), questionEnd);
572+
resp[2] = 0x81; // QR=1 (response), RD=1
573+
resp[3] = 0x80; // RA=1, RCODE=0 (NOERROR)
574+
resp[6] = 0;
575+
resp[7] = 1; // 1 answer
576+
577+
size_t respLen = questionEnd;
578+
resp[respLen++] = 0xC0;
579+
resp[respLen++] = 0x0C; // name: pointer to question name
580+
resp[respLen++] = 0x00;
581+
resp[respLen++] = 0x01; // type: A
582+
resp[respLen++] = 0x00;
583+
resp[respLen++] = 0x01; // class: IN
584+
resp[respLen++] = 0x00;
585+
resp[respLen++] = 0x00;
586+
resp[respLen++] = 0x01;
587+
resp[respLen++] = 0x2C; // TTL: 300
588+
resp[respLen++] = 0x00;
589+
resp[respLen++] = 0x04; // RDLENGTH: 4
590+
resp[respLen++] = 192;
591+
resp[respLen++] = 0;
592+
resp[respLen++] = 2;
593+
resp[respLen++] = 1; // 192.0.2.1
594+
595+
sendto(serverSock_,
596+
resp.data(),
597+
respLen,
598+
0,
599+
(struct sockaddr*)&clientAddr,
600+
clientAddrLen);
601+
}
602+
603+
int serverSock_{-1};
604+
uint16_t port_{0};
605+
std::thread thread_;
606+
std::mutex mutex_;
607+
std::condition_variable cv_;
608+
bool queryReceived_{false};
609+
bool released_{false};
610+
bool sendResponse_{false};
611+
};
612+
613+
bool containsSample(const std::vector<size_t>& samples, size_t value) {
614+
return std::find(samples.begin(), samples.end(), value) != samples.end();
615+
}
616+
617+
bool containsSampleAtLeast(const std::vector<size_t>& samples, size_t value) {
618+
return std::any_of(samples.begin(), samples.end(), [&](size_t sample) {
619+
return sample >= value;
620+
});
621+
}
622+
418623
// Verify that the c-ares query cache (qcache) is disabled. The qcache (enabled
419624
// by default since c-ares 1.31.0) caches DNS responses keyed by query name/type
420625
// and replays them for subsequent identical queries. This is undesirable for
@@ -540,3 +745,94 @@ TEST_F(CAresResolverTest, QCacheDisabledResponsesNotCached) {
540745
testResolver.reset();
541746
close(serverSock);
542747
}
748+
749+
TEST_F(CAresResolverTest, SamplesCAresActiveQueriesAndOpenSockets) {
750+
DelayedDnsServer server;
751+
752+
folly::EventBase evb;
753+
auto testResolver = CAresResolver::newResolver();
754+
RecordingStatsCollector stats;
755+
testResolver->attachEventBase(&evb);
756+
testResolver->enableCAresStateSampling();
757+
testResolver->setServers({folly::SocketAddress("127.0.0.1", server.port())});
758+
testResolver->setPort(server.port());
759+
testResolver->setStatsCollector(&stats);
760+
testResolver->init();
761+
762+
LoopTerminatingCallback cb(evb);
763+
testResolver->resolveHostname(
764+
&cb, "test.example.com", std::chrono::milliseconds(5000), AF_INET);
765+
766+
ASSERT_TRUE(server.waitForQuery(std::chrono::seconds(1)));
767+
EXPECT_TRUE(containsSampleAtLeast(stats.activeQueries(), 1));
768+
EXPECT_TRUE(containsSampleAtLeast(stats.openSockets(), 1));
769+
EXPECT_GE(stats.socketOpens(), size_t{1});
770+
771+
server.respondAndJoin();
772+
evb.loopForever();
773+
774+
EXPECT_TRUE(cb.success_);
775+
EXPECT_TRUE(containsSample(stats.activeQueries(), 0));
776+
777+
testResolver.reset();
778+
EXPECT_TRUE(containsSample(stats.openSockets(), 0));
779+
EXPECT_GE(stats.socketCloses(), size_t{1});
780+
}
781+
782+
TEST_F(CAresResolverTest, CAresStateSamplingIsDisabledByDefault) {
783+
DelayedDnsServer server;
784+
785+
folly::EventBase evb;
786+
auto testResolver = CAresResolver::newResolver();
787+
RecordingStatsCollector stats;
788+
testResolver->attachEventBase(&evb);
789+
testResolver->setServers({folly::SocketAddress("127.0.0.1", server.port())});
790+
testResolver->setPort(server.port());
791+
testResolver->setStatsCollector(&stats);
792+
testResolver->init();
793+
794+
LoopTerminatingCallback cb(evb);
795+
testResolver->resolveHostname(
796+
&cb, "disabled.example.com", std::chrono::milliseconds(5000), AF_INET);
797+
798+
ASSERT_TRUE(server.waitForQuery(std::chrono::seconds(1)));
799+
800+
server.respondAndJoin();
801+
evb.loopForever();
802+
803+
EXPECT_TRUE(cb.success_);
804+
EXPECT_TRUE(stats.activeQueries().empty());
805+
EXPECT_TRUE(stats.openSockets().empty());
806+
EXPECT_EQ(stats.socketOpens(), size_t{0});
807+
EXPECT_EQ(stats.socketCloses(), size_t{0});
808+
809+
testResolver.reset();
810+
}
811+
812+
TEST_F(CAresResolverTest, TimeoutStillSamplesActiveCAresQuery) {
813+
DelayedDnsServer server;
814+
815+
folly::EventBase evb;
816+
auto testResolver = CAresResolver::newResolver();
817+
RecordingStatsCollector stats;
818+
testResolver->attachEventBase(&evb);
819+
testResolver->enableCAresStateSampling();
820+
testResolver->setServers({folly::SocketAddress("127.0.0.1", server.port())});
821+
testResolver->setPort(server.port());
822+
testResolver->setStatsCollector(&stats);
823+
testResolver->init();
824+
825+
LoopTerminatingCallback cb(evb);
826+
testResolver->resolveHostname(
827+
&cb, "timeout.example.com", std::chrono::milliseconds(20), AF_INET);
828+
829+
ASSERT_TRUE(server.waitForQuery(std::chrono::seconds(1)));
830+
evb.loopForever();
831+
832+
EXPECT_FALSE(cb.success_);
833+
ASSERT_FALSE(stats.activeQueries().empty());
834+
EXPECT_GE(stats.activeQueries().back(), size_t{1});
835+
836+
server.stopAndJoin();
837+
testResolver.reset();
838+
}

0 commit comments

Comments
 (0)