• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 19741624072

27 Nov 2025 03:45PM UTC coverage: 73.086% (+0.02%) from 73.065%
19741624072

Pull #16570

github

web-flow
Merge 08a2cdb1d into f94a3f63f
Pull Request #16570: rec: rewrite all unwrap calls in web.rs

38523 of 63408 branches covered (60.75%)

Branch coverage included in aggregate %.

128044 of 164496 relevant lines covered (77.84%)

6531485.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.38
/pdns/dnsdistdist/dnsdist-concurrent-connections.cc
1
/*
2
 * This file is part of PowerDNS or dnsdist.
3
 * Copyright -- PowerDNS.COM B.V. and its contributors
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of version 2 of the GNU General Public License as
7
 * published by the Free Software Foundation.
8
 *
9
 * In addition, for the avoidance of any doubt, permission is granted to
10
 * link this program with OpenSSL and to (re)distribute the binaries
11
 * produced as the result of such linking.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
 */
22

23
#include "dnsdist-concurrent-connections.hh"
24

25
#include <boost/multi_index_container.hpp>
26
#include <boost/multi_index/ordered_index.hpp>
27
#include <boost/multi_index/hashed_index.hpp>
28
#include <boost/multi_index/key_extractors.hpp>
29

30
#include <utility>
31

32
#include "circular_buffer.hh"
33
#include "dnsdist-configuration.hh"
34
#include "dolog.hh"
35
#include "lock.hh"
36

37
namespace dnsdist
38
{
39

40
static constexpr size_t NB_SHARDS = 16;
41

42
struct ClientActivity
43
{
44
  uint64_t tcpConnections{0};
45
  uint64_t tlsNewSessions{0}; /* without resumption */
46
  uint64_t tlsResumedSessions{0};
47
  time_t bucketEndTime{0};
48
};
49

50
struct ClientEntry
51
{
52
  mutable boost::circular_buffer<ClientActivity> d_activity;
53
  AddressAndPortRange d_addr;
54
  mutable uint64_t d_concurrentConnections{0};
55
  mutable time_t d_bannedUntil{0};
56
  time_t d_lastSeen{0};
57
};
58

59
struct TimeTag
60
{
61
};
62
struct AddressTag
63
{
64
};
65

66
using map_t = boost::multi_index_container<
67
  ClientEntry,
68
  boost::multi_index::indexed_by<
69
    boost::multi_index::hashed_unique<boost::multi_index::tag<AddressTag>,
70
                                      boost::multi_index::member<ClientEntry, AddressAndPortRange, &ClientEntry::d_addr>, AddressAndPortRange::hash>,
71
    boost::multi_index::ordered_non_unique<boost::multi_index::tag<TimeTag>,
72
                                           boost::multi_index::member<ClientEntry, time_t, &ClientEntry::d_lastSeen>>>>;
73

74
static std::vector<LockGuarded<map_t>> s_tcpClientsConnectionMetrics{NB_SHARDS};
75
static std::atomic<time_t> s_nextCleanup{0};
76
static constexpr time_t INACTIVITY_DELAY{60};
77

78
static AddressAndPortRange getRange(const ComboAddress& from)
79
{
4,719✔
80
  const auto& immutable = dnsdist::configuration::getImmutableConfiguration();
4,719✔
81
  return AddressAndPortRange(from, from.isIPv4() ? immutable.d_tcpConnectionsMaskV4 : immutable.d_tcpConnectionsMaskV6, from.isIPv4() && immutable.d_tcpConnectionsMaskV4 == 32 ? immutable.d_tcpConnectionsMaskV4Port : 0);
4,719✔
82
}
4,719✔
83

84
static size_t getShardID(const AddressAndPortRange& from)
85
{
4,718✔
86
  auto hash = AddressAndPortRange::hash()(from);
4,718✔
87
  return hash % NB_SHARDS;
4,718✔
88
}
4,718✔
89

90
static bool checkTCPConnectionsRate(const boost::circular_buffer<ClientActivity>& activity, time_t now, uint64_t maxTCPRate, uint64_t maxTLSNewRate, uint64_t maxTLSResumedRate, uint64_t interval, bool isTLS)
91
{
2,422✔
92
  if (maxTCPRate == 0 && (!isTLS || (maxTLSNewRate == 0 && maxTLSResumedRate == 0))) {
2,422!
93
    return true;
2,386✔
94
  }
2,386✔
95
  uint64_t bucketsConsidered = 0;
36✔
96
  uint64_t connectionsSeen = 0;
36✔
97
  uint64_t tlsNewSeen = 0;
36✔
98
  uint64_t tlsResumedSeen = 0;
36✔
99
  const auto cutOff = static_cast<time_t>(now - (interval * 60)); // interval is in minutes
36✔
100
  for (const auto& entry : activity) {
36✔
101
    if (entry.bucketEndTime < cutOff) {
33!
102
      continue;
×
103
    }
×
104
    ++bucketsConsidered;
33✔
105
    connectionsSeen += entry.tcpConnections;
33✔
106
    tlsNewSeen += entry.tlsNewSessions;
33✔
107
    tlsResumedSeen += entry.tlsResumedSessions;
33✔
108
  }
33✔
109
  if (bucketsConsidered == 0) {
36✔
110
    return true;
3✔
111
  }
3✔
112
  if (maxTCPRate > 0) {
33✔
113
    auto rate = connectionsSeen / bucketsConsidered;
10✔
114
    if (rate > maxTCPRate) {
10✔
115
      return false;
1✔
116
    }
1✔
117
  }
10✔
118
  if (maxTLSNewRate > 0 && isTLS) {
32!
119
    auto rate = tlsNewSeen / bucketsConsidered;
23✔
120
    if (rate > maxTLSNewRate) {
23✔
121
      return false;
1✔
122
    }
1✔
123
  }
23✔
124
  if (maxTLSResumedRate > 0 && isTLS) {
31!
125
    auto rate = tlsResumedSeen / bucketsConsidered;
12✔
126
    if (rate > maxTLSResumedRate) {
12✔
127
      return false;
1✔
128
    }
1✔
129
  }
12✔
130
  return true;
30✔
131
}
31✔
132

133
void IncomingConcurrentTCPConnectionsManager::cleanup(time_t now)
134
{
35,608✔
135
  if (s_nextCleanup.load() > now) {
35,608✔
136
    return;
35,132✔
137
  }
35,132✔
138
  s_nextCleanup.store(now + 60);
476✔
139

140
  const auto& immutable = dnsdist::configuration::getImmutableConfiguration();
476✔
141
  const auto interval = immutable.d_tcpConnectionsRatePerClientInterval;
476✔
142
  const auto cutOff = static_cast<time_t>(now - (interval * 60)); // interval in minutes
476✔
143
  for (auto& shard : s_tcpClientsConnectionMetrics) {
6,479✔
144
    auto db = shard.lock();
6,479✔
145
    auto& index = db->get<TimeTag>();
6,479✔
146
    for (auto entry = index.begin(); entry != index.end();) {
6,479✔
147
      if (entry->d_lastSeen >= cutOff) {
393!
148
        /* this index is ordered on timestamps,
149
           so the first valid entry we see means we are done */
150
        break;
393✔
151
      }
393✔
152

153
      entry = index.erase(entry);
×
154
    }
×
155
  }
6,479✔
156
}
476✔
157

158
static ClientActivity& getCurrentClientActivity(const ClientEntry& entry, time_t now)
159
{
2,442✔
160
  auto& activity = entry.d_activity;
2,442✔
161
  if (activity.empty() || activity.front().bucketEndTime < now) {
2,442!
162
    activity.push_front(ClientActivity{1, 0, 0, now + INACTIVITY_DELAY});
285✔
163
  }
285✔
164
  return activity.front();
2,442✔
165
}
2,442✔
166

167
IncomingConcurrentTCPConnectionsManager::NewConnectionResult IncomingConcurrentTCPConnectionsManager::accountNewTCPConnection(const ComboAddress& from, bool isTLS)
168
{
2,818✔
169
  const auto& immutable = dnsdist::configuration::getImmutableConfiguration();
2,818✔
170
  const auto maxConnsPerClient = immutable.d_maxTCPConnectionsPerClient;
2,818✔
171
  const auto threshold = immutable.d_tcpConnectionsOverloadThreshold;
2,818✔
172
  const auto tcpRate = immutable.d_maxTCPConnectionsRatePerClient;
2,818✔
173
  const auto tlsNewRate = immutable.d_maxTLSNewSessionsRatePerClient;
2,818✔
174
  const auto tlsResumedRate = immutable.d_maxTLSResumedSessionsRatePerClient;
2,818✔
175
  const auto interval = immutable.d_tcpConnectionsRatePerClientInterval;
2,818✔
176
  if (maxConnsPerClient == 0 && tcpRate == 0 && tlsResumedRate == 0 && tlsNewRate == 0 && immutable.d_maxTCPReadIOsPerQuery == 0) {
2,818!
177
    return NewConnectionResult::Allowed;
×
178
  }
×
179

180
  auto now = time(nullptr);
2,818✔
181
  auto updateActivity = [now](ClientEntry& entry) {
2,818✔
182
    ++entry.d_concurrentConnections;
2,419✔
183
    entry.d_lastSeen = now;
2,419✔
184
    auto& activity = getCurrentClientActivity(entry, now);
2,419✔
185
    ++activity.tcpConnections;
2,419✔
186
  };
2,419✔
187

188
  auto checkConnectionAllowed = [now, from, maxConnsPerClient, threshold, tcpRate, tlsNewRate, tlsResumedRate, interval, isTLS, &immutable](const ClientEntry& entry) {
2,818✔
189
    if (entry.d_bannedUntil != 0 && entry.d_bannedUntil >= now) {
2,426!
190
      vinfolog("Refusing TCP connection from %s: banned", from.toStringWithPort());
1!
191
      return NewConnectionResult::Denied;
1✔
192
    }
1✔
193
    if (maxConnsPerClient > 0 && entry.d_concurrentConnections >= maxConnsPerClient) {
2,425✔
194
      vinfolog("Refusing TCP connection from %s: too many connections", from.toStringWithPort());
3!
195
      return NewConnectionResult::Denied;
3✔
196
    }
3✔
197
    if (!checkTCPConnectionsRate(entry.d_activity, now, tcpRate, tlsNewRate, tlsResumedRate, interval, isTLS)) {
2,422✔
198
      entry.d_bannedUntil = now + immutable.d_tcpBanDurationForExceedingTCPTLSRate;
3✔
199
      vinfolog("Banning TCP connections from %s for %d seconds: too many new TCP/TLS connections per second", from.toStringWithPort(), immutable.d_tcpBanDurationForExceedingTCPTLSRate);
3!
200
      return NewConnectionResult::Denied;
3✔
201
    }
3✔
202

203
    if (maxConnsPerClient == 0 || threshold == 0) {
2,419✔
204
      return NewConnectionResult::Allowed;
2,353✔
205
    }
2,353✔
206

207
    auto current = (100 * entry.d_concurrentConnections) / maxConnsPerClient;
66✔
208
    if (current < threshold) {
66!
209
      return NewConnectionResult::Allowed;
66✔
210
    }
66✔
211
    vinfolog("Restricting TCP connection from %s: nearly reaching the maximum number of concurrent TCP connections", from.toStringWithPort());
×
212
    return NewConnectionResult::Restricted;
×
213
  };
66✔
214

215
  auto addr = getRange(from);
2,818✔
216
  {
2,818✔
217
    auto shardID = getShardID(addr);
2,818✔
218
    auto db = s_tcpClientsConnectionMetrics.at(shardID).lock();
2,818✔
219
    const auto& entry = db->find(addr);
2,818✔
220
    if (entry == db->end()) {
2,818✔
221
      ClientEntry newEntry;
392✔
222
      newEntry.d_activity.set_capacity(interval);
392✔
223
      newEntry.d_addr = addr;
392✔
224
      newEntry.d_concurrentConnections = 1;
392✔
225
      newEntry.d_lastSeen = now;
392✔
226
      db->insert(std::move(newEntry));
392✔
227
      return NewConnectionResult::Allowed;
392✔
228
    }
392✔
229
    auto result = checkConnectionAllowed(*entry);
2,426✔
230
    if (result != NewConnectionResult::Denied) {
2,426✔
231
      db->modify(entry, updateActivity);
2,419✔
232
    }
2,419✔
233
    return result;
2,426✔
234
  }
2,818✔
235
}
2,818✔
236

237
bool IncomingConcurrentTCPConnectionsManager::isClientOverThreshold(const ComboAddress& from)
238
{
322,560✔
239
  const auto& immutable = dnsdist::configuration::getImmutableConfiguration();
322,560✔
240
  const auto maxConnsPerClient = immutable.d_maxTCPConnectionsPerClient;
322,560✔
241
  if (maxConnsPerClient == 0 || immutable.d_tcpConnectionsOverloadThreshold == 0) {
322,560!
242
    return false;
320,761✔
243
  }
320,761✔
244

245
  size_t count = 0;
1,799✔
246
  auto addr = getRange(from);
1,799✔
247
  auto shardID = getShardID(addr);
1,799✔
248
  {
1,799✔
249
    auto db = s_tcpClientsConnectionMetrics.at(shardID).lock();
1,799✔
250
    auto it = db->find(addr);
1,799✔
251
    if (it == db->end()) {
1,799!
252
      return false;
×
253
    }
×
254
    count = it->d_concurrentConnections;
1,799✔
255
  }
1,799✔
256

257
  auto current = (100 * count) / maxConnsPerClient;
×
258
  return current >= immutable.d_tcpConnectionsOverloadThreshold;
1,799✔
259
}
1,799✔
260

261
void IncomingConcurrentTCPConnectionsManager::banClientFor(const ComboAddress& from, time_t now, uint32_t seconds)
262
{
1✔
263
  auto addr = getRange(from);
1✔
264
  auto shardID = getShardID(addr);
1✔
265
  {
1✔
266
    auto db = s_tcpClientsConnectionMetrics.at(shardID).lock();
1✔
267
    auto it = db->find(addr);
1✔
268
    if (it == db->end()) {
1!
269
      return;
×
270
    }
×
271
    db->modify(it, [now, seconds](ClientEntry& entry) {
1✔
272
      entry.d_lastSeen = now;
1✔
273
      entry.d_bannedUntil = now + seconds;
1✔
274
    });
1✔
275
  }
1✔
276
  vinfolog("Banned TCP client %s for %d seconds", from.toStringWithPort(), seconds);
1!
277
}
1✔
278

279
static void editEntryIfPresent(const ComboAddress& from, const std::function<void(const ClientEntry& entry)>& callback)
280
{
99✔
281
  auto addr = getRange(from);
99✔
282
  auto shardID = getShardID(addr);
99✔
283
  {
99✔
284
    auto db = s_tcpClientsConnectionMetrics.at(shardID).lock();
99✔
285
    auto it = db->find(addr);
99✔
286
    if (it == db->end()) {
99!
287
      return;
×
288
    }
×
289
    callback(*it);
99✔
290
  }
99✔
291
}
99✔
292

293
void IncomingConcurrentTCPConnectionsManager::accountClosedTCPConnection(const ComboAddress& from)
294
{
2,959✔
295
  const auto maxConnsPerClient = dnsdist::configuration::getImmutableConfiguration().d_maxTCPConnectionsPerClient;
2,959✔
296
  if (maxConnsPerClient == 0) {
2,959✔
297
    return;
2,887✔
298
  }
2,887✔
299
  editEntryIfPresent(from, [](const ClientEntry& entry) {
78✔
300
    auto& count = entry.d_concurrentConnections;
78✔
301
    count--;
78✔
302
  });
78✔
303
}
72✔
304

305
void IncomingConcurrentTCPConnectionsManager::accountTLSNewSession(const ComboAddress& from)
306
{
484✔
307
  const auto maxRate = dnsdist::configuration::getImmutableConfiguration().d_maxTLSNewSessionsRatePerClient;
484✔
308
  if (maxRate == 0) {
484✔
309
    return;
472✔
310
  }
472✔
311
  editEntryIfPresent(from, [](const ClientEntry& entry) {
12✔
312
    auto& count = getCurrentClientActivity(entry, time(nullptr)).tlsNewSessions;
12✔
313
    count++;
12✔
314
  });
12✔
315
}
12✔
316

317
void IncomingConcurrentTCPConnectionsManager::accountTLSResumedSession(const ComboAddress& from)
318
{
236✔
319
  const auto maxRate = dnsdist::configuration::getImmutableConfiguration().d_maxTLSResumedSessionsRatePerClient;
236✔
320
  if (maxRate == 0) {
236✔
321
    return;
225✔
322
  }
225✔
323
  editEntryIfPresent(from, [](const ClientEntry& entry) {
11✔
324
    auto& count = getCurrentClientActivity(entry, time(nullptr)).tlsResumedSessions;
11✔
325
    count++;
11✔
326
  });
11✔
327
}
11✔
328

329
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc