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