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

PowerDNS / pdns / 13012068652

28 Jan 2025 01:59PM UTC coverage: 64.71% (+0.01%) from 64.699%
13012068652

Pull #14724

github

web-flow
Merge b15562560 into db18c3a17
Pull Request #14724: dnsdist: Add meson support

38328 of 90334 branches covered (42.43%)

Branch coverage included in aggregate %.

361 of 513 new or added lines in 35 files covered. (70.37%)

42 existing lines in 13 files now uncovered.

128150 of 166934 relevant lines covered (76.77%)

4540890.91 hits per line

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

83.42
/pdns/dnsdistdist/test-dnsdistnghttp2-in_cc.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
#ifndef BOOST_TEST_DYN_LINK
23
#define BOOST_TEST_DYN_LINK
24
#endif
25

26
#define BOOST_TEST_NO_MAIN
27

28
#include <boost/test/unit_test.hpp>
29

30
#include "dnswriter.hh"
31
#include "dnsdist.hh"
32
#include "dnsdist-proxy-protocol.hh"
33
#include "dnsdist-nghttp2-in.hh"
34

35
#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
36
#include <nghttp2/nghttp2.h>
37

38
extern std::function<ProcessQueryResult(DNSQuestion& dnsQuestion, std::shared_ptr<DownstreamState>& selectedBackend)> s_processQuery;
39

40
BOOST_AUTO_TEST_SUITE(test_dnsdistnghttp2_in_cc)
41

42
struct ExpectedStep
43
{
44
public:
45
  enum class ExpectedRequest
46
  {
47
    handshakeClient,
48
    readFromClient,
49
    writeToClient,
50
    closeClient,
51
  };
52

53
  ExpectedStep(ExpectedRequest req, IOState next, size_t bytes_ = 0, std::function<void(int descriptor)> func = nullptr) :
54
    cb(std::move(func)), request(req), nextState(next), bytes(bytes_)
55
  {
44✔
56
  }
44✔
57

58
  std::function<void(int descriptor)> cb{nullptr};
59
  ExpectedRequest request;
60
  IOState nextState;
61
  size_t bytes{0};
62
};
63

64
struct ExpectedData
65
{
66
  PacketBuffer d_proxyProtocolPayload;
67
  std::vector<PacketBuffer> d_queries;
68
  std::vector<PacketBuffer> d_responses;
69
  std::vector<uint16_t> d_responseCodes;
70
};
71

72
class DOHConnection;
73

74
static std::deque<ExpectedStep> s_steps;
75
static std::map<uint64_t, ExpectedData> s_connectionContexts;
76
static std::map<int, std::unique_ptr<DOHConnection>> s_connectionBuffers;
77
static uint64_t s_connectionID{0};
78

79
std::ostream& operator<<(std::ostream& outs, ExpectedStep::ExpectedRequest step);
80

81
std::ostream& operator<<(std::ostream& outs, ExpectedStep::ExpectedRequest step)
82
{
×
83
  static const std::vector<std::string> requests = {"handshake with client", "read from client", "write to client", "close connection to client", "connect to the backend", "read from the backend", "write to the backend", "close connection to backend"};
×
84
  outs << requests.at(static_cast<size_t>(step));
×
85
  return outs;
×
86
}
×
87

88
class DOHConnection
89
{
90
public:
91
  DOHConnection(uint64_t connectionID) :
92
    d_session(std::unique_ptr<nghttp2_session, void (*)(nghttp2_session*)>(nullptr, nghttp2_session_del)), d_connectionID(connectionID)
93
  {
7✔
94
    const auto& context = s_connectionContexts.at(connectionID);
7✔
95
    d_clientOutBuffer.insert(d_clientOutBuffer.begin(), context.d_proxyProtocolPayload.begin(), context.d_proxyProtocolPayload.end());
7✔
96

97
    nghttp2_session_callbacks* cbs = nullptr;
7✔
98
    nghttp2_session_callbacks_new(&cbs);
7✔
99
    std::unique_ptr<nghttp2_session_callbacks, void (*)(nghttp2_session_callbacks*)> callbacks(cbs, nghttp2_session_callbacks_del);
7✔
100
    cbs = nullptr;
7✔
101
    nghttp2_session_callbacks_set_send_callback(callbacks.get(), send_callback);
7✔
102
    nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks.get(), on_frame_recv_callback);
7✔
103
    nghttp2_session_callbacks_set_on_header_callback(callbacks.get(), on_header_callback);
7✔
104
    nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks.get(), on_data_chunk_recv_callback);
7✔
105
    nghttp2_session_callbacks_set_on_stream_close_callback(callbacks.get(), on_stream_close_callback);
7✔
106
    nghttp2_session* sess = nullptr;
7✔
107
    nghttp2_session_client_new(&sess, callbacks.get(), this);
7✔
108
    d_session = std::unique_ptr<nghttp2_session, void (*)(nghttp2_session*)>(sess, nghttp2_session_del);
7✔
109

110
    std::array<nghttp2_settings_entry, 3> settings{
7✔
111
      /* rfc7540 section-8.2.2:
112
         "Advertising a SETTINGS_MAX_CONCURRENT_STREAMS value of zero disables
113
         server push by preventing the server from creating the necessary
114
         streams."
115
      */
116
      nghttp2_settings_entry{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 0},
7✔
117
      nghttp2_settings_entry{NGHTTP2_SETTINGS_ENABLE_PUSH, 0},
7✔
118
      /* we might want to make the initial window size configurable, but 16M is a large enough default */
119
      nghttp2_settings_entry{NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 16 * 1024 * 1024}};
7✔
120
    /* client 24 bytes magic string will be sent by nghttp2 library */
121
    auto result = nghttp2_submit_settings(d_session.get(), NGHTTP2_FLAG_NONE, settings.data(), settings.size());
7✔
122
    if (result != 0) {
7!
123
      throw std::runtime_error("Error submitting settings:" + std::string(nghttp2_strerror(result)));
×
124
    }
×
125

126
    const std::string host("unit-tests");
7✔
127
    const std::string path("/dns-query");
7✔
128
    for (const auto& query : context.d_queries) {
7✔
129
      const auto querySize = std::to_string(query.size());
6✔
130
      std::vector<nghttp2_nv> headers;
6✔
131
      /* Pseudo-headers need to come first (rfc7540 8.1.2.1) */
132
      NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::METHOD_NAME, NGHTTP2Headers::HeaderConstantIndexes::METHOD_VALUE);
6✔
133
      NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::SCHEME_NAME, NGHTTP2Headers::HeaderConstantIndexes::SCHEME_VALUE);
6✔
134
      NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::AUTHORITY_NAME, host);
6✔
135
      NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::PATH_NAME, path);
6✔
136
      NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::ACCEPT_NAME, NGHTTP2Headers::HeaderConstantIndexes::ACCEPT_VALUE);
6✔
137
      NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_NAME, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_TYPE_VALUE);
6✔
138
      NGHTTP2Headers::addStaticHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::USER_AGENT_NAME, NGHTTP2Headers::HeaderConstantIndexes::USER_AGENT_VALUE);
6✔
139
      NGHTTP2Headers::addDynamicHeader(headers, NGHTTP2Headers::HeaderConstantIndexes::CONTENT_LENGTH_NAME, querySize);
6✔
140

141
      d_position = 0;
6✔
142
      d_currentQuery = query;
6✔
143
      nghttp2_data_provider data_provider;
6✔
144
      data_provider.source.ptr = this;
6✔
145
      data_provider.read_callback = [](nghttp2_session* session, int32_t stream_id, uint8_t* buf, size_t length, uint32_t* data_flags, nghttp2_data_source* source, void* user_data) -> ssize_t {
6✔
146
        (void)session;
6✔
147
        (void)stream_id;
6✔
148
        (void)source;
6✔
149
        auto* conn = static_cast<DOHConnection*>(user_data);
6✔
150
        auto& pos = conn->d_position;
6✔
151
        const auto& currentQuery = conn->d_currentQuery;
6✔
152
        size_t toCopy = 0;
6✔
153
        if (pos < currentQuery.size()) {
6!
154
          size_t remaining = currentQuery.size() - pos;
6✔
155
          toCopy = length > remaining ? remaining : length;
6!
156
          memcpy(buf, &currentQuery.at(pos), toCopy);
6✔
157
          pos += toCopy;
6✔
158
        }
6✔
159

160
        if (pos >= currentQuery.size()) {
6!
161
          *data_flags |= NGHTTP2_DATA_FLAG_EOF;
6✔
162
        }
6✔
163
        return static_cast<ssize_t>(toCopy);
6✔
164
      };
6✔
165

166
      auto newStreamId = nghttp2_submit_request(d_session.get(), nullptr, headers.data(), headers.size(), &data_provider, this);
6✔
167
      if (newStreamId < 0) {
6!
168
        throw std::runtime_error("Error submitting HTTP request:" + std::string(nghttp2_strerror(newStreamId)));
×
169
      }
×
170

171
      result = nghttp2_session_send(d_session.get());
6✔
172
      if (result != 0) {
6!
173
        throw std::runtime_error("Error in nghttp2_session_send:" + std::to_string(result));
×
174
      }
×
175
    }
6✔
176
  }
7✔
177

178
  std::map<int32_t, PacketBuffer> d_responses;
179
  std::map<int32_t, uint16_t> d_responseCodes;
180
  std::unique_ptr<nghttp2_session, void (*)(nghttp2_session*)> d_session;
181
  PacketBuffer d_currentQuery;
182
  PacketBuffer d_clientOutBuffer;
183
  uint64_t d_connectionID{0};
184
  size_t d_position{0};
185

186
  void submitIncoming(const PacketBuffer& data, size_t pos, size_t toWrite) const
187
  {
10✔
188
    ssize_t readlen = nghttp2_session_mem_recv(d_session.get(), &data.at(pos), toWrite);
10✔
189
    if (readlen < 0) {
10!
190
      throw("Fatal error while submitting line " + std::to_string(__LINE__) + ": " + std::string(nghttp2_strerror(static_cast<int>(readlen))));
×
191
    }
×
192

193
    /* just in case, see if we have anything to send */
194
    int got = nghttp2_session_send(d_session.get());
10✔
195
    if (got != 0) {
10!
196
      throw("Fatal error while sending: " + std::string(nghttp2_strerror(got)));
×
197
    }
×
198
  }
10✔
199

200
private:
201
  static ssize_t send_callback(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data)
202
  {
30✔
203
    (void)session;
30✔
204
    (void)flags;
30✔
205
    auto* conn = static_cast<DOHConnection*>(user_data);
30✔
206
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): nghttp2 API
207
    conn->d_clientOutBuffer.insert(conn->d_clientOutBuffer.end(), data, data + length);
30✔
208
    return static_cast<ssize_t>(length);
30✔
209
  }
30✔
210

211
  static int on_frame_recv_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data)
212
  {
15✔
213
    (void)session;
15✔
214
    auto* conn = static_cast<DOHConnection*>(user_data);
15✔
215
    if ((frame->hd.type == NGHTTP2_HEADERS || frame->hd.type == NGHTTP2_DATA) && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) != 0) {
15✔
216
      const auto& response = conn->d_responses.at(frame->hd.stream_id);
3✔
217
      if (conn->d_responseCodes.at(frame->hd.stream_id) != 200U) {
3✔
218
        return 0;
2✔
219
      }
2✔
220

221
      BOOST_REQUIRE_GT(response.size(), sizeof(dnsheader));
1✔
222
      const dnsheader_aligned dnsHeader(response.data());
1✔
223
      uint16_t queryID = ntohs(dnsHeader.get()->id);
1✔
224

225
      const auto& expected = s_connectionContexts.at(conn->d_connectionID).d_responses.at(queryID);
1✔
226
      BOOST_REQUIRE_EQUAL(expected.size(), response.size());
1✔
227
      for (size_t idx = 0; idx < response.size(); idx++) {
47✔
228
        if (expected.at(idx) != response.at(idx)) {
46!
229
          cerr << "Mismatch at offset " << idx << ", expected " << std::to_string(response.at(idx)) << " got " << std::to_string(expected.at(idx)) << endl;
×
230
          BOOST_CHECK(false);
×
231
        }
×
232
      }
46✔
233
    }
1✔
234

235
    return 0;
13✔
236
  }
15✔
237

238
  static int on_data_chunk_recv_callback(nghttp2_session* session, uint8_t flags, int32_t stream_id, const uint8_t* data, size_t len, void* user_data)
239
  {
3✔
240
    (void)session;
3✔
241
    (void)flags;
3✔
242
    auto* conn = static_cast<DOHConnection*>(user_data);
3✔
243
    auto& response = conn->d_responses[stream_id];
3✔
244
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): nghttp2 API
245
    response.insert(response.end(), data, data + len);
3✔
246
    return 0;
3✔
247
  }
3✔
248

249
  static int 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)
250
  {
9✔
251
    (void)session;
9✔
252
    (void)flags;
9✔
253
    auto* conn = static_cast<DOHConnection*>(user_data);
9✔
254
    const std::string status(":status");
9✔
255
    if (frame->hd.type == NGHTTP2_HEADERS && frame->headers.cat == NGHTTP2_HCAT_RESPONSE) {
9!
256
      if (namelen == status.size() && memcmp(status.data(), name, status.size()) == 0) {
9!
257
        try {
3✔
258
          uint16_t responseCode{0};
3✔
259
          auto expected = s_connectionContexts.at(conn->d_connectionID).d_responseCodes.at((frame->hd.stream_id - 1) / 2);
3✔
260
          // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): nghttp2 API
261
          pdns::checked_stoi_into(responseCode, std::string(reinterpret_cast<const char*>(value), valuelen));
3✔
262
          conn->d_responseCodes[frame->hd.stream_id] = responseCode;
3✔
263
          if (responseCode != expected) {
3!
264
            cerr << "Mismatch response code, expected " << std::to_string(expected) << " got " << std::to_string(responseCode) << endl;
×
265
            BOOST_CHECK(false);
×
266
          }
×
267
        }
3✔
268
        catch (const std::exception& e) {
3✔
269
          infolog("Error parsing the status header for stream ID %d: %s", frame->hd.stream_id, e.what());
×
270
          return NGHTTP2_ERR_CALLBACK_FAILURE;
×
271
        }
×
272
      }
3✔
273
    }
9✔
274
    return 0;
9✔
275
  }
9✔
276

277
  static int on_stream_close_callback(nghttp2_session* session, int32_t stream_id, uint32_t error_code, void* user_data)
278
  {
3✔
279
    (void)session;
3✔
280
    (void)stream_id;
3✔
281
    (void)error_code;
3✔
282
    (void)user_data;
3✔
283
    return 0;
3✔
284
  }
3✔
285
};
286

287
class MockupTLSConnection : public TLSConnection
288
{
289
public:
290
  MockupTLSConnection(int descriptor, [[maybe_unused]] bool client = false, [[maybe_unused]] bool needProxyProtocol = false) :
291
    d_descriptor(descriptor)
292
  {
7✔
293
    auto connectionID = s_connectionID++;
7✔
294
    auto conn = std::make_unique<DOHConnection>(connectionID);
7✔
295
    s_connectionBuffers[d_descriptor] = std::move(conn);
7✔
296
  }
7✔
297
  MockupTLSConnection(const MockupTLSConnection&) = delete;
298
  MockupTLSConnection(MockupTLSConnection&&) = delete;
299
  MockupTLSConnection& operator=(const MockupTLSConnection&) = delete;
300
  MockupTLSConnection& operator=(MockupTLSConnection&&) = delete;
301
  ~MockupTLSConnection() override = default;
302

303
  IOState tryHandshake() override
304
  {
7✔
305
    auto step = getStep();
7✔
306
    BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::handshakeClient);
7✔
307

308
    return step.nextState;
7✔
309
  }
7✔
310

311
  IOState tryWrite(const PacketBuffer& buffer, size_t& pos, size_t toWrite) override
312
  {
13✔
313
    auto& conn = s_connectionBuffers.at(d_descriptor);
13✔
314
    auto step = getStep();
13✔
315
    BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::writeToClient);
13✔
316

317
    if (step.bytes == 0) {
13✔
318
      if (step.nextState == IOState::NeedWrite) {
3!
319
        return step.nextState;
×
320
      }
×
321
      throw std::runtime_error("Remote host closed the connection");
3✔
322
    }
3✔
323

324
    toWrite -= pos;
10✔
325
    BOOST_REQUIRE_GE(buffer.size(), pos + toWrite);
10✔
326

327
    if (step.bytes < toWrite) {
10✔
328
      toWrite = step.bytes;
1✔
329
    }
1✔
330

331
    conn->submitIncoming(buffer, pos, toWrite);
10✔
332
    pos += toWrite;
10✔
333

334
    return step.nextState;
10✔
335
  }
13✔
336

337
  IOState tryRead(PacketBuffer& buffer, size_t& pos, size_t toRead, bool allowIncomplete = false) override
338
  {
17✔
339
    auto& conn = s_connectionBuffers.at(d_descriptor);
17✔
340
    auto step = getStep();
17✔
341
    BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::readFromClient);
17✔
342

343
    if (step.bytes == 0) {
17✔
344
      if (step.nextState == IOState::NeedRead) {
6✔
345
        return step.nextState;
2✔
346
      }
2✔
347
      throw std::runtime_error("Remote host closed the connection");
4✔
348
    }
6✔
349

350
    auto& externalBuffer = conn->d_clientOutBuffer;
11✔
351
    toRead -= pos;
11✔
352

353
    if (step.bytes < toRead) {
11!
354
      toRead = step.bytes;
11✔
355
    }
11✔
356
    if (allowIncomplete) {
11!
357
      if (toRead > externalBuffer.size()) {
11!
358
        toRead = externalBuffer.size();
×
359
      }
×
360
    }
11✔
361
    else {
×
362
      BOOST_REQUIRE_GE(externalBuffer.size(), toRead);
×
363
    }
×
364

365
    BOOST_REQUIRE_GE(buffer.size(), toRead);
11✔
366

367
    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
368
    std::copy(externalBuffer.begin(), externalBuffer.begin() + toRead, buffer.begin() + pos);
11✔
369
    pos += toRead;
11✔
370
    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
371
    externalBuffer.erase(externalBuffer.begin(), externalBuffer.begin() + toRead);
11✔
372

373
    return step.nextState;
11✔
374
  }
17✔
375

376
  IOState tryConnect(bool fastOpen, const ComboAddress& remote) override
377
  {
×
NEW
378
    (void)fastOpen;
×
NEW
379
    (void)remote;
×
380
    throw std::runtime_error("Should not happen");
×
381
  }
×
382

383
  void close() override
384
  {
7✔
385
    auto step = getStep();
7✔
386
    BOOST_REQUIRE_EQUAL(step.request, ExpectedStep::ExpectedRequest::closeClient);
7✔
387
  }
7✔
388

389
  [[nodiscard]] bool isUsable() const override
390
  {
×
391
    return true;
×
392
  }
×
393

394
  [[nodiscard]] std::string getServerNameIndication() const override
395
  {
5✔
396
    return "";
5✔
397
  }
5✔
398

399
  [[nodiscard]] std::vector<uint8_t> getNextProtocol() const override
400
  {
7✔
401
    return std::vector<uint8_t>{'h', '2'};
7✔
402
  }
7✔
403

404
  [[nodiscard]] LibsslTLSVersion getTLSVersion() const override
405
  {
5✔
406
    return LibsslTLSVersion::TLS13;
5✔
407
  }
5✔
408

409
  [[nodiscard]] bool hasSessionBeenResumed() const override
410
  {
7✔
411
    return false;
7✔
412
  }
7✔
413

414
  [[nodiscard]] std::vector<std::unique_ptr<TLSSession>> getSessions() override
415
  {
×
416
    return {};
×
417
  }
×
418

419
  void setSession(std::unique_ptr<TLSSession>& session) override
420
  {
×
NEW
421
    (void)session;
×
UNCOV
422
  }
×
423

424
  [[nodiscard]] std::vector<int> getAsyncFDs() override
425
  {
×
426
    return {};
×
427
  }
×
428

429
  /* unused in that context, don't bother */
430
  void doHandshake() override
431
  {
×
432
  }
×
433

434
  void connect(bool fastOpen, const ComboAddress& remote, const struct timeval& timeout) override
435
  {
×
NEW
436
    (void)fastOpen;
×
NEW
437
    (void)remote;
×
NEW
438
    (void)timeout;
×
UNCOV
439
  }
×
440

441
  size_t read(void* buffer, size_t bufferSize, const struct timeval& readTimeout, const struct timeval& totalTimeout = {0, 0}, bool allowIncomplete = false) override
442
  {
×
NEW
443
    (void)buffer;
×
NEW
444
    (void)bufferSize;
×
NEW
445
    (void)readTimeout;
×
NEW
446
    (void)totalTimeout;
×
NEW
447
    (void)allowIncomplete;
×
448
    return 0;
×
449
  }
×
450

451
  size_t write(const void* buffer, size_t bufferSize, const struct timeval& writeTimeout) override
452
  {
×
NEW
453
    (void)buffer;
×
NEW
454
    (void)bufferSize;
×
NEW
455
    (void)writeTimeout;
×
456
    return 0;
×
457
  }
×
458

459
private:
460
  [[nodiscard]] ExpectedStep getStep() const
461
  {
44✔
462
    BOOST_REQUIRE(!s_steps.empty());
44✔
463
    auto step = s_steps.front();
44✔
464
    s_steps.pop_front();
44✔
465

466
    if (step.cb) {
44✔
467
      step.cb(d_descriptor);
1✔
468
    }
1✔
469

470
    return step;
44✔
471
  }
44✔
472

473
  const int d_descriptor;
474
};
475

476
#include "test-dnsdistnghttp2_common.hh"
477

478
struct TestFixture
479
{
480
  TestFixture()
481
  {
3✔
482
    reset();
3✔
483
  }
3✔
484
  TestFixture(const TestFixture&) = delete;
485
  TestFixture(TestFixture&&) = delete;
486
  TestFixture& operator=(const TestFixture&) = delete;
487
  TestFixture& operator=(TestFixture&&) = delete;
488
  ~TestFixture()
489
  {
3✔
490
    reset();
3✔
491
  }
3✔
492

493
private:
494
  void reset()
495
  {
6✔
496
    s_steps.clear();
6✔
497
    s_connectionContexts.clear();
6✔
498
    s_connectionBuffers.clear();
6✔
499
    s_connectionID = 0;
6✔
500
    /* we _NEED_ to set this function to empty otherwise we might get what was set
501
       by the last test, and we might not like it at all */
502
    s_processQuery = nullptr;
6✔
503
  }
6✔
504
};
505

506
BOOST_FIXTURE_TEST_CASE(test_IncomingConnection_SelfAnswered, TestFixture)
507
{
1✔
508
  auto local = getBackendAddress("1", 80);
1✔
509
  ClientState localCS(local, true, false, 0, "", {}, true);
1✔
510
  localCS.dohFrontend = std::make_shared<DOHFrontend>(std::make_shared<MockupTLSCtx>());
1✔
511
  localCS.dohFrontend->d_urls.insert("/dns-query");
1✔
512

513
  TCPClientThreadData threadData;
1✔
514
  threadData.mplexer = std::make_unique<MockupFDMultiplexer>();
1✔
515

516
  struct timeval now
1✔
517
  {
1✔
518
  };
1✔
519
  gettimeofday(&now, nullptr);
1✔
520

521
  size_t counter = 0;
1✔
522
  DNSName name("powerdns.com.");
1✔
523
  PacketBuffer query;
1✔
524
  GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
1✔
525
  pwQ.getHeader()->rd = 1;
1✔
526
  pwQ.getHeader()->id = htons(counter);
1✔
527

528
  PacketBuffer response;
1✔
529
  GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
1✔
530
  pwR.getHeader()->qr = 1;
1✔
531
  pwR.getHeader()->rd = 1;
1✔
532
  pwR.getHeader()->ra = 1;
1✔
533
  pwR.getHeader()->id = htons(counter);
1✔
534
  pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
1✔
535
  pwR.xfr32BitInt(0x01020304);
1✔
536
  pwR.commit();
1✔
537

538
  {
1✔
539
    /* dnsdist drops the query right away after receiving it, client closes the connection */
540
    s_connectionContexts[counter++] = ExpectedData{{}, {query}, {response}, {403U}};
1✔
541
    s_steps = {
1✔
542
      /* opening */
543
      {ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done},
1✔
544
      /* settings server -> client */
545
      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 15},
1✔
546
      /* settings + headers + data client -> server.. */
547
      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 128},
1✔
548
      /* .. continued */
549
      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 60},
1✔
550
      /* headers + data */
551
      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, std::numeric_limits<size_t>::max()},
1✔
552
      /* wait for next query, but the client closes the connection */
553
      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 0},
1✔
554
      /* server close */
555
      {ExpectedStep::ExpectedRequest::closeClient, IOState::Done},
1✔
556
    };
1✔
557

558
    auto state = std::make_shared<IncomingHTTP2Connection>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
1✔
559
    state->handleIO();
1✔
560
  }
1✔
561

562
  {
1✔
563
    /* client closes the connection right in the middle of sending the query */
564
    s_connectionContexts[counter++] = ExpectedData{{}, {query}, {response}, {403U}};
1✔
565
    s_steps = {
1✔
566
      /* opening */
567
      {ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done},
1✔
568
      /* settings server -> client */
569
      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 15},
1✔
570
      /* client sends one byte */
571
      {ExpectedStep::ExpectedRequest::readFromClient, IOState::NeedRead, 1},
1✔
572
      /* then closes the connection */
573
      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 0},
1✔
574
      /* server close */
575
      {ExpectedStep::ExpectedRequest::closeClient, IOState::Done},
1✔
576
    };
1✔
577

578
    /* mark the incoming FD as always ready */
579
    dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setReady(-1);
1✔
580

581
    auto state = std::make_shared<IncomingHTTP2Connection>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
1✔
582
    state->handleIO();
1✔
583
    while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
2!
584
      threadData.mplexer->run(&now);
1✔
585
    }
1✔
586
  }
1✔
587

588
  {
1✔
589
    /* dnsdist sends a response right away, client closes the connection after getting the response */
590
    s_processQuery = [response](DNSQuestion& dnsQuestion, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
1✔
591
      (void)selectedBackend;
1✔
592
      /* self answered */
593
      dnsQuestion.getMutableData() = response;
1✔
594
      return ProcessQueryResult::SendAnswer;
1✔
595
    };
1✔
596

597
    s_connectionContexts[counter++] = ExpectedData{{}, {query}, {response}, {200U}};
1✔
598

599
    s_steps = {
1✔
600
      /* opening */
601
      {ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done},
1✔
602
      /* settings server -> client */
603
      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 15},
1✔
604
      /* settings + headers + data client -> server.. */
605
      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 128},
1✔
606
      /* .. continued */
607
      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 60},
1✔
608
      /* headers + data */
609
      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, std::numeric_limits<size_t>::max()},
1✔
610
      /* wait for next query, but the client closes the connection */
611
      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 0},
1✔
612
      /* server close */
613
      {ExpectedStep::ExpectedRequest::closeClient, IOState::Done},
1✔
614
    };
1✔
615

616
    auto state = std::make_shared<IncomingHTTP2Connection>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
1✔
617
    state->handleIO();
1✔
618
  }
1✔
619

620
  {
1✔
621
    /* dnsdist sends a response right away, but the client closes the connection without even reading the response */
622
    s_processQuery = [response](DNSQuestion& dnsQuestion, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
1✔
623
      (void)selectedBackend;
1✔
624
      /* self answered */
625
      dnsQuestion.getMutableData() = response;
1✔
626
      return ProcessQueryResult::SendAnswer;
1✔
627
    };
1✔
628

629
    s_connectionContexts[counter++] = ExpectedData{{}, {query}, {response}, {200U}};
1✔
630

631
    s_steps = {
1✔
632
      /* opening */
633
      {ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done},
1✔
634
      /* settings server -> client */
635
      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 15},
1✔
636
      /* settings + headers + data client -> server.. */
637
      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 128},
1✔
638
      /* .. continued */
639
      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 60},
1✔
640
      /* we want to send the response but the client closes the connection */
641
      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 0},
1✔
642
      /* server close */
643
      {ExpectedStep::ExpectedRequest::closeClient, IOState::Done},
1✔
644
    };
1✔
645

646
    /* mark the incoming FD as always ready */
647
    dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setReady(-1);
1✔
648

649
    auto state = std::make_shared<IncomingHTTP2Connection>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
1✔
650
    state->handleIO();
1✔
651
    while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
1!
652
      threadData.mplexer->run(&now);
×
653
    }
×
654
  }
1✔
655

656
  {
1✔
657
    /* dnsdist sends a response right away, client closes the connection while getting the response */
658
    s_processQuery = [response](DNSQuestion& dnsQuestion, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
1✔
659
      (void)selectedBackend;
1✔
660
      /* self answered */
661
      dnsQuestion.getMutableData() = response;
1✔
662
      return ProcessQueryResult::SendAnswer;
1✔
663
    };
1✔
664

665
    s_connectionContexts[counter++] = ExpectedData{{}, {query}, {response}, {200U}};
1✔
666

667
    s_steps = {
1✔
668
      /* opening */
669
      {ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done},
1✔
670
      /* settings server -> client */
671
      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 15},
1✔
672
      /* settings + headers + data client -> server.. */
673
      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 128},
1✔
674
      /* .. continued */
675
      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 60},
1✔
676
      /* headers + data (partial write) */
677
      {ExpectedStep::ExpectedRequest::writeToClient, IOState::NeedWrite, 1},
1✔
678
      /* nothing to read after that */
679
      {ExpectedStep::ExpectedRequest::readFromClient, IOState::NeedRead, 0},
1✔
680
      /* then the client closes the connection before we are done  */
681
      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 0},
1✔
682
      /* server close */
683
      {ExpectedStep::ExpectedRequest::closeClient, IOState::Done},
1✔
684
    };
1✔
685

686
    /* mark the incoming FD as always ready */
687
    dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setReady(-1);
1✔
688

689
    auto state = std::make_shared<IncomingHTTP2Connection>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
1✔
690
    state->handleIO();
1✔
691
    while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
2!
692
      threadData.mplexer->run(&now);
1✔
693
    }
1✔
694
  }
1✔
695
}
1✔
696

697
BOOST_FIXTURE_TEST_CASE(test_IncomingConnection_BackendTimeout, TestFixture)
698
{
1✔
699
  auto local = getBackendAddress("1", 80);
1✔
700
  ClientState localCS(local, true, false, 0, "", {}, true);
1✔
701
  localCS.dohFrontend = std::make_shared<DOHFrontend>(std::make_shared<MockupTLSCtx>());
1✔
702
  localCS.dohFrontend->d_urls.insert("/dns-query");
1✔
703

704
  TCPClientThreadData threadData;
1✔
705
  threadData.mplexer = std::make_unique<MockupFDMultiplexer>();
1✔
706

707
  auto backend = std::make_shared<DownstreamState>(getBackendAddress("42", 53));
1✔
708

709
  timeval now{};
1✔
710
  gettimeofday(&now, nullptr);
1✔
711

712
  size_t counter = 0;
1✔
713
  DNSName name("powerdns.com.");
1✔
714
  PacketBuffer query;
1✔
715
  GenericDNSPacketWriter<PacketBuffer> pwQ(query, name, QType::A, QClass::IN, 0);
1✔
716
  pwQ.getHeader()->rd = 1;
1✔
717
  pwQ.getHeader()->id = htons(counter);
1✔
718

719
  PacketBuffer response;
1✔
720
  GenericDNSPacketWriter<PacketBuffer> pwR(response, name, QType::A, QClass::IN, 0);
1✔
721
  pwR.getHeader()->qr = 1;
1✔
722
  pwR.getHeader()->rd = 1;
1✔
723
  pwR.getHeader()->ra = 1;
1✔
724
  pwR.getHeader()->id = htons(counter);
1✔
725
  pwR.startRecord(name, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
1✔
726
  pwR.xfr32BitInt(0x01020304);
1✔
727
  pwR.commit();
1✔
728

729
  {
1✔
730
    /* dnsdist forwards the query to the backend, which does not answer -> timeout */
731
    s_processQuery = [backend](DNSQuestion& dnsQuestion, std::shared_ptr<DownstreamState>& selectedBackend) -> ProcessQueryResult {
1✔
732
      (void)dnsQuestion;
1✔
733
      selectedBackend = backend;
1✔
734
      return ProcessQueryResult::PassToBackend;
1✔
735
    };
1✔
736
    s_connectionContexts[counter++] = ExpectedData{{}, {query}, {response}, {502U}};
1✔
737
    s_steps = {
1✔
738
      /* opening */
739
      {ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done},
1✔
740
      /* settings server -> client */
741
      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 15},
1✔
742
      /* settings + headers + data client -> server.. */
743
      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 128},
1✔
744
      /* .. continued */
745
      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 60},
1✔
746
      /* trying to read a new request while processing the first one */
747
      {ExpectedStep::ExpectedRequest::readFromClient, IOState::NeedRead},
1✔
748
      /* headers + data */
749
      {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, std::numeric_limits<size_t>::max(), [&threadData](int desc) {
1✔
750
         /* set the incoming descriptor as ready */
751
         dynamic_cast<MockupFDMultiplexer*>(threadData.mplexer.get())->setReady(desc);
1✔
752
       }},
1✔
753
      /* wait for next query, but the client closes the connection */
754
      {ExpectedStep::ExpectedRequest::readFromClient, IOState::Done, 0},
1✔
755
      /* server close */
756
      {ExpectedStep::ExpectedRequest::closeClient, IOState::Done},
1✔
757
    };
1✔
758

759
    auto state = std::make_shared<IncomingHTTP2Connection>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
1✔
760
    state->handleIO();
1✔
761
    TCPResponse resp;
1✔
762
    resp.d_idstate.d_streamID = 1;
1✔
763
    state->notifyIOError(now, std::move(resp));
1✔
764
    while (threadData.mplexer->getWatchedFDCount(false) != 0 || threadData.mplexer->getWatchedFDCount(true) != 0) {
2!
765
      threadData.mplexer->run(&now);
1✔
766
    }
1✔
767
  }
1✔
768
}
1✔
769

770
BOOST_FIXTURE_TEST_CASE(test_IncomingConnection_ClientTimeout_BackendTimeout, TestFixture)
771
{
1✔
772
  auto local = getBackendAddress("1", 80);
1✔
773
  ClientState localCS(local, true, false, 0, "", {}, true);
1✔
774
  localCS.dohFrontend = std::make_shared<DOHFrontend>(std::make_shared<MockupTLSCtx>());
1✔
775
  localCS.dohFrontend->d_urls.insert("/dns-query");
1✔
776

777
  TCPClientThreadData threadData;
1✔
778
  threadData.mplexer = std::make_unique<MockupFDMultiplexer>();
1✔
779

780
  auto backend = std::make_shared<DownstreamState>(getBackendAddress("42", 53));
1✔
781

782
  timeval now{};
1✔
783
  gettimeofday(&now, nullptr);
1✔
784

785
  size_t counter = 0;
1✔
786
  s_connectionContexts[counter++] = ExpectedData{{}, {}, {}, {}};
1✔
787
  s_steps = {
1✔
788
    {ExpectedStep::ExpectedRequest::handshakeClient, IOState::Done},
1✔
789
    /* write to client, but the client closes the connection */
790
    {ExpectedStep::ExpectedRequest::writeToClient, IOState::Done, 0},
1✔
791
    /* server close */
792
    {ExpectedStep::ExpectedRequest::closeClient, IOState::Done},
1✔
793
  };
1✔
794

795
  auto state = std::make_shared<IncomingHTTP2Connection>(ConnectionInfo(&localCS, getBackendAddress("84", 4242)), threadData, now);
1✔
796
  auto base = std::static_pointer_cast<IncomingTCPConnectionState>(state);
1✔
797
  IncomingHTTP2Connection::handleTimeout(base, true);
1✔
798
  state->handleIO();
1✔
799
}
1✔
800

801
BOOST_AUTO_TEST_SUITE_END();
802
#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