2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include <boost/format.hpp>
26 #include <sys/resource.h>
29 #include "ext/incbin/incbin.h"
30 #include "ext/json11/json11.hpp"
31 #include <yahttp/yahttp.hpp>
35 #include "dnsdist-healthchecks.hh"
36 #include "dnsdist-prometheus.hh"
37 #include "dnsdist-web.hh"
40 #include "htmlfiles.h"
41 #include "threadname.hh"
44 bool g_apiReadWrite
{false};
45 WebserverConfig g_webserverConfig
;
46 std::string g_apiConfigDirectory
;
47 static const MetricDefinitionStorage s_metricDefinitions
;
49 const std::map
<std::string
, MetricDefinition
> MetricDefinitionStorage::metrics
{
50 { "responses", MetricDefinition(PrometheusMetricType::counter
, "Number of responses received from backends") },
51 { "servfail-responses", MetricDefinition(PrometheusMetricType::counter
, "Number of SERVFAIL answers received from backends") },
52 { "queries", MetricDefinition(PrometheusMetricType::counter
, "Number of received queries")},
53 { "frontend-nxdomain", MetricDefinition(PrometheusMetricType::counter
, "Number of NXDomain answers sent to clients")},
54 { "frontend-servfail", MetricDefinition(PrometheusMetricType::counter
, "Number of SERVFAIL answers sent to clients")},
55 { "frontend-noerror", MetricDefinition(PrometheusMetricType::counter
, "Number of NoError answers sent to clients")},
56 { "acl-drops", MetricDefinition(PrometheusMetricType::counter
, "Number of packets dropped because of the ACL")},
57 { "rule-drop", MetricDefinition(PrometheusMetricType::counter
, "Number of queries dropped because of a rule")},
58 { "rule-nxdomain", MetricDefinition(PrometheusMetricType::counter
, "Number of NXDomain answers returned because of a rule")},
59 { "rule-refused", MetricDefinition(PrometheusMetricType::counter
, "Number of Refused answers returned because of a rule")},
60 { "rule-servfail", MetricDefinition(PrometheusMetricType::counter
, "Number of SERVFAIL answers received because of a rule")},
61 { "self-answered", MetricDefinition(PrometheusMetricType::counter
, "Number of self-answered responses")},
62 { "downstream-timeouts", MetricDefinition(PrometheusMetricType::counter
, "Number of queries not answered in time by a backend")},
63 { "downstream-send-errors", MetricDefinition(PrometheusMetricType::counter
, "Number of errors when sending a query to a backend")},
64 { "trunc-failures", MetricDefinition(PrometheusMetricType::counter
, "Number of errors encountered while truncating an answer")},
65 { "no-policy", MetricDefinition(PrometheusMetricType::counter
, "Number of queries dropped because no server was available")},
66 { "latency0-1", MetricDefinition(PrometheusMetricType::counter
, "Number of queries answered in less than 1ms")},
67 { "latency1-10", MetricDefinition(PrometheusMetricType::counter
, "Number of queries answered in 1-10 ms")},
68 { "latency10-50", MetricDefinition(PrometheusMetricType::counter
, "Number of queries answered in 10-50 ms")},
69 { "latency50-100", MetricDefinition(PrometheusMetricType::counter
, "Number of queries answered in 50-100 ms")},
70 { "latency100-1000", MetricDefinition(PrometheusMetricType::counter
, "Number of queries answered in 100-1000 ms")},
71 { "latency-slow", MetricDefinition(PrometheusMetricType::counter
, "Number of queries answered in more than 1 second")},
72 { "latency-avg100", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency in microseconds of the last 100 packets")},
73 { "latency-avg1000", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency in microseconds of the last 1000 packets")},
74 { "latency-avg10000", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency in microseconds of the last 10000 packets")},
75 { "latency-avg1000000", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency in microseconds of the last 1000000 packets")},
76 { "uptime", MetricDefinition(PrometheusMetricType::gauge
, "Uptime of the dnsdist process in seconds")},
77 { "real-memory-usage", MetricDefinition(PrometheusMetricType::gauge
, "Current memory usage in bytes")},
78 { "noncompliant-queries", MetricDefinition(PrometheusMetricType::counter
, "Number of queries dropped as non-compliant")},
79 { "noncompliant-responses", MetricDefinition(PrometheusMetricType::counter
, "Number of answers from a backend dropped as non-compliant")},
80 { "rdqueries", MetricDefinition(PrometheusMetricType::counter
, "Number of received queries with the recursion desired bit set")},
81 { "empty-queries", MetricDefinition(PrometheusMetricType::counter
, "Number of empty queries received from clients")},
82 { "cache-hits", MetricDefinition(PrometheusMetricType::counter
, "Number of times an answer was retrieved from cache")},
83 { "cache-misses", MetricDefinition(PrometheusMetricType::counter
, "Number of times an answer not found in the cache")},
84 { "cpu-iowait", MetricDefinition(PrometheusMetricType::counter
, "Time waiting for I/O to complete by the whole system, in units of USER_HZ")},
85 { "cpu-user-msec", MetricDefinition(PrometheusMetricType::counter
, "Milliseconds spent by dnsdist in the user state")},
86 { "cpu-steal", MetricDefinition(PrometheusMetricType::counter
, "Stolen time, which is the time spent by the whole system in other operating systems when running in a virtualized environment, in units of USER_HZ")},
87 { "cpu-sys-msec", MetricDefinition(PrometheusMetricType::counter
, "Milliseconds spent by dnsdist in the system state")},
88 { "fd-usage", MetricDefinition(PrometheusMetricType::gauge
, "Number of currently used file descriptors")},
89 { "dyn-blocked", MetricDefinition(PrometheusMetricType::counter
, "Number of queries dropped because of a dynamic block")},
90 { "dyn-block-nmg-size", MetricDefinition(PrometheusMetricType::gauge
, "Number of dynamic blocks entries") },
91 { "security-status", MetricDefinition(PrometheusMetricType::gauge
, "Security status of this software. 0=unknown, 1=OK, 2=upgrade recommended, 3=upgrade mandatory") },
92 { "udp-in-errors", MetricDefinition(PrometheusMetricType::counter
, "From /proc/net/snmp InErrors") },
93 { "udp-noport-errors", MetricDefinition(PrometheusMetricType::counter
, "From /proc/net/snmp NoPorts") },
94 { "udp-recvbuf-errors", MetricDefinition(PrometheusMetricType::counter
, "From /proc/net/snmp RcvbufErrors") },
95 { "udp-sndbuf-errors", MetricDefinition(PrometheusMetricType::counter
, "From /proc/net/snmp SndbufErrors") },
98 static bool apiWriteConfigFile(const string
& filebasename
, const string
& content
)
100 if (!g_apiReadWrite
) {
101 errlog("Not writing content to %s since the API is read-only", filebasename
);
105 if (g_apiConfigDirectory
.empty()) {
106 vinfolog("Not writing content to %s since the API configuration directory is not set", filebasename
);
110 string filename
= g_apiConfigDirectory
+ "/" + filebasename
+ ".conf";
111 ofstream
ofconf(filename
.c_str());
113 errlog("Could not open configuration fragment file '%s' for writing: %s", filename
, stringerror());
116 ofconf
<< "-- Generated by the REST API, DO NOT EDIT" << endl
;
117 ofconf
<< content
<< endl
;
122 static void apiSaveACL(const NetmaskGroup
& nmg
)
125 nmg
.toStringVector(&vec
);
128 for(const auto& s
: vec
) {
132 acl
+= "\"" + s
+ "\"";
135 string content
= "setACL({" + acl
+ "})";
136 apiWriteConfigFile("acl", content
);
139 static bool checkAPIKey(const YaHTTP::Request
& req
, const string
& expectedApiKey
)
141 if (expectedApiKey
.empty()) {
145 const auto header
= req
.headers
.find("x-api-key");
146 if (header
!= req
.headers
.end()) {
147 return (header
->second
== expectedApiKey
);
153 static bool checkWebPassword(const YaHTTP::Request
& req
, const string
&expected_password
)
155 static const char basicStr
[] = "basic ";
157 const auto header
= req
.headers
.find("authorization");
159 if (header
!= req
.headers
.end() && toLower(header
->second
).find(basicStr
) == 0) {
160 string cookie
= header
->second
.substr(sizeof(basicStr
) - 1);
163 B64Decode(cookie
, plain
);
165 vector
<string
> cparts
;
166 stringtok(cparts
, plain
, ":");
168 if (cparts
.size() == 2) {
169 return cparts
[1] == expected_password
;
176 static bool isAnAPIRequest(const YaHTTP::Request
& req
)
178 return req
.url
.path
.find("/api/") == 0;
181 static bool isAnAPIRequestAllowedWithWebAuth(const YaHTTP::Request
& req
)
183 return req
.url
.path
== "/api/v1/servers/localhost";
186 static bool isAStatsRequest(const YaHTTP::Request
& req
)
188 return req
.url
.path
== "/jsonstat" || req
.url
.path
== "/metrics";
191 static bool compareAuthorization(const YaHTTP::Request
& req
)
193 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
195 if (isAnAPIRequest(req
)) {
196 /* Access to the API requires a valid API key */
197 if (checkAPIKey(req
, g_webserverConfig
.apiKey
)) {
201 return isAnAPIRequestAllowedWithWebAuth(req
) && checkWebPassword(req
, g_webserverConfig
.password
);
204 if (isAStatsRequest(req
)) {
205 /* Access to the stats is allowed for both API and Web users */
206 return checkAPIKey(req
, g_webserverConfig
.apiKey
) || checkWebPassword(req
, g_webserverConfig
.password
);
209 return checkWebPassword(req
, g_webserverConfig
.password
);
212 static bool isMethodAllowed(const YaHTTP::Request
& req
)
214 if (req
.method
== "GET") {
217 if (req
.method
== "PUT" && g_apiReadWrite
) {
218 if (req
.url
.path
== "/api/v1/servers/localhost/config/allow-from") {
225 static bool isClientAllowedByACL(const ComboAddress
& remote
)
227 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
228 return g_webserverConfig
.acl
.match(remote
);
231 static void handleCORS(const YaHTTP::Request
& req
, YaHTTP::Response
& resp
)
233 const auto origin
= req
.headers
.find("Origin");
234 if (origin
!= req
.headers
.end()) {
235 if (req
.method
== "OPTIONS") {
236 /* Pre-flight request */
237 if (g_apiReadWrite
) {
238 resp
.headers
["Access-Control-Allow-Methods"] = "GET, PUT";
241 resp
.headers
["Access-Control-Allow-Methods"] = "GET";
243 resp
.headers
["Access-Control-Allow-Headers"] = "Authorization, X-API-Key";
246 resp
.headers
["Access-Control-Allow-Origin"] = origin
->second
;
248 if (isAStatsRequest(req
) || isAnAPIRequestAllowedWithWebAuth(req
)) {
249 resp
.headers
["Access-Control-Allow-Credentials"] = "true";
254 static void addSecurityHeaders(YaHTTP::Response
& resp
, const boost::optional
<std::map
<std::string
, std::string
> >& customHeaders
)
256 static const std::vector
<std::pair
<std::string
, std::string
> > headers
= {
257 { "X-Content-Type-Options", "nosniff" },
258 { "X-Frame-Options", "deny" },
259 { "X-Permitted-Cross-Domain-Policies", "none" },
260 { "X-XSS-Protection", "1; mode=block" },
261 { "Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'" },
264 for (const auto& h
: headers
) {
266 const auto& custom
= customHeaders
->find(h
.first
);
267 if (custom
!= customHeaders
->end()) {
271 resp
.headers
[h
.first
] = h
.second
;
275 static void addCustomHeaders(YaHTTP::Response
& resp
, const boost::optional
<std::map
<std::string
, std::string
> >& customHeaders
)
280 for (const auto& c
: *customHeaders
) {
281 if (!c
.second
.empty()) {
282 resp
.headers
[c
.first
] = c
.second
;
288 static json11::Json::array
someResponseRulesToJson(GlobalStateHolder
<vector
<T
>>* someResponseRules
)
290 using namespace json11
;
291 Json::array responseRules
;
293 auto localResponseRules
= someResponseRules
->getLocal();
294 for(const auto& a
: *localResponseRules
) {
297 {"creationOrder", (double)a
.d_creationOrder
},
298 {"uuid", boost::uuids::to_string(a
.d_id
)},
299 {"matches", (double)a
.d_rule
->d_matches
},
300 {"rule", a
.d_rule
->toString()},
301 {"action", a
.d_action
->toString()},
303 responseRules
.push_back(rule
);
305 return responseRules
;
308 static void connectionThread(int sock
, ComboAddress remote
)
310 setThreadName("dnsdist/webConn");
312 using namespace json11
;
313 vinfolog("Webserver handling connection from %s", remote
.toStringWithPort());
316 YaHTTP::AsyncRequestLoader yarl
;
318 bool finished
= false;
320 yarl
.initialize(&req
);
324 bytes
= read(sock
, buf
, sizeof(buf
));
326 string data
= string(buf
, bytes
);
327 finished
= yarl
.feed(data
);
335 string command
=req
.getvars
["command"];
337 req
.getvars
.erase("_"); // jQuery cache buster
339 YaHTTP::Response resp
;
340 resp
.version
= req
.version
;
341 const string charset
= "; charset=utf-8";
344 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
346 addCustomHeaders(resp
, g_webserverConfig
.customHeaders
);
347 addSecurityHeaders(resp
, g_webserverConfig
.customHeaders
);
349 /* indicate that the connection will be closed after completion of the response */
350 resp
.headers
["Connection"] = "close";
352 /* no need to send back the API key if any */
353 resp
.headers
.erase("X-API-Key");
355 if(req
.method
== "OPTIONS") {
356 /* the OPTIONS method should not require auth, otherwise it breaks CORS */
357 handleCORS(req
, resp
);
360 else if (!compareAuthorization(req
)) {
361 YaHTTP::strstr_map_t::iterator header
= req
.headers
.find("authorization");
362 if (header
!= req
.headers
.end())
363 errlog("HTTP Request \"%s\" from %s: Web Authentication failed", req
.url
.path
, remote
.toStringWithPort());
365 resp
.body
="<h1>Unauthorized</h1>";
366 resp
.headers
["WWW-Authenticate"] = "basic realm=\"PowerDNS\"";
369 else if(!isMethodAllowed(req
)) {
372 else if(req
.url
.path
=="/jsonstat") {
373 handleCORS(req
, resp
);
376 if(command
=="stats") {
377 auto obj
=Json::object
{
378 { "packetcache-hits", 0},
379 { "packetcache-misses", 0},
380 { "over-capacity-drops", 0 },
381 { "too-old-drops", 0 },
382 { "server-policy", g_policy
.getLocal()->name
}
385 for(const auto& e
: g_stats
.entries
) {
386 if (e
.first
== "special-memory-usage")
387 continue; // Too expensive for get-all
388 if(const auto& val
= boost::get
<DNSDistStats::stat_t
*>(&e
.second
))
389 obj
.insert({e
.first
, (double)(*val
)->load()});
390 else if (const auto& dval
= boost::get
<double*>(&e
.second
))
391 obj
.insert({e
.first
, (**dval
)});
393 obj
.insert({e
.first
, (double)(*boost::get
<DNSDistStats::statfunction_t
>(&e
.second
))(e
.first
)});
396 resp
.body
=my_json
.dump();
397 resp
.headers
["Content-Type"] = "application/json";
399 else if(command
=="dynblocklist") {
401 auto nmg
= g_dynblockNMG
.getLocal();
404 for(const auto& e
: *nmg
) {
405 if(now
< e
.second
.until
) {
407 {"reason", e
.second
.reason
},
408 {"seconds", (double)(e
.second
.until
.tv_sec
- now
.tv_sec
)},
409 {"blocks", (double)e
.second
.blocks
},
410 {"action", DNSAction::typeToString(e
.second
.action
!= DNSAction::Action::None
? e
.second
.action
: g_dynBlockAction
) },
411 {"warning", e
.second
.warning
}
413 obj
.insert({e
.first
.toString(), thing
});
417 auto smt
= g_dynblockSMT
.getLocal();
418 smt
->visit([&now
,&obj
](const SuffixMatchTree
<DynBlock
>& node
) {
419 if(now
<node
.d_value
.until
) {
421 if(!node
.d_value
.domain
.empty())
422 dom
= node
.d_value
.domain
.toString();
424 {"reason", node
.d_value
.reason
},
425 {"seconds", (double)(node
.d_value
.until
.tv_sec
- now
.tv_sec
)},
426 {"blocks", (double)node
.d_value
.blocks
},
427 {"action", DNSAction::typeToString(node
.d_value
.action
!= DNSAction::Action::None
? node
.d_value
.action
: g_dynBlockAction
) }
429 obj
.insert({dom
, thing
});
436 resp
.body
=my_json
.dump();
437 resp
.headers
["Content-Type"] = "application/json";
439 else if(command
=="ebpfblocklist") {
444 for (const auto& dynbpf
: g_dynBPFFilters
) {
445 std::vector
<std::tuple
<ComboAddress
, uint64_t, struct timespec
> > addrStats
= dynbpf
->getAddrStats();
446 for (const auto& entry
: addrStats
) {
449 {"seconds", (double)(std::get
<2>(entry
).tv_sec
- now
.tv_sec
)},
450 {"blocks", (double)(std::get
<1>(entry
))}
452 obj
.insert({std::get
<0>(entry
).toString(), thing
});
455 #endif /* HAVE_EBPF */
457 resp
.body
=my_json
.dump();
458 resp
.headers
["Content-Type"] = "application/json";
464 else if (req
.url
.path
== "/metrics") {
465 handleCORS(req
, resp
);
468 std::ostringstream output
;
469 static const std::set
<std::string
> metricBlacklist
= { "latency-count", "latency-sum" };
470 for (const auto& e
: g_stats
.entries
) {
471 if (e
.first
== "special-memory-usage")
472 continue; // Too expensive for get-all
473 std::string metricName
= std::get
<0>(e
);
475 // Prometheus suggest using '_' instead of '-'
476 std::string prometheusMetricName
= "dnsdist_" + boost::replace_all_copy(metricName
, "-", "_");
477 if (metricBlacklist
.count(metricName
) != 0) {
481 MetricDefinition metricDetails
;
482 if (!s_metricDefinitions
.getMetricDetails(metricName
, metricDetails
)) {
483 vinfolog("Do not have metric details for %s", metricName
);
487 std::string prometheusTypeName
= s_metricDefinitions
.getPrometheusStringMetricType(metricDetails
.prometheusType
);
489 if (prometheusTypeName
== "") {
490 vinfolog("Unknown Prometheus type for %s", metricName
);
494 // for these we have the help and types encoded in the sources:
495 output
<< "# HELP " << prometheusMetricName
<< " " << metricDetails
.description
<< "\n";
496 output
<< "# TYPE " << prometheusMetricName
<< " " << prometheusTypeName
<< "\n";
497 output
<< prometheusMetricName
<< " ";
499 if (const auto& val
= boost::get
<DNSDistStats::stat_t
*>(&std::get
<1>(e
)))
500 output
<< (*val
)->load();
501 else if (const auto& dval
= boost::get
<double*>(&std::get
<1>(e
)))
504 output
<< (*boost::get
<DNSDistStats::statfunction_t
>(&std::get
<1>(e
)))(std::get
<0>(e
));
509 // Latency histogram buckets
510 output
<< "# HELP dnsdist_latency Histogram of responses by latency (in milliseconds)\n";
511 output
<< "# TYPE dnsdist_latency histogram\n";
512 uint64_t latency_amounts
= g_stats
.latency0_1
;
513 output
<< "dnsdist_latency_bucket{le=\"1\"} " << latency_amounts
<< "\n";
514 latency_amounts
+= g_stats
.latency1_10
;
515 output
<< "dnsdist_latency_bucket{le=\"10\"} " << latency_amounts
<< "\n";
516 latency_amounts
+= g_stats
.latency10_50
;
517 output
<< "dnsdist_latency_bucket{le=\"50\"} " << latency_amounts
<< "\n";
518 latency_amounts
+= g_stats
.latency50_100
;
519 output
<< "dnsdist_latency_bucket{le=\"100\"} " << latency_amounts
<< "\n";
520 latency_amounts
+= g_stats
.latency100_1000
;
521 output
<< "dnsdist_latency_bucket{le=\"1000\"} " << latency_amounts
<< "\n";
522 latency_amounts
+= g_stats
.latencySlow
; // Should be the same as latency_count
523 output
<< "dnsdist_latency_bucket{le=\"+Inf\"} " << latency_amounts
<< "\n";
524 output
<< "dnsdist_latency_sum " << g_stats
.latencySum
<< "\n";
525 output
<< "dnsdist_latency_count " << getLatencyCount(std::string()) << "\n";
527 auto states
= g_dstates
.getLocal();
528 const string statesbase
= "dnsdist_server_";
530 output
<< "# HELP " << statesbase
<< "status " << "Whether this backend is up (1) or down (0)" << "\n";
531 output
<< "# TYPE " << statesbase
<< "status " << "gauge" << "\n";
532 output
<< "# HELP " << statesbase
<< "queries " << "Amount of queries relayed to server" << "\n";
533 output
<< "# TYPE " << statesbase
<< "queries " << "counter" << "\n";
534 output
<< "# HELP " << statesbase
<< "responses " << "Amount of responses received from this server" << "\n";
535 output
<< "# TYPE " << statesbase
<< "responses " << "counter" << "\n";
536 output
<< "# HELP " << statesbase
<< "drops " << "Amount of queries not answered by server" << "\n";
537 output
<< "# TYPE " << statesbase
<< "drops " << "counter" << "\n";
538 output
<< "# HELP " << statesbase
<< "latency " << "Server's latency when answering questions in milliseconds" << "\n";
539 output
<< "# TYPE " << statesbase
<< "latency " << "gauge" << "\n";
540 output
<< "# HELP " << statesbase
<< "senderrors " << "Total number of OS send errors while relaying queries" << "\n";
541 output
<< "# TYPE " << statesbase
<< "senderrors " << "counter" << "\n";
542 output
<< "# HELP " << statesbase
<< "outstanding " << "Current number of queries that are waiting for a backend response" << "\n";
543 output
<< "# TYPE " << statesbase
<< "outstanding " << "gauge" << "\n";
544 output
<< "# HELP " << statesbase
<< "order " << "The order in which this server is picked" << "\n";
545 output
<< "# TYPE " << statesbase
<< "order " << "gauge" << "\n";
546 output
<< "# HELP " << statesbase
<< "weight " << "The weight within the order in which this server is picked" << "\n";
547 output
<< "# TYPE " << statesbase
<< "weight " << "gauge" << "\n";
548 output
<< "# HELP " << statesbase
<< "tcpdiedsendingquery " << "The number of TCP I/O errors while sending the query" << "\n";
549 output
<< "# TYPE " << statesbase
<< "tcpdiedsendingquery " << "counter" << "\n";
550 output
<< "# HELP " << statesbase
<< "tcpdiedreadingresponse " << "The number of TCP I/O errors while reading the response" << "\n";
551 output
<< "# TYPE " << statesbase
<< "tcpdiedreadingresponse " << "counter" << "\n";
552 output
<< "# HELP " << statesbase
<< "tcpgaveup " << "The number of TCP connections failing after too many attempts" << "\n";
553 output
<< "# TYPE " << statesbase
<< "tcpgaveup " << "counter" << "\n";
554 output
<< "# HELP " << statesbase
<< "tcpreadtimeouts " << "The number of TCP read timeouts" << "\n";
555 output
<< "# TYPE " << statesbase
<< "tcpreadtimeouts " << "counter" << "\n";
556 output
<< "# HELP " << statesbase
<< "tcpwritetimeouts " << "The number of TCP write timeouts" << "\n";
557 output
<< "# TYPE " << statesbase
<< "tcpwritetimeouts " << "counter" << "\n";
558 output
<< "# HELP " << statesbase
<< "tcpcurrentconnections " << "The number of current TCP connections" << "\n";
559 output
<< "# TYPE " << statesbase
<< "tcpcurrentconnections " << "gauge" << "\n";
560 output
<< "# HELP " << statesbase
<< "tcpavgqueriesperconn " << "The average number of queries per TCP connection" << "\n";
561 output
<< "# TYPE " << statesbase
<< "tcpavgqueriesperconn " << "gauge" << "\n";
562 output
<< "# HELP " << statesbase
<< "tcpavgconnduration " << "The average duration of a TCP connection (ms)" << "\n";
563 output
<< "# TYPE " << statesbase
<< "tcpavgconnduration " << "gauge" << "\n";
565 for (const auto& state
: *states
) {
568 if (state
->getName().empty())
569 serverName
= state
->remote
.toStringWithPort();
571 serverName
= state
->getName();
573 boost::replace_all(serverName
, ".", "_");
575 const std::string label
= boost::str(boost::format("{server=\"%1%\",address=\"%2%\"}")
576 % serverName
% state
->remote
.toStringWithPort());
578 output
<< statesbase
<< "status" << label
<< " " << (state
->isUp() ? "1" : "0") << "\n";
579 output
<< statesbase
<< "queries" << label
<< " " << state
->queries
.load() << "\n";
580 output
<< statesbase
<< "responses" << label
<< " " << state
->responses
.load() << "\n";
581 output
<< statesbase
<< "drops" << label
<< " " << state
->reuseds
.load() << "\n";
582 output
<< statesbase
<< "latency" << label
<< " " << state
->latencyUsec
/1000.0 << "\n";
583 output
<< statesbase
<< "senderrors" << label
<< " " << state
->sendErrors
.load() << "\n";
584 output
<< statesbase
<< "outstanding" << label
<< " " << state
->outstanding
.load() << "\n";
585 output
<< statesbase
<< "order" << label
<< " " << state
->order
<< "\n";
586 output
<< statesbase
<< "weight" << label
<< " " << state
->weight
<< "\n";
587 output
<< statesbase
<< "tcpdiedsendingquery" << label
<< " " << state
->tcpDiedSendingQuery
<< "\n";
588 output
<< statesbase
<< "tcpdiedreadingresponse" << label
<< " " << state
->tcpDiedReadingResponse
<< "\n";
589 output
<< statesbase
<< "tcpgaveup" << label
<< " " << state
->tcpGaveUp
<< "\n";
590 output
<< statesbase
<< "tcpreadtimeouts" << label
<< " " << state
->tcpReadTimeouts
<< "\n";
591 output
<< statesbase
<< "tcpwritetimeouts" << label
<< " " << state
->tcpWriteTimeouts
<< "\n";
592 output
<< statesbase
<< "tcpcurrentconnections" << label
<< " " << state
->tcpCurrentConnections
<< "\n";
593 output
<< statesbase
<< "tcpavgqueriesperconn" << label
<< " " << state
->tcpAvgQueriesPerConnection
<< "\n";
594 output
<< statesbase
<< "tcpavgconnduration" << label
<< " " << state
->tcpAvgConnectionDuration
<< "\n";
597 const string frontsbase
= "dnsdist_frontend_";
598 output
<< "# HELP " << frontsbase
<< "queries " << "Amount of queries received by this frontend" << "\n";
599 output
<< "# TYPE " << frontsbase
<< "queries " << "counter" << "\n";
600 output
<< "# HELP " << frontsbase
<< "responses " << "Amount of responses sent by this frontend" << "\n";
601 output
<< "# TYPE " << frontsbase
<< "responses " << "counter" << "\n";
602 output
<< "# HELP " << frontsbase
<< "tcpdiedreadingquery " << "Amount of TCP connections terminated while reading the query from the client" << "\n";
603 output
<< "# TYPE " << frontsbase
<< "tcpdiedreadingquery " << "counter" << "\n";
604 output
<< "# HELP " << frontsbase
<< "tcpdiedsendingresponse " << "Amount of TCP connections terminated while sending a response to the client" << "\n";
605 output
<< "# TYPE " << frontsbase
<< "tcpdiedsendingresponse " << "counter" << "\n";
606 output
<< "# HELP " << frontsbase
<< "tcpgaveup " << "Amount of TCP connections terminated after too many attempts to get a connection to the backend" << "\n";
607 output
<< "# TYPE " << frontsbase
<< "tcpgaveup " << "counter" << "\n";
608 output
<< "# HELP " << frontsbase
<< "tcpclientimeouts " << "Amount of TCP connections terminated by a timeout while reading from the client" << "\n";
609 output
<< "# TYPE " << frontsbase
<< "tcpclientimeouts " << "counter" << "\n";
610 output
<< "# HELP " << frontsbase
<< "tcpdownstreamtimeouts " << "Amount of TCP connections terminated by a timeout while reading from the backend" << "\n";
611 output
<< "# TYPE " << frontsbase
<< "tcpdownstreamtimeouts " << "counter" << "\n";
612 output
<< "# HELP " << frontsbase
<< "tcpcurrentconnections " << "Amount of current incoming TCP connections from clients" << "\n";
613 output
<< "# TYPE " << frontsbase
<< "tcpcurrentconnections " << "gauge" << "\n";
614 output
<< "# HELP " << frontsbase
<< "tcpavgqueriesperconnection " << "The average number of queries per TCP connection" << "\n";
615 output
<< "# TYPE " << frontsbase
<< "tcpavgqueriesperconnection " << "gauge" << "\n";
616 output
<< "# HELP " << frontsbase
<< "tcpavgconnectionduration " << "The average duration of a TCP connection (ms)" << "\n";
617 output
<< "# TYPE " << frontsbase
<< "tcpavgconnectionduration " << "gauge" << "\n";
618 output
<< "# HELP " << frontsbase
<< "tlsqueries " << "Number of queries received by dnsdist over TLS, by TLS version" << "\n";
619 output
<< "# TYPE " << frontsbase
<< "tlsqueries " << "counter" << "\n";
620 output
<< "# HELP " << frontsbase
<< "tlsnewsessions " << "Amount of new TLS sessions negotiated" << "\n";
621 output
<< "# TYPE " << frontsbase
<< "tlsnewsessions " << "counter" << "\n";
622 output
<< "# HELP " << frontsbase
<< "tlsresumptions " << "Amount of TLS sessions resumed" << "\n";
623 output
<< "# TYPE " << frontsbase
<< "tlsresumptions " << "counter" << "\n";
624 output
<< "# HELP " << frontsbase
<< "tlsunknownticketkeys " << "Amount of attempts to resume TLS session from an unknown key (possibly expired)" << "\n";
625 output
<< "# TYPE " << frontsbase
<< "tlsunknownticketkeys " << "counter" << "\n";
626 output
<< "# HELP " << frontsbase
<< "tlsinactiveticketkeys " << "Amount of TLS sessions resumed from an inactive key" << "\n";
627 output
<< "# TYPE " << frontsbase
<< "tlsinactiveticketkeys " << "counter" << "\n";
629 output
<< "# HELP " << frontsbase
<< "tlshandshakefailures " << "Amount of TLS handshake failures" << "\n";
630 output
<< "# TYPE " << frontsbase
<< "tlshandshakefailures " << "counter" << "\n";
632 std::map
<std::string
,uint64_t> frontendDuplicates
;
633 for (const auto& front
: g_frontends
) {
634 if (front
->udpFD
== -1 && front
->tcpFD
== -1)
637 const string frontName
= front
->local
.toString() + ":" + std::to_string(front
->local
.getPort());
638 const string proto
= front
->getType();
639 const string fullName
= frontName
+ "_" + proto
;
640 uint64_t threadNumber
= 0;
641 auto dupPair
= frontendDuplicates
.insert({fullName
, 1});
642 if (!dupPair
.second
) {
643 threadNumber
= dupPair
.first
->second
;
644 ++(dupPair
.first
->second
);
646 const std::string label
= boost::str(boost::format("{frontend=\"%1%\",proto=\"%2%\",thread=\"%3%\"} ")
647 % frontName
% proto
% threadNumber
);
649 output
<< frontsbase
<< "queries" << label
<< front
->queries
.load() << "\n";
650 output
<< frontsbase
<< "responses" << label
<< front
->responses
.load() << "\n";
651 if (front
->isTCP()) {
652 output
<< frontsbase
<< "tcpdiedreadingquery" << label
<< front
->tcpDiedReadingQuery
.load() << "\n";
653 output
<< frontsbase
<< "tcpdiedsendingresponse" << label
<< front
->tcpDiedSendingResponse
.load() << "\n";
654 output
<< frontsbase
<< "tcpgaveup" << label
<< front
->tcpGaveUp
.load() << "\n";
655 output
<< frontsbase
<< "tcpclientimeouts" << label
<< front
->tcpClientTimeouts
.load() << "\n";
656 output
<< frontsbase
<< "tcpdownstreamtimeouts" << label
<< front
->tcpDownstreamTimeouts
.load() << "\n";
657 output
<< frontsbase
<< "tcpcurrentconnections" << label
<< front
->tcpCurrentConnections
.load() << "\n";
658 output
<< frontsbase
<< "tcpavgqueriesperconnection" << label
<< front
->tcpAvgQueriesPerConnection
.load() << "\n";
659 output
<< frontsbase
<< "tcpavgconnectionduration" << label
<< front
->tcpAvgConnectionDuration
.load() << "\n";
660 if (front
->hasTLS()) {
661 output
<< frontsbase
<< "tlsnewsessions" << label
<< front
->tlsNewSessions
.load() << "\n";
662 output
<< frontsbase
<< "tlsresumptions" << label
<< front
->tlsResumptions
.load() << "\n";
663 output
<< frontsbase
<< "tlsunknownticketkeys" << label
<< front
->tlsUnknownTicketKey
.load() << "\n";
664 output
<< frontsbase
<< "tlsinactiveticketkeys" << label
<< front
->tlsInactiveTicketKey
.load() << "\n";
666 output
<< frontsbase
<< "tlsqueries{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",tls=\"tls10\"} " << front
->tls10queries
.load() << "\n";
667 output
<< frontsbase
<< "tlsqueries{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",tls=\"tls11\"} " << front
->tls11queries
.load() << "\n";
668 output
<< frontsbase
<< "tlsqueries{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",tls=\"tls12\"} " << front
->tls12queries
.load() << "\n";
669 output
<< frontsbase
<< "tlsqueries{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",tls=\"tls13\"} " << front
->tls13queries
.load() << "\n";
670 output
<< frontsbase
<< "tlsqueries{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",tls=\"unknown\"} " << front
->tlsUnknownqueries
.load() << "\n";
672 const TLSErrorCounters
* errorCounters
= nullptr;
673 if (front
->tlsFrontend
!= nullptr) {
674 errorCounters
= &front
->tlsFrontend
->d_tlsCounters
;
676 else if (front
->dohFrontend
!= nullptr) {
677 errorCounters
= &front
->dohFrontend
->d_tlsCounters
;
680 if (errorCounters
!= nullptr) {
681 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"dhKeyTooSmall\"} " << errorCounters
->d_dhKeyTooSmall
<< "\n";
682 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"inappropriateFallBack\"} " << errorCounters
->d_inappropriateFallBack
<< "\n";
683 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"noSharedCipher\"} " << errorCounters
->d_noSharedCipher
<< "\n";
684 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"unknownCipherType\"} " << errorCounters
->d_unknownCipherType
<< "\n";
685 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"unknownKeyExchangeType\"} " << errorCounters
->d_unknownKeyExchangeType
<< "\n";
686 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"unknownProtocol\"} " << errorCounters
->d_unknownProtocol
<< "\n";
687 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"unsupportedEC\"} " << errorCounters
->d_unsupportedEC
<< "\n";
688 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"unsupportedProtocol{\"} " << errorCounters
->d_unsupportedProtocol
<< "\n";
694 output
<< "# HELP " << frontsbase
<< "http_connects " << "Number of DoH TCP connections established to this frontend" << "\n";
695 output
<< "# TYPE " << frontsbase
<< "http_connects " << "counter" << "\n";
697 output
<< "# HELP " << frontsbase
<< "doh_http_method_queries " << "Number of DoH queries received by dnsdist, by HTTP method" << "\n";
698 output
<< "# TYPE " << frontsbase
<< "doh_http_method_queries " << "counter" << "\n";
700 output
<< "# HELP " << frontsbase
<< "doh_http_version_queries " << "Number of DoH queries received by dnsdist, by HTTP version" << "\n";
701 output
<< "# TYPE " << frontsbase
<< "doh_http_version_queries " << "counter" << "\n";
703 output
<< "# HELP " << frontsbase
<< "doh_bad_requests " << "Number of requests that could not be converted to a DNS query" << "\n";
704 output
<< "# TYPE " << frontsbase
<< "doh_bad_requests " << "counter" << "\n";
706 output
<< "# HELP " << frontsbase
<< "doh_responses " << "Number of responses sent, by type" << "\n";
707 output
<< "# TYPE " << frontsbase
<< "doh_responses " << "counter" << "\n";
709 output
<< "# HELP " << frontsbase
<< "doh_version_status_responses " << "Number of requests that could not be converted to a DNS query" << "\n";
710 output
<< "# TYPE " << frontsbase
<< "doh_version_status_responses " << "counter" << "\n";
712 #ifdef HAVE_DNS_OVER_HTTPS
713 std::map
<std::string
,uint64_t> dohFrontendDuplicates
;
714 for(const auto& doh
: g_dohlocals
) {
715 const string frontName
= doh
->d_local
.toStringWithPort();
716 uint64_t threadNumber
= 0;
717 auto dupPair
= frontendDuplicates
.insert({frontName
, 1});
718 if (!dupPair
.second
) {
719 threadNumber
= dupPair
.first
->second
;
720 ++(dupPair
.first
->second
);
722 const std::string addrlabel
= boost::str(boost::format("frontend=\"%1%\",thread=\"%2%\"") % frontName
% threadNumber
);
723 const std::string label
= "{" + addrlabel
+ "} ";
725 output
<< frontsbase
<< "http_connects" << label
<< doh
->d_httpconnects
<< "\n";
726 output
<< frontsbase
<< "doh_http_method_queries{method=\"get\"," << addrlabel
<< "} " << doh
->d_getqueries
<< "\n";
727 output
<< frontsbase
<< "doh_http_method_queries{method=\"post\"," << addrlabel
<< "} " << doh
->d_postqueries
<< "\n";
729 output
<< frontsbase
<< "doh_http_version_queries{version=\"1\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nbQueries
<< "\n";
730 output
<< frontsbase
<< "doh_http_version_queries{version=\"2\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nbQueries
<< "\n";
732 output
<< frontsbase
<< "doh_bad_requests{" << addrlabel
<< "} " << doh
->d_badrequests
<< "\n";
734 output
<< frontsbase
<< "doh_responses{type=\"error\"," << addrlabel
<< "} " << doh
->d_errorresponses
<< "\n";
735 output
<< frontsbase
<< "doh_responses{type=\"redirect\"," << addrlabel
<< "} " << doh
->d_redirectresponses
<< "\n";
736 output
<< frontsbase
<< "doh_responses{type=\"valid\"," << addrlabel
<< "} " << doh
->d_validresponses
<< "\n";
738 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"1\",status=\"200\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nb200Responses
<< "\n";
739 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"1\",status=\"400\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nb400Responses
<< "\n";
740 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"1\",status=\"403\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nb403Responses
<< "\n";
741 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"1\",status=\"500\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nb500Responses
<< "\n";
742 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"1\",status=\"502\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nb502Responses
<< "\n";
743 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"1\",status=\"other\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nbOtherResponses
<< "\n";
744 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"2\",status=\"200\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nb200Responses
<< "\n";
745 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"2\",status=\"400\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nb400Responses
<< "\n";
746 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"2\",status=\"403\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nb403Responses
<< "\n";
747 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"2\",status=\"500\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nb500Responses
<< "\n";
748 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"2\",status=\"502\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nb502Responses
<< "\n";
749 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"2\",status=\"other\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nbOtherResponses
<< "\n";
751 #endif /* HAVE_DNS_OVER_HTTPS */
753 auto localPools
= g_pools
.getLocal();
754 const string cachebase
= "dnsdist_pool_";
755 output
<< "# HELP dnsdist_pool_servers " << "Number of servers in that pool" << "\n";
756 output
<< "# TYPE dnsdist_pool_servers " << "gauge" << "\n";
757 output
<< "# HELP dnsdist_pool_active_servers " << "Number of available servers in that pool" << "\n";
758 output
<< "# TYPE dnsdist_pool_active_servers " << "gauge" << "\n";
760 output
<< "# HELP dnsdist_pool_cache_size " << "Maximum number of entries that this cache can hold" << "\n";
761 output
<< "# TYPE dnsdist_pool_cache_size " << "gauge" << "\n";
762 output
<< "# HELP dnsdist_pool_cache_entries " << "Number of entries currently present in that cache" << "\n";
763 output
<< "# TYPE dnsdist_pool_cache_entries " << "gauge" << "\n";
764 output
<< "# HELP dnsdist_pool_cache_hits " << "Number of hits from that cache" << "\n";
765 output
<< "# TYPE dnsdist_pool_cache_hits " << "counter" << "\n";
766 output
<< "# HELP dnsdist_pool_cache_misses " << "Number of misses from that cache" << "\n";
767 output
<< "# TYPE dnsdist_pool_cache_misses " << "counter" << "\n";
768 output
<< "# HELP dnsdist_pool_cache_deferred_inserts " << "Number of insertions into that cache skipped because it was already locked" << "\n";
769 output
<< "# TYPE dnsdist_pool_cache_deferred_inserts " << "counter" << "\n";
770 output
<< "# HELP dnsdist_pool_cache_deferred_lookups " << "Number of lookups into that cache skipped because it was already locked" << "\n";
771 output
<< "# TYPE dnsdist_pool_cache_deferred_lookups " << "counter" << "\n";
772 output
<< "# HELP dnsdist_pool_cache_lookup_collisions " << "Number of lookups into that cache that triggered a collision (same hash but different entry)" << "\n";
773 output
<< "# TYPE dnsdist_pool_cache_lookup_collisions " << "counter" << "\n";
774 output
<< "# HELP dnsdist_pool_cache_insert_collisions " << "Number of insertions into that cache that triggered a collision (same hash but different entry)" << "\n";
775 output
<< "# TYPE dnsdist_pool_cache_insert_collisions " << "counter" << "\n";
776 output
<< "# HELP dnsdist_pool_cache_ttl_too_shorts " << "Number of insertions into that cache skipped because the TTL of the answer was not long enough" << "\n";
777 output
<< "# TYPE dnsdist_pool_cache_ttl_too_shorts " << "counter" << "\n";
779 for (const auto& entry
: *localPools
) {
780 string poolName
= entry
.first
;
782 if (poolName
.empty()) {
783 poolName
= "_default_";
785 const string label
= "{pool=\"" + poolName
+ "\"}";
786 const std::shared_ptr
<ServerPool
> pool
= entry
.second
;
787 output
<< "dnsdist_pool_servers" << label
<< " " << pool
->countServers(false) << "\n";
788 output
<< "dnsdist_pool_active_servers" << label
<< " " << pool
->countServers(true) << "\n";
790 if (pool
->packetCache
!= nullptr) {
791 const auto& cache
= pool
->packetCache
;
793 output
<< cachebase
<< "cache_size" <<label
<< " " << cache
->getMaxEntries() << "\n";
794 output
<< cachebase
<< "cache_entries" <<label
<< " " << cache
->getEntriesCount() << "\n";
795 output
<< cachebase
<< "cache_hits" <<label
<< " " << cache
->getHits() << "\n";
796 output
<< cachebase
<< "cache_misses" <<label
<< " " << cache
->getMisses() << "\n";
797 output
<< cachebase
<< "cache_deferred_inserts" <<label
<< " " << cache
->getDeferredInserts() << "\n";
798 output
<< cachebase
<< "cache_deferred_lookups" <<label
<< " " << cache
->getDeferredLookups() << "\n";
799 output
<< cachebase
<< "cache_lookup_collisions" <<label
<< " " << cache
->getLookupCollisions() << "\n";
800 output
<< cachebase
<< "cache_insert_collisions" <<label
<< " " << cache
->getInsertCollisions() << "\n";
801 output
<< cachebase
<< "cache_ttl_too_shorts" <<label
<< " " << cache
->getTTLTooShorts() << "\n";
805 resp
.body
= output
.str();
806 resp
.headers
["Content-Type"] = "text/plain";
809 else if(req
.url
.path
=="/api/v1/servers/localhost") {
810 handleCORS(req
, resp
);
814 auto localServers
= g_dstates
.getLocal();
816 for(const auto& a
: *localServers
) {
818 if(a
->availability
== DownstreamState::Availability::Up
)
820 else if(a
->availability
== DownstreamState::Availability::Down
)
823 status
= (a
->upStatus
? "up" : "down");
826 for(const auto& p
: a
->pools
)
831 {"name", a
->getName()},
832 {"address", a
->remote
.toStringWithPort()},
834 {"qps", (double)a
->queryLoad
},
835 {"qpsLimit", (double)a
->qps
.getRate()},
836 {"outstanding", (double)a
->outstanding
},
837 {"reuseds", (double)a
->reuseds
},
838 {"weight", (double)a
->weight
},
839 {"order", (double)a
->order
},
841 {"latency", (double)(a
->latencyUsec
/1000.0)},
842 {"queries", (double)a
->queries
},
843 {"responses", (double)a
->responses
},
844 {"sendErrors", (double)a
->sendErrors
},
845 {"tcpDiedSendingQuery", (double)a
->tcpDiedSendingQuery
},
846 {"tcpDiedReadingResponse", (double)a
->tcpDiedReadingResponse
},
847 {"tcpGaveUp", (double)a
->tcpGaveUp
},
848 {"tcpReadTimeouts", (double)a
->tcpReadTimeouts
},
849 {"tcpWriteTimeouts", (double)a
->tcpWriteTimeouts
},
850 {"tcpCurrentConnections", (double)a
->tcpCurrentConnections
},
851 {"tcpAvgQueriesPerConnection", (double)a
->tcpAvgQueriesPerConnection
},
852 {"tcpAvgConnectionDuration", (double)a
->tcpAvgConnectionDuration
},
853 {"dropRate", (double)a
->dropRate
}
856 /* sending a latency for a DOWN server doesn't make sense */
857 if (a
->availability
== DownstreamState::Availability::Down
) {
858 server
["latency"] = nullptr;
861 servers
.push_back(server
);
864 Json::array frontends
;
866 for(const auto& front
: g_frontends
) {
867 if (front
->udpFD
== -1 && front
->tcpFD
== -1)
869 Json::object frontend
{
871 { "address", front
->local
.toStringWithPort() },
872 { "udp", front
->udpFD
>= 0 },
873 { "tcp", front
->tcpFD
>= 0 },
874 { "type", front
->getType() },
875 { "queries", (double) front
->queries
.load() },
876 { "responses", (double) front
->responses
.load() },
877 { "tcpDiedReadingQuery", (double) front
->tcpDiedReadingQuery
.load() },
878 { "tcpDiedSendingResponse", (double) front
->tcpDiedSendingResponse
.load() },
879 { "tcpGaveUp", (double) front
->tcpGaveUp
.load() },
880 { "tcpClientTimeouts", (double) front
->tcpClientTimeouts
},
881 { "tcpDownstreamTimeouts", (double) front
->tcpDownstreamTimeouts
},
882 { "tcpCurrentConnections", (double) front
->tcpCurrentConnections
},
883 { "tcpAvgQueriesPerConnection", (double) front
->tcpAvgQueriesPerConnection
},
884 { "tcpAvgConnectionDuration", (double) front
->tcpAvgConnectionDuration
},
885 { "tlsNewSessions", (double) front
->tlsNewSessions
},
886 { "tlsResumptions", (double) front
->tlsResumptions
},
887 { "tlsUnknownTicketKey", (double) front
->tlsUnknownTicketKey
},
888 { "tlsInactiveTicketKey", (double) front
->tlsInactiveTicketKey
},
889 { "tls10Queries", (double) front
->tls10queries
},
890 { "tls11Queries", (double) front
->tls11queries
},
891 { "tls12Queries", (double) front
->tls12queries
},
892 { "tls13Queries", (double) front
->tls13queries
},
893 { "tlsUnknownQueries", (double) front
->tlsUnknownqueries
},
895 const TLSErrorCounters
* errorCounters
= nullptr;
896 if (front
->tlsFrontend
!= nullptr) {
897 errorCounters
= &front
->tlsFrontend
->d_tlsCounters
;
899 else if (front
->dohFrontend
!= nullptr) {
900 errorCounters
= &front
->dohFrontend
->d_tlsCounters
;
902 if (errorCounters
!= nullptr) {
903 frontend
["tlsHandshakeFailuresDHKeyTooSmall"] = (double)errorCounters
->d_dhKeyTooSmall
;
904 frontend
["tlsHandshakeFailuresInappropriateFallBack"] = (double)errorCounters
->d_inappropriateFallBack
;
905 frontend
["tlsHandshakeFailuresNoSharedCipher"] = (double)errorCounters
->d_noSharedCipher
;
906 frontend
["tlsHandshakeFailuresUnknownCipher"] = (double)errorCounters
->d_unknownCipherType
;
907 frontend
["tlsHandshakeFailuresUnknownKeyExchangeType"] = (double)errorCounters
->d_unknownKeyExchangeType
;
908 frontend
["tlsHandshakeFailuresUnknownProtocol"] = (double)errorCounters
->d_unknownProtocol
;
909 frontend
["tlsHandshakeFailuresUnsupportedEC"] = (double)errorCounters
->d_unsupportedEC
;
910 frontend
["tlsHandshakeFailuresUnsupportedProtocol"] = (double)errorCounters
->d_unsupportedProtocol
;
912 frontends
.push_back(frontend
);
916 #ifdef HAVE_DNS_OVER_HTTPS
919 for(const auto& doh
: g_dohlocals
) {
922 { "address", doh
->d_local
.toStringWithPort() },
923 { "http-connects", (double) doh
->d_httpconnects
},
924 { "http1-queries", (double) doh
->d_http1Stats
.d_nbQueries
},
925 { "http2-queries", (double) doh
->d_http2Stats
.d_nbQueries
},
926 { "http1-200-responses", (double) doh
->d_http1Stats
.d_nb200Responses
},
927 { "http2-200-responses", (double) doh
->d_http2Stats
.d_nb200Responses
},
928 { "http1-400-responses", (double) doh
->d_http1Stats
.d_nb400Responses
},
929 { "http2-400-responses", (double) doh
->d_http2Stats
.d_nb400Responses
},
930 { "http1-403-responses", (double) doh
->d_http1Stats
.d_nb403Responses
},
931 { "http2-403-responses", (double) doh
->d_http2Stats
.d_nb403Responses
},
932 { "http1-500-responses", (double) doh
->d_http1Stats
.d_nb500Responses
},
933 { "http2-500-responses", (double) doh
->d_http2Stats
.d_nb500Responses
},
934 { "http1-502-responses", (double) doh
->d_http1Stats
.d_nb502Responses
},
935 { "http2-502-responses", (double) doh
->d_http2Stats
.d_nb502Responses
},
936 { "http1-other-responses", (double) doh
->d_http1Stats
.d_nbOtherResponses
},
937 { "http2-other-responses", (double) doh
->d_http2Stats
.d_nbOtherResponses
},
938 { "get-queries", (double) doh
->d_getqueries
},
939 { "post-queries", (double) doh
->d_postqueries
},
940 { "bad-requests", (double) doh
->d_badrequests
},
941 { "error-responses", (double) doh
->d_errorresponses
},
942 { "redirect-responses", (double) doh
->d_redirectresponses
},
943 { "valid-responses", (double) doh
->d_validresponses
}
948 #endif /* HAVE_DNS_OVER_HTTPS */
951 auto localPools
= g_pools
.getLocal();
953 for(const auto& pool
: *localPools
) {
954 const auto& cache
= pool
.second
->packetCache
;
957 { "name", pool
.first
},
958 { "serversCount", (double) pool
.second
->countServers(false) },
959 { "cacheSize", (double) (cache
? cache
->getMaxEntries() : 0) },
960 { "cacheEntries", (double) (cache
? cache
->getEntriesCount() : 0) },
961 { "cacheHits", (double) (cache
? cache
->getHits() : 0) },
962 { "cacheMisses", (double) (cache
? cache
->getMisses() : 0) },
963 { "cacheDeferredInserts", (double) (cache
? cache
->getDeferredInserts() : 0) },
964 { "cacheDeferredLookups", (double) (cache
? cache
->getDeferredLookups() : 0) },
965 { "cacheLookupCollisions", (double) (cache
? cache
->getLookupCollisions() : 0) },
966 { "cacheInsertCollisions", (double) (cache
? cache
->getInsertCollisions() : 0) },
967 { "cacheTTLTooShorts", (double) (cache
? cache
->getTTLTooShorts() : 0) }
969 pools
.push_back(entry
);
973 auto localRules
= g_rulactions
.getLocal();
975 for(const auto& a
: *localRules
) {
978 {"creationOrder", (double)a
.d_creationOrder
},
979 {"uuid", boost::uuids::to_string(a
.d_id
)},
980 {"matches", (double)a
.d_rule
->d_matches
},
981 {"rule", a
.d_rule
->toString()},
982 {"action", a
.d_action
->toString()},
983 {"action-stats", a
.d_action
->getStats()}
985 rules
.push_back(rule
);
988 auto responseRules
= someResponseRulesToJson(&g_resprulactions
);
989 auto cacheHitResponseRules
= someResponseRulesToJson(&g_cachehitresprulactions
);
990 auto selfAnsweredResponseRules
= someResponseRulesToJson(&g_selfansweredresprulactions
);
995 g_ACL
.getLocal()->toStringVector(&vec
);
997 for(const auto& s
: vec
) {
998 if(!acl
.empty()) acl
+= ", ";
1001 string localaddressesStr
;
1002 std::set
<std::string
> localaddresses
;
1003 for(const auto& front
: g_frontends
) {
1004 localaddresses
.insert(front
->local
.toStringWithPort());
1006 for (const auto& addr
: localaddresses
) {
1007 if (!localaddressesStr
.empty()) {
1008 localaddressesStr
+= ", ";
1010 localaddressesStr
+= addr
;
1013 Json my_json
= Json::object
{
1014 { "daemon_type", "dnsdist" },
1015 { "version", VERSION
},
1016 { "servers", servers
},
1017 { "frontends", frontends
},
1020 { "response-rules", responseRules
},
1021 { "cache-hit-response-rules", cacheHitResponseRules
},
1022 { "self-answered-response-rules", selfAnsweredResponseRules
},
1024 { "local", localaddressesStr
},
1025 { "dohFrontends", dohs
}
1027 resp
.headers
["Content-Type"] = "application/json";
1028 resp
.body
=my_json
.dump();
1030 else if(req
.url
.path
=="/api/v1/servers/localhost/statistics") {
1031 handleCORS(req
, resp
);
1035 for(const auto& item
: g_stats
.entries
) {
1036 if (item
.first
== "special-memory-usage")
1037 continue; // Too expensive for get-all
1039 if(const auto& val
= boost::get
<DNSDistStats::stat_t
*>(&item
.second
)) {
1040 doc
.push_back(Json::object
{
1041 { "type", "StatisticItem" },
1042 { "name", item
.first
},
1043 { "value", (double)(*val
)->load() }
1046 else if (const auto& dval
= boost::get
<double*>(&item
.second
)) {
1047 doc
.push_back(Json::object
{
1048 { "type", "StatisticItem" },
1049 { "name", item
.first
},
1050 { "value", (**dval
) }
1054 doc
.push_back(Json::object
{
1055 { "type", "StatisticItem" },
1056 { "name", item
.first
},
1057 { "value", (double)(*boost::get
<DNSDistStats::statfunction_t
>(&item
.second
))(item
.first
) }
1062 resp
.body
=my_json
.dump();
1063 resp
.headers
["Content-Type"] = "application/json";
1065 else if(req
.url
.path
=="/api/v1/servers/localhost/config") {
1066 handleCORS(req
, resp
);
1070 typedef boost::variant
<bool, double, std::string
> configentry_t
;
1071 std::vector
<std::pair
<std::string
, configentry_t
> > configEntries
{
1072 { "acl", g_ACL
.getLocal()->toString() },
1073 { "allow-empty-response", g_allowEmptyResponse
},
1074 { "control-socket", g_serverControl
.toStringWithPort() },
1075 { "ecs-override", g_ECSOverride
},
1076 { "ecs-source-prefix-v4", (double) g_ECSSourcePrefixV4
},
1077 { "ecs-source-prefix-v6", (double) g_ECSSourcePrefixV6
},
1078 { "fixup-case", g_fixupCase
},
1079 { "max-outstanding", (double) g_maxOutstanding
},
1080 { "server-policy", g_policy
.getLocal()->name
},
1081 { "stale-cache-entries-ttl", (double) g_staleCacheEntriesTTL
},
1082 { "tcp-recv-timeout", (double) g_tcpRecvTimeout
},
1083 { "tcp-send-timeout", (double) g_tcpSendTimeout
},
1084 { "truncate-tc", g_truncateTC
},
1085 { "verbose", g_verbose
},
1086 { "verbose-health-checks", g_verboseHealthChecks
}
1088 for(const auto& item
: configEntries
) {
1089 if (const auto& bval
= boost::get
<bool>(&item
.second
)) {
1090 doc
.push_back(Json::object
{
1091 { "type", "ConfigSetting" },
1092 { "name", item
.first
},
1096 else if (const auto& sval
= boost::get
<string
>(&item
.second
)) {
1097 doc
.push_back(Json::object
{
1098 { "type", "ConfigSetting" },
1099 { "name", item
.first
},
1103 else if (const auto& dval
= boost::get
<double>(&item
.second
)) {
1104 doc
.push_back(Json::object
{
1105 { "type", "ConfigSetting" },
1106 { "name", item
.first
},
1112 resp
.body
=my_json
.dump();
1113 resp
.headers
["Content-Type"] = "application/json";
1115 else if(req
.url
.path
=="/api/v1/servers/localhost/config/allow-from") {
1116 handleCORS(req
, resp
);
1118 resp
.headers
["Content-Type"] = "application/json";
1121 if (req
.method
== "PUT") {
1123 Json doc
= Json::parse(req
.body
, err
);
1125 if (!doc
.is_null()) {
1127 auto aclList
= doc
["value"];
1128 if (aclList
.is_array()) {
1130 for (auto value
: aclList
.array_items()) {
1132 nmg
.addMask(value
.string_value());
1133 } catch (NetmaskException
&e
) {
1139 if (resp
.status
== 200) {
1140 infolog("Updating the ACL via the API to %s", nmg
.toString());
1141 g_ACL
.setState(nmg
);
1153 if (resp
.status
== 200) {
1156 g_ACL
.getLocal()->toStringVector(&vec
);
1158 for(const auto& s
: vec
) {
1163 { "type", "ConfigSetting" },
1164 { "name", "allow-from" },
1168 resp
.body
=my_json
.dump();
1171 else if(!req
.url
.path
.empty() && g_urlmap
.count(req
.url
.path
.c_str()+1)) {
1172 resp
.body
.assign(g_urlmap
[req
.url
.path
.c_str()+1]);
1173 vector
<string
> parts
;
1174 stringtok(parts
, req
.url
.path
, ".");
1175 if(parts
.back() == "html")
1176 resp
.headers
["Content-Type"] = "text/html" + charset
;
1177 else if(parts
.back() == "css")
1178 resp
.headers
["Content-Type"] = "text/css" + charset
;
1179 else if(parts
.back() == "js")
1180 resp
.headers
["Content-Type"] = "application/javascript" + charset
;
1181 else if(parts
.back() == "png")
1182 resp
.headers
["Content-Type"] = "image/png";
1185 else if(req
.url
.path
=="/") {
1186 resp
.body
.assign(g_urlmap
["index.html"]);
1187 resp
.headers
["Content-Type"] = "text/html" + charset
;
1191 // cerr<<"404 for: "<<req.url.path<<endl;
1195 std::ostringstream ofs
;
1199 writen2(sock
, done
.c_str(), done
.size());
1204 catch(const YaHTTP::ParseError
& e
) {
1205 vinfolog("Webserver thread died with parse error exception while processing a request from %s: %s", remote
.toStringWithPort(), e
.what());
1208 catch(const std::exception
& e
) {
1209 errlog("Webserver thread died with exception while processing a request from %s: %s", remote
.toStringWithPort(), e
.what());
1213 errlog("Webserver thread died with exception while processing a request from %s", remote
.toStringWithPort());
1218 void setWebserverAPIKey(const boost::optional
<std::string
> apiKey
)
1220 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
1223 g_webserverConfig
.apiKey
= *apiKey
;
1225 g_webserverConfig
.apiKey
.clear();
1229 void setWebserverPassword(const std::string
& password
)
1231 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
1233 g_webserverConfig
.password
= password
;
1236 void setWebserverACL(const std::string
& acl
)
1238 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
1240 g_webserverConfig
.acl
.clear();
1241 g_webserverConfig
.acl
.toMasks(acl
);
1244 void setWebserverCustomHeaders(const boost::optional
<std::map
<std::string
, std::string
> > customHeaders
)
1246 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
1248 g_webserverConfig
.customHeaders
= customHeaders
;
1251 void dnsdistWebserverThread(int sock
, const ComboAddress
& local
)
1253 setThreadName("dnsdist/webserv");
1254 warnlog("Webserver launched on %s", local
.toStringWithPort());
1258 ComboAddress
remote(local
);
1259 int fd
= SAccept(sock
, remote
);
1260 if (!isClientAllowedByACL(remote
)) {
1261 vinfolog("Connection to webserver from client %s is not allowed, closing", remote
.toStringWithPort());
1265 vinfolog("Got a connection to the webserver from %s", remote
.toStringWithPort());
1266 std::thread
t(connectionThread
, fd
, remote
);
1269 catch (const std::exception
& e
) {
1270 errlog("Had an error accepting new webserver connection: %s", e
.what());