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 { "doh-query-pipe-full", MetricDefinition(PrometheusMetricType::counter
, "Number of DoH queries dropped because the internal pipe used to distribute queries was full") },
93 { "doh-response-pipe-full", MetricDefinition(PrometheusMetricType::counter
, "Number of DoH responses dropped because the internal pipe used to distribute responses was full") },
94 { "udp-in-errors", MetricDefinition(PrometheusMetricType::counter
, "From /proc/net/snmp InErrors") },
95 { "udp-noport-errors", MetricDefinition(PrometheusMetricType::counter
, "From /proc/net/snmp NoPorts") },
96 { "udp-recvbuf-errors", MetricDefinition(PrometheusMetricType::counter
, "From /proc/net/snmp RcvbufErrors") },
97 { "udp-sndbuf-errors", MetricDefinition(PrometheusMetricType::counter
, "From /proc/net/snmp SndbufErrors") },
100 static bool apiWriteConfigFile(const string
& filebasename
, const string
& content
)
102 if (!g_apiReadWrite
) {
103 errlog("Not writing content to %s since the API is read-only", filebasename
);
107 if (g_apiConfigDirectory
.empty()) {
108 vinfolog("Not writing content to %s since the API configuration directory is not set", filebasename
);
112 string filename
= g_apiConfigDirectory
+ "/" + filebasename
+ ".conf";
113 ofstream
ofconf(filename
.c_str());
115 errlog("Could not open configuration fragment file '%s' for writing: %s", filename
, stringerror());
118 ofconf
<< "-- Generated by the REST API, DO NOT EDIT" << endl
;
119 ofconf
<< content
<< endl
;
124 static void apiSaveACL(const NetmaskGroup
& nmg
)
127 nmg
.toStringVector(&vec
);
130 for(const auto& s
: vec
) {
134 acl
+= "\"" + s
+ "\"";
137 string content
= "setACL({" + acl
+ "})";
138 apiWriteConfigFile("acl", content
);
141 static bool checkAPIKey(const YaHTTP::Request
& req
, const string
& expectedApiKey
)
143 if (expectedApiKey
.empty()) {
147 const auto header
= req
.headers
.find("x-api-key");
148 if (header
!= req
.headers
.end()) {
149 return (header
->second
== expectedApiKey
);
155 static bool checkWebPassword(const YaHTTP::Request
& req
, const string
&expected_password
)
157 static const char basicStr
[] = "basic ";
159 const auto header
= req
.headers
.find("authorization");
161 if (header
!= req
.headers
.end() && toLower(header
->second
).find(basicStr
) == 0) {
162 string cookie
= header
->second
.substr(sizeof(basicStr
) - 1);
165 B64Decode(cookie
, plain
);
167 vector
<string
> cparts
;
168 stringtok(cparts
, plain
, ":");
170 if (cparts
.size() == 2) {
171 return cparts
[1] == expected_password
;
178 static bool isAnAPIRequest(const YaHTTP::Request
& req
)
180 return req
.url
.path
.find("/api/") == 0;
183 static bool isAnAPIRequestAllowedWithWebAuth(const YaHTTP::Request
& req
)
185 return req
.url
.path
== "/api/v1/servers/localhost";
188 static bool isAStatsRequest(const YaHTTP::Request
& req
)
190 return req
.url
.path
== "/jsonstat" || req
.url
.path
== "/metrics";
193 static bool compareAuthorization(const YaHTTP::Request
& req
)
195 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
197 if (isAnAPIRequest(req
)) {
198 /* Access to the API requires a valid API key */
199 if (checkAPIKey(req
, g_webserverConfig
.apiKey
)) {
203 return isAnAPIRequestAllowedWithWebAuth(req
) && checkWebPassword(req
, g_webserverConfig
.password
);
206 if (isAStatsRequest(req
)) {
207 /* Access to the stats is allowed for both API and Web users */
208 return checkAPIKey(req
, g_webserverConfig
.apiKey
) || checkWebPassword(req
, g_webserverConfig
.password
);
211 return checkWebPassword(req
, g_webserverConfig
.password
);
214 static bool isMethodAllowed(const YaHTTP::Request
& req
)
216 if (req
.method
== "GET") {
219 if (req
.method
== "PUT" && g_apiReadWrite
) {
220 if (req
.url
.path
== "/api/v1/servers/localhost/config/allow-from") {
227 static bool isClientAllowedByACL(const ComboAddress
& remote
)
229 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
230 return g_webserverConfig
.acl
.match(remote
);
233 static void handleCORS(const YaHTTP::Request
& req
, YaHTTP::Response
& resp
)
235 const auto origin
= req
.headers
.find("Origin");
236 if (origin
!= req
.headers
.end()) {
237 if (req
.method
== "OPTIONS") {
238 /* Pre-flight request */
239 if (g_apiReadWrite
) {
240 resp
.headers
["Access-Control-Allow-Methods"] = "GET, PUT";
243 resp
.headers
["Access-Control-Allow-Methods"] = "GET";
245 resp
.headers
["Access-Control-Allow-Headers"] = "Authorization, X-API-Key";
248 resp
.headers
["Access-Control-Allow-Origin"] = origin
->second
;
250 if (isAStatsRequest(req
) || isAnAPIRequestAllowedWithWebAuth(req
)) {
251 resp
.headers
["Access-Control-Allow-Credentials"] = "true";
256 static void addSecurityHeaders(YaHTTP::Response
& resp
, const boost::optional
<std::map
<std::string
, std::string
> >& customHeaders
)
258 static const std::vector
<std::pair
<std::string
, std::string
> > headers
= {
259 { "X-Content-Type-Options", "nosniff" },
260 { "X-Frame-Options", "deny" },
261 { "X-Permitted-Cross-Domain-Policies", "none" },
262 { "X-XSS-Protection", "1; mode=block" },
263 { "Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'" },
266 for (const auto& h
: headers
) {
268 const auto& custom
= customHeaders
->find(h
.first
);
269 if (custom
!= customHeaders
->end()) {
273 resp
.headers
[h
.first
] = h
.second
;
277 static void addCustomHeaders(YaHTTP::Response
& resp
, const boost::optional
<std::map
<std::string
, std::string
> >& customHeaders
)
282 for (const auto& c
: *customHeaders
) {
283 if (!c
.second
.empty()) {
284 resp
.headers
[c
.first
] = c
.second
;
290 static json11::Json::array
someResponseRulesToJson(GlobalStateHolder
<vector
<T
>>* someResponseRules
)
292 using namespace json11
;
293 Json::array responseRules
;
295 auto localResponseRules
= someResponseRules
->getLocal();
296 for(const auto& a
: *localResponseRules
) {
299 {"creationOrder", (double)a
.d_creationOrder
},
300 {"uuid", boost::uuids::to_string(a
.d_id
)},
301 {"matches", (double)a
.d_rule
->d_matches
},
302 {"rule", a
.d_rule
->toString()},
303 {"action", a
.d_action
->toString()},
305 responseRules
.push_back(rule
);
307 return responseRules
;
310 static void connectionThread(int sock
, ComboAddress remote
)
312 setThreadName("dnsdist/webConn");
314 using namespace json11
;
315 vinfolog("Webserver handling connection from %s", remote
.toStringWithPort());
318 YaHTTP::AsyncRequestLoader yarl
;
320 bool finished
= false;
322 yarl
.initialize(&req
);
326 bytes
= read(sock
, buf
, sizeof(buf
));
328 string data
= string(buf
, bytes
);
329 finished
= yarl
.feed(data
);
337 string command
=req
.getvars
["command"];
339 req
.getvars
.erase("_"); // jQuery cache buster
341 YaHTTP::Response resp
;
342 resp
.version
= req
.version
;
343 const string charset
= "; charset=utf-8";
346 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
348 addCustomHeaders(resp
, g_webserverConfig
.customHeaders
);
349 addSecurityHeaders(resp
, g_webserverConfig
.customHeaders
);
351 /* indicate that the connection will be closed after completion of the response */
352 resp
.headers
["Connection"] = "close";
354 /* no need to send back the API key if any */
355 resp
.headers
.erase("X-API-Key");
357 if(req
.method
== "OPTIONS") {
358 /* the OPTIONS method should not require auth, otherwise it breaks CORS */
359 handleCORS(req
, resp
);
362 else if (!compareAuthorization(req
)) {
363 YaHTTP::strstr_map_t::iterator header
= req
.headers
.find("authorization");
364 if (header
!= req
.headers
.end())
365 errlog("HTTP Request \"%s\" from %s: Web Authentication failed", req
.url
.path
, remote
.toStringWithPort());
367 resp
.body
="<h1>Unauthorized</h1>";
368 resp
.headers
["WWW-Authenticate"] = "basic realm=\"PowerDNS\"";
371 else if(!isMethodAllowed(req
)) {
374 else if(req
.url
.path
=="/jsonstat") {
375 handleCORS(req
, resp
);
378 if(command
=="stats") {
379 auto obj
=Json::object
{
380 { "packetcache-hits", 0},
381 { "packetcache-misses", 0},
382 { "over-capacity-drops", 0 },
383 { "too-old-drops", 0 },
384 { "server-policy", g_policy
.getLocal()->name
}
387 for(const auto& e
: g_stats
.entries
) {
388 if (e
.first
== "special-memory-usage")
389 continue; // Too expensive for get-all
390 if(const auto& val
= boost::get
<DNSDistStats::stat_t
*>(&e
.second
))
391 obj
.insert({e
.first
, (double)(*val
)->load()});
392 else if (const auto& dval
= boost::get
<double*>(&e
.second
))
393 obj
.insert({e
.first
, (**dval
)});
395 obj
.insert({e
.first
, (double)(*boost::get
<DNSDistStats::statfunction_t
>(&e
.second
))(e
.first
)});
398 resp
.body
=my_json
.dump();
399 resp
.headers
["Content-Type"] = "application/json";
401 else if(command
=="dynblocklist") {
403 auto nmg
= g_dynblockNMG
.getLocal();
406 for(const auto& e
: *nmg
) {
407 if(now
< e
.second
.until
) {
409 {"reason", e
.second
.reason
},
410 {"seconds", (double)(e
.second
.until
.tv_sec
- now
.tv_sec
)},
411 {"blocks", (double)e
.second
.blocks
},
412 {"action", DNSAction::typeToString(e
.second
.action
!= DNSAction::Action::None
? e
.second
.action
: g_dynBlockAction
) },
413 {"warning", e
.second
.warning
}
415 obj
.insert({e
.first
.toString(), thing
});
419 auto smt
= g_dynblockSMT
.getLocal();
420 smt
->visit([&now
,&obj
](const SuffixMatchTree
<DynBlock
>& node
) {
421 if(now
<node
.d_value
.until
) {
423 if(!node
.d_value
.domain
.empty())
424 dom
= node
.d_value
.domain
.toString();
426 {"reason", node
.d_value
.reason
},
427 {"seconds", (double)(node
.d_value
.until
.tv_sec
- now
.tv_sec
)},
428 {"blocks", (double)node
.d_value
.blocks
},
429 {"action", DNSAction::typeToString(node
.d_value
.action
!= DNSAction::Action::None
? node
.d_value
.action
: g_dynBlockAction
) }
431 obj
.insert({dom
, thing
});
438 resp
.body
=my_json
.dump();
439 resp
.headers
["Content-Type"] = "application/json";
441 else if(command
=="ebpfblocklist") {
446 for (const auto& dynbpf
: g_dynBPFFilters
) {
447 std::vector
<std::tuple
<ComboAddress
, uint64_t, struct timespec
> > addrStats
= dynbpf
->getAddrStats();
448 for (const auto& entry
: addrStats
) {
451 {"seconds", (double)(std::get
<2>(entry
).tv_sec
- now
.tv_sec
)},
452 {"blocks", (double)(std::get
<1>(entry
))}
454 obj
.insert({std::get
<0>(entry
).toString(), thing
});
457 #endif /* HAVE_EBPF */
459 resp
.body
=my_json
.dump();
460 resp
.headers
["Content-Type"] = "application/json";
466 else if (req
.url
.path
== "/metrics") {
467 handleCORS(req
, resp
);
470 std::ostringstream output
;
471 static const std::set
<std::string
> metricBlacklist
= { "latency-count", "latency-sum" };
472 for (const auto& e
: g_stats
.entries
) {
473 if (e
.first
== "special-memory-usage")
474 continue; // Too expensive for get-all
475 std::string metricName
= std::get
<0>(e
);
477 // Prometheus suggest using '_' instead of '-'
478 std::string prometheusMetricName
= "dnsdist_" + boost::replace_all_copy(metricName
, "-", "_");
479 if (metricBlacklist
.count(metricName
) != 0) {
483 MetricDefinition metricDetails
;
484 if (!s_metricDefinitions
.getMetricDetails(metricName
, metricDetails
)) {
485 vinfolog("Do not have metric details for %s", metricName
);
489 std::string prometheusTypeName
= s_metricDefinitions
.getPrometheusStringMetricType(metricDetails
.prometheusType
);
491 if (prometheusTypeName
== "") {
492 vinfolog("Unknown Prometheus type for %s", metricName
);
496 // for these we have the help and types encoded in the sources:
497 output
<< "# HELP " << prometheusMetricName
<< " " << metricDetails
.description
<< "\n";
498 output
<< "# TYPE " << prometheusMetricName
<< " " << prometheusTypeName
<< "\n";
499 output
<< prometheusMetricName
<< " ";
501 if (const auto& val
= boost::get
<DNSDistStats::stat_t
*>(&std::get
<1>(e
)))
502 output
<< (*val
)->load();
503 else if (const auto& dval
= boost::get
<double*>(&std::get
<1>(e
)))
506 output
<< (*boost::get
<DNSDistStats::statfunction_t
>(&std::get
<1>(e
)))(std::get
<0>(e
));
511 // Latency histogram buckets
512 output
<< "# HELP dnsdist_latency Histogram of responses by latency (in milliseconds)\n";
513 output
<< "# TYPE dnsdist_latency histogram\n";
514 uint64_t latency_amounts
= g_stats
.latency0_1
;
515 output
<< "dnsdist_latency_bucket{le=\"1\"} " << latency_amounts
<< "\n";
516 latency_amounts
+= g_stats
.latency1_10
;
517 output
<< "dnsdist_latency_bucket{le=\"10\"} " << latency_amounts
<< "\n";
518 latency_amounts
+= g_stats
.latency10_50
;
519 output
<< "dnsdist_latency_bucket{le=\"50\"} " << latency_amounts
<< "\n";
520 latency_amounts
+= g_stats
.latency50_100
;
521 output
<< "dnsdist_latency_bucket{le=\"100\"} " << latency_amounts
<< "\n";
522 latency_amounts
+= g_stats
.latency100_1000
;
523 output
<< "dnsdist_latency_bucket{le=\"1000\"} " << latency_amounts
<< "\n";
524 latency_amounts
+= g_stats
.latencySlow
; // Should be the same as latency_count
525 output
<< "dnsdist_latency_bucket{le=\"+Inf\"} " << latency_amounts
<< "\n";
526 output
<< "dnsdist_latency_sum " << g_stats
.latencySum
<< "\n";
527 output
<< "dnsdist_latency_count " << getLatencyCount(std::string()) << "\n";
529 auto states
= g_dstates
.getLocal();
530 const string statesbase
= "dnsdist_server_";
532 output
<< "# HELP " << statesbase
<< "status " << "Whether this backend is up (1) or down (0)" << "\n";
533 output
<< "# TYPE " << statesbase
<< "status " << "gauge" << "\n";
534 output
<< "# HELP " << statesbase
<< "queries " << "Amount of queries relayed to server" << "\n";
535 output
<< "# TYPE " << statesbase
<< "queries " << "counter" << "\n";
536 output
<< "# HELP " << statesbase
<< "responses " << "Amount of responses received from this server" << "\n";
537 output
<< "# TYPE " << statesbase
<< "responses " << "counter" << "\n";
538 output
<< "# HELP " << statesbase
<< "drops " << "Amount of queries not answered by server" << "\n";
539 output
<< "# TYPE " << statesbase
<< "drops " << "counter" << "\n";
540 output
<< "# HELP " << statesbase
<< "latency " << "Server's latency when answering questions in milliseconds" << "\n";
541 output
<< "# TYPE " << statesbase
<< "latency " << "gauge" << "\n";
542 output
<< "# HELP " << statesbase
<< "senderrors " << "Total number of OS send errors while relaying queries" << "\n";
543 output
<< "# TYPE " << statesbase
<< "senderrors " << "counter" << "\n";
544 output
<< "# HELP " << statesbase
<< "outstanding " << "Current number of queries that are waiting for a backend response" << "\n";
545 output
<< "# TYPE " << statesbase
<< "outstanding " << "gauge" << "\n";
546 output
<< "# HELP " << statesbase
<< "order " << "The order in which this server is picked" << "\n";
547 output
<< "# TYPE " << statesbase
<< "order " << "gauge" << "\n";
548 output
<< "# HELP " << statesbase
<< "weight " << "The weight within the order in which this server is picked" << "\n";
549 output
<< "# TYPE " << statesbase
<< "weight " << "gauge" << "\n";
550 output
<< "# HELP " << statesbase
<< "tcpdiedsendingquery " << "The number of TCP I/O errors while sending the query" << "\n";
551 output
<< "# TYPE " << statesbase
<< "tcpdiedsendingquery " << "counter" << "\n";
552 output
<< "# HELP " << statesbase
<< "tcpdiedreadingresponse " << "The number of TCP I/O errors while reading the response" << "\n";
553 output
<< "# TYPE " << statesbase
<< "tcpdiedreadingresponse " << "counter" << "\n";
554 output
<< "# HELP " << statesbase
<< "tcpgaveup " << "The number of TCP connections failing after too many attempts" << "\n";
555 output
<< "# TYPE " << statesbase
<< "tcpgaveup " << "counter" << "\n";
556 output
<< "# HELP " << statesbase
<< "tcpreadtimeouts " << "The number of TCP read timeouts" << "\n";
557 output
<< "# TYPE " << statesbase
<< "tcpreadtimeouts " << "counter" << "\n";
558 output
<< "# HELP " << statesbase
<< "tcpwritetimeouts " << "The number of TCP write timeouts" << "\n";
559 output
<< "# TYPE " << statesbase
<< "tcpwritetimeouts " << "counter" << "\n";
560 output
<< "# HELP " << statesbase
<< "tcpcurrentconnections " << "The number of current TCP connections" << "\n";
561 output
<< "# TYPE " << statesbase
<< "tcpcurrentconnections " << "gauge" << "\n";
562 output
<< "# HELP " << statesbase
<< "tcpavgqueriesperconn " << "The average number of queries per TCP connection" << "\n";
563 output
<< "# TYPE " << statesbase
<< "tcpavgqueriesperconn " << "gauge" << "\n";
564 output
<< "# HELP " << statesbase
<< "tcpavgconnduration " << "The average duration of a TCP connection (ms)" << "\n";
565 output
<< "# TYPE " << statesbase
<< "tcpavgconnduration " << "gauge" << "\n";
567 for (const auto& state
: *states
) {
570 if (state
->getName().empty())
571 serverName
= state
->remote
.toStringWithPort();
573 serverName
= state
->getName();
575 boost::replace_all(serverName
, ".", "_");
577 const std::string label
= boost::str(boost::format("{server=\"%1%\",address=\"%2%\"}")
578 % serverName
% state
->remote
.toStringWithPort());
580 output
<< statesbase
<< "status" << label
<< " " << (state
->isUp() ? "1" : "0") << "\n";
581 output
<< statesbase
<< "queries" << label
<< " " << state
->queries
.load() << "\n";
582 output
<< statesbase
<< "responses" << label
<< " " << state
->responses
.load() << "\n";
583 output
<< statesbase
<< "drops" << label
<< " " << state
->reuseds
.load() << "\n";
584 output
<< statesbase
<< "latency" << label
<< " " << state
->latencyUsec
/1000.0 << "\n";
585 output
<< statesbase
<< "senderrors" << label
<< " " << state
->sendErrors
.load() << "\n";
586 output
<< statesbase
<< "outstanding" << label
<< " " << state
->outstanding
.load() << "\n";
587 output
<< statesbase
<< "order" << label
<< " " << state
->order
<< "\n";
588 output
<< statesbase
<< "weight" << label
<< " " << state
->weight
<< "\n";
589 output
<< statesbase
<< "tcpdiedsendingquery" << label
<< " " << state
->tcpDiedSendingQuery
<< "\n";
590 output
<< statesbase
<< "tcpdiedreadingresponse" << label
<< " " << state
->tcpDiedReadingResponse
<< "\n";
591 output
<< statesbase
<< "tcpgaveup" << label
<< " " << state
->tcpGaveUp
<< "\n";
592 output
<< statesbase
<< "tcpreadtimeouts" << label
<< " " << state
->tcpReadTimeouts
<< "\n";
593 output
<< statesbase
<< "tcpwritetimeouts" << label
<< " " << state
->tcpWriteTimeouts
<< "\n";
594 output
<< statesbase
<< "tcpcurrentconnections" << label
<< " " << state
->tcpCurrentConnections
<< "\n";
595 output
<< statesbase
<< "tcpavgqueriesperconn" << label
<< " " << state
->tcpAvgQueriesPerConnection
<< "\n";
596 output
<< statesbase
<< "tcpavgconnduration" << label
<< " " << state
->tcpAvgConnectionDuration
<< "\n";
599 const string frontsbase
= "dnsdist_frontend_";
600 output
<< "# HELP " << frontsbase
<< "queries " << "Amount of queries received by this frontend" << "\n";
601 output
<< "# TYPE " << frontsbase
<< "queries " << "counter" << "\n";
602 output
<< "# HELP " << frontsbase
<< "responses " << "Amount of responses sent by this frontend" << "\n";
603 output
<< "# TYPE " << frontsbase
<< "responses " << "counter" << "\n";
604 output
<< "# HELP " << frontsbase
<< "tcpdiedreadingquery " << "Amount of TCP connections terminated while reading the query from the client" << "\n";
605 output
<< "# TYPE " << frontsbase
<< "tcpdiedreadingquery " << "counter" << "\n";
606 output
<< "# HELP " << frontsbase
<< "tcpdiedsendingresponse " << "Amount of TCP connections terminated while sending a response to the client" << "\n";
607 output
<< "# TYPE " << frontsbase
<< "tcpdiedsendingresponse " << "counter" << "\n";
608 output
<< "# HELP " << frontsbase
<< "tcpgaveup " << "Amount of TCP connections terminated after too many attempts to get a connection to the backend" << "\n";
609 output
<< "# TYPE " << frontsbase
<< "tcpgaveup " << "counter" << "\n";
610 output
<< "# HELP " << frontsbase
<< "tcpclientimeouts " << "Amount of TCP connections terminated by a timeout while reading from the client" << "\n";
611 output
<< "# TYPE " << frontsbase
<< "tcpclientimeouts " << "counter" << "\n";
612 output
<< "# HELP " << frontsbase
<< "tcpdownstreamtimeouts " << "Amount of TCP connections terminated by a timeout while reading from the backend" << "\n";
613 output
<< "# TYPE " << frontsbase
<< "tcpdownstreamtimeouts " << "counter" << "\n";
614 output
<< "# HELP " << frontsbase
<< "tcpcurrentconnections " << "Amount of current incoming TCP connections from clients" << "\n";
615 output
<< "# TYPE " << frontsbase
<< "tcpcurrentconnections " << "gauge" << "\n";
616 output
<< "# HELP " << frontsbase
<< "tcpavgqueriesperconnection " << "The average number of queries per TCP connection" << "\n";
617 output
<< "# TYPE " << frontsbase
<< "tcpavgqueriesperconnection " << "gauge" << "\n";
618 output
<< "# HELP " << frontsbase
<< "tcpavgconnectionduration " << "The average duration of a TCP connection (ms)" << "\n";
619 output
<< "# TYPE " << frontsbase
<< "tcpavgconnectionduration " << "gauge" << "\n";
620 output
<< "# HELP " << frontsbase
<< "tlsqueries " << "Number of queries received by dnsdist over TLS, by TLS version" << "\n";
621 output
<< "# TYPE " << frontsbase
<< "tlsqueries " << "counter" << "\n";
622 output
<< "# HELP " << frontsbase
<< "tlsnewsessions " << "Amount of new TLS sessions negotiated" << "\n";
623 output
<< "# TYPE " << frontsbase
<< "tlsnewsessions " << "counter" << "\n";
624 output
<< "# HELP " << frontsbase
<< "tlsresumptions " << "Amount of TLS sessions resumed" << "\n";
625 output
<< "# TYPE " << frontsbase
<< "tlsresumptions " << "counter" << "\n";
626 output
<< "# HELP " << frontsbase
<< "tlsunknownticketkeys " << "Amount of attempts to resume TLS session from an unknown key (possibly expired)" << "\n";
627 output
<< "# TYPE " << frontsbase
<< "tlsunknownticketkeys " << "counter" << "\n";
628 output
<< "# HELP " << frontsbase
<< "tlsinactiveticketkeys " << "Amount of TLS sessions resumed from an inactive key" << "\n";
629 output
<< "# TYPE " << frontsbase
<< "tlsinactiveticketkeys " << "counter" << "\n";
631 output
<< "# HELP " << frontsbase
<< "tlshandshakefailures " << "Amount of TLS handshake failures" << "\n";
632 output
<< "# TYPE " << frontsbase
<< "tlshandshakefailures " << "counter" << "\n";
634 std::map
<std::string
,uint64_t> frontendDuplicates
;
635 for (const auto& front
: g_frontends
) {
636 if (front
->udpFD
== -1 && front
->tcpFD
== -1)
639 const string frontName
= front
->local
.toString() + ":" + std::to_string(front
->local
.getPort());
640 const string proto
= front
->getType();
641 const string fullName
= frontName
+ "_" + proto
;
642 uint64_t threadNumber
= 0;
643 auto dupPair
= frontendDuplicates
.insert({fullName
, 1});
644 if (!dupPair
.second
) {
645 threadNumber
= dupPair
.first
->second
;
646 ++(dupPair
.first
->second
);
648 const std::string label
= boost::str(boost::format("{frontend=\"%1%\",proto=\"%2%\",thread=\"%3%\"} ")
649 % frontName
% proto
% threadNumber
);
651 output
<< frontsbase
<< "queries" << label
<< front
->queries
.load() << "\n";
652 output
<< frontsbase
<< "responses" << label
<< front
->responses
.load() << "\n";
653 if (front
->isTCP()) {
654 output
<< frontsbase
<< "tcpdiedreadingquery" << label
<< front
->tcpDiedReadingQuery
.load() << "\n";
655 output
<< frontsbase
<< "tcpdiedsendingresponse" << label
<< front
->tcpDiedSendingResponse
.load() << "\n";
656 output
<< frontsbase
<< "tcpgaveup" << label
<< front
->tcpGaveUp
.load() << "\n";
657 output
<< frontsbase
<< "tcpclientimeouts" << label
<< front
->tcpClientTimeouts
.load() << "\n";
658 output
<< frontsbase
<< "tcpdownstreamtimeouts" << label
<< front
->tcpDownstreamTimeouts
.load() << "\n";
659 output
<< frontsbase
<< "tcpcurrentconnections" << label
<< front
->tcpCurrentConnections
.load() << "\n";
660 output
<< frontsbase
<< "tcpavgqueriesperconnection" << label
<< front
->tcpAvgQueriesPerConnection
.load() << "\n";
661 output
<< frontsbase
<< "tcpavgconnectionduration" << label
<< front
->tcpAvgConnectionDuration
.load() << "\n";
662 if (front
->hasTLS()) {
663 output
<< frontsbase
<< "tlsnewsessions" << label
<< front
->tlsNewSessions
.load() << "\n";
664 output
<< frontsbase
<< "tlsresumptions" << label
<< front
->tlsResumptions
.load() << "\n";
665 output
<< frontsbase
<< "tlsunknownticketkeys" << label
<< front
->tlsUnknownTicketKey
.load() << "\n";
666 output
<< frontsbase
<< "tlsinactiveticketkeys" << label
<< front
->tlsInactiveTicketKey
.load() << "\n";
668 output
<< frontsbase
<< "tlsqueries{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",tls=\"tls10\"} " << front
->tls10queries
.load() << "\n";
669 output
<< frontsbase
<< "tlsqueries{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",tls=\"tls11\"} " << front
->tls11queries
.load() << "\n";
670 output
<< frontsbase
<< "tlsqueries{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",tls=\"tls12\"} " << front
->tls12queries
.load() << "\n";
671 output
<< frontsbase
<< "tlsqueries{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",tls=\"tls13\"} " << front
->tls13queries
.load() << "\n";
672 output
<< frontsbase
<< "tlsqueries{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",tls=\"unknown\"} " << front
->tlsUnknownqueries
.load() << "\n";
674 const TLSErrorCounters
* errorCounters
= nullptr;
675 if (front
->tlsFrontend
!= nullptr) {
676 errorCounters
= &front
->tlsFrontend
->d_tlsCounters
;
678 else if (front
->dohFrontend
!= nullptr) {
679 errorCounters
= &front
->dohFrontend
->d_tlsCounters
;
682 if (errorCounters
!= nullptr) {
683 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"dhKeyTooSmall\"} " << errorCounters
->d_dhKeyTooSmall
<< "\n";
684 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"inappropriateFallBack\"} " << errorCounters
->d_inappropriateFallBack
<< "\n";
685 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"noSharedCipher\"} " << errorCounters
->d_noSharedCipher
<< "\n";
686 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"unknownCipherType\"} " << errorCounters
->d_unknownCipherType
<< "\n";
687 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"unknownKeyExchangeType\"} " << errorCounters
->d_unknownKeyExchangeType
<< "\n";
688 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"unknownProtocol\"} " << errorCounters
->d_unknownProtocol
<< "\n";
689 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"unsupportedEC\"} " << errorCounters
->d_unsupportedEC
<< "\n";
690 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"unsupportedProtocol{\"} " << errorCounters
->d_unsupportedProtocol
<< "\n";
696 output
<< "# HELP " << frontsbase
<< "http_connects " << "Number of DoH TCP connections established to this frontend" << "\n";
697 output
<< "# TYPE " << frontsbase
<< "http_connects " << "counter" << "\n";
699 output
<< "# HELP " << frontsbase
<< "doh_http_method_queries " << "Number of DoH queries received by dnsdist, by HTTP method" << "\n";
700 output
<< "# TYPE " << frontsbase
<< "doh_http_method_queries " << "counter" << "\n";
702 output
<< "# HELP " << frontsbase
<< "doh_http_version_queries " << "Number of DoH queries received by dnsdist, by HTTP version" << "\n";
703 output
<< "# TYPE " << frontsbase
<< "doh_http_version_queries " << "counter" << "\n";
705 output
<< "# HELP " << frontsbase
<< "doh_bad_requests " << "Number of requests that could not be converted to a DNS query" << "\n";
706 output
<< "# TYPE " << frontsbase
<< "doh_bad_requests " << "counter" << "\n";
708 output
<< "# HELP " << frontsbase
<< "doh_responses " << "Number of responses sent, by type" << "\n";
709 output
<< "# TYPE " << frontsbase
<< "doh_responses " << "counter" << "\n";
711 output
<< "# HELP " << frontsbase
<< "doh_version_status_responses " << "Number of requests that could not be converted to a DNS query" << "\n";
712 output
<< "# TYPE " << frontsbase
<< "doh_version_status_responses " << "counter" << "\n";
714 #ifdef HAVE_DNS_OVER_HTTPS
715 std::map
<std::string
,uint64_t> dohFrontendDuplicates
;
716 for(const auto& doh
: g_dohlocals
) {
717 const string frontName
= doh
->d_local
.toStringWithPort();
718 uint64_t threadNumber
= 0;
719 auto dupPair
= frontendDuplicates
.insert({frontName
, 1});
720 if (!dupPair
.second
) {
721 threadNumber
= dupPair
.first
->second
;
722 ++(dupPair
.first
->second
);
724 const std::string addrlabel
= boost::str(boost::format("frontend=\"%1%\",thread=\"%2%\"") % frontName
% threadNumber
);
725 const std::string label
= "{" + addrlabel
+ "} ";
727 output
<< frontsbase
<< "http_connects" << label
<< doh
->d_httpconnects
<< "\n";
728 output
<< frontsbase
<< "doh_http_method_queries{method=\"get\"," << addrlabel
<< "} " << doh
->d_getqueries
<< "\n";
729 output
<< frontsbase
<< "doh_http_method_queries{method=\"post\"," << addrlabel
<< "} " << doh
->d_postqueries
<< "\n";
731 output
<< frontsbase
<< "doh_http_version_queries{version=\"1\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nbQueries
<< "\n";
732 output
<< frontsbase
<< "doh_http_version_queries{version=\"2\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nbQueries
<< "\n";
734 output
<< frontsbase
<< "doh_bad_requests{" << addrlabel
<< "} " << doh
->d_badrequests
<< "\n";
736 output
<< frontsbase
<< "doh_responses{type=\"error\"," << addrlabel
<< "} " << doh
->d_errorresponses
<< "\n";
737 output
<< frontsbase
<< "doh_responses{type=\"redirect\"," << addrlabel
<< "} " << doh
->d_redirectresponses
<< "\n";
738 output
<< frontsbase
<< "doh_responses{type=\"valid\"," << addrlabel
<< "} " << doh
->d_validresponses
<< "\n";
740 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"1\",status=\"200\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nb200Responses
<< "\n";
741 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"1\",status=\"400\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nb400Responses
<< "\n";
742 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"1\",status=\"403\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nb403Responses
<< "\n";
743 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"1\",status=\"500\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nb500Responses
<< "\n";
744 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"1\",status=\"502\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nb502Responses
<< "\n";
745 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"1\",status=\"other\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nbOtherResponses
<< "\n";
746 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"2\",status=\"200\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nb200Responses
<< "\n";
747 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"2\",status=\"400\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nb400Responses
<< "\n";
748 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"2\",status=\"403\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nb403Responses
<< "\n";
749 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"2\",status=\"500\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nb500Responses
<< "\n";
750 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"2\",status=\"502\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nb502Responses
<< "\n";
751 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"2\",status=\"other\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nbOtherResponses
<< "\n";
753 #endif /* HAVE_DNS_OVER_HTTPS */
755 auto localPools
= g_pools
.getLocal();
756 const string cachebase
= "dnsdist_pool_";
757 output
<< "# HELP dnsdist_pool_servers " << "Number of servers in that pool" << "\n";
758 output
<< "# TYPE dnsdist_pool_servers " << "gauge" << "\n";
759 output
<< "# HELP dnsdist_pool_active_servers " << "Number of available servers in that pool" << "\n";
760 output
<< "# TYPE dnsdist_pool_active_servers " << "gauge" << "\n";
762 output
<< "# HELP dnsdist_pool_cache_size " << "Maximum number of entries that this cache can hold" << "\n";
763 output
<< "# TYPE dnsdist_pool_cache_size " << "gauge" << "\n";
764 output
<< "# HELP dnsdist_pool_cache_entries " << "Number of entries currently present in that cache" << "\n";
765 output
<< "# TYPE dnsdist_pool_cache_entries " << "gauge" << "\n";
766 output
<< "# HELP dnsdist_pool_cache_hits " << "Number of hits from that cache" << "\n";
767 output
<< "# TYPE dnsdist_pool_cache_hits " << "counter" << "\n";
768 output
<< "# HELP dnsdist_pool_cache_misses " << "Number of misses from that cache" << "\n";
769 output
<< "# TYPE dnsdist_pool_cache_misses " << "counter" << "\n";
770 output
<< "# HELP dnsdist_pool_cache_deferred_inserts " << "Number of insertions into that cache skipped because it was already locked" << "\n";
771 output
<< "# TYPE dnsdist_pool_cache_deferred_inserts " << "counter" << "\n";
772 output
<< "# HELP dnsdist_pool_cache_deferred_lookups " << "Number of lookups into that cache skipped because it was already locked" << "\n";
773 output
<< "# TYPE dnsdist_pool_cache_deferred_lookups " << "counter" << "\n";
774 output
<< "# HELP dnsdist_pool_cache_lookup_collisions " << "Number of lookups into that cache that triggered a collision (same hash but different entry)" << "\n";
775 output
<< "# TYPE dnsdist_pool_cache_lookup_collisions " << "counter" << "\n";
776 output
<< "# HELP dnsdist_pool_cache_insert_collisions " << "Number of insertions into that cache that triggered a collision (same hash but different entry)" << "\n";
777 output
<< "# TYPE dnsdist_pool_cache_insert_collisions " << "counter" << "\n";
778 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";
779 output
<< "# TYPE dnsdist_pool_cache_ttl_too_shorts " << "counter" << "\n";
781 for (const auto& entry
: *localPools
) {
782 string poolName
= entry
.first
;
784 if (poolName
.empty()) {
785 poolName
= "_default_";
787 const string label
= "{pool=\"" + poolName
+ "\"}";
788 const std::shared_ptr
<ServerPool
> pool
= entry
.second
;
789 output
<< "dnsdist_pool_servers" << label
<< " " << pool
->countServers(false) << "\n";
790 output
<< "dnsdist_pool_active_servers" << label
<< " " << pool
->countServers(true) << "\n";
792 if (pool
->packetCache
!= nullptr) {
793 const auto& cache
= pool
->packetCache
;
795 output
<< cachebase
<< "cache_size" <<label
<< " " << cache
->getMaxEntries() << "\n";
796 output
<< cachebase
<< "cache_entries" <<label
<< " " << cache
->getEntriesCount() << "\n";
797 output
<< cachebase
<< "cache_hits" <<label
<< " " << cache
->getHits() << "\n";
798 output
<< cachebase
<< "cache_misses" <<label
<< " " << cache
->getMisses() << "\n";
799 output
<< cachebase
<< "cache_deferred_inserts" <<label
<< " " << cache
->getDeferredInserts() << "\n";
800 output
<< cachebase
<< "cache_deferred_lookups" <<label
<< " " << cache
->getDeferredLookups() << "\n";
801 output
<< cachebase
<< "cache_lookup_collisions" <<label
<< " " << cache
->getLookupCollisions() << "\n";
802 output
<< cachebase
<< "cache_insert_collisions" <<label
<< " " << cache
->getInsertCollisions() << "\n";
803 output
<< cachebase
<< "cache_ttl_too_shorts" <<label
<< " " << cache
->getTTLTooShorts() << "\n";
807 resp
.body
= output
.str();
808 resp
.headers
["Content-Type"] = "text/plain";
811 else if(req
.url
.path
=="/api/v1/servers/localhost") {
812 handleCORS(req
, resp
);
816 auto localServers
= g_dstates
.getLocal();
818 for(const auto& a
: *localServers
) {
820 if(a
->availability
== DownstreamState::Availability::Up
)
822 else if(a
->availability
== DownstreamState::Availability::Down
)
825 status
= (a
->upStatus
? "up" : "down");
828 for(const auto& p
: a
->pools
)
833 {"name", a
->getName()},
834 {"address", a
->remote
.toStringWithPort()},
836 {"qps", (double)a
->queryLoad
},
837 {"qpsLimit", (double)a
->qps
.getRate()},
838 {"outstanding", (double)a
->outstanding
},
839 {"reuseds", (double)a
->reuseds
},
840 {"weight", (double)a
->weight
},
841 {"order", (double)a
->order
},
843 {"latency", (double)(a
->latencyUsec
/1000.0)},
844 {"queries", (double)a
->queries
},
845 {"responses", (double)a
->responses
},
846 {"sendErrors", (double)a
->sendErrors
},
847 {"tcpDiedSendingQuery", (double)a
->tcpDiedSendingQuery
},
848 {"tcpDiedReadingResponse", (double)a
->tcpDiedReadingResponse
},
849 {"tcpGaveUp", (double)a
->tcpGaveUp
},
850 {"tcpReadTimeouts", (double)a
->tcpReadTimeouts
},
851 {"tcpWriteTimeouts", (double)a
->tcpWriteTimeouts
},
852 {"tcpCurrentConnections", (double)a
->tcpCurrentConnections
},
853 {"tcpAvgQueriesPerConnection", (double)a
->tcpAvgQueriesPerConnection
},
854 {"tcpAvgConnectionDuration", (double)a
->tcpAvgConnectionDuration
},
855 {"dropRate", (double)a
->dropRate
}
858 /* sending a latency for a DOWN server doesn't make sense */
859 if (a
->availability
== DownstreamState::Availability::Down
) {
860 server
["latency"] = nullptr;
863 servers
.push_back(server
);
866 Json::array frontends
;
868 for(const auto& front
: g_frontends
) {
869 if (front
->udpFD
== -1 && front
->tcpFD
== -1)
871 Json::object frontend
{
873 { "address", front
->local
.toStringWithPort() },
874 { "udp", front
->udpFD
>= 0 },
875 { "tcp", front
->tcpFD
>= 0 },
876 { "type", front
->getType() },
877 { "queries", (double) front
->queries
.load() },
878 { "responses", (double) front
->responses
.load() },
879 { "tcpDiedReadingQuery", (double) front
->tcpDiedReadingQuery
.load() },
880 { "tcpDiedSendingResponse", (double) front
->tcpDiedSendingResponse
.load() },
881 { "tcpGaveUp", (double) front
->tcpGaveUp
.load() },
882 { "tcpClientTimeouts", (double) front
->tcpClientTimeouts
},
883 { "tcpDownstreamTimeouts", (double) front
->tcpDownstreamTimeouts
},
884 { "tcpCurrentConnections", (double) front
->tcpCurrentConnections
},
885 { "tcpAvgQueriesPerConnection", (double) front
->tcpAvgQueriesPerConnection
},
886 { "tcpAvgConnectionDuration", (double) front
->tcpAvgConnectionDuration
},
887 { "tlsNewSessions", (double) front
->tlsNewSessions
},
888 { "tlsResumptions", (double) front
->tlsResumptions
},
889 { "tlsUnknownTicketKey", (double) front
->tlsUnknownTicketKey
},
890 { "tlsInactiveTicketKey", (double) front
->tlsInactiveTicketKey
},
891 { "tls10Queries", (double) front
->tls10queries
},
892 { "tls11Queries", (double) front
->tls11queries
},
893 { "tls12Queries", (double) front
->tls12queries
},
894 { "tls13Queries", (double) front
->tls13queries
},
895 { "tlsUnknownQueries", (double) front
->tlsUnknownqueries
},
897 const TLSErrorCounters
* errorCounters
= nullptr;
898 if (front
->tlsFrontend
!= nullptr) {
899 errorCounters
= &front
->tlsFrontend
->d_tlsCounters
;
901 else if (front
->dohFrontend
!= nullptr) {
902 errorCounters
= &front
->dohFrontend
->d_tlsCounters
;
904 if (errorCounters
!= nullptr) {
905 frontend
["tlsHandshakeFailuresDHKeyTooSmall"] = (double)errorCounters
->d_dhKeyTooSmall
;
906 frontend
["tlsHandshakeFailuresInappropriateFallBack"] = (double)errorCounters
->d_inappropriateFallBack
;
907 frontend
["tlsHandshakeFailuresNoSharedCipher"] = (double)errorCounters
->d_noSharedCipher
;
908 frontend
["tlsHandshakeFailuresUnknownCipher"] = (double)errorCounters
->d_unknownCipherType
;
909 frontend
["tlsHandshakeFailuresUnknownKeyExchangeType"] = (double)errorCounters
->d_unknownKeyExchangeType
;
910 frontend
["tlsHandshakeFailuresUnknownProtocol"] = (double)errorCounters
->d_unknownProtocol
;
911 frontend
["tlsHandshakeFailuresUnsupportedEC"] = (double)errorCounters
->d_unsupportedEC
;
912 frontend
["tlsHandshakeFailuresUnsupportedProtocol"] = (double)errorCounters
->d_unsupportedProtocol
;
914 frontends
.push_back(frontend
);
918 #ifdef HAVE_DNS_OVER_HTTPS
921 for(const auto& doh
: g_dohlocals
) {
924 { "address", doh
->d_local
.toStringWithPort() },
925 { "http-connects", (double) doh
->d_httpconnects
},
926 { "http1-queries", (double) doh
->d_http1Stats
.d_nbQueries
},
927 { "http2-queries", (double) doh
->d_http2Stats
.d_nbQueries
},
928 { "http1-200-responses", (double) doh
->d_http1Stats
.d_nb200Responses
},
929 { "http2-200-responses", (double) doh
->d_http2Stats
.d_nb200Responses
},
930 { "http1-400-responses", (double) doh
->d_http1Stats
.d_nb400Responses
},
931 { "http2-400-responses", (double) doh
->d_http2Stats
.d_nb400Responses
},
932 { "http1-403-responses", (double) doh
->d_http1Stats
.d_nb403Responses
},
933 { "http2-403-responses", (double) doh
->d_http2Stats
.d_nb403Responses
},
934 { "http1-500-responses", (double) doh
->d_http1Stats
.d_nb500Responses
},
935 { "http2-500-responses", (double) doh
->d_http2Stats
.d_nb500Responses
},
936 { "http1-502-responses", (double) doh
->d_http1Stats
.d_nb502Responses
},
937 { "http2-502-responses", (double) doh
->d_http2Stats
.d_nb502Responses
},
938 { "http1-other-responses", (double) doh
->d_http1Stats
.d_nbOtherResponses
},
939 { "http2-other-responses", (double) doh
->d_http2Stats
.d_nbOtherResponses
},
940 { "get-queries", (double) doh
->d_getqueries
},
941 { "post-queries", (double) doh
->d_postqueries
},
942 { "bad-requests", (double) doh
->d_badrequests
},
943 { "error-responses", (double) doh
->d_errorresponses
},
944 { "redirect-responses", (double) doh
->d_redirectresponses
},
945 { "valid-responses", (double) doh
->d_validresponses
}
950 #endif /* HAVE_DNS_OVER_HTTPS */
953 auto localPools
= g_pools
.getLocal();
955 for(const auto& pool
: *localPools
) {
956 const auto& cache
= pool
.second
->packetCache
;
959 { "name", pool
.first
},
960 { "serversCount", (double) pool
.second
->countServers(false) },
961 { "cacheSize", (double) (cache
? cache
->getMaxEntries() : 0) },
962 { "cacheEntries", (double) (cache
? cache
->getEntriesCount() : 0) },
963 { "cacheHits", (double) (cache
? cache
->getHits() : 0) },
964 { "cacheMisses", (double) (cache
? cache
->getMisses() : 0) },
965 { "cacheDeferredInserts", (double) (cache
? cache
->getDeferredInserts() : 0) },
966 { "cacheDeferredLookups", (double) (cache
? cache
->getDeferredLookups() : 0) },
967 { "cacheLookupCollisions", (double) (cache
? cache
->getLookupCollisions() : 0) },
968 { "cacheInsertCollisions", (double) (cache
? cache
->getInsertCollisions() : 0) },
969 { "cacheTTLTooShorts", (double) (cache
? cache
->getTTLTooShorts() : 0) }
971 pools
.push_back(entry
);
975 auto localRules
= g_rulactions
.getLocal();
977 for(const auto& a
: *localRules
) {
980 {"creationOrder", (double)a
.d_creationOrder
},
981 {"uuid", boost::uuids::to_string(a
.d_id
)},
982 {"matches", (double)a
.d_rule
->d_matches
},
983 {"rule", a
.d_rule
->toString()},
984 {"action", a
.d_action
->toString()},
985 {"action-stats", a
.d_action
->getStats()}
987 rules
.push_back(rule
);
990 auto responseRules
= someResponseRulesToJson(&g_resprulactions
);
991 auto cacheHitResponseRules
= someResponseRulesToJson(&g_cachehitresprulactions
);
992 auto selfAnsweredResponseRules
= someResponseRulesToJson(&g_selfansweredresprulactions
);
997 g_ACL
.getLocal()->toStringVector(&vec
);
999 for(const auto& s
: vec
) {
1000 if(!acl
.empty()) acl
+= ", ";
1003 string localaddressesStr
;
1004 std::set
<std::string
> localaddresses
;
1005 for(const auto& front
: g_frontends
) {
1006 localaddresses
.insert(front
->local
.toStringWithPort());
1008 for (const auto& addr
: localaddresses
) {
1009 if (!localaddressesStr
.empty()) {
1010 localaddressesStr
+= ", ";
1012 localaddressesStr
+= addr
;
1015 Json my_json
= Json::object
{
1016 { "daemon_type", "dnsdist" },
1017 { "version", VERSION
},
1018 { "servers", servers
},
1019 { "frontends", frontends
},
1022 { "response-rules", responseRules
},
1023 { "cache-hit-response-rules", cacheHitResponseRules
},
1024 { "self-answered-response-rules", selfAnsweredResponseRules
},
1026 { "local", localaddressesStr
},
1027 { "dohFrontends", dohs
}
1029 resp
.headers
["Content-Type"] = "application/json";
1030 resp
.body
=my_json
.dump();
1032 else if(req
.url
.path
=="/api/v1/servers/localhost/statistics") {
1033 handleCORS(req
, resp
);
1037 for(const auto& item
: g_stats
.entries
) {
1038 if (item
.first
== "special-memory-usage")
1039 continue; // Too expensive for get-all
1041 if(const auto& val
= boost::get
<DNSDistStats::stat_t
*>(&item
.second
)) {
1042 doc
.push_back(Json::object
{
1043 { "type", "StatisticItem" },
1044 { "name", item
.first
},
1045 { "value", (double)(*val
)->load() }
1048 else if (const auto& dval
= boost::get
<double*>(&item
.second
)) {
1049 doc
.push_back(Json::object
{
1050 { "type", "StatisticItem" },
1051 { "name", item
.first
},
1052 { "value", (**dval
) }
1056 doc
.push_back(Json::object
{
1057 { "type", "StatisticItem" },
1058 { "name", item
.first
},
1059 { "value", (double)(*boost::get
<DNSDistStats::statfunction_t
>(&item
.second
))(item
.first
) }
1064 resp
.body
=my_json
.dump();
1065 resp
.headers
["Content-Type"] = "application/json";
1067 else if(req
.url
.path
=="/api/v1/servers/localhost/config") {
1068 handleCORS(req
, resp
);
1072 typedef boost::variant
<bool, double, std::string
> configentry_t
;
1073 std::vector
<std::pair
<std::string
, configentry_t
> > configEntries
{
1074 { "acl", g_ACL
.getLocal()->toString() },
1075 { "allow-empty-response", g_allowEmptyResponse
},
1076 { "control-socket", g_serverControl
.toStringWithPort() },
1077 { "ecs-override", g_ECSOverride
},
1078 { "ecs-source-prefix-v4", (double) g_ECSSourcePrefixV4
},
1079 { "ecs-source-prefix-v6", (double) g_ECSSourcePrefixV6
},
1080 { "fixup-case", g_fixupCase
},
1081 { "max-outstanding", (double) g_maxOutstanding
},
1082 { "server-policy", g_policy
.getLocal()->name
},
1083 { "stale-cache-entries-ttl", (double) g_staleCacheEntriesTTL
},
1084 { "tcp-recv-timeout", (double) g_tcpRecvTimeout
},
1085 { "tcp-send-timeout", (double) g_tcpSendTimeout
},
1086 { "truncate-tc", g_truncateTC
},
1087 { "verbose", g_verbose
},
1088 { "verbose-health-checks", g_verboseHealthChecks
}
1090 for(const auto& item
: configEntries
) {
1091 if (const auto& bval
= boost::get
<bool>(&item
.second
)) {
1092 doc
.push_back(Json::object
{
1093 { "type", "ConfigSetting" },
1094 { "name", item
.first
},
1098 else if (const auto& sval
= boost::get
<string
>(&item
.second
)) {
1099 doc
.push_back(Json::object
{
1100 { "type", "ConfigSetting" },
1101 { "name", item
.first
},
1105 else if (const auto& dval
= boost::get
<double>(&item
.second
)) {
1106 doc
.push_back(Json::object
{
1107 { "type", "ConfigSetting" },
1108 { "name", item
.first
},
1114 resp
.body
=my_json
.dump();
1115 resp
.headers
["Content-Type"] = "application/json";
1117 else if(req
.url
.path
=="/api/v1/servers/localhost/config/allow-from") {
1118 handleCORS(req
, resp
);
1120 resp
.headers
["Content-Type"] = "application/json";
1123 if (req
.method
== "PUT") {
1125 Json doc
= Json::parse(req
.body
, err
);
1127 if (!doc
.is_null()) {
1129 auto aclList
= doc
["value"];
1130 if (aclList
.is_array()) {
1132 for (auto value
: aclList
.array_items()) {
1134 nmg
.addMask(value
.string_value());
1135 } catch (NetmaskException
&e
) {
1141 if (resp
.status
== 200) {
1142 infolog("Updating the ACL via the API to %s", nmg
.toString());
1143 g_ACL
.setState(nmg
);
1155 if (resp
.status
== 200) {
1158 g_ACL
.getLocal()->toStringVector(&vec
);
1160 for(const auto& s
: vec
) {
1165 { "type", "ConfigSetting" },
1166 { "name", "allow-from" },
1170 resp
.body
=my_json
.dump();
1173 else if(!req
.url
.path
.empty() && g_urlmap
.count(req
.url
.path
.c_str()+1)) {
1174 resp
.body
.assign(g_urlmap
[req
.url
.path
.c_str()+1]);
1175 vector
<string
> parts
;
1176 stringtok(parts
, req
.url
.path
, ".");
1177 if(parts
.back() == "html")
1178 resp
.headers
["Content-Type"] = "text/html" + charset
;
1179 else if(parts
.back() == "css")
1180 resp
.headers
["Content-Type"] = "text/css" + charset
;
1181 else if(parts
.back() == "js")
1182 resp
.headers
["Content-Type"] = "application/javascript" + charset
;
1183 else if(parts
.back() == "png")
1184 resp
.headers
["Content-Type"] = "image/png";
1187 else if(req
.url
.path
=="/") {
1188 resp
.body
.assign(g_urlmap
["index.html"]);
1189 resp
.headers
["Content-Type"] = "text/html" + charset
;
1193 // cerr<<"404 for: "<<req.url.path<<endl;
1197 std::ostringstream ofs
;
1201 writen2(sock
, done
.c_str(), done
.size());
1206 catch(const YaHTTP::ParseError
& e
) {
1207 vinfolog("Webserver thread died with parse error exception while processing a request from %s: %s", remote
.toStringWithPort(), e
.what());
1210 catch(const std::exception
& e
) {
1211 errlog("Webserver thread died with exception while processing a request from %s: %s", remote
.toStringWithPort(), e
.what());
1215 errlog("Webserver thread died with exception while processing a request from %s", remote
.toStringWithPort());
1220 void setWebserverAPIKey(const boost::optional
<std::string
> apiKey
)
1222 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
1225 g_webserverConfig
.apiKey
= *apiKey
;
1227 g_webserverConfig
.apiKey
.clear();
1231 void setWebserverPassword(const std::string
& password
)
1233 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
1235 g_webserverConfig
.password
= password
;
1238 void setWebserverACL(const std::string
& acl
)
1240 NetmaskGroup newACL
;
1241 newACL
.toMasks(acl
);
1244 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
1245 g_webserverConfig
.acl
= std::move(newACL
);
1249 void setWebserverCustomHeaders(const boost::optional
<std::map
<std::string
, std::string
> > customHeaders
)
1251 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
1253 g_webserverConfig
.customHeaders
= customHeaders
;
1256 void dnsdistWebserverThread(int sock
, const ComboAddress
& local
)
1258 setThreadName("dnsdist/webserv");
1259 warnlog("Webserver launched on %s", local
.toStringWithPort());
1263 ComboAddress
remote(local
);
1264 int fd
= SAccept(sock
, remote
);
1265 if (!isClientAllowedByACL(remote
)) {
1266 vinfolog("Connection to webserver from client %s is not allowed, closing", remote
.toStringWithPort());
1270 vinfolog("Got a connection to the webserver from %s", remote
.toStringWithPort());
1271 std::thread
t(connectionThread
, fd
, remote
);
1274 catch (const std::exception
& e
) {
1275 errlog("Had an error accepting new webserver connection: %s", e
.what());