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