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
));
66 DOHAcceptContext(const DOHAcceptContext
&) = delete;
67 DOHAcceptContext
& operator=(const DOHAcceptContext
&) = delete;
69 h2o_accept_ctx_t
* get()
72 return &d_h2o_accept_ctx
;
79 SSL_CTX_free(d_h2o_accept_ctx
.ssl_ctx
);
80 d_h2o_accept_ctx
.ssl_ctx
= nullptr;
86 h2o_accept_ctx_t d_h2o_accept_ctx
;
87 std::atomic
<uint64_t> d_refcnt
{1};
90 // we create one of these per thread, and pass around a pointer to it
91 // through the bowels of h2o
92 struct DOHServerConfig
94 DOHServerConfig(uint32_t idleTimeout
): accept_ctx(new DOHAcceptContext
)
96 if(socketpair(AF_LOCAL
, SOCK_DGRAM
, 0, dohquerypair
) < 0) {
97 unixDie("Creating a socket pair for DNS over HTTPS");
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");
106 h2o_config_init(&h2o_config
);
107 h2o_config
.http2
.idle_timeout
= idleTimeout
* 1000;
109 DOHServerConfig(const DOHServerConfig
&) = delete;
110 DOHServerConfig
& operator=(const DOHServerConfig
&) = delete;
115 accept_ctx
->release();
119 LocalHolders holders
;
120 h2o_globalconf_t h2o_config
;
121 h2o_context_t h2o_ctx
;
122 DOHAcceptContext
* accept_ctx
{nullptr};
123 ClientState
* cs
{nullptr};
124 std::shared_ptr
<DOHFrontend
> df
{nullptr};
125 int dohquerypair
[2]{-1,-1};
126 int dohresponsepair
[2]{-1,-1};
129 void handleDOHTimeout(DOHUnit
* oldDU
)
131 if (oldDU
== nullptr) {
135 /* we are about to erase an existing DU */
136 oldDU
->status_code
= 502;
138 if (send(oldDU
->rsock
, &oldDU
, sizeof(oldDU
), 0) != sizeof(oldDU
)) {
144 static void on_socketclose(void *data
)
146 DOHAcceptContext
* ctx
= reinterpret_cast<DOHAcceptContext
*>(data
);
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
155 static int processDOHQuery(DOHUnit
* du
)
157 uint16_t queryId
= 0;
161 // we got closed meanwhile. XXX small race condition here
165 DOHServerConfig
* dsc
= reinterpret_cast<DOHServerConfig
*>(du
->req
->conn
->ctx
->storage
.entries
[0].data
);
166 auto& holders
= dsc
->holders
;
167 ClientState
& cs
= *dsc
->cs
;
169 if (du
->query
.size() < sizeof(dnsheader
)) {
170 ++g_stats
.nonCompliantQueries
;
171 du
->status_code
= 400;
178 /* we need an accurate ("real") value for the response and
179 to store into the IDS, but not for insertion into the
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
);
191 if (!checkQueryHeaders(dh
)) {
192 du
->status_code
= 400;
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
;
202 queryId
= ntohs(dh
->id
);
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) {
209 #endif /* HAVE_H2O_SOCKET_BET_SSL_SERVER_NAME */
211 std::shared_ptr
<DownstreamState
> ss
{nullptr};
212 auto result
= processQuery(dq
, cs
, holders
, ss
);
214 if (result
== ProcessQueryResult::Drop
) {
215 du
->status_code
= 403;
219 if (result
== ProcessQueryResult::SendAnswer
) {
220 du
->response
= std::string(reinterpret_cast<char*>(dq
.dh
), dq
.len
);
221 send(du
->rsock
, &du
, sizeof(du
), 0);
225 if (result
!= ProcessQueryResult::PassToBackend
) {
226 du
->status_code
= 500;
231 du
->status_code
= 502;
235 ComboAddress dest
= du
->dest
;
236 unsigned int idOffset
= (ss
->idOffset
++) % ss
->idStates
.size();
237 IDState
* ids
= &ss
->idStates
[idOffset
];
239 DOHUnit
* oldDU
= nullptr;
240 if (ids
->origFD
!= -1) {
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 */
247 /* we atomically replace the value with 0, we now own this state */
248 int oldFD
= ids
->origFD
.exchange(0);
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. */
257 ++g_stats
.downstreamTimeouts
;
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. */
261 handleDOHTimeout(oldDU
);
267 ids
->origID
= dh
->id
;
268 setIDStateFromDNSQuestion(*ids
, dq
, std::move(qname
));
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.
276 if (dest
.sin4
.sin_family
!= 0) {
277 ids
->origDest
= dest
;
278 ids
->destHarvested
= true;
281 ids
->origDest
= cs
.local
;
282 ids
->destHarvested
= false;
287 int fd
= pickBackendSocketForSending(ss
);
288 /* you can't touch du after this line, because it might already have been freed */
289 ssize_t ret
= udpClientSendRequestToBackend(ss
, fd
, query
, dq
.len
);
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) {
300 ++g_stats
.downstreamSendErrors
;
301 du
->status_code
= 502;
305 vinfolog("Got query for %s|%s from %s (https), relayed to %s", ids
->qname
.toString(), QType(ids
->qtype
).getName(), remote
.toStringWithPort(), ss
->getName());
307 catch(const std::exception
& e
) {
308 vinfolog("Got an error in DOH question thread while parsing a query from %s, id %d: %s", remote
.toStringWithPort(), queryId
, e
.what());
309 du
->status_code
= 500;
316 static void on_response_ready_cb(struct st_h2o_filter_t
*self
, h2o_req_t
*req
, h2o_ostream_t
**slot
)
318 if (req
== nullptr) {
322 DOHServerConfig
* dsc
= reinterpret_cast<DOHServerConfig
*>(req
->conn
->ctx
->storage
.entries
[0].data
);
324 DOHFrontend::HTTPVersionStats
* stats
= nullptr;
325 if (req
->version
< 0x200) {
327 stats
= &dsc
->df
->d_http1Stats
;
331 stats
= &dsc
->df
->d_http2Stats
;
334 switch (req
->res
.status
) {
336 ++stats
->d_nb200Responses
;
339 ++stats
->d_nb400Responses
;
342 ++stats
->d_nb403Responses
;
345 ++stats
->d_nb500Responses
;
348 ++stats
->d_nb502Responses
;
351 ++stats
->d_nbOtherResponses
;
355 h2o_setup_next_ostream(req
, slot
);
358 static h2o_pathconf_t
*register_handler(h2o_hostconf_t
*hostconf
, const char *path
, int (*on_req
)(h2o_handler_t
*, h2o_req_t
*))
360 h2o_pathconf_t
*pathconf
= h2o_config_register_path(hostconf
, path
, 0);
361 if (pathconf
== nullptr) {
364 h2o_filter_t
*filter
= h2o_create_filter(pathconf
, sizeof(*filter
));
366 filter
->on_setup_ostream
= on_response_ready_cb
;
369 h2o_handler_t
*handler
= h2o_create_handler(pathconf
, sizeof(*handler
));
370 if (handler
!= nullptr) {
371 handler
->on_req
= on_req
;
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 */
379 static void on_generator_dispose(void *_self
)
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;
388 static void doh_dispatch_query(DOHServerConfig
* dsc
, h2o_handler_t
* self
, h2o_req_t
* req
, std::string
&& query
, const ComboAddress
& local
, const ComboAddress
& remote
)
392 DNSName
qname(query
.c_str(), query
.size(), sizeof(dnsheader
), false, &qtype
);
394 auto du
= std::unique_ptr
<DOHUnit
>(new DOHUnit
);
398 du
->rsock
= dsc
->dohresponsepair
[0];
399 du
->query
= std::move(query
);
401 du
->self
= reinterpret_cast<DOHUnit
**>(h2o_mem_alloc_shared(&req
->pool
, sizeof(*self
), on_generator_dispose
));
402 auto ptr
= du
.release();
405 if(send(dsc
->dohquerypair
[0], &ptr
, sizeof(ptr
), 0) != sizeof(ptr
)) {
408 h2o_send_error_500(req
, "Internal Server Error", "Internal Server Error", 0);
415 catch(const std::exception
& e
) {
416 vinfolog("Had error parsing DoH DNS packet from %s: %s", remote
.toStringWithPort(), e
.what());
417 h2o_send_error_400(req
, "Bad Request", "The DNS query could not be parsed", 0);
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.
425 static int doh_handler(h2o_handler_t
*self
, h2o_req_t
*req
)
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
432 h2o_socket_t
* sock
= req
->conn
->callbacks
->get_socket(req
->conn
);
435 h2o_socket_getpeername(sock
, reinterpret_cast<struct sockaddr
*>(&remote
));
436 h2o_socket_getsockname(sock
, reinterpret_cast<struct sockaddr
*>(&local
));
437 DOHServerConfig
* dsc
= reinterpret_cast<DOHServerConfig
*>(req
->conn
->ctx
->storage
.entries
[0].data
);
439 auto& holders
= dsc
->holders
;
440 if (!holders
.acl
->match(remote
)) {
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);
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
);
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
;
463 ++dsc
->df
->d_tlsUnknownqueries
;
466 string
path(req
->path
.base
, req
->path
.len
);
468 if (h2o_memis(req
->method
.base
, req
->method
.len
, H2O_STRLIT("POST"))) {
469 ++dsc
->df
->d_postqueries
;
470 if(req
->version
>= 0x0200)
471 ++dsc
->df
->d_http2Stats
.d_nbQueries
;
473 ++dsc
->df
->d_http1Stats
.d_nbQueries
;
476 query
.reserve(req
->entity
.len
+ 512);
477 query
.assign(req
->entity
.base
, req
->entity
.len
);
478 doh_dispatch_query(dsc
, self
, req
, std::move(query
), local
, remote
);
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
,"_", "/");
489 // re-add padding that may have been missing
490 switch (sdns
.size() % 4) {
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) {
503 h2o_send_error_400(req
, "Bad Request", "Unable to decode BASE64-URL", 0);
504 ++dsc
->df
->d_badrequests
;
508 ++dsc
->df
->d_getqueries
;
509 if(req
->version
>= 0x0200)
510 ++dsc
->df
->d_http2Stats
.d_nbQueries
;
512 ++dsc
->df
->d_http1Stats
.d_nbQueries
;
514 doh_dispatch_query(dsc
, self
, req
, std::move(decoded
), local
, remote
);
519 vinfolog("HTTP request without DNS parameter: %s", req
->path
.base
);
520 h2o_send_error_400(req
, "Bad Request", "Unable to find the DNS parameter", 0);
521 ++dsc
->df
->d_badrequests
;
526 h2o_send_error_400(req
, "Bad Request", "Unable to parse the request", 0);
527 ++dsc
->df
->d_badrequests
;
531 catch(const exception
& e
)
533 errlog("DOH Handler function failed with error %s", e
.what());
537 HTTPHeaderRule::HTTPHeaderRule(const std::string
& header
, const std::string
& regex
)
540 d_header
= toLower(header
);
541 d_visual
= "http[" + header
+ "] ~ " + regex
;
545 bool HTTPHeaderRule::matches(const DNSQuestion
* dq
) const
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
))) {
560 string
HTTPHeaderRule::toString() const
565 HTTPPathRule::HTTPPathRule(const std::string
& path
)
571 bool HTTPPathRule::matches(const DNSQuestion
* dq
) const
577 if(dq
->du
->req
->query_at
== SIZE_MAX
) {
578 return dq
->du
->req
->path
.base
== d_path
;
581 return d_path
.compare(0, d_path
.size(), dq
->du
->req
->path
.base
, dq
->du
->req
->query_at
) == 0;
585 string
HTTPPathRule::toString() const
587 return "url path == " + d_path
;
590 HTTPPathRegexRule::HTTPPathRegexRule(const std::string
& regex
): d_regex(regex
), d_visual("http path ~ " + regex
)
594 bool HTTPPathRegexRule::matches(const DNSQuestion
* dq
) const
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
));
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
));
609 string
HTTPPathRegexRule::toString() const
614 void DOHSetHTTPResponse(DOHUnit
& du
, uint16_t statusCode
, const std::string
& reason
, const std::string
& body
)
616 du
.status_code
= statusCode
;
621 void dnsdistclient(int qsock
, int rsock
)
623 setThreadName("dnsdist/doh-cli");
627 DOHUnit
* du
= nullptr;
628 ssize_t got
= recv(qsock
, &du
, sizeof(du
), 0);
630 warnlog("Error receiving internal DoH query: %s", strerror(errno
));
633 else if (static_cast<size_t>(got
) < sizeof(du
)) {
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()));
643 generateOptRR(std::string(), res
, 4096, 0, false);
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;
651 // we leave existing EDNS in place
654 if(processDOHQuery(du
) < 0) {
655 du
->status_code
= 500;
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?
660 catch(const std::exception
& e
) {
661 errlog("Error while processing query received over DoH: %s", e
.what());
664 errlog("Unspecified error while processing query received over DoH");
669 // called if h2o finds that dnsdist gave us an answer
670 static void on_dnsdist(h2o_socket_t
*listener
, const char *err
)
672 DOHUnit
*du
= nullptr;
673 DOHServerConfig
* dsc
= reinterpret_cast<DOHServerConfig
*>(listener
->data
);
674 ssize_t got
= recv(dsc
->dohresponsepair
[1], &du
, sizeof(du
), 0);
677 warnlog("Error reading a DOH internal response: %s", strerror(errno
));
680 else if (static_cast<size_t>(got
) != sizeof(du
)) {
684 if(!du
->req
) { // it got killed in flight
685 // cout << "du "<<(void*)du<<" came back from dnsdist, but it was killed"<<endl;
690 *du
->self
= nullptr; // so we don't clean up again in on_generator_dispose
691 if (du
->status_code
== 200) {
692 ++dsc
->df
->d_validresponses
;
693 du
->req
->res
.status
= 200;
694 du
->req
->res
.reason
= "OK";
696 h2o_add_header(&du
->req
->pool
, &du
->req
->res
.headers
, H2O_TOKEN_CONTENT_TYPE
, nullptr, H2O_STRLIT("application/dns-message"));
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;
701 du
->req
->res
.content_length
= du
->response
.size();
702 h2o_send_inline(du
->req
, du
->response
.c_str(), du
->response
.size());
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
;
710 switch(du
->status_code
) {
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);
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);
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);
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);
727 ++dsc
->df
->d_errorresponses
;
733 static void on_accept(h2o_socket_t
*listener
, const char *err
)
735 DOHServerConfig
* dsc
= reinterpret_cast<DOHServerConfig
*>(listener
->data
);
736 h2o_socket_t
*sock
= nullptr;
738 if (err
!= nullptr) {
741 // do some dnsdist rules here to filter based on IP address
742 if ((sock
= h2o_evloop_socket_accept(listener
)) == nullptr) {
747 h2o_socket_getpeername(sock
, reinterpret_cast<struct sockaddr
*>(&remote
));
748 // cout<<"New HTTP accept for client "<<remote.toStringWithPort()<<": "<< listener->data << endl;
751 sock
->on_close
.cb
= on_socketclose
;
752 auto accept_ctx
= dsc
->accept_ctx
->get();
753 sock
->on_close
.data
= dsc
->accept_ctx
;
754 ++dsc
->df
->d_httpconnects
;
755 h2o_accept(accept_ctx
, sock
);
758 static int create_listener(const ComboAddress
& addr
, std::shared_ptr
<DOHServerConfig
>& dsc
, int fd
)
760 auto sock
= h2o_evloop_socket_create(dsc
->h2o_ctx
.loop
, fd
, H2O_SOCKET_FLAG_DONT_READ
);
761 sock
->data
= dsc
.get();
762 h2o_socket_read_start(sock
, on_accept
);
767 static std::unique_ptr
<SSL_CTX
, void(*)(SSL_CTX
*)> getTLSContext(const std::vector
<std::pair
<std::string
, std::string
>>& pairs
, const std::string
& ciphers
, const std::string
& ciphers13
)
769 auto ctx
= std::unique_ptr
<SSL_CTX
, void(*)(SSL_CTX
*)>(SSL_CTX_new(SSLv23_server_method()), SSL_CTX_free
);
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
;
779 SSL_CTX_set_options(ctx
.get(), sslOptions
);
781 #ifdef SSL_CTX_set_ecdh_auto
782 SSL_CTX_set_ecdh_auto(ctx
.get(), 1);
785 /* load certificate and private key */
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
);
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
);
797 if (SSL_CTX_set_cipher_list(ctx
.get(), ciphers
.empty() == false ? ciphers
.c_str() : DOH_DEFAULT_CIPHERS
) != 1) {
798 throw std::runtime_error("Failed to setup SSL/TLS for DoH listener, DOH ciphers could not be set: " + ciphers
);
801 #ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
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
);
805 #endif /* HAVE_SSL_CTX_SET_CIPHERSUITES */
807 h2o_ssl_register_alpn_protocols(ctx
.get(), h2o_http2_alpn_protocols
);
812 static void setupAcceptContext(DOHAcceptContext
& ctx
, DOHServerConfig
& dsc
, bool setupTLS
)
814 auto nativeCtx
= ctx
.get();
815 nativeCtx
->ctx
= &dsc
.h2o_ctx
;
816 nativeCtx
->hosts
= dsc
.h2o_config
.hosts
;
818 auto tlsCtx
= getTLSContext(dsc
.df
->d_certKeyPairs
,
820 dsc
.df
->d_ciphers13
);
822 nativeCtx
->ssl_ctx
= tlsCtx
.release();
828 void DOHFrontend::reloadCertificates()
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();
837 void DOHFrontend::setup()
839 registerOpenSSLUser();
841 d_dsc
= std::make_shared
<DOHServerConfig
>(d_idleTimeout
);
843 auto tlsCtx
= getTLSContext(d_certKeyPairs
,
847 auto accept_ctx
= d_dsc
->accept_ctx
->get();
848 accept_ctx
->ssl_ctx
= tlsCtx
.release();
849 d_dsc
->accept_ctx
->release();
852 // this is the entrypoint from dnsdist.cc
853 void dohThread(ClientState
* cs
)
856 std::shared_ptr
<DOHFrontend
>& df
= cs
->dohFrontend
;
857 auto& dsc
= df
->d_dsc
;
859 dsc
->df
= cs
->dohFrontend
;
860 dsc
->h2o_config
.server_name
= h2o_iovec_init(df
->d_serverTokens
.c_str(), df
->d_serverTokens
.size());
863 std::thread
dnsdistThread(dnsdistclient
, dsc
->dohquerypair
[1], dsc
->dohresponsepair
[0]);
864 dnsdistThread
.detach(); // gets us better error reporting
866 setThreadName("dnsdist/doh");
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);
871 for(const auto& url
: df
->d_urls
) {
872 register_handler(hostconf
, url
.c_str(), doh_handler
);
875 h2o_context_init(&dsc
->h2o_ctx
, h2o_evloop_create(), &dsc
->h2o_config
);
877 // in this complicated way we insert the DOHServerConfig pointer in there
878 h2o_vector_reserve(nullptr, &dsc
->h2o_ctx
.storage
, 1);
879 dsc
->h2o_ctx
.storage
.entries
[0].data
= dsc
.get();
880 ++dsc
->h2o_ctx
.storage
.size
;
882 auto sock
= h2o_evloop_socket_create(dsc
->h2o_ctx
.loop
, dsc
->dohresponsepair
[1], H2O_SOCKET_FLAG_DONT_READ
);
883 sock
->data
= dsc
.get();
885 // this listens to responses from dnsdist to turn into http responses
886 h2o_socket_read_start(sock
, on_dnsdist
);
888 setupAcceptContext(*dsc
->accept_ctx
, *dsc
, false);
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
));
896 int result
= h2o_evloop_run(dsc
->h2o_ctx
.loop
, INT32_MAX
);
898 if (errno
!= EINTR
) {
899 errlog("Error in the DoH event loop: %s", strerror(errno
));
904 while (stop
== false);
907 catch(const std::exception
& e
) {
908 throw runtime_error("DOH thread failed to launch: " + std::string(e
.what()));
911 throw runtime_error("DOH thread failed to launch");
914 #else /* HAVE_DNS_OVER_HTTPS */
916 void handleDOHTimeout(DOHUnit
* oldDU
)
920 #endif /* HAVE_DNS_OVER_HTTPS */