dns_udp_tracker.cc 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "net/dns/dns_udp_tracker.h"
  5. #include <algorithm>
  6. #include <utility>
  7. #include "base/metrics/histogram_macros.h"
  8. #include "base/numerics/safe_conversions.h"
  9. #include "base/time/tick_clock.h"
  10. #include "net/base/net_errors.h"
  11. namespace net {
  12. namespace {
  13. // Used in UMA (DNS.UdpLowEntropyReason). Do not renumber or remove values.
  14. enum class LowEntropyReason {
  15. kPortReuse = 0,
  16. kRecognizedIdMismatch = 1,
  17. kUnrecognizedIdMismatch = 2,
  18. kSocketLimitExhaustion = 3,
  19. kMaxValue = kSocketLimitExhaustion,
  20. };
  21. void RecordLowEntropyUma(LowEntropyReason reason) {
  22. UMA_HISTOGRAM_ENUMERATION("Net.DNS.DnsTransaction.UDP.LowEntropyReason",
  23. reason);
  24. }
  25. } // namespace
  26. // static
  27. constexpr base::TimeDelta DnsUdpTracker::kMaxAge;
  28. // static
  29. constexpr size_t DnsUdpTracker::kMaxRecordedQueries;
  30. // static
  31. constexpr base::TimeDelta DnsUdpTracker::kMaxRecognizedIdAge;
  32. // static
  33. constexpr size_t DnsUdpTracker::kUnrecognizedIdMismatchThreshold;
  34. // static
  35. constexpr size_t DnsUdpTracker::kRecognizedIdMismatchThreshold;
  36. // static
  37. constexpr int DnsUdpTracker::kPortReuseThreshold;
  38. struct DnsUdpTracker::QueryData {
  39. uint16_t port;
  40. uint16_t query_id;
  41. base::TimeTicks time;
  42. };
  43. DnsUdpTracker::DnsUdpTracker() = default;
  44. DnsUdpTracker::~DnsUdpTracker() = default;
  45. DnsUdpTracker::DnsUdpTracker(DnsUdpTracker&&) = default;
  46. DnsUdpTracker& DnsUdpTracker::operator=(DnsUdpTracker&&) = default;
  47. void DnsUdpTracker::RecordQuery(uint16_t port, uint16_t query_id) {
  48. PurgeOldRecords();
  49. int reused_port_count = base::checked_cast<int>(std::count_if(
  50. recent_queries_.cbegin(), recent_queries_.cend(),
  51. [port](const auto& recent_query) { return port == recent_query.port; }));
  52. if (reused_port_count >= kPortReuseThreshold && !low_entropy_) {
  53. low_entropy_ = true;
  54. RecordLowEntropyUma(LowEntropyReason::kPortReuse);
  55. }
  56. SaveQuery({port, query_id, tick_clock_->NowTicks()});
  57. }
  58. void DnsUdpTracker::RecordResponseId(uint16_t query_id, uint16_t response_id) {
  59. PurgeOldRecords();
  60. if (query_id != response_id) {
  61. SaveIdMismatch(response_id);
  62. }
  63. }
  64. void DnsUdpTracker::RecordConnectionError(int connection_error) {
  65. if (!low_entropy_ && connection_error == ERR_INSUFFICIENT_RESOURCES) {
  66. // On UDP connection, this error signifies that the process is using an
  67. // unreasonably large number of UDP sockets, potentially a deliberate
  68. // attack to reduce DNS port entropy.
  69. low_entropy_ = true;
  70. RecordLowEntropyUma(LowEntropyReason::kSocketLimitExhaustion);
  71. }
  72. }
  73. void DnsUdpTracker::PurgeOldRecords() {
  74. base::TimeTicks now = tick_clock_->NowTicks();
  75. while (!recent_queries_.empty() &&
  76. (now - recent_queries_.front().time) > kMaxAge) {
  77. recent_queries_.pop_front();
  78. }
  79. while (!recent_unrecognized_id_hits_.empty() &&
  80. now - recent_unrecognized_id_hits_.front() > kMaxAge) {
  81. recent_unrecognized_id_hits_.pop_front();
  82. }
  83. while (!recent_recognized_id_hits_.empty() &&
  84. now - recent_recognized_id_hits_.front() > kMaxAge) {
  85. recent_recognized_id_hits_.pop_front();
  86. }
  87. }
  88. void DnsUdpTracker::SaveQuery(QueryData query) {
  89. if (recent_queries_.size() == kMaxRecordedQueries)
  90. recent_queries_.pop_front();
  91. DCHECK_LT(recent_queries_.size(), kMaxRecordedQueries);
  92. DCHECK(recent_queries_.empty() || query.time >= recent_queries_.back().time);
  93. recent_queries_.push_back(std::move(query));
  94. }
  95. void DnsUdpTracker::SaveIdMismatch(uint16_t id) {
  96. // No need to track mismatches if already flagged for low entropy.
  97. if (low_entropy_)
  98. return;
  99. base::TimeTicks now = tick_clock_->NowTicks();
  100. base::TimeTicks time_cutoff = now - kMaxRecognizedIdAge;
  101. bool is_recognized = std::any_of(
  102. recent_queries_.cbegin(), recent_queries_.cend(),
  103. [&](const auto& recent_query) {
  104. return recent_query.query_id == id && recent_query.time >= time_cutoff;
  105. });
  106. if (is_recognized) {
  107. DCHECK_LT(recent_recognized_id_hits_.size(),
  108. kRecognizedIdMismatchThreshold);
  109. if (recent_recognized_id_hits_.size() ==
  110. kRecognizedIdMismatchThreshold - 1) {
  111. low_entropy_ = true;
  112. RecordLowEntropyUma(LowEntropyReason::kRecognizedIdMismatch);
  113. return;
  114. }
  115. DCHECK(recent_recognized_id_hits_.empty() ||
  116. now >= recent_recognized_id_hits_.back());
  117. recent_recognized_id_hits_.push_back(now);
  118. } else {
  119. DCHECK_LT(recent_unrecognized_id_hits_.size(),
  120. kUnrecognizedIdMismatchThreshold);
  121. if (recent_unrecognized_id_hits_.size() ==
  122. kUnrecognizedIdMismatchThreshold - 1) {
  123. low_entropy_ = true;
  124. RecordLowEntropyUma(LowEntropyReason::kUnrecognizedIdMismatch);
  125. return;
  126. }
  127. DCHECK(recent_unrecognized_id_hits_.empty() ||
  128. now >= recent_unrecognized_id_hits_.back());
  129. recent_unrecognized_id_hits_.push_back(now);
  130. }
  131. }
  132. } // namespace net