|
6 | 6 | * LICENSE file in the root directory of this source tree. |
7 | 7 | */ |
8 | 8 |
|
| 9 | +#include <algorithm> |
| 10 | +#include <array> |
9 | 11 | #include <atomic> |
| 12 | +#include <condition_variable> |
10 | 13 | #include <cstring> |
| 14 | +#include <mutex> |
11 | 15 | #include <thread> |
12 | 16 |
|
13 | 17 | #include <folly/portability/GMock.h> |
@@ -415,6 +419,207 @@ class LoopTerminatingCallback : public CAresResolver::ResolutionCallback { |
415 | 419 | folly::EventBase& evb_; |
416 | 420 | }; |
417 | 421 |
|
| 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 | + |
418 | 623 | // Verify that the c-ares query cache (qcache) is disabled. The qcache (enabled |
419 | 624 | // by default since c-ares 1.31.0) caches DNS responses keyed by query name/type |
420 | 625 | // and replays them for subsequent identical queries. This is undesirable for |
@@ -540,3 +745,94 @@ TEST_F(CAresResolverTest, QCacheDisabledResponsesNotCached) { |
540 | 745 | testResolver.reset(); |
541 | 746 | close(serverSock); |
542 | 747 | } |
| 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