]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/dnsdist-web.cc
Update rules-actions.rst
[thirdparty/pdns.git] / pdns / dnsdist-web.cc
CommitLineData
12471842
PL
1/*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
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.
8 *
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.
12 *
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.
17 *
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.
21 */
50bed881 22#include "dnsdist.hh"
23#include "sstuff.hh"
24#include "ext/json11/json11.hpp"
25#include "ext/incbin/incbin.h"
26#include "dolog.hh"
27#include <thread>
519f5484 28#include "threadname.hh"
50bed881 29#include <sstream>
30#include <yahttp/yahttp.hpp>
31#include "namespaces.hh"
32#include <sys/time.h>
33#include <sys/resource.h>
34#include "ext/incbin/incbin.h"
35#include "htmlfiles.h"
36#include "base64.hh"
85c7ca75 37#include "gettime.hh"
76025fec 38#include <boost/format.hpp>
50bed881 39
56d68fad 40bool g_apiReadWrite{false};
80dbd7d2 41WebserverConfig g_webserverConfig;
56d68fad
RG
42std::string g_apiConfigDirectory;
43
44static bool apiWriteConfigFile(const string& filebasename, const string& content)
45{
46 if (!g_apiReadWrite) {
47 errlog("Not writing content to %s since the API is read-only", filebasename);
48 return false;
49 }
50
51 if (g_apiConfigDirectory.empty()) {
52 vinfolog("Not writing content to %s since the API configuration directory is not set", filebasename);
53 return false;
54 }
55
56 string filename = g_apiConfigDirectory + "/" + filebasename + ".conf";
57 ofstream ofconf(filename.c_str());
58 if (!ofconf) {
59 errlog("Could not open configuration fragment file '%s' for writing: %s", filename, stringerror());
60 return false;
61 }
62 ofconf << "-- Generated by the REST API, DO NOT EDIT" << endl;
63 ofconf << content << endl;
64 ofconf.close();
65 return true;
66}
67
68static void apiSaveACL(const NetmaskGroup& nmg)
69{
70 vector<string> vec;
2d29e6b7 71 nmg.toStringVector(&vec);
56d68fad
RG
72
73 string acl;
74 for(const auto& s : vec) {
75 if (!acl.empty()) {
76 acl += ", ";
77 }
78 acl += "\"" + s + "\"";
79 }
80
81 string content = "setACL({" + acl + "})";
82 apiWriteConfigFile("acl", content);
83}
50bed881 84
55afa518 85static bool checkAPIKey(const YaHTTP::Request& req, const string& expectedApiKey)
50bed881 86{
55afa518
RG
87 if (expectedApiKey.empty()) {
88 return false;
89 }
90
91 const auto header = req.headers.find("x-api-key");
92 if (header != req.headers.end()) {
93 return (header->second == expectedApiKey);
94 }
95
96 return false;
97}
98
99static bool checkWebPassword(const YaHTTP::Request& req, const string &expected_password)
100{
101 static const char basicStr[] = "basic ";
102
103 const auto header = req.headers.find("authorization");
104
105 if (header != req.headers.end() && toLower(header->second).find(basicStr) == 0) {
106 string cookie = header->second.substr(sizeof(basicStr) - 1);
50bed881 107
108 string plain;
109 B64Decode(cookie, plain);
110
111 vector<string> cparts;
112 stringtok(cparts, plain, ":");
113
55afa518
RG
114 if (cparts.size() == 2) {
115 return cparts[1] == expected_password;
116 }
50bed881 117 }
55afa518
RG
118
119 return false;
120}
121
122static bool isAnAPIRequest(const YaHTTP::Request& req)
123{
124 return req.url.path.find("/api/") == 0;
125}
126
127static bool isAnAPIRequestAllowedWithWebAuth(const YaHTTP::Request& req)
128{
129 return req.url.path == "/api/v1/servers/localhost";
130}
131
132static bool isAStatsRequest(const YaHTTP::Request& req)
133{
37a5c2d5 134 return req.url.path == "/jsonstat" || req.url.path == "/metrics";
55afa518
RG
135}
136
80dbd7d2 137static bool compareAuthorization(const YaHTTP::Request& req)
55afa518 138{
80dbd7d2
CHB
139 std::lock_guard<std::mutex> lock(g_webserverConfig.lock);
140
55afa518
RG
141 if (isAnAPIRequest(req)) {
142 /* Access to the API requires a valid API key */
80dbd7d2 143 if (checkAPIKey(req, g_webserverConfig.apiKey)) {
55afa518 144 return true;
87893e08 145 }
55afa518 146
80dbd7d2 147 return isAnAPIRequestAllowedWithWebAuth(req) && checkWebPassword(req, g_webserverConfig.password);
55afa518
RG
148 }
149
150 if (isAStatsRequest(req)) {
151 /* Access to the stats is allowed for both API and Web users */
80dbd7d2 152 return checkAPIKey(req, g_webserverConfig.apiKey) || checkWebPassword(req, g_webserverConfig.password);
87893e08 153 }
55afa518 154
80dbd7d2 155 return checkWebPassword(req, g_webserverConfig.password);
50bed881 156}
157
56d68fad
RG
158static bool isMethodAllowed(const YaHTTP::Request& req)
159{
160 if (req.method == "GET") {
161 return true;
162 }
163 if (req.method == "PUT" && g_apiReadWrite) {
164 if (req.url.path == "/api/v1/servers/localhost/config/allow-from") {
165 return true;
166 }
167 }
168 return false;
169}
170
55afa518 171static void handleCORS(const YaHTTP::Request& req, YaHTTP::Response& resp)
5387cc78 172{
55afa518 173 const auto origin = req.headers.find("Origin");
5387cc78
RG
174 if (origin != req.headers.end()) {
175 if (req.method == "OPTIONS") {
176 /* Pre-flight request */
56d68fad
RG
177 if (g_apiReadWrite) {
178 resp.headers["Access-Control-Allow-Methods"] = "GET, PUT";
179 }
180 else {
181 resp.headers["Access-Control-Allow-Methods"] = "GET";
182 }
87893e08 183 resp.headers["Access-Control-Allow-Headers"] = "Authorization, X-API-Key";
5387cc78
RG
184 }
185
186 resp.headers["Access-Control-Allow-Origin"] = origin->second;
55afa518
RG
187
188 if (isAStatsRequest(req) || isAnAPIRequestAllowedWithWebAuth(req)) {
189 resp.headers["Access-Control-Allow-Credentials"] = "true";
190 }
5387cc78
RG
191 }
192}
50bed881 193
002decab
RG
194static void addSecurityHeaders(YaHTTP::Response& resp, const boost::optional<std::map<std::string, std::string> >& customHeaders)
195{
196 static const std::vector<std::pair<std::string, std::string> > headers = {
197 { "X-Content-Type-Options", "nosniff" },
198 { "X-Frame-Options", "deny" },
199 { "X-Permitted-Cross-Domain-Policies", "none" },
200 { "X-XSS-Protection", "1; mode=block" },
201 { "Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'" },
202 };
203
204 for (const auto& h : headers) {
205 if (customHeaders) {
206 const auto& custom = customHeaders->find(h.first);
207 if (custom != customHeaders->end()) {
208 continue;
209 }
210 }
211 resp.headers[h.first] = h.second;
212 }
213}
214
215static void addCustomHeaders(YaHTTP::Response& resp, const boost::optional<std::map<std::string, std::string> >& customHeaders)
216{
217 if (!customHeaders)
218 return;
219
220 for (const auto& c : *customHeaders) {
221 if (!c.second.empty()) {
222 resp.headers[c.first] = c.second;
223 }
224 }
225}
226
d18eab67
CH
227template<typename T>
228static json11::Json::array someResponseRulesToJson(GlobalStateHolder<vector<T>>* someResponseRules)
229{
230 using namespace json11;
231 Json::array responseRules;
232 int num=0;
a9c2e4ab
RG
233 auto localResponseRules = someResponseRules->getLocal();
234 for(const auto& a : *localResponseRules) {
d18eab67
CH
235 Json::object rule{
236 {"id", num++},
f8a222ac 237 {"creationOrder", (double)a.d_creationOrder},
d18eab67
CH
238 {"uuid", boost::uuids::to_string(a.d_id)},
239 {"matches", (double)a.d_rule->d_matches},
240 {"rule", a.d_rule->toString()},
241 {"action", a.d_action->toString()},
242 };
243 responseRules.push_back(rule);
244 }
245 return responseRules;
246}
247
80dbd7d2 248static void connectionThread(int sock, ComboAddress remote)
50bed881 249{
519f5484 250 setThreadName("dnsdist/webConn");
77c9bc9a 251
50bed881 252 using namespace json11;
fb7f8ec3 253 vinfolog("Webserver handling connection from %s", remote.toStringWithPort());
56d68fad 254
438a189b 255 try {
56d68fad 256 YaHTTP::AsyncRequestLoader yarl;
438a189b 257 YaHTTP::Request req;
56d68fad
RG
258 bool finished = false;
259
260 yarl.initialize(&req);
261 while(!finished) {
262 int bytes;
263 char buf[1024];
264 bytes = read(sock, buf, sizeof(buf));
265 if (bytes > 0) {
266 string data = string(buf, bytes);
267 finished = yarl.feed(data);
268 } else {
269 // read error OR EOF
270 break;
271 }
272 }
273 yarl.finalize();
50bed881 274
438a189b 275 string command=req.getvars["command"];
50bed881 276
438a189b 277 req.getvars.erase("_"); // jQuery cache buster
50bed881 278
e3d76be2
RG
279 YaHTTP::Response resp;
280 resp.version = req.version;
c8c1a4fc 281 const string charset = "; charset=utf-8";
002decab 282
80dbd7d2
CHB
283 {
284 std::lock_guard<std::mutex> lock(g_webserverConfig.lock);
285
286 addCustomHeaders(resp, g_webserverConfig.customHeaders);
287 addSecurityHeaders(resp, g_webserverConfig.customHeaders);
288 }
00b29455
CHB
289 /* indicate that the connection will be closed after completion of the response */
290 resp.headers["Connection"] = "close";
002decab 291
87893e08
RG
292 /* no need to send back the API key if any */
293 resp.headers.erase("X-API-Key");
50bed881 294
5387cc78
RG
295 if(req.method == "OPTIONS") {
296 /* the OPTIONS method should not require auth, otherwise it breaks CORS */
297 handleCORS(req, resp);
298 resp.status=200;
299 }
80dbd7d2 300 else if (!compareAuthorization(req)) {
cc55a8f2
JB
301 YaHTTP::strstr_map_t::iterator header = req.headers.find("authorization");
302 if (header != req.headers.end())
303 errlog("HTTP Request \"%s\" from %s: Web Authentication failed", req.url.path, remote.toStringWithPort());
438a189b 304 resp.status=401;
305 resp.body="<h1>Unauthorized</h1>";
306 resp.headers["WWW-Authenticate"] = "basic realm=\"PowerDNS\"";
50bed881 307
50bed881 308 }
56d68fad 309 else if(!isMethodAllowed(req)) {
5387cc78
RG
310 resp.status=405;
311 }
312 else if(req.url.path=="/jsonstat") {
313 handleCORS(req, resp);
438a189b 314 resp.status=200;
d745cd6f 315
5387cc78
RG
316 if(command=="stats") {
317 auto obj=Json::object {
318 { "packetcache-hits", 0},
319 { "packetcache-misses", 0},
320 { "over-capacity-drops", 0 },
321 { "too-old-drops", 0 },
322 { "server-policy", g_policy.getLocal()->name}
323 };
324
325 for(const auto& e : g_stats.entries) {
330dcb5c
OM
326 if (e.first == "special-memory-usage")
327 continue; // Too expensive for get-all
5387cc78 328 if(const auto& val = boost::get<DNSDistStats::stat_t*>(&e.second))
ca12836d 329 obj.insert({e.first, (double)(*val)->load()});
af619119
RG
330 else if (const auto& dval = boost::get<double*>(&e.second))
331 obj.insert({e.first, (**dval)});
5387cc78 332 else
778fe0a8 333 obj.insert({e.first, (double)(*boost::get<DNSDistStats::statfunction_t>(&e.second))(e.first)});
5387cc78
RG
334 }
335 Json my_json = obj;
336 resp.body=my_json.dump();
5d1c98df 337 resp.headers["Content-Type"] = "application/json";
d745cd6f 338 }
5387cc78
RG
339 else if(command=="dynblocklist") {
340 Json::object obj;
a9c2e4ab 341 auto nmg = g_dynblockNMG.getLocal();
5387cc78 342 struct timespec now;
85c7ca75 343 gettime(&now);
a9c2e4ab 344 for(const auto& e: *nmg) {
5387cc78 345 if(now < e->second.until ) {
8429ad04
RG
346 Json::object thing{
347 {"reason", e->second.reason},
348 {"seconds", (double)(e->second.until.tv_sec - now.tv_sec)},
477c86a0 349 {"blocks", (double)e->second.blocks},
1d3ba133
RG
350 {"action", DNSAction::typeToString(e->second.action != DNSAction::Action::None ? e->second.action : g_dynBlockAction) },
351 {"warning", e->second.warning }
8429ad04 352 };
5387cc78
RG
353 obj.insert({e->first.toString(), thing});
354 }
355 }
0b71b874 356
a9c2e4ab
RG
357 auto smt = g_dynblockSMT.getLocal();
358 smt->visit([&now,&obj](const SuffixMatchTree<DynBlock>& node) {
0b71b874 359 if(now <node.d_value.until) {
360 string dom("empty");
361 if(!node.d_value.domain.empty())
362 dom = node.d_value.domain.toString();
477c86a0
RG
363 Json::object thing{
364 {"reason", node.d_value.reason},
365 {"seconds", (double)(node.d_value.until.tv_sec - now.tv_sec)},
366 {"blocks", (double)node.d_value.blocks},
693f7eba 367 {"action", DNSAction::typeToString(node.d_value.action != DNSAction::Action::None ? node.d_value.action : g_dynBlockAction) }
477c86a0 368 };
0b71b874 369 obj.insert({dom, thing});
370 }
371 });
372
373
374
8429ad04
RG
375 Json my_json = obj;
376 resp.body=my_json.dump();
377 resp.headers["Content-Type"] = "application/json";
378 }
379 else if(command=="ebpfblocklist") {
380 Json::object obj;
381#ifdef HAVE_EBPF
382 struct timespec now;
383 gettime(&now);
384 for (const auto& dynbpf : g_dynBPFFilters) {
385 std::vector<std::tuple<ComboAddress, uint64_t, struct timespec> > addrStats = dynbpf->getAddrStats();
386 for (const auto& entry : addrStats) {
387 Json::object thing
388 {
389 {"seconds", (double)(std::get<2>(entry).tv_sec - now.tv_sec)},
390 {"blocks", (double)(std::get<1>(entry))}
391 };
392 obj.insert({std::get<0>(entry).toString(), thing });
393 }
394 }
395#endif /* HAVE_EBPF */
5387cc78
RG
396 Json my_json = obj;
397 resp.body=my_json.dump();
5d1c98df 398 resp.headers["Content-Type"] = "application/json";
5387cc78
RG
399 }
400 else {
401 resp.status=404;
7be71139 402 }
7be71139 403 }
37a5c2d5 404 else if (req.url.path == "/metrics") {
d18e3070 405 handleCORS(req, resp);
37a5c2d5 406 resp.status = 200;
d18e3070 407
37a5c2d5
PO
408 std::ostringstream output;
409 for (const auto& e : g_stats.entries) {
330dcb5c
OM
410 if (e.first == "special-memory-usage")
411 continue; // Too expensive for get-all
37a5c2d5
PO
412 std::string metricName = std::get<0>(e);
413
414 // Prometheus suggest using '_' instead of '-'
bead1c22 415 std::string prometheusMetricName = "dnsdist_" + boost::replace_all_copy(metricName, "-", "_");
37a5c2d5 416
80dbd7d2 417 MetricDefinition metricDetails;
37a5c2d5
PO
418
419 if (!g_metricDefinitions.getMetricDetails(metricName, metricDetails)) {
b5e27ae4 420 vinfolog("Do not have metric details for %s", metricName);
37a5c2d5
PO
421 continue;
422 }
d18e3070 423
2e567c94
PO
424 std::string prometheusTypeName = g_metricDefinitions.getPrometheusStringMetricType(metricDetails.prometheusType);
425
426 if (prometheusTypeName == "") {
427 vinfolog("Unknown Prometheus type for %s", metricName);
428 continue;
429 }
430
d18e3070 431 // for these we have the help and types encoded in the sources:
37a5c2d5 432 output << "# HELP " << prometheusMetricName << " " << metricDetails.description << "\n";
2e567c94 433 output << "# TYPE " << prometheusMetricName << " " << prometheusTypeName << "\n";
37a5c2d5
PO
434 output << prometheusMetricName << " ";
435
436 if (const auto& val = boost::get<DNSDistStats::stat_t*>(&std::get<1>(e)))
437 output << (*val)->load();
d18e3070 438 else if (const auto& dval = boost::get<double*>(&std::get<1>(e)))
37a5c2d5 439 output << **dval;
d18e3070 440 else
37a5c2d5 441 output << (*boost::get<DNSDistStats::statfunction_t>(&std::get<1>(e)))(std::get<0>(e));
80dbd7d2 442
37a5c2d5 443 output << "\n";
d18e3070 444 }
37a5c2d5 445
eb0335ff
MC
446 // Latency histogram buckets
447 output << "# HELP dnsdist_latency Histogram of responses by latency\n";
448 output << "# TYPE dnsdist_latency histogram\n";
449 uint64_t latency_amounts = g_stats.latency0_1;
450 output << "dnsdist_latency_bucket{le=\"1\"} " << latency_amounts << "\n";
451 latency_amounts += g_stats.latency1_10;
452 output << "dnsdist_latency_bucket{le=\"10\"} " << latency_amounts << "\n";
453 latency_amounts += g_stats.latency10_50;
454 output << "dnsdist_latency_bucket{le=\"50\"} " << latency_amounts << "\n";
455 latency_amounts += g_stats.latency50_100;
456 output << "dnsdist_latency_bucket{le=\"100\"} " << latency_amounts << "\n";
457 latency_amounts += g_stats.latency100_1000;
458 output << "dnsdist_latency_bucket{le=\"1000\"} " << latency_amounts << "\n";
459 latency_amounts += g_stats.latencySlow; // Should be the same as latency_count
460 output << "dnsdist_latency_bucket{le=\"+Inf\"} " << latency_amounts << "\n";
461
6141572b 462 auto states = g_dstates.getLocal();
d37db88f 463 const string statesbase = "dnsdist_server_";
e7fe227b 464
a6e9e107
RG
465 output << "# HELP " << statesbase << "queries " << "Amount of queries relayed to server" << "\n";
466 output << "# TYPE " << statesbase << "queries " << "counter" << "\n";
467 output << "# HELP " << statesbase << "drops " << "Amount of queries not answered by server" << "\n";
468 output << "# TYPE " << statesbase << "drops " << "counter" << "\n";
c8464367 469 output << "# HELP " << statesbase << "latency " << "Server's latency when answering questions in milliseconds" << "\n";
a6e9e107
RG
470 output << "# TYPE " << statesbase << "latency " << "gauge" << "\n";
471 output << "# HELP " << statesbase << "senderrors " << "Total number of OS snd errors while relaying queries" << "\n";
472 output << "# TYPE " << statesbase << "senderrors " << "counter" << "\n";
473 output << "# HELP " << statesbase << "outstanding " << "Current number of queries that are waiting for a backend response" << "\n";
474 output << "# TYPE " << statesbase << "outstanding " << "gauge" << "\n";
475 output << "# HELP " << statesbase << "order " << "The order in which this server is picked" << "\n";
476 output << "# TYPE " << statesbase << "order " << "gauge" << "\n";
477 output << "# HELP " << statesbase << "weight " << "The weight within the order in which this server is picked" << "\n";
478 output << "# TYPE " << statesbase << "weight " << "gauge" << "\n";
479 output << "# HELP " << statesbase << "tcpdiedsendingquery " << "The number of TCP I/O errors while sending the query" << "\n";
480 output << "# TYPE " << statesbase << "tcpdiedsendingquery " << "counter" << "\n";
481 output << "# HELP " << statesbase << "tcpdiedreadingresponse " << "The number of TCP I/O errors while reading the response" << "\n";
482 output << "# TYPE " << statesbase << "tcpdiedreadingresponse " << "counter" << "\n";
483 output << "# HELP " << statesbase << "tcpgaveup " << "The number of TCP connections failing after too many attempts" << "\n";
484 output << "# TYPE " << statesbase << "tcpgaveup " << "counter" << "\n";
485 output << "# HELP " << statesbase << "tcpreadtimeouts " << "The number of TCP read timeouts" << "\n";
486 output << "# TYPE " << statesbase << "tcpreadtimeouts " << "counter" << "\n";
487 output << "# HELP " << statesbase << "tcpwritetimeouts " << "The number of TCP write timeouts" << "\n";
488 output << "# TYPE " << statesbase << "tcpwritetimeouts " << "counter" << "\n";
cff9aa03
RG
489 output << "# HELP " << statesbase << "tcpcurrentconnections " << "The number of current TCP connections" << "\n";
490 output << "# TYPE " << statesbase << "tcpcurrentconnections " << "gauge" << "\n";
491 output << "# HELP " << statesbase << "tcpavgqueriesperconn " << "The average number of queries per TCP connection" << "\n";
492 output << "# TYPE " << statesbase << "tcpavgqueriesperconn " << "gauge" << "\n";
493 output << "# HELP " << statesbase << "tcpavgconnduration " << "The average duration of a TCP connection (ms)" << "\n";
494 output << "# TYPE " << statesbase << "tcpavgconnduration " << "gauge" << "\n";
80dbd7d2 495
6141572b 496 for (const auto& state : *states) {
37a5c2d5 497 string serverName;
80dbd7d2 498
37a5c2d5 499 if (state->name.empty())
47be7d7c 500 serverName = state->remote.toStringWithPort();
37a5c2d5
PO
501 else
502 serverName = state->getName();
503
d18e3070 504 boost::replace_all(serverName, ".", "_");
37a5c2d5 505
76025fec
PO
506 const std::string label = boost::str(boost::format("{server=\"%1%\",address=\"%2%\"}")
507 % serverName % state->remote.toStringWithPort());
508
cff9aa03
RG
509 output << statesbase << "queries" << label << " " << state->queries.load() << "\n";
510 output << statesbase << "drops" << label << " " << state->reuseds.load() << "\n";
511 output << statesbase << "latency" << label << " " << state->latencyUsec/1000.0 << "\n";
512 output << statesbase << "senderrors" << label << " " << state->sendErrors.load() << "\n";
513 output << statesbase << "outstanding" << label << " " << state->outstanding.load() << "\n";
514 output << statesbase << "order" << label << " " << state->order << "\n";
515 output << statesbase << "weight" << label << " " << state->weight << "\n";
516 output << statesbase << "tcpdiedsendingquery" << label << " " << state->tcpDiedSendingQuery << "\n";
517 output << statesbase << "tcpdiedreadingresponse" << label << " " << state->tcpDiedReadingResponse << "\n";
518 output << statesbase << "tcpgaveup" << label << " " << state->tcpGaveUp << "\n";
519 output << statesbase << "tcpreadtimeouts" << label << " " << state->tcpReadTimeouts << "\n";
520 output << statesbase << "tcpwritetimeouts" << label << " " << state->tcpWriteTimeouts << "\n";
521 output << statesbase << "tcpcurrentconnections" << label << " " << state->tcpCurrentConnections << "\n";
522 output << statesbase << "tcpavgqueriesperconn" << label << " " << state->tcpAvgQueriesPerConnection << "\n";
523 output << statesbase << "tcpavgconnduration" << label << " " << state->tcpAvgConnectionDuration << "\n";
d18e3070 524 }
37a5c2d5 525
5bbcbea0
RG
526 const string frontsbase = "dnsdist_frontend_";
527 output << "# HELP " << frontsbase << "queries " << "Amount of queries received by this frontend" << "\n";
528 output << "# TYPE " << frontsbase << "queries " << "counter" << "\n";
529 output << "# HELP " << frontsbase << "tcpdiedreadingquery " << "Amount of TCP connections terminated while reading the query from the client" << "\n";
530 output << "# TYPE " << frontsbase << "tcpdiedreadingquery " << "counter" << "\n";
531 output << "# HELP " << frontsbase << "tcpdiedsendingresponse " << "Amount of TCP connections terminated while sending a response to the client" << "\n";
532 output << "# TYPE " << frontsbase << "tcpdiedsendingresponse " << "counter" << "\n";
533 output << "# HELP " << frontsbase << "tcpgaveup " << "Amount of TCP connections terminated after too many attemps to get a connection to the backend" << "\n";
534 output << "# TYPE " << frontsbase << "tcpgaveup " << "counter" << "\n";
535 output << "# HELP " << frontsbase << "tcpclientimeouts " << "Amount of TCP connections terminated by a timeout while reading from the client" << "\n";
536 output << "# TYPE " << frontsbase << "tcpclientimeouts " << "counter" << "\n";
537 output << "# HELP " << frontsbase << "tcpdownstreamimeouts " << "Amount of TCP connections terminated by a timeout while reading from the backend" << "\n";
538 output << "# TYPE " << frontsbase << "tcpdownstreamimeouts " << "counter" << "\n";
539 output << "# HELP " << frontsbase << "tcpcurrentconnections " << "Amount of current incoming TCP connections from clients" << "\n";
540 output << "# TYPE " << frontsbase << "tcpcurrentconnections " << "gauge" << "\n";
541 output << "# HELP " << frontsbase << "tcpavgqueriesperconnection " << "The average number of queries per TCP connection" << "\n";
542 output << "# TYPE " << frontsbase << "tcpavgqueriesperconnection " << "gauge" << "\n";
543 output << "# HELP " << frontsbase << "tcpavgconnectionduration " << "The average duration of a TCP connection (ms)" << "\n";
544 output << "# TYPE " << frontsbase << "tcpavgconnectionduration " << "gauge" << "\n";
545
a8b5b7a7 546 std::map<std::string,uint64_t> frontendDuplicates;
37a5c2d5 547 for (const auto& front : g_frontends) {
d18e3070
PO
548 if (front->udpFD == -1 && front->tcpFD == -1)
549 continue;
550
551 string frontName = front->local.toString() + ":" + std::to_string(front->local.getPort());
5bbcbea0 552 const string proto = front->getType();
a8b5b7a7
RG
553 string fullName = frontName + "_" + proto;
554 auto dupPair = frontendDuplicates.insert({fullName, 1});
131df256 555 if (!dupPair.second) {
a8b5b7a7 556 frontName = frontName + "_" + std::to_string(dupPair.first->second);
131df256 557 ++(dupPair.first->second);
a8b5b7a7 558 }
5bbcbea0
RG
559 const std::string label = boost::str(boost::format("{frontend=\"%1%\",proto=\"%2%\"} ")
560 % frontName % proto);
561
562 output << frontsbase << "queries" << label << front->queries.load() << "\n";
563 output << frontsbase << "tcpdiedreadingquery" << label << front->tcpDiedReadingQuery.load() << "\n";
564 output << frontsbase << "tcpdiedsendingresponse" << label << front->tcpDiedSendingResponse.load() << "\n";
565 output << frontsbase << "tcpgaveup" << label << front->tcpGaveUp.load() << "\n";
566 output << frontsbase << "tcpclientimeouts" << label << front->tcpClientTimeouts.load() << "\n";
567 output << frontsbase << "tcpdownstreamtimeouts" << label << front->tcpDownstreamTimeouts.load() << "\n";
568 output << frontsbase << "tcpcurrentconnections" << label << front->tcpCurrentConnections.load() << "\n";
569 output << frontsbase << "tcpavgqueriesperconnection" << label << front->tcpAvgQueriesPerConnection.load() << "\n";
570 output << frontsbase << "tcpavgconnectionduration" << label << front->tcpAvgConnectionDuration.load() << "\n";
571 }
37a5c2d5 572
5bbcbea0
RG
573 const string dohfrontsbase = "dnsdist_doh_frontend_";
574 output << "# HELP " << dohfrontsbase << "http_connects " << "Number of TCP connections establoshed to this frontend" << "\n";
575 output << "# TYPE " << dohfrontsbase << "queries " << "counter" << "\n";
576 output << "# HELP " << dohfrontsbase << "tls10_queries " << "Number of valid DNS queries received via TLS 1.0" << "\n";
577 output << "# TYPE " << dohfrontsbase << "tls10_queries " << "counter" << "\n";
578 output << "# HELP " << dohfrontsbase << "tls11_queries " << "Number of valid DNS queries received via TLS 1.1" << "\n";
579 output << "# TYPE " << dohfrontsbase << "tls11_queries " << "counter" << "\n";
580 output << "# HELP " << dohfrontsbase << "tls12_queries " << "Number of valid DNS queries received via TLS 1.2" << "\n";
581 output << "# TYPE " << dohfrontsbase << "tls12_queries " << "counter" << "\n";
582 output << "# HELP " << dohfrontsbase << "tls13_queries " << "Number of valid DNS queries received via TLS 1.3" << "\n";
583 output << "# TYPE " << dohfrontsbase << "tls13_queries " << "counter" << "\n";
584 output << "# HELP " << dohfrontsbase << "tlsunknown_queries " << "Number of valid DNS queries received via an unknown TLS version" << "\n";
585 output << "# TYPE " << dohfrontsbase << "tlsunknown_queries " << "counter" << "\n";
586 output << "# HELP " << dohfrontsbase << "get_queries " << "Number of valid DNS queries received via GET" << "\n";
587 output << "# TYPE " << dohfrontsbase << "get_queries " << "counter" << "\n";
588 output << "# HELP " << dohfrontsbase << "post_queries " << "Number of valid DNS queries received via POST" << "\n";
589 output << "# TYPE " << dohfrontsbase << "post_queries " << "counter" << "\n";
590 output << "# HELP " << dohfrontsbase << "bad_requests " << "Number of requests that could not be converted to a DNS query" << "\n";
591 output << "# TYPE " << dohfrontsbase << "bad_requests " << "counter" << "\n";
592 output << "# HELP " << dohfrontsbase << "error_responses " << "Number of responses sent by dnsdist indicating an error" << "\n";
593 output << "# TYPE " << dohfrontsbase << "error_responses " << "counter" << "\n";
1ddac024
RG
594 output << "# HELP " << dohfrontsbase << "redirect_responses " << "Number of responses sent by dnsdist indicating a redirect" << "\n";
595 output << "# TYPE " << dohfrontsbase << "redirect_responses " << "counter" << "\n";
5bbcbea0
RG
596 output << "# HELP " << dohfrontsbase << "valid_responses " << "Number of valid responses sent by dnsdist" << "\n";
597 output << "# TYPE " << dohfrontsbase << "valid_responses " << "counter" << "\n";
598 output << "# HELP " << dohfrontsbase << "http1_queries " << "Number of queries received over HTTP/1.x" << "\n";
599 output << "# TYPE " << dohfrontsbase << "http1_queries " << "counter" << "\n";
600 output << "# HELP " << dohfrontsbase << "http1_nb200responses " << "Number of responses with a 200 status code sent over HTTP/1.x" << "\n";
601 output << "# TYPE " << dohfrontsbase << "http1_nb200responses " << "counter" << "\n";
602 output << "# HELP " << dohfrontsbase << "http1_nb400responses " << "Number of responses with a 400 status code sent over HTTP/1.x" << "\n";
603 output << "# TYPE " << dohfrontsbase << "http1_nb400responses " << "counter" << "\n";
604 output << "# HELP " << dohfrontsbase << "http1_nb403responses " << "Number of responses with a 403 status code sent over HTTP/1.x" << "\n";
605 output << "# TYPE " << dohfrontsbase << "http1_nb403responses " << "counter" << "\n";
606 output << "# HELP " << dohfrontsbase << "http1_nb500responses " << "Number of responses with a 500 status code sent over HTTP/1.x" << "\n";
607 output << "# TYPE " << dohfrontsbase << "http1_nb500responses " << "counter" << "\n";
608 output << "# HELP " << dohfrontsbase << "http1_nb502responses " << "Number of responses with a 502 status code sent over HTTP/1.x" << "\n";
609 output << "# TYPE " << dohfrontsbase << "http1_nb502responses " << "counter" << "\n";
610 output << "# HELP " << dohfrontsbase << "http1_nbotherresponses " << "Number of responses with an other status code sent over HTTP/1.x" << "\n";
611 output << "# TYPE " << dohfrontsbase << "http1_nbotherresponses " << "counter" << "\n";
612 output << "# HELP " << dohfrontsbase << "http2_queries " << "Number of queries received over HTTP/2.x" << "\n";
613 output << "# TYPE " << dohfrontsbase << "http2_queries " << "counter" << "\n";
614 output << "# HELP " << dohfrontsbase << "http2_nb200responses " << "Number of responses with a 200 status code sent over HTTP/2.x" << "\n";
615 output << "# TYPE " << dohfrontsbase << "http2_nb200responses " << "counter" << "\n";
616 output << "# HELP " << dohfrontsbase << "http2_nb400responses " << "Number of responses with a 400 status code sent over HTTP/2.x" << "\n";
617 output << "# TYPE " << dohfrontsbase << "http2_nb400responses " << "counter" << "\n";
618 output << "# HELP " << dohfrontsbase << "http2_nb403responses " << "Number of responses with a 403 status code sent over HTTP/2.x" << "\n";
619 output << "# TYPE " << dohfrontsbase << "http2_nb403responses " << "counter" << "\n";
620 output << "# HELP " << dohfrontsbase << "http2_nb500responses " << "Number of responses with a 500 status code sent over HTTP/2.x" << "\n";
621 output << "# TYPE " << dohfrontsbase << "http2_nb500responses " << "counter" << "\n";
622 output << "# HELP " << dohfrontsbase << "http2_nb502responses " << "Number of responses with a 502 status code sent over HTTP/2.x" << "\n";
623 output << "# TYPE " << dohfrontsbase << "http2_nb502responses " << "counter" << "\n";
624 output << "# HELP " << dohfrontsbase << "http2_nbotherresponses " << "Number of responses with an other status code sent over HTTP/2.x" << "\n";
625 output << "# TYPE " << dohfrontsbase << "http2_nbotherresponses " << "counter" << "\n";
626
627#ifdef HAVE_DNS_OVER_HTTPS
628 for(const auto& doh : g_dohlocals) {
629 const std::string label = boost::str(boost::format("{address=\"%1%\"} ") % doh->d_local.toStringWithPort());
630
631 output << dohfrontsbase << "http_connects" << label << doh->d_httpconnects << "\n";
632 output << dohfrontsbase << "tls10_queries" << label << doh->d_tls10queries << "\n";
633 output << dohfrontsbase << "tls11_queries" << label << doh->d_tls11queries << "\n";
634 output << dohfrontsbase << "tls12_queries" << label << doh->d_tls12queries << "\n";
635 output << dohfrontsbase << "tls13_queries" << label << doh->d_tls13queries << "\n";
636 output << dohfrontsbase << "tlsunknown_queries" << label << doh->d_tlsUnknownqueries << "\n";
637 output << dohfrontsbase << "get_queries" << label << doh->d_getqueries << "\n";
638 output << dohfrontsbase << "post_queries" << label << doh->d_postqueries << "\n";
639 output << dohfrontsbase << "bad_requests" << label << doh->d_badrequests << "\n";
640 output << dohfrontsbase << "error_responses" << label << doh->d_errorresponses << "\n";
1ddac024 641 output << dohfrontsbase << "redirect_responses" << label << doh->d_redirectresponses << "\n";
5bbcbea0
RG
642 output << dohfrontsbase << "valid_responses" << label << doh->d_validresponses << "\n";
643
644 output << dohfrontsbase << "http1_queries" << label << doh->d_http1Stats.d_nbQueries << "\n";
645 output << dohfrontsbase << "http1_nb200responses" << label << doh->d_http1Stats.d_nb200Responses << "\n";
646 output << dohfrontsbase << "http1_nb400responses" << label << doh->d_http1Stats.d_nb400Responses << "\n";
647 output << dohfrontsbase << "http1_nb403responses" << label << doh->d_http1Stats.d_nb403Responses << "\n";
648 output << dohfrontsbase << "http1_nb500responses" << label << doh->d_http1Stats.d_nb500Responses << "\n";
649 output << dohfrontsbase << "http1_nb502responses" << label << doh->d_http1Stats.d_nb502Responses << "\n";
650 output << dohfrontsbase << "http1_nbotherresponses" << label << doh->d_http1Stats.d_nbOtherResponses << "\n";
651 output << dohfrontsbase << "http2_queries" << label << doh->d_http2Stats.d_nbQueries << "\n";
652 output << dohfrontsbase << "http2_nb200responses" << label << doh->d_http2Stats.d_nb200Responses << "\n";
653 output << dohfrontsbase << "http2_nb400responses" << label << doh->d_http2Stats.d_nb400Responses << "\n";
654 output << dohfrontsbase << "http2_nb403responses" << label << doh->d_http2Stats.d_nb403Responses << "\n";
655 output << dohfrontsbase << "http2_nb500responses" << label << doh->d_http2Stats.d_nb500Responses << "\n";
656 output << dohfrontsbase << "http2_nb502responses" << label << doh->d_http2Stats.d_nb502Responses << "\n";
657 output << dohfrontsbase << "http2_nbotherresponses" << label << doh->d_http2Stats.d_nbOtherResponses << "\n";
d18e3070 658 }
5bbcbea0 659#endif /* HAVE_DNS_OVER_HTTPS */
37a5c2d5 660
6141572b 661 auto localPools = g_pools.getLocal();
d18e3070 662 const string cachebase = "dnsdist_pool_";
80dbd7d2 663
6141572b 664 for (const auto& entry : *localPools) {
d18e3070 665 string poolName = entry.first;
80dbd7d2 666
d18e3070
PO
667 if (poolName.empty()) {
668 poolName = "_default_";
669 }
670 const string label = "{pool=\"" + poolName + "\"}";
671 const std::shared_ptr<ServerPool> pool = entry.second;
3004c35b 672 output << "dnsdist_pool_servers" << label << " " << pool->countServers(false) << "\n";
5040e507 673 output << "dnsdist_pool_active_servers" << label << " " << pool->countServers(true) << "\n";
37a5c2d5 674
d18e3070
PO
675 if (pool->packetCache != nullptr) {
676 const auto& cache = pool->packetCache;
37a5c2d5
PO
677
678 output << cachebase << "cache_size" <<label << " " << cache->getMaxEntries() << "\n";
679 output << cachebase << "cache_entries" <<label << " " << cache->getEntriesCount() << "\n";
680 output << cachebase << "cache_hits" <<label << " " << cache->getHits() << "\n";
681 output << cachebase << "cache_misses" <<label << " " << cache->getMisses() << "\n";
682 output << cachebase << "cache_deferred_inserts" <<label << " " << cache->getDeferredInserts() << "\n";
683 output << cachebase << "cache_deferred_lookups" <<label << " " << cache->getDeferredLookups() << "\n";
684 output << cachebase << "cache_lookup_collisions" <<label << " " << cache->getLookupCollisions() << "\n";
685 output << cachebase << "cache_insert_collisions" <<label << " " << cache->getInsertCollisions() << "\n";
686 output << cachebase << "cache_ttl_too_shorts" <<label << " " << cache->getTTLTooShorts() << "\n";
d18e3070
PO
687 }
688 }
689
37a5c2d5 690 resp.body = output.str();
d18e3070
PO
691 resp.headers["Content-Type"] = "text/plain";
692 }
693
46d06a12 694 else if(req.url.path=="/api/v1/servers/localhost") {
5387cc78 695 handleCORS(req, resp);
438a189b 696 resp.status=200;
697
698 Json::array servers;
a9c2e4ab 699 auto localServers = g_dstates.getLocal();
438a189b 700 int num=0;
a9c2e4ab 701 for(const auto& a : *localServers) {
438a189b 702 string status;
80dbd7d2 703 if(a->availability == DownstreamState::Availability::Up)
438a189b 704 status = "UP";
80dbd7d2 705 else if(a->availability == DownstreamState::Availability::Down)
438a189b 706 status = "DOWN";
80dbd7d2 707 else
438a189b 708 status = (a->upStatus ? "up" : "down");
4ace9fe8 709
357c22dd 710 Json::array pools;
438a189b 711 for(const auto& p: a->pools)
357c22dd
RG
712 pools.push_back(p);
713
80dbd7d2 714 Json::object server{
357c22dd 715 {"id", num++},
18eeccc9 716 {"name", a->name},
357c22dd
RG
717 {"address", a->remote.toStringWithPort()},
718 {"state", status},
778fe0a8
RG
719 {"qps", (double)a->queryLoad},
720 {"qpsLimit", (double)a->qps.getRate()},
721 {"outstanding", (double)a->outstanding},
722 {"reuseds", (double)a->reuseds},
723 {"weight", (double)a->weight},
724 {"order", (double)a->order},
357c22dd 725 {"pools", pools},
778fe0a8 726 {"latency", (double)(a->latencyUsec/1000.0)},
4ace9fe8 727 {"queries", (double)a->queries},
684f332e 728 {"sendErrors", (double)a->sendErrors},
a6e9e107
RG
729 {"tcpDiedSendingQuery", (double)a->tcpDiedSendingQuery},
730 {"tcpDiedReadingResponse", (double)a->tcpDiedReadingResponse},
731 {"tcpGaveUp", (double)a->tcpGaveUp},
732 {"tcpReadTimeouts", (double)a->tcpReadTimeouts},
733 {"tcpWriteTimeouts", (double)a->tcpWriteTimeouts},
cff9aa03
RG
734 {"tcpCurrentConnections", (double)a->tcpCurrentConnections},
735 {"tcpAvgQueriesPerConnection", (double)a->tcpAvgQueriesPerConnection},
736 {"tcpAvgConnectionDuration", (double)a->tcpAvgConnectionDuration},
684f332e 737 {"dropRate", (double)a->dropRate}
4ace9fe8 738 };
357c22dd 739
36927800
RG
740 /* sending a latency for a DOWN server doesn't make sense */
741 if (a->availability == DownstreamState::Availability::Down) {
742 server["latency"] = nullptr;
743 }
744
438a189b 745 servers.push_back(server);
746 }
747
65dec2a3
RG
748 Json::array frontends;
749 num=0;
750 for(const auto& front : g_frontends) {
751 if (front->udpFD == -1 && front->tcpFD == -1)
752 continue;
753 Json::object frontend{
754 { "id", num++ },
755 { "address", front->local.toStringWithPort() },
756 { "udp", front->udpFD >= 0 },
757 { "tcp", front->tcpFD >= 0 },
ba7ec340 758 { "type", front->getType() },
a6e9e107
RG
759 { "queries", (double) front->queries.load() },
760 { "tcpDiedReadingQuery", (double) front->tcpDiedReadingQuery.load() },
761 { "tcpDiedSendingResponse", (double) front->tcpDiedSendingResponse.load() },
762 { "tcpGaveUp", (double) front->tcpGaveUp.load() },
763 { "tcpClientTimeouts", (double) front->tcpClientTimeouts },
764 { "tcpDownstreamTimeouts", (double) front->tcpDownstreamTimeouts },
cff9aa03
RG
765 { "tcpCurrentConnections", (double) front->tcpCurrentConnections },
766 { "tcpAvgQueriesPerConnection", (double) front->tcpAvgQueriesPerConnection },
767 { "tcpAvgConnectionDuration", (double) front->tcpAvgConnectionDuration },
65dec2a3
RG
768 };
769 frontends.push_back(frontend);
770 }
771
5bbcbea0
RG
772 Json::array dohs;
773#ifdef HAVE_DNS_OVER_HTTPS
774 {
775 num = 0;
776 for(const auto& doh : g_dohlocals) {
777 Json::object obj{
778 { "id", num++ },
779 { "address", doh->d_local.toStringWithPort() },
780 { "http-connects", (double) doh->d_httpconnects },
781 { "http1-queries", (double) doh->d_http1Stats.d_nbQueries },
782 { "http2-queries", (double) doh->d_http2Stats.d_nbQueries },
783 { "http1-200-responses", (double) doh->d_http1Stats.d_nb200Responses },
784 { "http2-200-responses", (double) doh->d_http2Stats.d_nb200Responses },
785 { "http1-400-responses", (double) doh->d_http1Stats.d_nb400Responses },
786 { "http2-400-responses", (double) doh->d_http2Stats.d_nb400Responses },
787 { "http1-403-responses", (double) doh->d_http1Stats.d_nb403Responses },
788 { "http2-403-responses", (double) doh->d_http2Stats.d_nb403Responses },
789 { "http1-500-responses", (double) doh->d_http1Stats.d_nb500Responses },
790 { "http2-500-responses", (double) doh->d_http2Stats.d_nb500Responses },
791 { "http1-502-responses", (double) doh->d_http1Stats.d_nb502Responses },
792 { "http2-502-responses", (double) doh->d_http2Stats.d_nb502Responses },
793 { "http1-other-responses", (double) doh->d_http1Stats.d_nbOtherResponses },
794 { "http2-other-responses", (double) doh->d_http2Stats.d_nbOtherResponses },
795 { "tls10-queries", (double) doh->d_tls10queries },
796 { "tls11-queries", (double) doh->d_tls11queries },
797 { "tls12-queries", (double) doh->d_tls12queries },
798 { "tls13-queries", (double) doh->d_tls13queries },
799 { "tls-unknown-queries", (double) doh->d_tlsUnknownqueries },
800 { "get-queries", (double) doh->d_getqueries },
801 { "post-queries", (double) doh->d_postqueries },
802 { "bad-requests", (double) doh->d_badrequests },
803 { "error-responses", (double) doh->d_errorresponses },
1ddac024 804 { "redirect-responses", (double) doh->d_redirectresponses },
5bbcbea0
RG
805 { "valid-responses", (double) doh->d_validresponses }
806 };
807 dohs.push_back(obj);
808 }
809 }
810#endif /* HAVE_DNS_OVER_HTTPS */
811
4ace9fe8 812 Json::array pools;
a9c2e4ab 813 auto localPools = g_pools.getLocal();
4ace9fe8 814 num=0;
a9c2e4ab 815 for(const auto& pool : *localPools) {
4ace9fe8
RG
816 const auto& cache = pool.second->packetCache;
817 Json::object entry {
818 { "id", num++ },
819 { "name", pool.first },
778fe0a8 820 { "serversCount", (double) pool.second->countServers(false) },
4ace9fe8
RG
821 { "cacheSize", (double) (cache ? cache->getMaxEntries() : 0) },
822 { "cacheEntries", (double) (cache ? cache->getEntriesCount() : 0) },
823 { "cacheHits", (double) (cache ? cache->getHits() : 0) },
824 { "cacheMisses", (double) (cache ? cache->getMisses() : 0) },
825 { "cacheDeferredInserts", (double) (cache ? cache->getDeferredInserts() : 0) },
826 { "cacheDeferredLookups", (double) (cache ? cache->getDeferredLookups() : 0) },
827 { "cacheLookupCollisions", (double) (cache ? cache->getLookupCollisions() : 0) },
828 { "cacheInsertCollisions", (double) (cache ? cache->getInsertCollisions() : 0) },
829 { "cacheTTLTooShorts", (double) (cache ? cache->getTTLTooShorts() : 0) }
830 };
831 pools.push_back(entry);
832 }
833
438a189b 834 Json::array rules;
a9c2e4ab 835 auto localRules = g_rulactions.getLocal();
438a189b 836 num=0;
a9c2e4ab 837 for(const auto& a : *localRules) {
438a189b 838 Json::object rule{
4ace9fe8 839 {"id", num++},
f8a222ac 840 {"creationOrder", (double)a.d_creationOrder},
4d5959e6
RG
841 {"uuid", boost::uuids::to_string(a.d_id)},
842 {"matches", (double)a.d_rule->d_matches},
843 {"rule", a.d_rule->toString()},
844 {"action", a.d_action->toString()},
845 {"action-stats", a.d_action->getStats()}
1f4059be 846 };
438a189b 847 rules.push_back(rule);
848 }
80dbd7d2 849
d18eab67
CH
850 auto responseRules = someResponseRulesToJson(&g_resprulactions);
851 auto cacheHitResponseRules = someResponseRulesToJson(&g_cachehitresprulactions);
2d4783a8 852 auto selfAnsweredResponseRules = someResponseRulesToJson(&g_selfansweredresprulactions);
4ace9fe8 853
da5d4999 854 string acl;
855
856 vector<string> vec;
a9c2e4ab 857 g_ACL.getLocal()->toStringVector(&vec);
da5d4999 858
859 for(const auto& s : vec) {
860 if(!acl.empty()) acl += ", ";
4ace9fe8 861 acl+=s;
da5d4999 862 }
863 string localaddresses;
6e9fd124
RG
864 for(const auto& front : g_frontends) {
865 if (front->tcp) {
866 continue;
867 }
868 if (!localaddresses.empty()) {
869 localaddresses += ", ";
870 }
871 localaddresses += front->local.toStringWithPort();
da5d4999 872 }
80dbd7d2 873
438a189b 874 Json my_json = Json::object {
4ace9fe8
RG
875 { "daemon_type", "dnsdist" },
876 { "version", VERSION},
877 { "servers", servers},
878 { "frontends", frontends },
879 { "pools", pools },
880 { "rules", rules},
881 { "response-rules", responseRules},
882 { "cache-hit-response-rules", cacheHitResponseRules},
2d4783a8 883 { "self-answered-response-rules", selfAnsweredResponseRules},
4ace9fe8 884 { "acl", acl},
5bbcbea0
RG
885 { "local", localaddresses},
886 { "dohFrontends", dohs }
438a189b 887 };
5d1c98df 888 resp.headers["Content-Type"] = "application/json";
438a189b 889 resp.body=my_json.dump();
c8adc34c
RG
890 }
891 else if(req.url.path=="/api/v1/servers/localhost/statistics") {
892 handleCORS(req, resp);
893 resp.status=200;
894
895 Json::array doc;
896 for(const auto& item : g_stats.entries) {
330dcb5c
OM
897 if (item.first == "special-memory-usage")
898 continue; // Too expensive for get-all
899
c8adc34c
RG
900 if(const auto& val = boost::get<DNSDistStats::stat_t*>(&item.second)) {
901 doc.push_back(Json::object {
902 { "type", "StatisticItem" },
903 { "name", item.first },
904 { "value", (double)(*val)->load() }
905 });
906 }
af619119 907 else if (const auto& dval = boost::get<double*>(&item.second)) {
c8adc34c
RG
908 doc.push_back(Json::object {
909 { "type", "StatisticItem" },
910 { "name", item.first },
af619119 911 { "value", (**dval) }
c8adc34c
RG
912 });
913 }
914 else {
915 doc.push_back(Json::object {
916 { "type", "StatisticItem" },
917 { "name", item.first },
778fe0a8 918 { "value", (double)(*boost::get<DNSDistStats::statfunction_t>(&item.second))(item.first) }
c8adc34c
RG
919 });
920 }
921 }
922 Json my_json = doc;
923 resp.body=my_json.dump();
924 resp.headers["Content-Type"] = "application/json";
925 }
926 else if(req.url.path=="/api/v1/servers/localhost/config") {
927 handleCORS(req, resp);
928 resp.status=200;
50bed881 929
c8adc34c
RG
930 Json::array doc;
931 typedef boost::variant<bool, double, std::string> configentry_t;
932 std::vector<std::pair<std::string, configentry_t> > configEntries {
a9c2e4ab 933 { "acl", g_ACL.getLocal()->toString() },
0dffe9e3 934 { "allow-empty-response", g_allowEmptyResponse },
c8adc34c
RG
935 { "control-socket", g_serverControl.toStringWithPort() },
936 { "ecs-override", g_ECSOverride },
937 { "ecs-source-prefix-v4", (double) g_ECSSourcePrefixV4 },
938 { "ecs-source-prefix-v6", (double) g_ECSSourcePrefixV6 },
939 { "fixup-case", g_fixupCase },
940 { "max-outstanding", (double) g_maxOutstanding },
941 { "server-policy", g_policy.getLocal()->name },
942 { "stale-cache-entries-ttl", (double) g_staleCacheEntriesTTL },
943 { "tcp-recv-timeout", (double) g_tcpRecvTimeout },
944 { "tcp-send-timeout", (double) g_tcpSendTimeout },
945 { "truncate-tc", g_truncateTC },
946 { "verbose", g_verbose },
947 { "verbose-health-checks", g_verboseHealthChecks }
948 };
949 for(const auto& item : configEntries) {
af619119 950 if (const auto& bval = boost::get<bool>(&item.second)) {
c8adc34c
RG
951 doc.push_back(Json::object {
952 { "type", "ConfigSetting" },
953 { "name", item.first },
af619119 954 { "value", *bval }
c8adc34c
RG
955 });
956 }
af619119 957 else if (const auto& sval = boost::get<string>(&item.second)) {
c8adc34c
RG
958 doc.push_back(Json::object {
959 { "type", "ConfigSetting" },
960 { "name", item.first },
af619119 961 { "value", *sval }
c8adc34c
RG
962 });
963 }
af619119 964 else if (const auto& dval = boost::get<double>(&item.second)) {
c8adc34c
RG
965 doc.push_back(Json::object {
966 { "type", "ConfigSetting" },
967 { "name", item.first },
af619119 968 { "value", *dval }
c8adc34c
RG
969 });
970 }
971 }
972 Json my_json = doc;
973 resp.body=my_json.dump();
974 resp.headers["Content-Type"] = "application/json";
438a189b 975 }
56d68fad
RG
976 else if(req.url.path=="/api/v1/servers/localhost/config/allow-from") {
977 handleCORS(req, resp);
978
979 resp.headers["Content-Type"] = "application/json";
980 resp.status=200;
981
982 if (req.method == "PUT") {
983 std::string err;
984 Json doc = Json::parse(req.body, err);
985
986 if (!doc.is_null()) {
987 NetmaskGroup nmg;
988 auto aclList = doc["value"];
989 if (aclList.is_array()) {
990
991 for (auto value : aclList.array_items()) {
992 try {
993 nmg.addMask(value.string_value());
994 } catch (NetmaskException &e) {
995 resp.status = 400;
996 break;
997 }
998 }
999
1000 if (resp.status == 200) {
1001 infolog("Updating the ACL via the API to %s", nmg.toString());
1002 g_ACL.setState(nmg);
1003 apiSaveACL(nmg);
1004 }
1005 }
1006 else {
1007 resp.status = 400;
1008 }
1009 }
1010 else {
1011 resp.status = 400;
1012 }
1013 }
1014 if (resp.status == 200) {
1015 Json::array acl;
1016 vector<string> vec;
a9c2e4ab 1017 g_ACL.getLocal()->toStringVector(&vec);
56d68fad
RG
1018
1019 for(const auto& s : vec) {
1020 acl.push_back(s);
1021 }
1022
1023 Json::object obj{
1024 { "type", "ConfigSetting" },
1025 { "name", "allow-from" },
1026 { "value", acl }
1027 };
1028 Json my_json = obj;
1029 resp.body=my_json.dump();
1030 }
1031 }
e3d76be2
RG
1032 else if(!req.url.path.empty() && g_urlmap.count(req.url.path.c_str()+1)) {
1033 resp.body.assign(g_urlmap[req.url.path.c_str()+1]);
fe20a1c1 1034 vector<string> parts;
e3d76be2 1035 stringtok(parts, req.url.path, ".");
fe20a1c1 1036 if(parts.back() == "html")
c8c1a4fc 1037 resp.headers["Content-Type"] = "text/html" + charset;
fe20a1c1 1038 else if(parts.back() == "css")
c8c1a4fc 1039 resp.headers["Content-Type"] = "text/css" + charset;
fe20a1c1 1040 else if(parts.back() == "js")
c8c1a4fc 1041 resp.headers["Content-Type"] = "application/javascript" + charset;
864a6641
RG
1042 else if(parts.back() == "png")
1043 resp.headers["Content-Type"] = "image/png";
438a189b 1044 resp.status=200;
1045 }
e3d76be2 1046 else if(req.url.path=="/") {
438a189b 1047 resp.body.assign(g_urlmap["index.html"]);
c8c1a4fc 1048 resp.headers["Content-Type"] = "text/html" + charset;
438a189b 1049 resp.status=200;
1050 }
1051 else {
e3d76be2 1052 // cerr<<"404 for: "<<req.url.path<<endl;
438a189b 1053 resp.status=404;
1054 }
50bed881 1055
438a189b 1056 std::ostringstream ofs;
1057 ofs << resp;
1058 string done;
1059 done=ofs.str();
1060 writen2(sock, done.c_str(), done.size());
50bed881 1061
56d68fad
RG
1062 close(sock);
1063 sock = -1;
438a189b 1064 }
59e9504e
RG
1065 catch(const YaHTTP::ParseError& e) {
1066 vinfolog("Webserver thread died with parse error exception while processing a request from %s: %s", remote.toStringWithPort(), e.what());
56d68fad 1067 close(sock);
59e9504e 1068 }
34a6dd76 1069 catch(const std::exception& e) {
59e9504e 1070 errlog("Webserver thread died with exception while processing a request from %s: %s", remote.toStringWithPort(), e.what());
56d68fad 1071 close(sock);
34a6dd76
RG
1072 }
1073 catch(...) {
59e9504e 1074 errlog("Webserver thread died with exception while processing a request from %s", remote.toStringWithPort());
56d68fad 1075 close(sock);
34a6dd76 1076 }
50bed881 1077}
32c97b56
CHB
1078
1079void setWebserverAPIKey(const boost::optional<std::string> apiKey)
80dbd7d2
CHB
1080{
1081 std::lock_guard<std::mutex> lock(g_webserverConfig.lock);
1082
80dbd7d2
CHB
1083 if (apiKey) {
1084 g_webserverConfig.apiKey = *apiKey;
1085 } else {
1086 g_webserverConfig.apiKey.clear();
1087 }
32c97b56
CHB
1088}
1089
1090void setWebserverPassword(const std::string& password)
1091{
1092 std::lock_guard<std::mutex> lock(g_webserverConfig.lock);
1093
1094 g_webserverConfig.password = password;
1095}
1096
1097void setWebserverCustomHeaders(const boost::optional<std::map<std::string, std::string> > customHeaders)
1098{
1099 std::lock_guard<std::mutex> lock(g_webserverConfig.lock);
1100
80dbd7d2
CHB
1101 g_webserverConfig.customHeaders = customHeaders;
1102}
1103
1104void dnsdistWebserverThread(int sock, const ComboAddress& local)
50bed881 1105{
519f5484 1106 setThreadName("dnsdist/webserv");
d745cd6f 1107 warnlog("Webserver launched on %s", local.toStringWithPort());
50bed881 1108 for(;;) {
1109 try {
1110 ComboAddress remote(local);
1111 int fd = SAccept(sock, remote);
1112 vinfolog("Got connection from %s", remote.toStringWithPort());
80dbd7d2 1113 std::thread t(connectionThread, fd, remote);
50bed881 1114 t.detach();
1115 }
1116 catch(std::exception& e) {
1117 errlog("Had an error accepting new webserver connection: %s", e.what());
1118 }
1119 }
1120}