]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnsdistdist/doh.cc
dnsdist: Add HTTPStatusAction to return a specific HTTP response
[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 }
66 DOHAcceptContext(const DOHAcceptContext&) = delete;
67 DOHAcceptContext& operator=(const DOHAcceptContext&) = delete;
68
69 h2o_accept_ctx_t* get()
70 {
71 ++d_refcnt;
72 return &d_h2o_accept_ctx;
73 }
74
75 void release()
76 {
77 --d_refcnt;
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 private:
86 h2o_accept_ctx_t d_h2o_accept_ctx;
87 std::atomic<uint64_t> d_refcnt{1};
88 };
89
90 // we create one of these per thread, and pass around a pointer to it
91 // through the bowels of h2o
92 struct DOHServerConfig
93 {
94 DOHServerConfig(uint32_t idleTimeout): accept_ctx(new DOHAcceptContext)
95 {
96 if(socketpair(AF_LOCAL, SOCK_DGRAM, 0, dohquerypair) < 0) {
97 unixDie("Creating a socket pair for DNS over HTTPS");
98 }
99
100 if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, dohresponsepair) < 0) {
101 close(dohquerypair[0]);
102 close(dohquerypair[1]);
103 unixDie("Creating a socket pair for DNS over HTTPS");
104 }
105
106 h2o_config_init(&h2o_config);
107 h2o_config.http2.idle_timeout = idleTimeout * 1000;
108 }
109 DOHServerConfig(const DOHServerConfig&) = delete;
110 DOHServerConfig& operator=(const DOHServerConfig&) = delete;
111
112 ~DOHServerConfig()
113 {
114 if (accept_ctx) {
115 accept_ctx->release();
116 }
117 }
118
119 LocalHolders holders;
120 h2o_globalconf_t h2o_config;
121 h2o_context_t h2o_ctx;
122 DOHAcceptContext* accept_ctx{nullptr};
123 ClientState* cs{nullptr};
124 std::shared_ptr<DOHFrontend> df{nullptr};
125 int dohquerypair[2]{-1,-1};
126 int dohresponsepair[2]{-1,-1};
127 };
128
129 void handleDOHTimeout(DOHUnit* oldDU)
130 {
131 if (oldDU == nullptr) {
132 return;
133 }
134
135 /* we are about to erase an existing DU */
136 oldDU->status_code = 502;
137
138 if (send(oldDU->rsock, &oldDU, sizeof(oldDU), 0) != sizeof(oldDU)) {
139 delete oldDU;
140 oldDU = nullptr;
141 }
142 }
143
144 static void on_socketclose(void *data)
145 {
146 DOHAcceptContext* ctx = reinterpret_cast<DOHAcceptContext*>(data);
147 ctx->release();
148 }
149
150 /* this duplicates way too much from the UDP handler. Sorry.
151 this function calls 'return -1' to drop a query without sending it
152 caller should make sure HTTPS thread hears of that
153 */
154
155 static int processDOHQuery(DOHUnit* du)
156 {
157 uint16_t queryId = 0;
158 ComboAddress remote;
159 try {
160 if(!du->req) {
161 // we got closed meanwhile. XXX small race condition here
162 return -1;
163 }
164 remote = du->remote;
165 DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(du->req->conn->ctx->storage.entries[0].data);
166 auto& holders = dsc->holders;
167 ClientState& cs = *dsc->cs;
168
169 if (du->query.size() < sizeof(dnsheader)) {
170 ++g_stats.nonCompliantQueries;
171 du->status_code = 400;
172 return -1;
173 }
174
175 ++cs.queries;
176 ++g_stats.queries;
177
178 /* we need an accurate ("real") value for the response and
179 to store into the IDS, but not for insertion into the
180 rings for example */
181 struct timespec queryRealTime;
182 gettime(&queryRealTime, true);
183 uint16_t len = du->query.length();
184 /* allocate a bit more memory to be able to spoof the content,
185 or to add ECS without allocating a new buffer */
186 du->query.resize(du->query.size() + 512);
187 size_t bufferSize = du->query.size();
188 auto query = const_cast<char*>(du->query.c_str());
189 struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(query);
190
191 if (!checkQueryHeaders(dh)) {
192 du->status_code = 400;
193 return -1; // drop
194 }
195
196 uint16_t qtype, qclass;
197 unsigned int consumed = 0;
198 DNSName qname(query, len, sizeof(dnsheader), false, &qtype, &qclass, &consumed);
199 DNSQuestion dq(&qname, qtype, qclass, consumed, &du->dest, &du->remote, dh, bufferSize, len, false, &queryRealTime);
200 dq.ednsAdded = du->ednsAdded;
201 dq.du = du;
202 queryId = ntohs(dh->id);
203 #ifdef HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME
204 h2o_socket_t* sock = du->req->conn->callbacks->get_socket(du->req->conn);
205 const char * sni = h2o_socket_get_ssl_server_name(sock);
206 if (sni != nullptr) {
207 dq.sni = sni;
208 }
209 #endif /* HAVE_H2O_SOCKET_BET_SSL_SERVER_NAME */
210
211 std::shared_ptr<DownstreamState> ss{nullptr};
212 auto result = processQuery(dq, cs, holders, ss);
213
214 if (result == ProcessQueryResult::Drop) {
215 du->status_code = 403;
216 return -1;
217 }
218
219 if (result == ProcessQueryResult::SendAnswer) {
220 du->response = std::string(reinterpret_cast<char*>(dq.dh), dq.len);
221 send(du->rsock, &du, sizeof(du), 0);
222 return 0;
223 }
224
225 if (result != ProcessQueryResult::PassToBackend) {
226 du->status_code = 500;
227 return -1;
228 }
229
230 if (ss == nullptr) {
231 du->status_code = 502;
232 return -1;
233 }
234
235 ComboAddress dest = du->dest;
236 unsigned int idOffset = (ss->idOffset++) % ss->idStates.size();
237 IDState* ids = &ss->idStates[idOffset];
238 ids->age = 0;
239 DOHUnit* oldDU = nullptr;
240 if (ids->origFD != -1) {
241 /* that means that the state was in use, possibly with an allocated
242 DOHUnit that we will need to handle, but we can't touch it before
243 confirming that we now own this state */
244 oldDU = ids->du;
245 }
246
247 /* we atomically replace the value with 0, we now own this state */
248 int oldFD = ids->origFD.exchange(0);
249 if (oldFD < 0) {
250 /* the value was -1, meaning that the state was not in use.
251 we reset 'oldDU' because it might have still been in use when we read it. */
252 oldDU = nullptr;
253 ++ss->outstanding;
254 }
255 else {
256 ++ss->reuseds;
257 ++g_stats.downstreamTimeouts;
258 /* we are reusing a state, if there was an existing DOHUnit we need
259 to handle it because it's about to be overwritten. */
260 ids->du = nullptr;
261 handleDOHTimeout(oldDU);
262 }
263
264 ids->du = du;
265
266 ids->cs = &cs;
267 ids->origID = dh->id;
268 setIDStateFromDNSQuestion(*ids, dq, std::move(qname));
269
270 /* If we couldn't harvest the real dest addr, still
271 write down the listening addr since it will be useful
272 (especially if it's not an 'any' one).
273 We need to keep track of which one it is since we may
274 want to use the real but not the listening addr to reply.
275 */
276 if (dest.sin4.sin_family != 0) {
277 ids->origDest = dest;
278 ids->destHarvested = true;
279 }
280 else {
281 ids->origDest = cs.local;
282 ids->destHarvested = false;
283 }
284
285 dh->id = idOffset;
286
287 int fd = pickBackendSocketForSending(ss);
288 /* you can't touch du after this line, because it might already have been freed */
289 ssize_t ret = udpClientSendRequestToBackend(ss, fd, query, dq.len);
290
291 if(ret < 0) {
292 /* we are about to handle the error, make sure that
293 this pointer is not accessed when the state is cleaned,
294 but first check that it still belongs to us */
295 if (ids->origFD == 0 && ids->origFD.exchange(-1) == 0) {
296 ids->du = nullptr;
297 --ss->outstanding;
298 }
299 ++ss->sendErrors;
300 ++g_stats.downstreamSendErrors;
301 du->status_code = 502;
302 return -1;
303 }
304
305 vinfolog("Got query for %s|%s from %s (https), relayed to %s", ids->qname.toString(), QType(ids->qtype).getName(), remote.toStringWithPort(), ss->getName());
306 }
307 catch(const std::exception& e) {
308 vinfolog("Got an error in DOH question thread while parsing a query from %s, id %d: %s", remote.toStringWithPort(), queryId, e.what());
309 du->status_code = 500;
310 return -1;
311 }
312
313 return 0;
314 }
315
316 static void on_response_ready_cb(struct st_h2o_filter_t *self, h2o_req_t *req, h2o_ostream_t **slot)
317 {
318 if (req == nullptr) {
319 return;
320 }
321
322 DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(req->conn->ctx->storage.entries[0].data);
323
324 DOHFrontend::HTTPVersionStats* stats = nullptr;
325 if (req->version < 0x200) {
326 /* HTTP 1.x */
327 stats = &dsc->df->d_http1Stats;
328 }
329 else {
330 /* HTTP 2.0 */
331 stats = &dsc->df->d_http2Stats;
332 }
333
334 switch (req->res.status) {
335 case 200:
336 ++stats->d_nb200Responses;
337 break;
338 case 400:
339 ++stats->d_nb400Responses;
340 break;
341 case 403:
342 ++stats->d_nb403Responses;
343 break;
344 case 500:
345 ++stats->d_nb500Responses;
346 break;
347 case 502:
348 ++stats->d_nb502Responses;
349 break;
350 default:
351 ++stats->d_nbOtherResponses;
352 break;
353 }
354
355 h2o_setup_next_ostream(req, slot);
356 }
357
358 static h2o_pathconf_t *register_handler(h2o_hostconf_t *hostconf, const char *path, int (*on_req)(h2o_handler_t *, h2o_req_t *))
359 {
360 h2o_pathconf_t *pathconf = h2o_config_register_path(hostconf, path, 0);
361 if (pathconf == nullptr) {
362 return pathconf;
363 }
364 h2o_filter_t *filter = h2o_create_filter(pathconf, sizeof(*filter));
365 if (filter) {
366 filter->on_setup_ostream = on_response_ready_cb;
367 }
368
369 h2o_handler_t *handler = h2o_create_handler(pathconf, sizeof(*handler));
370 if (handler != nullptr) {
371 handler->on_req = on_req;
372 }
373
374 return pathconf;
375 }
376
377 /* this is called by h2o when our request dies.
378 We use this to signal to the 'du' that this req is no longer alive */
379 static void on_generator_dispose(void *_self)
380 {
381 DOHUnit** du = (DOHUnit**)_self;
382 if(*du) { // if 0, on_dnsdist cleaned up du already
383 // cout << "du "<<(void*)*du<<" containing req "<<(*du)->req<<" got killed"<<endl;
384 (*du)->req = nullptr;
385 }
386 }
387
388 static void doh_dispatch_query(DOHServerConfig* dsc, h2o_handler_t* self, h2o_req_t* req, std::string&& query, const ComboAddress& local, const ComboAddress& remote)
389 {
390 try {
391 uint16_t qtype;
392 DNSName qname(query.c_str(), query.size(), sizeof(dnsheader), false, &qtype);
393
394 auto du = std::unique_ptr<DOHUnit>(new DOHUnit);
395 du->req = req;
396 du->dest = local;
397 du->remote = remote;
398 du->rsock = dsc->dohresponsepair[0];
399 du->query = std::move(query);
400 du->qtype = qtype;
401 du->self = reinterpret_cast<DOHUnit**>(h2o_mem_alloc_shared(&req->pool, sizeof(*self), on_generator_dispose));
402 auto ptr = du.release();
403 *(ptr->self) = ptr;
404 try {
405 if(send(dsc->dohquerypair[0], &ptr, sizeof(ptr), 0) != sizeof(ptr)) {
406 delete ptr;
407 ptr = nullptr;
408 h2o_send_error_500(req, "Internal Server Error", "Internal Server Error", 0);
409 }
410 }
411 catch(...) {
412 delete ptr;
413 }
414 }
415 catch(const std::exception& e) {
416 vinfolog("Had error parsing DoH DNS packet from %s: %s", remote.toStringWithPort(), e.what());
417 h2o_send_error_400(req, "Bad Request", "The DNS query could not be parsed", 0);
418 }
419 }
420
421 /*
422 For GET, the base64url-encoded payload is in the 'dns' parameter, which might be the first parameter, or not.
423 For POST, the payload is the payload.
424 */
425 static int doh_handler(h2o_handler_t *self, h2o_req_t *req)
426 try
427 {
428 // g_logstream<<(void*)req<<" doh_handler"<<endl;
429 if(!req->conn->ctx->storage.size) {
430 return 0; // although we might was well crash on this
431 }
432 h2o_socket_t* sock = req->conn->callbacks->get_socket(req->conn);
433 ComboAddress remote;
434 ComboAddress local;
435 h2o_socket_getpeername(sock, reinterpret_cast<struct sockaddr*>(&remote));
436 h2o_socket_getsockname(sock, reinterpret_cast<struct sockaddr*>(&local));
437 DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(req->conn->ctx->storage.entries[0].data);
438
439 auto& holders = dsc->holders;
440 if (!holders.acl->match(remote)) {
441 ++g_stats.aclDrops;
442 vinfolog("Query from %s (DoH) dropped because of ACL", remote.toStringWithPort());
443 h2o_send_error_403(req, "Forbidden", "dns query not allowed because of ACL", 0);
444 return 0;
445 }
446
447 constexpr int overwrite_if_exists = 1;
448 constexpr int maybe_token = 1;
449 for (auto const& headerPair : dsc->df->d_customResponseHeaders) {
450 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);
451 }
452
453 if(auto tlsversion = h2o_socket_get_ssl_protocol_version(sock)) {
454 if(!strcmp(tlsversion, "TLSv1.0"))
455 ++dsc->df->d_tls10queries;
456 else if(!strcmp(tlsversion, "TLSv1.1"))
457 ++dsc->df->d_tls11queries;
458 else if(!strcmp(tlsversion, "TLSv1.2"))
459 ++dsc->df->d_tls12queries;
460 else if(!strcmp(tlsversion, "TLSv1.3"))
461 ++dsc->df->d_tls13queries;
462 else
463 ++dsc->df->d_tlsUnknownqueries;
464 }
465
466 string path(req->path.base, req->path.len);
467
468 if (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("POST"))) {
469 ++dsc->df->d_postqueries;
470 if(req->version >= 0x0200)
471 ++dsc->df->d_http2Stats.d_nbQueries;
472 else
473 ++dsc->df->d_http1Stats.d_nbQueries;
474
475 std::string query;
476 query.reserve(req->entity.len + 512);
477 query.assign(req->entity.base, req->entity.len);
478 doh_dispatch_query(dsc, self, req, std::move(query), local, remote);
479 }
480 else if(req->query_at != SIZE_MAX && (req->path.len - req->query_at > 5)) {
481 auto pos = path.find("?dns=");
482 if(pos == string::npos)
483 pos = path.find("&dns=");
484 if(pos != string::npos) {
485 // need to base64url decode this
486 string sdns(path.substr(pos+5));
487 boost::replace_all(sdns,"-", "+");
488 boost::replace_all(sdns,"_", "/");
489 // re-add padding that may have been missing
490 switch (sdns.size() % 4) {
491 case 2:
492 sdns.append(2, '=');
493 break;
494 case 3:
495 sdns.append(1, '=');
496 break;
497 }
498
499 string decoded;
500 /* rough estimate so we hopefully don't need a need allocation later */
501 decoded.reserve(((sdns.size() * 3) / 4) + 512);
502 if(B64Decode(sdns, decoded) < 0) {
503 h2o_send_error_400(req, "Bad Request", "Unable to decode BASE64-URL", 0);
504 ++dsc->df->d_badrequests;
505 return 0;
506 }
507 else {
508 ++dsc->df->d_getqueries;
509 if(req->version >= 0x0200)
510 ++dsc->df->d_http2Stats.d_nbQueries;
511 else
512 ++dsc->df->d_http1Stats.d_nbQueries;
513
514 doh_dispatch_query(dsc, self, req, std::move(decoded), local, remote);
515 }
516 }
517 else
518 {
519 vinfolog("HTTP request without DNS parameter: %s", req->path.base);
520 h2o_send_error_400(req, "Bad Request", "Unable to find the DNS parameter", 0);
521 ++dsc->df->d_badrequests;
522 return 0;
523 }
524 }
525 else {
526 h2o_send_error_400(req, "Bad Request", "Unable to parse the request", 0);
527 ++dsc->df->d_badrequests;
528 }
529 return 0;
530 }
531 catch(const exception& e)
532 {
533 errlog("DOH Handler function failed with error %s", e.what());
534 return 0;
535 }
536
537 HTTPHeaderRule::HTTPHeaderRule(const std::string& header, const std::string& regex)
538 : d_regex(regex)
539 {
540 d_header = toLower(header);
541 d_visual = "http[" + header+ "] ~ " + regex;
542
543 }
544
545 bool HTTPHeaderRule::matches(const DNSQuestion* dq) const
546 {
547 if(!dq->du) {
548 return false;
549 }
550
551 for (unsigned int i = 0; i != dq->du->req->headers.size; ++i) {
552 if(std::string(dq->du->req->headers.entries[i].name->base, dq->du->req->headers.entries[i].name->len) == d_header &&
553 d_regex.match(std::string(dq->du->req->headers.entries[i].value.base, dq->du->req->headers.entries[i].value.len))) {
554 return true;
555 }
556 }
557 return false;
558 }
559
560 string HTTPHeaderRule::toString() const
561 {
562 return d_visual;
563 }
564
565 HTTPPathRule::HTTPPathRule(const std::string& path)
566 : d_path(path)
567 {
568
569 }
570
571 bool HTTPPathRule::matches(const DNSQuestion* dq) const
572 {
573 if(!dq->du) {
574 return false;
575 }
576
577 if(dq->du->req->query_at == SIZE_MAX) {
578 return dq->du->req->path.base == d_path;
579 }
580 else {
581 return d_path.compare(0, d_path.size(), dq->du->req->path.base, dq->du->req->query_at) == 0;
582 }
583 }
584
585 string HTTPPathRule::toString() const
586 {
587 return "url path == " + d_path;
588 }
589
590 HTTPPathRegexRule::HTTPPathRegexRule(const std::string& regex): d_regex(regex), d_visual("http path ~ " + regex)
591 {
592 }
593
594 bool HTTPPathRegexRule::matches(const DNSQuestion* dq) const
595 {
596 if(!dq->du) {
597 return false;
598 }
599
600 if(dq->du->req->query_at == SIZE_MAX) {
601 return d_regex.match(std::string(dq->du->req->path.base, dq->du->req->path.len));
602 }
603 else {
604 cerr<<std::string(dq->du->req->path.base, dq->du->req->path.len - dq->du->req->query_at)<<endl;
605 return d_regex.match(std::string(dq->du->req->path.base, dq->du->req->path.len - dq->du->req->query_at));
606 }
607 }
608
609 string HTTPPathRegexRule::toString() const
610 {
611 return d_visual;
612 }
613
614 void DOHSetHTTPResponse(DOHUnit& du, uint16_t statusCode, const std::string& reason, const std::string& body)
615 {
616 du.status_code = statusCode;
617 du.reason = reason;
618 du.body = body;
619 }
620
621 void dnsdistclient(int qsock, int rsock)
622 {
623 setThreadName("dnsdist/doh-cli");
624
625 for(;;) {
626 try {
627 DOHUnit* du = nullptr;
628 ssize_t got = recv(qsock, &du, sizeof(du), 0);
629 if (got < 0) {
630 warnlog("Error receiving internal DoH query: %s", strerror(errno));
631 continue;
632 }
633 else if (static_cast<size_t>(got) < sizeof(du)) {
634 continue;
635 }
636
637 // if there was no EDNS, we add it with a large buffer size
638 // so we can use UDP to talk to the backend.
639 auto dh = const_cast<struct dnsheader*>(reinterpret_cast<const struct dnsheader*>(du->query.c_str()));
640
641 if(!dh->arcount) {
642 std::string res;
643 generateOptRR(std::string(), res, 4096, 0, false);
644
645 du->query += res;
646 dh = const_cast<struct dnsheader*>(reinterpret_cast<const struct dnsheader*>(du->query.c_str())); // may have reallocated
647 dh->arcount = htons(1);
648 du->ednsAdded = true;
649 }
650 else {
651 // we leave existing EDNS in place
652 }
653
654 if(processDOHQuery(du) < 0) {
655 du->status_code = 500;
656 if(send(du->rsock, &du, sizeof(du), 0) != sizeof(du))
657 delete du; // XXX but now what - will h2o time this out for us?
658 }
659 }
660 catch(const std::exception& e) {
661 errlog("Error while processing query received over DoH: %s", e.what());
662 }
663 catch(...) {
664 errlog("Unspecified error while processing query received over DoH");
665 }
666 }
667 }
668
669 // called if h2o finds that dnsdist gave us an answer
670 static void on_dnsdist(h2o_socket_t *listener, const char *err)
671 {
672 DOHUnit *du = nullptr;
673 DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(listener->data);
674 ssize_t got = recv(dsc->dohresponsepair[1], &du, sizeof(du), 0);
675
676 if (got < 0) {
677 warnlog("Error reading a DOH internal response: %s", strerror(errno));
678 return;
679 }
680 else if (static_cast<size_t>(got) != sizeof(du)) {
681 return;
682 }
683
684 if(!du->req) { // it got killed in flight
685 // cout << "du "<<(void*)du<<" came back from dnsdist, but it was killed"<<endl;
686 delete du;
687 return;
688 }
689
690 *du->self = nullptr; // so we don't clean up again in on_generator_dispose
691 if (du->status_code == 200) {
692 ++dsc->df->d_validresponses;
693 du->req->res.status = 200;
694 du->req->res.reason = "OK";
695
696 h2o_add_header(&du->req->pool, &du->req->res.headers, H2O_TOKEN_CONTENT_TYPE, nullptr, H2O_STRLIT("application/dns-message"));
697
698 // struct dnsheader* dh = (struct dnsheader*)du->query.c_str();
699 // cout<<"Attempt to send out "<<du->query.size()<<" bytes over https, TC="<<dh->tc<<", RCODE="<<dh->rcode<<", qtype="<<du->qtype<<", req="<<(void*)du->req<<endl;
700
701 du->req->res.content_length = du->response.size();
702 h2o_send_inline(du->req, du->response.c_str(), du->response.size());
703 }
704 else if (du->status_code >= 300 && du->status_code < 400) {
705 /* in that case the body is actually a URL */
706 h2o_send_redirect(du->req, du->status_code, du->reason.c_str(), du->body.c_str(), du->body.size());
707 ++dsc->df->d_redirectresponses;
708 }
709 else {
710 switch(du->status_code) {
711 case 400:
712 h2o_send_error_400(du->req, du->reason.empty() ? "Bad Request" : du->reason.c_str(), du->body.empty() ? "invalid DNS query" : du->body.c_str(), 0);
713 break;
714 case 403:
715 h2o_send_error_403(du->req, du->reason.empty() ? "Forbidden" : du->reason.c_str(), du->body.empty() ? "dns query not allowed" : du->body.c_str(), 0);
716 break;
717 case 502:
718 h2o_send_error_502(du->req, du->reason.empty() ? "Bad Gateway" : du->reason.c_str(), du->body.empty() ? "no downstream server available" : du->body.c_str(), 0);
719 break;
720 case 500:
721 /* fall-through */
722 default:
723 h2o_send_error_500(du->req, du->reason.empty() ? "Internal Server Error" : du->reason.c_str(), du->body.empty() ? "Internal Server Error" : du->body.c_str(), 0);
724 break;
725 }
726
727 ++dsc->df->d_errorresponses;
728 }
729
730 delete du;
731 }
732
733 static void on_accept(h2o_socket_t *listener, const char *err)
734 {
735 DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(listener->data);
736 h2o_socket_t *sock = nullptr;
737
738 if (err != nullptr) {
739 return;
740 }
741 // do some dnsdist rules here to filter based on IP address
742 if ((sock = h2o_evloop_socket_accept(listener)) == nullptr) {
743 return;
744 }
745
746 ComboAddress remote;
747 h2o_socket_getpeername(sock, reinterpret_cast<struct sockaddr*>(&remote));
748 // cout<<"New HTTP accept for client "<<remote.toStringWithPort()<<": "<< listener->data << endl;
749
750 sock->data = dsc;
751 sock->on_close.cb = on_socketclose;
752 auto accept_ctx = dsc->accept_ctx->get();
753 sock->on_close.data = dsc->accept_ctx;
754 ++dsc->df->d_httpconnects;
755 h2o_accept(accept_ctx, sock);
756 }
757
758 static int create_listener(const ComboAddress& addr, std::shared_ptr<DOHServerConfig>& dsc, int fd)
759 {
760 auto sock = h2o_evloop_socket_create(dsc->h2o_ctx.loop, fd, H2O_SOCKET_FLAG_DONT_READ);
761 sock->data = dsc.get();
762 h2o_socket_read_start(sock, on_accept);
763
764 return 0;
765 }
766
767 static std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> getTLSContext(const std::vector<std::pair<std::string, std::string>>& pairs, const std::string& ciphers, const std::string& ciphers13)
768 {
769 auto ctx = std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)>(SSL_CTX_new(SSLv23_server_method()), SSL_CTX_free);
770
771 int sslOptions =
772 SSL_OP_NO_SSLv2 |
773 SSL_OP_NO_SSLv3 |
774 SSL_OP_NO_COMPRESSION |
775 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
776 SSL_OP_SINGLE_DH_USE |
777 SSL_OP_SINGLE_ECDH_USE;
778
779 SSL_CTX_set_options(ctx.get(), sslOptions);
780
781 #ifdef SSL_CTX_set_ecdh_auto
782 SSL_CTX_set_ecdh_auto(ctx.get(), 1);
783 #endif
784
785 /* load certificate and private key */
786 for (const auto& pair : pairs) {
787 if (SSL_CTX_use_certificate_chain_file(ctx.get(), pair.first.c_str()) != 1) {
788 ERR_print_errors_fp(stderr);
789 throw std::runtime_error("Failed to setup SSL/TLS for DoH listener, an error occurred while trying to load the DOH server certificate file: " + pair.first);
790 }
791 if (SSL_CTX_use_PrivateKey_file(ctx.get(), pair.second.c_str(), SSL_FILETYPE_PEM) != 1) {
792 ERR_print_errors_fp(stderr);
793 throw std::runtime_error("Failed to setup SSL/TLS for DoH listener, an error occurred while trying to load the DOH server private key file: " + pair.second);
794 }
795 }
796
797 if (SSL_CTX_set_cipher_list(ctx.get(), ciphers.empty() == false ? ciphers.c_str() : DOH_DEFAULT_CIPHERS) != 1) {
798 throw std::runtime_error("Failed to setup SSL/TLS for DoH listener, DOH ciphers could not be set: " + ciphers);
799 }
800
801 #ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
802 if (!ciphers13.empty() && SSL_CTX_set_ciphersuites(ctx.get(), ciphers13.c_str()) != 1) {
803 throw std::runtime_error("Failed to setup SSL/TLS for DoH listener, DOH TLS 1.3 ciphers could not be set: " + ciphers13);
804 }
805 #endif /* HAVE_SSL_CTX_SET_CIPHERSUITES */
806
807 h2o_ssl_register_alpn_protocols(ctx.get(), h2o_http2_alpn_protocols);
808
809 return ctx;
810 }
811
812 static void setupAcceptContext(DOHAcceptContext& ctx, DOHServerConfig& dsc, bool setupTLS)
813 {
814 auto nativeCtx = ctx.get();
815 nativeCtx->ctx = &dsc.h2o_ctx;
816 nativeCtx->hosts = dsc.h2o_config.hosts;
817 if (setupTLS) {
818 auto tlsCtx = getTLSContext(dsc.df->d_certKeyPairs,
819 dsc.df->d_ciphers,
820 dsc.df->d_ciphers13);
821
822 nativeCtx->ssl_ctx = tlsCtx.release();
823 }
824
825 ctx.release();
826 }
827
828 void DOHFrontend::reloadCertificates()
829 {
830 auto newAcceptContext = std::unique_ptr<DOHAcceptContext>(new DOHAcceptContext());
831 setupAcceptContext(*newAcceptContext, *d_dsc, true);
832 DOHAcceptContext* oldCtx = d_dsc->accept_ctx;
833 d_dsc->accept_ctx = newAcceptContext.release();
834 oldCtx->release();
835 }
836
837 void DOHFrontend::setup()
838 {
839 registerOpenSSLUser();
840
841 d_dsc = std::make_shared<DOHServerConfig>(d_idleTimeout);
842
843 auto tlsCtx = getTLSContext(d_certKeyPairs,
844 d_ciphers,
845 d_ciphers13);
846
847 auto accept_ctx = d_dsc->accept_ctx->get();
848 accept_ctx->ssl_ctx = tlsCtx.release();
849 d_dsc->accept_ctx->release();
850 }
851
852 // this is the entrypoint from dnsdist.cc
853 void dohThread(ClientState* cs)
854 try
855 {
856 std::shared_ptr<DOHFrontend>& df = cs->dohFrontend;
857 auto& dsc = df->d_dsc;
858 dsc->cs = cs;
859 dsc->df = cs->dohFrontend;
860 dsc->h2o_config.server_name = h2o_iovec_init(df->d_serverTokens.c_str(), df->d_serverTokens.size());
861
862
863 std::thread dnsdistThread(dnsdistclient, dsc->dohquerypair[1], dsc->dohresponsepair[0]);
864 dnsdistThread.detach(); // gets us better error reporting
865
866 setThreadName("dnsdist/doh");
867 // I wonder if this registers an IP address.. I think it does
868 // this may mean we need to actually register a site "name" here and not the IP address
869 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);
870
871 for(const auto& url : df->d_urls) {
872 register_handler(hostconf, url.c_str(), doh_handler);
873 }
874
875 h2o_context_init(&dsc->h2o_ctx, h2o_evloop_create(), &dsc->h2o_config);
876
877 // in this complicated way we insert the DOHServerConfig pointer in there
878 h2o_vector_reserve(nullptr, &dsc->h2o_ctx.storage, 1);
879 dsc->h2o_ctx.storage.entries[0].data = dsc.get();
880 ++dsc->h2o_ctx.storage.size;
881
882 auto sock = h2o_evloop_socket_create(dsc->h2o_ctx.loop, dsc->dohresponsepair[1], H2O_SOCKET_FLAG_DONT_READ);
883 sock->data = dsc.get();
884
885 // this listens to responses from dnsdist to turn into http responses
886 h2o_socket_read_start(sock, on_dnsdist);
887
888 setupAcceptContext(*dsc->accept_ctx, *dsc, false);
889
890 if (create_listener(df->d_local, dsc, cs->tcpFD) != 0) {
891 throw std::runtime_error("DOH server failed to listen on " + df->d_local.toStringWithPort() + ": " + strerror(errno));
892 }
893
894 bool stop = false;
895 do {
896 int result = h2o_evloop_run(dsc->h2o_ctx.loop, INT32_MAX);
897 if (result == -1) {
898 if (errno != EINTR) {
899 errlog("Error in the DoH event loop: %s", strerror(errno));
900 stop = true;
901 }
902 }
903 }
904 while (stop == false);
905
906 }
907 catch(const std::exception& e) {
908 throw runtime_error("DOH thread failed to launch: " + std::string(e.what()));
909 }
910 catch(...) {
911 throw runtime_error("DOH thread failed to launch");
912 }
913
914 #else /* HAVE_DNS_OVER_HTTPS */
915
916 void handleDOHTimeout(DOHUnit* oldDU)
917 {
918 }
919
920 #endif /* HAVE_DNS_OVER_HTTPS */