• 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

69.6
/pdns/dnsdistdist/doq-common.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 "doq-common.hh"
24
#include "dnsdist-random.hh"
25
#include "libssl.hh"
26

27
#ifdef HAVE_DNS_OVER_QUIC
28

29
#if 0
30
#define DEBUGLOG_ENABLED
31
#define DEBUGLOG(x) std::cerr << x << std::endl;
32
#else
33
#define DEBUGLOG(x)
34
#endif
35

36
namespace dnsdist::doq
37
{
38

39
static const std::string s_quicRetryTokenKey = dnsdist::crypto::authenticated::newKey(false);
40

41
PacketBuffer mintToken(const PacketBuffer& dcid, const ComboAddress& peer)
42
{
121✔
43
  try {
121✔
44
    dnsdist::crypto::authenticated::Nonce nonce;
121✔
45
    nonce.init();
121✔
46

47
    const auto addrBytes = peer.toByteString();
121✔
48
    // this token will be valid for 60s
49
    const uint64_t ttd = time(nullptr) + 60U;
121✔
50
    PacketBuffer plainTextToken;
121✔
51
    plainTextToken.reserve(sizeof(ttd) + addrBytes.size() + dcid.size());
121✔
52
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic)
53
    plainTextToken.insert(plainTextToken.end(), reinterpret_cast<const uint8_t*>(&ttd), reinterpret_cast<const uint8_t*>(&ttd) + sizeof(ttd));
121✔
54
    plainTextToken.insert(plainTextToken.end(), addrBytes.begin(), addrBytes.end());
121✔
55
    plainTextToken.insert(plainTextToken.end(), dcid.begin(), dcid.end());
121✔
56
    //        NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
57
    const auto encryptedToken = dnsdist::crypto::authenticated::encryptSym(std::string_view(reinterpret_cast<const char*>(plainTextToken.data()), plainTextToken.size()), s_quicRetryTokenKey, nonce, false);
121✔
58
    // a bit sad, let's see if we can do better later
59
    PacketBuffer encryptedTokenPacket;
121✔
60
    encryptedTokenPacket.reserve(encryptedToken.size() + nonce.value.size());
121✔
61
    encryptedTokenPacket.insert(encryptedTokenPacket.begin(), encryptedToken.begin(), encryptedToken.end());
121✔
62
    encryptedTokenPacket.insert(encryptedTokenPacket.begin(), nonce.value.begin(), nonce.value.end());
121✔
63
    return encryptedTokenPacket;
121✔
64
  }
121✔
65
  catch (const std::exception& exp) {
121✔
66
    vinfolog("Error while minting DoH3 token: %s", exp.what());
×
67
    throw;
×
68
  }
×
69
}
121✔
70

71
void fillRandom(PacketBuffer& buffer, size_t size)
72
{
159✔
73
  buffer.reserve(size);
159✔
74
  while (size > 0) {
2,703✔
75
    buffer.insert(buffer.end(), dnsdist::getRandomValue(std::numeric_limits<uint8_t>::max()));
2,544✔
76
    --size;
2,544✔
77
  }
2,544✔
78
}
159✔
79

80
std::optional<PacketBuffer> getCID()
81
{
121✔
82
  PacketBuffer buffer;
121✔
83

84
  fillRandom(buffer, LOCAL_CONN_ID_LEN);
121✔
85

86
  return buffer;
121✔
87
}
121✔
88

89
// returns the original destination ID if the token is valid, nothing otherwise
90
std::optional<PacketBuffer> validateToken(const PacketBuffer& token, const ComboAddress& peer)
91
{
121✔
92
  try {
121✔
93
    dnsdist::crypto::authenticated::Nonce nonce;
121✔
94
    auto addrBytes = peer.toByteString();
121✔
95
    const uint64_t now = time(nullptr);
121✔
96
    const auto minimumSize = nonce.value.size() + sizeof(now) + addrBytes.size();
121✔
97
    if (token.size() <= minimumSize) {
121!
98
      return std::nullopt;
×
99
    }
×
100

101
    memcpy(nonce.value.data(), token.data(), nonce.value.size());
121✔
102

103
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
104
    auto cipher = std::string_view(reinterpret_cast<const char*>(&token.at(nonce.value.size())), token.size() - nonce.value.size());
121✔
105
    auto plainText = dnsdist::crypto::authenticated::decryptSym(cipher, s_quicRetryTokenKey, nonce, false);
121✔
106

107
    if (plainText.size() <= sizeof(now) + addrBytes.size()) {
121!
108
      return std::nullopt;
×
109
    }
×
110

111
    uint64_t ttd{0};
121✔
112
    memcpy(&ttd, plainText.data(), sizeof(ttd));
121✔
113
    if (ttd < now) {
121!
114
      return std::nullopt;
×
115
    }
×
116

117
    if (std::memcmp(&plainText.at(sizeof(ttd)), &*addrBytes.begin(), addrBytes.size()) != 0) {
121!
118
      return std::nullopt;
×
119
    }
×
120
    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
121
    return PacketBuffer(plainText.begin() + (sizeof(ttd) + addrBytes.size()), plainText.end());
121✔
122
  }
121✔
123
  catch (const std::exception& exp) {
121✔
124
    vinfolog("Error while validating DoH3 token: %s", exp.what());
×
125
    return std::nullopt;
×
126
  }
×
127
}
121✔
128

129
static void sendFromTo(Socket& sock, const ComboAddress& peer, const ComboAddress& local, PacketBuffer& buffer, [[maybe_unused]] bool socketBoundToAny)
130
{
886✔
131
  /* we only want to specify the source address to use if we were able to
132
     either harvest it from the incoming packet, or if our socket is already
133
     bound to a specific address */
134
  bool setSourceAddress = local.sin4.sin_family != 0;
886✔
135
#if defined(__FreeBSD__) || defined(__DragonFly__)
136
  /* FreeBSD and DragonFlyBSD refuse the use of IP_SENDSRCADDR on a socket that is bound to a
137
     specific address, returning EINVAL in that case. */
138
  if (!socketBoundToAny) {
139
    setSourceAddress = false;
140
  }
141
#endif /* __FreeBSD__ || __DragonFly__ */
142

143
  if (!setSourceAddress) {
886!
144
    const int flags = 0;
×
145
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
146
    auto ret = sendto(sock.getHandle(), buffer.data(), buffer.size(), flags, reinterpret_cast<const struct sockaddr*>(&peer), peer.getSocklen());
×
147
    if (ret < 0) {
×
148
      auto error = errno;
×
149
      vinfolog("Error while sending QUIC datagram of size %d to %s: %s", buffer.size(), peer.toStringWithPort(), stringerror(error));
×
150
    }
×
151
    return;
×
152
  }
×
153

154
  try {
886✔
155
    sendMsgWithOptions(sock.getHandle(), buffer.data(), buffer.size(), &peer, &local, 0, 0);
886✔
156
  }
886✔
157
  catch (const std::exception& exp) {
886✔
158
    vinfolog("Error while sending QUIC datagram of size %d from %s to %s: %s", buffer.size(), local.toStringWithPort(), peer.toStringWithPort(), exp.what());
×
159
  }
×
160
}
886✔
161

162
void handleStatelessRetry(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, const ComboAddress& localAddr, uint32_t version, PacketBuffer& buffer, bool socketBoundToAny)
163
{
121✔
164
  auto newServerConnID = getCID();
121✔
165
  if (!newServerConnID) {
121!
166
    return;
×
167
  }
×
168

169
  auto token = mintToken(serverConnID, peer);
121✔
170

171
  buffer.resize(MAX_DATAGRAM_SIZE);
121✔
172
  auto written = quiche_retry(clientConnID.data(), clientConnID.size(),
121✔
173
                              serverConnID.data(), serverConnID.size(),
121✔
174
                              newServerConnID->data(), newServerConnID->size(),
121✔
175
                              token.data(), token.size(),
121✔
176
                              version,
121✔
177
                              buffer.data(), buffer.size());
121✔
178

179
  if (written < 0) {
121!
180
    DEBUGLOG("failed to create retry packet " << written);
×
181
    return;
×
182
  }
×
183

184
  buffer.resize(static_cast<size_t>(written));
121✔
185
  sendFromTo(sock, peer, localAddr, buffer, socketBoundToAny);
121✔
186
}
121✔
187

188
void handleVersionNegotiation(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, const ComboAddress& localAddr, PacketBuffer& buffer, bool socketBoundToAny)
189
{
×
190
  buffer.resize(MAX_DATAGRAM_SIZE);
×
191

192
  auto written = quiche_negotiate_version(clientConnID.data(), clientConnID.size(),
×
193
                                          serverConnID.data(), serverConnID.size(),
×
194
                                          buffer.data(), buffer.size());
×
195

196
  if (written < 0) {
×
197
    DEBUGLOG("failed to create vneg packet " << written);
×
198
    return;
×
199
  }
×
200

201
  buffer.resize(static_cast<size_t>(written));
×
202
  sendFromTo(sock, peer, localAddr, buffer, socketBoundToAny);
×
203
}
×
204

205
void flushEgress(Socket& sock, QuicheConnection& conn, const ComboAddress& peer, const ComboAddress& localAddr, PacketBuffer& buffer, bool socketBoundToAny)
206
{
1,545✔
207
  buffer.resize(MAX_DATAGRAM_SIZE);
1,545✔
208
  quiche_send_info send_info;
1,545✔
209

210
  while (true) {
2,310✔
211
    auto written = quiche_conn_send(conn.get(), buffer.data(), buffer.size(), &send_info);
2,310✔
212
    if (written == QUICHE_ERR_DONE) {
2,310✔
213
      return;
1,545✔
214
    }
1,545✔
215

216
    if (written < 0) {
765!
217
      return;
×
218
    }
×
219
    // FIXME pacing (as send_info.at should tell us when to send the packet) ?
220
    buffer.resize(static_cast<size_t>(written));
765✔
221
    sendFromTo(sock, peer, localAddr, buffer, socketBoundToAny);
765✔
222
  }
765✔
223
}
1,545✔
224

225
void configureQuiche(QuicheConfig& config, const QuicheParams& params, bool isHTTP)
226
{
38✔
227
  for (const auto& pair : params.d_tlsConfig.d_certKeyPairs) {
38✔
228
    auto res = quiche_config_load_cert_chain_from_pem_file(config.get(), pair.d_cert.c_str());
38✔
229
    if (res != 0) {
38!
230
      throw std::runtime_error("Error loading the server certificate from '" + pair.d_cert + "': " + std::to_string(res));
×
231
    }
×
232
    if (pair.d_key) {
38!
233
      res = quiche_config_load_priv_key_from_pem_file(config.get(), pair.d_key->c_str());
38✔
234
      if (res != 0) {
38!
235
        throw std::runtime_error("Error loading the server key from '" + *(pair.d_key) + "': " + std::to_string(res));
×
236
      }
×
237
    }
38✔
238
  }
38✔
239

240
  {
38✔
241
    auto res = quiche_config_set_application_protos(config.get(),
38✔
242
                                                    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
243
                                                    reinterpret_cast<const uint8_t*>(params.d_alpn.data()),
38✔
244
                                                    params.d_alpn.size());
38✔
245
    if (res != 0) {
38!
246
      throw std::runtime_error("Error setting ALPN: " + std::to_string(res));
×
247
    }
×
248
  }
38✔
249

250
  quiche_config_set_max_idle_timeout(config.get(), params.d_idleTimeout * 1000);
38✔
251
  /* maximum size of an outgoing packet, which means the buffer we pass to quiche_conn_send() should be at least that big */
252
  quiche_config_set_max_send_udp_payload_size(config.get(), MAX_DATAGRAM_SIZE);
38✔
253
  quiche_config_set_max_recv_udp_payload_size(config.get(), MAX_DATAGRAM_SIZE);
38✔
254

255
  // The number of concurrent remotely-initiated bidirectional streams to be open at any given time
256
  // https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_streams_bidi
257
  // 0 means none will get accepted, that's why we have a default value of 65535
258
  quiche_config_set_initial_max_streams_bidi(config.get(), params.d_maxInFlight);
38✔
259

260
  // The number of bytes of incoming stream data to be buffered for each locally or remotely-initiated bidirectional stream
261
  quiche_config_set_initial_max_stream_data_bidi_local(config.get(), 8192);
38✔
262
  quiche_config_set_initial_max_stream_data_bidi_remote(config.get(), 8192);
38✔
263

264
  if (isHTTP) {
38✔
265
    /* see rfc9114 section 6.2. Unidirectional Streams:
266
       Each endpoint needs to create at least one unidirectional stream for the HTTP control stream.
267
       QPACK requires two additional unidirectional streams, and other extensions might require further streams.
268
       Therefore, the transport parameters sent by both clients and servers MUST allow the peer to create at least three
269
       unidirectional streams.
270
       These transport parameters SHOULD also provide at least 1,024 bytes of flow-control credit to each unidirectional stream.
271
    */
272
    quiche_config_set_initial_max_streams_uni(config.get(), 3U);
17✔
273
    quiche_config_set_initial_max_stream_data_uni(config.get(), 1024U);
17✔
274
  }
17✔
275

276
  // The number of total bytes of incoming stream data to be buffered for the whole connection
277
  // https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_data
278
  quiche_config_set_initial_max_data(config.get(), 8192 * params.d_maxInFlight);
38✔
279
  if (!params.d_keyLogFile.empty()) {
38✔
280
    quiche_config_log_keys(config.get());
3✔
281
  }
3✔
282

283
  auto algo = dnsdist::doq::s_available_cc_algorithms.find(params.d_ccAlgo);
38✔
284
  if (algo != dnsdist::doq::s_available_cc_algorithms.end()) {
38!
285
    quiche_config_set_cc_algorithm(config.get(), static_cast<enum quiche_cc_algorithm>(algo->second));
38✔
286
  }
38✔
287

288
  {
38✔
289
    PacketBuffer resetToken;
38✔
290
    fillRandom(resetToken, 16);
38✔
291
    quiche_config_set_stateless_reset_token(config.get(), resetToken.data());
38✔
292
  }
38✔
293
}
38✔
294

295
bool recvAsync(Socket& socket, PacketBuffer& buffer, ComboAddress& clientAddr, ComboAddress& localAddr)
296
{
1,751✔
297
  msghdr msgh{};
1,751✔
298
  iovec iov{};
1,751✔
299
  /* used by HarvestDestinationAddress */
300
  cmsgbuf_aligned cbuf;
1,751✔
301
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
302
  fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), reinterpret_cast<char*>(&buffer.at(0)), buffer.size(), &clientAddr);
1,751✔
303

304
  ssize_t got = recvmsg(socket.getHandle(), &msgh, 0);
1,751✔
305
  if (got < 0) {
1,751✔
306
    int error = errno;
867✔
307
    if (error != EAGAIN) {
867!
308
      throw NetworkError("Error in recvmsg: " + stringerror(error));
×
309
    }
×
310
    return false;
867✔
311
  }
867✔
312

313
  if ((msgh.msg_flags & MSG_TRUNC) != 0) {
884!
314
    return false;
×
315
  }
×
316

317
  buffer.resize(static_cast<size_t>(got));
884✔
318

319
  if (HarvestDestinationAddress(&msgh, &localAddr)) {
884✔
320
    /* so it turns out that sometimes the kernel lies to us:
321
       the address is set to 0.0.0.0:0 which makes our sendfromto() use
322
       the wrong address. In that case it's better to let the kernel
323
       do the work by itself and use sendto() instead.
324
       This is indicated by setting the family to 0 which is acted upon
325
       in sendUDPResponse() and DelayedPacket::().
326
    */
327
    if (localAddr.isUnspecified()) {
14!
328
      localAddr.sin4.sin_family = 0;
×
329
    }
×
330
  }
14✔
331
  else {
870✔
332
    localAddr.sin4.sin_family = 0;
870✔
333
  }
870✔
334

335
  return !buffer.empty();
884✔
336
}
884✔
337

338
std::string getSNIFromQuicheConnection([[maybe_unused]] const QuicheConnection& conn)
339
{
116✔
340
#if defined(HAVE_QUICHE_CONN_SERVER_NAME)
116✔
341
  const uint8_t* sniPtr = nullptr;
116✔
342
  size_t sniPtrSize = 0;
116✔
343
  quiche_conn_server_name(conn.get(), &sniPtr, &sniPtrSize);
116✔
344
  if (sniPtrSize > 0) {
116✔
345
    return std::string(reinterpret_cast<const char*>(sniPtr), sniPtrSize);
107✔
346
  }
107✔
347
#endif /* HAVE_QUICHE_CONN_SERVER_NAME */
9✔
348
  return {};
9✔
349
}
116✔
350
}
351

352
#endif
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