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

PowerDNS / pdns / 18837422702

27 Oct 2025 10:15AM UTC coverage: 73.025% (+0.02%) from 73.004%
18837422702

Pull #16375

github

web-flow
Merge 2f23fc90d into 82ea647b4
Pull Request #16375: dnsdist: Include a Date: response header for rejected HTTP1 requests

38279 of 63122 branches covered (60.64%)

Branch coverage included in aggregate %.

0 of 11 new or added lines in 1 file covered. (0.0%)

10680 existing lines in 98 files now uncovered.

127481 of 163870 relevant lines covered (77.79%)

5384548.9 hits per line

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

78.63
/pdns/dnsdistdist/dnsdist-nghttp2-in.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
#include "dnsdist-nghttp2-in.hh"
23

24
#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
25

26
#include "dnsdist-dnsparser.hh"
27
#include "dnsdist-doh-common.hh"
28
#include "dnsdist-proxy-protocol.hh"
29
#include "dnsparser.hh"
30

31
#if 0
32
class IncomingDoHCrossProtocolContext : public CrossProtocolContext
33
{
34
public:
35
  IncomingDoHCrossProtocolContext(IncomingHTTP2Connection::PendingQuery&& query, std::shared_ptr<IncomingHTTP2Connection> connection, IncomingHTTP2Connection::StreamID streamID): CrossProtocolContext(std::move(query.d_buffer)), d_connection(connection), d_query(std::move(query))
36
  {
37
  }
38

39
  std::optional<std::string> getHTTPPath() const override
40
  {
41
    return d_query.d_path;
42
  }
43

44
  std::optional<std::string> getHTTPScheme() const override
45
  {
46
    return d_query.d_scheme;
47
  }
48

49
  std::optional<std::string> getHTTPHost() const override
50
  {
51
    return d_query.d_host;
52
  }
53

54
  std::optional<std::string> getHTTPQueryString() const override
55
  {
56
    return d_query.d_queryString;
57
  }
58

59
  std::optional<HeadersMap> getHTTPHeaders() const override
60
  {
61
    if (!d_query.d_headers) {
62
      return std::nullopt;
63
    }
64
    return *d_query.d_headers;
65
  }
66

67
  void handleResponse(PacketBuffer&& response, InternalQueryState&& state) override
68
  {
69
    auto conn = d_connection.lock();
70
    if (!conn) {
71
      /* the connection has been closed in the meantime */
72
      return;
73
    }
74
  }
75

76
  void handleTimeout() override
77
  {
78
    auto conn = d_connection.lock();
79
    if (!conn) {
80
      /* the connection has been closed in the meantime */
81
      return;
82
    }
83
  }
84

85
  ~IncomingDoHCrossProtocolContext() override
86
  {
87
  }
88

89
private:
90
  std::weak_ptr<IncomingHTTP2Connection> d_connection;
91
  IncomingHTTP2Connection::PendingQuery d_query;
92
  IncomingHTTP2Connection::StreamID d_streamID{-1};
93
};
94
#endif
95

96
class IncomingDoHCrossProtocolContext : public DOHUnitInterface
97
{
98
public:
99
  IncomingDoHCrossProtocolContext(IncomingHTTP2Connection::PendingQuery&& query, const std::shared_ptr<IncomingHTTP2Connection>& connection, IncomingHTTP2Connection::StreamID streamID) :
100
    d_connection(connection), d_query(std::move(query)), d_streamID(streamID)
220✔
101
  {
220✔
102
  }
220✔
103
  IncomingDoHCrossProtocolContext(const IncomingDoHCrossProtocolContext&) = delete;
104
  IncomingDoHCrossProtocolContext(IncomingDoHCrossProtocolContext&&) = delete;
105
  IncomingDoHCrossProtocolContext& operator=(const IncomingDoHCrossProtocolContext&) = delete;
106
  IncomingDoHCrossProtocolContext& operator=(IncomingDoHCrossProtocolContext&&) = delete;
107

108
  ~IncomingDoHCrossProtocolContext() override = default;
220✔
109

110
  [[nodiscard]] std::string getHTTPPath() const override
UNCOV
111
  {
67✔
UNCOV
112
    return d_query.d_path;
67✔
UNCOV
113
  }
67✔
114

115
  [[nodiscard]] const std::string& getHTTPScheme() const override
UNCOV
116
  {
5✔
UNCOV
117
    return d_query.d_scheme;
5✔
UNCOV
118
  }
5✔
119

120
  [[nodiscard]] const std::string& getHTTPHost() const override
UNCOV
121
  {
5✔
UNCOV
122
    return d_query.d_host;
5✔
UNCOV
123
  }
5✔
124

125
  [[nodiscard]] std::string getHTTPQueryString() const override
UNCOV
126
  {
5✔
UNCOV
127
    return d_query.d_queryString;
5✔
UNCOV
128
  }
5✔
129

130
  [[nodiscard]] const HeadersMap& getHTTPHeaders() const override
UNCOV
131
  {
39✔
UNCOV
132
    if (!d_query.d_headers) {
39!
133
      static const HeadersMap empty{};
×
134
      return empty;
×
135
    }
×
UNCOV
136
    return *d_query.d_headers;
39✔
UNCOV
137
  }
39✔
138

139
  [[nodiscard]] std::shared_ptr<TCPQuerySender> getQuerySender() const override
UNCOV
140
  {
1✔
UNCOV
141
    return std::dynamic_pointer_cast<TCPQuerySender>(d_connection.lock());
1✔
UNCOV
142
  }
1✔
143

144
  void setHTTPResponse(uint16_t statusCode, PacketBuffer&& body, const std::string& contentType = "") override
UNCOV
145
  {
7✔
UNCOV
146
    d_query.d_statusCode = statusCode;
7✔
UNCOV
147
    d_query.d_response = std::move(body);
7✔
UNCOV
148
    d_query.d_contentTypeOut = contentType;
7✔
UNCOV
149
  }
7✔
150

151
  void handleUDPResponse(PacketBuffer&& response, InternalQueryState&& state, const std::shared_ptr<DownstreamState>& downstream_) override
UNCOV
152
  {
79✔
UNCOV
153
    std::unique_ptr<DOHUnitInterface> unit(this);
79✔
UNCOV
154
    auto conn = d_connection.lock();
79✔
UNCOV
155
    if (!conn) {
79!
156
      /* the connection has been closed in the meantime */
157
      return;
×
158
    }
×
159

UNCOV
160
    state.du = std::move(unit);
79✔
UNCOV
161
    TCPResponse resp(std::move(response), std::move(state), nullptr, nullptr);
79✔
UNCOV
162
    resp.d_ds = downstream_;
79✔
UNCOV
163
    struct timeval now{};
79✔
UNCOV
164
    gettimeofday(&now, nullptr);
79✔
UNCOV
165
    conn->handleResponse(now, std::move(resp));
79✔
UNCOV
166
  }
79✔
167

168
  void handleTimeout() override
169
  {
×
170
    std::unique_ptr<DOHUnitInterface> unit(this);
×
171
    auto conn = d_connection.lock();
×
172
    if (!conn) {
×
173
      /* the connection has been closed in the meantime */
174
      return;
×
175
    }
×
176
    struct timeval now{};
×
177
    gettimeofday(&now, nullptr);
×
178
    TCPResponse resp;
×
179
    resp.d_idstate.d_streamID = d_streamID;
×
180
    conn->notifyIOError(now, std::move(resp));
×
181
  }
×
182

183
  std::weak_ptr<IncomingHTTP2Connection> d_connection;
184
  IncomingHTTP2Connection::PendingQuery d_query;
185
  IncomingHTTP2Connection::StreamID d_streamID{-1};
186
};
187

188
void IncomingHTTP2Connection::handleResponse(const struct timeval& now, TCPResponse&& response)
UNCOV
189
{
255✔
UNCOV
190
  if (std::this_thread::get_id() != d_creatorThreadID) {
255✔
UNCOV
191
    handleCrossProtocolResponse(now, std::move(response));
127✔
UNCOV
192
    return;
127✔
UNCOV
193
  }
127✔
194

UNCOV
195
  auto& state = response.d_idstate;
128✔
UNCOV
196
  if (state.forwardedOverUDP) {
128✔
UNCOV
197
    dnsheader_aligned responseDH(response.d_buffer.data());
91✔
198

UNCOV
199
    if (responseDH.get()->tc && state.d_packet && state.d_packet->size() > state.d_proxyProtocolPayloadSize && state.d_packet->size() - state.d_proxyProtocolPayloadSize > sizeof(dnsheader)) {
91!
UNCOV
200
      vinfolog("Response received from backend %s via UDP, for query %d received from %s via DoH, is truncated, retrying over TCP", response.d_ds->getNameWithAddr(), state.d_streamID, state.origRemote.toStringWithPort());
4✔
UNCOV
201
      auto& query = *state.d_packet;
4✔
UNCOV
202
      dnsdist::PacketMangling::editDNSHeaderFromRawPacket(&query.at(state.d_proxyProtocolPayloadSize), [origID = state.origID](dnsheader& header) {
4✔
203
        /* restoring the original ID */
UNCOV
204
        header.id = origID;
4✔
UNCOV
205
        return true;
4✔
UNCOV
206
      });
4✔
207

UNCOV
208
      state.forwardedOverUDP = false;
4✔
UNCOV
209
      bool proxyProtocolPayloadAdded = state.d_proxyProtocolPayloadSize > 0;
4✔
UNCOV
210
      auto cpq = getCrossProtocolQuery(std::move(query), std::move(state), response.d_ds);
4✔
211
      /* 'd_packet' buffer moved by InternalQuery constructor, need re-association */
UNCOV
212
      cpq->query.d_idstate.d_packet = std::make_unique<PacketBuffer>(cpq->query.d_buffer);
4✔
UNCOV
213
      cpq->query.d_proxyProtocolPayloadAdded = proxyProtocolPayloadAdded;
4✔
UNCOV
214
      if (g_tcpclientthreads && g_tcpclientthreads->passCrossProtocolQueryToThread(std::move(cpq))) {
4!
UNCOV
215
        return;
4✔
UNCOV
216
      }
4✔
217
      vinfolog("Unable to pass DoH query to a TCP worker thread after getting a TC response over UDP");
×
218
      notifyIOError(now, std::move(response));
×
219
      return;
×
UNCOV
220
    }
4✔
UNCOV
221
  }
91✔
222

UNCOV
223
  IncomingTCPConnectionState::handleResponse(now, std::move(response));
124✔
UNCOV
224
}
124✔
225

226
std::unique_ptr<DOHUnitInterface> IncomingHTTP2Connection::getDOHUnit(uint32_t streamID)
227
{
220✔
228
  if (streamID > std::numeric_limits<IncomingHTTP2Connection::StreamID>::max()) {
220!
229
    throw std::runtime_error("Invalid stream ID while retrieving DoH unit");
×
230
  }
×
231

232
  // NOLINTNEXTLINE(*-narrowing-conversions): generic interface between DNS and DoH with different types
233
  auto query = std::move(d_currentStreams.at(static_cast<IncomingHTTP2Connection::StreamID>(streamID)));
220✔
234
  return std::make_unique<IncomingDoHCrossProtocolContext>(std::move(query), std::dynamic_pointer_cast<IncomingHTTP2Connection>(shared_from_this()), streamID);
220✔
235
}
220✔
236

237
void IncomingHTTP2Connection::restoreDOHUnit(std::unique_ptr<DOHUnitInterface>&& unit)
238
{
120✔
239
  auto context = std::unique_ptr<IncomingDoHCrossProtocolContext>(dynamic_cast<IncomingDoHCrossProtocolContext*>(unit.release()));
120✔
240
  if (context) {
120!
241
    d_currentStreams.at(context->d_streamID) = std::move(context->d_query);
120✔
242
  }
120✔
243
}
120✔
244

245
IncomingHTTP2Connection::IncomingHTTP2Connection(ConnectionInfo&& connectionInfo, TCPClientThreadData& threadData, const struct timeval& now) :
246
  IncomingTCPConnectionState(std::move(connectionInfo), threadData, now)
208✔
247
{
208✔
248
  nghttp2_session_callbacks* cbs = nullptr;
208✔
249
  if (nghttp2_session_callbacks_new(&cbs) != 0) {
208!
250
    throw std::runtime_error("Unable to create a callback object for a new incoming HTTP/2 session");
×
251
  }
×
252
  std::unique_ptr<nghttp2_session_callbacks, void (*)(nghttp2_session_callbacks*)> callbacks(cbs, nghttp2_session_callbacks_del);
208✔
253
  cbs = nullptr;
208✔
254

255
  nghttp2_session_callbacks_set_send_callback(callbacks.get(), send_callback);
208✔
256
  nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks.get(), on_frame_recv_callback);
208✔
257
  nghttp2_session_callbacks_set_on_stream_close_callback(callbacks.get(), on_stream_close_callback);
208✔
258
  nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks.get(), on_begin_headers_callback);
208✔
259
  nghttp2_session_callbacks_set_on_header_callback(callbacks.get(), on_header_callback);
208✔
260
  nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks.get(), on_data_chunk_recv_callback);
208✔
261
  nghttp2_session_callbacks_set_error_callback2(callbacks.get(), on_error_callback);
208✔
262

263
  nghttp2_session* sess = nullptr;
208✔
264
  if (nghttp2_session_server_new(&sess, callbacks.get(), this) != 0) {
208!
265
    throw std::runtime_error("Coult not allocate a new incoming HTTP/2 session");
×
266
  }
×
267

268
  d_session = std::unique_ptr<nghttp2_session, decltype(&nghttp2_session_del)>(sess, nghttp2_session_del);
208✔
269
  sess = nullptr;
208✔
270
}
208✔
271

272
bool IncomingHTTP2Connection::checkALPN()
273
{
205✔
274
  constexpr std::array<uint8_t, 2> h2ALPN{'h', '2'};
205✔
275
  const auto protocols = d_handler.getNextProtocol();
205✔
276
  if (protocols.size() == h2ALPN.size() && memcmp(protocols.data(), h2ALPN.data(), h2ALPN.size()) == 0) {
205!
277
    return true;
180✔
278
  }
180✔
279

UNCOV
280
  constexpr std::array<uint8_t, 8> http11ALPN{'h', 't', 't', 'p', '/', '1', '.', '1'};
25✔
UNCOV
281
  if (protocols.size() == http11ALPN.size() && memcmp(protocols.data(), http11ALPN.data(), http11ALPN.size()) == 0) {
25!
UNCOV
282
    ++d_ci.cs->dohFrontend->d_http1Stats.d_nbQueries;
2✔
UNCOV
283
  }
2✔
284

NEW
285
  static const std::string data0("HTTP/1.1 400 Bad Request\r\nConnection: Close\r\n");
25✔
286

NEW
287
  std::array<char, 40> data1{};
25✔
NEW
288
  static const std::string dateformat("Date: %a, %d %h %Y %T GMT\r\n");
25✔
NEW
289
  struct tm tmval{};
25✔
NEW
290
  time_t timestamp = time(nullptr);
25✔
NEW
291
  size_t len = strftime(data1.data(), data1.size(), dateformat.data(), gmtime_r(&timestamp, &tmval));
25✔
NEW
292
  assert(len != 0);
25!
293

NEW
294
  static const std::string data2("\r\n<html><body>This server implements RFC 8484 - DNS Queries over HTTP, and requires HTTP/2 in accordance with section 5.2 of the RFC.</body></html>\r\n");
25✔
295

NEW
296
  d_out.insert(d_out.end(), data0.begin(), data0.end());
25✔
NEW
297
  d_out.insert(d_out.end(), data1.begin(), data1.begin() + len);
25✔
NEW
298
  d_out.insert(d_out.end(), data2.begin(), data2.end());
25✔
UNCOV
299
  writeToSocket(false);
25✔
300

UNCOV
301
  vinfolog("DoH connection from %s expected ALPN value 'h2', got '%s'", d_ci.remote.toStringWithPort(), std::string(protocols.begin(), protocols.end()));
25✔
UNCOV
302
  return false;
25✔
UNCOV
303
}
25✔
304

305
void IncomingHTTP2Connection::handleConnectionReady()
306
{
182✔
307
  constexpr std::array<nghttp2_settings_entry, 1> settings{{{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, dnsdist::doh::MAX_INCOMING_CONCURRENT_STREAMS}}};
182✔
308
  constexpr std::array<nghttp2_settings_entry, 1> nearLimitsSettings{{{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 1U}}};
182✔
309
  auto ret = nghttp2_submit_settings(d_session.get(), NGHTTP2_FLAG_NONE, isNearTCPLimits() ? nearLimitsSettings.data() : settings.data(), isNearTCPLimits() ? nearLimitsSettings.size() : settings.size());
182✔
310
  if (ret != 0) {
182!
311
    throw std::runtime_error("Fatal error: " + std::string(nghttp2_strerror(ret)));
×
312
  }
×
313
  d_needFlush = true;
182✔
314

315
  if (!d_inReadFunction) {
182!
316
    ret = nghttp2_session_send(d_session.get());
182✔
317
    if (ret != 0) {
182!
318
      throw std::runtime_error("Fatal error: " + std::string(nghttp2_strerror(ret)));
×
319
    }
×
320
  }
182✔
321
}
182✔
322

323
bool IncomingHTTP2Connection::hasPendingWrite() const
324
{
628✔
325
  return d_pendingWrite;
628✔
326
}
628✔
327

328
IOState IncomingHTTP2Connection::handleHandshake(const struct timeval& now)
329
{
409✔
330
  auto iostate = d_handler.tryHandshake();
409✔
331
  if (iostate == IOState::Done) {
409✔
332
    handleHandshakeDone(now);
207✔
333
    if (d_handler.isTLS()) {
207✔
334
      if (!checkALPN()) {
205✔
UNCOV
335
        d_connectionDied = true;
25✔
UNCOV
336
        stopIO();
25✔
UNCOV
337
        return iostate;
25✔
UNCOV
338
      }
25✔
339
    }
205✔
340

341
    if (d_ci.cs != nullptr && d_ci.cs->d_enableProxyProtocol && !isProxyPayloadOutsideTLS() && expectProxyProtocolFrom(d_ci.remote)) {
182!
UNCOV
342
      d_state = State::readingProxyProtocolHeader;
1✔
UNCOV
343
      d_buffer.resize(s_proxyProtocolMinimumHeaderSize);
1✔
UNCOV
344
      d_proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize;
1✔
UNCOV
345
    }
1✔
346
    else {
181✔
347
      d_state = State::waitingForQuery;
181✔
348
      handleConnectionReady();
181✔
349
    }
181✔
350
  }
182✔
351
  return iostate;
384✔
352
}
409✔
353

354
class ReadFunctionGuard
355
{
356
public:
357
  ReadFunctionGuard(bool& inReadFunction) :
358
    d_inReadFunctionRef(inReadFunction)
1,127✔
359
  {
1,127✔
360
    d_inReadFunctionRef = true;
1,127✔
361
  }
1,127✔
362
  ReadFunctionGuard(ReadFunctionGuard&&) = delete;
363
  ReadFunctionGuard(const ReadFunctionGuard&) = delete;
364
  ReadFunctionGuard& operator=(ReadFunctionGuard&&) = delete;
365
  ReadFunctionGuard& operator=(const ReadFunctionGuard&) = delete;
366
  ~ReadFunctionGuard()
367
  {
1,127✔
368
    d_inReadFunctionRef = false;
1,127✔
369
  }
1,127✔
370

371
private:
372
  bool& d_inReadFunctionRef;
373
};
374

375
void IncomingHTTP2Connection::handleIO()
376
{
688✔
377
  IOState iostate = IOState::Done;
688✔
378
  struct timeval now{};
688✔
379
  gettimeofday(&now, nullptr);
688✔
380

381
  try {
688✔
382
    if (maxConnectionDurationReached(dnsdist::configuration::getCurrentRuntimeConfiguration().d_maxTCPConnectionDuration, now)) {
688!
383
      vinfolog("Terminating DoH connection from %s because it reached the maximum TCP connection duration", d_ci.remote.toStringWithPort());
×
384
      stopIO();
×
385
      d_connectionClosing = true;
×
386
      return;
×
387
    }
×
388

389
    if (d_state == State::starting) {
688✔
390
      if (d_ci.cs != nullptr && d_ci.cs->dohFrontend != nullptr) {
208!
391
        ++d_ci.cs->dohFrontend->d_httpconnects;
208✔
392
      }
208✔
393
      if (d_ci.cs != nullptr && d_ci.cs->d_enableProxyProtocol && isProxyPayloadOutsideTLS() && expectProxyProtocolFrom(d_ci.remote)) {
208!
UNCOV
394
        d_state = State::readingProxyProtocolHeader;
1✔
UNCOV
395
        d_buffer.resize(s_proxyProtocolMinimumHeaderSize);
1✔
UNCOV
396
        d_proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize;
1✔
UNCOV
397
      }
1✔
398
      else {
207✔
399
        d_state = State::doingHandshake;
207✔
400
      }
207✔
401
    }
208✔
402

403
    if (d_state == State::doingHandshake) {
688✔
404
      iostate = handleHandshake(now);
408✔
405
      if (d_connectionDied) {
408✔
406
        return;
28✔
407
      }
28✔
408
    }
408✔
409

410
    if (d_state == State::readingProxyProtocolHeader) {
660✔
UNCOV
411
      auto status = handleProxyProtocolPayload();
2✔
UNCOV
412
      if (status == ProxyProtocolResult::Done) {
2!
UNCOV
413
        if (isProxyPayloadOutsideTLS()) {
2✔
UNCOV
414
          d_state = State::doingHandshake;
1✔
UNCOV
415
          iostate = handleHandshake(now);
1✔
UNCOV
416
          if (d_connectionDied) {
1!
417
            return;
×
418
          }
×
UNCOV
419
        }
1✔
UNCOV
420
        else {
1✔
UNCOV
421
          d_state = State::waitingForQuery;
1✔
UNCOV
422
          handleConnectionReady();
1✔
UNCOV
423
        }
1✔
UNCOV
424
      }
2✔
425
      else if (status == ProxyProtocolResult::Error) {
×
426
        d_connectionDied = true;
×
427
        stopIO();
×
428
        return;
×
429
      }
×
UNCOV
430
    }
2✔
431

432
    if (!d_inReadFunction && active() && !d_connectionClosing && (d_state == State::waitingForQuery || d_state == State::idle)) {
660!
433
      do {
1,127✔
434
        iostate = readHTTPData();
1,127✔
435
      } while (!d_inReadFunction && active() && !d_connectionClosing && iostate == IOState::Done);
1,127!
436
    }
458✔
437

438
    if (!active()) {
660✔
439
      stopIO();
175✔
440
      return;
175✔
441
    }
175✔
442
    /*
443
      So:
444
      - if we have a pending write, we need to wait until the socket becomes writable
445
        and then call handleWritableCallback
446
      - if we have NeedWrite but no pending write, we need to wait until the socket
447
        becomes writable but for handleReadableIOCallback
448
      - if we have NeedRead, or nghttp2_session_want_read, wait until the socket
449
        becomes readable and call handleReadableIOCallback
450
    */
451
    if (hasPendingWrite()) {
485✔
452
      updateIO(IOState::NeedWrite, handleWritableIOCallback);
1✔
453
    }
1✔
454
    else if (iostate == IOState::NeedWrite) {
484!
455
      updateIO(IOState::NeedWrite, handleReadableIOCallback);
×
456
    }
×
457
    else if (!d_connectionClosing) {
484✔
458
      if (nghttp2_session_want_read(d_session.get()) != 0) {
481✔
459
        updateIO(IOState::NeedRead, handleReadableIOCallback);
480✔
460
      }
480✔
461
      else {
1✔
462
        if (getConcurrentStreamsCount() == 0) {
1!
463
          d_connectionDied = true;
1✔
464
          stopIO();
1✔
465
        }
1✔
466
        else {
×
467
          updateIO(IOState::Done, handleReadableIOCallback);
×
468
        }
×
469
      }
1✔
470
    }
481✔
UNCOV
471
    else {
3✔
UNCOV
472
      if (getConcurrentStreamsCount() == 0) {
3!
473
        d_connectionDied = true;
×
474
        stopIO();
×
475
      }
×
UNCOV
476
      else {
3✔
UNCOV
477
        updateIO(IOState::Done, handleReadableIOCallback);
3✔
UNCOV
478
      }
3✔
UNCOV
479
    }
3✔
480
  }
485✔
481
  catch (const std::exception& e) {
688✔
UNCOV
482
    vinfolog("Exception when processing IO for incoming DoH connection from %s: %s", d_ci.remote.toStringWithPort(), e.what());
1!
UNCOV
483
    d_connectionDied = true;
1✔
UNCOV
484
    stopIO();
1✔
UNCOV
485
  }
1✔
486
}
688✔
487

488
void IncomingHTTP2Connection::writeToSocket(bool socketReady)
489
{
391✔
490
  try {
391✔
491
    d_needFlush = false;
391✔
492
    IOState newState = d_handler.tryWrite(d_out, d_outPos, d_out.size());
391✔
493

494
    if (newState == IOState::Done) {
391✔
495
      d_pendingWrite = false;
383✔
496
      d_out.clear();
383✔
497
      d_outPos = 0;
383✔
498
      if (active() && !d_connectionClosing) {
383!
499
        updateIO(IOState::NeedRead, handleReadableIOCallback);
381✔
500
      }
381✔
UNCOV
501
      else {
2✔
UNCOV
502
        stopIO();
2✔
UNCOV
503
      }
2✔
504
    }
383✔
505
    else {
8✔
506
      updateIO(newState, handleWritableIOCallback);
8✔
507
      d_pendingWrite = true;
8✔
508
    }
8✔
509
  }
391✔
510
  catch (const std::exception& e) {
391✔
511
    vinfolog("Exception while trying to write (%s) to HTTP client connection to %s: %s", (socketReady ? "ready" : "send"), d_ci.remote.toStringWithPort(), e.what());
7!
512
    handleIOError();
7✔
513
  }
7✔
514
}
391✔
515

516
ssize_t IncomingHTTP2Connection::send_callback(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data)
517
{
723✔
518
  (void)session;
723✔
519
  (void)flags;
723✔
520
  auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
723✔
521
  if (conn->d_connectionDied) {
723✔
522
    return static_cast<ssize_t>(length);
4✔
523
  }
4✔
524
  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): nghttp2 API
525
  conn->d_out.insert(conn->d_out.end(), data, data + length);
719✔
526

527
  if (conn->d_connectionClosing || conn->d_needFlush) {
719✔
528
    conn->writeToSocket(false);
365✔
529
  }
365✔
530

531
  return static_cast<ssize_t>(length);
719✔
532
}
723✔
533

534
static const std::array<const std::string, static_cast<size_t>(NGHTTP2Headers::HeaderConstantIndexes::COUNT)> s_headerConstants{
535
  "200",
536
  ":method",
537
  "POST",
538
  ":scheme",
539
  "https",
540
  ":authority",
541
  "x-forwarded-for",
542
  ":path",
543
  "content-length",
544
  ":status",
545
  "location",
546
  "accept",
547
  "application/dns-message",
548
  "cache-control",
549
  "content-type",
550
  "application/dns-message",
551
  "user-agent",
552
  "nghttp2-" NGHTTP2_VERSION "/dnsdist",
553
  "x-forwarded-port",
554
  "x-forwarded-proto",
555
  "dns-over-udp",
556
  "dns-over-tcp",
557
  "dns-over-tls",
558
  "dns-over-http",
559
  "dns-over-https"};
560

561
static const std::string s_authorityHeaderName(":authority");
562
static const std::string s_pathHeaderName(":path");
563
static const std::string s_methodHeaderName(":method");
564
static const std::string s_schemeHeaderName(":scheme");
565
static const std::string s_xForwardedForHeaderName("x-forwarded-for");
566

567
static void addHeader(std::vector<nghttp2_nv>& headers, const std::string_view& name, bool nameIsStatic, const std::string_view& value, bool valueIsStatic)
568
{
2,116✔
569
  /* Be careful when setting NGHTTP2_NV_FLAG_NO_COPY_NAME or NGHTTP2_NV_FLAG_NO_COPY_VALUE, the corresponding name or value needs to exist until after nghttp2_session_send() has been called, not just nghttp2_submit_response(). */
570
  uint8_t flag{NGHTTP2_NV_FLAG_NONE};
2,116✔
571
  if (nameIsStatic) {
2,116✔
572
    flag |= NGHTTP2_NV_FLAG_NO_COPY_NAME;
1,929✔
573
  }
1,929✔
574
  if (valueIsStatic) {
2,116✔
575
    flag |= NGHTTP2_NV_FLAG_NO_COPY_VALUE;
1,086✔
576
  }
1,086✔
577

578
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-type-reinterpret-cast): nghttp2 API
579
  headers.push_back({const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(name.data())), const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(value.data())), name.size(), value.size(), flag});
2,116✔
580
}
2,116✔
581

582
void NGHTTP2Headers::addStaticHeader(std::vector<nghttp2_nv>& headers, NGHTTP2Headers::HeaderConstantIndexes nameKey, NGHTTP2Headers::HeaderConstantIndexes valueKey)
583
{
1,086✔
584
  const auto& name = s_headerConstants.at(static_cast<size_t>(nameKey));
1,086✔
585
  const auto& value = s_headerConstants.at(static_cast<size_t>(valueKey));
1,086✔
586

587
  addHeader(headers, name, true, value, true);
1,086✔
588
}
1,086✔
589

590
void NGHTTP2Headers::addCustomDynamicHeader(std::vector<nghttp2_nv>& headers, const std::string& name, const std::string_view& value)
UNCOV
591
{
187✔
UNCOV
592
  addHeader(headers, name, false, value, false);
187✔
UNCOV
593
}
187✔
594

595
void NGHTTP2Headers::addDynamicHeader(std::vector<nghttp2_nv>& headers, NGHTTP2Headers::HeaderConstantIndexes nameKey, const std::string_view& value)
596
{
843✔
597
  const auto& name = s_headerConstants.at(static_cast<size_t>(nameKey));
843✔
598
  addHeader(headers, name, true, value, false);
843✔
599
}
843✔
600

601
std::unordered_map<IncomingHTTP2Connection::StreamID, IncomingHTTP2Connection::PendingQuery>::iterator IncomingHTTP2Connection::getStreamContext(StreamID streamID)
602
{
1,649✔
603
  auto streamIt = d_currentStreams.find(streamID);
1,649✔
604
  if (streamIt == d_currentStreams.end()) {
1,649!
605
    /* it might have been closed by the remote end in the meantime */
606
    d_killedStreams.erase(streamID);
×
607
  }
×
608
  return streamIt;
1,649✔
609
}
1,649✔
610

611
IOState IncomingHTTP2Connection::sendResponse(const struct timeval& now, TCPResponse&& response)
612
{
144✔
613
  (void)now;
144✔
614
  if (response.d_idstate.d_streamID == -1) {
144!
615
    throw std::runtime_error("Invalid DoH stream ID while sending response");
×
616
  }
×
617
  auto streamIt = getStreamContext(response.d_idstate.d_streamID);
144✔
618
  if (streamIt == d_currentStreams.end()) {
144!
619
    /* it might have been closed by the remote end in the meantime */
620
    return hasPendingWrite() ? IOState::NeedWrite : IOState::Done;
×
621
  }
×
622
  auto& context = streamIt->second;
144✔
623

624
  uint32_t statusCode = 200U;
144✔
625
  std::string contentType;
144✔
626
  bool sendContentType = true;
144✔
627
  auto& responseBuffer = context.d_buffer;
144✔
628
  if (context.d_statusCode != 0) {
144✔
UNCOV
629
    responseBuffer = std::move(context.d_response);
7✔
UNCOV
630
    statusCode = context.d_statusCode;
7✔
UNCOV
631
    contentType = std::move(context.d_contentTypeOut);
7✔
UNCOV
632
  }
7✔
633
  else {
137✔
634
    responseBuffer = std::move(response.d_buffer);
137✔
635
  }
137✔
636

637
  auto sent = responseBuffer.size();
144✔
638
  sendResponse(response.d_idstate.d_streamID, context, statusCode, d_ci.cs->dohFrontend->d_customResponseHeaders, contentType, sendContentType);
144✔
639
  handleResponseSent(response, sent);
144✔
640

641
  return hasPendingWrite() ? IOState::NeedWrite : IOState::Done;
144!
642
}
144✔
643

644
void IncomingHTTP2Connection::notifyIOError(const struct timeval& now, TCPResponse&& response)
645
{
13✔
646
  if (std::this_thread::get_id() != d_creatorThreadID) {
13✔
647
    /* empty buffer will signal an IO error */
UNCOV
648
    response.d_buffer.clear();
6✔
UNCOV
649
    handleCrossProtocolResponse(now, std::move(response));
6✔
UNCOV
650
    return;
6✔
UNCOV
651
  }
6✔
652

653
  if (response.d_idstate.d_streamID == -1) {
7!
654
    throw std::runtime_error("Invalid DoH stream ID while handling I/O error notification");
×
655
  }
×
656

657
  auto streamIt = getStreamContext(response.d_idstate.d_streamID);
7✔
658
  if (streamIt == d_currentStreams.end()) {
7!
659
    /* it might have been closed by the remote end in the meantime */
660
    return;
×
661
  }
×
662
  auto& context = streamIt->second;
7✔
663
  context.d_buffer = std::move(response.d_buffer);
7✔
664
  sendResponse(response.d_idstate.d_streamID, context, 502, d_ci.cs->dohFrontend->d_customResponseHeaders);
7✔
665
}
7✔
666

667
bool IncomingHTTP2Connection::sendResponse(IncomingHTTP2Connection::StreamID streamID, IncomingHTTP2Connection::PendingQuery& context, uint16_t responseCode, const HeadersMap& customResponseHeaders, const std::string& contentType, bool addContentType)
668
{
181✔
669
  /* if data_prd is not NULL, it provides data which will be sent in subsequent DATA frames. In this case, a method that allows request message bodies (https://tools.ietf.org/html/rfc7231#section-4) must be specified with :method key (e.g. POST). This function does not take ownership of the data_prd. The function copies the members of the data_prd. If data_prd is NULL, HEADERS have END_STREAM set.
670
   */
671
  nghttp2_data_provider data_provider;
181✔
672

673
  data_provider.source.ptr = this;
181✔
674
  data_provider.read_callback = [](nghttp2_session*, IncomingHTTP2Connection::StreamID stream_id, uint8_t* buf, size_t length, uint32_t* data_flags, nghttp2_data_source* source, void* cb_data) -> ssize_t {
181✔
675
    (void)source;
181✔
676
    auto* connection = static_cast<IncomingHTTP2Connection*>(cb_data);
181✔
677
    auto& obj = connection->d_currentStreams.at(stream_id);
181✔
678
    size_t toCopy = 0;
181✔
679
    if (obj.d_queryPos < obj.d_buffer.size()) {
181!
680
      size_t remaining = obj.d_buffer.size() - obj.d_queryPos;
181✔
681
      toCopy = length > remaining ? remaining : length;
181!
682
      memcpy(buf, &obj.d_buffer.at(obj.d_queryPos), toCopy);
181✔
683
      obj.d_queryPos += toCopy;
181✔
684
    }
181✔
685

686
    if (obj.d_queryPos >= obj.d_buffer.size()) {
181!
687
      *data_flags |= NGHTTP2_DATA_FLAG_EOF;
181✔
688
      obj.d_buffer.clear();
181✔
689
      connection->d_needFlush = true;
181✔
690
    }
181✔
691
    return static_cast<ssize_t>(toCopy);
181✔
692
  };
181✔
693

694
  const auto& dohFrontend = d_ci.cs->dohFrontend;
181✔
695
  auto& responseBody = context.d_buffer;
181✔
696

697
  std::vector<nghttp2_nv> headers;
181✔
698
  std::string responseCodeStr;
181✔
699
  std::string cacheControlValue;
181✔
700
  std::string location;
181✔
701
  /* remember that dynamic header values should be kept alive
702
     until we have called nghttp2_submit_response(), at least */
703
  /* status, content-type, cache-control, content-length */
704
  headers.reserve(4);
181✔
705

706
  if (responseCode == 200) {
181✔
707
    NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::STATUS_NAME, NGHTTP2Headers::HeaderConstantIndexes::OK_200_VALUE);
142✔
708
    ++dohFrontend->d_validresponses;
142✔
709
    ++dohFrontend->d_http2Stats.d_nb200Responses;
142✔
710

711
    if (addContentType) {
142!
712
      if (contentType.empty()) {
142✔
713
        NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_NAME, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_VALUE);
137✔
714
      }
137✔
UNCOV
715
      else {
5✔
UNCOV
716
        NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_NAME, contentType);
5✔
UNCOV
717
      }
5✔
718
    }
142✔
719

720
    if (dohFrontend->d_sendCacheControlHeaders && responseBody.size() > sizeof(dnsheader)) {
142✔
721
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): API
722
      uint32_t minTTL = getDNSPacketMinTTL(reinterpret_cast<const char*>(responseBody.data()), responseBody.size());
136✔
723
      if (minTTL != std::numeric_limits<uint32_t>::max()) {
136✔
724
        cacheControlValue = "max-age=" + std::to_string(minTTL);
97✔
725
        NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CACHE_CONTROL_NAME, cacheControlValue);
97✔
726
      }
97✔
727
    }
136✔
728
  }
142✔
729
  else {
39✔
730
    responseCodeStr = std::to_string(responseCode);
39✔
731
    NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::STATUS_NAME, responseCodeStr);
39✔
732

733
    if (responseCode >= 300 && responseCode < 400) {
39!
734
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
UNCOV
735
      location = std::string(reinterpret_cast<const char*>(responseBody.data()), responseBody.size());
2✔
UNCOV
736
      NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_NAME, "text/html; charset=utf-8");
2✔
UNCOV
737
      NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::LOCATION_NAME, location);
2✔
UNCOV
738
      static const std::string s_redirectStart{"<!DOCTYPE html><TITLE>Moved</TITLE><P>The document has moved <A HREF=\""};
2✔
UNCOV
739
      static const std::string s_redirectEnd{"\">here</A>"};
2✔
UNCOV
740
      responseBody.reserve(s_redirectStart.size() + responseBody.size() + s_redirectEnd.size());
2✔
UNCOV
741
      responseBody.insert(responseBody.begin(), s_redirectStart.begin(), s_redirectStart.end());
2✔
UNCOV
742
      responseBody.insert(responseBody.end(), s_redirectEnd.begin(), s_redirectEnd.end());
2✔
UNCOV
743
      ++dohFrontend->d_redirectresponses;
2✔
UNCOV
744
    }
2✔
745
    else {
37✔
746
      ++dohFrontend->d_errorresponses;
37✔
747
      switch (responseCode) {
37✔
UNCOV
748
      case 400:
15✔
UNCOV
749
        ++dohFrontend->d_http2Stats.d_nb400Responses;
15✔
UNCOV
750
        break;
15✔
751
      case 403:
6✔
752
        ++dohFrontend->d_http2Stats.d_nb403Responses;
6✔
753
        break;
6✔
754
      case 500:
×
755
        ++dohFrontend->d_http2Stats.d_nb500Responses;
×
756
        break;
×
757
      case 502:
7✔
758
        ++dohFrontend->d_http2Stats.d_nb502Responses;
7✔
759
        break;
7✔
UNCOV
760
      default:
9✔
UNCOV
761
        ++dohFrontend->d_http2Stats.d_nbOtherResponses;
9✔
UNCOV
762
        break;
9✔
763
      }
37✔
764

765
      if (!responseBody.empty()) {
37✔
766
        NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_NAME, "text/plain; charset=utf-8");
30✔
767
      }
30✔
768
      else {
7✔
769
        static const std::string invalid{"invalid DNS query"};
7✔
770
        static const std::string notAllowed{"dns query not allowed"};
7✔
771
        static const std::string noDownstream{"no downstream server available"};
7✔
772
        static const std::string internalServerError{"Internal Server Error"};
7✔
773

774
        switch (responseCode) {
7✔
775
        case 400:
×
776
          responseBody.insert(responseBody.begin(), invalid.begin(), invalid.end());
×
777
          break;
×
778
        case 403:
×
779
          responseBody.insert(responseBody.begin(), notAllowed.begin(), notAllowed.end());
×
780
          break;
×
781
        case 502:
7!
782
          responseBody.insert(responseBody.begin(), noDownstream.begin(), noDownstream.end());
7✔
783
          break;
7✔
784
        case 500:
×
785
          /* fall-through */
786
        default:
×
787
          responseBody.insert(responseBody.begin(), internalServerError.begin(), internalServerError.end());
×
788
          break;
×
789
        }
7✔
790
      }
7✔
791
    }
37✔
792
  }
39✔
793

794
  const std::string contentLength = std::to_string(responseBody.size());
181✔
795
  NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_LENGTH_NAME, contentLength);
181✔
796

797
  for (const auto& [key, value] : customResponseHeaders) {
192✔
UNCOV
798
    NGHTTP2Headers::addCustomDynamicHeader(headers, key, value);
187✔
UNCOV
799
  }
187✔
800

801
  context.d_sendingResponse = true;
181✔
802
  auto ret = nghttp2_submit_response(d_session.get(), streamID, headers.data(), headers.size(), &data_provider);
181✔
803
  if (ret != 0) {
181!
804
    vinfolog("Error submitting HTTP response for stream %d: %s", streamID, nghttp2_strerror(ret));
×
805
    d_currentStreams.erase(streamID);
×
806
    return false;
×
807
  }
×
808

809
  if (!d_inReadFunction) {
181✔
810
    ret = nghttp2_session_send(d_session.get());
103✔
811
    if (ret != 0) {
103!
812
      d_currentStreams.erase(streamID);
×
813
      vinfolog("Error flushing HTTP response for stream %d: %s", streamID, nghttp2_strerror(ret));
×
814
      return false;
×
815
    }
×
816
  }
103✔
817

818
  return true;
181✔
819
}
181✔
820

821
static void processForwardedForHeader(const std::unique_ptr<HeadersMap>& headers, ComboAddress& remote)
UNCOV
822
{
5✔
UNCOV
823
  if (!headers) {
5✔
UNCOV
824
    return;
1✔
UNCOV
825
  }
1✔
826

UNCOV
827
  auto headerIt = headers->find(s_xForwardedForHeaderName);
4✔
UNCOV
828
  if (headerIt == headers->end()) {
4!
829
    return;
×
830
  }
×
831

UNCOV
832
  std::string_view value = headerIt->second;
4✔
UNCOV
833
  try {
4✔
UNCOV
834
    auto pos = value.rfind(',');
4✔
UNCOV
835
    if (pos != std::string_view::npos) {
4✔
UNCOV
836
      ++pos;
2✔
UNCOV
837
      for (; pos < value.size() && value[pos] == ' '; ++pos) {
4!
UNCOV
838
      }
2✔
839

UNCOV
840
      if (pos < value.size()) {
2!
UNCOV
841
        value = value.substr(pos);
2✔
UNCOV
842
      }
2✔
UNCOV
843
    }
2✔
UNCOV
844
    auto newRemote = ComboAddress(std::string(value));
4✔
UNCOV
845
    remote = newRemote;
4✔
UNCOV
846
  }
4✔
UNCOV
847
  catch (const std::exception& e) {
4✔
848
    vinfolog("Invalid X-Forwarded-For header ('%s') received from %s : %s", std::string(value), remote.toStringWithPort(), e.what());
×
849
  }
×
UNCOV
850
  catch (const PDNSException& e) {
4✔
851
    vinfolog("Invalid X-Forwarded-For header ('%s') received from %s : %s", std::string(value), remote.toStringWithPort(), e.reason);
×
852
  }
×
UNCOV
853
}
4✔
854

855
void IncomingHTTP2Connection::handleIncomingQuery(IncomingHTTP2Connection::PendingQuery&& query, IncomingHTTP2Connection::StreamID streamID)
856
{
181✔
857
  const auto handleImmediateResponse = [this, &query, streamID](uint16_t code, const std::string& reason, PacketBuffer&& response = PacketBuffer()) {
181✔
858
    if (response.empty()) {
26!
859
      query.d_buffer.clear();
26✔
860
      query.d_buffer.insert(query.d_buffer.begin(), reason.begin(), reason.end());
26✔
861
    }
26✔
862
    else {
×
863
      query.d_buffer = std::move(response);
×
864
    }
×
865
    vinfolog("Sending an immediate %d response to incoming DoH query: %s", code, reason);
26✔
866
    sendResponse(streamID, query, code, d_ci.cs->dohFrontend->d_customResponseHeaders);
26✔
867
  };
26✔
868

869
  if (query.d_method == PendingQuery::Method::Unknown || query.d_method == PendingQuery::Method::Unsupported) {
181!
UNCOV
870
    handleImmediateResponse(400, "DoH query not allowed because of unsupported HTTP method");
2✔
UNCOV
871
    return;
2✔
UNCOV
872
  }
2✔
873

874
  ++d_ci.cs->dohFrontend->d_http2Stats.d_nbQueries;
179✔
875

876
  if (d_ci.cs->dohFrontend->d_trustForwardedForHeader) {
179✔
UNCOV
877
    processForwardedForHeader(query.d_headers, d_proxiedRemote);
5✔
878

879
    /* second ACL lookup based on the updated address */
UNCOV
880
    if (!dnsdist::configuration::getCurrentRuntimeConfiguration().d_ACL.match(d_proxiedRemote)) {
5✔
UNCOV
881
      ++dnsdist::metrics::g_stats.aclDrops;
1✔
UNCOV
882
      vinfolog("Query from %s (%s) (DoH) dropped because of ACL", d_ci.remote.toStringWithPort(), d_proxiedRemote.toStringWithPort());
1!
UNCOV
883
      handleImmediateResponse(403, "DoH query not allowed because of ACL");
1✔
UNCOV
884
      return;
1✔
UNCOV
885
    }
1✔
886

UNCOV
887
    if (!d_ci.cs->dohFrontend->d_keepIncomingHeaders) {
4!
UNCOV
888
      query.d_headers.reset();
4✔
UNCOV
889
    }
4✔
UNCOV
890
  }
4✔
891

892
  if (d_ci.cs->dohFrontend->d_exactPathMatching) {
178✔
893
    if (d_ci.cs->dohFrontend->d_urls.count(query.d_path) == 0) {
175✔
UNCOV
894
      handleImmediateResponse(404, "there is no endpoint configured for this path");
4✔
UNCOV
895
      return;
4✔
UNCOV
896
    }
4✔
897
  }
175✔
UNCOV
898
  else {
3✔
UNCOV
899
    bool found = false;
3✔
UNCOV
900
    for (const auto& path : d_ci.cs->dohFrontend->d_urls) {
3✔
UNCOV
901
      if (boost::starts_with(query.d_path, path)) {
3✔
UNCOV
902
        found = true;
2✔
UNCOV
903
        break;
2✔
UNCOV
904
      }
2✔
UNCOV
905
    }
3✔
UNCOV
906
    if (!found) {
3✔
UNCOV
907
      handleImmediateResponse(404, "there is no endpoint configured for this path");
1✔
UNCOV
908
      return;
1✔
UNCOV
909
    }
1✔
UNCOV
910
  }
3✔
911

912
  /* the responses map can be updated at runtime, so we need to take a copy of
913
     the shared pointer, increasing the reference counter */
914
  auto responsesMap = d_ci.cs->dohFrontend->d_responsesMap;
173✔
915
  if (responsesMap) {
173✔
UNCOV
916
    for (const auto& entry : *responsesMap) {
58✔
UNCOV
917
      if (entry->matches(query.d_path)) {
58✔
UNCOV
918
        const auto& customHeaders = entry->getHeaders();
4✔
UNCOV
919
        query.d_buffer = entry->getContent();
4✔
UNCOV
920
        if (entry->getStatusCode() >= 400 && !query.d_buffer.empty()) {
4!
921
          // legacy trailing 0 from the h2o era
UNCOV
922
          query.d_buffer.pop_back();
4✔
UNCOV
923
        }
4✔
924

UNCOV
925
        sendResponse(streamID, query, entry->getStatusCode(), customHeaders ? *customHeaders : d_ci.cs->dohFrontend->d_customResponseHeaders, std::string(), false);
4!
UNCOV
926
        return;
4✔
UNCOV
927
      }
4✔
UNCOV
928
    }
58✔
UNCOV
929
  }
58✔
930

931
  if (query.d_buffer.empty() && query.d_method == PendingQuery::Method::Get && !query.d_queryString.empty()) {
169✔
UNCOV
932
    auto payload = dnsdist::doh::getPayloadFromPath(query.d_queryString);
152✔
UNCOV
933
    if (payload) {
152✔
UNCOV
934
      query.d_buffer = std::move(*payload);
148✔
UNCOV
935
    }
148✔
UNCOV
936
    else {
4✔
UNCOV
937
      ++d_ci.cs->dohFrontend->d_badrequests;
4✔
UNCOV
938
      handleImmediateResponse(400, "DoH unable to decode BASE64-URL");
4✔
UNCOV
939
      return;
4✔
UNCOV
940
    }
4✔
UNCOV
941
  }
152✔
942

943
  if (query.d_method == PendingQuery::Method::Get) {
165✔
UNCOV
944
    ++d_ci.cs->dohFrontend->d_getqueries;
150✔
UNCOV
945
  }
150✔
946
  else if (query.d_method == PendingQuery::Method::Post) {
15!
947
    ++d_ci.cs->dohFrontend->d_postqueries;
15✔
948
  }
15✔
949

950
  try {
165✔
951
    struct timeval now{};
165✔
952
    gettimeofday(&now, nullptr);
165✔
953
    auto processingResult = handleQuery(std::move(query.d_buffer), now, streamID);
165✔
954

955
    switch (processingResult) {
165✔
UNCOV
956
    case QueryProcessingResult::TooSmall:
4✔
UNCOV
957
      handleImmediateResponse(400, "DoH non-compliant query");
4✔
UNCOV
958
      break;
4✔
UNCOV
959
    case QueryProcessingResult::InvalidHeaders:
2✔
UNCOV
960
      handleImmediateResponse(400, "DoH invalid headers");
2✔
UNCOV
961
      break;
2✔
962
    case QueryProcessingResult::Dropped:
5✔
963
      handleImmediateResponse(403, "DoH dropped query");
5✔
964
      break;
5✔
965
    case QueryProcessingResult::NoBackend:
×
966
      handleImmediateResponse(502, "DoH no backend available");
×
967
      return;
×
968
    case QueryProcessingResult::Forwarded:
67✔
969
    case QueryProcessingResult::Asynchronous:
103✔
970
    case QueryProcessingResult::SelfAnswered:
151✔
971
      break;
151✔
972
    }
165✔
973
  }
165✔
974
  catch (const std::exception& e) {
165✔
UNCOV
975
    vinfolog("Exception while processing DoH query: %s", e.what());
3✔
UNCOV
976
    handleImmediateResponse(400, "DoH non-compliant query");
3✔
UNCOV
977
    return;
3✔
UNCOV
978
  }
3✔
979
}
165✔
980

981
int IncomingHTTP2Connection::on_frame_recv_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data)
982
{
876✔
983
  (void)session;
876✔
984
  auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
876✔
985
  /* is this the last frame for this stream? */
986
  if ((frame->hd.type == NGHTTP2_HEADERS || frame->hd.type == NGHTTP2_DATA) && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) != 0) {
876✔
987
    auto streamID = frame->hd.stream_id;
181✔
988
    auto stream = conn->getStreamContext(streamID);
181✔
989
    if (stream != conn->d_currentStreams.end()) {
181!
990
      conn->handleIncomingQuery(std::move(stream->second), streamID);
181✔
991
    }
181✔
992
    else {
×
993
      vinfolog("Stream %d NOT FOUND", streamID);
×
994
      return NGHTTP2_ERR_CALLBACK_FAILURE;
×
995
    }
×
996
  }
181✔
997
  else if (frame->hd.type == NGHTTP2_PING) {
695!
998
    conn->d_needFlush = true;
×
999
  }
×
1000

1001
  return 0;
876✔
1002
}
876✔
1003

1004
int IncomingHTTP2Connection::on_stream_close_callback(nghttp2_session* session, IncomingHTTP2Connection::StreamID stream_id, uint32_t error_code, void* user_data)
1005
{
181✔
1006
  (void)session;
181✔
1007
  (void)error_code;
181✔
1008
  auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
181✔
1009

1010
  auto streamIt = conn->d_currentStreams.find(stream_id);
181✔
1011
  if (streamIt == conn->d_currentStreams.end()) {
181✔
1012
    return 0;
1✔
1013
  }
1✔
1014

1015
  if (!streamIt->second.d_sendingResponse) {
180!
1016
    conn->d_killedStreams.emplace(stream_id);
×
1017
  }
×
1018

1019
  conn->d_currentStreams.erase(streamIt);
180✔
1020
  return 0;
180✔
1021
}
181✔
1022

1023
int IncomingHTTP2Connection::on_begin_headers_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data)
1024
{
183✔
1025
  (void)session;
183✔
1026
  if (frame->hd.type != NGHTTP2_HEADERS || frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
183!
1027
    return 0;
×
1028
  }
×
1029

1030
  auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
183✔
1031
  auto close_connection = [](IncomingHTTP2Connection* connection) -> int {
183✔
1032
    connection->d_connectionClosing = true;
×
1033
    connection->d_needFlush = true;
×
1034
    nghttp2_session_terminate_session(connection->d_session.get(), NGHTTP2_REFUSED_STREAM);
×
1035
    return 0;
×
1036
  };
×
1037

1038
  if (conn->getConcurrentStreamsCount() >= dnsdist::doh::MAX_INCOMING_CONCURRENT_STREAMS) {
183!
1039
    vinfolog("Too many concurrent streams on connection from %s", conn->d_ci.remote.toStringWithPort());
×
1040
    return close_connection(conn);
×
1041
  }
×
1042

1043
  auto insertPair = conn->d_currentStreams.emplace(frame->hd.stream_id, PendingQuery());
183✔
1044
  if (!insertPair.second) {
183!
1045
    /* there is a stream ID collision, something is very wrong! */
1046
    vinfolog("Stream ID collision (%d) on connection from %s", frame->hd.stream_id, conn->d_ci.remote.toStringWithPort());
×
1047
    return close_connection(conn);
×
1048
  }
×
1049

1050
  return 0;
183✔
1051
}
183✔
1052

1053
static std::string::size_type getLengthOfPathWithoutParameters(const std::string_view& path)
1054
{
183✔
1055
  auto pos = path.find('?');
183✔
1056
  if (pos == string::npos) {
183✔
1057
    return path.size();
23✔
1058
  }
23✔
1059

UNCOV
1060
  return pos;
160✔
1061
}
183✔
1062

1063
int IncomingHTTP2Connection::on_header_callback(nghttp2_session* session, const nghttp2_frame* frame, const uint8_t* name, size_t nameLen, const uint8_t* value, size_t valuelen, uint8_t flags, void* user_data)
1064
{
1,296✔
1065
  (void)session;
1,296✔
1066
  (void)flags;
1,296✔
1067
  auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
1,296✔
1068

1069
  if (frame->hd.type == NGHTTP2_HEADERS && frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
1,296!
1070
    if (nghttp2_check_header_name(name, nameLen) == 0) {
1,296!
1071
      vinfolog("Invalid header name");
×
1072
      return NGHTTP2_ERR_CALLBACK_FAILURE;
×
1073
    }
×
1074

1075
#if defined(HAVE_NGHTTP2_CHECK_HEADER_VALUE_RFC9113)
1,296✔
1076
    if (nghttp2_check_header_value_rfc9113(value, valuelen) == 0) {
1,296!
1077
      vinfolog("Invalid header value");
×
1078
      return NGHTTP2_ERR_CALLBACK_FAILURE;
×
1079
    }
×
1080
#endif /* HAVE_NGHTTP2_CHECK_HEADER_VALUE_RFC9113 */
1,296✔
1081

1082
    auto headerMatches = [name, nameLen](const std::string& expected) -> bool {
4,121✔
1083
      return nameLen == expected.size() && memcmp(name, expected.data(), expected.size()) == 0;
4,121✔
1084
    };
4,121✔
1085

1086
    auto stream = conn->getStreamContext(frame->hd.stream_id);
1,296✔
1087
    if (stream == conn->d_currentStreams.end()) {
1,296!
1088
      vinfolog("Unable to match the stream ID %d to a known one!", frame->hd.stream_id);
×
1089
      return NGHTTP2_ERR_CALLBACK_FAILURE;
×
1090
    }
×
1091
    auto& query = stream->second;
1,296✔
1092
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): nghttp2 API
1093
    auto valueView = std::string_view(reinterpret_cast<const char*>(value), valuelen);
1,296✔
1094
    if (headerMatches(s_pathHeaderName)) {
1,296✔
1095
#if defined(HAVE_NGHTTP2_CHECK_PATH)
183✔
1096
      if (nghttp2_check_path(value, valuelen) == 0) {
183!
1097
        vinfolog("Invalid path value");
×
1098
        return NGHTTP2_ERR_CALLBACK_FAILURE;
×
1099
      }
×
1100
#endif /* HAVE_NGHTTP2_CHECK_PATH */
183✔
1101

1102
      auto pathLen = getLengthOfPathWithoutParameters(valueView);
183✔
1103
      query.d_path = valueView.substr(0, pathLen);
183✔
1104
      if (pathLen < valueView.size()) {
183✔
UNCOV
1105
        query.d_queryString = valueView.substr(pathLen);
160✔
UNCOV
1106
      }
160✔
1107
    }
183✔
1108
    else if (headerMatches(s_authorityHeaderName)) {
1,113✔
1109
      query.d_host = valueView;
183✔
1110
    }
183✔
1111
    else if (headerMatches(s_schemeHeaderName)) {
930✔
1112
      query.d_scheme = valueView;
183✔
1113
    }
183✔
1114
    else if (headerMatches(s_methodHeaderName)) {
747✔
1115
#if defined(HAVE_NGHTTP2_CHECK_METHOD)
183✔
1116
      if (nghttp2_check_method(value, valuelen) == 0) {
183!
1117
        vinfolog("Invalid method value");
×
1118
        return NGHTTP2_ERR_CALLBACK_FAILURE;
×
1119
      }
×
1120
#endif /* HAVE_NGHTTP2_CHECK_METHOD */
183✔
1121

1122
      if (valueView == "GET") {
183✔
UNCOV
1123
        query.d_method = PendingQuery::Method::Get;
164✔
UNCOV
1124
      }
164✔
1125
      else if (valueView == "POST") {
19✔
1126
        query.d_method = PendingQuery::Method::Post;
17✔
1127
      }
17✔
UNCOV
1128
      else {
2✔
UNCOV
1129
        query.d_method = PendingQuery::Method::Unsupported;
2✔
UNCOV
1130
        vinfolog("Unsupported method value");
2!
UNCOV
1131
        return 0;
2✔
UNCOV
1132
      }
2✔
1133
    }
183✔
1134

1135
    if (conn->d_ci.cs->dohFrontend->d_keepIncomingHeaders || (conn->d_ci.cs->dohFrontend->d_trustForwardedForHeader && headerMatches(s_xForwardedForHeaderName))) {
1,294✔
UNCOV
1136
      if (!query.d_headers) {
487✔
UNCOV
1137
        query.d_headers = std::make_unique<HeadersMap>();
72✔
UNCOV
1138
      }
72✔
1139
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): nghttp2 API
UNCOV
1140
      query.d_headers->insert({std::string(reinterpret_cast<const char*>(name), nameLen), std::string(valueView)});
487✔
UNCOV
1141
    }
487✔
1142
  }
1,294✔
1143
  return 0;
1,294✔
1144
}
1,296✔
1145

1146
int IncomingHTTP2Connection::on_data_chunk_recv_callback(nghttp2_session* session, uint8_t flags, IncomingHTTP2Connection::StreamID stream_id, const uint8_t* data, size_t len, void* user_data)
1147
{
21✔
1148
  (void)session;
21✔
1149
  (void)flags;
21✔
1150
  auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
21✔
1151
  auto stream = conn->getStreamContext(stream_id);
21✔
1152
  if (stream == conn->d_currentStreams.end()) {
21!
1153
    vinfolog("Unable to match the stream ID %d to a known one!", stream_id);
×
1154
    return NGHTTP2_ERR_CALLBACK_FAILURE;
×
1155
  }
×
1156
  if (len > std::numeric_limits<uint16_t>::max() || (std::numeric_limits<uint16_t>::max() - stream->second.d_buffer.size()) < len) {
21!
1157
    vinfolog("Data frame of size %d is too large for a DNS query (we already have %d)", len, stream->second.d_buffer.size());
×
1158
    return NGHTTP2_ERR_CALLBACK_FAILURE;
×
1159
  }
×
1160

1161
  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): nghttp2 API
1162
  stream->second.d_buffer.insert(stream->second.d_buffer.end(), data, data + len);
21✔
1163

1164
  return 0;
21✔
1165
}
21✔
1166

1167
int IncomingHTTP2Connection::on_error_callback(nghttp2_session* session, int lib_error_code, const char* msg, size_t len, void* user_data)
UNCOV
1168
{
2✔
UNCOV
1169
  (void)session;
2✔
UNCOV
1170
  auto* conn = static_cast<IncomingHTTP2Connection*>(user_data);
2✔
1171

UNCOV
1172
  vinfolog("Error in HTTP/2 connection from %s: %s (%d)", conn->d_ci.remote.toStringWithPort(), std::string(msg, len), lib_error_code);
2!
UNCOV
1173
  conn->d_connectionClosing = true;
2✔
UNCOV
1174
  conn->d_needFlush = true;
2✔
UNCOV
1175
  nghttp2_session_terminate_session(conn->d_session.get(), NGHTTP2_NO_ERROR);
2✔
1176

UNCOV
1177
  return 0;
2✔
UNCOV
1178
}
2✔
1179

1180
IOState IncomingHTTP2Connection::readHTTPData()
1181
{
1,127✔
1182
  if (d_inReadFunction) {
1,127!
1183
    return IOState::Done;
×
1184
  }
×
1185
  ReadFunctionGuard readGuard(d_inReadFunction);
1,127✔
1186

1187
  IOState newState = IOState::Done;
1,127✔
1188
  size_t got = 0;
1,127✔
1189
  if (d_in.size() < s_initialReceiveBufferSize) {
1,127✔
1190
    d_in.resize(std::max(s_initialReceiveBufferSize, d_in.capacity()));
973✔
1191
  }
973✔
1192
  try {
1,127✔
1193
    newState = d_handler.tryRead(d_in, got, d_in.size(), true);
1,127✔
1194
    d_in.resize(got);
1,127✔
1195

1196
    if (got > 0) {
1,127✔
1197
      /* we got something */
1198
      auto readlen = nghttp2_session_mem_recv(d_session.get(), d_in.data(), d_in.size());
678✔
1199
      /* as long as we don't require a pause by returning nghttp2_error.NGHTTP2_ERR_PAUSE from a CB,
1200
         all data should be consumed before returning */
1201
      if (readlen < 0 || static_cast<size_t>(readlen) < d_in.size()) {
678!
UNCOV
1202
        throw std::runtime_error("Fatal error while passing received data to nghttp2: " + std::string(nghttp2_strerror((int)readlen)));
5✔
UNCOV
1203
      }
5✔
1204

1205
      nghttp2_session_send(d_session.get());
673✔
1206
    }
673✔
1207
  }
1,127✔
1208
  catch (const std::exception& e) {
1,127✔
1209
    vinfolog("Exception while trying to read from HTTP client connection to %s: %s", d_ci.remote.toStringWithPort(), e.what());
174✔
1210
    handleIOError();
174✔
1211
    return IOState::Done;
174✔
1212
  }
174✔
1213
  return newState;
953✔
1214
}
1,127✔
1215

1216
void IncomingHTTP2Connection::handleReadableIOCallback([[maybe_unused]] int descriptor, FDMultiplexer::funcparam_t& param)
1217
{
384✔
1218
  auto conn = boost::any_cast<std::shared_ptr<IncomingHTTP2Connection>>(param);
384✔
1219
  conn->handleIO();
384✔
1220
}
384✔
1221

1222
void IncomingHTTP2Connection::handleWritableIOCallback([[maybe_unused]] int descriptor, FDMultiplexer::funcparam_t& param)
1223
{
1✔
1224
  auto conn = boost::any_cast<std::shared_ptr<IncomingHTTP2Connection>>(param);
1✔
1225
  conn->writeToSocket(true);
1✔
1226
}
1✔
1227

1228
void IncomingHTTP2Connection::stopIO()
1229
{
385✔
1230
  if (d_ioState) {
385✔
1231
    d_ioState->reset();
384✔
1232
  }
384✔
1233
}
385✔
1234

1235
uint32_t IncomingHTTP2Connection::getConcurrentStreamsCount() const
1236
{
186✔
1237
  return d_currentStreams.size() + d_killedStreams.size();
186✔
1238
}
186✔
1239

1240
boost::optional<struct timeval> IncomingHTTP2Connection::getIdleClientReadTTD(struct timeval now) const
1241
{
555✔
1242
  const auto& currentConfig = dnsdist::configuration::getCurrentRuntimeConfiguration();
555✔
1243
  auto idleTimeout = d_ci.cs->dohFrontend->d_idleTimeout;
555✔
1244
  if (currentConfig.d_maxTCPConnectionDuration == 0 && idleTimeout == 0) {
555!
1245
    return boost::none;
×
1246
  }
×
1247

1248
  if (currentConfig.d_maxTCPConnectionDuration > 0) {
555!
1249
    auto elapsed = now.tv_sec - d_connectionStartTime.tv_sec;
×
1250
    if (elapsed < 0 || (static_cast<size_t>(elapsed) >= currentConfig.d_maxTCPConnectionDuration)) {
×
1251
      return now;
×
1252
    }
×
1253
    auto remaining = currentConfig.d_maxTCPConnectionDuration - elapsed;
×
1254
    if (idleTimeout == 0 || remaining <= static_cast<size_t>(idleTimeout)) {
×
1255
      now.tv_sec += static_cast<time_t>(remaining);
×
1256
      return now;
×
1257
    }
×
1258
  }
×
1259

1260
  now.tv_sec += idleTimeout;
555✔
1261
  return now;
555✔
1262
}
555✔
1263

1264
void IncomingHTTP2Connection::updateIO(IOState newState, const timeval& now)
1265
{
144✔
1266
  (void)now;
144✔
1267
  updateIO(newState, newState == IOState::NeedWrite ? handleWritableIOCallback : handleReadableIOCallback);
144!
1268
}
144✔
1269

1270
void IncomingHTTP2Connection::updateIO(IOState newState, const FDMultiplexer::callbackfunc_t& callback)
1271
{
1,009✔
1272
  boost::optional<struct timeval> ttd{boost::none};
1,009✔
1273

1274
  if (newState == IOState::Async) {
1,009!
1275
    auto shared = shared_from_this();
×
1276
    updateIOForAsync(shared);
×
1277
    return;
×
1278
  }
×
1279

1280
  auto shared = std::dynamic_pointer_cast<IncomingHTTP2Connection>(shared_from_this());
1,009✔
1281
  if (!shared || !d_ioState) {
1,009!
1282
    return;
×
1283
  }
×
1284

1285
  timeval now{};
1,009✔
1286
  gettimeofday(&now, nullptr);
1,009✔
1287

1288
  if (newState == IOState::NeedRead) {
1,009✔
1289
    /* use the idle TTL if the handshake has been completed (and proxy protocol payload received, if any),
1290
       and we have processed at least one query, otherwise we use the shorter read TTL  */
1291
    if ((d_state == State::waitingForQuery || d_state == State::idle) && (d_queriesCount > 0 || d_currentQueriesCount > 0)) {
1,004!
1292
      ttd = getIdleClientReadTTD(now);
555✔
1293
    }
555✔
1294
    else {
449✔
1295
      ttd = getClientReadTTD(now);
449✔
1296
    }
449✔
1297
    d_ioState->update(newState, callback, shared, ttd);
1,004✔
1298
  }
1,004✔
1299
  else if (newState == IOState::NeedWrite) {
5✔
1300
    ttd = getClientWriteTTD(now);
2✔
1301
    d_ioState->update(newState, callback, shared, ttd);
2✔
1302
  }
2✔
UNCOV
1303
  else if (newState == IOState::Done) {
3!
UNCOV
1304
    d_ioState->reset();
3✔
UNCOV
1305
  }
3✔
1306
}
1,009✔
1307

1308
void IncomingHTTP2Connection::handleIOError()
1309
{
181✔
1310
  d_connectionDied = true;
181✔
1311
  d_out.clear();
181✔
1312
  d_outPos = 0;
181✔
1313
  nghttp2_session_terminate_session(d_session.get(), NGHTTP2_PROTOCOL_ERROR);
181✔
1314
  d_currentStreams.clear();
181✔
1315
  d_killedStreams.clear();
181✔
1316
  stopIO();
181✔
1317
}
181✔
1318

1319
bool IncomingHTTP2Connection::active() const
1320
{
3,446✔
1321
  return !d_connectionDied && d_ioState != nullptr;
3,446!
1322
}
3,446✔
1323

1324
#endif /* HAVE_DNS_OVER_HTTPS && HAVE_NGHTTP2 */
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