]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/dnsdistdist/doh.cc
dnsdist: Don't accept sub-paths of configured DoH URLs
[thirdparty/pdns.git] / pdns / dnsdistdist / doh.cc
CommitLineData
0956c5c5
RG
1#include "config.h"
2#include "doh.hh"
3
4#ifdef HAVE_DNS_OVER_HTTPS
fbf14b03 5#define H2O_USE_EPOLL 1
ede152ec 6
fbf14b03
RG
7#include <errno.h>
8#include <iostream>
ede152ec
RG
9#include <thread>
10
11#include <boost/algorithm/string.hpp>
12#include <h2o.h>
13//#include <h2o/http1.h>
14#include <h2o/http2.h>
15
bf8cd40d
RG
16#include <openssl/err.h>
17#include <openssl/ssl.h>
18
fbf14b03
RG
19#include "base64.hh"
20#include "dnsname.hh"
21#undef CERT
22#include "dnsdist.hh"
23#include "misc.hh"
fbf14b03
RG
24#include "dns.hh"
25#include "dolog.hh"
26#include "dnsdist-ecs.hh"
27#include "dnsdist-rules.hh"
28#include "dnsdist-xpf.hh"
ede152ec 29#include "libssl.hh"
70915d97 30#include "threadname.hh"
fbf14b03
RG
31
32using namespace std;
33
34/* So, how does this work. We use h2o for our http2 and TLS needs.
35 If the operator has configured multiple IP addresses to listen on,
36 we launch multiple h2o listener threads. We can hook in to multiple
37 URLs though on the same IP. There is no SNI yet (I think).
38
39 h2o is event driven, so we get callbacks if a new DNS query arrived.
40 When it does, we do some minimal parsing on it, and send it on to the
41 dnsdist worker thread which we also launched.
42
43 This dnsdist worker thread injects the query into the normal dnsdist flow
44 (as a datagram over a socketpair). The response also goes back over a
45 (different) socketpair, where we pick it up and deliver it back to h2o.
46
47 For coordination, we use the h2o socket multiplexer, which is sensitive to our
48 socketpair too.
49*/
50
51/* h2o notes.
52 Paths and parameters etc just *happen* to be null-terminated in HTTP2.
53 They are not in HTTP1. So you MUST use the length field!
54*/
55
bde7143b
RG
56/* 'Intermediate' compatibility from https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29 */
57#define DOH_DEFAULT_CIPHERS "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"
58
ede152ec
RG
59class DOHAcceptContext
60{
61public:
62 DOHAcceptContext()
63 {
64 memset(&d_h2o_accept_ctx, 0, sizeof(d_h2o_accept_ctx));
9f32da84 65 d_rotatingTicketsKey.clear();
ede152ec
RG
66 }
67 DOHAcceptContext(const DOHAcceptContext&) = delete;
68 DOHAcceptContext& operator=(const DOHAcceptContext&) = delete;
69
70 h2o_accept_ctx_t* get()
71 {
72 ++d_refcnt;
73 return &d_h2o_accept_ctx;
74 }
75
76 void release()
77 {
241ec94c 78 if (--d_refcnt == 0) {
ede152ec
RG
79 SSL_CTX_free(d_h2o_accept_ctx.ssl_ctx);
80 d_h2o_accept_ctx.ssl_ctx = nullptr;
81 delete this;
82 }
83 }
84
f2affb3b
RG
85 void decrementConcurrentConnections()
86 {
87 if (d_cs != nullptr) {
88 --d_cs->tcpCurrentConnections;
89 }
90 }
91
9f32da84
RG
92 time_t getNextTicketsKeyRotation() const
93 {
94 return d_ticketsKeyNextRotation;
95 }
96
97 size_t getTicketsKeysCount() const
98 {
99 size_t res = 0;
100 if (d_ticketKeys) {
101 res = d_ticketKeys->getKeysCount();
102 }
103 return res;
104 }
105
106 void rotateTicketsKey(time_t now)
107 {
108 if (!d_ticketKeys) {
109 return;
110 }
111
112 d_ticketKeys->rotateTicketsKey(now);
113
114 if (d_ticketsKeyRotationDelay > 0) {
115 d_ticketsKeyNextRotation = now + d_ticketsKeyRotationDelay;
116 }
117 }
118
119 void loadTicketsKeys(const std::string& keyFile)
120 {
121 if (!d_ticketKeys) {
122 return;
123 }
124 d_ticketKeys->loadTicketsKeys(keyFile);
125
126 if (d_ticketsKeyRotationDelay > 0) {
127 d_ticketsKeyNextRotation = time(nullptr) + d_ticketsKeyRotationDelay;
128 }
129 }
130
131 void handleTicketsKeyRotation()
132 {
133 if (d_ticketsKeyRotationDelay == 0) {
134 return;
135 }
136
137 time_t now = time(nullptr);
138 if (now > d_ticketsKeyNextRotation) {
139 if (d_rotatingTicketsKey.test_and_set()) {
140 /* someone is already rotating */
141 return;
142 }
143 try {
144 rotateTicketsKey(now);
145
146 d_rotatingTicketsKey.clear();
147 }
148 catch(const std::runtime_error& e) {
149 d_rotatingTicketsKey.clear();
150 throw std::runtime_error(std::string("Error generating a new tickets key for TLS context:") + e.what());
151 }
152 catch(...) {
153 d_rotatingTicketsKey.clear();
154 throw;
155 }
156 }
157 }
158
be3183ed 159 std::map<int, std::string> d_ocspResponses;
9f32da84 160 std::unique_ptr<OpenSSLTLSTicketKeysRing> d_ticketKeys{nullptr};
7f8a5a32 161 std::unique_ptr<FILE, int(*)(FILE*)> d_keyLogFile{nullptr, fclose};
f2affb3b 162 ClientState* d_cs{nullptr};
9f32da84 163 time_t d_ticketsKeyRotationDelay{0};
be3183ed 164
ede152ec
RG
165private:
166 h2o_accept_ctx_t d_h2o_accept_ctx;
167 std::atomic<uint64_t> d_refcnt{1};
9f32da84
RG
168 time_t d_ticketsKeyNextRotation{0};
169 std::atomic_flag d_rotatingTicketsKey;
ede152ec
RG
170};
171
fbf14b03
RG
172// we create one of these per thread, and pass around a pointer to it
173// through the bowels of h2o
174struct DOHServerConfig
175{
ede152ec 176 DOHServerConfig(uint32_t idleTimeout): accept_ctx(new DOHAcceptContext)
fbf14b03 177 {
fbf14b03
RG
178 if(socketpair(AF_LOCAL, SOCK_DGRAM, 0, dohquerypair) < 0) {
179 unixDie("Creating a socket pair for DNS over HTTPS");
180 }
181
182 if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, dohresponsepair) < 0) {
183 close(dohquerypair[0]);
184 close(dohquerypair[1]);
185 unixDie("Creating a socket pair for DNS over HTTPS");
186 }
187
188 h2o_config_init(&h2o_config);
6e3a9fe4 189 h2o_config.http2.idle_timeout = idleTimeout * 1000;
fbf14b03 190 }
ede152ec
RG
191 DOHServerConfig(const DOHServerConfig&) = delete;
192 DOHServerConfig& operator=(const DOHServerConfig&) = delete;
193
194 ~DOHServerConfig()
195 {
196 if (accept_ctx) {
197 accept_ctx->release();
198 }
199 }
fbf14b03 200
ede152ec 201 LocalHolders holders;
767fbba3 202 std::unordered_set<std::string> paths;
fbf14b03
RG
203 h2o_globalconf_t h2o_config;
204 h2o_context_t h2o_ctx;
ede152ec 205 DOHAcceptContext* accept_ctx{nullptr};
fbf14b03
RG
206 ClientState* cs{nullptr};
207 std::shared_ptr<DOHFrontend> df{nullptr};
208 int dohquerypair[2]{-1,-1};
209 int dohresponsepair[2]{-1,-1};
210};
211
0956c5c5
RG
212void handleDOHTimeout(DOHUnit* oldDU)
213{
214 if (oldDU == nullptr) {
215 return;
216 }
217
218/* we are about to erase an existing DU */
0956c5c5
RG
219 oldDU->status_code = 502;
220
041ab34a
RG
221 /* increase the ref counter before sending the pointer */
222 oldDU->get();
0956c5c5 223 if (send(oldDU->rsock, &oldDU, sizeof(oldDU), 0) != sizeof(oldDU)) {
041ab34a 224 oldDU->release();
0956c5c5 225 }
041ab34a
RG
226 oldDU->release();
227 oldDU = nullptr;
0956c5c5
RG
228}
229
ede152ec
RG
230static void on_socketclose(void *data)
231{
232 DOHAcceptContext* ctx = reinterpret_cast<DOHAcceptContext*>(data);
f2affb3b 233 ctx->decrementConcurrentConnections();
ede152ec
RG
234 ctx->release();
235}
236
28b56482
RG
237static const std::string& getReasonFromStatusCode(uint16_t statusCode)
238{
239 /* no need to care too much about this, HTTP/2 has no 'reason' anyway */
240 static const std::unordered_map<uint16_t, std::string> reasons = {
241 { 200, "OK" },
242 { 301, "Moved Permanently" },
243 { 302, "Found" },
244 { 303, "See Other" },
245 { 304, "Not Modified" },
246 { 305, "Use Proxy" },
247 { 306, "Switch Proxy" },
248 { 307, "Temporary Redirect" },
249 { 308, "Permanent Redirect" },
250 { 400, "Bad Request" },
251 { 401, "Unauthorized" },
252 { 402, "Payment Required" },
253 { 403, "Forbidden" },
254 { 404, "Not Found" },
255 { 405, "Method Not Allowed" },
256 { 406, "Not Acceptable" },
257 { 407, "Proxy Authentication Required" },
258 { 408, "Request Timeout" },
259 { 409, "Conflict" },
260 { 410, "Gone" },
261 { 411, "Length Required" },
262 { 412, "Precondition Failed" },
263 { 413, "Payload Too Large" },
264 { 414, "URI Too Long" },
265 { 415, "Unsupported Media Type" },
266 { 416, "Range Not Satisfiable" },
267 { 417, "Expectation Failed" },
268 { 418, "I'm a teapot" },
269 { 451, "Unavailable For Legal Reasons" },
270 { 500, "Internal Server Error" },
271 { 501, "Not Implemented" },
272 { 502, "Bad Gateway" },
273 { 503, "Service Unavailable" },
274 { 504, "Gateway Timeout" },
275 { 505, "HTTP Version Not Supported" }
276 };
277 static const std::string unknown = "Unknown";
278
279 const auto it = reasons.find(statusCode);
280 if (it == reasons.end()) {
281 return unknown;
282 }
283 else {
284 return it->second;
285 }
286}
287
ded6907c 288static void handleResponse(DOHFrontend& df, st_h2o_req_t* req, uint16_t statusCode, const std::string& response, const std::vector<std::pair<std::string, std::string>>& customResponseHeaders, const std::string& contentType, bool addContentType)
28b56482 289{
ded6907c
RG
290 constexpr int overwrite_if_exists = 1;
291 constexpr int maybe_token = 1;
292 for (auto const& headerPair : customResponseHeaders) {
293 h2o_set_header_by_str(&req->pool, &req->res.headers, headerPair.first.c_str(), headerPair.first.size(), maybe_token, headerPair.second.c_str(), headerPair.second.size(), overwrite_if_exists);
294 }
295
28b56482
RG
296 if (statusCode == 200) {
297 ++df.d_validresponses;
298 req->res.status = 200;
299
a5582d57
RG
300 if (addContentType) {
301 if (contentType.empty()) {
302 h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, nullptr, H2O_STRLIT("application/dns-message"));
303 }
304 else {
305 /* we need to duplicate the header content because h2o keeps a pointer and we will be deleted before the response has been sent */
306 h2o_iovec_t ct = h2o_strdup(&req->pool, contentType.c_str(), contentType.size());
307 h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, nullptr, ct.base, ct.len);
308 }
28b56482
RG
309 }
310
311 req->res.content_length = response.size();
312 h2o_send_inline(req, response.c_str(), response.size());
313 }
314 else if (statusCode >= 300 && statusCode < 400) {
315 /* in that case the response is actually a URL */
316 /* we need to duplicate the URL because h2o uses it for the location header, keeping a pointer, and we will be deleted before the response has been sent */
317 h2o_iovec_t url = h2o_strdup(&req->pool, response.c_str(), response.size());
318 h2o_send_redirect(req, statusCode, getReasonFromStatusCode(statusCode).c_str(), url.base, url.len);
319 ++df.d_redirectresponses;
320 }
321 else {
322 if (!response.empty()) {
ded6907c 323 h2o_send_error_generic(req, statusCode, getReasonFromStatusCode(statusCode).c_str(), response.c_str(), H2O_SEND_ERROR_KEEP_HEADERS);
28b56482
RG
324 }
325 else {
326 switch(statusCode) {
327 case 400:
328 h2o_send_error_400(req, getReasonFromStatusCode(statusCode).c_str(), "invalid DNS query" , 0);
329 break;
330 case 403:
331 h2o_send_error_403(req, getReasonFromStatusCode(statusCode).c_str(), "dns query not allowed", 0);
332 break;
333 case 502:
334 h2o_send_error_502(req, getReasonFromStatusCode(statusCode).c_str(), "no downstream server available", 0);
335 break;
336 case 500:
337 /* fall-through */
338 default:
339 h2o_send_error_500(req, getReasonFromStatusCode(statusCode).c_str(), "Internal Server Error", 0);
340 break;
341 }
342 }
343
344 ++df.d_errorresponses;
345 }
346}
347
348/*
fbf14b03
RG
349 this function calls 'return -1' to drop a query without sending it
350 caller should make sure HTTPS thread hears of that
351*/
fbf14b03
RG
352static int processDOHQuery(DOHUnit* du)
353{
fbf14b03 354 uint16_t queryId = 0;
ede152ec 355 ComboAddress remote;
b6d19fca 356 bool duRefCountIncremented = false;
fbf14b03
RG
357 try {
358 if(!du->req) {
359 // we got closed meanwhile. XXX small race condition here
360 return -1;
361 }
ede152ec 362 remote = du->remote;
6e3a9fe4 363 DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(du->req->conn->ctx->storage.entries[0].data);
ede152ec 364 auto& holders = dsc->holders;
fbf14b03
RG
365 ClientState& cs = *dsc->cs;
366
367 if (du->query.size() < sizeof(dnsheader)) {
368 ++g_stats.nonCompliantQueries;
b2664a49 369 du->status_code = 400;
fbf14b03
RG
370 return -1;
371 }
372
373 ++cs.queries;
374 ++g_stats.queries;
375
376 /* we need an accurate ("real") value for the response and
377 to store into the IDS, but not for insertion into the
378 rings for example */
379 struct timespec queryRealTime;
380 gettime(&queryRealTime, true);
381 uint16_t len = du->query.length();
8179b6d6
RG
382 /* We reserve at least 512 additional bytes to be able to add EDNS, but we also want
383 at least s_maxPacketCacheEntrySize bytes to be able to spoof the content or fill the answer from the packet cache */
57eb4882 384 du->query.resize(std::max(du->query.size() + 512, s_maxPacketCacheEntrySize));
fbf14b03
RG
385 size_t bufferSize = du->query.size();
386 auto query = const_cast<char*>(du->query.c_str());
387 struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(query);
388
389 if (!checkQueryHeaders(dh)) {
b2664a49 390 du->status_code = 400;
fbf14b03
RG
391 return -1; // drop
392 }
393
394 uint16_t qtype, qclass;
395 unsigned int consumed = 0;
396 DNSName qname(query, len, sizeof(dnsheader), false, &qtype, &qclass, &consumed);
397 DNSQuestion dq(&qname, qtype, qclass, consumed, &du->dest, &du->remote, dh, bufferSize, len, false, &queryRealTime);
398 dq.ednsAdded = du->ednsAdded;
399 dq.du = du;
400 queryId = ntohs(dh->id);
87c0eabd
RG
401#ifdef HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME
402 h2o_socket_t* sock = du->req->conn->callbacks->get_socket(du->req->conn);
403 const char * sni = h2o_socket_get_ssl_server_name(sock);
404 if (sni != nullptr) {
405 dq.sni = sni;
406 }
44947230 407#endif /* HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME */
fbf14b03
RG
408
409 std::shared_ptr<DownstreamState> ss{nullptr};
410 auto result = processQuery(dq, cs, holders, ss);
411
412 if (result == ProcessQueryResult::Drop) {
b2664a49 413 du->status_code = 403;
fbf14b03
RG
414 return -1;
415 }
416
417 if (result == ProcessQueryResult::SendAnswer) {
255abac6
RG
418 if (du->response.empty()) {
419 du->response = std::string(reinterpret_cast<char*>(dq.dh), dq.len);
420 }
041ab34a
RG
421 /* increase the ref counter before sending the pointer */
422 du->get();
423 if (send(du->rsock, &du, sizeof(du), 0) != sizeof(du)) {
424 du->release();
425 }
fbf14b03
RG
426 return 0;
427 }
428
b2664a49
RG
429 if (result != ProcessQueryResult::PassToBackend) {
430 du->status_code = 500;
431 return -1;
432 }
433
434 if (ss == nullptr) {
435 du->status_code = 502;
fbf14b03
RG
436 return -1;
437 }
438
0956c5c5 439 ComboAddress dest = du->dest;
fbf14b03
RG
440 unsigned int idOffset = (ss->idOffset++) % ss->idStates.size();
441 IDState* ids = &ss->idStates[idOffset];
442 ids->age = 0;
0956c5c5 443 DOHUnit* oldDU = nullptr;
311f19d5 444 if (ids->isInUse()) {
9bd1a882
RG
445 /* that means that the state was in use, possibly with an allocated
446 DOHUnit that we will need to handle, but we can't touch it before
447 confirming that we now own this state */
0956c5c5
RG
448 oldDU = ids->du;
449 }
fbf14b03 450
a9489723
RG
451 /* we atomically replace the value, we now own this state */
452 int64_t generation = ids->generation++;
311f19d5
RG
453 if (!ids->markAsUsed(generation)) {
454 /* the state was not in use.
9bd1a882 455 we reset 'oldDU' because it might have still been in use when we read it. */
07b66264 456 oldDU = nullptr;
fbf14b03
RG
457 ++ss->outstanding;
458 }
459 else {
311f19d5
RG
460 ids->du = nullptr;
461 /* we are reusing a state, no change in outstanding but if there was an existing DOHUnit we need
462 to handle it because it's about to be overwritten. */
fbf14b03
RG
463 ++ss->reuseds;
464 ++g_stats.downstreamTimeouts;
07b66264 465 handleDOHTimeout(oldDU);
fbf14b03
RG
466 }
467
a9489723 468 ids->origFD = 0;
b6d19fca
RG
469 /* increase the ref count since we are about to store the pointer */
470 du->get();
471 duRefCountIncremented = true;
0956c5c5
RG
472 ids->du = du;
473
fbf14b03
RG
474 ids->cs = &cs;
475 ids->origID = dh->id;
476 setIDStateFromDNSQuestion(*ids, dq, std::move(qname));
477
478 /* If we couldn't harvest the real dest addr, still
479 write down the listening addr since it will be useful
480 (especially if it's not an 'any' one).
481 We need to keep track of which one it is since we may
482 want to use the real but not the listening addr to reply.
483 */
0956c5c5
RG
484 if (dest.sin4.sin_family != 0) {
485 ids->origDest = dest;
fbf14b03
RG
486 ids->destHarvested = true;
487 }
488 else {
489 ids->origDest = cs.local;
490 ids->destHarvested = false;
491 }
492
493 dh->id = idOffset;
494
495 int fd = pickBackendSocketForSending(ss);
041ab34a 496 try {
041ab34a
RG
497 /* you can't touch du after this line, because it might already have been freed */
498 ssize_t ret = udpClientSendRequestToBackend(ss, fd, query, dq.len);
499
500 if(ret < 0) {
041ab34a
RG
501 /* we are about to handle the error, make sure that
502 this pointer is not accessed when the state is cleaned,
503 but first check that it still belongs to us */
504 if (ids->tryMarkUnused(generation)) {
505 ids->du = nullptr;
b6d19fca
RG
506 du->release();
507 duRefCountIncremented = false;
041ab34a
RG
508 --ss->outstanding;
509 }
510 ++ss->sendErrors;
511 ++g_stats.downstreamSendErrors;
512 du->status_code = 502;
513 return -1;
543dfbd7 514 }
041ab34a
RG
515 }
516 catch (const std::exception& e) {
b6d19fca
RG
517 if (duRefCountIncremented) {
518 du->release();
519 }
041ab34a 520 throw;
fbf14b03 521 }
543dfbd7
RG
522
523 vinfolog("Got query for %s|%s from %s (https), relayed to %s", ids->qname.toString(), QType(ids->qtype).getName(), remote.toStringWithPort(), ss->getName());
fbf14b03
RG
524 }
525 catch(const std::exception& e) {
ede152ec 526 vinfolog("Got an error in DOH question thread while parsing a query from %s, id %d: %s", remote.toStringWithPort(), queryId, e.what());
b2664a49 527 du->status_code = 500;
fbf14b03
RG
528 return -1;
529 }
b2664a49 530
fbf14b03
RG
531 return 0;
532}
533
b6d19fca 534/* called when a HTTP response is about to be sent */
5bbcbea0
RG
535static void on_response_ready_cb(struct st_h2o_filter_t *self, h2o_req_t *req, h2o_ostream_t **slot)
536{
537 if (req == nullptr) {
538 return;
539 }
540
541 DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(req->conn->ctx->storage.entries[0].data);
542
543 DOHFrontend::HTTPVersionStats* stats = nullptr;
544 if (req->version < 0x200) {
545 /* HTTP 1.x */
546 stats = &dsc->df->d_http1Stats;
547 }
548 else {
549 /* HTTP 2.0 */
550 stats = &dsc->df->d_http2Stats;
551 }
552
553 switch (req->res.status) {
554 case 200:
555 ++stats->d_nb200Responses;
556 break;
557 case 400:
558 ++stats->d_nb400Responses;
559 break;
560 case 403:
561 ++stats->d_nb403Responses;
562 break;
563 case 500:
564 ++stats->d_nb500Responses;
565 break;
566 case 502:
567 ++stats->d_nb502Responses;
568 break;
569 default:
570 ++stats->d_nbOtherResponses;
571 break;
572 }
573
574 h2o_setup_next_ostream(req, slot);
575}
576
fbf14b03
RG
577static h2o_pathconf_t *register_handler(h2o_hostconf_t *hostconf, const char *path, int (*on_req)(h2o_handler_t *, h2o_req_t *))
578{
579 h2o_pathconf_t *pathconf = h2o_config_register_path(hostconf, path, 0);
5bbcbea0
RG
580 if (pathconf == nullptr) {
581 return pathconf;
582 }
583 h2o_filter_t *filter = h2o_create_filter(pathconf, sizeof(*filter));
584 if (filter) {
585 filter->on_setup_ostream = on_response_ready_cb;
586 }
587
fbf14b03 588 h2o_handler_t *handler = h2o_create_handler(pathconf, sizeof(*handler));
5bbcbea0
RG
589 if (handler != nullptr) {
590 handler->on_req = on_req;
591 }
592
fbf14b03
RG
593 return pathconf;
594}
595
596/* this is called by h2o when our request dies.
597 We use this to signal to the 'du' that this req is no longer alive */
598static void on_generator_dispose(void *_self)
599{
600 DOHUnit** du = (DOHUnit**)_self;
601 if(*du) { // if 0, on_dnsdist cleaned up du already
602// cout << "du "<<(void*)*du<<" containing req "<<(*du)->req<<" got killed"<<endl;
603 (*du)->req = nullptr;
604 }
605}
606
b6d19fca
RG
607/* We allocate a DOHUnit and send it to dnsdistclient() function in the doh client thread
608 via a pipe */
efa87914 609static void doh_dispatch_query(DOHServerConfig* dsc, h2o_handler_t* self, h2o_req_t* req, std::string&& query, const ComboAddress& local, const ComboAddress& remote)
fbf14b03
RG
610{
611 try {
fbf14b03
RG
612 uint16_t qtype;
613 DNSName qname(query.c_str(), query.size(), sizeof(dnsheader), false, &qtype);
19642bff
RG
614
615 auto du = std::unique_ptr<DOHUnit>(new DOHUnit);
fbf14b03 616 du->req = req;
efa87914 617 du->dest = local;
fbf14b03
RG
618 du->remote = remote;
619 du->rsock = dsc->dohresponsepair[0];
19642bff 620 du->query = std::move(query);
fbf14b03 621 du->qtype = qtype;
19642bff 622 du->self = reinterpret_cast<DOHUnit**>(h2o_mem_alloc_shared(&req->pool, sizeof(*self), on_generator_dispose));
fbf14b03
RG
623 auto ptr = du.release();
624 *(ptr->self) = ptr;
625 try {
626 if(send(dsc->dohquerypair[0], &ptr, sizeof(ptr), 0) != sizeof(ptr)) {
041ab34a 627 ptr->release();
fbf14b03 628 ptr = nullptr;
b2664a49 629 h2o_send_error_500(req, "Internal Server Error", "Internal Server Error", 0);
fbf14b03
RG
630 }
631 }
632 catch(...) {
041ab34a 633 ptr->release();
fbf14b03
RG
634 }
635 }
636 catch(const std::exception& e) {
637 vinfolog("Had error parsing DoH DNS packet from %s: %s", remote.toStringWithPort(), e.what());
01d50ff3 638 h2o_send_error_400(req, "Bad Request", "The DNS query could not be parsed", 0);
fbf14b03
RG
639 }
640}
641
642/*
b6d19fca
RG
643 A query has been parsed by h2o.
644 For GET, the base64url-encoded payload is in the 'dns' parameter, which might be the first parameter, or not.
645 For POST, the payload is the payload.
fbf14b03
RG
646 */
647static int doh_handler(h2o_handler_t *self, h2o_req_t *req)
648try
649{
650 // g_logstream<<(void*)req<<" doh_handler"<<endl;
651 if(!req->conn->ctx->storage.size) {
652 return 0; // although we might was well crash on this
653 }
654 h2o_socket_t* sock = req->conn->callbacks->get_socket(req->conn);
655 ComboAddress remote;
efa87914 656 ComboAddress local;
fbf14b03 657 h2o_socket_getpeername(sock, reinterpret_cast<struct sockaddr*>(&remote));
efa87914 658 h2o_socket_getsockname(sock, reinterpret_cast<struct sockaddr*>(&local));
6e3a9fe4 659 DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(req->conn->ctx->storage.entries[0].data);
fbf14b03 660
3b6a6899
RG
661 auto& holders = dsc->holders;
662 if (!holders.acl->match(remote)) {
663 ++g_stats.aclDrops;
664 vinfolog("Query from %s (DoH) dropped because of ACL", remote.toStringWithPort());
665 h2o_send_error_403(req, "Forbidden", "dns query not allowed because of ACL", 0);
666 return 0;
667 }
668
846b63bb
RG
669 if (h2o_socket_get_ssl_session_reused(sock) == 0) {
670 ++dsc->cs->tlsNewSessions;
671 }
672 else {
673 ++dsc->cs->tlsResumptions;
674 }
675
fbf14b03
RG
676 if(auto tlsversion = h2o_socket_get_ssl_protocol_version(sock)) {
677 if(!strcmp(tlsversion, "TLSv1.0"))
bb3954f0 678 ++dsc->cs->tls10queries;
fbf14b03 679 else if(!strcmp(tlsversion, "TLSv1.1"))
bb3954f0 680 ++dsc->cs->tls11queries;
fbf14b03 681 else if(!strcmp(tlsversion, "TLSv1.2"))
bb3954f0 682 ++dsc->cs->tls12queries;
fbf14b03 683 else if(!strcmp(tlsversion, "TLSv1.3"))
bb3954f0 684 ++dsc->cs->tls13queries;
fbf14b03 685 else
bb3954f0 686 ++dsc->cs->tlsUnknownqueries;
fbf14b03
RG
687 }
688
689 string path(req->path.base, req->path.len);
690
767fbba3
RG
691 string pathOnly(req->path_normalized.base, req->path_normalized.len);
692 if (dsc->paths.count(pathOnly) == 0) {
693 h2o_send_error_404(req, "Not Found", "there is no endpoint configured for this path", 0);
694 return 0;
695 }
696
28b56482
RG
697 for (const auto& entry : dsc->df->d_responsesMap) {
698 if (entry->matches(path)) {
ded6907c
RG
699 const auto& customHeaders = entry->getHeaders();
700 handleResponse(*dsc->df, req, entry->getStatusCode(), entry->getContent(), customHeaders ? *customHeaders : dsc->df->d_customResponseHeaders, std::string(), false);
28b56482
RG
701 return 0;
702 }
703 }
704
fbf14b03
RG
705 if (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("POST"))) {
706 ++dsc->df->d_postqueries;
707 if(req->version >= 0x0200)
5bbcbea0 708 ++dsc->df->d_http2Stats.d_nbQueries;
fbf14b03 709 else
5bbcbea0 710 ++dsc->df->d_http1Stats.d_nbQueries;
fbf14b03
RG
711
712 std::string query;
8179b6d6
RG
713 /* We reserve at least 512 additional bytes to be able to add EDNS, but we also want
714 at least s_maxPacketCacheEntrySize bytes to be able to fill the answer from the packet cache */
57eb4882 715 query.reserve(std::max(req->entity.len + 512, s_maxPacketCacheEntrySize));
fbf14b03 716 query.assign(req->entity.base, req->entity.len);
efa87914 717 doh_dispatch_query(dsc, self, req, std::move(query), local, remote);
fbf14b03
RG
718 }
719 else if(req->query_at != SIZE_MAX && (req->path.len - req->query_at > 5)) {
720 auto pos = path.find("?dns=");
721 if(pos == string::npos)
722 pos = path.find("&dns=");
723 if(pos != string::npos) {
724 // need to base64url decode this
725 string sdns(path.substr(pos+5));
726 boost::replace_all(sdns,"-", "+");
727 boost::replace_all(sdns,"_", "/");
53e47857
RG
728 // re-add padding that may have been missing
729 switch (sdns.size() % 4) {
730 case 2:
731 sdns.append(2, '=');
732 break;
733 case 3:
734 sdns.append(1, '=');
735 break;
736 }
fbf14b03
RG
737
738 string decoded;
739 /* rough estimate so we hopefully don't need a need allocation later */
8179b6d6
RG
740 /* We reserve at least 512 additional bytes to be able to add EDNS, but we also want
741 at least s_maxPacketCacheEntrySize bytes to be able to fill the answer from the packet cache */
57eb4882
RG
742 const size_t estimate = ((sdns.size() * 3) / 4);
743 decoded.reserve(std::max(estimate + 512, s_maxPacketCacheEntrySize));
fbf14b03 744 if(B64Decode(sdns, decoded) < 0) {
01d50ff3 745 h2o_send_error_400(req, "Bad Request", "Unable to decode BASE64-URL", 0);
fbf14b03
RG
746 ++dsc->df->d_badrequests;
747 return 0;
748 }
749 else {
750 ++dsc->df->d_getqueries;
751 if(req->version >= 0x0200)
5bbcbea0 752 ++dsc->df->d_http2Stats.d_nbQueries;
fbf14b03 753 else
5bbcbea0 754 ++dsc->df->d_http1Stats.d_nbQueries;
fbf14b03 755
efa87914 756 doh_dispatch_query(dsc, self, req, std::move(decoded), local, remote);
fbf14b03
RG
757 }
758 }
759 else
760 {
761 vinfolog("HTTP request without DNS parameter: %s", req->path.base);
01d50ff3 762 h2o_send_error_400(req, "Bad Request", "Unable to find the DNS parameter", 0);
fbf14b03
RG
763 ++dsc->df->d_badrequests;
764 return 0;
765 }
766 }
767 else {
01d50ff3 768 h2o_send_error_400(req, "Bad Request", "Unable to parse the request", 0);
fbf14b03
RG
769 ++dsc->df->d_badrequests;
770 }
771 return 0;
772}
773catch(const exception& e)
774{
775 errlog("DOH Handler function failed with error %s", e.what());
776 return 0;
777}
778
779HTTPHeaderRule::HTTPHeaderRule(const std::string& header, const std::string& regex)
1fab05c7 780 : d_header(toLower(header)), d_regex(regex), d_visual("http[" + header+ "] ~ " + regex)
fbf14b03 781{
fbf14b03 782}
56f2845e 783
fbf14b03
RG
784bool HTTPHeaderRule::matches(const DNSQuestion* dq) const
785{
255abac6 786 if (!dq->du) {
fbf14b03
RG
787 return false;
788 }
789
255abac6 790 for (size_t i = 0; i < dq->du->req->headers.size; ++i) {
fbf14b03
RG
791 if(std::string(dq->du->req->headers.entries[i].name->base, dq->du->req->headers.entries[i].name->len) == d_header &&
792 d_regex.match(std::string(dq->du->req->headers.entries[i].value.base, dq->du->req->headers.entries[i].value.len))) {
793 return true;
794 }
795 }
796 return false;
797}
798
799string HTTPHeaderRule::toString() const
800{
801 return d_visual;
802}
803
804HTTPPathRule::HTTPPathRule(const std::string& path)
805 : d_path(path)
806{
807
808}
809
810bool HTTPPathRule::matches(const DNSQuestion* dq) const
811{
812 if(!dq->du) {
813 return false;
814 }
815
816 if(dq->du->req->query_at == SIZE_MAX) {
817 return dq->du->req->path.base == d_path;
818 }
819 else {
820 return d_path.compare(0, d_path.size(), dq->du->req->path.base, dq->du->req->query_at) == 0;
821 }
822}
823
824string HTTPPathRule::toString() const
825{
826 return "url path == " + d_path;
827}
828
56f2845e
RG
829HTTPPathRegexRule::HTTPPathRegexRule(const std::string& regex): d_regex(regex), d_visual("http path ~ " + regex)
830{
831}
832
833bool HTTPPathRegexRule::matches(const DNSQuestion* dq) const
834{
255abac6 835 if (!dq->du) {
56f2845e
RG
836 return false;
837 }
838
255abac6
RG
839 return d_regex.match(dq->du->getHTTPPath());
840}
841
842string HTTPPathRegexRule::toString() const
843{
844 return d_visual;
845}
846
847std::unordered_map<std::string, std::string> DOHUnit::getHTTPHeaders() const
848{
849 std::unordered_map<std::string, std::string> results;
850 results.reserve(req->headers.size);
851
852 for (size_t i = 0; i < req->headers.size; ++i) {
853 results.insert({std::string(req->headers.entries[i].name->base, req->headers.entries[i].name->len),
854 std::string(req->headers.entries[i].value.base, req->headers.entries[i].value.len)});
855 }
856
857 return results;
858}
859
860std::string DOHUnit::getHTTPPath() const
861{
862 if (req->query_at == SIZE_MAX) {
863 return std::string(req->path.base, req->path.len);
56f2845e
RG
864 }
865 else {
255abac6 866 return std::string(req->path.base, req->query_at);
56f2845e
RG
867 }
868}
869
255abac6 870std::string DOHUnit::getHTTPHost() const
56f2845e 871{
255abac6 872 return std::string(req->authority.base, req->authority.len);
56f2845e
RG
873}
874
255abac6 875std::string DOHUnit::getHTTPScheme() const
13c1fc12 876{
255abac6
RG
877 if (req->scheme == nullptr) {
878 return std::string();
879 }
880
881 return std::string(req->scheme->name.base, req->scheme->name.len);
882}
883
884std::string DOHUnit::getHTTPQueryString() const
885{
886 if (req->query_at == SIZE_MAX) {
887 return std::string();
888 }
889 else {
890 return std::string(req->path.base + req->query_at, req->path.len - req->query_at);
891 }
892}
893
9676d2a9 894void DOHUnit::setHTTPResponse(uint16_t statusCode, const std::string& body_, const std::string& contentType_)
255abac6
RG
895{
896 status_code = statusCode;
255abac6 897 response = body_;
9676d2a9 898 contentType = contentType_;
13c1fc12
RG
899}
900
b6d19fca
RG
901/* query has been parsed by h2o, which called doh_handler() in the main DoH thread.
902 In order not to blockfor long, doh_handler() called doh_dispatch_query() which allocated
903 a DOHUnit object and passed it to us */
904static void dnsdistclient(int qsock, int rsock)
fbf14b03 905{
70915d97
RG
906 setThreadName("dnsdist/doh-cli");
907
fbf14b03
RG
908 for(;;) {
909 try {
910 DOHUnit* du = nullptr;
911 ssize_t got = recv(qsock, &du, sizeof(du), 0);
912 if (got < 0) {
11d127d9 913 warnlog("Error receiving internal DoH query: %s", strerror(errno));
fbf14b03
RG
914 continue;
915 }
916 else if (static_cast<size_t>(got) < sizeof(du)) {
917 continue;
918 }
919
920 // if there was no EDNS, we add it with a large buffer size
921 // so we can use UDP to talk to the backend.
922 auto dh = const_cast<struct dnsheader*>(reinterpret_cast<const struct dnsheader*>(du->query.c_str()));
923
924 if(!dh->arcount) {
925 std::string res;
926 generateOptRR(std::string(), res, 4096, 0, false);
927
928 du->query += res;
929 dh = const_cast<struct dnsheader*>(reinterpret_cast<const struct dnsheader*>(du->query.c_str())); // may have reallocated
930 dh->arcount = htons(1);
931 du->ednsAdded = true;
932 }
933 else {
934 // we leave existing EDNS in place
935 }
936
937 if(processDOHQuery(du) < 0) {
13c1fc12 938 du->status_code = 500;
041ab34a
RG
939 /* increase the ref count before sending the pointer */
940 du->get();
941 if(send(du->rsock, &du, sizeof(du), 0) != sizeof(du)) {
942 du->release(); // XXX but now what - will h2o time this out for us?
943 }
fbf14b03 944 }
041ab34a 945 du->release();
fbf14b03
RG
946 }
947 catch(const std::exception& e) {
948 errlog("Error while processing query received over DoH: %s", e.what());
949 }
950 catch(...) {
951 errlog("Unspecified error while processing query received over DoH");
952 }
953 }
954}
955
b6d19fca
RG
956/* called if h2o finds that dnsdist gave us an answer by writing into
957 the dohresponsepair[0] side of the pipe so from:
958 - handleDOHTimeout() when we did not get a response fast enough (called
959 either from the health check thread (active) or from the frontend ones (reused))
960 - dnsdistclient (error 500 because processDOHQuery() returned a negative value)
961 - processDOHQuery (self-answered queries)
962 */
fbf14b03
RG
963static void on_dnsdist(h2o_socket_t *listener, const char *err)
964{
965 DOHUnit *du = nullptr;
6e3a9fe4 966 DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(listener->data);
fbf14b03
RG
967 ssize_t got = recv(dsc->dohresponsepair[1], &du, sizeof(du), 0);
968
969 if (got < 0) {
970 warnlog("Error reading a DOH internal response: %s", strerror(errno));
971 return;
972 }
973 else if (static_cast<size_t>(got) != sizeof(du)) {
974 return;
975 }
976
977 if(!du->req) { // it got killed in flight
978// cout << "du "<<(void*)du<<" came back from dnsdist, but it was killed"<<endl;
041ab34a 979 du->release();
fbf14b03
RG
980 return;
981 }
982
983 *du->self = nullptr; // so we don't clean up again in on_generator_dispose
9676d2a9 984
ded6907c 985 handleResponse(*dsc->df, du->req, du->status_code, du->response, dsc->df->d_customResponseHeaders, du->contentType, true);
0956c5c5 986
041ab34a 987 du->release();
fbf14b03
RG
988}
989
b6d19fca 990/* called when a TCP connection has been accepted, the TLS session has not been established */
fbf14b03
RG
991static void on_accept(h2o_socket_t *listener, const char *err)
992{
6e3a9fe4 993 DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(listener->data);
fbf14b03
RG
994 h2o_socket_t *sock = nullptr;
995
996 if (err != nullptr) {
997 return;
998 }
999 // do some dnsdist rules here to filter based on IP address
ede152ec 1000 if ((sock = h2o_evloop_socket_accept(listener)) == nullptr) {
fbf14b03 1001 return;
ede152ec 1002 }
fbf14b03
RG
1003
1004 ComboAddress remote;
fbf14b03
RG
1005 h2o_socket_getpeername(sock, reinterpret_cast<struct sockaddr*>(&remote));
1006 // cout<<"New HTTP accept for client "<<remote.toStringWithPort()<<": "<< listener->data << endl;
1007
1008 sock->data = dsc;
ede152ec
RG
1009 sock->on_close.cb = on_socketclose;
1010 auto accept_ctx = dsc->accept_ctx->get();
1011 sock->on_close.data = dsc->accept_ctx;
fbf14b03 1012 ++dsc->df->d_httpconnects;
f2affb3b 1013 ++dsc->cs->tcpCurrentConnections;
ede152ec 1014 h2o_accept(accept_ctx, sock);
fbf14b03
RG
1015}
1016
6e3a9fe4 1017static int create_listener(const ComboAddress& addr, std::shared_ptr<DOHServerConfig>& dsc, int fd)
fbf14b03
RG
1018{
1019 auto sock = h2o_evloop_socket_create(dsc->h2o_ctx.loop, fd, H2O_SOCKET_FLAG_DONT_READ);
6e3a9fe4 1020 sock->data = dsc.get();
fbf14b03
RG
1021 h2o_socket_read_start(sock, on_accept);
1022
1023 return 0;
1024}
1025
be3183ed
RG
1026static int ocsp_stapling_callback(SSL* ssl, void* arg)
1027{
1028 if (ssl == nullptr || arg == nullptr) {
1029 return SSL_TLSEXT_ERR_NOACK;
1030 }
1031 const auto ocspMap = reinterpret_cast<std::map<int, std::string>*>(arg);
1032 return libssl_ocsp_stapling_callback(ssl, *ocspMap);
1033}
1034
0ef9ab19
RG
1035static int ticket_key_callback(SSL *s, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx, int enc)
1036{
9f32da84
RG
1037 DOHAcceptContext* ctx = reinterpret_cast<DOHAcceptContext*>(libssl_get_ticket_key_callback_data(s));
1038 if (ctx == nullptr || !ctx->d_ticketKeys) {
0ef9ab19
RG
1039 return -1;
1040 }
1041
9f32da84 1042 ctx->handleTicketsKeyRotation();
0ef9ab19 1043
9f32da84 1044 auto ret = libssl_ticket_key_callback(s, *ctx->d_ticketKeys, keyName, iv, ectx, hctx, enc);
b608e6c6
RG
1045 if (enc == 0) {
1046 if (ret == 0) {
9f32da84 1047 ++ctx->d_cs->tlsUnknownTicketKey;
b608e6c6
RG
1048 }
1049 else if (ret == 2) {
9f32da84 1050 ++ctx->d_cs->tlsInactiveTicketKey;
b608e6c6
RG
1051 }
1052 }
1053
1054 return ret;
0ef9ab19
RG
1055}
1056
9f32da84
RG
1057static void setupTLSContext(DOHAcceptContext& acceptCtx,
1058 TLSConfig& tlsConfig,
1059 TLSErrorCounters& counters)
fbf14b03 1060{
9f32da84
RG
1061 if (tlsConfig.d_ciphers.empty()) {
1062 tlsConfig.d_ciphers = DOH_DEFAULT_CIPHERS;
1063 }
0ef9ab19 1064
9f32da84 1065 auto ctx = libssl_init_server_context(tlsConfig, acceptCtx.d_ocspResponses);
be3183ed 1066
9f32da84
RG
1067 if (tlsConfig.d_enableTickets && tlsConfig.d_numberOfTicketsKeys > 0) {
1068 acceptCtx.d_ticketKeys = std::unique_ptr<OpenSSLTLSTicketKeysRing>(new OpenSSLTLSTicketKeysRing(tlsConfig.d_numberOfTicketsKeys));
1069 SSL_CTX_set_tlsext_ticket_key_cb(ctx.get(), &ticket_key_callback);
1070 libssl_set_ticket_key_callback_data(ctx.get(), &acceptCtx);
1071 }
fbf14b03 1072
9f32da84
RG
1073 if (!acceptCtx.d_ocspResponses.empty()) {
1074 SSL_CTX_set_tlsext_status_cb(ctx.get(), &ocsp_stapling_callback);
1075 SSL_CTX_set_tlsext_status_arg(ctx.get(), &acceptCtx.d_ocspResponses);
1076 }
f34fdcc5 1077
9f32da84 1078 libssl_set_error_counters_callback(ctx, &counters);
fbf14b03 1079
7f8a5a32
RG
1080 if (!tlsConfig.d_keyLogFile.empty()) {
1081 acceptCtx.d_keyLogFile = libssl_set_key_log_file(ctx, tlsConfig.d_keyLogFile);
1082 }
1083
9f32da84 1084 h2o_ssl_register_alpn_protocols(ctx.get(), h2o_http2_alpn_protocols);
b54e94dc 1085
9f32da84
RG
1086 if (tlsConfig.d_ticketKeyFile.empty()) {
1087 acceptCtx.handleTicketsKeyRotation();
0ef9ab19 1088 }
9f32da84
RG
1089 else {
1090 acceptCtx.loadTicketsKeys(tlsConfig.d_ticketKeyFile);
0ef9ab19 1091 }
9f32da84
RG
1092
1093 auto nativeCtx = acceptCtx.get();
1094 nativeCtx->ssl_ctx = ctx.release();
1095 acceptCtx.release();
ede152ec
RG
1096}
1097
1098static void setupAcceptContext(DOHAcceptContext& ctx, DOHServerConfig& dsc, bool setupTLS)
1099{
1100 auto nativeCtx = ctx.get();
1101 nativeCtx->ctx = &dsc.h2o_ctx;
1102 nativeCtx->hosts = dsc.h2o_config.hosts;
9f32da84 1103 ctx.d_ticketsKeyRotationDelay = dsc.df->d_tlsConfig.d_ticketsKeyRotationDelay;
ede152ec 1104
9f32da84
RG
1105 if (setupTLS && !dsc.df->d_tlsConfig.d_certKeyPairs.empty()) {
1106 try {
1107 setupTLSContext(ctx,
1108 dsc.df->d_tlsConfig,
1109 dsc.df->d_tlsCounters);
1110 }
1111 catch (const std::runtime_error& e) {
1112 throw std::runtime_error("Error setting up TLS context for DoH listener on '" + dsc.df->d_local.toStringWithPort() + "': " + e.what());
1113 }
ede152ec 1114 }
f2affb3b 1115 ctx.d_cs = dsc.cs;
ede152ec
RG
1116 ctx.release();
1117}
1118
0ef9ab19
RG
1119void DOHFrontend::rotateTicketsKey(time_t now)
1120{
9f32da84
RG
1121 if (d_dsc && d_dsc->accept_ctx) {
1122 d_dsc->accept_ctx->rotateTicketsKey(now);
0ef9ab19
RG
1123 }
1124}
1125
1126void DOHFrontend::loadTicketsKeys(const std::string& keyFile)
1127{
9f32da84
RG
1128 if (d_dsc && d_dsc->accept_ctx) {
1129 d_dsc->accept_ctx->loadTicketsKeys(keyFile);
0ef9ab19
RG
1130 }
1131}
1132
1133void DOHFrontend::handleTicketsKeyRotation()
1134{
9f32da84
RG
1135 if (d_dsc && d_dsc->accept_ctx) {
1136 d_dsc->accept_ctx->handleTicketsKeyRotation();
0ef9ab19 1137 }
9f32da84 1138}
0ef9ab19 1139
9f32da84
RG
1140time_t DOHFrontend::getNextTicketsKeyRotation() const
1141{
1142 if (d_dsc && d_dsc->accept_ctx) {
1143 return d_dsc->accept_ctx->getNextTicketsKeyRotation();
1144 }
1145 return 0;
1146}
0ef9ab19 1147
9f32da84
RG
1148size_t DOHFrontend::getTicketsKeysCount() const
1149{
1150 size_t res = 0;
1151 if (d_dsc && d_dsc->accept_ctx) {
1152 res = d_dsc->accept_ctx->getTicketsKeysCount();
0ef9ab19 1153 }
9f32da84 1154 return res;
0ef9ab19
RG
1155}
1156
6c7cec08 1157void DOHFrontend::reloadCertificates()
ede152ec
RG
1158{
1159 auto newAcceptContext = std::unique_ptr<DOHAcceptContext>(new DOHAcceptContext());
1160 setupAcceptContext(*newAcceptContext, *d_dsc, true);
1161 DOHAcceptContext* oldCtx = d_dsc->accept_ctx;
1162 d_dsc->accept_ctx = newAcceptContext.release();
1163 oldCtx->release();
fbf14b03
RG
1164}
1165
1166void DOHFrontend::setup()
1167{
ede152ec
RG
1168 registerOpenSSLUser();
1169
6e3a9fe4
RG
1170 d_dsc = std::make_shared<DOHServerConfig>(d_idleTimeout);
1171
b54e94dc 1172 if (!d_tlsConfig.d_certKeyPairs.empty()) {
9f32da84
RG
1173 try {
1174 setupTLSContext(*d_dsc->accept_ctx,
1175 d_tlsConfig,
1176 d_tlsCounters);
1177 }
1178 catch (const std::runtime_error& e) {
1179 throw std::runtime_error("Error setting up TLS context for DoH listener on '" + d_local.toStringWithPort() + "': " + e.what());
1180 }
44947230 1181 }
fbf14b03
RG
1182}
1183
1184// this is the entrypoint from dnsdist.cc
1185void dohThread(ClientState* cs)
1186try
1187{
1188 std::shared_ptr<DOHFrontend>& df = cs->dohFrontend;
6e3a9fe4
RG
1189 auto& dsc = df->d_dsc;
1190 dsc->cs = cs;
1191 dsc->df = cs->dohFrontend;
a429a303
RG
1192 dsc->h2o_config.server_name = h2o_iovec_init(df->d_serverTokens.c_str(), df->d_serverTokens.size());
1193
fbf14b03
RG
1194
1195 std::thread dnsdistThread(dnsdistclient, dsc->dohquerypair[1], dsc->dohresponsepair[0]);
1196 dnsdistThread.detach(); // gets us better error reporting
1197
70915d97 1198 setThreadName("dnsdist/doh");
fbf14b03
RG
1199 // I wonder if this registers an IP address.. I think it does
1200 // this may mean we need to actually register a site "name" here and not the IP address
1201 h2o_hostconf_t *hostconf = h2o_config_register_host(&dsc->h2o_config, h2o_iovec_init(df->d_local.toString().c_str(), df->d_local.toString().size()), 65535);
1202
1203 for(const auto& url : df->d_urls) {
1204 register_handler(hostconf, url.c_str(), doh_handler);
767fbba3 1205 dsc->paths.insert(url);
fbf14b03
RG
1206 }
1207
1208 h2o_context_init(&dsc->h2o_ctx, h2o_evloop_create(), &dsc->h2o_config);
1209
1210 // in this complicated way we insert the DOHServerConfig pointer in there
1211 h2o_vector_reserve(nullptr, &dsc->h2o_ctx.storage, 1);
6e3a9fe4 1212 dsc->h2o_ctx.storage.entries[0].data = dsc.get();
fbf14b03
RG
1213 ++dsc->h2o_ctx.storage.size;
1214
1215 auto sock = h2o_evloop_socket_create(dsc->h2o_ctx.loop, dsc->dohresponsepair[1], H2O_SOCKET_FLAG_DONT_READ);
6e3a9fe4 1216 sock->data = dsc.get();
fbf14b03
RG
1217
1218 // this listens to responses from dnsdist to turn into http responses
1219 h2o_socket_read_start(sock, on_dnsdist);
1220
ede152ec 1221 setupAcceptContext(*dsc->accept_ctx, *dsc, false);
fbf14b03
RG
1222
1223 if (create_listener(df->d_local, dsc, cs->tcpFD) != 0) {
1224 throw std::runtime_error("DOH server failed to listen on " + df->d_local.toStringWithPort() + ": " + strerror(errno));
1225 }
1226
50f53f00
RG
1227 bool stop = false;
1228 do {
1229 int result = h2o_evloop_run(dsc->h2o_ctx.loop, INT32_MAX);
1230 if (result == -1) {
1231 if (errno != EINTR) {
1232 errlog("Error in the DoH event loop: %s", strerror(errno));
1233 stop = true;
1234 }
1235 }
1236 }
1237 while (stop == false);
1238
1239}
1240catch(const std::exception& e) {
1241 throw runtime_error("DOH thread failed to launch: " + std::string(e.what()));
1242}
1243catch(...) {
1244 throw runtime_error("DOH thread failed to launch");
1245}
0956c5c5
RG
1246
1247#else /* HAVE_DNS_OVER_HTTPS */
1248
1249void handleDOHTimeout(DOHUnit* oldDU)
1250{
1251}
1252
1253#endif /* HAVE_DNS_OVER_HTTPS */