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/json11/json11.hpp"
30 #include <yahttp/yahttp.hpp>
33 #include "connection-management.hh"
35 #include "dnsdist-dynblocks.hh"
36 #include "dnsdist-healthchecks.hh"
37 #include "dnsdist-metrics.hh"
38 #include "dnsdist-prometheus.hh"
39 #include "dnsdist-rings.hh"
40 #include "dnsdist-web.hh"
43 #include "threadname.hh"
46 struct WebserverConfig
50 acl
.toMasks("127.0.0.1, ::1");
54 std::unique_ptr
<CredentialsHolder
> password
;
55 std::unique_ptr
<CredentialsHolder
> apiKey
;
56 boost::optional
<std::unordered_map
<std::string
, std::string
> > customHeaders
;
57 bool apiRequiresAuthentication
{true};
58 bool dashboardRequiresAuthentication
{true};
59 bool statsRequireAuthentication
{true};
62 bool g_apiReadWrite
{false};
63 LockGuarded
<WebserverConfig
> g_webserverConfig
;
64 std::string g_apiConfigDirectory
;
66 static ConcurrentConnectionManager
s_connManager(100);
68 std::string
getWebserverConfig()
73 auto config
= g_webserverConfig
.lock();
74 out
<< "Current web server configuration:" << endl
;
75 out
<< "ACL: " << config
->acl
.toString() << endl
;
76 out
<< "Custom headers: ";
77 if (config
->customHeaders
) {
79 for (const auto& header
: *config
->customHeaders
) {
80 out
<< " - " << header
.first
<< ": " << header
.second
<< endl
;
84 out
<< "None" << endl
;
86 out
<< "API requires authentication: " << (config
->apiRequiresAuthentication
? "yes" : "no") << endl
;
87 out
<< "Dashboard requires authentication: " << (config
->dashboardRequiresAuthentication
? "yes" : "no") << endl
;
88 out
<< "Statistics require authentication: " << (config
->statsRequireAuthentication
? "yes" : "no") << endl
;
89 out
<< "Password: " << (config
->password
? "set" : "unset") << endl
;
90 out
<< "API key: " << (config
->apiKey
? "set" : "unset") << endl
;
92 out
<< "API writable: " << (g_apiReadWrite
? "yes" : "no") << endl
;
93 out
<< "API configuration directory: " << g_apiConfigDirectory
<< endl
;
94 out
<< "Maximum concurrent connections: " << s_connManager
.getMaxConcurrentConnections() << endl
;
99 class WebClientConnection
102 WebClientConnection(const ComboAddress
& client
, int fd
): d_client(client
), d_socket(fd
)
104 if (!s_connManager
.registerConnection()) {
105 throw std::runtime_error("Too many concurrent web client connections");
108 WebClientConnection(WebClientConnection
&& rhs
): d_client(rhs
.d_client
), d_socket(std::move(rhs
.d_socket
))
112 WebClientConnection(const WebClientConnection
&) = delete;
113 WebClientConnection
& operator=(const WebClientConnection
&) = delete;
115 ~WebClientConnection()
117 if (d_socket
.getHandle() != -1) {
118 s_connManager
.releaseConnection();
122 const Socket
& getSocket() const
127 const ComboAddress
& getClient() const
133 ComboAddress d_client
;
137 #ifndef DISABLE_PROMETHEUS
138 static MetricDefinitionStorage s_metricDefinitions
;
140 std::map
<std::string
, MetricDefinition
> MetricDefinitionStorage::metrics
{
141 { "responses", MetricDefinition(PrometheusMetricType::counter
, "Number of responses received from backends") },
142 { "servfail-responses", MetricDefinition(PrometheusMetricType::counter
, "Number of SERVFAIL answers received from backends") },
143 { "queries", MetricDefinition(PrometheusMetricType::counter
, "Number of received queries")},
144 { "frontend-nxdomain", MetricDefinition(PrometheusMetricType::counter
, "Number of NXDomain answers sent to clients")},
145 { "frontend-servfail", MetricDefinition(PrometheusMetricType::counter
, "Number of SERVFAIL answers sent to clients")},
146 { "frontend-noerror", MetricDefinition(PrometheusMetricType::counter
, "Number of NoError answers sent to clients")},
147 { "acl-drops", MetricDefinition(PrometheusMetricType::counter
, "Number of packets dropped because of the ACL")},
148 { "rule-drop", MetricDefinition(PrometheusMetricType::counter
, "Number of queries dropped because of a rule")},
149 { "rule-nxdomain", MetricDefinition(PrometheusMetricType::counter
, "Number of NXDomain answers returned because of a rule")},
150 { "rule-refused", MetricDefinition(PrometheusMetricType::counter
, "Number of Refused answers returned because of a rule")},
151 { "rule-servfail", MetricDefinition(PrometheusMetricType::counter
, "Number of SERVFAIL answers received because of a rule")},
152 { "rule-truncated", MetricDefinition(PrometheusMetricType::counter
, "Number of truncated answers returned because of a rule")},
153 { "self-answered", MetricDefinition(PrometheusMetricType::counter
, "Number of self-answered responses")},
154 { "downstream-timeouts", MetricDefinition(PrometheusMetricType::counter
, "Number of queries not answered in time by a backend")},
155 { "downstream-send-errors", MetricDefinition(PrometheusMetricType::counter
, "Number of errors when sending a query to a backend")},
156 { "trunc-failures", MetricDefinition(PrometheusMetricType::counter
, "Number of errors encountered while truncating an answer")},
157 { "no-policy", MetricDefinition(PrometheusMetricType::counter
, "Number of queries dropped because no server was available")},
158 { "latency0-1", MetricDefinition(PrometheusMetricType::counter
, "Number of queries answered in less than 1ms")},
159 { "latency1-10", MetricDefinition(PrometheusMetricType::counter
, "Number of queries answered in 1-10 ms")},
160 { "latency10-50", MetricDefinition(PrometheusMetricType::counter
, "Number of queries answered in 10-50 ms")},
161 { "latency50-100", MetricDefinition(PrometheusMetricType::counter
, "Number of queries answered in 50-100 ms")},
162 { "latency100-1000", MetricDefinition(PrometheusMetricType::counter
, "Number of queries answered in 100-1000 ms")},
163 { "latency-slow", MetricDefinition(PrometheusMetricType::counter
, "Number of queries answered in more than 1 second")},
164 { "latency-avg100", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency in microseconds of the last 100 packets")},
165 { "latency-avg1000", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency in microseconds of the last 1000 packets")},
166 { "latency-avg10000", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency in microseconds of the last 10000 packets")},
167 { "latency-avg1000000", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency in microseconds of the last 1000000 packets")},
168 { "latency-tcp-avg100", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency, in microseconds, of the last 100 packets received over TCP")},
169 { "latency-tcp-avg1000", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency, in microseconds, of the last 1000 packets received over TCP")},
170 { "latency-tcp-avg10000", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency, in microseconds, of the last 10000 packets received over TCP")},
171 { "latency-tcp-avg1000000", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency, in microseconds, of the last 1000000 packets received over TCP")},
172 { "latency-dot-avg100", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency, in microseconds, of the last 100 packets received over DoT")},
173 { "latency-dot-avg1000", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency, in microseconds, of the last 1000 packets received over DoT")},
174 { "latency-dot-avg10000", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency, in microseconds, of the last 10000 packets received over DoT")},
175 { "latency-dot-avg1000000", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency, in microseconds, of the last 1000000 packets received over DoT")},
176 { "latency-doh-avg100", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency, in microseconds, of the last 100 packets received over DoH")},
177 { "latency-doh-avg1000", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency, in microseconds, of the last 1000 packets received over DoH")},
178 { "latency-doh-avg10000", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency, in microseconds, of the last 10000 packets received over DoH")},
179 { "latency-doh-avg1000000", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency, in microseconds, of the last 1000000 packets received over DoH")},
180 { "latency-doq-avg100", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency, in microseconds, of the last 100 packets received over DoQ")},
181 { "latency-doq-avg1000", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency, in microseconds, of the last 1000 packets received over DoQ")},
182 { "latency-doq-avg10000", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency, in microseconds, of the last 10000 packets received over DoQ")},
183 { "latency-doq-avg1000000", MetricDefinition(PrometheusMetricType::gauge
, "Average response latency, in microseconds, of the last 1000000 packets received over DoQ")},
184 { "uptime", MetricDefinition(PrometheusMetricType::gauge
, "Uptime of the dnsdist process in seconds")},
185 { "real-memory-usage", MetricDefinition(PrometheusMetricType::gauge
, "Current memory usage in bytes")},
186 { "noncompliant-queries", MetricDefinition(PrometheusMetricType::counter
, "Number of queries dropped as non-compliant")},
187 { "noncompliant-responses", MetricDefinition(PrometheusMetricType::counter
, "Number of answers from a backend dropped as non-compliant")},
188 { "rdqueries", MetricDefinition(PrometheusMetricType::counter
, "Number of received queries with the recursion desired bit set")},
189 { "empty-queries", MetricDefinition(PrometheusMetricType::counter
, "Number of empty queries received from clients")},
190 { "cache-hits", MetricDefinition(PrometheusMetricType::counter
, "Number of times an answer was retrieved from cache")},
191 { "cache-misses", MetricDefinition(PrometheusMetricType::counter
, "Number of times an answer not found in the cache")},
192 { "cpu-iowait", MetricDefinition(PrometheusMetricType::counter
, "Time waiting for I/O to complete by the whole system, in units of USER_HZ")},
193 { "cpu-user-msec", MetricDefinition(PrometheusMetricType::counter
, "Milliseconds spent by dnsdist in the user state")},
194 { "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")},
195 { "cpu-sys-msec", MetricDefinition(PrometheusMetricType::counter
, "Milliseconds spent by dnsdist in the system state")},
196 { "fd-usage", MetricDefinition(PrometheusMetricType::gauge
, "Number of currently used file descriptors")},
197 { "dyn-blocked", MetricDefinition(PrometheusMetricType::counter
, "Number of queries dropped because of a dynamic block")},
198 { "dyn-block-nmg-size", MetricDefinition(PrometheusMetricType::gauge
, "Number of dynamic blocks entries") },
199 { "security-status", MetricDefinition(PrometheusMetricType::gauge
, "Security status of this software. 0=unknown, 1=OK, 2=upgrade recommended, 3=upgrade mandatory") },
200 { "doh-query-pipe-full", MetricDefinition(PrometheusMetricType::counter
, "Number of DoH queries dropped because the internal pipe used to distribute queries was full") },
201 { "doh-response-pipe-full", MetricDefinition(PrometheusMetricType::counter
, "Number of DoH responses dropped because the internal pipe used to distribute responses was full") },
202 { "outgoing-doh-query-pipe-full", MetricDefinition(PrometheusMetricType::counter
, "Number of outgoing DoH queries dropped because the internal pipe used to distribute queries was full") },
203 { "tcp-query-pipe-full", MetricDefinition(PrometheusMetricType::counter
, "Number of TCP queries dropped because the internal pipe used to distribute queries was full") },
204 { "tcp-cross-protocol-query-pipe-full", MetricDefinition(PrometheusMetricType::counter
, "Number of TCP cross-protocol queries dropped because the internal pipe used to distribute queries was full") },
205 { "tcp-cross-protocol-response-pipe-full", MetricDefinition(PrometheusMetricType::counter
, "Number of TCP cross-protocol responses dropped because the internal pipe used to distribute queries was full") },
206 { "udp-in-errors", MetricDefinition(PrometheusMetricType::counter
, "From /proc/net/snmp InErrors") },
207 { "udp-noport-errors", MetricDefinition(PrometheusMetricType::counter
, "From /proc/net/snmp NoPorts") },
208 { "udp-recvbuf-errors", MetricDefinition(PrometheusMetricType::counter
, "From /proc/net/snmp RcvbufErrors") },
209 { "udp-sndbuf-errors", MetricDefinition(PrometheusMetricType::counter
, "From /proc/net/snmp SndbufErrors") },
210 { "udp-in-csum-errors", MetricDefinition(PrometheusMetricType::counter
, "From /proc/net/snmp InCsumErrors") },
211 { "udp6-in-errors", MetricDefinition(PrometheusMetricType::counter
, "From /proc/net/snmp6 Udp6InErrors") },
212 { "udp6-recvbuf-errors", MetricDefinition(PrometheusMetricType::counter
, "From /proc/net/snmp6 Udp6RcvbufErrors") },
213 { "udp6-sndbuf-errors", MetricDefinition(PrometheusMetricType::counter
, "From /proc/net/snmp6 Udp6SndbufErrors") },
214 { "udp6-noport-errors", MetricDefinition(PrometheusMetricType::counter
, "From /proc/net/snmp6 Udp6NoPorts") },
215 { "udp6-in-csum-errors", MetricDefinition(PrometheusMetricType::counter
, "From /proc/net/snmp6 Udp6InCsumErrors") },
216 { "tcp-listen-overflows", MetricDefinition(PrometheusMetricType::counter
, "From /proc/net/netstat ListenOverflows") },
217 { "proxy-protocol-invalid", MetricDefinition(PrometheusMetricType::counter
, "Number of queries dropped because of an invalid Proxy Protocol header") },
219 #endif /* DISABLE_PROMETHEUS */
221 bool addMetricDefinition(const dnsdist::prometheus::PrometheusMetricDefinition
& def
) {
222 #ifndef DISABLE_PROMETHEUS
223 return MetricDefinitionStorage::addMetricDefinition(def
);
226 #endif /* DISABLE_PROMETHEUS */
229 #ifndef DISABLE_WEB_CONFIG
230 static bool apiWriteConfigFile(const string
& filebasename
, const string
& content
)
232 if (!g_apiReadWrite
) {
233 warnlog("Not writing content to %s since the API is read-only", filebasename
);
237 if (g_apiConfigDirectory
.empty()) {
238 vinfolog("Not writing content to %s since the API configuration directory is not set", filebasename
);
242 string filename
= g_apiConfigDirectory
+ "/" + filebasename
+ ".conf";
243 ofstream
ofconf(filename
.c_str());
245 errlog("Could not open configuration fragment file '%s' for writing: %s", filename
, stringerror());
248 ofconf
<< "-- Generated by the REST API, DO NOT EDIT" << endl
;
249 ofconf
<< content
<< endl
;
254 static void apiSaveACL(const NetmaskGroup
& nmg
)
257 nmg
.toStringVector(&vec
);
260 for(const auto& s
: vec
) {
264 acl
+= "\"" + s
+ "\"";
267 string content
= "setACL({" + acl
+ "})";
268 apiWriteConfigFile("acl", content
);
270 #endif /* DISABLE_WEB_CONFIG */
272 static bool checkAPIKey(const YaHTTP::Request
& req
, const std::unique_ptr
<CredentialsHolder
>& apiKey
)
278 const auto header
= req
.headers
.find("x-api-key");
279 if (header
!= req
.headers
.end()) {
280 return apiKey
->matches(header
->second
);
286 static bool checkWebPassword(const YaHTTP::Request
& req
, const std::unique_ptr
<CredentialsHolder
>& password
, bool dashboardRequiresAuthentication
)
288 if (!dashboardRequiresAuthentication
) {
292 static const char basicStr
[] = "basic ";
294 const auto header
= req
.headers
.find("authorization");
296 if (header
!= req
.headers
.end() && toLower(header
->second
).find(basicStr
) == 0) {
297 string cookie
= header
->second
.substr(sizeof(basicStr
) - 1);
300 B64Decode(cookie
, plain
);
302 vector
<string
> cparts
;
303 stringtok(cparts
, plain
, ":");
305 if (cparts
.size() == 2) {
307 return password
->matches(cparts
.at(1));
316 static bool isAnAPIRequest(const YaHTTP::Request
& req
)
318 return req
.url
.path
.find("/api/") == 0;
321 static bool isAnAPIRequestAllowedWithWebAuth(const YaHTTP::Request
& req
)
323 return req
.url
.path
== "/api/v1/servers/localhost";
326 static bool isAStatsRequest(const YaHTTP::Request
& req
)
328 return req
.url
.path
== "/jsonstat" || req
.url
.path
== "/metrics";
331 static bool handleAuthorization(const YaHTTP::Request
& req
)
333 auto config
= g_webserverConfig
.lock();
335 if (isAStatsRequest(req
)) {
336 if (config
->statsRequireAuthentication
) {
337 /* Access to the stats is allowed for both API and Web users */
338 return checkAPIKey(req
, config
->apiKey
) || checkWebPassword(req
, config
->password
, config
->dashboardRequiresAuthentication
);
343 if (isAnAPIRequest(req
)) {
344 /* Access to the API requires a valid API key */
345 if (!config
->apiRequiresAuthentication
|| checkAPIKey(req
, config
->apiKey
)) {
349 return isAnAPIRequestAllowedWithWebAuth(req
) && checkWebPassword(req
, config
->password
, config
->dashboardRequiresAuthentication
);
352 return checkWebPassword(req
, config
->password
, config
->dashboardRequiresAuthentication
);
355 static bool isMethodAllowed(const YaHTTP::Request
& req
)
357 if (req
.method
== "GET") {
360 if (req
.method
== "PUT" && g_apiReadWrite
) {
361 if (req
.url
.path
== "/api/v1/servers/localhost/config/allow-from") {
365 #ifndef DISABLE_WEB_CACHE_MANAGEMENT
366 if (req
.method
== "DELETE") {
367 if (req
.url
.path
== "/api/v1/cache") {
371 #endif /* DISABLE_WEB_CACHE_MANAGEMENT */
375 static bool isClientAllowedByACL(const ComboAddress
& remote
)
377 return g_webserverConfig
.lock()->acl
.match(remote
);
380 static void handleCORS(const YaHTTP::Request
& req
, YaHTTP::Response
& resp
)
382 const auto origin
= req
.headers
.find("Origin");
383 if (origin
!= req
.headers
.end()) {
384 if (req
.method
== "OPTIONS") {
385 /* Pre-flight request */
386 if (g_apiReadWrite
) {
387 resp
.headers
["Access-Control-Allow-Methods"] = "GET, PUT";
390 resp
.headers
["Access-Control-Allow-Methods"] = "GET";
392 resp
.headers
["Access-Control-Allow-Headers"] = "Authorization, X-API-Key";
395 resp
.headers
["Access-Control-Allow-Origin"] = origin
->second
;
397 if (isAStatsRequest(req
) || isAnAPIRequestAllowedWithWebAuth(req
)) {
398 resp
.headers
["Access-Control-Allow-Credentials"] = "true";
403 static void addSecurityHeaders(YaHTTP::Response
& resp
, const boost::optional
<std::unordered_map
<std::string
, std::string
> >& customHeaders
)
405 static const std::vector
<std::pair
<std::string
, std::string
> > headers
= {
406 { "X-Content-Type-Options", "nosniff" },
407 { "X-Frame-Options", "deny" },
408 { "X-Permitted-Cross-Domain-Policies", "none" },
409 { "X-XSS-Protection", "1; mode=block" },
410 { "Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'" },
413 for (const auto& h
: headers
) {
415 const auto& custom
= customHeaders
->find(h
.first
);
416 if (custom
!= customHeaders
->end()) {
420 resp
.headers
[h
.first
] = h
.second
;
424 static void addCustomHeaders(YaHTTP::Response
& resp
, const boost::optional
<std::unordered_map
<std::string
, std::string
> >& customHeaders
)
429 for (const auto& c
: *customHeaders
) {
430 if (!c
.second
.empty()) {
431 resp
.headers
[c
.first
] = c
.second
;
437 static json11::Json::array
someResponseRulesToJson(GlobalStateHolder
<vector
<T
>>* someResponseRules
)
439 using namespace json11
;
440 Json::array responseRules
;
442 auto localResponseRules
= someResponseRules
->getLocal();
443 responseRules
.reserve(localResponseRules
->size());
444 for (const auto& a
: *localResponseRules
) {
445 responseRules
.push_back(Json::object
{
447 {"creationOrder", (double)a
.d_creationOrder
},
448 {"uuid", boost::uuids::to_string(a
.d_id
)},
450 {"matches", (double)a
.d_rule
->d_matches
},
451 {"rule", a
.d_rule
->toString()},
452 {"action", a
.d_action
->toString()},
455 return responseRules
;
458 #ifndef DISABLE_PROMETHEUS
460 static void addRulesToPrometheusOutput(std::ostringstream
& output
, GlobalStateHolder
<vector
<T
> >& rules
)
462 auto localRules
= rules
.getLocal();
463 for (const auto& entry
: *localRules
) {
464 std::string id
= !entry
.d_name
.empty() ? entry
.d_name
: boost::uuids::to_string(entry
.d_id
);
465 output
<< "dnsdist_rule_hits{id=\"" << id
<< "\"} " << entry
.d_rule
->d_matches
<< "\n";
469 static void handlePrometheus(const YaHTTP::Request
& req
, YaHTTP::Response
& resp
)
471 handleCORS(req
, resp
);
474 std::ostringstream output
;
475 static const std::set
<std::string
> metricBlacklist
= { "special-memory-usage", "latency-count", "latency-sum" };
477 auto entries
= dnsdist::metrics::g_stats
.entries
.read_lock();
478 for (const auto& entry
: *entries
) {
479 const auto& metricName
= entry
.d_name
;
481 if (metricBlacklist
.count(metricName
) != 0) {
485 MetricDefinition metricDetails
;
486 if (!s_metricDefinitions
.getMetricDetails(metricName
, metricDetails
)) {
487 vinfolog("Do not have metric details for %s", metricName
);
491 const std::string prometheusTypeName
= s_metricDefinitions
.getPrometheusStringMetricType(metricDetails
.prometheusType
);
492 if (prometheusTypeName
.empty()) {
493 vinfolog("Unknown Prometheus type for %s", metricName
);
497 // Prometheus suggest using '_' instead of '-'
498 std::string prometheusMetricName
;
499 if (metricDetails
.customName
.empty()) {
500 prometheusMetricName
= "dnsdist_" + boost::replace_all_copy(metricName
, "-", "_");
503 prometheusMetricName
= metricDetails
.customName
;
506 // for these we have the help and types encoded in the sources
507 // but we need to be careful about labels in custom metrics
508 std::string helpName
= prometheusMetricName
.substr(0, prometheusMetricName
.find('{'));
509 output
<< "# HELP " << helpName
<< " " << metricDetails
.description
<< "\n";
510 output
<< "# TYPE " << helpName
<< " " << prometheusTypeName
<< "\n";
511 output
<< prometheusMetricName
<< " ";
513 if (const auto& val
= std::get_if
<pdns::stat_t
*>(&entry
.d_value
)) {
514 output
<< (*val
)->load();
516 else if (const auto& adval
= std::get_if
<pdns::stat_t_trait
<double>*>(&entry
.d_value
)) {
517 output
<< (*adval
)->load();
519 else if (const auto& dval
= std::get_if
<double*>(&entry
.d_value
)) {
522 else if (const auto& func
= std::get_if
<dnsdist::metrics::Stats::statfunction_t
>(&entry
.d_value
)) {
523 output
<< (*func
)(entry
.d_name
);
530 // Latency histogram buckets
531 output
<< "# HELP dnsdist_latency Histogram of responses by latency (in milliseconds)\n";
532 output
<< "# TYPE dnsdist_latency histogram\n";
533 uint64_t latency_amounts
= dnsdist::metrics::g_stats
.latency0_1
;
534 output
<< "dnsdist_latency_bucket{le=\"1\"} " << latency_amounts
<< "\n";
535 latency_amounts
+= dnsdist::metrics::g_stats
.latency1_10
;
536 output
<< "dnsdist_latency_bucket{le=\"10\"} " << latency_amounts
<< "\n";
537 latency_amounts
+= dnsdist::metrics::g_stats
.latency10_50
;
538 output
<< "dnsdist_latency_bucket{le=\"50\"} " << latency_amounts
<< "\n";
539 latency_amounts
+= dnsdist::metrics::g_stats
.latency50_100
;
540 output
<< "dnsdist_latency_bucket{le=\"100\"} " << latency_amounts
<< "\n";
541 latency_amounts
+= dnsdist::metrics::g_stats
.latency100_1000
;
542 output
<< "dnsdist_latency_bucket{le=\"1000\"} " << latency_amounts
<< "\n";
543 latency_amounts
+= dnsdist::metrics::g_stats
.latencySlow
; // Should be the same as latency_count
544 output
<< "dnsdist_latency_bucket{le=\"+Inf\"} " << latency_amounts
<< "\n";
545 output
<< "dnsdist_latency_sum " << dnsdist::metrics::g_stats
.latencySum
<< "\n";
546 output
<< "dnsdist_latency_count " << dnsdist::metrics::g_stats
.latencyCount
<< "\n";
548 auto states
= g_dstates
.getLocal();
549 const string statesbase
= "dnsdist_server_";
551 output
<< "# HELP " << statesbase
<< "status " << "Whether this backend is up (1) or down (0)" << "\n";
552 output
<< "# TYPE " << statesbase
<< "status " << "gauge" << "\n";
553 output
<< "# HELP " << statesbase
<< "queries " << "Amount of queries relayed to server" << "\n";
554 output
<< "# TYPE " << statesbase
<< "queries " << "counter" << "\n";
555 output
<< "# HELP " << statesbase
<< "responses " << "Amount of responses received from this server" << "\n";
556 output
<< "# TYPE " << statesbase
<< "responses " << "counter" << "\n";
557 output
<< "# HELP " << statesbase
<< "noncompliantresponses " << "Amount of non-compliant responses received from this server" << "\n";
558 output
<< "# TYPE " << statesbase
<< "noncompliantresponses " << "counter" << "\n";
559 output
<< "# HELP " << statesbase
<< "drops " << "Amount of queries not answered by server" << "\n";
560 output
<< "# TYPE " << statesbase
<< "drops " << "counter" << "\n";
561 output
<< "# HELP " << statesbase
<< "latency " << "Server's latency when answering questions in milliseconds" << "\n";
562 output
<< "# TYPE " << statesbase
<< "latency " << "gauge" << "\n";
563 output
<< "# HELP " << statesbase
<< "senderrors " << "Total number of OS send errors while relaying queries" << "\n";
564 output
<< "# TYPE " << statesbase
<< "senderrors " << "counter" << "\n";
565 output
<< "# HELP " << statesbase
<< "outstanding " << "Current number of queries that are waiting for a backend response" << "\n";
566 output
<< "# TYPE " << statesbase
<< "outstanding " << "gauge" << "\n";
567 output
<< "# HELP " << statesbase
<< "order " << "The order in which this server is picked" << "\n";
568 output
<< "# TYPE " << statesbase
<< "order " << "gauge" << "\n";
569 output
<< "# HELP " << statesbase
<< "weight " << "The weight within the order in which this server is picked" << "\n";
570 output
<< "# TYPE " << statesbase
<< "weight " << "gauge" << "\n";
571 output
<< "# HELP " << statesbase
<< "tcpdiedsendingquery " << "The number of TCP I/O errors while sending the query" << "\n";
572 output
<< "# TYPE " << statesbase
<< "tcpdiedsendingquery " << "counter" << "\n";
573 output
<< "# HELP " << statesbase
<< "tcpdiedreadingresponse " << "The number of TCP I/O errors while reading the response" << "\n";
574 output
<< "# TYPE " << statesbase
<< "tcpdiedreadingresponse " << "counter" << "\n";
575 output
<< "# HELP " << statesbase
<< "tcpgaveup " << "The number of TCP connections failing after too many attempts" << "\n";
576 output
<< "# TYPE " << statesbase
<< "tcpgaveup " << "counter" << "\n";
577 output
<< "# HELP " << statesbase
<< "tcpconnecttimeouts " << "The number of TCP connect timeouts" << "\n";
578 output
<< "# TYPE " << statesbase
<< "tcpconnecttimeouts " << "counter" << "\n";
579 output
<< "# HELP " << statesbase
<< "tcpreadtimeouts " << "The number of TCP read timeouts" << "\n";
580 output
<< "# TYPE " << statesbase
<< "tcpreadtimeouts " << "counter" << "\n";
581 output
<< "# HELP " << statesbase
<< "tcpwritetimeouts " << "The number of TCP write timeouts" << "\n";
582 output
<< "# TYPE " << statesbase
<< "tcpwritetimeouts " << "counter" << "\n";
583 output
<< "# HELP " << statesbase
<< "tcpcurrentconnections " << "The number of current TCP connections" << "\n";
584 output
<< "# TYPE " << statesbase
<< "tcpcurrentconnections " << "gauge" << "\n";
585 output
<< "# HELP " << statesbase
<< "tcpmaxconcurrentconnections " << "The maximum number of concurrent TCP connections" << "\n";
586 output
<< "# TYPE " << statesbase
<< "tcpmaxconcurrentconnections " << "counter" << "\n";
587 output
<< "# HELP " << statesbase
<< "tcptoomanyconcurrentconnections " << "Number of times we had to enforce the maximum number of concurrent TCP connections" << "\n";
588 output
<< "# TYPE " << statesbase
<< "tcptoomanyconcurrentconnections " << "counter" << "\n";
589 output
<< "# HELP " << statesbase
<< "tcpnewconnections " << "The number of established TCP connections in total" << "\n";
590 output
<< "# TYPE " << statesbase
<< "tcpnewconnections " << "counter" << "\n";
591 output
<< "# HELP " << statesbase
<< "tcpreusedconnections " << "The number of times a TCP connection has been reused" << "\n";
592 output
<< "# TYPE " << statesbase
<< "tcpreusedconnections " << "counter" << "\n";
593 output
<< "# HELP " << statesbase
<< "tcpavgqueriesperconn " << "The average number of queries per TCP connection" << "\n";
594 output
<< "# TYPE " << statesbase
<< "tcpavgqueriesperconn " << "gauge" << "\n";
595 output
<< "# HELP " << statesbase
<< "tcpavgconnduration " << "The average duration of a TCP connection (ms)" << "\n";
596 output
<< "# TYPE " << statesbase
<< "tcpavgconnduration " << "gauge" << "\n";
597 output
<< "# HELP " << statesbase
<< "tlsresumptions " << "The number of times a TLS session has been resumed" << "\n";
598 output
<< "# TYPE " << statesbase
<< "tlsresumptions " << "counter" << "\n";
599 output
<< "# HELP " << statesbase
<< "tcplatency " << "Server's latency when answering TCP questions in milliseconds" << "\n";
600 output
<< "# TYPE " << statesbase
<< "tcplatency " << "gauge" << "\n";
601 output
<< "# HELP " << statesbase
<< "healthcheckfailures " << "Number of health check attempts that failed (total)" << "\n";
602 output
<< "# TYPE " << statesbase
<< "healthcheckfailures " << "counter" << "\n";
603 output
<< "# HELP " << statesbase
<< "healthcheckfailuresparsing " << "Number of health check attempts where the response could not be parsed" << "\n";
604 output
<< "# TYPE " << statesbase
<< "healthcheckfailuresparsing " << "counter" << "\n";
605 output
<< "# HELP " << statesbase
<< "healthcheckfailurestimeout " << "Number of health check attempts where the response did not arrive in time" << "\n";
606 output
<< "# TYPE " << statesbase
<< "healthcheckfailurestimeout " << "counter" << "\n";
607 output
<< "# HELP " << statesbase
<< "healthcheckfailuresnetwork " << "Number of health check attempts that experienced a network issue" << "\n";
608 output
<< "# TYPE " << statesbase
<< "healthcheckfailuresnetwork " << "counter" << "\n";
609 output
<< "# HELP " << statesbase
<< "healthcheckfailuresmismatch " << "Number of health check attempts where the response did not match the query" << "\n";
610 output
<< "# TYPE " << statesbase
<< "healthcheckfailuresmismatch " << "counter" << "\n";
611 output
<< "# HELP " << statesbase
<< "healthcheckfailuresinvalid " << "Number of health check attempts where the DNS response was invalid" << "\n";
612 output
<< "# TYPE " << statesbase
<< "healthcheckfailuresinvalid " << "counter" << "\n";
614 for (const auto& state
: *states
) {
617 if (state
->getName().empty()) {
618 serverName
= state
->d_config
.remote
.toStringWithPort();
621 serverName
= state
->getName();
624 boost::replace_all(serverName
, ".", "_");
626 const std::string label
= boost::str(boost::format("{server=\"%1%\",address=\"%2%\"}")
627 % serverName
% state
->d_config
.remote
.toStringWithPort());
629 output
<< statesbase
<< "status" << label
<< " " << (state
->isUp() ? "1" : "0") << "\n";
630 output
<< statesbase
<< "queries" << label
<< " " << state
->queries
.load() << "\n";
631 output
<< statesbase
<< "responses" << label
<< " " << state
->responses
.load() << "\n";
632 output
<< statesbase
<< "noncompliantresponses" << label
<< " " << state
->nonCompliantResponses
.load() << "\n";
633 output
<< statesbase
<< "drops" << label
<< " " << state
->reuseds
.load() << "\n";
635 output
<< statesbase
<< "latency" << label
<< " " << state
->latencyUsec
/1000.0 << "\n";
636 output
<< statesbase
<< "tcplatency" << label
<< " " << state
->latencyUsecTCP
/1000.0 << "\n";
638 output
<< statesbase
<< "senderrors" << label
<< " " << state
->sendErrors
.load() << "\n";
639 output
<< statesbase
<< "outstanding" << label
<< " " << state
->outstanding
.load() << "\n";
640 output
<< statesbase
<< "order" << label
<< " " << state
->d_config
.order
<< "\n";
641 output
<< statesbase
<< "weight" << label
<< " " << state
->d_config
.d_weight
<< "\n";
642 output
<< statesbase
<< "tcpdiedsendingquery" << label
<< " " << state
->tcpDiedSendingQuery
<< "\n";
643 output
<< statesbase
<< "tcpdiedreadingresponse" << label
<< " " << state
->tcpDiedReadingResponse
<< "\n";
644 output
<< statesbase
<< "tcpgaveup" << label
<< " " << state
->tcpGaveUp
<< "\n";
645 output
<< statesbase
<< "tcpreadtimeouts" << label
<< " " << state
->tcpReadTimeouts
<< "\n";
646 output
<< statesbase
<< "tcpwritetimeouts" << label
<< " " << state
->tcpWriteTimeouts
<< "\n";
647 output
<< statesbase
<< "tcpconnecttimeouts" << label
<< " " << state
->tcpConnectTimeouts
<< "\n";
648 output
<< statesbase
<< "tcpcurrentconnections" << label
<< " " << state
->tcpCurrentConnections
<< "\n";
649 output
<< statesbase
<< "tcpmaxconcurrentconnections" << label
<< " " << state
->tcpMaxConcurrentConnections
<< "\n";
650 output
<< statesbase
<< "tcptoomanyconcurrentconnections" << label
<< " " << state
->tcpTooManyConcurrentConnections
<< "\n";
651 output
<< statesbase
<< "tcpnewconnections" << label
<< " " << state
->tcpNewConnections
<< "\n";
652 output
<< statesbase
<< "tcpreusedconnections" << label
<< " " << state
->tcpReusedConnections
<< "\n";
653 output
<< statesbase
<< "tcpavgqueriesperconn" << label
<< " " << state
->tcpAvgQueriesPerConnection
<< "\n";
654 output
<< statesbase
<< "tcpavgconnduration" << label
<< " " << state
->tcpAvgConnectionDuration
<< "\n";
655 output
<< statesbase
<< "tlsresumptions" << label
<< " " << state
->tlsResumptions
<< "\n";
656 output
<< statesbase
<< "healthcheckfailures" << label
<< " " << state
->d_healthCheckMetrics
.d_failures
<< "\n";
657 output
<< statesbase
<< "healthcheckfailuresparsing" << label
<< " " << state
->d_healthCheckMetrics
.d_parseErrors
<< "\n";
658 output
<< statesbase
<< "healthcheckfailurestimeout" << label
<< " " << state
->d_healthCheckMetrics
.d_timeOuts
<< "\n";
659 output
<< statesbase
<< "healthcheckfailuresnetwork" << label
<< " " << state
->d_healthCheckMetrics
.d_networkErrors
<< "\n";
660 output
<< statesbase
<< "healthcheckfailuresmismatch" << label
<< " " << state
->d_healthCheckMetrics
.d_mismatchErrors
<< "\n";
661 output
<< statesbase
<< "healthcheckfailuresinvalid" << label
<< " " << state
->d_healthCheckMetrics
.d_invalidResponseErrors
<< "\n";
664 const string frontsbase
= "dnsdist_frontend_";
665 output
<< "# HELP " << frontsbase
<< "queries " << "Amount of queries received by this frontend" << "\n";
666 output
<< "# TYPE " << frontsbase
<< "queries " << "counter" << "\n";
667 output
<< "# HELP " << frontsbase
<< "noncompliantqueries " << "Amount of non-compliant queries received by this frontend" << "\n";
668 output
<< "# TYPE " << frontsbase
<< "noncompliantqueries " << "counter" << "\n";
669 output
<< "# HELP " << frontsbase
<< "responses " << "Amount of responses sent by this frontend" << "\n";
670 output
<< "# TYPE " << frontsbase
<< "responses " << "counter" << "\n";
671 output
<< "# HELP " << frontsbase
<< "tcpdiedreadingquery " << "Amount of TCP connections terminated while reading the query from the client" << "\n";
672 output
<< "# TYPE " << frontsbase
<< "tcpdiedreadingquery " << "counter" << "\n";
673 output
<< "# HELP " << frontsbase
<< "tcpdiedsendingresponse " << "Amount of TCP connections terminated while sending a response to the client" << "\n";
674 output
<< "# TYPE " << frontsbase
<< "tcpdiedsendingresponse " << "counter" << "\n";
675 output
<< "# HELP " << frontsbase
<< "tcpgaveup " << "Amount of TCP connections terminated after too many attempts to get a connection to the backend" << "\n";
676 output
<< "# TYPE " << frontsbase
<< "tcpgaveup " << "counter" << "\n";
677 output
<< "# HELP " << frontsbase
<< "tcpclienttimeouts " << "Amount of TCP connections terminated by a timeout while reading from the client" << "\n";
678 output
<< "# TYPE " << frontsbase
<< "tcpclienttimeouts " << "counter" << "\n";
679 output
<< "# HELP " << frontsbase
<< "tcpdownstreamtimeouts " << "Amount of TCP connections terminated by a timeout while reading from the backend" << "\n";
680 output
<< "# TYPE " << frontsbase
<< "tcpdownstreamtimeouts " << "counter" << "\n";
681 output
<< "# HELP " << frontsbase
<< "tcpcurrentconnections " << "Amount of current incoming TCP connections from clients" << "\n";
682 output
<< "# TYPE " << frontsbase
<< "tcpcurrentconnections " << "gauge" << "\n";
683 output
<< "# HELP " << frontsbase
<< "tcpmaxconcurrentconnections " << "Maximum number of concurrent incoming TCP connections from clients" << "\n";
684 output
<< "# TYPE " << frontsbase
<< "tcpmaxconcurrentconnections " << "counter" << "\n";
685 output
<< "# HELP " << frontsbase
<< "tcpavgqueriesperconnection " << "The average number of queries per TCP connection" << "\n";
686 output
<< "# TYPE " << frontsbase
<< "tcpavgqueriesperconnection " << "gauge" << "\n";
687 output
<< "# HELP " << frontsbase
<< "tcpavgconnectionduration " << "The average duration of a TCP connection (ms)" << "\n";
688 output
<< "# TYPE " << frontsbase
<< "tcpavgconnectionduration " << "gauge" << "\n";
689 output
<< "# HELP " << frontsbase
<< "tlsqueries " << "Number of queries received by dnsdist over TLS, by TLS version" << "\n";
690 output
<< "# TYPE " << frontsbase
<< "tlsqueries " << "counter" << "\n";
691 output
<< "# HELP " << frontsbase
<< "tlsnewsessions " << "Amount of new TLS sessions negotiated" << "\n";
692 output
<< "# TYPE " << frontsbase
<< "tlsnewsessions " << "counter" << "\n";
693 output
<< "# HELP " << frontsbase
<< "tlsresumptions " << "Amount of TLS sessions resumed" << "\n";
694 output
<< "# TYPE " << frontsbase
<< "tlsresumptions " << "counter" << "\n";
695 output
<< "# HELP " << frontsbase
<< "tlsunknownticketkeys " << "Amount of attempts to resume TLS session from an unknown key (possibly expired)" << "\n";
696 output
<< "# TYPE " << frontsbase
<< "tlsunknownticketkeys " << "counter" << "\n";
697 output
<< "# HELP " << frontsbase
<< "tlsinactiveticketkeys " << "Amount of TLS sessions resumed from an inactive key" << "\n";
698 output
<< "# TYPE " << frontsbase
<< "tlsinactiveticketkeys " << "counter" << "\n";
699 output
<< "# HELP " << frontsbase
<< "tlshandshakefailures " << "Amount of TLS handshake failures" << "\n";
700 output
<< "# TYPE " << frontsbase
<< "tlshandshakefailures " << "counter" << "\n";
702 std::map
<std::string
,uint64_t> frontendDuplicates
;
703 for (const auto& front
: g_frontends
) {
704 if (front
->udpFD
== -1 && front
->tcpFD
== -1)
707 const string frontName
= front
->local
.toStringWithPort();
708 const string proto
= front
->getType();
709 const string fullName
= frontName
+ "_" + proto
;
710 uint64_t threadNumber
= 0;
711 auto dupPair
= frontendDuplicates
.emplace(fullName
, 1);
712 if (!dupPair
.second
) {
713 threadNumber
= dupPair
.first
->second
;
714 ++(dupPair
.first
->second
);
716 const std::string label
= boost::str(boost::format("{frontend=\"%1%\",proto=\"%2%\",thread=\"%3%\"} ")
717 % frontName
% proto
% threadNumber
);
719 output
<< frontsbase
<< "queries" << label
<< front
->queries
.load() << "\n";
720 output
<< frontsbase
<< "noncompliantqueries" << label
<< front
->nonCompliantQueries
.load() << "\n";
721 output
<< frontsbase
<< "responses" << label
<< front
->responses
.load() << "\n";
722 if (front
->isTCP()) {
723 output
<< frontsbase
<< "tcpdiedreadingquery" << label
<< front
->tcpDiedReadingQuery
.load() << "\n";
724 output
<< frontsbase
<< "tcpdiedsendingresponse" << label
<< front
->tcpDiedSendingResponse
.load() << "\n";
725 output
<< frontsbase
<< "tcpgaveup" << label
<< front
->tcpGaveUp
.load() << "\n";
726 output
<< frontsbase
<< "tcpclienttimeouts" << label
<< front
->tcpClientTimeouts
.load() << "\n";
727 output
<< frontsbase
<< "tcpdownstreamtimeouts" << label
<< front
->tcpDownstreamTimeouts
.load() << "\n";
728 output
<< frontsbase
<< "tcpcurrentconnections" << label
<< front
->tcpCurrentConnections
.load() << "\n";
729 output
<< frontsbase
<< "tcpmaxconcurrentconnections" << label
<< front
->tcpMaxConcurrentConnections
.load() << "\n";
730 output
<< frontsbase
<< "tcpavgqueriesperconnection" << label
<< front
->tcpAvgQueriesPerConnection
.load() << "\n";
731 output
<< frontsbase
<< "tcpavgconnectionduration" << label
<< front
->tcpAvgConnectionDuration
.load() << "\n";
732 if (front
->hasTLS()) {
733 output
<< frontsbase
<< "tlsnewsessions" << label
<< front
->tlsNewSessions
.load() << "\n";
734 output
<< frontsbase
<< "tlsresumptions" << label
<< front
->tlsResumptions
.load() << "\n";
735 output
<< frontsbase
<< "tlsunknownticketkeys" << label
<< front
->tlsUnknownTicketKey
.load() << "\n";
736 output
<< frontsbase
<< "tlsinactiveticketkeys" << label
<< front
->tlsInactiveTicketKey
.load() << "\n";
738 output
<< frontsbase
<< "tlsqueries{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",tls=\"tls10\"} " << front
->tls10queries
.load() << "\n";
739 output
<< frontsbase
<< "tlsqueries{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",tls=\"tls11\"} " << front
->tls11queries
.load() << "\n";
740 output
<< frontsbase
<< "tlsqueries{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",tls=\"tls12\"} " << front
->tls12queries
.load() << "\n";
741 output
<< frontsbase
<< "tlsqueries{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",tls=\"tls13\"} " << front
->tls13queries
.load() << "\n";
742 output
<< frontsbase
<< "tlsqueries{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",tls=\"unknown\"} " << front
->tlsUnknownqueries
.load() << "\n";
744 const TLSErrorCounters
* errorCounters
= nullptr;
745 if (front
->tlsFrontend
!= nullptr) {
746 errorCounters
= &front
->tlsFrontend
->d_tlsCounters
;
748 else if (front
->dohFrontend
!= nullptr) {
749 errorCounters
= &front
->dohFrontend
->d_tlsContext
.d_tlsCounters
;
752 if (errorCounters
!= nullptr) {
753 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"dhKeyTooSmall\"} " << errorCounters
->d_dhKeyTooSmall
<< "\n";
754 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"inappropriateFallBack\"} " << errorCounters
->d_inappropriateFallBack
<< "\n";
755 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"noSharedCipher\"} " << errorCounters
->d_noSharedCipher
<< "\n";
756 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"unknownCipherType\"} " << errorCounters
->d_unknownCipherType
<< "\n";
757 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"unknownKeyExchangeType\"} " << errorCounters
->d_unknownKeyExchangeType
<< "\n";
758 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"unknownProtocol\"} " << errorCounters
->d_unknownProtocol
<< "\n";
759 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"unsupportedEC\"} " << errorCounters
->d_unsupportedEC
<< "\n";
760 output
<< frontsbase
<< "tlshandshakefailures{frontend=\"" << frontName
<< "\",proto=\"" << proto
<< "\",thread=\"" << threadNumber
<< "\",error=\"unsupportedProtocol\"} " << errorCounters
->d_unsupportedProtocol
<< "\n";
766 output
<< "# HELP " << frontsbase
<< "http_connects " << "Number of DoH TCP connections established to this frontend" << "\n";
767 output
<< "# TYPE " << frontsbase
<< "http_connects " << "counter" << "\n";
769 output
<< "# HELP " << frontsbase
<< "doh_http_method_queries " << "Number of DoH queries received by dnsdist, by HTTP method" << "\n";
770 output
<< "# TYPE " << frontsbase
<< "doh_http_method_queries " << "counter" << "\n";
772 output
<< "# HELP " << frontsbase
<< "doh_http_version_queries " << "Number of DoH queries received by dnsdist, by HTTP version" << "\n";
773 output
<< "# TYPE " << frontsbase
<< "doh_http_version_queries " << "counter" << "\n";
775 output
<< "# HELP " << frontsbase
<< "doh_bad_requests " << "Number of requests that could not be converted to a DNS query" << "\n";
776 output
<< "# TYPE " << frontsbase
<< "doh_bad_requests " << "counter" << "\n";
778 output
<< "# HELP " << frontsbase
<< "doh_responses " << "Number of responses sent, by type" << "\n";
779 output
<< "# TYPE " << frontsbase
<< "doh_responses " << "counter" << "\n";
781 output
<< "# HELP " << frontsbase
<< "doh_version_status_responses " << "Number of requests that could not be converted to a DNS query" << "\n";
782 output
<< "# TYPE " << frontsbase
<< "doh_version_status_responses " << "counter" << "\n";
784 #ifdef HAVE_DNS_OVER_HTTPS
785 std::map
<std::string
,uint64_t> dohFrontendDuplicates
;
786 for(const auto& doh
: g_dohlocals
) {
787 const string frontName
= doh
->d_tlsContext
.d_addr
.toStringWithPort();
788 uint64_t threadNumber
= 0;
789 auto dupPair
= frontendDuplicates
.emplace(frontName
, 1);
790 if (!dupPair
.second
) {
791 threadNumber
= dupPair
.first
->second
;
792 ++(dupPair
.first
->second
);
794 const std::string addrlabel
= boost::str(boost::format("frontend=\"%1%\",thread=\"%2%\"") % frontName
% threadNumber
);
795 const std::string label
= "{" + addrlabel
+ "} ";
797 output
<< frontsbase
<< "http_connects" << label
<< doh
->d_httpconnects
<< "\n";
798 output
<< frontsbase
<< "doh_http_method_queries{method=\"get\"," << addrlabel
<< "} " << doh
->d_getqueries
<< "\n";
799 output
<< frontsbase
<< "doh_http_method_queries{method=\"post\"," << addrlabel
<< "} " << doh
->d_postqueries
<< "\n";
801 output
<< frontsbase
<< "doh_http_version_queries{version=\"1\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nbQueries
<< "\n";
802 output
<< frontsbase
<< "doh_http_version_queries{version=\"2\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nbQueries
<< "\n";
804 output
<< frontsbase
<< "doh_bad_requests{" << addrlabel
<< "} " << doh
->d_badrequests
<< "\n";
806 output
<< frontsbase
<< "doh_responses{type=\"error\"," << addrlabel
<< "} " << doh
->d_errorresponses
<< "\n";
807 output
<< frontsbase
<< "doh_responses{type=\"redirect\"," << addrlabel
<< "} " << doh
->d_redirectresponses
<< "\n";
808 output
<< frontsbase
<< "doh_responses{type=\"valid\"," << addrlabel
<< "} " << doh
->d_validresponses
<< "\n";
810 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"1\",status=\"200\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nb200Responses
<< "\n";
811 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"1\",status=\"400\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nb400Responses
<< "\n";
812 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"1\",status=\"403\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nb403Responses
<< "\n";
813 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"1\",status=\"500\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nb500Responses
<< "\n";
814 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"1\",status=\"502\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nb502Responses
<< "\n";
815 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"1\",status=\"other\"," << addrlabel
<< "} " << doh
->d_http1Stats
.d_nbOtherResponses
<< "\n";
816 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"2\",status=\"200\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nb200Responses
<< "\n";
817 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"2\",status=\"400\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nb400Responses
<< "\n";
818 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"2\",status=\"403\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nb403Responses
<< "\n";
819 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"2\",status=\"500\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nb500Responses
<< "\n";
820 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"2\",status=\"502\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nb502Responses
<< "\n";
821 output
<< frontsbase
<< "doh_version_status_responses{httpversion=\"2\",status=\"other\"," << addrlabel
<< "} " << doh
->d_http2Stats
.d_nbOtherResponses
<< "\n";
823 #endif /* HAVE_DNS_OVER_HTTPS */
825 auto localPools
= g_pools
.getLocal();
826 const string cachebase
= "dnsdist_pool_";
827 output
<< "# HELP dnsdist_pool_servers " << "Number of servers in that pool" << "\n";
828 output
<< "# TYPE dnsdist_pool_servers " << "gauge" << "\n";
829 output
<< "# HELP dnsdist_pool_active_servers " << "Number of available servers in that pool" << "\n";
830 output
<< "# TYPE dnsdist_pool_active_servers " << "gauge" << "\n";
832 output
<< "# HELP dnsdist_pool_cache_size " << "Maximum number of entries that this cache can hold" << "\n";
833 output
<< "# TYPE dnsdist_pool_cache_size " << "gauge" << "\n";
834 output
<< "# HELP dnsdist_pool_cache_entries " << "Number of entries currently present in that cache" << "\n";
835 output
<< "# TYPE dnsdist_pool_cache_entries " << "gauge" << "\n";
836 output
<< "# HELP dnsdist_pool_cache_hits " << "Number of hits from that cache" << "\n";
837 output
<< "# TYPE dnsdist_pool_cache_hits " << "counter" << "\n";
838 output
<< "# HELP dnsdist_pool_cache_misses " << "Number of misses from that cache" << "\n";
839 output
<< "# TYPE dnsdist_pool_cache_misses " << "counter" << "\n";
840 output
<< "# HELP dnsdist_pool_cache_deferred_inserts " << "Number of insertions into that cache skipped because it was already locked" << "\n";
841 output
<< "# TYPE dnsdist_pool_cache_deferred_inserts " << "counter" << "\n";
842 output
<< "# HELP dnsdist_pool_cache_deferred_lookups " << "Number of lookups into that cache skipped because it was already locked" << "\n";
843 output
<< "# TYPE dnsdist_pool_cache_deferred_lookups " << "counter" << "\n";
844 output
<< "# HELP dnsdist_pool_cache_lookup_collisions " << "Number of lookups into that cache that triggered a collision (same hash but different entry)" << "\n";
845 output
<< "# TYPE dnsdist_pool_cache_lookup_collisions " << "counter" << "\n";
846 output
<< "# HELP dnsdist_pool_cache_insert_collisions " << "Number of insertions into that cache that triggered a collision (same hash but different entry)" << "\n";
847 output
<< "# TYPE dnsdist_pool_cache_insert_collisions " << "counter" << "\n";
848 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";
849 output
<< "# TYPE dnsdist_pool_cache_ttl_too_shorts " << "counter" << "\n";
850 output
<< "# HELP dnsdist_pool_cache_cleanup_count_total " << "Number of times the cache has been scanned to remove expired entries, if any" << "\n";
851 output
<< "# TYPE dnsdist_pool_cache_cleanup_count_total " << "counter" << "\n";
853 for (const auto& entry
: *localPools
) {
854 string poolName
= entry
.first
;
856 if (poolName
.empty()) {
857 poolName
= "_default_";
859 const string label
= "{pool=\"" + poolName
+ "\"}";
860 const std::shared_ptr
<ServerPool
> pool
= entry
.second
;
861 output
<< "dnsdist_pool_servers" << label
<< " " << pool
->countServers(false) << "\n";
862 output
<< "dnsdist_pool_active_servers" << label
<< " " << pool
->countServers(true) << "\n";
864 if (pool
->packetCache
!= nullptr) {
865 const auto& cache
= pool
->packetCache
;
867 output
<< cachebase
<< "cache_size" <<label
<< " " << cache
->getMaxEntries() << "\n";
868 output
<< cachebase
<< "cache_entries" <<label
<< " " << cache
->getEntriesCount() << "\n";
869 output
<< cachebase
<< "cache_hits" <<label
<< " " << cache
->getHits() << "\n";
870 output
<< cachebase
<< "cache_misses" <<label
<< " " << cache
->getMisses() << "\n";
871 output
<< cachebase
<< "cache_deferred_inserts" <<label
<< " " << cache
->getDeferredInserts() << "\n";
872 output
<< cachebase
<< "cache_deferred_lookups" <<label
<< " " << cache
->getDeferredLookups() << "\n";
873 output
<< cachebase
<< "cache_lookup_collisions" <<label
<< " " << cache
->getLookupCollisions() << "\n";
874 output
<< cachebase
<< "cache_insert_collisions" <<label
<< " " << cache
->getInsertCollisions() << "\n";
875 output
<< cachebase
<< "cache_ttl_too_shorts" <<label
<< " " << cache
->getTTLTooShorts() << "\n";
876 output
<< cachebase
<< "cache_cleanup_count_total" <<label
<< " " << cache
->getCleanupCount() << "\n";
880 output
<< "# HELP dnsdist_rule_hits " << "Number of hits of that rule" << "\n";
881 output
<< "# TYPE dnsdist_rule_hits " << "counter" << "\n";
882 addRulesToPrometheusOutput(output
, g_ruleactions
);
883 addRulesToPrometheusOutput(output
, g_respruleactions
);
884 addRulesToPrometheusOutput(output
, g_cachehitrespruleactions
);
885 addRulesToPrometheusOutput(output
, g_cacheInsertedRespRuleActions
);
886 addRulesToPrometheusOutput(output
, g_selfansweredrespruleactions
);
888 #ifndef DISABLE_DYNBLOCKS
889 output
<< "# HELP dnsdist_dynblocks_nmg_top_offenders_hits_per_second " << "Number of hits per second blocked by Dynamic Blocks (netmasks) for the top offenders, averaged over the last 60s" << "\n";
890 output
<< "# TYPE dnsdist_dynblocks_nmg_top_offenders_hits_per_second " << "gauge" << "\n";
891 auto topNetmasksByReason
= DynBlockMaintenance::getHitsForTopNetmasks();
892 for (const auto& entry
: topNetmasksByReason
) {
893 for (const auto& netmask
: entry
.second
) {
894 output
<< "dnsdist_dynblocks_nmg_top_offenders_hits_per_second{reason=\"" << entry
.first
<< "\",netmask=\"" << netmask
.first
.toString() << "\"} " << netmask
.second
<< "\n";
898 output
<< "# HELP dnsdist_dynblocks_smt_top_offenders_hits_per_second " << "Number of this per second blocked by Dynamic Blocks (suffixes) for the top offenders, averaged over the last 60s" << "\n";
899 output
<< "# TYPE dnsdist_dynblocks_smt_top_offenders_hits_per_second " << "gauge" << "\n";
900 auto topSuffixesByReason
= DynBlockMaintenance::getHitsForTopSuffixes();
901 for (const auto& entry
: topSuffixesByReason
) {
902 for (const auto& suffix
: entry
.second
) {
903 output
<< "dnsdist_dynblocks_smt_top_offenders_hits_per_second{reason=\"" << entry
.first
<< "\",suffix=\"" << suffix
.first
.toString() << "\"} " << suffix
.second
<< "\n";
906 #endif /* DISABLE_DYNBLOCKS */
908 output
<< "# HELP dnsdist_info " << "Info from dnsdist, value is always 1" << "\n";
909 output
<< "# TYPE dnsdist_info " << "gauge" << "\n";
910 output
<< "dnsdist_info{version=\"" << VERSION
<< "\"} " << "1" << "\n";
912 resp
.body
= output
.str();
913 resp
.headers
["Content-Type"] = "text/plain";
915 #endif /* DISABLE_PROMETHEUS */
917 using namespace json11
;
919 static void addStatsToJSONObject(Json::object
& obj
)
921 auto entries
= dnsdist::metrics::g_stats
.entries
.read_lock();
922 for (const auto& entry
: *entries
) {
923 if (entry
.d_name
== "special-memory-usage") {
924 continue; // Too expensive for get-all
926 if (const auto& val
= std::get_if
<pdns::stat_t
*>(&entry
.d_value
)) {
927 obj
.emplace(entry
.d_name
, (double)(*val
)->load());
928 } else if (const auto& adval
= std::get_if
<pdns::stat_t_trait
<double>*>(&entry
.d_value
)) {
929 obj
.emplace(entry
.d_name
, (*adval
)->load());
930 } else if (const auto& dval
= std::get_if
<double*>(&entry
.d_value
)) {
931 obj
.emplace(entry
.d_name
, (**dval
));
932 } else if (const auto& func
= std::get_if
<dnsdist::metrics::Stats::statfunction_t
>(&entry
.d_value
)) {
933 obj
.emplace(entry
.d_name
, (double)(*func
)(entry
.d_name
));
938 #ifndef DISABLE_BUILTIN_HTML
939 static void handleJSONStats(const YaHTTP::Request
& req
, YaHTTP::Response
& resp
)
941 handleCORS(req
, resp
);
944 if (req
.getvars
.count("command") == 0) {
949 const string
& command
= req
.getvars
.at("command");
951 if (command
== "stats") {
952 auto obj
=Json::object
{
953 { "packetcache-hits", 0},
954 { "packetcache-misses", 0},
955 { "over-capacity-drops", 0 },
956 { "too-old-drops", 0 },
957 { "server-policy", g_policy
.getLocal()->getName()}
960 addStatsToJSONObject(obj
);
963 resp
.body
= my_json
.dump();
964 resp
.headers
["Content-Type"] = "application/json";
966 else if (command
== "dynblocklist") {
968 #ifndef DISABLE_DYNBLOCKS
969 auto nmg
= g_dynblockNMG
.getLocal();
972 for (const auto& entry
: *nmg
) {
973 if (!(now
< entry
.second
.until
)) {
976 uint64_t counter
= entry
.second
.blocks
;
977 if (entry
.second
.bpf
&& g_defaultBPFFilter
) {
978 counter
+= g_defaultBPFFilter
->getHits(entry
.first
.getNetwork());
981 {"reason", entry
.second
.reason
},
982 {"seconds", static_cast<double>(entry
.second
.until
.tv_sec
- now
.tv_sec
)},
983 {"blocks", static_cast<double>(counter
)},
984 {"action", DNSAction::typeToString(entry
.second
.action
!= DNSAction::Action::None
? entry
.second
.action
: g_dynBlockAction
)},
985 {"warning", entry
.second
.warning
},
986 {"ebpf", entry
.second
.bpf
}
988 obj
.emplace(entry
.first
.toString(), thing
);
991 auto smt
= g_dynblockSMT
.getLocal();
992 smt
->visit([&now
,&obj
](const SuffixMatchTree
<DynBlock
>& node
) {
993 if (!(now
< node
.d_value
.until
)) {
997 if (!node
.d_value
.domain
.empty()) {
998 dom
= node
.d_value
.domain
.toString();
1001 {"reason", node
.d_value
.reason
},
1002 {"seconds", static_cast<double>(node
.d_value
.until
.tv_sec
- now
.tv_sec
)},
1003 {"blocks", static_cast<double>(node
.d_value
.blocks
)},
1004 {"action", DNSAction::typeToString(node
.d_value
.action
!= DNSAction::Action::None
? node
.d_value
.action
: g_dynBlockAction
)},
1005 {"ebpf", node
.d_value
.bpf
}
1007 obj
.emplace(dom
, thing
);
1009 #endif /* DISABLE_DYNBLOCKS */
1011 resp
.body
= my_json
.dump();
1012 resp
.headers
["Content-Type"] = "application/json";
1014 else if (command
== "ebpfblocklist") {
1017 struct timespec now
;
1019 for (const auto& dynbpf
: g_dynBPFFilters
) {
1020 std::vector
<std::tuple
<ComboAddress
, uint64_t, struct timespec
> > addrStats
= dynbpf
->getAddrStats();
1021 for (const auto& entry
: addrStats
) {
1024 {"seconds", (double)(std::get
<2>(entry
).tv_sec
- now
.tv_sec
)},
1025 {"blocks", (double)(std::get
<1>(entry
))}
1027 obj
.emplace(std::get
<0>(entry
).toString(), thing
);
1030 if (g_defaultBPFFilter
) {
1031 auto nmg
= g_dynblockNMG
.getLocal();
1032 for (const auto& entry
: *nmg
) {
1033 if (!(now
< entry
.second
.until
) || !entry
.second
.bpf
) {
1036 uint64_t counter
= entry
.second
.blocks
+ g_defaultBPFFilter
->getHits(entry
.first
.getNetwork());
1038 {"reason", entry
.second
.reason
},
1039 {"seconds", static_cast<double>(entry
.second
.until
.tv_sec
- now
.tv_sec
)},
1040 {"blocks", static_cast<double>(counter
)},
1041 {"action", DNSAction::typeToString(entry
.second
.action
!= DNSAction::Action::None
? entry
.second
.action
: g_dynBlockAction
)},
1042 {"warning", entry
.second
.warning
},
1044 obj
.emplace(entry
.first
.toString(), thing
);
1047 #endif /* HAVE_EBPF */
1049 resp
.body
= my_json
.dump();
1050 resp
.headers
["Content-Type"] = "application/json";
1056 #endif /* DISABLE_BUILTIN_HTML */
1058 static void addServerToJSON(Json::array
& servers
, int id
, const std::shared_ptr
<DownstreamState
>& a
)
1061 if (a
->d_config
.availability
== DownstreamState::Availability::Up
) {
1064 else if (a
->d_config
.availability
== DownstreamState::Availability::Down
) {
1068 status
= (a
->upStatus
? "up" : "down");
1072 pools
.reserve(a
->d_config
.pools
.size());
1073 for (const auto& p
: a
->d_config
.pools
) {
1077 Json::object server
{
1079 {"name", a
->getName()},
1080 {"address", a
->d_config
.remote
.toStringWithPort()},
1082 {"protocol", a
->getProtocol().toPrettyString()},
1083 {"qps", (double)a
->queryLoad
},
1084 {"qpsLimit", (double)a
->qps
.getRate()},
1085 {"outstanding", (double)a
->outstanding
},
1086 {"reuseds", (double)a
->reuseds
},
1087 {"weight", (double)a
->d_config
.d_weight
},
1088 {"order", (double)a
->d_config
.order
},
1089 {"pools", std::move(pools
)},
1090 {"latency", (double)(a
->latencyUsec
/1000.0)},
1091 {"queries", (double)a
->queries
},
1092 {"responses", (double)a
->responses
},
1093 {"nonCompliantResponses", (double)a
->nonCompliantResponses
},
1094 {"sendErrors", (double)a
->sendErrors
},
1095 {"tcpDiedSendingQuery", (double)a
->tcpDiedSendingQuery
},
1096 {"tcpDiedReadingResponse", (double)a
->tcpDiedReadingResponse
},
1097 {"tcpGaveUp", (double)a
->tcpGaveUp
},
1098 {"tcpConnectTimeouts", (double)a
->tcpConnectTimeouts
},
1099 {"tcpReadTimeouts", (double)a
->tcpReadTimeouts
},
1100 {"tcpWriteTimeouts", (double)a
->tcpWriteTimeouts
},
1101 {"tcpCurrentConnections", (double)a
->tcpCurrentConnections
},
1102 {"tcpMaxConcurrentConnections", (double)a
->tcpMaxConcurrentConnections
},
1103 {"tcpTooManyConcurrentConnections", (double)a
->tcpTooManyConcurrentConnections
},
1104 {"tcpNewConnections", (double)a
->tcpNewConnections
},
1105 {"tcpReusedConnections", (double)a
->tcpReusedConnections
},
1106 {"tcpAvgQueriesPerConnection", (double)a
->tcpAvgQueriesPerConnection
},
1107 {"tcpAvgConnectionDuration", (double)a
->tcpAvgConnectionDuration
},
1108 {"tlsResumptions", (double)a
->tlsResumptions
},
1109 {"tcpLatency", (double)(a
->latencyUsecTCP
/1000.0)},
1110 {"healthCheckFailures", (double)(a
->d_healthCheckMetrics
.d_failures
)},
1111 {"healthCheckFailuresParsing", (double)(a
->d_healthCheckMetrics
.d_parseErrors
)},
1112 {"healthCheckFailuresTimeout", (double)(a
->d_healthCheckMetrics
.d_timeOuts
)},
1113 {"healthCheckFailuresNetwork", (double)(a
->d_healthCheckMetrics
.d_networkErrors
)},
1114 {"healthCheckFailuresMismatch", (double)(a
->d_healthCheckMetrics
.d_mismatchErrors
)},
1115 {"healthCheckFailuresInvalid", (double)(a
->d_healthCheckMetrics
.d_invalidResponseErrors
)},
1116 {"dropRate", (double)a
->dropRate
}
1119 /* sending a latency for a DOWN server doesn't make sense */
1120 if (a
->d_config
.availability
== DownstreamState::Availability::Down
) {
1121 server
["latency"] = nullptr;
1122 server
["tcpLatency"] = nullptr;
1125 servers
.push_back(std::move(server
));
1128 static void handleStats(const YaHTTP::Request
& req
, YaHTTP::Response
& resp
)
1130 handleCORS(req
, resp
);
1135 Json::array servers
;
1137 auto localServers
= g_dstates
.getLocal();
1138 servers
.reserve(localServers
->size());
1139 for (const auto& a
: *localServers
) {
1140 addServerToJSON(servers
, num
++, a
);
1144 Json::array frontends
;
1146 frontends
.reserve(g_frontends
.size());
1147 for (const auto& front
: g_frontends
) {
1148 if (front
->udpFD
== -1 && front
->tcpFD
== -1)
1150 Json::object frontend
{
1152 { "address", front
->local
.toStringWithPort() },
1153 { "udp", front
->udpFD
>= 0 },
1154 { "tcp", front
->tcpFD
>= 0 },
1155 { "type", front
->getType() },
1156 { "queries", (double) front
->queries
.load() },
1157 { "nonCompliantQueries", (double) front
->nonCompliantQueries
.load() },
1158 { "responses", (double) front
->responses
.load() },
1159 { "tcpDiedReadingQuery", (double) front
->tcpDiedReadingQuery
.load() },
1160 { "tcpDiedSendingResponse", (double) front
->tcpDiedSendingResponse
.load() },
1161 { "tcpGaveUp", (double) front
->tcpGaveUp
.load() },
1162 { "tcpClientTimeouts", (double) front
->tcpClientTimeouts
},
1163 { "tcpDownstreamTimeouts", (double) front
->tcpDownstreamTimeouts
},
1164 { "tcpCurrentConnections", (double) front
->tcpCurrentConnections
},
1165 { "tcpMaxConcurrentConnections", (double) front
->tcpMaxConcurrentConnections
},
1166 { "tcpAvgQueriesPerConnection", (double) front
->tcpAvgQueriesPerConnection
},
1167 { "tcpAvgConnectionDuration", (double) front
->tcpAvgConnectionDuration
},
1168 { "tlsNewSessions", (double) front
->tlsNewSessions
},
1169 { "tlsResumptions", (double) front
->tlsResumptions
},
1170 { "tlsUnknownTicketKey", (double) front
->tlsUnknownTicketKey
},
1171 { "tlsInactiveTicketKey", (double) front
->tlsInactiveTicketKey
},
1172 { "tls10Queries", (double) front
->tls10queries
},
1173 { "tls11Queries", (double) front
->tls11queries
},
1174 { "tls12Queries", (double) front
->tls12queries
},
1175 { "tls13Queries", (double) front
->tls13queries
},
1176 { "tlsUnknownQueries", (double) front
->tlsUnknownqueries
},
1178 const TLSErrorCounters
* errorCounters
= nullptr;
1179 if (front
->tlsFrontend
!= nullptr) {
1180 errorCounters
= &front
->tlsFrontend
->d_tlsCounters
;
1182 else if (front
->dohFrontend
!= nullptr) {
1183 errorCounters
= &front
->dohFrontend
->d_tlsContext
.d_tlsCounters
;
1185 if (errorCounters
!= nullptr) {
1186 frontend
["tlsHandshakeFailuresDHKeyTooSmall"] = (double)errorCounters
->d_dhKeyTooSmall
;
1187 frontend
["tlsHandshakeFailuresInappropriateFallBack"] = (double)errorCounters
->d_inappropriateFallBack
;
1188 frontend
["tlsHandshakeFailuresNoSharedCipher"] = (double)errorCounters
->d_noSharedCipher
;
1189 frontend
["tlsHandshakeFailuresUnknownCipher"] = (double)errorCounters
->d_unknownCipherType
;
1190 frontend
["tlsHandshakeFailuresUnknownKeyExchangeType"] = (double)errorCounters
->d_unknownKeyExchangeType
;
1191 frontend
["tlsHandshakeFailuresUnknownProtocol"] = (double)errorCounters
->d_unknownProtocol
;
1192 frontend
["tlsHandshakeFailuresUnsupportedEC"] = (double)errorCounters
->d_unsupportedEC
;
1193 frontend
["tlsHandshakeFailuresUnsupportedProtocol"] = (double)errorCounters
->d_unsupportedProtocol
;
1195 frontends
.push_back(std::move(frontend
));
1199 #ifdef HAVE_DNS_OVER_HTTPS
1201 dohs
.reserve(g_dohlocals
.size());
1203 for (const auto& doh
: g_dohlocals
) {
1204 dohs
.emplace_back(Json::object
{
1206 { "address", doh
->d_tlsContext
.d_addr
.toStringWithPort() },
1207 { "http-connects", (double) doh
->d_httpconnects
},
1208 { "http1-queries", (double) doh
->d_http1Stats
.d_nbQueries
},
1209 { "http2-queries", (double) doh
->d_http2Stats
.d_nbQueries
},
1210 { "http1-200-responses", (double) doh
->d_http1Stats
.d_nb200Responses
},
1211 { "http2-200-responses", (double) doh
->d_http2Stats
.d_nb200Responses
},
1212 { "http1-400-responses", (double) doh
->d_http1Stats
.d_nb400Responses
},
1213 { "http2-400-responses", (double) doh
->d_http2Stats
.d_nb400Responses
},
1214 { "http1-403-responses", (double) doh
->d_http1Stats
.d_nb403Responses
},
1215 { "http2-403-responses", (double) doh
->d_http2Stats
.d_nb403Responses
},
1216 { "http1-500-responses", (double) doh
->d_http1Stats
.d_nb500Responses
},
1217 { "http2-500-responses", (double) doh
->d_http2Stats
.d_nb500Responses
},
1218 { "http1-502-responses", (double) doh
->d_http1Stats
.d_nb502Responses
},
1219 { "http2-502-responses", (double) doh
->d_http2Stats
.d_nb502Responses
},
1220 { "http1-other-responses", (double) doh
->d_http1Stats
.d_nbOtherResponses
},
1221 { "http2-other-responses", (double) doh
->d_http2Stats
.d_nbOtherResponses
},
1222 { "get-queries", (double) doh
->d_getqueries
},
1223 { "post-queries", (double) doh
->d_postqueries
},
1224 { "bad-requests", (double) doh
->d_badrequests
},
1225 { "error-responses", (double) doh
->d_errorresponses
},
1226 { "redirect-responses", (double) doh
->d_redirectresponses
},
1227 { "valid-responses", (double) doh
->d_validresponses
}
1231 #endif /* HAVE_DNS_OVER_HTTPS */
1235 auto localPools
= g_pools
.getLocal();
1237 pools
.reserve(localPools
->size());
1238 for (const auto& pool
: *localPools
) {
1239 const auto& cache
= pool
.second
->packetCache
;
1240 Json::object entry
{
1242 { "name", pool
.first
},
1243 { "serversCount", (double) pool
.second
->countServers(false) },
1244 { "cacheSize", (double) (cache
? cache
->getMaxEntries() : 0) },
1245 { "cacheEntries", (double) (cache
? cache
->getEntriesCount() : 0) },
1246 { "cacheHits", (double) (cache
? cache
->getHits() : 0) },
1247 { "cacheMisses", (double) (cache
? cache
->getMisses() : 0) },
1248 { "cacheDeferredInserts", (double) (cache
? cache
->getDeferredInserts() : 0) },
1249 { "cacheDeferredLookups", (double) (cache
? cache
->getDeferredLookups() : 0) },
1250 { "cacheLookupCollisions", (double) (cache
? cache
->getLookupCollisions() : 0) },
1251 { "cacheInsertCollisions", (double) (cache
? cache
->getInsertCollisions() : 0) },
1252 { "cacheTTLTooShorts", (double) (cache
? cache
->getTTLTooShorts() : 0) },
1253 { "cacheCleanupCount", (double) (cache
? cache
->getCleanupCount() : 0) }
1255 pools
.push_back(std::move(entry
));
1260 /* unfortunately DNSActions have getStats(),
1261 and DNSResponseActions do not. */
1263 auto localRules
= g_ruleactions
.getLocal();
1265 rules
.reserve(localRules
->size());
1266 for (const auto& a
: *localRules
) {
1269 {"creationOrder", (double)a
.d_creationOrder
},
1270 {"uuid", boost::uuids::to_string(a
.d_id
)},
1272 {"matches", (double)a
.d_rule
->d_matches
},
1273 {"rule", a
.d_rule
->toString()},
1274 {"action", a
.d_action
->toString()},
1275 {"action-stats", a
.d_action
->getStats()}
1277 rules
.push_back(std::move(rule
));
1280 auto responseRules
= someResponseRulesToJson(&g_respruleactions
);
1281 auto cacheHitResponseRules
= someResponseRulesToJson(&g_cachehitrespruleactions
);
1282 auto cacheInsertedResponseRules
= someResponseRulesToJson(&g_cacheInsertedRespRuleActions
);
1283 auto selfAnsweredResponseRules
= someResponseRulesToJson(&g_selfansweredrespruleactions
);
1288 g_ACL
.getLocal()->toStringVector(&vec
);
1290 for (const auto& s
: vec
) {
1298 string localaddressesStr
;
1300 std::set
<std::string
> localaddresses
;
1301 for (const auto& front
: g_frontends
) {
1302 localaddresses
.insert(front
->local
.toStringWithPort());
1304 for (const auto& addr
: localaddresses
) {
1305 if (!localaddressesStr
.empty()) {
1306 localaddressesStr
+= ", ";
1308 localaddressesStr
+= addr
;
1313 addStatsToJSONObject(stats
);
1315 Json
responseObject(Json::object({
1316 { "daemon_type", "dnsdist" },
1317 { "version", VERSION
},
1318 { "servers", std::move(servers
) },
1319 { "frontends", std::move(frontends
) },
1320 { "pools", std::move(pools
) },
1321 { "rules", std::move(rules
) },
1322 { "response-rules", std::move(responseRules
) },
1323 { "cache-hit-response-rules", std::move(cacheHitResponseRules
) },
1324 { "cache-inserted-response-rules", std::move(cacheInsertedResponseRules
) },
1325 { "self-answered-response-rules", std::move(selfAnsweredResponseRules
) },
1326 { "acl", std::move(acl
) },
1327 { "local", std::move(localaddressesStr
) },
1328 { "dohFrontends", std::move(dohs
) },
1329 { "statistics", std::move(stats
) }
1332 resp
.headers
["Content-Type"] = "application/json";
1333 resp
.body
= responseObject
.dump();
1336 static void handlePoolStats(const YaHTTP::Request
& req
, YaHTTP::Response
& resp
)
1338 handleCORS(req
, resp
);
1339 const auto poolName
= req
.getvars
.find("name");
1340 if (poolName
== req
.getvars
.end()) {
1348 auto localPools
= g_pools
.getLocal();
1349 const auto poolIt
= localPools
->find(poolName
->second
);
1350 if (poolIt
== localPools
->end()) {
1355 const auto& pool
= poolIt
->second
;
1356 const auto& cache
= pool
->packetCache
;
1357 Json::object entry
{
1358 { "name", poolName
->second
},
1359 { "serversCount", (double) pool
->countServers(false) },
1360 { "cacheSize", (double) (cache
? cache
->getMaxEntries() : 0) },
1361 { "cacheEntries", (double) (cache
? cache
->getEntriesCount() : 0) },
1362 { "cacheHits", (double) (cache
? cache
->getHits() : 0) },
1363 { "cacheMisses", (double) (cache
? cache
->getMisses() : 0) },
1364 { "cacheDeferredInserts", (double) (cache
? cache
->getDeferredInserts() : 0) },
1365 { "cacheDeferredLookups", (double) (cache
? cache
->getDeferredLookups() : 0) },
1366 { "cacheLookupCollisions", (double) (cache
? cache
->getLookupCollisions() : 0) },
1367 { "cacheInsertCollisions", (double) (cache
? cache
->getInsertCollisions() : 0) },
1368 { "cacheTTLTooShorts", (double) (cache
? cache
->getTTLTooShorts() : 0) },
1369 { "cacheCleanupCount", (double) (cache
? cache
->getCleanupCount() : 0) }
1372 Json::array servers
;
1374 for (const auto& a
: *pool
->getServers()) {
1375 addServerToJSON(servers
, num
, a
.second
);
1379 resp
.headers
["Content-Type"] = "application/json";
1380 Json my_json
= Json::object
{
1382 { "servers", servers
}
1385 resp
.body
= my_json
.dump();
1388 static void handleStatsOnly(const YaHTTP::Request
& req
, YaHTTP::Response
& resp
)
1390 handleCORS(req
, resp
);
1395 auto entries
= dnsdist::metrics::g_stats
.entries
.read_lock();
1396 for (const auto& item
: *entries
) {
1397 if (item
.d_name
== "special-memory-usage") {
1398 continue; // Too expensive for get-all
1401 if (const auto& val
= std::get_if
<pdns::stat_t
*>(&item
.d_value
)) {
1402 doc
.push_back(Json::object
{
1403 { "type", "StatisticItem" },
1404 { "name", item
.d_name
},
1405 { "value", (double)(*val
)->load() }
1408 else if (const auto& adval
= std::get_if
<pdns::stat_t_trait
<double>*>(&item
.d_value
)) {
1409 doc
.push_back(Json::object
{
1410 { "type", "StatisticItem" },
1411 { "name", item
.d_name
},
1412 { "value", (*adval
)->load() }
1415 else if (const auto& dval
= std::get_if
<double*>(&item
.d_value
)) {
1416 doc
.push_back(Json::object
{
1417 { "type", "StatisticItem" },
1418 { "name", item
.d_name
},
1419 { "value", (**dval
) }
1422 else if (const auto& func
= std::get_if
<dnsdist::metrics::Stats::statfunction_t
>(&item
.d_value
)) {
1423 doc
.push_back(Json::object
{
1424 { "type", "StatisticItem" },
1425 { "name", item
.d_name
},
1426 { "value", (double)(*func
)(item
.d_name
) }
1432 resp
.body
= my_json
.dump();
1433 resp
.headers
["Content-Type"] = "application/json";
1436 #ifndef DISABLE_WEB_CONFIG
1437 static void handleConfigDump(const YaHTTP::Request
& req
, YaHTTP::Response
& resp
)
1439 handleCORS(req
, resp
);
1443 typedef boost::variant
<bool, double, std::string
> configentry_t
;
1444 std::vector
<std::pair
<std::string
, configentry_t
> > configEntries
{
1445 { "acl", g_ACL
.getLocal()->toString() },
1446 { "allow-empty-response", g_allowEmptyResponse
},
1447 { "control-socket", g_serverControl
.toStringWithPort() },
1448 { "ecs-override", g_ECSOverride
},
1449 { "ecs-source-prefix-v4", (double) g_ECSSourcePrefixV4
},
1450 { "ecs-source-prefix-v6", (double) g_ECSSourcePrefixV6
},
1451 { "fixup-case", g_fixupCase
},
1452 { "max-outstanding", (double) g_maxOutstanding
},
1453 { "server-policy", g_policy
.getLocal()->getName() },
1454 { "stale-cache-entries-ttl", (double) g_staleCacheEntriesTTL
},
1455 { "tcp-recv-timeout", (double) g_tcpRecvTimeout
},
1456 { "tcp-send-timeout", (double) g_tcpSendTimeout
},
1457 { "truncate-tc", g_truncateTC
},
1458 { "verbose", g_verbose
},
1459 { "verbose-health-checks", g_verboseHealthChecks
}
1461 for(const auto& item
: configEntries
) {
1462 if (const auto& bval
= boost::get
<bool>(&item
.second
)) {
1463 doc
.push_back(Json::object
{
1464 { "type", "ConfigSetting" },
1465 { "name", item
.first
},
1469 else if (const auto& sval
= boost::get
<string
>(&item
.second
)) {
1470 doc
.push_back(Json::object
{
1471 { "type", "ConfigSetting" },
1472 { "name", item
.first
},
1476 else if (const auto& dval
= boost::get
<double>(&item
.second
)) {
1477 doc
.push_back(Json::object
{
1478 { "type", "ConfigSetting" },
1479 { "name", item
.first
},
1485 resp
.body
= my_json
.dump();
1486 resp
.headers
["Content-Type"] = "application/json";
1489 static void handleAllowFrom(const YaHTTP::Request
& req
, YaHTTP::Response
& resp
)
1491 handleCORS(req
, resp
);
1493 resp
.headers
["Content-Type"] = "application/json";
1496 if (req
.method
== "PUT") {
1498 Json doc
= Json::parse(req
.body
, err
);
1500 if (!doc
.is_null()) {
1502 auto aclList
= doc
["value"];
1503 if (aclList
.is_array()) {
1505 for (const auto& value
: aclList
.array_items()) {
1507 nmg
.addMask(value
.string_value());
1508 } catch (NetmaskException
&e
) {
1514 if (resp
.status
== 200) {
1515 infolog("Updating the ACL via the API to %s", nmg
.toString());
1516 g_ACL
.setState(nmg
);
1528 if (resp
.status
== 200) {
1531 g_ACL
.getLocal()->toStringVector(&vec
);
1533 for(const auto& s
: vec
) {
1538 { "type", "ConfigSetting" },
1539 { "name", "allow-from" },
1543 resp
.body
= my_json
.dump();
1546 #endif /* DISABLE_WEB_CONFIG */
1548 #ifndef DISABLE_WEB_CACHE_MANAGEMENT
1549 static void handleCacheManagement(const YaHTTP::Request
& req
, YaHTTP::Response
& resp
)
1551 handleCORS(req
, resp
);
1553 resp
.headers
["Content-Type"] = "application/json";
1556 if (req
.method
!= "DELETE") {
1559 { "status", "denied" },
1560 { "error", "invalid method" }
1562 resp
.body
= Json(obj
).dump();
1566 const auto poolName
= req
.getvars
.find("pool");
1567 const auto expungeName
= req
.getvars
.find("name");
1568 const auto expungeType
= req
.getvars
.find("type");
1569 const auto suffix
= req
.getvars
.find("suffix");
1570 if (poolName
== req
.getvars
.end() || expungeName
== req
.getvars
.end()) {
1573 { "status", "denied" },
1574 { "error", "missing 'pool' or 'name' parameter" },
1576 resp
.body
= Json(obj
).dump();
1581 QType
type(QType::ANY
);
1583 name
= DNSName(expungeName
->second
);
1585 catch (const std::exception
& e
) {
1588 { "status", "error" },
1589 { "error", "unable to parse the requested name" },
1591 resp
.body
= Json(obj
).dump();
1594 if (expungeType
!= req
.getvars
.end()) {
1595 type
= QType::chartocode(expungeType
->second
.c_str());
1598 std::shared_ptr
<ServerPool
> pool
;
1600 pool
= getPool(g_pools
.getCopy(), poolName
->second
);
1602 catch (const std::exception
& e
) {
1605 { "status", "not found" },
1606 { "error", "the requested pool does not exist" },
1608 resp
.body
= Json(obj
).dump();
1612 auto cache
= pool
->getCache();
1613 if (cache
== nullptr) {
1616 { "status", "not found" },
1617 { "error", "there is no cache associated with the requested pool" },
1619 resp
.body
= Json(obj
).dump();
1623 auto removed
= cache
->expungeByName(name
, type
.getCode(), suffix
!= req
.getvars
.end());
1626 { "status", "purged" },
1627 { "count", std::to_string(removed
) }
1629 resp
.body
= Json(obj
).dump();
1631 #endif /* DISABLE_WEB_CACHE_MANAGEMENT */
1633 template<typename T
> static void addRingEntryToList(const struct timespec
& now
, Json::array
& list
, const T
& entry
)
1635 constexpr bool response
= std::is_same_v
<T
, Rings::Response
>;
1637 { "age", static_cast<double>(DiffTime(entry
.when
, now
)) },
1638 { "id", ntohs(entry
.dh
.id
) },
1639 { "name", entry
.name
.toString() },
1640 { "requestor", entry
.requestor
.toStringWithPort() },
1641 { "size", static_cast<int>(entry
.size
) },
1642 { "qtype", entry
.qtype
},
1643 { "protocol", entry
.protocol
.toString() },
1644 { "rd", static_cast<bool>(entry
.dh
.rd
) },
1646 if constexpr (!response
) {
1647 #if defined(DNSDIST_RINGS_WITH_MACADDRESS)
1648 tmp
.emplace("mac", entry
.hasmac
? std::string(reinterpret_cast<const char*>(entry
.macaddress
.data()), entry
.macaddress
.size()) : std::string());
1652 tmp
.emplace("latency", static_cast<double>(entry
.usec
));
1653 tmp
.emplace("rcode", static_cast<uint8_t>(entry
.dh
.rcode
));
1654 tmp
.emplace("tc", static_cast<bool>(entry
.dh
.tc
));
1655 tmp
.emplace("aa", static_cast<bool>(entry
.dh
.aa
));
1656 tmp
.emplace("answers", ntohs(entry
.dh
.ancount
));
1657 auto server
= entry
.ds
.toStringWithPort();
1658 tmp
.emplace("backend", server
!= "0.0.0.0:0" ? std::move(server
) : "Cache");
1660 list
.push_back(std::move(tmp
));
1663 static void handleRings(const YaHTTP::Request
& req
, YaHTTP::Response
& resp
)
1665 handleCORS(req
, resp
);
1667 std::optional
<size_t> maxNumberOfQueries
{std::nullopt
};
1668 std::optional
<size_t> maxNumberOfResponses
{std::nullopt
};
1670 const auto maxQueries
= req
.getvars
.find("maxQueries");
1671 if (maxQueries
!= req
.getvars
.end()) {
1673 maxNumberOfQueries
= pdns::checked_stoi
<size_t>(maxQueries
->second
);
1675 catch (const std::exception
& exp
) {
1676 vinfolog("Error parsing the 'maxQueries' value from rings HTTP GET query: %s", exp
.what());
1680 const auto maxResponses
= req
.getvars
.find("maxResponses");
1681 if (maxResponses
!= req
.getvars
.end()) {
1683 maxNumberOfResponses
= pdns::checked_stoi
<size_t>(maxResponses
->second
);
1685 catch (const std::exception
& exp
) {
1686 vinfolog("Error parsing the 'maxResponses' value from rings HTTP GET query: %s", exp
.what());
1693 size_t numberOfQueries
= 0;
1694 size_t numberOfResponses
= 0;
1695 Json::array queries
;
1696 Json::array responses
;
1702 for (const auto& shard
: g_rings
.d_shards
) {
1703 if (!maxNumberOfQueries
|| numberOfQueries
< *maxNumberOfQueries
) {
1704 auto queryRing
= shard
->queryRing
.lock();
1705 for (const auto& entry
: *queryRing
) {
1706 addRingEntryToList(now
, queries
, entry
);
1708 if (maxNumberOfQueries
&& numberOfQueries
>= *maxNumberOfQueries
) {
1713 if (!maxNumberOfResponses
|| numberOfResponses
< *maxNumberOfResponses
) {
1714 auto responseRing
= shard
->respRing
.lock();
1715 for (const auto& entry
: *responseRing
) {
1716 addRingEntryToList(now
, responses
, entry
);
1717 numberOfResponses
++;
1718 if (maxNumberOfResponses
&& numberOfResponses
>= *maxNumberOfResponses
) {
1724 doc
.emplace("queries", std::move(queries
));
1725 doc
.emplace("responses", std::move(responses
));
1727 resp
.body
= my_json
.dump();
1728 resp
.headers
["Content-Type"] = "application/json";
1731 static std::unordered_map
<std::string
, std::function
<void(const YaHTTP::Request
&, YaHTTP::Response
&)>> s_webHandlers
;
1733 void registerWebHandler(const std::string
& endpoint
, std::function
<void(const YaHTTP::Request
&, YaHTTP::Response
&)> handler
);
1735 void registerWebHandler(const std::string
& endpoint
, std::function
<void(const YaHTTP::Request
&, YaHTTP::Response
&)> handler
)
1737 s_webHandlers
[endpoint
] = std::move(handler
);
1740 void clearWebHandlers()
1742 s_webHandlers
.clear();
1745 #ifndef DISABLE_BUILTIN_HTML
1746 #include "htmlfiles.h"
1748 static void redirectToIndex(const YaHTTP::Request
& req
, YaHTTP::Response
& resp
)
1750 const string charset
= "; charset=utf-8";
1751 resp
.body
.assign(s_urlmap
.at("index.html"));
1752 resp
.headers
["Content-Type"] = "text/html" + charset
;
1756 static void handleBuiltInFiles(const YaHTTP::Request
& req
, YaHTTP::Response
& resp
)
1758 if (req
.url
.path
.empty() || !s_urlmap
.count(req
.url
.path
.c_str()+1)) {
1763 resp
.body
.assign(s_urlmap
.at(req
.url
.path
.c_str()+1));
1765 vector
<string
> parts
;
1766 stringtok(parts
, req
.url
.path
, ".");
1767 static const std::unordered_map
<std::string
, std::string
> contentTypeMap
= {
1768 { "html", "text/html" },
1769 { "css", "text/css" },
1770 { "js", "application/javascript" },
1771 { "png", "image/png" },
1774 const auto& it
= contentTypeMap
.find(parts
.back());
1775 if (it
!= contentTypeMap
.end()) {
1776 const string charset
= "; charset=utf-8";
1777 resp
.headers
["Content-Type"] = it
->second
+ charset
;
1782 #endif /* DISABLE_BUILTIN_HTML */
1784 void registerBuiltInWebHandlers()
1786 #ifndef DISABLE_BUILTIN_HTML
1787 registerWebHandler("/jsonstat", handleJSONStats
);
1788 #endif /* DISABLE_BUILTIN_HTML */
1789 #ifndef DISABLE_PROMETHEUS
1790 registerWebHandler("/metrics", handlePrometheus
);
1791 #endif /* DISABLE_PROMETHEUS */
1792 registerWebHandler("/api/v1/servers/localhost", handleStats
);
1793 registerWebHandler("/api/v1/servers/localhost/pool", handlePoolStats
);
1794 registerWebHandler("/api/v1/servers/localhost/statistics", handleStatsOnly
);
1795 registerWebHandler("/api/v1/servers/localhost/rings", handleRings
);
1796 #ifndef DISABLE_WEB_CONFIG
1797 registerWebHandler("/api/v1/servers/localhost/config", handleConfigDump
);
1798 registerWebHandler("/api/v1/servers/localhost/config/allow-from", handleAllowFrom
);
1799 #endif /* DISABLE_WEB_CONFIG */
1800 #ifndef DISABLE_WEB_CACHE_MANAGEMENT
1801 registerWebHandler("/api/v1/cache", handleCacheManagement
);
1802 #endif /* DISABLE_WEB_CACHE_MANAGEMENT */
1803 #ifndef DISABLE_BUILTIN_HTML
1804 registerWebHandler("/", redirectToIndex
);
1806 for (const auto& path
: s_urlmap
) {
1807 registerWebHandler("/" + path
.first
, handleBuiltInFiles
);
1809 #endif /* DISABLE_BUILTIN_HTML */
1812 static void connectionThread(WebClientConnection
&& conn
)
1814 setThreadName("dnsdist/webConn");
1816 vinfolog("Webserver handling connection from %s", conn
.getClient().toStringWithPort());
1819 YaHTTP::AsyncRequestLoader yarl
;
1820 YaHTTP::Request req
;
1821 bool finished
= false;
1823 yarl
.initialize(&req
);
1827 bytes
= read(conn
.getSocket().getHandle(), buf
, sizeof(buf
));
1829 string data
= string(buf
, bytes
);
1830 finished
= yarl
.feed(data
);
1832 // read error OR EOF
1838 req
.getvars
.erase("_"); // jQuery cache buster
1840 YaHTTP::Response resp
;
1841 resp
.version
= req
.version
;
1844 auto config
= g_webserverConfig
.lock();
1846 addCustomHeaders(resp
, config
->customHeaders
);
1847 addSecurityHeaders(resp
, config
->customHeaders
);
1849 /* indicate that the connection will be closed after completion of the response */
1850 resp
.headers
["Connection"] = "close";
1852 /* no need to send back the API key if any */
1853 resp
.headers
.erase("X-API-Key");
1855 if (req
.method
== "OPTIONS") {
1856 /* the OPTIONS method should not require auth, otherwise it breaks CORS */
1857 handleCORS(req
, resp
);
1860 else if (!handleAuthorization(req
)) {
1861 YaHTTP::strstr_map_t::iterator header
= req
.headers
.find("authorization");
1862 if (header
!= req
.headers
.end()) {
1863 vinfolog("HTTP Request \"%s\" from %s: Web Authentication failed", req
.url
.path
, conn
.getClient().toStringWithPort());
1866 resp
.body
= "<h1>Unauthorized</h1>";
1867 resp
.headers
["WWW-Authenticate"] = "basic realm=\"PowerDNS\"";
1869 else if (!isMethodAllowed(req
)) {
1873 const auto it
= s_webHandlers
.find(req
.url
.path
);
1874 if (it
!= s_webHandlers
.end()) {
1875 it
->second(req
, resp
);
1882 std::ostringstream ofs
;
1884 string done
= ofs
.str();
1885 writen2(conn
.getSocket().getHandle(), done
.c_str(), done
.size());
1887 catch (const YaHTTP::ParseError
& e
) {
1888 vinfolog("Webserver thread died with parse error exception while processing a request from %s: %s", conn
.getClient().toStringWithPort(), e
.what());
1890 catch (const std::exception
& e
) {
1891 vinfolog("Webserver thread died with exception while processing a request from %s: %s", conn
.getClient().toStringWithPort(), e
.what());
1894 vinfolog("Webserver thread died with exception while processing a request from %s", conn
.getClient().toStringWithPort());
1898 void setWebserverAPIKey(std::unique_ptr
<CredentialsHolder
>&& apiKey
)
1900 auto config
= g_webserverConfig
.lock();
1903 config
->apiKey
= std::move(apiKey
);
1905 config
->apiKey
.reset();
1909 void setWebserverPassword(std::unique_ptr
<CredentialsHolder
>&& password
)
1911 g_webserverConfig
.lock()->password
= std::move(password
);
1914 void setWebserverACL(const std::string
& acl
)
1916 NetmaskGroup newACL
;
1917 newACL
.toMasks(acl
);
1919 g_webserverConfig
.lock()->acl
= std::move(newACL
);
1922 void setWebserverCustomHeaders(const boost::optional
<std::unordered_map
<std::string
, std::string
> > customHeaders
)
1924 g_webserverConfig
.lock()->customHeaders
= customHeaders
;
1927 void setWebserverStatsRequireAuthentication(bool require
)
1929 g_webserverConfig
.lock()->statsRequireAuthentication
= require
;
1932 void setWebserverAPIRequiresAuthentication(bool require
)
1934 g_webserverConfig
.lock()->apiRequiresAuthentication
= require
;
1937 void setWebserverDashboardRequiresAuthentication(bool require
)
1939 g_webserverConfig
.lock()->dashboardRequiresAuthentication
= require
;
1942 void setWebserverMaxConcurrentConnections(size_t max
)
1944 s_connManager
.setMaxConcurrentConnections(max
);
1947 void dnsdistWebserverThread(int sock
, const ComboAddress
& local
)
1949 setThreadName("dnsdist/webserv");
1950 infolog("Webserver launched on %s", local
.toStringWithPort());
1953 auto config
= g_webserverConfig
.lock();
1954 if (!config
->password
&& config
->dashboardRequiresAuthentication
) {
1955 warnlog("Webserver launched on %s without a password set!", local
.toStringWithPort());
1961 ComboAddress
remote(local
);
1962 int fd
= SAccept(sock
, remote
);
1964 if (!isClientAllowedByACL(remote
)) {
1965 vinfolog("Connection to webserver from client %s is not allowed, closing", remote
.toStringWithPort());
1970 WebClientConnection
conn(remote
, fd
);
1971 vinfolog("Got a connection to the webserver from %s", remote
.toStringWithPort());
1973 std::thread
t(connectionThread
, std::move(conn
));
1976 catch (const std::exception
& e
) {
1977 vinfolog("Had an error accepting new webserver connection: %s", e
.what());