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