]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnsdistdist/doh.cc
22c3765ca83a85547ed97261b1fea7020e186184
[thirdparty/pdns.git] / pdns / dnsdistdist / doh.cc
1 #define H2O_USE_EPOLL 1
2
3 #include <errno.h>
4 #include <iostream>
5 #include <thread>
6
7 #include <boost/algorithm/string.hpp>
8 #include <h2o.h>
9 //#include <h2o/http1.h>
10 #include <h2o/http2.h>
11
12 #include <openssl/err.h>
13 #include <openssl/ssl.h>
14
15 #include "base64.hh"
16 #include "dnsname.hh"
17 #undef CERT
18 #include "dnsdist.hh"
19 #include "misc.hh"
20 #include "dns.hh"
21 #include "dolog.hh"
22 #include "dnsdist-ecs.hh"
23 #include "dnsdist-rules.hh"
24 #include "dnsdist-xpf.hh"
25 #include "libssl.hh"
26 #include "threadname.hh"
27
28 using namespace std;
29
30 /* So, how does this work. We use h2o for our http2 and TLS needs.
31 If the operator has configured multiple IP addresses to listen on,
32 we launch multiple h2o listener threads. We can hook in to multiple
33 URLs though on the same IP. There is no SNI yet (I think).
34
35 h2o is event driven, so we get callbacks if a new DNS query arrived.
36 When it does, we do some minimal parsing on it, and send it on to the
37 dnsdist worker thread which we also launched.
38
39 This dnsdist worker thread injects the query into the normal dnsdist flow
40 (as a datagram over a socketpair). The response also goes back over a
41 (different) socketpair, where we pick it up and deliver it back to h2o.
42
43 For coordination, we use the h2o socket multiplexer, which is sensitive to our
44 socketpair too.
45 */
46
47 /* h2o notes.
48 Paths and parameters etc just *happen* to be null-terminated in HTTP2.
49 They are not in HTTP1. So you MUST use the length field!
50 */
51
52 /* 'Intermediate' compatibility from https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29 */
53 #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"
54
55 class DOHAcceptContext
56 {
57 public:
58 DOHAcceptContext()
59 {
60 memset(&d_h2o_accept_ctx, 0, sizeof(d_h2o_accept_ctx));
61 }
62 DOHAcceptContext(const DOHAcceptContext&) = delete;
63 DOHAcceptContext& operator=(const DOHAcceptContext&) = delete;
64
65 h2o_accept_ctx_t* get()
66 {
67 ++d_refcnt;
68 return &d_h2o_accept_ctx;
69 }
70
71 void release()
72 {
73 --d_refcnt;
74 if (d_refcnt == 0) {
75 SSL_CTX_free(d_h2o_accept_ctx.ssl_ctx);
76 d_h2o_accept_ctx.ssl_ctx = nullptr;
77 delete this;
78 }
79 }
80
81 private:
82 h2o_accept_ctx_t d_h2o_accept_ctx;
83 std::atomic<uint64_t> d_refcnt{1};
84 };
85
86 // we create one of these per thread, and pass around a pointer to it
87 // through the bowels of h2o
88 struct DOHServerConfig
89 {
90 DOHServerConfig(uint32_t idleTimeout): accept_ctx(new DOHAcceptContext)
91 {
92 if(socketpair(AF_LOCAL, SOCK_DGRAM, 0, dohquerypair) < 0) {
93 unixDie("Creating a socket pair for DNS over HTTPS");
94 }
95
96 if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, dohresponsepair) < 0) {
97 close(dohquerypair[0]);
98 close(dohquerypair[1]);
99 unixDie("Creating a socket pair for DNS over HTTPS");
100 }
101
102 h2o_config_init(&h2o_config);
103 h2o_config.http2.idle_timeout = idleTimeout * 1000;
104 }
105 DOHServerConfig(const DOHServerConfig&) = delete;
106 DOHServerConfig& operator=(const DOHServerConfig&) = delete;
107
108 ~DOHServerConfig()
109 {
110 if (accept_ctx) {
111 accept_ctx->release();
112 }
113 }
114
115 LocalHolders holders;
116 h2o_globalconf_t h2o_config;
117 h2o_context_t h2o_ctx;
118 DOHAcceptContext* accept_ctx{nullptr};
119 ClientState* cs{nullptr};
120 std::shared_ptr<DOHFrontend> df{nullptr};
121 int dohquerypair[2]{-1,-1};
122 int dohresponsepair[2]{-1,-1};
123 };
124
125 static void on_socketclose(void *data)
126 {
127 DOHAcceptContext* ctx = reinterpret_cast<DOHAcceptContext*>(data);
128 ctx->release();
129 }
130
131 /* this duplicates way too much from the UDP handler. Sorry.
132 this function calls 'return -1' to drop a query without sending it
133 caller should make sure HTTPS thread hears of that
134 */
135
136 static int processDOHQuery(DOHUnit* du)
137 {
138 uint16_t queryId = 0;
139 ComboAddress remote;
140 try {
141 if(!du->req) {
142 // we got closed meanwhile. XXX small race condition here
143 return -1;
144 }
145 remote = du->remote;
146 DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(du->req->conn->ctx->storage.entries[0].data);
147 auto& holders = dsc->holders;
148 ClientState& cs = *dsc->cs;
149
150 if (du->query.size() < sizeof(dnsheader)) {
151 ++g_stats.nonCompliantQueries;
152 return -1;
153 }
154
155 if(!holders.acl->match(du->remote)) {
156 vinfolog("Query from %s (DoH) dropped because of ACL", du->remote.toStringWithPort());
157 ++g_stats.aclDrops;
158 return -1;
159 }
160
161 ++cs.queries;
162 ++g_stats.queries;
163
164 /* we need an accurate ("real") value for the response and
165 to store into the IDS, but not for insertion into the
166 rings for example */
167 struct timespec queryRealTime;
168 gettime(&queryRealTime, true);
169 uint16_t len = du->query.length();
170 /* allocate a bit more memory to be able to spoof the content,
171 or to add ECS without allocating a new buffer */
172 du->query.resize(du->query.size() + 512);
173 size_t bufferSize = du->query.size();
174 auto query = const_cast<char*>(du->query.c_str());
175 struct dnsheader* dh = reinterpret_cast<struct dnsheader*>(query);
176
177 if (!checkQueryHeaders(dh)) {
178 return -1; // drop
179 }
180
181 uint16_t qtype, qclass;
182 unsigned int consumed = 0;
183 DNSName qname(query, len, sizeof(dnsheader), false, &qtype, &qclass, &consumed);
184 DNSQuestion dq(&qname, qtype, qclass, consumed, &du->dest, &du->remote, dh, bufferSize, len, false, &queryRealTime);
185 dq.ednsAdded = du->ednsAdded;
186 dq.du = du;
187 queryId = ntohs(dh->id);
188
189 std::shared_ptr<DownstreamState> ss{nullptr};
190 auto result = processQuery(dq, cs, holders, ss);
191
192 if (result == ProcessQueryResult::Drop) {
193 return -1;
194 }
195
196 if (result == ProcessQueryResult::SendAnswer) {
197 du->query = std::string(reinterpret_cast<char*>(dq.dh), dq.len);
198 send(du->rsock, &du, sizeof(du), 0);
199 return 0;
200 }
201
202 if (result != ProcessQueryResult::PassToBackend || ss == nullptr) {
203 return -1;
204 }
205
206 unsigned int idOffset = (ss->idOffset++) % ss->idStates.size();
207 IDState* ids = &ss->idStates[idOffset];
208 ids->age = 0;
209 ids->du = du;
210
211 int oldFD = ids->origFD.exchange(cs.udpFD);
212 if(oldFD < 0) {
213 // if we are reusing, no change in outstanding
214 ++ss->outstanding;
215 }
216 else {
217 ++ss->reuseds;
218 ++g_stats.downstreamTimeouts;
219 }
220
221 ids->cs = &cs;
222 ids->origID = dh->id;
223 setIDStateFromDNSQuestion(*ids, dq, std::move(qname));
224
225 /* If we couldn't harvest the real dest addr, still
226 write down the listening addr since it will be useful
227 (especially if it's not an 'any' one).
228 We need to keep track of which one it is since we may
229 want to use the real but not the listening addr to reply.
230 */
231 if (du->dest.sin4.sin_family != 0) {
232 ids->origDest = du->dest;
233 ids->destHarvested = true;
234 }
235 else {
236 ids->origDest = cs.local;
237 ids->destHarvested = false;
238 }
239
240 dh->id = idOffset;
241
242 int fd = pickBackendSocketForSending(ss);
243 /* you can't touch du after this line, because it might already have been freed */
244 ssize_t ret = udpClientSendRequestToBackend(ss, fd, query, dq.len);
245
246 vinfolog("Got query for %s|%s from %s (https), relayed to %s", ids->qname.toString(), QType(ids->qtype).getName(), remote.toStringWithPort(), ss->getName());
247
248 if(ret < 0) {
249 ++ss->sendErrors;
250 ++g_stats.downstreamSendErrors;
251 }
252 }
253 catch(const std::exception& e) {
254 vinfolog("Got an error in DOH question thread while parsing a query from %s, id %d: %s", remote.toStringWithPort(), queryId, e.what());
255 return -1;
256 }
257 return 0;
258 }
259
260 static h2o_pathconf_t *register_handler(h2o_hostconf_t *hostconf, const char *path, int (*on_req)(h2o_handler_t *, h2o_req_t *))
261 {
262 h2o_pathconf_t *pathconf = h2o_config_register_path(hostconf, path, 0);
263 h2o_handler_t *handler = h2o_create_handler(pathconf, sizeof(*handler));
264 handler->on_req = on_req;
265 return pathconf;
266 }
267
268 /* this is called by h2o when our request dies.
269 We use this to signal to the 'du' that this req is no longer alive */
270 static void on_generator_dispose(void *_self)
271 {
272 DOHUnit** du = (DOHUnit**)_self;
273 if(*du) { // if 0, on_dnsdist cleaned up du already
274 // cout << "du "<<(void*)*du<<" containing req "<<(*du)->req<<" got killed"<<endl;
275 (*du)->req = nullptr;
276 }
277 }
278
279 static void doh_dispatch_query(DOHServerConfig* dsc, h2o_handler_t* self, h2o_req_t* req, std::string&& query, const ComboAddress& local, const ComboAddress& remote)
280 {
281 try {
282 uint16_t qtype;
283 DNSName qname(query.c_str(), query.size(), sizeof(dnsheader), false, &qtype);
284
285 auto du = std::unique_ptr<DOHUnit>(new DOHUnit);
286 du->req = req;
287 du->dest = local;
288 du->remote = remote;
289 du->rsock = dsc->dohresponsepair[0];
290 du->query = std::move(query);
291 du->qtype = qtype;
292 du->self = reinterpret_cast<DOHUnit**>(h2o_mem_alloc_shared(&req->pool, sizeof(*self), on_generator_dispose));
293 auto ptr = du.release();
294 *(ptr->self) = ptr;
295 try {
296 if(send(dsc->dohquerypair[0], &ptr, sizeof(ptr), 0) != sizeof(ptr)) {
297 delete ptr; // XXX but now what - will h2o time this out for us?
298 ptr = nullptr;
299 }
300 }
301 catch(...) {
302 delete ptr;
303 }
304 }
305 catch(const std::exception& e) {
306 vinfolog("Had error parsing DoH DNS packet from %s: %s", remote.toStringWithPort(), e.what());
307 h2o_send_error_400(req, "Bad Request", "The DNS query could not be parsed", 0);
308 }
309 }
310
311 /*
312 For GET, the base64url-encoded payload is in the 'dns' parameter, which might be the first parameter, or not.
313 For POST, the payload is the payload.
314 */
315 static int doh_handler(h2o_handler_t *self, h2o_req_t *req)
316 try
317 {
318 // g_logstream<<(void*)req<<" doh_handler"<<endl;
319 if(!req->conn->ctx->storage.size) {
320 return 0; // although we might was well crash on this
321 }
322 h2o_socket_t* sock = req->conn->callbacks->get_socket(req->conn);
323 ComboAddress remote;
324 ComboAddress local;
325 h2o_socket_getpeername(sock, reinterpret_cast<struct sockaddr*>(&remote));
326 h2o_socket_getsockname(sock, reinterpret_cast<struct sockaddr*>(&local));
327 DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(req->conn->ctx->storage.entries[0].data);
328
329 /* looks like we can't delete the Server: header with most versions of h2o */
330 h2o_set_header(&req->pool, &req->res.headers, H2O_TOKEN_SERVER, dsc->df->d_serverTokens.c_str(), dsc->df->d_serverTokens.size(), 1);
331
332 if(auto tlsversion = h2o_socket_get_ssl_protocol_version(sock)) {
333 if(!strcmp(tlsversion, "TLSv1.0"))
334 ++dsc->df->d_tls10queries;
335 else if(!strcmp(tlsversion, "TLSv1.1"))
336 ++dsc->df->d_tls11queries;
337 else if(!strcmp(tlsversion, "TLSv1.2"))
338 ++dsc->df->d_tls12queries;
339 else if(!strcmp(tlsversion, "TLSv1.3"))
340 ++dsc->df->d_tls13queries;
341 else
342 ++dsc->df->d_tlsUnknownqueries;
343 }
344
345 string path(req->path.base, req->path.len);
346
347 if (h2o_memis(req->method.base, req->method.len, H2O_STRLIT("POST"))) {
348 ++dsc->df->d_postqueries;
349 if(req->version >= 0x0200)
350 ++dsc->df->d_http2queries;
351 else
352 ++dsc->df->d_http1queries;
353
354 std::string query;
355 query.reserve(req->entity.len + 512);
356 query.assign(req->entity.base, req->entity.len);
357 doh_dispatch_query(dsc, self, req, std::move(query), local, remote);
358 }
359 else if(req->query_at != SIZE_MAX && (req->path.len - req->query_at > 5)) {
360 auto pos = path.find("?dns=");
361 if(pos == string::npos)
362 pos = path.find("&dns=");
363 if(pos != string::npos) {
364 // need to base64url decode this
365 string sdns(path.substr(pos+5));
366 boost::replace_all(sdns,"-", "+");
367 boost::replace_all(sdns,"_", "/");
368 // re-add padding that may have been missing
369 switch (sdns.size() % 4) {
370 case 2:
371 sdns.append(2, '=');
372 break;
373 case 3:
374 sdns.append(1, '=');
375 break;
376 }
377
378 string decoded;
379 /* rough estimate so we hopefully don't need a need allocation later */
380 decoded.reserve(((sdns.size() * 3) / 4) + 512);
381 if(B64Decode(sdns, decoded) < 0) {
382 h2o_send_error_400(req, "Bad Request", "Unable to decode BASE64-URL", 0);
383 ++dsc->df->d_badrequests;
384 return 0;
385 }
386 else {
387 ++dsc->df->d_getqueries;
388 if(req->version >= 0x0200)
389 ++dsc->df->d_http2queries;
390 else
391 ++dsc->df->d_http1queries;
392
393 doh_dispatch_query(dsc, self, req, std::move(decoded), local, remote);
394 }
395 }
396 else
397 {
398 vinfolog("HTTP request without DNS parameter: %s", req->path.base);
399 h2o_send_error_400(req, "Bad Request", "Unable to find the DNS parameter", 0);
400 ++dsc->df->d_badrequests;
401 return 0;
402 }
403 }
404 else {
405 h2o_send_error_400(req, "Bad Request", "Unable to parse the request", 0);
406 ++dsc->df->d_badrequests;
407 }
408 return 0;
409 }
410 catch(const exception& e)
411 {
412 errlog("DOH Handler function failed with error %s", e.what());
413 return 0;
414 }
415
416 HTTPHeaderRule::HTTPHeaderRule(const std::string& header, const std::string& regex)
417 : d_regex(regex)
418 {
419 d_header = toLower(header);
420 d_visual = "http[" + header+ "] ~ " + regex;
421
422 }
423 bool HTTPHeaderRule::matches(const DNSQuestion* dq) const
424 {
425 if(!dq->du) {
426 return false;
427 }
428
429 for (unsigned int i = 0; i != dq->du->req->headers.size; ++i) {
430 if(std::string(dq->du->req->headers.entries[i].name->base, dq->du->req->headers.entries[i].name->len) == d_header &&
431 d_regex.match(std::string(dq->du->req->headers.entries[i].value.base, dq->du->req->headers.entries[i].value.len))) {
432 return true;
433 }
434 }
435 return false;
436 }
437
438 string HTTPHeaderRule::toString() const
439 {
440 return d_visual;
441 }
442
443 HTTPPathRule::HTTPPathRule(const std::string& path)
444 : d_path(path)
445 {
446
447 }
448
449 bool HTTPPathRule::matches(const DNSQuestion* dq) const
450 {
451 if(!dq->du) {
452 return false;
453 }
454
455 if(dq->du->req->query_at == SIZE_MAX) {
456 return dq->du->req->path.base == d_path;
457 }
458 else {
459 return d_path.compare(0, d_path.size(), dq->du->req->path.base, dq->du->req->query_at) == 0;
460 }
461 }
462
463 string HTTPPathRule::toString() const
464 {
465 return "url path == " + d_path;
466 }
467
468 void dnsdistclient(int qsock, int rsock)
469 {
470 setThreadName("dnsdist/doh-cli");
471
472 for(;;) {
473 try {
474 DOHUnit* du = nullptr;
475 ssize_t got = recv(qsock, &du, sizeof(du), 0);
476 if (got < 0) {
477 warnlog("Error receving internal DoH query: %s", strerror(errno));
478 continue;
479 }
480 else if (static_cast<size_t>(got) < sizeof(du)) {
481 continue;
482 }
483
484 // if there was no EDNS, we add it with a large buffer size
485 // so we can use UDP to talk to the backend.
486 auto dh = const_cast<struct dnsheader*>(reinterpret_cast<const struct dnsheader*>(du->query.c_str()));
487
488 if(!dh->arcount) {
489 std::string res;
490 generateOptRR(std::string(), res, 4096, 0, false);
491
492 du->query += res;
493 dh = const_cast<struct dnsheader*>(reinterpret_cast<const struct dnsheader*>(du->query.c_str())); // may have reallocated
494 dh->arcount = htons(1);
495 du->ednsAdded = true;
496 }
497 else {
498 // we leave existing EDNS in place
499 }
500
501 if(processDOHQuery(du) < 0) {
502 du->error = true; // turns our drop into a 500
503 if(send(du->rsock, &du, sizeof(du), 0) != sizeof(du))
504 delete du; // XXX but now what - will h2o time this out for us?
505 }
506 }
507 catch(const std::exception& e) {
508 errlog("Error while processing query received over DoH: %s", e.what());
509 }
510 catch(...) {
511 errlog("Unspecified error while processing query received over DoH");
512 }
513 }
514 }
515
516 // called if h2o finds that dnsdist gave us an answer
517 static void on_dnsdist(h2o_socket_t *listener, const char *err)
518 {
519 DOHUnit *du = nullptr;
520 DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(listener->data);
521 ssize_t got = recv(dsc->dohresponsepair[1], &du, sizeof(du), 0);
522
523 if (got < 0) {
524 warnlog("Error reading a DOH internal response: %s", strerror(errno));
525 return;
526 }
527 else if (static_cast<size_t>(got) != sizeof(du)) {
528 return;
529 }
530
531 if(!du->req) { // it got killed in flight
532 // cout << "du "<<(void*)du<<" came back from dnsdist, but it was killed"<<endl;
533 delete du;
534 return;
535 }
536
537 *du->self = nullptr; // so we don't clean up again in on_generator_dispose
538 if(!du->error) {
539 ++dsc->df->d_validresponses;
540 du->req->res.status = 200;
541 du->req->res.reason = "OK";
542
543 h2o_add_header(&du->req->pool, &du->req->res.headers, H2O_TOKEN_CONTENT_TYPE, nullptr, H2O_STRLIT("application/dns-message"));
544
545 // struct dnsheader* dh = (struct dnsheader*)du->query.c_str();
546 // 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;
547
548 du->req->res.content_length = du->query.size();
549 h2o_send_inline(du->req, du->query.c_str(), du->query.size());
550 }
551 else {
552 h2o_send_error_500(du->req, "Internal Server Error", "Internal Server Error", 0);
553 ++dsc->df->d_errorresponses;
554 }
555 delete du;
556 }
557
558 static void on_accept(h2o_socket_t *listener, const char *err)
559 {
560 DOHServerConfig* dsc = reinterpret_cast<DOHServerConfig*>(listener->data);
561 h2o_socket_t *sock = nullptr;
562
563 if (err != nullptr) {
564 return;
565 }
566 // do some dnsdist rules here to filter based on IP address
567 if ((sock = h2o_evloop_socket_accept(listener)) == nullptr) {
568 return;
569 }
570
571 ComboAddress remote;
572
573 h2o_socket_getpeername(sock, reinterpret_cast<struct sockaddr*>(&remote));
574 // cout<<"New HTTP accept for client "<<remote.toStringWithPort()<<": "<< listener->data << endl;
575
576 sock->data = dsc;
577 sock->on_close.cb = on_socketclose;
578 auto accept_ctx = dsc->accept_ctx->get();
579 sock->on_close.data = dsc->accept_ctx;
580 ++dsc->df->d_httpconnects;
581 h2o_accept(accept_ctx, sock);
582 }
583
584 static int create_listener(const ComboAddress& addr, std::shared_ptr<DOHServerConfig>& dsc, int fd)
585 {
586 auto sock = h2o_evloop_socket_create(dsc->h2o_ctx.loop, fd, H2O_SOCKET_FLAG_DONT_READ);
587 sock->data = dsc.get();
588 h2o_socket_read_start(sock, on_accept);
589
590 return 0;
591 }
592
593 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)
594 {
595 auto ctx = std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)>(SSL_CTX_new(SSLv23_server_method()), SSL_CTX_free);
596
597 int sslOptions =
598 SSL_OP_NO_SSLv2 |
599 SSL_OP_NO_SSLv3 |
600 SSL_OP_NO_COMPRESSION |
601 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
602 SSL_OP_SINGLE_DH_USE |
603 SSL_OP_SINGLE_ECDH_USE;
604
605 SSL_CTX_set_options(ctx.get(), sslOptions);
606
607 #ifdef SSL_CTX_set_ecdh_auto
608 SSL_CTX_set_ecdh_auto(ctx.get(), 1);
609 #endif
610
611 /* load certificate and private key */
612 for (const auto& pair : pairs) {
613 if (SSL_CTX_use_certificate_chain_file(ctx.get(), pair.first.c_str()) != 1) {
614 ERR_print_errors_fp(stderr);
615 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);
616 }
617 if (SSL_CTX_use_PrivateKey_file(ctx.get(), pair.second.c_str(), SSL_FILETYPE_PEM) != 1) {
618 ERR_print_errors_fp(stderr);
619 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);
620 }
621 }
622
623 if (SSL_CTX_set_cipher_list(ctx.get(), ciphers.empty() == false ? ciphers.c_str() : DOH_DEFAULT_CIPHERS) != 1) {
624 throw std::runtime_error("Failed to setup SSL/TLS for DoH listener, DOH ciphers could not be set: " + ciphers);
625 }
626
627 #ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
628 if (!ciphers13.empty() && SSL_CTX_set_ciphersuites(ctx.get(), ciphers13.c_str()) != 1) {
629 throw std::runtime_error("Failed to setup SSL/TLS for DoH listener, DOH TLS 1.3 ciphers could not be set: " + ciphers13);
630 }
631 #endif /* HAVE_SSL_CTX_SET_CIPHERSUITES */
632
633 h2o_ssl_register_alpn_protocols(ctx.get(), h2o_http2_alpn_protocols);
634
635 return ctx;
636 }
637
638 static void setupAcceptContext(DOHAcceptContext& ctx, DOHServerConfig& dsc, bool setupTLS)
639 {
640 auto nativeCtx = ctx.get();
641 nativeCtx->ctx = &dsc.h2o_ctx;
642 nativeCtx->hosts = dsc.h2o_config.hosts;
643 if (setupTLS) {
644 auto tlsCtx = getTLSContext(dsc.df->d_certKeyPairs,
645 dsc.df->d_ciphers,
646 dsc.df->d_ciphers13);
647
648 nativeCtx->ssl_ctx = tlsCtx.release();
649 }
650
651 ctx.release();
652 }
653
654 void DOHFrontend::reloadCertificates()
655 {
656 auto newAcceptContext = std::unique_ptr<DOHAcceptContext>(new DOHAcceptContext());
657 setupAcceptContext(*newAcceptContext, *d_dsc, true);
658 DOHAcceptContext* oldCtx = d_dsc->accept_ctx;
659 d_dsc->accept_ctx = newAcceptContext.release();
660 oldCtx->release();
661 }
662
663 void DOHFrontend::setup()
664 {
665 registerOpenSSLUser();
666
667 d_dsc = std::make_shared<DOHServerConfig>(d_idleTimeout);
668
669 auto tlsCtx = getTLSContext(d_certKeyPairs,
670 d_ciphers,
671 d_ciphers13);
672
673 auto accept_ctx = d_dsc->accept_ctx->get();
674 accept_ctx->ssl_ctx = tlsCtx.release();
675 d_dsc->accept_ctx->release();
676 }
677
678 // this is the entrypoint from dnsdist.cc
679 void dohThread(ClientState* cs)
680 try
681 {
682 std::shared_ptr<DOHFrontend>& df = cs->dohFrontend;
683 auto& dsc = df->d_dsc;
684 dsc->cs = cs;
685 dsc->df = cs->dohFrontend;
686
687 std::thread dnsdistThread(dnsdistclient, dsc->dohquerypair[1], dsc->dohresponsepair[0]);
688 dnsdistThread.detach(); // gets us better error reporting
689
690 setThreadName("dnsdist/doh");
691 // I wonder if this registers an IP address.. I think it does
692 // this may mean we need to actually register a site "name" here and not the IP address
693 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);
694
695 for(const auto& url : df->d_urls) {
696 register_handler(hostconf, url.c_str(), doh_handler);
697 }
698
699 h2o_context_init(&dsc->h2o_ctx, h2o_evloop_create(), &dsc->h2o_config);
700
701 // in this complicated way we insert the DOHServerConfig pointer in there
702 h2o_vector_reserve(nullptr, &dsc->h2o_ctx.storage, 1);
703 dsc->h2o_ctx.storage.entries[0].data = dsc.get();
704 ++dsc->h2o_ctx.storage.size;
705
706 auto sock = h2o_evloop_socket_create(dsc->h2o_ctx.loop, dsc->dohresponsepair[1], H2O_SOCKET_FLAG_DONT_READ);
707 sock->data = dsc.get();
708
709 // this listens to responses from dnsdist to turn into http responses
710 h2o_socket_read_start(sock, on_dnsdist);
711
712 setupAcceptContext(*dsc->accept_ctx, *dsc, false);
713
714 if (create_listener(df->d_local, dsc, cs->tcpFD) != 0) {
715 throw std::runtime_error("DOH server failed to listen on " + df->d_local.toStringWithPort() + ": " + strerror(errno));
716 }
717
718 bool stop = false;
719 do {
720 int result = h2o_evloop_run(dsc->h2o_ctx.loop, INT32_MAX);
721 if (result == -1) {
722 if (errno != EINTR) {
723 errlog("Error in the DoH event loop: %s", strerror(errno));
724 stop = true;
725 }
726 }
727 }
728 while (stop == false);
729
730 }
731 catch(const std::exception& e) {
732 throw runtime_error("DOH thread failed to launch: " + std::string(e.what()));
733 }
734 catch(...) {
735 throw runtime_error("DOH thread failed to launch");
736 }