blob: 74c714b64325097f91619858037d13d7ce7cb5fc [file] [log] [blame]
Avi Drissman64595482022-09-14 20:52:291// Copyright 2020 The Chromium Authors
Eric Orthf8680ac2020-06-10 22:57:242// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "net/dns/dns_udp_tracker.h"
6
Eric Orthf8680ac2020-06-10 22:57:247#include <utility>
8
9#include "base/metrics/histogram_macros.h"
10#include "base/numerics/safe_conversions.h"
Peter Kastinge1961a9f2022-08-30 17:26:4611#include "base/ranges/algorithm.h"
Eric Orthf8680ac2020-06-10 22:57:2412#include "base/time/tick_clock.h"
Eric Orthf64533e2020-08-21 01:09:3013#include "net/base/net_errors.h"
Eric Orthf8680ac2020-06-10 22:57:2414
15namespace net {
16
Eric Ortha167bdf2020-08-18 22:15:2617namespace {
18// Used in UMA (DNS.UdpLowEntropyReason). Do not renumber or remove values.
19enum class LowEntropyReason {
20 kPortReuse = 0,
21 kRecognizedIdMismatch = 1,
22 kUnrecognizedIdMismatch = 2,
Eric Orthf64533e2020-08-21 01:09:3023 kSocketLimitExhaustion = 3,
24 kMaxValue = kSocketLimitExhaustion,
Eric Ortha167bdf2020-08-18 22:15:2625};
26
27void RecordLowEntropyUma(LowEntropyReason reason) {
28 UMA_HISTOGRAM_ENUMERATION("Net.DNS.DnsTransaction.UDP.LowEntropyReason",
29 reason);
30}
31
32} // namespace
33
Eric Orthf8680ac2020-06-10 22:57:2434// static
35constexpr base::TimeDelta DnsUdpTracker::kMaxAge;
36
37// static
38constexpr size_t DnsUdpTracker::kMaxRecordedQueries;
39
Eric Ortheafc5b42020-08-13 20:53:4440// static
41constexpr base::TimeDelta DnsUdpTracker::kMaxRecognizedIdAge;
42
43// static
44constexpr size_t DnsUdpTracker::kUnrecognizedIdMismatchThreshold;
45
46// static
47constexpr size_t DnsUdpTracker::kRecognizedIdMismatchThreshold;
48
49// static
50constexpr int DnsUdpTracker::kPortReuseThreshold;
51
Eric Orthf8680ac2020-06-10 22:57:2452struct DnsUdpTracker::QueryData {
53 uint16_t port;
54 uint16_t query_id;
55 base::TimeTicks time;
56};
57
58DnsUdpTracker::DnsUdpTracker() = default;
59DnsUdpTracker::~DnsUdpTracker() = default;
60DnsUdpTracker::DnsUdpTracker(DnsUdpTracker&&) = default;
61DnsUdpTracker& DnsUdpTracker::operator=(DnsUdpTracker&&) = default;
62
63void DnsUdpTracker::RecordQuery(uint16_t port, uint16_t query_id) {
Eric Ortheafc5b42020-08-13 20:53:4464 PurgeOldRecords();
Eric Orthf8680ac2020-06-10 22:57:2465
66 int reused_port_count = base::checked_cast<int>(std::count_if(
67 recent_queries_.cbegin(), recent_queries_.cend(),
68 [port](const auto& recent_query) { return port == recent_query.port; }));
Eric Orthf8680ac2020-06-10 22:57:2469
Eric Ortha167bdf2020-08-18 22:15:2670 if (reused_port_count >= kPortReuseThreshold && !low_entropy_) {
Eric Ortheafc5b42020-08-13 20:53:4471 low_entropy_ = true;
Eric Ortha167bdf2020-08-18 22:15:2672 RecordLowEntropyUma(LowEntropyReason::kPortReuse);
Eric Ortheafc5b42020-08-13 20:53:4473 }
74
Eric Orthfefc40f2021-01-30 00:28:3875 SaveQuery({port, query_id, tick_clock_->NowTicks()});
Eric Orthf8680ac2020-06-10 22:57:2476}
77
78void DnsUdpTracker::RecordResponseId(uint16_t query_id, uint16_t response_id) {
Eric Ortheafc5b42020-08-13 20:53:4479 PurgeOldRecords();
Eric Orthf8680ac2020-06-10 22:57:2480
Eric Orthfefc40f2021-01-30 00:28:3881 if (query_id != response_id) {
Eric Ortheafc5b42020-08-13 20:53:4482 SaveIdMismatch(response_id);
Eric Orthf8680ac2020-06-10 22:57:2483 }
Eric Orthf8680ac2020-06-10 22:57:2484}
85
Eric Orthf64533e2020-08-21 01:09:3086void DnsUdpTracker::RecordConnectionError(int connection_error) {
87 if (!low_entropy_ && connection_error == ERR_INSUFFICIENT_RESOURCES) {
88 // On UDP connection, this error signifies that the process is using an
89 // unreasonably large number of UDP sockets, potentially a deliberate
90 // attack to reduce DNS port entropy.
91 low_entropy_ = true;
92 RecordLowEntropyUma(LowEntropyReason::kSocketLimitExhaustion);
93 }
94}
95
Eric Ortheafc5b42020-08-13 20:53:4496void DnsUdpTracker::PurgeOldRecords() {
Eric Orthf8680ac2020-06-10 22:57:2497 base::TimeTicks now = tick_clock_->NowTicks();
Eric Ortheafc5b42020-08-13 20:53:4498
Eric Orthf8680ac2020-06-10 22:57:2499 while (!recent_queries_.empty() &&
100 (now - recent_queries_.front().time) > kMaxAge) {
101 recent_queries_.pop_front();
102 }
Eric Ortheafc5b42020-08-13 20:53:44103 while (!recent_unrecognized_id_hits_.empty() &&
104 now - recent_unrecognized_id_hits_.front() > kMaxAge) {
105 recent_unrecognized_id_hits_.pop_front();
106 }
107 while (!recent_recognized_id_hits_.empty() &&
108 now - recent_recognized_id_hits_.front() > kMaxAge) {
109 recent_recognized_id_hits_.pop_front();
110 }
Eric Orthf8680ac2020-06-10 22:57:24111}
112
113void DnsUdpTracker::SaveQuery(QueryData query) {
114 if (recent_queries_.size() == kMaxRecordedQueries)
115 recent_queries_.pop_front();
116 DCHECK_LT(recent_queries_.size(), kMaxRecordedQueries);
117
118 DCHECK(recent_queries_.empty() || query.time >= recent_queries_.back().time);
119 recent_queries_.push_back(std::move(query));
120}
121
Eric Ortheafc5b42020-08-13 20:53:44122void DnsUdpTracker::SaveIdMismatch(uint16_t id) {
123 // No need to track mismatches if already flagged for low entropy.
124 if (low_entropy_)
125 return;
126
127 base::TimeTicks now = tick_clock_->NowTicks();
128 base::TimeTicks time_cutoff = now - kMaxRecognizedIdAge;
Peter Kastinge1961a9f2022-08-30 17:26:46129 bool is_recognized =
130 base::ranges::any_of(recent_queries_, [&](const auto& recent_query) {
Eric Ortheafc5b42020-08-13 20:53:44131 return recent_query.query_id == id && recent_query.time >= time_cutoff;
132 });
133
134 if (is_recognized) {
135 DCHECK_LT(recent_recognized_id_hits_.size(),
136 kRecognizedIdMismatchThreshold);
137 if (recent_recognized_id_hits_.size() ==
138 kRecognizedIdMismatchThreshold - 1) {
139 low_entropy_ = true;
Eric Ortha167bdf2020-08-18 22:15:26140 RecordLowEntropyUma(LowEntropyReason::kRecognizedIdMismatch);
Eric Ortheafc5b42020-08-13 20:53:44141 return;
142 }
143
144 DCHECK(recent_recognized_id_hits_.empty() ||
145 now >= recent_recognized_id_hits_.back());
146 recent_recognized_id_hits_.push_back(now);
147 } else {
148 DCHECK_LT(recent_unrecognized_id_hits_.size(),
149 kUnrecognizedIdMismatchThreshold);
150 if (recent_unrecognized_id_hits_.size() ==
151 kUnrecognizedIdMismatchThreshold - 1) {
152 low_entropy_ = true;
Eric Ortha167bdf2020-08-18 22:15:26153 RecordLowEntropyUma(LowEntropyReason::kUnrecognizedIdMismatch);
Eric Ortheafc5b42020-08-13 20:53:44154 return;
155 }
156
157 DCHECK(recent_unrecognized_id_hits_.empty() ||
158 now >= recent_unrecognized_id_hits_.back());
159 recent_unrecognized_id_hits_.push_back(now);
160 }
161}
162
Eric Orthf8680ac2020-06-10 22:57:24163} // namespace net