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