• 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

67.29
/pdns/dnsdistdist/doh3.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 "doh3.hh"
24

25
#ifdef HAVE_DNS_OVER_HTTP3
26
#include <quiche.h>
27

28
#include "dolog.hh"
29
#include "iputils.hh"
30
#include "misc.hh"
31
#include "sstuff.hh"
32
#include "threadname.hh"
33
#include "base64.hh"
34

35
#include "dnsdist-dnsparser.hh"
36
#include "dnsdist-ecs.hh"
37
#include "dnsdist-proxy-protocol.hh"
38
#include "dnsdist-tcp.hh"
39
#include "dnsdist-random.hh"
40

41
#include "doq-common.hh"
42

43
#if 0
44
#define DEBUGLOG_ENABLED
45
#define DEBUGLOG(x) std::cerr << x << std::endl;
46
#else
47
#define DEBUGLOG(x)
48
#endif
49

50
using namespace dnsdist::doq;
51

52
class H3Connection
53
{
54
public:
55
  H3Connection(const ComboAddress& peer, const ComboAddress& localAddr, QuicheConfig config, QuicheConnection&& conn) :
56
    d_peer(peer), d_localAddr(localAddr), d_conn(std::move(conn)), d_config(std::move(config))
43✔
57
  {
43✔
58
  }
43✔
59
  H3Connection(const H3Connection&) = delete;
60
  H3Connection(H3Connection&&) = default;
43✔
61
  H3Connection& operator=(const H3Connection&) = delete;
62
  H3Connection& operator=(H3Connection&&) = default;
63
  ~H3Connection() = default;
84✔
64

65
  std::shared_ptr<const std::string> getSNI()
66
  {
41✔
67
    if (!d_sni) {
41!
68
      d_sni = std::make_shared<const std::string>(getSNIFromQuicheConnection(d_conn));
41✔
69
    }
41✔
70
    return d_sni;
41✔
71
  }
41✔
72

73
  ComboAddress d_peer;
74
  ComboAddress d_localAddr;
75
  QuicheConnection d_conn;
76
  QuicheConfig d_config;
77
  QuicheHTTP3Connection d_http3{nullptr, quiche_h3_conn_free};
78
  // buffer request headers by streamID
79
  std::unordered_map<uint64_t, dnsdist::doh3::h3_headers_t> d_headersBuffers;
80
  std::unordered_map<uint64_t, PacketBuffer> d_streamBuffers;
81
  std::unordered_map<uint64_t, PacketBuffer> d_streamOutBuffers;
82
  std::shared_ptr<const std::string> d_sni{nullptr};
83
};
84

85
static void sendBackDOH3Unit(DOH3UnitUniquePtr&& unit, const char* description);
86

87
struct DOH3ServerConfig
88
{
89
  DOH3ServerConfig(QuicheConfig&& config_, QuicheHTTP3Config&& http3config_, uint32_t internalPipeBufferSize) :
90
    config(std::move(config_)), http3config(std::move(http3config_))
17✔
91
  {
17✔
92
    {
17✔
93
      auto [sender, receiver] = pdns::channel::createObjectQueue<DOH3Unit>(pdns::channel::SenderBlockingMode::SenderNonBlocking, pdns::channel::ReceiverBlockingMode::ReceiverNonBlocking, internalPipeBufferSize);
17✔
94
      d_responseSender = std::move(sender);
17✔
95
      d_responseReceiver = std::move(receiver);
17✔
96
    }
17✔
97
  }
17✔
98
  DOH3ServerConfig(const DOH3ServerConfig&) = delete;
99
  DOH3ServerConfig(DOH3ServerConfig&&) = default;
100
  DOH3ServerConfig& operator=(const DOH3ServerConfig&) = delete;
101
  DOH3ServerConfig& operator=(DOH3ServerConfig&&) = default;
102
  ~DOH3ServerConfig() = default;
×
103

104
  using ConnectionsMap = std::map<PacketBuffer, H3Connection>;
105

106
  ConnectionsMap d_connections;
107
  QuicheConfig config;
108
  QuicheHTTP3Config http3config;
109
  ClientState* clientState{nullptr};
110
  std::shared_ptr<DOH3Frontend> df{nullptr};
111
  pdns::channel::Sender<DOH3Unit> d_responseSender;
112
  pdns::channel::Receiver<DOH3Unit> d_responseReceiver;
113
};
114

115
/* these might seem useless, but they are needed because
116
   they need to be declared _after_ the definition of DOH3ServerConfig
117
   so that we can use a unique_ptr in DOH3Frontend */
118
DOH3Frontend::DOH3Frontend() = default;
34✔
119
DOH3Frontend::~DOH3Frontend() = default;
×
120

121
class DOH3TCPCrossQuerySender final : public TCPQuerySender
122
{
123
public:
124
  DOH3TCPCrossQuerySender() = default;
816✔
125

126
  [[nodiscard]] bool active() const override
127
  {
40✔
128
    return true;
40✔
129
  }
40✔
130

131
  void handleResponse([[maybe_unused]] const struct timeval& now, TCPResponse&& response) override
132
  {
20✔
133
    if (!response.d_idstate.doh3u) {
20!
134
      return;
×
135
    }
×
136

137
    auto unit = std::move(response.d_idstate.doh3u);
20✔
138
    if (unit->dsc == nullptr) {
20!
139
      return;
×
140
    }
×
141

142
    unit->response = std::move(response.d_buffer);
20✔
143
    unit->ids = std::move(response.d_idstate);
20✔
144
    DNSResponse dnsResponse(unit->ids, unit->response, unit->downstream);
20✔
145

146
    dnsheader cleartextDH{};
20✔
147
    memcpy(&cleartextDH, dnsResponse.getHeader().get(), sizeof(cleartextDH));
20✔
148

149
    if (!response.isAsync()) {
20!
150

151
      dnsResponse.ids.doh3u = std::move(unit);
20✔
152

153
      if (!processResponse(dnsResponse.ids.doh3u->response, dnsResponse, false)) {
20!
154
        if (dnsResponse.ids.doh3u) {
×
155

156
          sendBackDOH3Unit(std::move(dnsResponse.ids.doh3u), "Response dropped by rules");
×
157
        }
×
158
        return;
×
159
      }
×
160

161
      if (dnsResponse.isAsynchronous()) {
20!
162
        return;
×
163
      }
×
164

165
      unit = std::move(dnsResponse.ids.doh3u);
20✔
166
    }
20✔
167

168
    if (!unit->ids.selfGenerated) {
20!
169
      double udiff = unit->ids.queryRealTime.udiff();
20✔
170
      vinfolog("Got answer from %s, relayed to %s (DoH3, %d bytes), took %f us", unit->downstream->d_config.remote.toStringWithPort(), unit->ids.origRemote.toStringWithPort(), unit->response.size(), udiff);
20✔
171

172
      auto backendProtocol = unit->downstream->getProtocol();
20✔
173
      if (backendProtocol == dnsdist::Protocol::DoUDP && unit->tcp) {
20!
174
        backendProtocol = dnsdist::Protocol::DoTCP;
18✔
175
      }
18✔
176
      handleResponseSent(unit->ids, udiff, unit->ids.origRemote, unit->downstream->d_config.remote, unit->response.size(), cleartextDH, backendProtocol, true);
20✔
177
    }
20✔
178

179
    ++dnsdist::metrics::g_stats.responses;
20✔
180
    if (unit->ids.cs != nullptr) {
20!
181
      ++unit->ids.cs->responses;
20✔
182
    }
20✔
183

184
    sendBackDOH3Unit(std::move(unit), "Cross-protocol response");
20✔
185
  }
20✔
186

187
  void handleXFRResponse(const struct timeval& now, TCPResponse&& response) override
188
  {
×
189
    return handleResponse(now, std::move(response));
×
190
  }
×
191

192
  void notifyIOError([[maybe_unused]] const struct timeval& now, TCPResponse&& response) override
193
  {
×
194
    if (!response.d_idstate.doh3u) {
×
195
      return;
×
196
    }
×
197

198
    auto unit = std::move(response.d_idstate.doh3u);
×
199
    if (unit->dsc == nullptr) {
×
200
      return;
×
201
    }
×
202

203
    /* this will signal an error */
204
    unit->response.clear();
×
205
    unit->ids = std::move(response.d_idstate);
×
206
    sendBackDOH3Unit(std::move(unit), "Cross-protocol error");
×
207
  }
×
208
};
209

210
class DOH3CrossProtocolQuery : public CrossProtocolQuery
211
{
212
public:
213
  DOH3CrossProtocolQuery(DOH3UnitUniquePtr&& unit, bool isResponse)
214
  {
23✔
215
    if (isResponse) {
23!
216
      /* happens when a response becomes async */
217
      query = InternalQuery(std::move(unit->response), std::move(unit->ids));
×
218
    }
×
219
    else {
23✔
220
      /* we need to duplicate the query here because we might need
221
         the existing query later if we get a truncated answer */
222
      query = InternalQuery(PacketBuffer(unit->query), std::move(unit->ids));
23✔
223
    }
23✔
224

225
    /* it might have been moved when we moved unit->ids */
226
    if (unit) {
23✔
227
      query.d_idstate.doh3u = std::move(unit);
3✔
228
    }
3✔
229

230
    /* we _could_ remove it from the query buffer and put in query's d_proxyProtocolPayload,
231
       clearing query.d_proxyProtocolPayloadAdded and unit->proxyProtocolPayloadSize.
232
       Leave it for now because we know that the onky case where the payload has been
233
       added is when we tried over UDP, got a TC=1 answer and retried over TCP/DoT,
234
       and we know the TCP/DoT code can handle it. */
235
    query.d_proxyProtocolPayloadAdded = query.d_idstate.doh3u->proxyProtocolPayloadSize > 0;
23✔
236
    downstream = query.d_idstate.doh3u->downstream;
23✔
237
  }
23✔
238

239
  void handleInternalError()
240
  {
×
241
    sendBackDOH3Unit(std::move(query.d_idstate.doh3u), "DOH3 internal error");
×
242
  }
×
243

244
  std::shared_ptr<TCPQuerySender> getTCPQuerySender() override
245
  {
23✔
246
    query.d_idstate.doh3u->downstream = downstream;
23✔
247
    return s_sender;
23✔
248
  }
23✔
249

250
  DNSQuestion getDQ() override
251
  {
3✔
252
    auto& ids = query.d_idstate;
3✔
253
    DNSQuestion dnsQuestion(ids, query.d_buffer);
3✔
254
    return dnsQuestion;
3✔
255
  }
3✔
256

257
  DNSResponse getDR() override
258
  {
×
259
    auto& ids = query.d_idstate;
×
260
    DNSResponse dnsResponse(ids, query.d_buffer, downstream);
×
261
    return dnsResponse;
×
262
  }
×
263

264
  DOH3UnitUniquePtr&& releaseDU()
265
  {
×
266
    return std::move(query.d_idstate.doh3u);
×
267
  }
×
268

269
private:
270
  static std::shared_ptr<DOH3TCPCrossQuerySender> s_sender;
271
};
272

273
std::shared_ptr<DOH3TCPCrossQuerySender> DOH3CrossProtocolQuery::s_sender = std::make_shared<DOH3TCPCrossQuerySender>();
274

275
static bool tryWriteResponse(H3Connection& conn, const uint64_t streamID, PacketBuffer& response)
276
{
×
277
  size_t pos = 0;
×
278
  while (pos < response.size()) {
×
279
    // send_body takes care of setting fin to false if it cannot send the entire content so we can try again.
280
    auto res = quiche_h3_send_body(conn.d_http3.get(), conn.d_conn.get(),
×
281
                                   streamID, &response.at(pos), response.size() - pos, true);
×
282
    if (res == QUICHE_H3_ERR_DONE || res == QUICHE_H3_TRANSPORT_ERR_DONE) {
×
283
      response.erase(response.begin(), response.begin() + static_cast<ssize_t>(pos));
×
284
      return false;
×
285
    }
×
286
    if (res < 0) {
×
287
      // Shutdown with internal error code
288
      quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(dnsdist::doq::DOQ_Error_Codes::DOQ_INTERNAL_ERROR));
×
289
      return true;
×
290
    }
×
291
    pos += res;
×
292
  }
×
293

294
  return true;
×
295
}
×
296

297
static void addHeaderToList(std::vector<quiche_h3_header>& headers, const char* name, size_t nameLen, const char* value, size_t valueLen)
298
{
109✔
299
  headers.emplace_back((quiche_h3_header){
109✔
300
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
301
    .name = reinterpret_cast<const uint8_t*>(name),
109✔
302
    .name_len = nameLen,
109✔
303
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
304
    .value = reinterpret_cast<const uint8_t*>(value),
109✔
305
    .value_len = valueLen,
109✔
306
  });
109✔
307
}
109✔
308

309
static void h3_send_response(H3Connection& conn, const uint64_t streamID, uint16_t statusCode, const uint8_t* body, size_t len, const std::string& contentType = {})
310
{
36✔
311
  std::string status = std::to_string(statusCode);
36✔
312
  PacketBuffer location;
36✔
313
  PacketBuffer responseBody;
36✔
314
  std::vector<quiche_h3_header> headers;
36✔
315
  headers.reserve(4);
36✔
316
  addHeaderToList(headers, ":status", sizeof(":status") - 1, status.data(), status.size());
36✔
317

318
  if (statusCode >= 300 && statusCode < 400) {
36!
319
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
320
    addHeaderToList(headers, "location", sizeof("location") - 1, reinterpret_cast<const char*>(body), len);
1✔
321
    static const std::string s_redirectStart{"<!DOCTYPE html><TITLE>Moved</TITLE><P>The document has moved <A HREF=\""};
1✔
322
    static const std::string s_redirectEnd{"\">here</A>"};
1✔
323
    static const std::string s_redirectContentType("text/html; charset=utf-8");
1✔
324
    addHeaderToList(headers, "content-type", sizeof("content-type") - 1, s_redirectContentType.data(), s_redirectContentType.size());
1✔
325
    responseBody.reserve(s_redirectStart.size() + len + s_redirectEnd.size());
1✔
326
    responseBody.insert(responseBody.begin(), s_redirectStart.begin(), s_redirectStart.end());
1✔
327
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
328
    responseBody.insert(responseBody.end(), body, body + len);
1✔
329
    responseBody.insert(responseBody.end(), s_redirectEnd.begin(), s_redirectEnd.end());
1✔
330
    body = responseBody.data();
1✔
331
    len = responseBody.size();
1✔
332
  }
1✔
333
  else if (len > 0 && (statusCode == 200U || !contentType.empty())) {
35!
334
    // do not include content-type header info if there is no content
335
    addHeaderToList(headers, "content-type", sizeof("content-type") - 1, contentType.empty() ? "application/dns-message" : contentType.data(), contentType.empty() ? sizeof("application/dns-message") - 1 : contentType.size());
35✔
336
  }
35✔
337

338
  const std::string lenStr = std::to_string(len);
36✔
339
  addHeaderToList(headers, "content-length", sizeof("content-length") - 1, lenStr.data(), lenStr.size());
36✔
340

341
  auto returnValue = quiche_h3_send_response(conn.d_http3.get(), conn.d_conn.get(),
36✔
342
                                             streamID, headers.data(),
36✔
343
                                             headers.size(),
36✔
344
                                             len == 0);
36✔
345
  if (returnValue != 0) {
36!
346
    /* in theory it could be QUICHE_H3_ERR_STREAM_BLOCKED if the stream is not writable / congested, but we are not going to handle this case */
347
    quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(dnsdist::doq::DOQ_Error_Codes::DOQ_INTERNAL_ERROR));
×
348
    return;
×
349
  }
×
350

351
  if (len == 0) {
36!
352
    return;
×
353
  }
×
354

355
  size_t pos = 0;
36✔
356
  while (pos < len) {
72✔
357
    // send_body takes care of setting fin to false if it cannot send the entire content so we can try again.
358
    auto res = quiche_h3_send_body(conn.d_http3.get(), conn.d_conn.get(),
36✔
359
                                   // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic): Quiche API
360
                                   streamID, const_cast<uint8_t*>(body) + pos, len - pos, true);
36✔
361
    if (res == QUICHE_H3_ERR_DONE || res == QUICHE_H3_TRANSPORT_ERR_DONE) {
36!
362
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic): Quiche API
363
      conn.d_streamOutBuffers[streamID] = PacketBuffer(body + pos, body + len);
×
364
      return;
×
365
    }
×
366
    if (res < 0) {
36!
367
      // Shutdown with internal error code
368
      quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(1));
×
369
      return;
×
370
    }
×
371
    pos += res;
36✔
372
  }
36✔
373
}
36✔
374

375
static void h3_send_response(H3Connection& conn, const uint64_t streamID, uint16_t statusCode, const std::string& content = {})
376
{
×
377
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
378
  h3_send_response(conn, streamID, statusCode, reinterpret_cast<const uint8_t*>(content.data()), content.size());
×
379
}
×
380

381
static void handleResponse(DOH3Frontend& frontend, H3Connection& conn, const uint64_t streamID, uint16_t statusCode, const PacketBuffer& response, const std::string& contentType)
382
{
41✔
383
  if (statusCode == 200) {
41✔
384
    ++frontend.d_validResponses;
35✔
385
  }
35✔
386
  else {
6✔
387
    ++frontend.d_errorResponses;
6✔
388
  }
6✔
389
  if (response.empty()) {
41✔
390
    quiche_conn_stream_shutdown(conn.d_conn.get(), streamID, QUICHE_SHUTDOWN_WRITE, static_cast<uint64_t>(DOQ_Error_Codes::DOQ_UNSPECIFIED_ERROR));
5✔
391
  }
5✔
392
  else {
36✔
393
    h3_send_response(conn, streamID, statusCode, &response.at(0), response.size(), contentType);
36✔
394
  }
36✔
395
}
41✔
396

397
void DOH3Frontend::setup()
398
{
17✔
399
  auto config = QuicheConfig(quiche_config_new(QUICHE_PROTOCOL_VERSION), quiche_config_free);
17✔
400
  d_quicheParams.d_alpn = std::string(DOH3_ALPN.begin(), DOH3_ALPN.end());
17✔
401
  configureQuiche(config, d_quicheParams, true);
17✔
402

403
  auto http3config = QuicheHTTP3Config(quiche_h3_config_new(), quiche_h3_config_free);
17✔
404

405
  d_server_config = std::make_unique<DOH3ServerConfig>(std::move(config), std::move(http3config), d_internalPipeBufferSize);
17✔
406
}
17✔
407

408
void DOH3Frontend::reloadCertificates()
409
{
×
410
  auto config = QuicheConfig(quiche_config_new(QUICHE_PROTOCOL_VERSION), quiche_config_free);
×
411
  d_quicheParams.d_alpn = std::string(DOH3_ALPN.begin(), DOH3_ALPN.end());
×
412
  configureQuiche(config, d_quicheParams, true);
×
413
  std::atomic_store_explicit(&d_server_config->config, std::move(config), std::memory_order_release);
×
414
}
×
415

416
static std::optional<std::reference_wrapper<H3Connection>> getConnection(DOH3ServerConfig::ConnectionsMap& connMap, const PacketBuffer& connID)
417
{
348✔
418
  auto iter = connMap.find(connID);
348✔
419
  if (iter == connMap.end()) {
348✔
420
    return std::nullopt;
86✔
421
  }
86✔
422
  return iter->second;
262✔
423
}
348✔
424

425
static void sendBackDOH3Unit(DOH3UnitUniquePtr&& unit, const char* description)
426
{
20✔
427
  if (unit->dsc == nullptr) {
20!
428
    return;
×
429
  }
×
430
  try {
20✔
431
    if (!unit->dsc->d_responseSender.send(std::move(unit))) {
20!
432
      ++dnsdist::metrics::g_stats.doh3ResponsePipeFull;
×
433
      vinfolog("Unable to pass a %s to the DoH3 worker thread because the pipe is full", description);
×
434
    }
×
435
  }
20✔
436
  catch (const std::exception& e) {
20✔
437
    vinfolog("Unable to pass a %s to the DoH3 worker thread because we couldn't write to the pipe: %s", description, e.what());
×
438
  }
×
439
}
20✔
440

441
static std::optional<std::reference_wrapper<H3Connection>> createConnection(DOH3ServerConfig& config, const PacketBuffer& serverSideID, const PacketBuffer& originalDestinationID, const ComboAddress& localAddr, const ComboAddress& peer)
442
{
43✔
443
  auto quicheConfig = std::atomic_load_explicit(&config.config, std::memory_order_acquire);
43✔
444
  auto quicheConn = QuicheConnection(quiche_accept(serverSideID.data(), serverSideID.size(),
43✔
445
                                                   originalDestinationID.data(), originalDestinationID.size(),
43✔
446
                                                   // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
447
                                                   reinterpret_cast<const struct sockaddr*>(&localAddr),
43✔
448
                                                   localAddr.getSocklen(),
43✔
449
                                                   // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
450
                                                   reinterpret_cast<const struct sockaddr*>(&peer),
43✔
451
                                                   peer.getSocklen(),
43✔
452
                                                   quicheConfig.get()),
43✔
453
                                     quiche_conn_free);
43✔
454

455
  if (config.df && !config.df->d_quicheParams.d_keyLogFile.empty()) {
43!
456
    quiche_conn_set_keylog_path(quicheConn.get(), config.df->d_quicheParams.d_keyLogFile.c_str());
19✔
457
  }
19✔
458

459
  auto conn = H3Connection(peer, localAddr, std::move(quicheConfig), std::move(quicheConn));
43✔
460
  auto pair = config.d_connections.emplace(serverSideID, std::move(conn));
43✔
461
  return pair.first->second;
43✔
462
}
43✔
463

464
std::unique_ptr<CrossProtocolQuery> getDOH3CrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion, bool isResponse)
465
{
3✔
466
  if (!dnsQuestion.ids.doh3u) {
3!
467
    throw std::runtime_error("Trying to create a DoH3 cross protocol query without a valid DoH3 unit");
×
468
  }
×
469

470
  auto unit = std::move(dnsQuestion.ids.doh3u);
3✔
471
  if (&dnsQuestion.ids != &unit->ids) {
3!
472
    unit->ids = std::move(dnsQuestion.ids);
3✔
473
  }
3✔
474

475
  unit->ids.origID = dnsQuestion.getHeader()->id;
3✔
476

477
  if (!isResponse) {
3!
478
    if (unit->query.data() != dnsQuestion.getMutableData().data()) {
3!
479
      unit->query = std::move(dnsQuestion.getMutableData());
3✔
480
    }
3✔
481
  }
3✔
482
  else {
×
483
    if (unit->response.data() != dnsQuestion.getMutableData().data()) {
×
484
      unit->response = std::move(dnsQuestion.getMutableData());
×
485
    }
×
486
  }
×
487

488
  return std::make_unique<DOH3CrossProtocolQuery>(std::move(unit), isResponse);
3✔
489
}
3✔
490

491
static void processDOH3Query(DOH3UnitUniquePtr&& doh3Unit)
492
{
41✔
493
  const auto handleImmediateResponse = [](DOH3UnitUniquePtr&& unit, [[maybe_unused]] const char* reason) {
41✔
494
    DEBUGLOG("handleImmediateResponse() reason=" << reason);
21✔
495
    auto conn = getConnection(unit->dsc->df->d_server_config->d_connections, unit->serverConnID);
21✔
496
    handleResponse(*unit->dsc->df, *conn, unit->streamID, unit->status_code, unit->response, unit->d_contentTypeOut);
21✔
497
    unit->ids.doh3u.reset();
21✔
498
  };
21✔
499

500
  auto& ids = doh3Unit->ids;
41✔
501
  ids.doh3u = std::move(doh3Unit);
41✔
502
  auto& unit = ids.doh3u;
41✔
503
  uint16_t queryId = 0;
41✔
504
  ComboAddress remote;
41✔
505

506
  try {
41✔
507

508
    remote = unit->ids.origRemote;
41✔
509
    DOH3ServerConfig* dsc = unit->dsc;
41✔
510
    ClientState& clientState = *dsc->clientState;
41✔
511

512
    if (!dnsdist::configuration::getCurrentRuntimeConfiguration().d_ACL.match(remote)) {
41✔
513
      vinfolog("Query from %s (DoH3) dropped because of ACL", remote.toStringWithPort());
1!
514
      ++dnsdist::metrics::g_stats.aclDrops;
1✔
515
      unit->response.clear();
1✔
516

517
      unit->status_code = 403;
1✔
518
      handleImmediateResponse(std::move(unit), "DoH3 query dropped because of ACL");
1✔
519
      return;
1✔
520
    }
1✔
521

522
    if (unit->query.size() < sizeof(dnsheader)) {
40!
523
      ++dnsdist::metrics::g_stats.nonCompliantQueries;
×
524
      ++clientState.nonCompliantQueries;
×
525
      unit->response.clear();
×
526

527
      unit->status_code = 400;
×
528
      handleImmediateResponse(std::move(unit), "DoH3 non-compliant query");
×
529
      return;
×
530
    }
×
531

532
    ++clientState.queries;
40✔
533
    ++dnsdist::metrics::g_stats.queries;
40✔
534
    unit->ids.queryRealTime.start();
40✔
535

536
    {
40✔
537
      /* don't keep that pointer around, it will be invalidated if the buffer is ever resized */
538
      dnsheader_aligned dnsHeader(unit->query.data());
40✔
539

540
      if (!checkQueryHeaders(*dnsHeader, clientState)) {
40!
541
        dnsdist::PacketMangling::editDNSHeaderFromPacket(unit->query, [](dnsheader& header) {
×
542
          header.rcode = RCode::ServFail;
×
543
          header.qr = true;
×
544
          return true;
×
545
        });
×
546
        unit->response = std::move(unit->query);
×
547

548
        unit->status_code = 400;
×
549
        handleImmediateResponse(std::move(unit), "DoH3 invalid headers");
×
550
        return;
×
551
      }
×
552

553
      if (dnsHeader->qdcount == 0) {
40!
554
        dnsdist::PacketMangling::editDNSHeaderFromPacket(unit->query, [](dnsheader& header) {
×
555
          header.rcode = RCode::NotImp;
×
556
          header.qr = true;
×
557
          return true;
×
558
        });
×
559
        unit->response = std::move(unit->query);
×
560

561
        unit->status_code = 400;
×
562
        handleImmediateResponse(std::move(unit), "DoH3 empty query");
×
563
        return;
×
564
      }
×
565

566
      queryId = ntohs(dnsHeader->id);
40✔
567
    }
40✔
568

569
    auto downstream = unit->downstream;
×
570
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
571
    unit->ids.qname = DNSName(reinterpret_cast<const char*>(unit->query.data()), static_cast<int>(unit->query.size()), sizeof(dnsheader), false, &unit->ids.qtype, &unit->ids.qclass);
40✔
572
    DNSQuestion dnsQuestion(unit->ids, unit->query);
40✔
573
    dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [&ids](dnsheader& header) {
40✔
574
      const uint16_t* flags = getFlagsFromDNSHeader(&header);
40✔
575
      ids.origFlags = *flags;
40✔
576
      return true;
40✔
577
    });
40✔
578
    if (unit->sni) {
40!
579
      dnsQuestion.sni = *unit->sni;
40✔
580
    }
40✔
581
    unit->ids.cs = &clientState;
40✔
582

583
    auto result = processQuery(dnsQuestion, downstream);
40✔
584
    if (result == ProcessQueryResult::Drop) {
40✔
585
      unit->status_code = 403;
4✔
586
      handleImmediateResponse(std::move(unit), "DoH3 dropped query");
4✔
587
      return;
4✔
588
    }
4✔
589
    if (result == ProcessQueryResult::Asynchronous) {
36!
590
      return;
×
591
    }
×
592
    if (result == ProcessQueryResult::SendAnswer) {
36✔
593
      if (unit->response.empty()) {
16✔
594
        unit->response = std::move(unit->query);
13✔
595
      }
13✔
596
      if (unit->response.size() >= sizeof(dnsheader)) {
16✔
597
        const dnsheader_aligned dnsHeader(unit->response.data());
15✔
598

599
        handleResponseSent(unit->ids.qname, QType(unit->ids.qtype), 0., unit->ids.origDest, ComboAddress(), unit->response.size(), *dnsHeader, dnsdist::Protocol::DoH3, dnsdist::Protocol::DoH3, false);
15✔
600
      }
15✔
601
      handleImmediateResponse(std::move(unit), "DoH3 self-answered response");
16✔
602
      return;
16✔
603
    }
16✔
604

605
    ++dnsdist::metrics::g_stats.responses;
20✔
606
    if (unit->ids.cs != nullptr) {
20!
607
      ++unit->ids.cs->responses;
20✔
608
    }
20✔
609

610
    if (result != ProcessQueryResult::PassToBackend) {
20!
611
      unit->status_code = 500;
×
612
      handleImmediateResponse(std::move(unit), "DoH3 no backend available");
×
613
      return;
×
614
    }
×
615

616
    if (downstream == nullptr) {
20!
617
      unit->status_code = 502;
×
618
      handleImmediateResponse(std::move(unit), "DoH3 no backend available");
×
619
      return;
×
620
    }
×
621

622
    unit->downstream = downstream;
20✔
623

624
    std::string proxyProtocolPayload;
20✔
625
    /* we need to do this _before_ creating the cross protocol query because
626
       after that the buffer will have been moved */
627
    if (downstream->d_config.useProxyProtocol) {
20!
628
      proxyProtocolPayload = getProxyProtocolPayload(dnsQuestion);
×
629
    }
×
630

631
    unit->ids.origID = htons(queryId);
20✔
632
    unit->tcp = true;
20✔
633

634
    /* this moves unit->ids, careful! */
635
    auto cpq = std::make_unique<DOH3CrossProtocolQuery>(std::move(unit), false);
20✔
636
    cpq->query.d_proxyProtocolPayload = std::move(proxyProtocolPayload);
20✔
637

638
    if (downstream->passCrossProtocolQuery(std::move(cpq))) {
20!
639
      return;
20✔
640
    }
20✔
641
    // NOLINTNEXTLINE(bugprone-use-after-move): it was only moved if the call succeeded
642
    unit = cpq->releaseDU();
×
643
    unit->status_code = 500;
×
644
    handleImmediateResponse(std::move(unit), "DoH3 internal error");
×
645
    return;
×
646
  }
20✔
647
  catch (const std::exception& e) {
41✔
648
    vinfolog("Got an error in DOH3 question thread while parsing a query from %s, id %d: %s", remote.toStringWithPort(), queryId, e.what());
×
649
    unit->status_code = 500;
×
650
    handleImmediateResponse(std::move(unit), "DoH3 internal error");
×
651
    return;
×
652
  }
×
653
}
41✔
654

655
static void doh3_dispatch_query(DOH3ServerConfig& dsc, PacketBuffer&& query, const ComboAddress& local, const ComboAddress& remote, const PacketBuffer& serverConnID, const uint64_t streamID, const std::shared_ptr<const std::string>& sni, dnsdist::doh3::h3_headers_t&& headers)
656
{
41✔
657
  try {
41✔
658
    auto unit = std::make_unique<DOH3Unit>(std::move(query));
41✔
659
    unit->dsc = &dsc;
41✔
660
    unit->ids.origDest = local;
41✔
661
    unit->ids.origRemote = remote;
41✔
662
    unit->ids.protocol = dnsdist::Protocol::DoH3;
41✔
663
    unit->serverConnID = serverConnID;
41✔
664
    unit->streamID = streamID;
41✔
665
    unit->sni = sni;
41✔
666
    unit->headers = std::move(headers);
41✔
667

668
    processDOH3Query(std::move(unit));
41✔
669
  }
41✔
670
  catch (const std::exception& exp) {
41✔
671
    vinfolog("Had error handling DoH3 DNS packet from %s: %s", remote.toStringWithPort(), exp.what());
×
672
  }
×
673
}
41✔
674

675
static void flushResponses(pdns::channel::Receiver<DOH3Unit>& receiver)
676
{
20✔
677
  for (;;) {
40✔
678
    try {
40✔
679
      auto tmp = receiver.receive();
40✔
680
      if (!tmp) {
40✔
681
        return;
20✔
682
      }
20✔
683

684
      auto unit = std::move(*tmp);
20✔
685
      auto conn = getConnection(unit->dsc->df->d_server_config->d_connections, unit->serverConnID);
20✔
686
      if (conn) {
20!
687
        handleResponse(*unit->dsc->df, *conn, unit->streamID, unit->status_code, unit->response, unit->d_contentTypeOut);
20✔
688
      }
20✔
689
    }
20✔
690
    catch (const std::exception& e) {
40✔
691
      errlog("Error while processing response received over DoH3: %s", e.what());
×
692
    }
×
693
    catch (...) {
40✔
694
      errlog("Unspecified error while processing response received over DoH3");
×
695
    }
×
696
  }
40✔
697
}
20✔
698

699
static void flushStalledResponses(H3Connection& conn)
700
{
372✔
701
  for (auto streamIt = conn.d_streamOutBuffers.begin(); streamIt != conn.d_streamOutBuffers.end();) {
372!
702
    const auto streamID = streamIt->first;
×
703
    auto& response = streamIt->second;
×
704
    if (quiche_conn_stream_writable(conn.d_conn.get(), streamID, response.size()) == 1) {
×
705
      if (tryWriteResponse(conn, streamID, response)) {
×
706
        streamIt = conn.d_streamOutBuffers.erase(streamIt);
×
707
        continue;
×
708
      }
×
709
    }
×
710
    ++streamIt;
×
711
  }
×
712
}
372✔
713

714
static void processH3HeaderEvent(ClientState& clientState, DOH3Frontend& frontend, H3Connection& conn, const ComboAddress& client, const PacketBuffer& serverConnID, const uint64_t streamID, quiche_h3_event* event)
715
{
41✔
716
  auto handleImmediateError = [&clientState, &frontend, &conn, streamID](const char* msg) {
41✔
717
    DEBUGLOG(msg);
×
718
    ++dnsdist::metrics::g_stats.nonCompliantQueries;
×
719
    ++clientState.nonCompliantQueries;
×
720
    ++frontend.d_errorResponses;
×
721
    h3_send_response(conn, streamID, 400, msg);
×
722
  };
×
723

724
  auto& headers = conn.d_headersBuffers.at(streamID);
41✔
725
  // Callback result. Any value other than 0 will interrupt further header processing.
726
  int cbresult = quiche_h3_event_for_each_header(
41✔
727
    event,
41✔
728
    [](uint8_t* name, size_t name_len, uint8_t* value, size_t value_len, void* argp) -> int {
174✔
729
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
730
      std::string_view key(reinterpret_cast<char*>(name), name_len);
174✔
731
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
732
      std::string_view content(reinterpret_cast<char*>(value), value_len);
174✔
733
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
734
      auto* headersptr = reinterpret_cast<dnsdist::doh3::h3_headers_t*>(argp);
174✔
735
      headersptr->emplace(key, content);
174✔
736
      return 0;
174✔
737
    },
174✔
738
    &headers);
41✔
739

740
#ifdef DEBUGLOG_ENABLED
741
  DEBUGLOG("Processed headers of stream " << streamID);
742
  for (const auto& [key, value] : headers) {
743
    DEBUGLOG(" " << key << ": " << value);
744
  }
745
#endif
746
  if (cbresult != 0 || headers.count(":method") == 0) {
41!
747
    handleImmediateError("Unable to process query headers");
×
748
    return;
×
749
  }
×
750

751
  if (headers.at(":method") == "GET") {
41✔
752
    if (headers.count(":path") == 0 || headers.at(":path").empty()) {
37!
753
      handleImmediateError("Path not found");
×
754
      return;
×
755
    }
×
756
    const auto& path = headers.at(":path");
37✔
757
    auto payload = dnsdist::doh::getPayloadFromPath(path);
37✔
758
    if (!payload) {
37!
759
      handleImmediateError("Unable to find the DNS parameter");
×
760
      return;
×
761
    }
×
762
    if (payload->size() < sizeof(dnsheader)) {
37!
763
      handleImmediateError("DoH3 non-compliant query");
×
764
      return;
×
765
    }
×
766
    DEBUGLOG("Dispatching GET query");
37✔
767
    doh3_dispatch_query(*(frontend.d_server_config), std::move(*payload), conn.d_localAddr, client, serverConnID, streamID, conn.getSNI(), std::move(headers));
37✔
768
    conn.d_streamBuffers.erase(streamID);
37✔
769
    conn.d_headersBuffers.erase(streamID);
37✔
770
    return;
37✔
771
  }
37✔
772

773
  if (headers.at(":method") == "POST") {
4!
774
#if defined(HAVE_QUICHE_H3_EVENT_HEADERS_HAS_MORE_FRAMES)
4✔
775
    if (!quiche_h3_event_headers_has_more_frames(event)) {
4!
776
#else
777
    if (!quiche_h3_event_headers_has_body(event)) {
778
#endif
779
      handleImmediateError("Empty POST query");
×
780
    }
×
781
    return;
4✔
782
  }
4✔
783

784
  handleImmediateError("Unsupported HTTP method");
×
785
}
×
786

787
static void processH3DataEvent(ClientState& clientState, DOH3Frontend& frontend, H3Connection& conn, const ComboAddress& client, const PacketBuffer& serverConnID, const uint64_t streamID, PacketBuffer& buffer)
788
{
4✔
789
  auto handleImmediateError = [&clientState, &frontend, &conn, streamID](const char* msg) {
4✔
790
    DEBUGLOG(msg);
×
791
    ++dnsdist::metrics::g_stats.nonCompliantQueries;
×
792
    ++clientState.nonCompliantQueries;
×
793
    ++frontend.d_errorResponses;
×
794
    h3_send_response(conn, streamID, 400, msg);
×
795
  };
×
796
  auto& headers = conn.d_headersBuffers.at(streamID);
4✔
797

798
  if (headers.at(":method") != "POST") {
4!
799
    handleImmediateError("DATA frame for non-POST method");
×
800
    return;
×
801
  }
×
802

803
  if (headers.count("content-type") == 0 || headers.at("content-type") != "application/dns-message") {
4!
804
    handleImmediateError("Unsupported content-type");
×
805
    return;
×
806
  }
×
807

808
  buffer.resize(std::numeric_limits<uint16_t>::max());
4✔
809
  auto& streamBuffer = conn.d_streamBuffers[streamID];
4✔
810

811
  while (true) {
8✔
812
    buffer.resize(std::numeric_limits<uint16_t>::max());
8✔
813
    ssize_t len = quiche_h3_recv_body(conn.d_http3.get(),
8✔
814
                                      conn.d_conn.get(), streamID,
8✔
815
                                      buffer.data(), buffer.size());
8✔
816

817
    if (len <= 0) {
8✔
818
      break;
4✔
819
    }
4✔
820

821
    buffer.resize(static_cast<size_t>(len));
4✔
822
    streamBuffer.insert(streamBuffer.end(), buffer.begin(), buffer.end());
4✔
823
  }
4✔
824

825
  if (!quiche_conn_stream_finished(conn.d_conn.get(), streamID)) {
4!
826
    return;
×
827
  }
×
828

829
  if (streamBuffer.size() < sizeof(dnsheader)) {
4!
830
    conn.d_streamBuffers.erase(streamID);
×
831
    handleImmediateError("DoH3 non-compliant query");
×
832
    return;
×
833
  }
×
834

835
  DEBUGLOG("Dispatching POST query");
4✔
836
  doh3_dispatch_query(*(frontend.d_server_config), std::move(streamBuffer), conn.d_localAddr, client, serverConnID, streamID, conn.getSNI(), std::move(headers));
4✔
837
  conn.d_headersBuffers.erase(streamID);
4✔
838
  conn.d_streamBuffers.erase(streamID);
4✔
839
}
4✔
840

841
static void processH3Events(ClientState& clientState, DOH3Frontend& frontend, H3Connection& conn, const ComboAddress& client, const PacketBuffer& serverConnID, PacketBuffer& buffer)
842
{
139✔
843
  while (true) {
225✔
844
    quiche_h3_event* event{nullptr};
225✔
845
    // Processes HTTP/3 data received from the peer
846
    const int64_t streamID = quiche_h3_conn_poll(conn.d_http3.get(),
225✔
847
                                                 conn.d_conn.get(),
225✔
848
                                                 &event);
225✔
849

850
    if (streamID < 0) {
225✔
851
      break;
139✔
852
    }
139✔
853
    conn.d_headersBuffers.try_emplace(streamID, dnsdist::doh3::h3_headers_t{});
86✔
854

855
    switch (quiche_h3_event_type(event)) {
86!
856
    case QUICHE_H3_EVENT_HEADERS: {
41✔
857
      processH3HeaderEvent(clientState, frontend, conn, client, serverConnID, streamID, event);
41✔
858
      break;
41✔
859
    }
×
860
    case QUICHE_H3_EVENT_DATA: {
4✔
861
      processH3DataEvent(clientState, frontend, conn, client, serverConnID, streamID, buffer);
4✔
862
      break;
4✔
863
    }
×
864
    case QUICHE_H3_EVENT_FINISHED:
41✔
865
    case QUICHE_H3_EVENT_RESET:
41!
866
    case QUICHE_H3_EVENT_PRIORITY_UPDATE:
41!
867
    case QUICHE_H3_EVENT_GOAWAY:
41!
868
      break;
41✔
869
    }
86✔
870

871
    quiche_h3_event_free(event);
86✔
872
  }
86✔
873
}
139✔
874

875
static void handleSocketReadable(DOH3Frontend& frontend, ClientState& clientState, Socket& sock, PacketBuffer& buffer)
876
{
295✔
877
  // destination connection ID, will have to be sent as original destination connection ID
878
  PacketBuffer serverConnID;
295✔
879
  // source connection ID, will have to be sent as destination connection ID
880
  PacketBuffer clientConnID;
295✔
881
  PacketBuffer tokenBuf;
295✔
882
  while (true) {
602✔
883
    ComboAddress client;
602✔
884
    ComboAddress localAddr;
602✔
885
    client.sin4.sin_family = clientState.local.sin4.sin_family;
602✔
886
    localAddr.sin4.sin_family = clientState.local.sin4.sin_family;
602✔
887
    buffer.resize(4096);
602✔
888
    if (!dnsdist::doq::recvAsync(sock, buffer, client, localAddr)) {
602✔
889
      return;
295✔
890
    }
295✔
891
    if (localAddr.sin4.sin_family == 0) {
307✔
892
      localAddr = clientState.local;
300✔
893
    }
300✔
894
    else {
7✔
895
      /* we don't get the port, only the address */
896
      localAddr.sin4.sin_port = clientState.local.sin4.sin_port;
7✔
897
    }
7✔
898

899
    DEBUGLOG("Received DoH3 datagram of size " << buffer.size() << " from " << client.toStringWithPort());
307✔
900

901
    uint32_t version{0};
307✔
902
    uint8_t type{0};
307✔
903
    std::array<uint8_t, QUICHE_MAX_CONN_ID_LEN> scid{};
307✔
904
    size_t scid_len = scid.size();
307✔
905
    std::array<uint8_t, QUICHE_MAX_CONN_ID_LEN> dcid{};
307✔
906
    size_t dcid_len = dcid.size();
307✔
907
    std::array<uint8_t, MAX_TOKEN_LEN> token{};
307✔
908
    size_t token_len = token.size();
307✔
909

910
    auto res = quiche_header_info(buffer.data(), buffer.size(), LOCAL_CONN_ID_LEN,
307✔
911
                                  &version, &type,
307✔
912
                                  scid.data(), &scid_len,
307✔
913
                                  dcid.data(), &dcid_len,
307✔
914
                                  token.data(), &token_len);
307✔
915
    if (res != 0) {
307!
916
      DEBUGLOG("Error in quiche_header_info: " << res);
×
917
      continue;
×
918
    }
×
919

920
    serverConnID.assign(dcid.begin(), dcid.begin() + dcid_len);
307✔
921
    // source connection ID, will have to be sent as destination connection ID
922
    clientConnID.assign(scid.begin(), scid.begin() + scid_len);
307✔
923
    auto conn = getConnection(frontend.d_server_config->d_connections, serverConnID);
307✔
924

925
    if (!conn) {
307✔
926
      DEBUGLOG("Connection not found");
86✔
927
      if (type != static_cast<uint8_t>(DOQ_Packet_Types::QUIC_PACKET_TYPE_INITIAL)) {
86!
928
        DEBUGLOG("Packet is not initial");
×
929
        continue;
×
930
      }
×
931

932
      if (!quiche_version_is_supported(version)) {
86!
933
        DEBUGLOG("Unsupported version");
×
934
        ++frontend.d_doh3UnsupportedVersionErrors;
×
935
        handleVersionNegotiation(sock, clientConnID, serverConnID, client, localAddr, buffer, clientState.local.isUnspecified());
×
936
        continue;
×
937
      }
×
938

939
      if (token_len == 0) {
86✔
940
        /* stateless retry */
941
        DEBUGLOG("No token received");
43✔
942
        handleStatelessRetry(sock, clientConnID, serverConnID, client, localAddr, version, buffer, clientState.local.isUnspecified());
43✔
943
        continue;
43✔
944
      }
43✔
945

946
      tokenBuf.assign(token.begin(), token.begin() + token_len);
43✔
947
      auto originalDestinationID = validateToken(tokenBuf, client);
43✔
948
      if (!originalDestinationID) {
43!
949
        ++frontend.d_doh3InvalidTokensReceived;
×
950
        DEBUGLOG("Discarding invalid token");
×
951
        continue;
×
952
      }
×
953

954
      DEBUGLOG("Creating a new connection");
43✔
955
      conn = createConnection(*frontend.d_server_config, serverConnID, *originalDestinationID, localAddr, client);
43✔
956
      if (!conn) {
43!
957
        continue;
×
958
      }
×
959
    }
43✔
960
    DEBUGLOG("Connection found");
264✔
961
    quiche_recv_info recv_info = {
264✔
962
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
963
      reinterpret_cast<struct sockaddr*>(&client),
264✔
964
      client.getSocklen(),
264✔
965
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
966
      reinterpret_cast<struct sockaddr*>(&localAddr),
264✔
967
      localAddr.getSocklen(),
264✔
968
    };
264✔
969

970
    auto done = quiche_conn_recv(conn->get().d_conn.get(), buffer.data(), buffer.size(), &recv_info);
264✔
971
    if (done < 0) {
264✔
972
      continue;
2✔
973
    }
2✔
974

975
    if (quiche_conn_is_established(conn->get().d_conn.get()) || quiche_conn_is_in_early_data(conn->get().d_conn.get())) {
262!
976
      DEBUGLOG("Connection is established");
139✔
977

978
      if (!conn->get().d_http3) {
139✔
979
        conn->get().d_http3 = QuicheHTTP3Connection(quiche_h3_conn_new_with_transport(conn->get().d_conn.get(), frontend.d_server_config->http3config.get()),
41✔
980
                                                    quiche_h3_conn_free);
41✔
981
        if (!conn->get().d_http3) {
41!
982
          continue;
×
983
        }
×
984
        DEBUGLOG("Successfully created HTTP/3 connection");
41✔
985
      }
41✔
986

987
      processH3Events(clientState, frontend, conn->get(), client, serverConnID, buffer);
139✔
988

989
      flushEgress(sock, conn->get().d_conn, client, localAddr, buffer, clientState.local.isUnspecified());
139✔
990
    }
139✔
991
    else {
123✔
992
      DEBUGLOG("Connection not established");
123✔
993
    }
123✔
994
  }
262✔
995
}
295✔
996

997
// this is the entrypoint from dnsdist.cc
998
void doh3Thread(ClientState* clientState)
999
{
17✔
1000
  try {
17✔
1001
    std::shared_ptr<DOH3Frontend>& frontend = clientState->doh3Frontend;
17✔
1002

1003
    frontend->d_server_config->clientState = clientState;
17✔
1004
    frontend->d_server_config->df = clientState->doh3Frontend;
17✔
1005

1006
    setThreadName("dnsdist/doh3");
17✔
1007

1008
    Socket sock(clientState->udpFD);
17✔
1009
    sock.setNonBlocking();
17✔
1010

1011
    auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent());
17✔
1012

1013
    auto responseReceiverFD = frontend->d_server_config->d_responseReceiver.getDescriptor();
17✔
1014
    mplexer->addReadFD(sock.getHandle(), [](int, FDMultiplexer::funcparam_t&) {});
17✔
1015
    mplexer->addReadFD(responseReceiverFD, [](int, FDMultiplexer::funcparam_t&) {});
17✔
1016
    std::vector<int> readyFDs;
17✔
1017
    PacketBuffer buffer(4096);
17✔
1018
    while (true) {
438✔
1019
      readyFDs.clear();
438✔
1020
      mplexer->getAvailableFDs(readyFDs, 500);
438✔
1021

1022
      dnsdist::configuration::refreshLocalRuntimeConfiguration();
438✔
1023

1024
      try {
438✔
1025
        if (std::find(readyFDs.begin(), readyFDs.end(), sock.getHandle()) != readyFDs.end()) {
438✔
1026
          handleSocketReadable(*frontend, *clientState, sock, buffer);
295✔
1027
        }
295✔
1028

1029
        if (std::find(readyFDs.begin(), readyFDs.end(), responseReceiverFD) != readyFDs.end()) {
438✔
1030
          flushResponses(frontend->d_server_config->d_responseReceiver);
20✔
1031
        }
20✔
1032

1033
        for (auto conn = frontend->d_server_config->d_connections.begin(); conn != frontend->d_server_config->d_connections.end();) {
851✔
1034
          quiche_conn_on_timeout(conn->second.d_conn.get());
413✔
1035

1036
          flushEgress(sock, conn->second.d_conn, conn->second.d_peer, conn->second.d_localAddr, buffer, clientState->local.isUnspecified());
413✔
1037

1038
          if (quiche_conn_is_closed(conn->second.d_conn.get())) {
413✔
1039
#ifdef DEBUGLOG_ENABLED
1040
            quiche_stats stats;
1041
            quiche_path_stats path_stats;
1042

1043
            quiche_conn_stats(conn->second.d_conn.get(), &stats);
1044
            quiche_conn_path_stats(conn->second.d_conn.get(), 0, &path_stats);
1045

1046
            DEBUGLOG("Connection (DoH3) closed, recv=" << stats.recv << " sent=" << stats.sent << " lost=" << stats.lost << " rtt=" << path_stats.rtt << "ns cwnd=" << path_stats.cwnd);
1047
#endif
1048
            conn = frontend->d_server_config->d_connections.erase(conn);
41✔
1049
          }
41✔
1050
          else {
372✔
1051
            flushStalledResponses(conn->second);
372✔
1052
            ++conn;
372✔
1053
          }
372✔
1054
        }
413✔
1055
      }
438✔
1056
      catch (const std::exception& exp) {
438✔
1057
        vinfolog("Caught exception in the main DoH3 thread: %s", exp.what());
×
1058
      }
×
1059
      catch (...) {
438✔
1060
        vinfolog("Unknown exception in the main DoH3 thread");
×
1061
      }
×
1062
    }
438✔
1063
  }
17✔
1064
  catch (const std::exception& e) {
17✔
1065
    DEBUGLOG("Caught fatal error in the main DoH3 thread: " << e.what());
×
1066
  }
×
1067
}
17✔
1068

1069
std::string DOH3Unit::getHTTPPath() const
1070
{
24✔
1071
  const auto& path = headers.at(":path");
24✔
1072
  auto pos = path.find('?');
24✔
1073
  if (pos == string::npos) {
24✔
1074
    return path;
7✔
1075
  }
7✔
1076
  return path.substr(0, pos);
17✔
1077
}
24✔
1078

1079
std::string DOH3Unit::getHTTPQueryString() const
1080
{
1✔
1081
  const auto& path = headers.at(":path");
1✔
1082
  auto pos = path.find('?');
1✔
1083
  if (pos == string::npos) {
1!
1084
    return {};
1✔
1085
  }
1✔
1086

1087
  return path.substr(pos);
×
1088
}
1✔
1089

1090
std::string DOH3Unit::getHTTPHost() const
1091
{
1✔
1092
  const auto& host = headers.find(":authority");
1✔
1093
  if (host == headers.end()) {
1!
1094
    return {};
×
1095
  }
×
1096
  return host->second;
1✔
1097
}
1✔
1098

1099
std::string DOH3Unit::getHTTPScheme() const
1100
{
1✔
1101
  const auto& scheme = headers.find(":scheme");
1✔
1102
  if (scheme == headers.end()) {
1!
1103
    return {};
×
1104
  }
×
1105
  return scheme->second;
1✔
1106
}
1✔
1107

1108
const dnsdist::doh3::h3_headers_t& DOH3Unit::getHTTPHeaders() const
1109
{
14✔
1110
  return headers;
14✔
1111
}
14✔
1112

1113
void DOH3Unit::setHTTPResponse(uint16_t statusCode, PacketBuffer&& body, const std::string& contentType)
1114
{
3✔
1115
  status_code = statusCode;
3✔
1116
  response = std::move(body);
3✔
1117
  d_contentTypeOut = contentType;
3✔
1118
}
3✔
1119

1120
#else /* HAVE_DNS_OVER_HTTP3 */
1121

1122
std::string DOH3Unit::getHTTPPath() const
1123
{
1124
  return {};
1125
}
1126

1127
std::string DOH3Unit::getHTTPQueryString() const
1128
{
1129
  return {};
1130
}
1131

1132
std::string DOH3Unit::getHTTPHost() const
1133
{
1134
  return {};
1135
}
1136

1137
std::string DOH3Unit::getHTTPScheme() const
1138
{
1139
  return {};
1140
}
1141

1142
const dnsdist::doh3::h3_headers_t& DOH3Unit::getHTTPHeaders() const
1143
{
1144
  static const dnsdist::doh3::h3_headers_t headers;
1145
  return headers;
1146
}
1147

1148
void DOH3Unit::setHTTPResponse(uint16_t, PacketBuffer&&, const std::string&)
1149
{
1150
}
1151

1152
#endif /* HAVE_DNS_OVER_HTTP3 */
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