4 #ifdef HAVE_DNS_OVER_HTTPS
5 #define H2O_USE_EPOLL 1
11 #include <boost/algorithm/string.hpp>
13 //#include <h2o/http1.h>
14 #include <h2o/http2.h>
16 #include <openssl/err.h>
17 #include <openssl/ssl.h>
26 #include "dnsdist-ecs.hh"
27 #include "dnsdist-rules.hh"
28 #include "dnsdist-xpf.hh"
30 #include "threadname.hh"
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).
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.
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.
47 For coordination, we use the h2o socket multiplexer, which is sensitive to our
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!
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"
59 class DOHAcceptContext
64 memset(&d_h2o_accept_ctx
, 0, sizeof(d_h2o_accept_ctx
));
65 d_rotatingTicketsKey
.clear();
67 DOHAcceptContext(const DOHAcceptContext
&) = delete;
68 DOHAcceptContext
& operator=(const DOHAcceptContext
&) = delete;
70 h2o_accept_ctx_t
* get()
73 return &d_h2o_accept_ctx
;
78 if (--d_refcnt
== 0) {
79 SSL_CTX_free(d_h2o_accept_ctx
.ssl_ctx
);
80 d_h2o_accept_ctx
.ssl_ctx
= nullptr;
85 void decrementConcurrentConnections()
87 if (d_cs
!= nullptr) {
88 --d_cs
->tcpCurrentConnections
;
92 time_t getNextTicketsKeyRotation() const
94 return d_ticketsKeyNextRotation
;
97 size_t getTicketsKeysCount() const
101 res
= d_ticketKeys
->getKeysCount();
106 void rotateTicketsKey(time_t now
)
112 d_ticketKeys
->rotateTicketsKey(now
);
114 if (d_ticketsKeyRotationDelay
> 0) {
115 d_ticketsKeyNextRotation
= now
+ d_ticketsKeyRotationDelay
;
119 void loadTicketsKeys(const std::string
& keyFile
)
124 d_ticketKeys
->loadTicketsKeys(keyFile
);
126 if (d_ticketsKeyRotationDelay
> 0) {
127 d_ticketsKeyNextRotation
= time(nullptr) + d_ticketsKeyRotationDelay
;
131 void handleTicketsKeyRotation()
133 if (d_ticketsKeyRotationDelay
== 0) {
137 time_t now
= time(nullptr);
138 if (now
> d_ticketsKeyNextRotation
) {
139 if (d_rotatingTicketsKey
.test_and_set()) {
140 /* someone is already rotating */
144 rotateTicketsKey(now
);
146 d_rotatingTicketsKey
.clear();
148 catch(const std::runtime_error
& e
) {
149 d_rotatingTicketsKey
.clear();
150 throw std::runtime_error(std::string("Error generating a new tickets key for TLS context:") + e
.what());
153 d_rotatingTicketsKey
.clear();
159 std::map
<int, std::string
> d_ocspResponses
;
160 std::unique_ptr
<OpenSSLTLSTicketKeysRing
> d_ticketKeys
{nullptr};
161 std::unique_ptr
<FILE, int(*)(FILE*)> d_keyLogFile
{nullptr, fclose
};
162 ClientState
* d_cs
{nullptr};
163 time_t d_ticketsKeyRotationDelay
{0};
166 h2o_accept_ctx_t d_h2o_accept_ctx
;
167 std::atomic
<uint64_t> d_refcnt
{1};
168 time_t d_ticketsKeyNextRotation
{0};
169 std::atomic_flag d_rotatingTicketsKey
;
172 // we create one of these per thread, and pass around a pointer to it
173 // through the bowels of h2o
174 struct DOHServerConfig
176 DOHServerConfig(uint32_t idleTimeout
): accept_ctx(new DOHAcceptContext
)
178 if(socketpair(AF_LOCAL
, SOCK_DGRAM
, 0, dohquerypair
) < 0) {
179 unixDie("Creating a socket pair for DNS over HTTPS");
182 if (socketpair(AF_LOCAL
, SOCK_DGRAM
, 0, dohresponsepair
) < 0) {
183 close(dohquerypair
[0]);
184 close(dohquerypair
[1]);
185 unixDie("Creating a socket pair for DNS over HTTPS");
188 h2o_config_init(&h2o_config
);
189 h2o_config
.http2
.idle_timeout
= idleTimeout
* 1000;
191 DOHServerConfig(const DOHServerConfig
&) = delete;
192 DOHServerConfig
& operator=(const DOHServerConfig
&) = delete;
197 accept_ctx
->release();
201 LocalHolders holders
;
202 std::unordered_set
<std::string
> paths
;
203 h2o_globalconf_t h2o_config
;
204 h2o_context_t h2o_ctx
;
205 DOHAcceptContext
* accept_ctx
{nullptr};
206 ClientState
* cs
{nullptr};
207 std::shared_ptr
<DOHFrontend
> df
{nullptr};
208 int dohquerypair
[2]{-1,-1};
209 int dohresponsepair
[2]{-1,-1};
212 void handleDOHTimeout(DOHUnit
* oldDU
)
214 if (oldDU
== nullptr) {
218 /* we are about to erase an existing DU */
219 oldDU
->status_code
= 502;
221 /* increase the ref counter before sending the pointer */
223 if (send(oldDU
->rsock
, &oldDU
, sizeof(oldDU
), 0) != sizeof(oldDU
)) {
230 static void on_socketclose(void *data
)
232 DOHAcceptContext
* ctx
= reinterpret_cast<DOHAcceptContext
*>(data
);
233 ctx
->decrementConcurrentConnections();
237 static const std::string
& getReasonFromStatusCode(uint16_t statusCode
)
239 /* no need to care too much about this, HTTP/2 has no 'reason' anyway */
240 static const std::unordered_map
<uint16_t, std::string
> reasons
= {
242 { 301, "Moved Permanently" },
244 { 303, "See Other" },
245 { 304, "Not Modified" },
246 { 305, "Use Proxy" },
247 { 306, "Switch Proxy" },
248 { 307, "Temporary Redirect" },
249 { 308, "Permanent Redirect" },
250 { 400, "Bad Request" },
251 { 401, "Unauthorized" },
252 { 402, "Payment Required" },
253 { 403, "Forbidden" },
254 { 404, "Not Found" },
255 { 405, "Method Not Allowed" },
256 { 406, "Not Acceptable" },
257 { 407, "Proxy Authentication Required" },
258 { 408, "Request Timeout" },
261 { 411, "Length Required" },
262 { 412, "Precondition Failed" },
263 { 413, "Payload Too Large" },
264 { 414, "URI Too Long" },
265 { 415, "Unsupported Media Type" },
266 { 416, "Range Not Satisfiable" },
267 { 417, "Expectation Failed" },
268 { 418, "I'm a teapot" },
269 { 451, "Unavailable For Legal Reasons" },
270 { 500, "Internal Server Error" },
271 { 501, "Not Implemented" },
272 { 502, "Bad Gateway" },
273 { 503, "Service Unavailable" },
274 { 504, "Gateway Timeout" },
275 { 505, "HTTP Version Not Supported" }
277 static const std::string unknown
= "Unknown";
279 const auto it
= reasons
.find(statusCode
);
280 if (it
== reasons
.end()) {
288 static void handleResponse(DOHFrontend
& df
, st_h2o_req_t
* req
, uint16_t statusCode
, const std::string
& response
, const std::vector
<std::pair
<std::string
, std::string
>>& customResponseHeaders
, const std::string
& contentType
, bool addContentType
)
290 constexpr int overwrite_if_exists
= 1;
291 constexpr int maybe_token
= 1;
292 for (auto const& headerPair
: customResponseHeaders
) {
293 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
);
296 if (statusCode
== 200) {
297 ++df
.d_validresponses
;
298 req
->res
.status
= 200;
300 if (addContentType
) {
301 if (contentType
.empty()) {
302 h2o_add_header(&req
->pool
, &req
->res
.headers
, H2O_TOKEN_CONTENT_TYPE
, nullptr, H2O_STRLIT("application/dns-message"));
305 /* we need to duplicate the header content because h2o keeps a pointer and we will be deleted before the response has been sent */
306 h2o_iovec_t ct
= h2o_strdup(&req
->pool
, contentType
.c_str(), contentType
.size());
307 h2o_add_header(&req
->pool
, &req
->res
.headers
, H2O_TOKEN_CONTENT_TYPE
, nullptr, ct
.base
, ct
.len
);
311 req
->res
.content_length
= response
.size();
312 h2o_send_inline(req
, response
.c_str(), response
.size());
314 else if (statusCode
>= 300 && statusCode
< 400) {
315 /* in that case the response is actually a URL */
316 /* we need to duplicate the URL because h2o uses it for the location header, keeping a pointer, and we will be deleted before the response has been sent */
317 h2o_iovec_t url
= h2o_strdup(&req
->pool
, response
.c_str(), response
.size());
318 h2o_send_redirect(req
, statusCode
, getReasonFromStatusCode(statusCode
).c_str(), url
.base
, url
.len
);
319 ++df
.d_redirectresponses
;
322 if (!response
.empty()) {
323 h2o_send_error_generic(req
, statusCode
, getReasonFromStatusCode(statusCode
).c_str(), response
.c_str(), H2O_SEND_ERROR_KEEP_HEADERS
);
328 h2o_send_error_400(req
, getReasonFromStatusCode(statusCode
).c_str(), "invalid DNS query" , 0);
331 h2o_send_error_403(req
, getReasonFromStatusCode(statusCode
).c_str(), "dns query not allowed", 0);
334 h2o_send_error_502(req
, getReasonFromStatusCode(statusCode
).c_str(), "no downstream server available", 0);
339 h2o_send_error_500(req
, getReasonFromStatusCode(statusCode
).c_str(), "Internal Server Error", 0);
344 ++df
.d_errorresponses
;
349 this function calls 'return -1' to drop a query without sending it
350 caller should make sure HTTPS thread hears of that
352 static int processDOHQuery(DOHUnit
* du
)
354 uint16_t queryId
= 0;
356 bool duRefCountIncremented
= false;
359 // we got closed meanwhile. XXX small race condition here
363 DOHServerConfig
* dsc
= reinterpret_cast<DOHServerConfig
*>(du
->req
->conn
->ctx
->storage
.entries
[0].data
);
364 auto& holders
= dsc
->holders
;
365 ClientState
& cs
= *dsc
->cs
;
367 if (du
->query
.size() < sizeof(dnsheader
)) {
368 ++g_stats
.nonCompliantQueries
;
369 du
->status_code
= 400;
376 /* we need an accurate ("real") value for the response and
377 to store into the IDS, but not for insertion into the
379 struct timespec queryRealTime
;
380 gettime(&queryRealTime
, true);
381 uint16_t len
= du
->query
.length();
382 /* We reserve at least 512 additional bytes to be able to add EDNS, but we also want
383 at least s_maxPacketCacheEntrySize bytes to be able to spoof the content or fill the answer from the packet cache */
384 du
->query
.resize(std::max(du
->query
.size() + 512, s_maxPacketCacheEntrySize
));
385 size_t bufferSize
= du
->query
.size();
386 auto query
= const_cast<char*>(du
->query
.c_str());
387 struct dnsheader
* dh
= reinterpret_cast<struct dnsheader
*>(query
);
389 if (!checkQueryHeaders(dh
)) {
390 du
->status_code
= 400;
394 uint16_t qtype
, qclass
;
395 unsigned int consumed
= 0;
396 DNSName
qname(query
, len
, sizeof(dnsheader
), false, &qtype
, &qclass
, &consumed
);
397 DNSQuestion
dq(&qname
, qtype
, qclass
, consumed
, &du
->dest
, &du
->remote
, dh
, bufferSize
, len
, false, &queryRealTime
);
398 dq
.ednsAdded
= du
->ednsAdded
;
400 queryId
= ntohs(dh
->id
);
401 #ifdef HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME
402 h2o_socket_t
* sock
= du
->req
->conn
->callbacks
->get_socket(du
->req
->conn
);
403 const char * sni
= h2o_socket_get_ssl_server_name(sock
);
404 if (sni
!= nullptr) {
407 #endif /* HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME */
409 std::shared_ptr
<DownstreamState
> ss
{nullptr};
410 auto result
= processQuery(dq
, cs
, holders
, ss
);
412 if (result
== ProcessQueryResult::Drop
) {
413 du
->status_code
= 403;
417 if (result
== ProcessQueryResult::SendAnswer
) {
418 if (du
->response
.empty()) {
419 du
->response
= std::string(reinterpret_cast<char*>(dq
.dh
), dq
.len
);
421 /* increase the ref counter before sending the pointer */
423 if (send(du
->rsock
, &du
, sizeof(du
), 0) != sizeof(du
)) {
429 if (result
!= ProcessQueryResult::PassToBackend
) {
430 du
->status_code
= 500;
435 du
->status_code
= 502;
439 ComboAddress dest
= du
->dest
;
440 unsigned int idOffset
= (ss
->idOffset
++) % ss
->idStates
.size();
441 IDState
* ids
= &ss
->idStates
[idOffset
];
443 DOHUnit
* oldDU
= nullptr;
444 if (ids
->isInUse()) {
445 /* that means that the state was in use, possibly with an allocated
446 DOHUnit that we will need to handle, but we can't touch it before
447 confirming that we now own this state */
451 /* we atomically replace the value, we now own this state */
452 int64_t generation
= ids
->generation
++;
453 if (!ids
->markAsUsed(generation
)) {
454 /* the state was not in use.
455 we reset 'oldDU' because it might have still been in use when we read it. */
461 /* we are reusing a state, no change in outstanding but if there was an existing DOHUnit we need
462 to handle it because it's about to be overwritten. */
464 ++g_stats
.downstreamTimeouts
;
465 handleDOHTimeout(oldDU
);
469 /* increase the ref count since we are about to store the pointer */
471 duRefCountIncremented
= true;
475 ids
->origID
= dh
->id
;
476 setIDStateFromDNSQuestion(*ids
, dq
, std::move(qname
));
478 /* If we couldn't harvest the real dest addr, still
479 write down the listening addr since it will be useful
480 (especially if it's not an 'any' one).
481 We need to keep track of which one it is since we may
482 want to use the real but not the listening addr to reply.
484 if (dest
.sin4
.sin_family
!= 0) {
485 ids
->origDest
= dest
;
486 ids
->destHarvested
= true;
489 ids
->origDest
= cs
.local
;
490 ids
->destHarvested
= false;
495 int fd
= pickBackendSocketForSending(ss
);
497 /* you can't touch du after this line, because it might already have been freed */
498 ssize_t ret
= udpClientSendRequestToBackend(ss
, fd
, query
, dq
.len
);
501 /* we are about to handle the error, make sure that
502 this pointer is not accessed when the state is cleaned,
503 but first check that it still belongs to us */
504 if (ids
->tryMarkUnused(generation
)) {
507 duRefCountIncremented
= false;
511 ++g_stats
.downstreamSendErrors
;
512 du
->status_code
= 502;
516 catch (const std::exception
& e
) {
517 if (duRefCountIncremented
) {
523 vinfolog("Got query for %s|%s from %s (https), relayed to %s", ids
->qname
.toString(), QType(ids
->qtype
).getName(), remote
.toStringWithPort(), ss
->getName());
525 catch(const std::exception
& e
) {
526 vinfolog("Got an error in DOH question thread while parsing a query from %s, id %d: %s", remote
.toStringWithPort(), queryId
, e
.what());
527 du
->status_code
= 500;
534 /* called when a HTTP response is about to be sent */
535 static void on_response_ready_cb(struct st_h2o_filter_t
*self
, h2o_req_t
*req
, h2o_ostream_t
**slot
)
537 if (req
== nullptr) {
541 DOHServerConfig
* dsc
= reinterpret_cast<DOHServerConfig
*>(req
->conn
->ctx
->storage
.entries
[0].data
);
543 DOHFrontend::HTTPVersionStats
* stats
= nullptr;
544 if (req
->version
< 0x200) {
546 stats
= &dsc
->df
->d_http1Stats
;
550 stats
= &dsc
->df
->d_http2Stats
;
553 switch (req
->res
.status
) {
555 ++stats
->d_nb200Responses
;
558 ++stats
->d_nb400Responses
;
561 ++stats
->d_nb403Responses
;
564 ++stats
->d_nb500Responses
;
567 ++stats
->d_nb502Responses
;
570 ++stats
->d_nbOtherResponses
;
574 h2o_setup_next_ostream(req
, slot
);
577 static h2o_pathconf_t
*register_handler(h2o_hostconf_t
*hostconf
, const char *path
, int (*on_req
)(h2o_handler_t
*, h2o_req_t
*))
579 h2o_pathconf_t
*pathconf
= h2o_config_register_path(hostconf
, path
, 0);
580 if (pathconf
== nullptr) {
583 h2o_filter_t
*filter
= h2o_create_filter(pathconf
, sizeof(*filter
));
585 filter
->on_setup_ostream
= on_response_ready_cb
;
588 h2o_handler_t
*handler
= h2o_create_handler(pathconf
, sizeof(*handler
));
589 if (handler
!= nullptr) {
590 handler
->on_req
= on_req
;
596 /* this is called by h2o when our request dies.
597 We use this to signal to the 'du' that this req is no longer alive */
598 static void on_generator_dispose(void *_self
)
600 DOHUnit
** du
= (DOHUnit
**)_self
;
601 if(*du
) { // if 0, on_dnsdist cleaned up du already
602 // cout << "du "<<(void*)*du<<" containing req "<<(*du)->req<<" got killed"<<endl;
603 (*du
)->req
= nullptr;
607 /* We allocate a DOHUnit and send it to dnsdistclient() function in the doh client thread
609 static void doh_dispatch_query(DOHServerConfig
* dsc
, h2o_handler_t
* self
, h2o_req_t
* req
, std::string
&& query
, const ComboAddress
& local
, const ComboAddress
& remote
)
613 DNSName
qname(query
.c_str(), query
.size(), sizeof(dnsheader
), false, &qtype
);
615 auto du
= std::unique_ptr
<DOHUnit
>(new DOHUnit
);
619 du
->rsock
= dsc
->dohresponsepair
[0];
620 du
->query
= std::move(query
);
622 du
->self
= reinterpret_cast<DOHUnit
**>(h2o_mem_alloc_shared(&req
->pool
, sizeof(*self
), on_generator_dispose
));
623 auto ptr
= du
.release();
626 if(send(dsc
->dohquerypair
[0], &ptr
, sizeof(ptr
), 0) != sizeof(ptr
)) {
629 h2o_send_error_500(req
, "Internal Server Error", "Internal Server Error", 0);
636 catch(const std::exception
& e
) {
637 vinfolog("Had error parsing DoH DNS packet from %s: %s", remote
.toStringWithPort(), e
.what());
638 h2o_send_error_400(req
, "Bad Request", "The DNS query could not be parsed", 0);
643 A query has been parsed by h2o.
644 For GET, the base64url-encoded payload is in the 'dns' parameter, which might be the first parameter, or not.
645 For POST, the payload is the payload.
647 static int doh_handler(h2o_handler_t
*self
, h2o_req_t
*req
)
650 // g_logstream<<(void*)req<<" doh_handler"<<endl;
651 if(!req
->conn
->ctx
->storage
.size
) {
652 return 0; // although we might was well crash on this
654 h2o_socket_t
* sock
= req
->conn
->callbacks
->get_socket(req
->conn
);
657 h2o_socket_getpeername(sock
, reinterpret_cast<struct sockaddr
*>(&remote
));
658 h2o_socket_getsockname(sock
, reinterpret_cast<struct sockaddr
*>(&local
));
659 DOHServerConfig
* dsc
= reinterpret_cast<DOHServerConfig
*>(req
->conn
->ctx
->storage
.entries
[0].data
);
661 auto& holders
= dsc
->holders
;
662 if (!holders
.acl
->match(remote
)) {
664 vinfolog("Query from %s (DoH) dropped because of ACL", remote
.toStringWithPort());
665 h2o_send_error_403(req
, "Forbidden", "dns query not allowed because of ACL", 0);
669 if (h2o_socket_get_ssl_session_reused(sock
) == 0) {
670 ++dsc
->cs
->tlsNewSessions
;
673 ++dsc
->cs
->tlsResumptions
;
676 if(auto tlsversion
= h2o_socket_get_ssl_protocol_version(sock
)) {
677 if(!strcmp(tlsversion
, "TLSv1.0"))
678 ++dsc
->cs
->tls10queries
;
679 else if(!strcmp(tlsversion
, "TLSv1.1"))
680 ++dsc
->cs
->tls11queries
;
681 else if(!strcmp(tlsversion
, "TLSv1.2"))
682 ++dsc
->cs
->tls12queries
;
683 else if(!strcmp(tlsversion
, "TLSv1.3"))
684 ++dsc
->cs
->tls13queries
;
686 ++dsc
->cs
->tlsUnknownqueries
;
689 string
path(req
->path
.base
, req
->path
.len
);
691 string
pathOnly(req
->path_normalized
.base
, req
->path_normalized
.len
);
692 if (dsc
->paths
.count(pathOnly
) == 0) {
693 h2o_send_error_404(req
, "Not Found", "there is no endpoint configured for this path", 0);
697 for (const auto& entry
: dsc
->df
->d_responsesMap
) {
698 if (entry
->matches(path
)) {
699 const auto& customHeaders
= entry
->getHeaders();
700 handleResponse(*dsc
->df
, req
, entry
->getStatusCode(), entry
->getContent(), customHeaders
? *customHeaders
: dsc
->df
->d_customResponseHeaders
, std::string(), false);
705 if (h2o_memis(req
->method
.base
, req
->method
.len
, H2O_STRLIT("POST"))) {
706 ++dsc
->df
->d_postqueries
;
707 if(req
->version
>= 0x0200)
708 ++dsc
->df
->d_http2Stats
.d_nbQueries
;
710 ++dsc
->df
->d_http1Stats
.d_nbQueries
;
713 /* We reserve at least 512 additional bytes to be able to add EDNS, but we also want
714 at least s_maxPacketCacheEntrySize bytes to be able to fill the answer from the packet cache */
715 query
.reserve(std::max(req
->entity
.len
+ 512, s_maxPacketCacheEntrySize
));
716 query
.assign(req
->entity
.base
, req
->entity
.len
);
717 doh_dispatch_query(dsc
, self
, req
, std::move(query
), local
, remote
);
719 else if(req
->query_at
!= SIZE_MAX
&& (req
->path
.len
- req
->query_at
> 5)) {
720 auto pos
= path
.find("?dns=");
721 if(pos
== string::npos
)
722 pos
= path
.find("&dns=");
723 if(pos
!= string::npos
) {
724 // need to base64url decode this
725 string
sdns(path
.substr(pos
+5));
726 boost::replace_all(sdns
,"-", "+");
727 boost::replace_all(sdns
,"_", "/");
728 // re-add padding that may have been missing
729 switch (sdns
.size() % 4) {
739 /* rough estimate so we hopefully don't need a need allocation later */
740 /* We reserve at least 512 additional bytes to be able to add EDNS, but we also want
741 at least s_maxPacketCacheEntrySize bytes to be able to fill the answer from the packet cache */
742 const size_t estimate
= ((sdns
.size() * 3) / 4);
743 decoded
.reserve(std::max(estimate
+ 512, s_maxPacketCacheEntrySize
));
744 if(B64Decode(sdns
, decoded
) < 0) {
745 h2o_send_error_400(req
, "Bad Request", "Unable to decode BASE64-URL", 0);
746 ++dsc
->df
->d_badrequests
;
750 ++dsc
->df
->d_getqueries
;
751 if(req
->version
>= 0x0200)
752 ++dsc
->df
->d_http2Stats
.d_nbQueries
;
754 ++dsc
->df
->d_http1Stats
.d_nbQueries
;
756 doh_dispatch_query(dsc
, self
, req
, std::move(decoded
), local
, remote
);
761 vinfolog("HTTP request without DNS parameter: %s", req
->path
.base
);
762 h2o_send_error_400(req
, "Bad Request", "Unable to find the DNS parameter", 0);
763 ++dsc
->df
->d_badrequests
;
768 h2o_send_error_400(req
, "Bad Request", "Unable to parse the request", 0);
769 ++dsc
->df
->d_badrequests
;
773 catch(const exception
& e
)
775 errlog("DOH Handler function failed with error %s", e
.what());
779 HTTPHeaderRule::HTTPHeaderRule(const std::string
& header
, const std::string
& regex
)
780 : d_header(toLower(header
)), d_regex(regex
), d_visual("http[" + header
+ "] ~ " + regex
)
784 bool HTTPHeaderRule::matches(const DNSQuestion
* dq
) const
790 for (size_t i
= 0; i
< dq
->du
->req
->headers
.size
; ++i
) {
791 if(std::string(dq
->du
->req
->headers
.entries
[i
].name
->base
, dq
->du
->req
->headers
.entries
[i
].name
->len
) == d_header
&&
792 d_regex
.match(std::string(dq
->du
->req
->headers
.entries
[i
].value
.base
, dq
->du
->req
->headers
.entries
[i
].value
.len
))) {
799 string
HTTPHeaderRule::toString() const
804 HTTPPathRule::HTTPPathRule(const std::string
& path
)
810 bool HTTPPathRule::matches(const DNSQuestion
* dq
) const
816 if(dq
->du
->req
->query_at
== SIZE_MAX
) {
817 return dq
->du
->req
->path
.base
== d_path
;
820 return d_path
.compare(0, d_path
.size(), dq
->du
->req
->path
.base
, dq
->du
->req
->query_at
) == 0;
824 string
HTTPPathRule::toString() const
826 return "url path == " + d_path
;
829 HTTPPathRegexRule::HTTPPathRegexRule(const std::string
& regex
): d_regex(regex
), d_visual("http path ~ " + regex
)
833 bool HTTPPathRegexRule::matches(const DNSQuestion
* dq
) const
839 return d_regex
.match(dq
->du
->getHTTPPath());
842 string
HTTPPathRegexRule::toString() const
847 std::unordered_map
<std::string
, std::string
> DOHUnit::getHTTPHeaders() const
849 std::unordered_map
<std::string
, std::string
> results
;
850 results
.reserve(req
->headers
.size
);
852 for (size_t i
= 0; i
< req
->headers
.size
; ++i
) {
853 results
.insert({std::string(req
->headers
.entries
[i
].name
->base
, req
->headers
.entries
[i
].name
->len
),
854 std::string(req
->headers
.entries
[i
].value
.base
, req
->headers
.entries
[i
].value
.len
)});
860 std::string
DOHUnit::getHTTPPath() const
862 if (req
->query_at
== SIZE_MAX
) {
863 return std::string(req
->path
.base
, req
->path
.len
);
866 return std::string(req
->path
.base
, req
->query_at
);
870 std::string
DOHUnit::getHTTPHost() const
872 return std::string(req
->authority
.base
, req
->authority
.len
);
875 std::string
DOHUnit::getHTTPScheme() const
877 if (req
->scheme
== nullptr) {
878 return std::string();
881 return std::string(req
->scheme
->name
.base
, req
->scheme
->name
.len
);
884 std::string
DOHUnit::getHTTPQueryString() const
886 if (req
->query_at
== SIZE_MAX
) {
887 return std::string();
890 return std::string(req
->path
.base
+ req
->query_at
, req
->path
.len
- req
->query_at
);
894 void DOHUnit::setHTTPResponse(uint16_t statusCode
, const std::string
& body_
, const std::string
& contentType_
)
896 status_code
= statusCode
;
898 contentType
= contentType_
;
901 /* query has been parsed by h2o, which called doh_handler() in the main DoH thread.
902 In order not to blockfor long, doh_handler() called doh_dispatch_query() which allocated
903 a DOHUnit object and passed it to us */
904 static void dnsdistclient(int qsock
, int rsock
)
906 setThreadName("dnsdist/doh-cli");
910 DOHUnit
* du
= nullptr;
911 ssize_t got
= recv(qsock
, &du
, sizeof(du
), 0);
913 warnlog("Error receiving internal DoH query: %s", strerror(errno
));
916 else if (static_cast<size_t>(got
) < sizeof(du
)) {
920 // if there was no EDNS, we add it with a large buffer size
921 // so we can use UDP to talk to the backend.
922 auto dh
= const_cast<struct dnsheader
*>(reinterpret_cast<const struct dnsheader
*>(du
->query
.c_str()));
926 generateOptRR(std::string(), res
, 4096, 0, false);
929 dh
= const_cast<struct dnsheader
*>(reinterpret_cast<const struct dnsheader
*>(du
->query
.c_str())); // may have reallocated
930 dh
->arcount
= htons(1);
931 du
->ednsAdded
= true;
934 // we leave existing EDNS in place
937 if(processDOHQuery(du
) < 0) {
938 du
->status_code
= 500;
939 /* increase the ref count before sending the pointer */
941 if(send(du
->rsock
, &du
, sizeof(du
), 0) != sizeof(du
)) {
942 du
->release(); // XXX but now what - will h2o time this out for us?
947 catch(const std::exception
& e
) {
948 errlog("Error while processing query received over DoH: %s", e
.what());
951 errlog("Unspecified error while processing query received over DoH");
956 /* called if h2o finds that dnsdist gave us an answer by writing into
957 the dohresponsepair[0] side of the pipe so from:
958 - handleDOHTimeout() when we did not get a response fast enough (called
959 either from the health check thread (active) or from the frontend ones (reused))
960 - dnsdistclient (error 500 because processDOHQuery() returned a negative value)
961 - processDOHQuery (self-answered queries)
963 static void on_dnsdist(h2o_socket_t
*listener
, const char *err
)
965 DOHUnit
*du
= nullptr;
966 DOHServerConfig
* dsc
= reinterpret_cast<DOHServerConfig
*>(listener
->data
);
967 ssize_t got
= recv(dsc
->dohresponsepair
[1], &du
, sizeof(du
), 0);
970 warnlog("Error reading a DOH internal response: %s", strerror(errno
));
973 else if (static_cast<size_t>(got
) != sizeof(du
)) {
977 if(!du
->req
) { // it got killed in flight
978 // cout << "du "<<(void*)du<<" came back from dnsdist, but it was killed"<<endl;
983 *du
->self
= nullptr; // so we don't clean up again in on_generator_dispose
985 handleResponse(*dsc
->df
, du
->req
, du
->status_code
, du
->response
, dsc
->df
->d_customResponseHeaders
, du
->contentType
, true);
990 /* called when a TCP connection has been accepted, the TLS session has not been established */
991 static void on_accept(h2o_socket_t
*listener
, const char *err
)
993 DOHServerConfig
* dsc
= reinterpret_cast<DOHServerConfig
*>(listener
->data
);
994 h2o_socket_t
*sock
= nullptr;
996 if (err
!= nullptr) {
999 // do some dnsdist rules here to filter based on IP address
1000 if ((sock
= h2o_evloop_socket_accept(listener
)) == nullptr) {
1004 ComboAddress remote
;
1005 h2o_socket_getpeername(sock
, reinterpret_cast<struct sockaddr
*>(&remote
));
1006 // cout<<"New HTTP accept for client "<<remote.toStringWithPort()<<": "<< listener->data << endl;
1009 sock
->on_close
.cb
= on_socketclose
;
1010 auto accept_ctx
= dsc
->accept_ctx
->get();
1011 sock
->on_close
.data
= dsc
->accept_ctx
;
1012 ++dsc
->df
->d_httpconnects
;
1013 ++dsc
->cs
->tcpCurrentConnections
;
1014 h2o_accept(accept_ctx
, sock
);
1017 static int create_listener(const ComboAddress
& addr
, std::shared_ptr
<DOHServerConfig
>& dsc
, int fd
)
1019 auto sock
= h2o_evloop_socket_create(dsc
->h2o_ctx
.loop
, fd
, H2O_SOCKET_FLAG_DONT_READ
);
1020 sock
->data
= dsc
.get();
1021 h2o_socket_read_start(sock
, on_accept
);
1026 static int ocsp_stapling_callback(SSL
* ssl
, void* arg
)
1028 if (ssl
== nullptr || arg
== nullptr) {
1029 return SSL_TLSEXT_ERR_NOACK
;
1031 const auto ocspMap
= reinterpret_cast<std::map
<int, std::string
>*>(arg
);
1032 return libssl_ocsp_stapling_callback(ssl
, *ocspMap
);
1035 static int ticket_key_callback(SSL
*s
, unsigned char keyName
[TLS_TICKETS_KEY_NAME_SIZE
], unsigned char *iv
, EVP_CIPHER_CTX
*ectx
, HMAC_CTX
*hctx
, int enc
)
1037 DOHAcceptContext
* ctx
= reinterpret_cast<DOHAcceptContext
*>(libssl_get_ticket_key_callback_data(s
));
1038 if (ctx
== nullptr || !ctx
->d_ticketKeys
) {
1042 ctx
->handleTicketsKeyRotation();
1044 auto ret
= libssl_ticket_key_callback(s
, *ctx
->d_ticketKeys
, keyName
, iv
, ectx
, hctx
, enc
);
1047 ++ctx
->d_cs
->tlsUnknownTicketKey
;
1049 else if (ret
== 2) {
1050 ++ctx
->d_cs
->tlsInactiveTicketKey
;
1057 static void setupTLSContext(DOHAcceptContext
& acceptCtx
,
1058 TLSConfig
& tlsConfig
,
1059 TLSErrorCounters
& counters
)
1061 if (tlsConfig
.d_ciphers
.empty()) {
1062 tlsConfig
.d_ciphers
= DOH_DEFAULT_CIPHERS
;
1065 auto ctx
= libssl_init_server_context(tlsConfig
, acceptCtx
.d_ocspResponses
);
1067 if (tlsConfig
.d_enableTickets
&& tlsConfig
.d_numberOfTicketsKeys
> 0) {
1068 acceptCtx
.d_ticketKeys
= std::unique_ptr
<OpenSSLTLSTicketKeysRing
>(new OpenSSLTLSTicketKeysRing(tlsConfig
.d_numberOfTicketsKeys
));
1069 SSL_CTX_set_tlsext_ticket_key_cb(ctx
.get(), &ticket_key_callback
);
1070 libssl_set_ticket_key_callback_data(ctx
.get(), &acceptCtx
);
1073 if (!acceptCtx
.d_ocspResponses
.empty()) {
1074 SSL_CTX_set_tlsext_status_cb(ctx
.get(), &ocsp_stapling_callback
);
1075 SSL_CTX_set_tlsext_status_arg(ctx
.get(), &acceptCtx
.d_ocspResponses
);
1078 libssl_set_error_counters_callback(ctx
, &counters
);
1080 if (!tlsConfig
.d_keyLogFile
.empty()) {
1081 acceptCtx
.d_keyLogFile
= libssl_set_key_log_file(ctx
, tlsConfig
.d_keyLogFile
);
1084 h2o_ssl_register_alpn_protocols(ctx
.get(), h2o_http2_alpn_protocols
);
1086 if (tlsConfig
.d_ticketKeyFile
.empty()) {
1087 acceptCtx
.handleTicketsKeyRotation();
1090 acceptCtx
.loadTicketsKeys(tlsConfig
.d_ticketKeyFile
);
1093 auto nativeCtx
= acceptCtx
.get();
1094 nativeCtx
->ssl_ctx
= ctx
.release();
1095 acceptCtx
.release();
1098 static void setupAcceptContext(DOHAcceptContext
& ctx
, DOHServerConfig
& dsc
, bool setupTLS
)
1100 auto nativeCtx
= ctx
.get();
1101 nativeCtx
->ctx
= &dsc
.h2o_ctx
;
1102 nativeCtx
->hosts
= dsc
.h2o_config
.hosts
;
1103 ctx
.d_ticketsKeyRotationDelay
= dsc
.df
->d_tlsConfig
.d_ticketsKeyRotationDelay
;
1105 if (setupTLS
&& !dsc
.df
->d_tlsConfig
.d_certKeyPairs
.empty()) {
1107 setupTLSContext(ctx
,
1108 dsc
.df
->d_tlsConfig
,
1109 dsc
.df
->d_tlsCounters
);
1111 catch (const std::runtime_error
& e
) {
1112 throw std::runtime_error("Error setting up TLS context for DoH listener on '" + dsc
.df
->d_local
.toStringWithPort() + "': " + e
.what());
1119 void DOHFrontend::rotateTicketsKey(time_t now
)
1121 if (d_dsc
&& d_dsc
->accept_ctx
) {
1122 d_dsc
->accept_ctx
->rotateTicketsKey(now
);
1126 void DOHFrontend::loadTicketsKeys(const std::string
& keyFile
)
1128 if (d_dsc
&& d_dsc
->accept_ctx
) {
1129 d_dsc
->accept_ctx
->loadTicketsKeys(keyFile
);
1133 void DOHFrontend::handleTicketsKeyRotation()
1135 if (d_dsc
&& d_dsc
->accept_ctx
) {
1136 d_dsc
->accept_ctx
->handleTicketsKeyRotation();
1140 time_t DOHFrontend::getNextTicketsKeyRotation() const
1142 if (d_dsc
&& d_dsc
->accept_ctx
) {
1143 return d_dsc
->accept_ctx
->getNextTicketsKeyRotation();
1148 size_t DOHFrontend::getTicketsKeysCount() const
1151 if (d_dsc
&& d_dsc
->accept_ctx
) {
1152 res
= d_dsc
->accept_ctx
->getTicketsKeysCount();
1157 void DOHFrontend::reloadCertificates()
1159 auto newAcceptContext
= std::unique_ptr
<DOHAcceptContext
>(new DOHAcceptContext());
1160 setupAcceptContext(*newAcceptContext
, *d_dsc
, true);
1161 DOHAcceptContext
* oldCtx
= d_dsc
->accept_ctx
;
1162 d_dsc
->accept_ctx
= newAcceptContext
.release();
1166 void DOHFrontend::setup()
1168 registerOpenSSLUser();
1170 d_dsc
= std::make_shared
<DOHServerConfig
>(d_idleTimeout
);
1172 if (!d_tlsConfig
.d_certKeyPairs
.empty()) {
1174 setupTLSContext(*d_dsc
->accept_ctx
,
1178 catch (const std::runtime_error
& e
) {
1179 throw std::runtime_error("Error setting up TLS context for DoH listener on '" + d_local
.toStringWithPort() + "': " + e
.what());
1184 // this is the entrypoint from dnsdist.cc
1185 void dohThread(ClientState
* cs
)
1188 std::shared_ptr
<DOHFrontend
>& df
= cs
->dohFrontend
;
1189 auto& dsc
= df
->d_dsc
;
1191 dsc
->df
= cs
->dohFrontend
;
1192 dsc
->h2o_config
.server_name
= h2o_iovec_init(df
->d_serverTokens
.c_str(), df
->d_serverTokens
.size());
1195 std::thread
dnsdistThread(dnsdistclient
, dsc
->dohquerypair
[1], dsc
->dohresponsepair
[0]);
1196 dnsdistThread
.detach(); // gets us better error reporting
1198 setThreadName("dnsdist/doh");
1199 // I wonder if this registers an IP address.. I think it does
1200 // this may mean we need to actually register a site "name" here and not the IP address
1201 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);
1203 for(const auto& url
: df
->d_urls
) {
1204 register_handler(hostconf
, url
.c_str(), doh_handler
);
1205 dsc
->paths
.insert(url
);
1208 h2o_context_init(&dsc
->h2o_ctx
, h2o_evloop_create(), &dsc
->h2o_config
);
1210 // in this complicated way we insert the DOHServerConfig pointer in there
1211 h2o_vector_reserve(nullptr, &dsc
->h2o_ctx
.storage
, 1);
1212 dsc
->h2o_ctx
.storage
.entries
[0].data
= dsc
.get();
1213 ++dsc
->h2o_ctx
.storage
.size
;
1215 auto sock
= h2o_evloop_socket_create(dsc
->h2o_ctx
.loop
, dsc
->dohresponsepair
[1], H2O_SOCKET_FLAG_DONT_READ
);
1216 sock
->data
= dsc
.get();
1218 // this listens to responses from dnsdist to turn into http responses
1219 h2o_socket_read_start(sock
, on_dnsdist
);
1221 setupAcceptContext(*dsc
->accept_ctx
, *dsc
, false);
1223 if (create_listener(df
->d_local
, dsc
, cs
->tcpFD
) != 0) {
1224 throw std::runtime_error("DOH server failed to listen on " + df
->d_local
.toStringWithPort() + ": " + strerror(errno
));
1229 int result
= h2o_evloop_run(dsc
->h2o_ctx
.loop
, INT32_MAX
);
1231 if (errno
!= EINTR
) {
1232 errlog("Error in the DoH event loop: %s", strerror(errno
));
1237 while (stop
== false);
1240 catch(const std::exception
& e
) {
1241 throw runtime_error("DOH thread failed to launch: " + std::string(e
.what()));
1244 throw runtime_error("DOH thread failed to launch");
1247 #else /* HAVE_DNS_OVER_HTTPS */
1249 void handleDOHTimeout(DOHUnit
* oldDU
)
1253 #endif /* HAVE_DNS_OVER_HTTPS */