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 */
137 oldDU
->status_code
= 502;
139 if (send(oldDU
->rsock
, &oldDU
, sizeof(oldDU
), 0) != sizeof(oldDU
)) {
145 static void on_socketclose(void *data
)
147 DOHAcceptContext
* ctx
= reinterpret_cast<DOHAcceptContext
*>(data
);
151 /* this duplicates way too much from the UDP handler. Sorry.
152 this function calls 'return -1' to drop a query without sending it
153 caller should make sure HTTPS thread hears of that
156 static int processDOHQuery(DOHUnit
* du
)
158 uint16_t queryId
= 0;
162 // we got closed meanwhile. XXX small race condition here
166 DOHServerConfig
* dsc
= reinterpret_cast<DOHServerConfig
*>(du
->req
->conn
->ctx
->storage
.entries
[0].data
);
167 auto& holders
= dsc
->holders
;
168 ClientState
& cs
= *dsc
->cs
;
170 if (du
->query
.size() < sizeof(dnsheader
)) {
171 ++g_stats
.nonCompliantQueries
;
172 du
->status_code
= 400;
179 /* we need an accurate ("real") value for the response and
180 to store into the IDS, but not for insertion into the
182 struct timespec queryRealTime
;
183 gettime(&queryRealTime
, true);
184 uint16_t len
= du
->query
.length();
185 /* allocate a bit more memory to be able to spoof the content,
186 or to add ECS without allocating a new buffer */
187 du
->query
.resize(du
->query
.size() + 512);
188 size_t bufferSize
= du
->query
.size();
189 auto query
= const_cast<char*>(du
->query
.c_str());
190 struct dnsheader
* dh
= reinterpret_cast<struct dnsheader
*>(query
);
192 if (!checkQueryHeaders(dh
)) {
193 du
->status_code
= 400;
197 uint16_t qtype
, qclass
;
198 unsigned int consumed
= 0;
199 DNSName
qname(query
, len
, sizeof(dnsheader
), false, &qtype
, &qclass
, &consumed
);
200 DNSQuestion
dq(&qname
, qtype
, qclass
, consumed
, &du
->dest
, &du
->remote
, dh
, bufferSize
, len
, false, &queryRealTime
);
201 dq
.ednsAdded
= du
->ednsAdded
;
203 queryId
= ntohs(dh
->id
);
204 #ifdef HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME
205 h2o_socket_t
* sock
= du
->req
->conn
->callbacks
->get_socket(du
->req
->conn
);
206 const char * sni
= h2o_socket_get_ssl_server_name(sock
);
207 if (sni
!= nullptr) {
210 #endif /* HAVE_H2O_SOCKET_BET_SSL_SERVER_NAME */
212 std::shared_ptr
<DownstreamState
> ss
{nullptr};
213 auto result
= processQuery(dq
, cs
, holders
, ss
);
215 if (result
== ProcessQueryResult::Drop
) {
216 du
->status_code
= 403;
220 if (result
== ProcessQueryResult::SendAnswer
) {
221 du
->response
= std::string(reinterpret_cast<char*>(dq
.dh
), dq
.len
);
222 send(du
->rsock
, &du
, sizeof(du
), 0);
226 if (result
!= ProcessQueryResult::PassToBackend
) {
227 du
->status_code
= 500;
232 du
->status_code
= 502;
236 ComboAddress dest
= du
->dest
;
237 unsigned int idOffset
= (ss
->idOffset
++) % ss
->idStates
.size();
238 IDState
* ids
= &ss
->idStates
[idOffset
];
240 DOHUnit
* oldDU
= nullptr;
241 if (ids
->origFD
!= -1) {
242 /* that means that the state was in use, possibly with an allocated
243 DOHUnit that we will need to handle, but we can't touch it before
244 confirming that we now own this state */
248 /* we atomically replace the value with 0, we now own this state */
249 int oldFD
= ids
->origFD
.exchange(0);
251 /* the value was -1, meaning that the state was not in use.
252 we reset 'oldDU' because it might have still been in use when we read it. */
258 ++g_stats
.downstreamTimeouts
;
259 /* we are reusing a state, if there was an existing DOHUnit we need
260 to handle it because it's about to be overwritten. */
262 handleDOHTimeout(oldDU
);
268 ids
->origID
= dh
->id
;
269 setIDStateFromDNSQuestion(*ids
, dq
, std::move(qname
));
271 /* If we couldn't harvest the real dest addr, still
272 write down the listening addr since it will be useful
273 (especially if it's not an 'any' one).
274 We need to keep track of which one it is since we may
275 want to use the real but not the listening addr to reply.
277 if (dest
.sin4
.sin_family
!= 0) {
278 ids
->origDest
= dest
;
279 ids
->destHarvested
= true;
282 ids
->origDest
= cs
.local
;
283 ids
->destHarvested
= false;
288 int fd
= pickBackendSocketForSending(ss
);
289 /* you can't touch du after this line, because it might already have been freed */
290 ssize_t ret
= udpClientSendRequestToBackend(ss
, fd
, query
, dq
.len
);
293 /* we are about to handle the error, make sure that
294 this pointer is not accessed when the state is cleaned,
295 but first check that it still belongs to us */
296 if (ids
->origFD
== 0 && ids
->origFD
.exchange(-1) == 0) {
301 ++g_stats
.downstreamSendErrors
;
302 du
->status_code
= 502;
306 vinfolog("Got query for %s|%s from %s (https), relayed to %s", ids
->qname
.toString(), QType(ids
->qtype
).getName(), remote
.toStringWithPort(), ss
->getName());
308 catch(const std::exception
& e
) {
309 vinfolog("Got an error in DOH question thread while parsing a query from %s, id %d: %s", remote
.toStringWithPort(), queryId
, e
.what());
310 du
->status_code
= 500;
317 static void on_response_ready_cb(struct st_h2o_filter_t
*self
, h2o_req_t
*req
, h2o_ostream_t
**slot
)
319 if (req
== nullptr) {
323 DOHServerConfig
* dsc
= reinterpret_cast<DOHServerConfig
*>(req
->conn
->ctx
->storage
.entries
[0].data
);
325 DOHFrontend::HTTPVersionStats
* stats
= nullptr;
326 if (req
->version
< 0x200) {
328 stats
= &dsc
->df
->d_http1Stats
;
332 stats
= &dsc
->df
->d_http2Stats
;
335 switch (req
->res
.status
) {
337 ++stats
->d_nb200Responses
;
340 ++stats
->d_nb400Responses
;
343 ++stats
->d_nb403Responses
;
346 ++stats
->d_nb500Responses
;
349 ++stats
->d_nb502Responses
;
352 ++stats
->d_nbOtherResponses
;
356 h2o_setup_next_ostream(req
, slot
);
359 static h2o_pathconf_t
*register_handler(h2o_hostconf_t
*hostconf
, const char *path
, int (*on_req
)(h2o_handler_t
*, h2o_req_t
*))
361 h2o_pathconf_t
*pathconf
= h2o_config_register_path(hostconf
, path
, 0);
362 if (pathconf
== nullptr) {
365 h2o_filter_t
*filter
= h2o_create_filter(pathconf
, sizeof(*filter
));
367 filter
->on_setup_ostream
= on_response_ready_cb
;
370 h2o_handler_t
*handler
= h2o_create_handler(pathconf
, sizeof(*handler
));
371 if (handler
!= nullptr) {
372 handler
->on_req
= on_req
;
378 /* this is called by h2o when our request dies.
379 We use this to signal to the 'du' that this req is no longer alive */
380 static void on_generator_dispose(void *_self
)
382 DOHUnit
** du
= (DOHUnit
**)_self
;
383 if(*du
) { // if 0, on_dnsdist cleaned up du already
384 // cout << "du "<<(void*)*du<<" containing req "<<(*du)->req<<" got killed"<<endl;
385 (*du
)->req
= nullptr;
389 static void doh_dispatch_query(DOHServerConfig
* dsc
, h2o_handler_t
* self
, h2o_req_t
* req
, std::string
&& query
, const ComboAddress
& local
, const ComboAddress
& remote
)
393 DNSName
qname(query
.c_str(), query
.size(), sizeof(dnsheader
), false, &qtype
);
395 auto du
= std::unique_ptr
<DOHUnit
>(new DOHUnit
);
399 du
->rsock
= dsc
->dohresponsepair
[0];
400 du
->query
= std::move(query
);
402 du
->self
= reinterpret_cast<DOHUnit
**>(h2o_mem_alloc_shared(&req
->pool
, sizeof(*self
), on_generator_dispose
));
403 auto ptr
= du
.release();
406 if(send(dsc
->dohquerypair
[0], &ptr
, sizeof(ptr
), 0) != sizeof(ptr
)) {
409 h2o_send_error_500(req
, "Internal Server Error", "Internal Server Error", 0);
416 catch(const std::exception
& e
) {
417 vinfolog("Had error parsing DoH DNS packet from %s: %s", remote
.toStringWithPort(), e
.what());
418 h2o_send_error_400(req
, "Bad Request", "The DNS query could not be parsed", 0);
423 For GET, the base64url-encoded payload is in the 'dns' parameter, which might be the first parameter, or not.
424 For POST, the payload is the payload.
426 static int doh_handler(h2o_handler_t
*self
, h2o_req_t
*req
)
429 // g_logstream<<(void*)req<<" doh_handler"<<endl;
430 if(!req
->conn
->ctx
->storage
.size
) {
431 return 0; // although we might was well crash on this
433 h2o_socket_t
* sock
= req
->conn
->callbacks
->get_socket(req
->conn
);
436 h2o_socket_getpeername(sock
, reinterpret_cast<struct sockaddr
*>(&remote
));
437 h2o_socket_getsockname(sock
, reinterpret_cast<struct sockaddr
*>(&local
));
438 DOHServerConfig
* dsc
= reinterpret_cast<DOHServerConfig
*>(req
->conn
->ctx
->storage
.entries
[0].data
);
440 auto& holders
= dsc
->holders
;
441 if (!holders
.acl
->match(remote
)) {
443 vinfolog("Query from %s (DoH) dropped because of ACL", remote
.toStringWithPort());
444 h2o_send_error_403(req
, "Forbidden", "dns query not allowed because of ACL", 0);
448 constexpr int overwrite_if_exists
= 1;
449 constexpr int maybe_token
= 1;
450 for (auto const& headerPair
: dsc
->df
->d_customResponseHeaders
) {
451 h2o_set_header_by_str(&req
->pool
, &req
->res
.headers
, headerPair
.first
.c_str(), headerPair
.first
.size(), maybe_token
, headerPair
.second
.c_str(), headerPair
.second
.size(), overwrite_if_exists
);
454 if(auto tlsversion
= h2o_socket_get_ssl_protocol_version(sock
)) {
455 if(!strcmp(tlsversion
, "TLSv1.0"))
456 ++dsc
->df
->d_tls10queries
;
457 else if(!strcmp(tlsversion
, "TLSv1.1"))
458 ++dsc
->df
->d_tls11queries
;
459 else if(!strcmp(tlsversion
, "TLSv1.2"))
460 ++dsc
->df
->d_tls12queries
;
461 else if(!strcmp(tlsversion
, "TLSv1.3"))
462 ++dsc
->df
->d_tls13queries
;
464 ++dsc
->df
->d_tlsUnknownqueries
;
467 string
path(req
->path
.base
, req
->path
.len
);
469 if (h2o_memis(req
->method
.base
, req
->method
.len
, H2O_STRLIT("POST"))) {
470 ++dsc
->df
->d_postqueries
;
471 if(req
->version
>= 0x0200)
472 ++dsc
->df
->d_http2Stats
.d_nbQueries
;
474 ++dsc
->df
->d_http1Stats
.d_nbQueries
;
477 query
.reserve(req
->entity
.len
+ 512);
478 query
.assign(req
->entity
.base
, req
->entity
.len
);
479 doh_dispatch_query(dsc
, self
, req
, std::move(query
), local
, remote
);
481 else if(req
->query_at
!= SIZE_MAX
&& (req
->path
.len
- req
->query_at
> 5)) {
482 auto pos
= path
.find("?dns=");
483 if(pos
== string::npos
)
484 pos
= path
.find("&dns=");
485 if(pos
!= string::npos
) {
486 // need to base64url decode this
487 string
sdns(path
.substr(pos
+5));
488 boost::replace_all(sdns
,"-", "+");
489 boost::replace_all(sdns
,"_", "/");
490 // re-add padding that may have been missing
491 switch (sdns
.size() % 4) {
501 /* rough estimate so we hopefully don't need a need allocation later */
502 decoded
.reserve(((sdns
.size() * 3) / 4) + 512);
503 if(B64Decode(sdns
, decoded
) < 0) {
504 h2o_send_error_400(req
, "Bad Request", "Unable to decode BASE64-URL", 0);
505 ++dsc
->df
->d_badrequests
;
509 ++dsc
->df
->d_getqueries
;
510 if(req
->version
>= 0x0200)
511 ++dsc
->df
->d_http2Stats
.d_nbQueries
;
513 ++dsc
->df
->d_http1Stats
.d_nbQueries
;
515 doh_dispatch_query(dsc
, self
, req
, std::move(decoded
), local
, remote
);
520 vinfolog("HTTP request without DNS parameter: %s", req
->path
.base
);
521 h2o_send_error_400(req
, "Bad Request", "Unable to find the DNS parameter", 0);
522 ++dsc
->df
->d_badrequests
;
527 h2o_send_error_400(req
, "Bad Request", "Unable to parse the request", 0);
528 ++dsc
->df
->d_badrequests
;
532 catch(const exception
& e
)
534 errlog("DOH Handler function failed with error %s", e
.what());
538 HTTPHeaderRule::HTTPHeaderRule(const std::string
& header
, const std::string
& regex
)
541 d_header
= toLower(header
);
542 d_visual
= "http[" + header
+ "] ~ " + regex
;
546 bool HTTPHeaderRule::matches(const DNSQuestion
* dq
) const
552 for (unsigned int i
= 0; i
!= dq
->du
->req
->headers
.size
; ++i
) {
553 if(std::string(dq
->du
->req
->headers
.entries
[i
].name
->base
, dq
->du
->req
->headers
.entries
[i
].name
->len
) == d_header
&&
554 d_regex
.match(std::string(dq
->du
->req
->headers
.entries
[i
].value
.base
, dq
->du
->req
->headers
.entries
[i
].value
.len
))) {
561 string
HTTPHeaderRule::toString() const
566 HTTPPathRule::HTTPPathRule(const std::string
& path
)
572 bool HTTPPathRule::matches(const DNSQuestion
* dq
) const
578 if(dq
->du
->req
->query_at
== SIZE_MAX
) {
579 return dq
->du
->req
->path
.base
== d_path
;
582 return d_path
.compare(0, d_path
.size(), dq
->du
->req
->path
.base
, dq
->du
->req
->query_at
) == 0;
586 string
HTTPPathRule::toString() const
588 return "url path == " + d_path
;
591 HTTPPathRegexRule::HTTPPathRegexRule(const std::string
& regex
): d_regex(regex
), d_visual("http path ~ " + regex
)
595 bool HTTPPathRegexRule::matches(const DNSQuestion
* dq
) const
601 if(dq
->du
->req
->query_at
== SIZE_MAX
) {
602 return d_regex
.match(std::string(dq
->du
->req
->path
.base
, dq
->du
->req
->path
.len
));
605 cerr
<<std::string(dq
->du
->req
->path
.base
, dq
->du
->req
->path
.len
- dq
->du
->req
->query_at
)<<endl
;
606 return d_regex
.match(std::string(dq
->du
->req
->path
.base
, dq
->du
->req
->path
.len
- dq
->du
->req
->query_at
));
610 string
HTTPPathRegexRule::toString() const
615 void dnsdistclient(int qsock
, int rsock
)
617 setThreadName("dnsdist/doh-cli");
621 DOHUnit
* du
= nullptr;
622 ssize_t got
= recv(qsock
, &du
, sizeof(du
), 0);
624 warnlog("Error receiving internal DoH query: %s", strerror(errno
));
627 else if (static_cast<size_t>(got
) < sizeof(du
)) {
631 // if there was no EDNS, we add it with a large buffer size
632 // so we can use UDP to talk to the backend.
633 auto dh
= const_cast<struct dnsheader
*>(reinterpret_cast<const struct dnsheader
*>(du
->query
.c_str()));
637 generateOptRR(std::string(), res
, 4096, 0, false);
640 dh
= const_cast<struct dnsheader
*>(reinterpret_cast<const struct dnsheader
*>(du
->query
.c_str())); // may have reallocated
641 dh
->arcount
= htons(1);
642 du
->ednsAdded
= true;
645 // we leave existing EDNS in place
648 if(processDOHQuery(du
) < 0) {
649 du
->error
= true; // turns our drop into a 500
650 if(send(du
->rsock
, &du
, sizeof(du
), 0) != sizeof(du
))
651 delete du
; // XXX but now what - will h2o time this out for us?
654 catch(const std::exception
& e
) {
655 errlog("Error while processing query received over DoH: %s", e
.what());
658 errlog("Unspecified error while processing query received over DoH");
663 // called if h2o finds that dnsdist gave us an answer
664 static void on_dnsdist(h2o_socket_t
*listener
, const char *err
)
666 DOHUnit
*du
= nullptr;
667 DOHServerConfig
* dsc
= reinterpret_cast<DOHServerConfig
*>(listener
->data
);
668 ssize_t got
= recv(dsc
->dohresponsepair
[1], &du
, sizeof(du
), 0);
671 warnlog("Error reading a DOH internal response: %s", strerror(errno
));
674 else if (static_cast<size_t>(got
) != sizeof(du
)) {
678 if(!du
->req
) { // it got killed in flight
679 // cout << "du "<<(void*)du<<" came back from dnsdist, but it was killed"<<endl;
684 *du
->self
= nullptr; // so we don't clean up again in on_generator_dispose
686 ++dsc
->df
->d_validresponses
;
687 du
->req
->res
.status
= 200;
688 du
->req
->res
.reason
= "OK";
690 h2o_add_header(&du
->req
->pool
, &du
->req
->res
.headers
, H2O_TOKEN_CONTENT_TYPE
, nullptr, H2O_STRLIT("application/dns-message"));
692 // struct dnsheader* dh = (struct dnsheader*)du->query.c_str();
693 // 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;
695 du
->req
->res
.content_length
= du
->response
.size();
696 h2o_send_inline(du
->req
, du
->response
.c_str(), du
->response
.size());
699 switch(du
->status_code
) {
701 h2o_send_error_400(du
->req
, "Bad Request", "invalid DNS query", 0);
704 h2o_send_error_403(du
->req
, "Forbidden", "dns query not allowed", 0);
707 h2o_send_error_502(du
->req
, "Bad Gateway", "no downstream server available", 0);
712 h2o_send_error_500(du
->req
, "Internal Server Error", "Internal Server Error", 0);
716 ++dsc
->df
->d_errorresponses
;
722 static void on_accept(h2o_socket_t
*listener
, const char *err
)
724 DOHServerConfig
* dsc
= reinterpret_cast<DOHServerConfig
*>(listener
->data
);
725 h2o_socket_t
*sock
= nullptr;
727 if (err
!= nullptr) {
730 // do some dnsdist rules here to filter based on IP address
731 if ((sock
= h2o_evloop_socket_accept(listener
)) == nullptr) {
736 h2o_socket_getpeername(sock
, reinterpret_cast<struct sockaddr
*>(&remote
));
737 // cout<<"New HTTP accept for client "<<remote.toStringWithPort()<<": "<< listener->data << endl;
740 sock
->on_close
.cb
= on_socketclose
;
741 auto accept_ctx
= dsc
->accept_ctx
->get();
742 sock
->on_close
.data
= dsc
->accept_ctx
;
743 ++dsc
->df
->d_httpconnects
;
744 h2o_accept(accept_ctx
, sock
);
747 static int create_listener(const ComboAddress
& addr
, std::shared_ptr
<DOHServerConfig
>& dsc
, int fd
)
749 auto sock
= h2o_evloop_socket_create(dsc
->h2o_ctx
.loop
, fd
, H2O_SOCKET_FLAG_DONT_READ
);
750 sock
->data
= dsc
.get();
751 h2o_socket_read_start(sock
, on_accept
);
756 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
)
758 auto ctx
= std::unique_ptr
<SSL_CTX
, void(*)(SSL_CTX
*)>(SSL_CTX_new(SSLv23_server_method()), SSL_CTX_free
);
763 SSL_OP_NO_COMPRESSION
|
764 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
|
765 SSL_OP_SINGLE_DH_USE
|
766 SSL_OP_SINGLE_ECDH_USE
;
768 SSL_CTX_set_options(ctx
.get(), sslOptions
);
770 #ifdef SSL_CTX_set_ecdh_auto
771 SSL_CTX_set_ecdh_auto(ctx
.get(), 1);
774 /* load certificate and private key */
775 for (const auto& pair
: pairs
) {
776 if (SSL_CTX_use_certificate_chain_file(ctx
.get(), pair
.first
.c_str()) != 1) {
777 ERR_print_errors_fp(stderr
);
778 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
);
780 if (SSL_CTX_use_PrivateKey_file(ctx
.get(), pair
.second
.c_str(), SSL_FILETYPE_PEM
) != 1) {
781 ERR_print_errors_fp(stderr
);
782 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
);
786 if (SSL_CTX_set_cipher_list(ctx
.get(), ciphers
.empty() == false ? ciphers
.c_str() : DOH_DEFAULT_CIPHERS
) != 1) {
787 throw std::runtime_error("Failed to setup SSL/TLS for DoH listener, DOH ciphers could not be set: " + ciphers
);
790 #ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
791 if (!ciphers13
.empty() && SSL_CTX_set_ciphersuites(ctx
.get(), ciphers13
.c_str()) != 1) {
792 throw std::runtime_error("Failed to setup SSL/TLS for DoH listener, DOH TLS 1.3 ciphers could not be set: " + ciphers13
);
794 #endif /* HAVE_SSL_CTX_SET_CIPHERSUITES */
796 h2o_ssl_register_alpn_protocols(ctx
.get(), h2o_http2_alpn_protocols
);
801 static void setupAcceptContext(DOHAcceptContext
& ctx
, DOHServerConfig
& dsc
, bool setupTLS
)
803 auto nativeCtx
= ctx
.get();
804 nativeCtx
->ctx
= &dsc
.h2o_ctx
;
805 nativeCtx
->hosts
= dsc
.h2o_config
.hosts
;
807 auto tlsCtx
= getTLSContext(dsc
.df
->d_certKeyPairs
,
809 dsc
.df
->d_ciphers13
);
811 nativeCtx
->ssl_ctx
= tlsCtx
.release();
817 void DOHFrontend::reloadCertificates()
819 auto newAcceptContext
= std::unique_ptr
<DOHAcceptContext
>(new DOHAcceptContext());
820 setupAcceptContext(*newAcceptContext
, *d_dsc
, true);
821 DOHAcceptContext
* oldCtx
= d_dsc
->accept_ctx
;
822 d_dsc
->accept_ctx
= newAcceptContext
.release();
826 void DOHFrontend::setup()
828 registerOpenSSLUser();
830 d_dsc
= std::make_shared
<DOHServerConfig
>(d_idleTimeout
);
832 auto tlsCtx
= getTLSContext(d_certKeyPairs
,
836 auto accept_ctx
= d_dsc
->accept_ctx
->get();
837 accept_ctx
->ssl_ctx
= tlsCtx
.release();
838 d_dsc
->accept_ctx
->release();
841 // this is the entrypoint from dnsdist.cc
842 void dohThread(ClientState
* cs
)
845 std::shared_ptr
<DOHFrontend
>& df
= cs
->dohFrontend
;
846 auto& dsc
= df
->d_dsc
;
848 dsc
->df
= cs
->dohFrontend
;
849 dsc
->h2o_config
.server_name
= h2o_iovec_init(df
->d_serverTokens
.c_str(), df
->d_serverTokens
.size());
852 std::thread
dnsdistThread(dnsdistclient
, dsc
->dohquerypair
[1], dsc
->dohresponsepair
[0]);
853 dnsdistThread
.detach(); // gets us better error reporting
855 setThreadName("dnsdist/doh");
856 // I wonder if this registers an IP address.. I think it does
857 // this may mean we need to actually register a site "name" here and not the IP address
858 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);
860 for(const auto& url
: df
->d_urls
) {
861 register_handler(hostconf
, url
.c_str(), doh_handler
);
864 h2o_context_init(&dsc
->h2o_ctx
, h2o_evloop_create(), &dsc
->h2o_config
);
866 // in this complicated way we insert the DOHServerConfig pointer in there
867 h2o_vector_reserve(nullptr, &dsc
->h2o_ctx
.storage
, 1);
868 dsc
->h2o_ctx
.storage
.entries
[0].data
= dsc
.get();
869 ++dsc
->h2o_ctx
.storage
.size
;
871 auto sock
= h2o_evloop_socket_create(dsc
->h2o_ctx
.loop
, dsc
->dohresponsepair
[1], H2O_SOCKET_FLAG_DONT_READ
);
872 sock
->data
= dsc
.get();
874 // this listens to responses from dnsdist to turn into http responses
875 h2o_socket_read_start(sock
, on_dnsdist
);
877 setupAcceptContext(*dsc
->accept_ctx
, *dsc
, false);
879 if (create_listener(df
->d_local
, dsc
, cs
->tcpFD
) != 0) {
880 throw std::runtime_error("DOH server failed to listen on " + df
->d_local
.toStringWithPort() + ": " + strerror(errno
));
885 int result
= h2o_evloop_run(dsc
->h2o_ctx
.loop
, INT32_MAX
);
887 if (errno
!= EINTR
) {
888 errlog("Error in the DoH event loop: %s", strerror(errno
));
893 while (stop
== false);
896 catch(const std::exception
& e
) {
897 throw runtime_error("DOH thread failed to launch: " + std::string(e
.what()));
900 throw runtime_error("DOH thread failed to launch");
903 #else /* HAVE_DNS_OVER_HTTPS */
905 void handleDOHTimeout(DOHUnit
* oldDU
)
909 #endif /* HAVE_DNS_OVER_HTTPS */