]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnsdistdist/doh.cc
dnsdist: Don't accept sub-paths of configured DoH URLs
[thirdparty/pdns.git] / pdns / dnsdistdist / doh.cc
1 #include "config.h"
2 #include "doh.hh"
3
4 #ifdef HAVE_DNS_OVER_HTTPS
5 #define H2O_USE_EPOLL 1
6
7 #include <errno.h>
8 #include <iostream>
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
16 #include <openssl/err.h>
17 #include <openssl/ssl.h>
18
19 #include "base64.hh"
20 #include "dnsname.hh"
21 #undef CERT
22 #include "dnsdist.hh"
23 #include "misc.hh"
24 #include "dns.hh"
25 #include "dolog.hh"
26 #include "dnsdist-ecs.hh"
27 #include "dnsdist-rules.hh"
28 #include "dnsdist-xpf.hh"
29 #include "libssl.hh"
30 #include "threadname.hh"
31
32 using 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
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
59 class DOHAcceptContext
60 {
61 public:
62 DOHAcceptContext()
63 {
64 memset(&d_h2o_accept_ctx, 0, sizeof(d_h2o_accept_ctx));
65 d_rotatingTicketsKey.clear();
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 {
78 if (--d_refcnt == 0) {
79 SSL_CTX_free(d_h2o_accept_ctx.ssl_ctx);
80 d_h2o_accept_ctx.ssl_ctx = nullptr;
81 delete this;
82 }
83 }
84
85 void decrementConcurrentConnections()
86 {
87 if (d_cs != nullptr) {
88 --d_cs->tcpCurrentConnections;
89 }
90 }
91
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
159 std::map<int, std::string> d_ocspResponses;
160 std::unique_ptr<OpenSSLTLSTicketKeysRing> d_ticketKeys{nullptr};
161 std::unique_ptr<FILE, int(*)(FILE*)> d_keyLogFile{nullptr, fclose};
162 ClientState* d_cs{nullptr};
163 time_t d_ticketsKeyRotationDelay{0};
164
165 private:
166 h2o_accept_ctx_t d_h2o_accept_ctx;
167 std::atomic<uint64_t> d_refcnt{1};
168 time_t d_ticketsKeyNextRotation{0};
169 std::atomic_flag d_rotatingTicketsKey;
170 };
171
172 // we create one of these per thread, and pass around a pointer to it
173 // through the bowels of h2o
174 struct DOHServerConfig
175 {
176 DOHServerConfig(uint32_t idleTimeout): accept_ctx(new DOHAcceptContext)
177 {
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);
189 h2o_config.http2.idle_timeout = idleTimeout * 1000;
190 }
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 }
200
201 LocalHolders holders;
202 std::unordered_set<std::string> paths;
203 h2o_globalconf_t h2o_config;
204 h2o_context_t h2o_ctx;
205 DOHAcceptContext* accept_ctx{nullptr};
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
212 void handleDOHTimeout(DOHUnit* oldDU)
213 {
214 if (oldDU == nullptr) {
215 return;
216 }
217
218 /* we are about to erase an existing DU */
219 oldDU->status_code = 502;
220
221 /* increase the ref counter before sending the pointer */
222 oldDU->get();
223 if (send(oldDU->rsock, &oldDU, sizeof(oldDU), 0) != sizeof(oldDU)) {
224 oldDU->release();
225 }
226 oldDU->release();
227 oldDU = nullptr;
228 }
229
230 static void on_socketclose(void *data)
231 {
232 DOHAcceptContext* ctx = reinterpret_cast<DOHAcceptContext*>(data);
233 ctx->decrementConcurrentConnections();
234 ctx->release();
235 }
236
237 static 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
288 static 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)
289 {
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
296 if (statusCode == 200) {
297 ++df.d_validresponses;
298 req->res.status = 200;
299
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 }
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()) {
323 h2o_send_error_generic(req, statusCode, getReasonFromStatusCode(statusCode).c_str(), response.c_str(), H2O_SEND_ERROR_KEEP_HEADERS);
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 /*
349 this function calls 'return -1' to drop a query without sending it
350 caller should make sure HTTPS thread hears of that
351 */
352 static int processDOHQuery(DOHUnit* du)
353 {
354 uint16_t queryId = 0;
355 ComboAddress remote;
356 bool duRefCountIncremented = false;
357 try {
358 if(!du->req) {
359 // we got closed meanwhile. XXX small race condition here
360 return -1;
361 }
362 remote = du->remote;
363 DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(du->req->conn->ctx->storage.entries[0].data);
364 auto& holders = dsc->holders;
365 ClientState& cs = *dsc->cs;
366
367 if (du->query.size() < sizeof(dnsheader)) {
368 ++g_stats.nonCompliantQueries;
369 du->status_code = 400;
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();
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 */
384 du->query.resize(std::max(du->query.size() + 512, s_maxPacketCacheEntrySize));
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)) {
390 du->status_code = 400;
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);
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 }
407 #endif /* HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME */
408
409 std::shared_ptr<DownstreamState> ss{nullptr};
410 auto result = processQuery(dq, cs, holders, ss);
411
412 if (result == ProcessQueryResult::Drop) {
413 du->status_code = 403;
414 return -1;
415 }
416
417 if (result == ProcessQueryResult::SendAnswer) {
418 if (du->response.empty()) {
419 du->response = std::string(reinterpret_cast<char*>(dq.dh), dq.len);
420 }
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 }
426 return 0;
427 }
428
429 if (result != ProcessQueryResult::PassToBackend) {
430 du->status_code = 500;
431 return -1;
432 }
433
434 if (ss == nullptr) {
435 du->status_code = 502;
436 return -1;
437 }
438
439 ComboAddress dest = du->dest;
440 unsigned int idOffset = (ss->idOffset++) % ss->idStates.size();
441 IDState* ids = &ss->idStates[idOffset];
442 ids->age = 0;
443 DOHUnit* oldDU = nullptr;
444 if (ids->isInUse()) {
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 */
448 oldDU = ids->du;
449 }
450
451 /* we atomically replace the value, we now own this state */
452 int64_t generation = ids->generation++;
453 if (!ids->markAsUsed(generation)) {
454 /* the state was not in use.
455 we reset 'oldDU' because it might have still been in use when we read it. */
456 oldDU = nullptr;
457 ++ss->outstanding;
458 }
459 else {
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. */
463 ++ss->reuseds;
464 ++g_stats.downstreamTimeouts;
465 handleDOHTimeout(oldDU);
466 }
467
468 ids->origFD = 0;
469 /* increase the ref count since we are about to store the pointer */
470 du->get();
471 duRefCountIncremented = true;
472 ids->du = du;
473
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 */
484 if (dest.sin4.sin_family != 0) {
485 ids->origDest = dest;
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);
496 try {
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) {
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;
506 du->release();
507 duRefCountIncremented = false;
508 --ss->outstanding;
509 }
510 ++ss->sendErrors;
511 ++g_stats.downstreamSendErrors;
512 du->status_code = 502;
513 return -1;
514 }
515 }
516 catch (const std::exception& e) {
517 if (duRefCountIncremented) {
518 du->release();
519 }
520 throw;
521 }
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());
524 }
525 catch(const std::exception& e) {
526 vinfolog("Got an error in DOH question thread while parsing a query from %s, id %d: %s", remote.toStringWithPort(), queryId, e.what());
527 du->status_code = 500;
528 return -1;
529 }
530
531 return 0;
532 }
533
534 /* called when a HTTP response is about to be sent */
535 static 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
577 static 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);
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
588 h2o_handler_t *handler = h2o_create_handler(pathconf, sizeof(*handler));
589 if (handler != nullptr) {
590 handler->on_req = on_req;
591 }
592
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 */
598 static 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
607 /* We allocate a DOHUnit and send it to dnsdistclient() function in the doh client thread
608 via a pipe */
609 static void doh_dispatch_query(DOHServerConfig* dsc, h2o_handler_t* self, h2o_req_t* req, std::string&& query, const ComboAddress& local, const ComboAddress& remote)
610 {
611 try {
612 uint16_t qtype;
613 DNSName qname(query.c_str(), query.size(), sizeof(dnsheader), false, &qtype);
614
615 auto du = std::unique_ptr<DOHUnit>(new DOHUnit);
616 du->req = req;
617 du->dest = local;
618 du->remote = remote;
619 du->rsock = dsc->dohresponsepair[0];
620 du->query = std::move(query);
621 du->qtype = qtype;
622 du->self = reinterpret_cast<DOHUnit**>(h2o_mem_alloc_shared(&req->pool, sizeof(*self), on_generator_dispose));
623 auto ptr = du.release();
624 *(ptr->self) = ptr;
625 try {
626 if(send(dsc->dohquerypair[0], &ptr, sizeof(ptr), 0) != sizeof(ptr)) {
627 ptr->release();
628 ptr = nullptr;
629 h2o_send_error_500(req, "Internal Server Error", "Internal Server Error", 0);
630 }
631 }
632 catch(...) {
633 ptr->release();
634 }
635 }
636 catch(const std::exception& e) {
637 vinfolog("Had error parsing DoH DNS packet from %s: %s", remote.toStringWithPort(), e.what());
638 h2o_send_error_400(req, "Bad Request", "The DNS query could not be parsed", 0);
639 }
640 }
641
642 /*
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.
646 */
647 static int doh_handler(h2o_handler_t *self, h2o_req_t *req)
648 try
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;
656 ComboAddress local;
657 h2o_socket_getpeername(sock, reinterpret_cast<struct sockaddr*>(&remote));
658 h2o_socket_getsockname(sock, reinterpret_cast<struct sockaddr*>(&local));
659 DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(req->conn->ctx->storage.entries[0].data);
660
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
669 if (h2o_socket_get_ssl_session_reused(sock) == 0) {
670 ++dsc->cs->tlsNewSessions;
671 }
672 else {
673 ++dsc->cs->tlsResumptions;
674 }
675
676 if(auto tlsversion = h2o_socket_get_ssl_protocol_version(sock)) {
677 if(!strcmp(tlsversion, "TLSv1.0"))
678 ++dsc->cs->tls10queries;
679 else if(!strcmp(tlsversion, "TLSv1.1"))
680 ++dsc->cs->tls11queries;
681 else if(!strcmp(tlsversion, "TLSv1.2"))
682 ++dsc->cs->tls12queries;
683 else if(!strcmp(tlsversion, "TLSv1.3"))
684 ++dsc->cs->tls13queries;
685 else
686 ++dsc->cs->tlsUnknownqueries;
687 }
688
689 string path(req->path.base, req->path.len);
690
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
697 for (const auto& entry : dsc->df->d_responsesMap) {
698 if (entry->matches(path)) {
699 const auto& customHeaders = entry->getHeaders();
700 handleResponse(*dsc->df, req, entry->getStatusCode(), entry->getContent(), customHeaders ? *customHeaders : dsc->df->d_customResponseHeaders, std::string(), false);
701 return 0;
702 }
703 }
704
705 if (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("POST"))) {
706 ++dsc->df->d_postqueries;
707 if(req->version >= 0x0200)
708 ++dsc->df->d_http2Stats.d_nbQueries;
709 else
710 ++dsc->df->d_http1Stats.d_nbQueries;
711
712 std::string query;
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 */
715 query.reserve(std::max(req->entity.len + 512, s_maxPacketCacheEntrySize));
716 query.assign(req->entity.base, req->entity.len);
717 doh_dispatch_query(dsc, self, req, std::move(query), local, remote);
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,"_", "/");
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 }
737
738 string decoded;
739 /* rough estimate so we hopefully don't need a need allocation later */
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 */
742 const size_t estimate = ((sdns.size() * 3) / 4);
743 decoded.reserve(std::max(estimate + 512, s_maxPacketCacheEntrySize));
744 if(B64Decode(sdns, decoded) < 0) {
745 h2o_send_error_400(req, "Bad Request", "Unable to decode BASE64-URL", 0);
746 ++dsc->df->d_badrequests;
747 return 0;
748 }
749 else {
750 ++dsc->df->d_getqueries;
751 if(req->version >= 0x0200)
752 ++dsc->df->d_http2Stats.d_nbQueries;
753 else
754 ++dsc->df->d_http1Stats.d_nbQueries;
755
756 doh_dispatch_query(dsc, self, req, std::move(decoded), local, remote);
757 }
758 }
759 else
760 {
761 vinfolog("HTTP request without DNS parameter: %s", req->path.base);
762 h2o_send_error_400(req, "Bad Request", "Unable to find the DNS parameter", 0);
763 ++dsc->df->d_badrequests;
764 return 0;
765 }
766 }
767 else {
768 h2o_send_error_400(req, "Bad Request", "Unable to parse the request", 0);
769 ++dsc->df->d_badrequests;
770 }
771 return 0;
772 }
773 catch(const exception& e)
774 {
775 errlog("DOH Handler function failed with error %s", e.what());
776 return 0;
777 }
778
779 HTTPHeaderRule::HTTPHeaderRule(const std::string& header, const std::string& regex)
780 : d_header(toLower(header)), d_regex(regex), d_visual("http[" + header+ "] ~ " + regex)
781 {
782 }
783
784 bool HTTPHeaderRule::matches(const DNSQuestion* dq) const
785 {
786 if (!dq->du) {
787 return false;
788 }
789
790 for (size_t i = 0; i < dq->du->req->headers.size; ++i) {
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
799 string HTTPHeaderRule::toString() const
800 {
801 return d_visual;
802 }
803
804 HTTPPathRule::HTTPPathRule(const std::string& path)
805 : d_path(path)
806 {
807
808 }
809
810 bool 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
824 string HTTPPathRule::toString() const
825 {
826 return "url path == " + d_path;
827 }
828
829 HTTPPathRegexRule::HTTPPathRegexRule(const std::string& regex): d_regex(regex), d_visual("http path ~ " + regex)
830 {
831 }
832
833 bool HTTPPathRegexRule::matches(const DNSQuestion* dq) const
834 {
835 if (!dq->du) {
836 return false;
837 }
838
839 return d_regex.match(dq->du->getHTTPPath());
840 }
841
842 string HTTPPathRegexRule::toString() const
843 {
844 return d_visual;
845 }
846
847 std::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
860 std::string DOHUnit::getHTTPPath() const
861 {
862 if (req->query_at == SIZE_MAX) {
863 return std::string(req->path.base, req->path.len);
864 }
865 else {
866 return std::string(req->path.base, req->query_at);
867 }
868 }
869
870 std::string DOHUnit::getHTTPHost() const
871 {
872 return std::string(req->authority.base, req->authority.len);
873 }
874
875 std::string DOHUnit::getHTTPScheme() const
876 {
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
884 std::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
894 void DOHUnit::setHTTPResponse(uint16_t statusCode, const std::string& body_, const std::string& contentType_)
895 {
896 status_code = statusCode;
897 response = body_;
898 contentType = contentType_;
899 }
900
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 */
904 static void dnsdistclient(int qsock, int rsock)
905 {
906 setThreadName("dnsdist/doh-cli");
907
908 for(;;) {
909 try {
910 DOHUnit* du = nullptr;
911 ssize_t got = recv(qsock, &du, sizeof(du), 0);
912 if (got < 0) {
913 warnlog("Error receiving internal DoH query: %s", strerror(errno));
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) {
938 du->status_code = 500;
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 }
944 }
945 du->release();
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
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 */
963 static void on_dnsdist(h2o_socket_t *listener, const char *err)
964 {
965 DOHUnit *du = nullptr;
966 DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(listener->data);
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;
979 du->release();
980 return;
981 }
982
983 *du->self = nullptr; // so we don't clean up again in on_generator_dispose
984
985 handleResponse(*dsc->df, du->req, du->status_code, du->response, dsc->df->d_customResponseHeaders, du->contentType, true);
986
987 du->release();
988 }
989
990 /* called when a TCP connection has been accepted, the TLS session has not been established */
991 static void on_accept(h2o_socket_t *listener, const char *err)
992 {
993 DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(listener->data);
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
1000 if ((sock = h2o_evloop_socket_accept(listener)) == nullptr) {
1001 return;
1002 }
1003
1004 ComboAddress remote;
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;
1009 sock->on_close.cb = on_socketclose;
1010 auto accept_ctx = dsc->accept_ctx->get();
1011 sock->on_close.data = dsc->accept_ctx;
1012 ++dsc->df->d_httpconnects;
1013 ++dsc->cs->tcpCurrentConnections;
1014 h2o_accept(accept_ctx, sock);
1015 }
1016
1017 static int create_listener(const ComboAddress& addr, std::shared_ptr<DOHServerConfig>& dsc, int fd)
1018 {
1019 auto sock = h2o_evloop_socket_create(dsc->h2o_ctx.loop, fd, H2O_SOCKET_FLAG_DONT_READ);
1020 sock->data = dsc.get();
1021 h2o_socket_read_start(sock, on_accept);
1022
1023 return 0;
1024 }
1025
1026 static 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
1035 static 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 {
1037 DOHAcceptContext* ctx = reinterpret_cast<DOHAcceptContext*>(libssl_get_ticket_key_callback_data(s));
1038 if (ctx == nullptr || !ctx->d_ticketKeys) {
1039 return -1;
1040 }
1041
1042 ctx->handleTicketsKeyRotation();
1043
1044 auto ret = libssl_ticket_key_callback(s, *ctx->d_ticketKeys, keyName, iv, ectx, hctx, enc);
1045 if (enc == 0) {
1046 if (ret == 0) {
1047 ++ctx->d_cs->tlsUnknownTicketKey;
1048 }
1049 else if (ret == 2) {
1050 ++ctx->d_cs->tlsInactiveTicketKey;
1051 }
1052 }
1053
1054 return ret;
1055 }
1056
1057 static void setupTLSContext(DOHAcceptContext& acceptCtx,
1058 TLSConfig& tlsConfig,
1059 TLSErrorCounters& counters)
1060 {
1061 if (tlsConfig.d_ciphers.empty()) {
1062 tlsConfig.d_ciphers = DOH_DEFAULT_CIPHERS;
1063 }
1064
1065 auto ctx = libssl_init_server_context(tlsConfig, acceptCtx.d_ocspResponses);
1066
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 }
1072
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 }
1077
1078 libssl_set_error_counters_callback(ctx, &counters);
1079
1080 if (!tlsConfig.d_keyLogFile.empty()) {
1081 acceptCtx.d_keyLogFile = libssl_set_key_log_file(ctx, tlsConfig.d_keyLogFile);
1082 }
1083
1084 h2o_ssl_register_alpn_protocols(ctx.get(), h2o_http2_alpn_protocols);
1085
1086 if (tlsConfig.d_ticketKeyFile.empty()) {
1087 acceptCtx.handleTicketsKeyRotation();
1088 }
1089 else {
1090 acceptCtx.loadTicketsKeys(tlsConfig.d_ticketKeyFile);
1091 }
1092
1093 auto nativeCtx = acceptCtx.get();
1094 nativeCtx->ssl_ctx = ctx.release();
1095 acceptCtx.release();
1096 }
1097
1098 static 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;
1103 ctx.d_ticketsKeyRotationDelay = dsc.df->d_tlsConfig.d_ticketsKeyRotationDelay;
1104
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 }
1114 }
1115 ctx.d_cs = dsc.cs;
1116 ctx.release();
1117 }
1118
1119 void DOHFrontend::rotateTicketsKey(time_t now)
1120 {
1121 if (d_dsc && d_dsc->accept_ctx) {
1122 d_dsc->accept_ctx->rotateTicketsKey(now);
1123 }
1124 }
1125
1126 void DOHFrontend::loadTicketsKeys(const std::string& keyFile)
1127 {
1128 if (d_dsc && d_dsc->accept_ctx) {
1129 d_dsc->accept_ctx->loadTicketsKeys(keyFile);
1130 }
1131 }
1132
1133 void DOHFrontend::handleTicketsKeyRotation()
1134 {
1135 if (d_dsc && d_dsc->accept_ctx) {
1136 d_dsc->accept_ctx->handleTicketsKeyRotation();
1137 }
1138 }
1139
1140 time_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 }
1147
1148 size_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();
1153 }
1154 return res;
1155 }
1156
1157 void DOHFrontend::reloadCertificates()
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();
1164 }
1165
1166 void DOHFrontend::setup()
1167 {
1168 registerOpenSSLUser();
1169
1170 d_dsc = std::make_shared<DOHServerConfig>(d_idleTimeout);
1171
1172 if (!d_tlsConfig.d_certKeyPairs.empty()) {
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 }
1181 }
1182 }
1183
1184 // this is the entrypoint from dnsdist.cc
1185 void dohThread(ClientState* cs)
1186 try
1187 {
1188 std::shared_ptr<DOHFrontend>& df = cs->dohFrontend;
1189 auto& dsc = df->d_dsc;
1190 dsc->cs = cs;
1191 dsc->df = cs->dohFrontend;
1192 dsc->h2o_config.server_name = h2o_iovec_init(df->d_serverTokens.c_str(), df->d_serverTokens.size());
1193
1194
1195 std::thread dnsdistThread(dnsdistclient, dsc->dohquerypair[1], dsc->dohresponsepair[0]);
1196 dnsdistThread.detach(); // gets us better error reporting
1197
1198 setThreadName("dnsdist/doh");
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);
1205 dsc->paths.insert(url);
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);
1212 dsc->h2o_ctx.storage.entries[0].data = dsc.get();
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);
1216 sock->data = dsc.get();
1217
1218 // this listens to responses from dnsdist to turn into http responses
1219 h2o_socket_read_start(sock, on_dnsdist);
1220
1221 setupAcceptContext(*dsc->accept_ctx, *dsc, false);
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
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 }
1240 catch(const std::exception& e) {
1241 throw runtime_error("DOH thread failed to launch: " + std::string(e.what()));
1242 }
1243 catch(...) {
1244 throw runtime_error("DOH thread failed to launch");
1245 }
1246
1247 #else /* HAVE_DNS_OVER_HTTPS */
1248
1249 void handleDOHTimeout(DOHUnit* oldDU)
1250 {
1251 }
1252
1253 #endif /* HAVE_DNS_OVER_HTTPS */