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.
24 #include "ext/json11/json11.hpp"
25 #include "ext/incbin/incbin.h"
28 #include "threadname.hh"
30 #include <yahttp/yahttp.hpp>
31 #include "namespaces.hh"
33 #include <sys/resource.h>
34 #include "ext/incbin/incbin.h"
35 #include "htmlfiles.h"
38 #include <boost/format.hpp>
40 bool g_apiReadWrite
{false};
41 WebserverConfig g_webserverConfig
;
42 std::string g_apiConfigDirectory
;
44 static bool apiWriteConfigFile(const string
& filebasename
, const string
& content
)
46 if (!g_apiReadWrite
) {
47 errlog("Not writing content to %s since the API is read-only", filebasename
);
51 if (g_apiConfigDirectory
.empty()) {
52 vinfolog("Not writing content to %s since the API configuration directory is not set", filebasename
);
56 string filename
= g_apiConfigDirectory
+ "/" + filebasename
+ ".conf";
57 ofstream
ofconf(filename
.c_str());
59 errlog("Could not open configuration fragment file '%s' for writing: %s", filename
, stringerror());
62 ofconf
<< "-- Generated by the REST API, DO NOT EDIT" << endl
;
63 ofconf
<< content
<< endl
;
68 static void apiSaveACL(const NetmaskGroup
& nmg
)
71 nmg
.toStringVector(&vec
);
74 for(const auto& s
: vec
) {
78 acl
+= "\"" + s
+ "\"";
81 string content
= "setACL({" + acl
+ "})";
82 apiWriteConfigFile("acl", content
);
85 static bool checkAPIKey(const YaHTTP::Request
& req
, const string
& expectedApiKey
)
87 if (expectedApiKey
.empty()) {
91 const auto header
= req
.headers
.find("x-api-key");
92 if (header
!= req
.headers
.end()) {
93 return (header
->second
== expectedApiKey
);
99 static bool checkWebPassword(const YaHTTP::Request
& req
, const string
&expected_password
)
101 static const char basicStr
[] = "basic ";
103 const auto header
= req
.headers
.find("authorization");
105 if (header
!= req
.headers
.end() && toLower(header
->second
).find(basicStr
) == 0) {
106 string cookie
= header
->second
.substr(sizeof(basicStr
) - 1);
109 B64Decode(cookie
, plain
);
111 vector
<string
> cparts
;
112 stringtok(cparts
, plain
, ":");
114 if (cparts
.size() == 2) {
115 return cparts
[1] == expected_password
;
122 static bool isAnAPIRequest(const YaHTTP::Request
& req
)
124 return req
.url
.path
.find("/api/") == 0;
127 static bool isAnAPIRequestAllowedWithWebAuth(const YaHTTP::Request
& req
)
129 return req
.url
.path
== "/api/v1/servers/localhost";
132 static bool isAStatsRequest(const YaHTTP::Request
& req
)
134 return req
.url
.path
== "/jsonstat" || req
.url
.path
== "/metrics";
137 static bool compareAuthorization(const YaHTTP::Request
& req
)
139 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
141 if (isAnAPIRequest(req
)) {
142 /* Access to the API requires a valid API key */
143 if (checkAPIKey(req
, g_webserverConfig
.apiKey
)) {
147 return isAnAPIRequestAllowedWithWebAuth(req
) && checkWebPassword(req
, g_webserverConfig
.password
);
150 if (isAStatsRequest(req
)) {
151 /* Access to the stats is allowed for both API and Web users */
152 return checkAPIKey(req
, g_webserverConfig
.apiKey
) || checkWebPassword(req
, g_webserverConfig
.password
);
155 return checkWebPassword(req
, g_webserverConfig
.password
);
158 static bool isMethodAllowed(const YaHTTP::Request
& req
)
160 if (req
.method
== "GET") {
163 if (req
.method
== "PUT" && g_apiReadWrite
) {
164 if (req
.url
.path
== "/api/v1/servers/localhost/config/allow-from") {
171 static void handleCORS(const YaHTTP::Request
& req
, YaHTTP::Response
& resp
)
173 const auto origin
= req
.headers
.find("Origin");
174 if (origin
!= req
.headers
.end()) {
175 if (req
.method
== "OPTIONS") {
176 /* Pre-flight request */
177 if (g_apiReadWrite
) {
178 resp
.headers
["Access-Control-Allow-Methods"] = "GET, PUT";
181 resp
.headers
["Access-Control-Allow-Methods"] = "GET";
183 resp
.headers
["Access-Control-Allow-Headers"] = "Authorization, X-API-Key";
186 resp
.headers
["Access-Control-Allow-Origin"] = origin
->second
;
188 if (isAStatsRequest(req
) || isAnAPIRequestAllowedWithWebAuth(req
)) {
189 resp
.headers
["Access-Control-Allow-Credentials"] = "true";
194 static void addSecurityHeaders(YaHTTP::Response
& resp
, const boost::optional
<std::map
<std::string
, std::string
> >& customHeaders
)
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'" },
204 for (const auto& h
: headers
) {
206 const auto& custom
= customHeaders
->find(h
.first
);
207 if (custom
!= customHeaders
->end()) {
211 resp
.headers
[h
.first
] = h
.second
;
215 static void addCustomHeaders(YaHTTP::Response
& resp
, const boost::optional
<std::map
<std::string
, std::string
> >& customHeaders
)
220 for (const auto& c
: *customHeaders
) {
221 if (!c
.second
.empty()) {
222 resp
.headers
[c
.first
] = c
.second
;
228 static json11::Json::array
someResponseRulesToJson(GlobalStateHolder
<vector
<T
>>* someResponseRules
)
230 using namespace json11
;
231 Json::array responseRules
;
233 auto localResponseRules
= someResponseRules
->getLocal();
234 for(const auto& a
: *localResponseRules
) {
237 {"creationOrder", (double)a
.d_creationOrder
},
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()},
243 responseRules
.push_back(rule
);
245 return responseRules
;
248 static void connectionThread(int sock
, ComboAddress remote
)
250 setThreadName("dnsdist/webConn");
252 using namespace json11
;
253 vinfolog("Webserver handling connection from %s", remote
.toStringWithPort());
256 YaHTTP::AsyncRequestLoader yarl
;
258 bool finished
= false;
260 yarl
.initialize(&req
);
264 bytes
= read(sock
, buf
, sizeof(buf
));
266 string data
= string(buf
, bytes
);
267 finished
= yarl
.feed(data
);
275 string command
=req
.getvars
["command"];
277 req
.getvars
.erase("_"); // jQuery cache buster
279 YaHTTP::Response resp
;
280 resp
.version
= req
.version
;
281 const string charset
= "; charset=utf-8";
284 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
286 addCustomHeaders(resp
, g_webserverConfig
.customHeaders
);
287 addSecurityHeaders(resp
, g_webserverConfig
.customHeaders
);
289 /* indicate that the connection will be closed after completion of the response */
290 resp
.headers
["Connection"] = "close";
292 /* no need to send back the API key if any */
293 resp
.headers
.erase("X-API-Key");
295 if(req
.method
== "OPTIONS") {
296 /* the OPTIONS method should not require auth, otherwise it breaks CORS */
297 handleCORS(req
, resp
);
300 else if (!compareAuthorization(req
)) {
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());
305 resp
.body
="<h1>Unauthorized</h1>";
306 resp
.headers
["WWW-Authenticate"] = "basic realm=\"PowerDNS\"";
309 else if(!isMethodAllowed(req
)) {
312 else if(req
.url
.path
=="/jsonstat") {
313 handleCORS(req
, resp
);
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
}
325 for(const auto& e
: g_stats
.entries
) {
326 if (e
.first
== "special-memory-usage")
327 continue; // Too expensive for get-all
328 if(const auto& val
= boost::get
<DNSDistStats::stat_t
*>(&e
.second
))
329 obj
.insert({e
.first
, (double)(*val
)->load()});
330 else if (const auto& dval
= boost::get
<double*>(&e
.second
))
331 obj
.insert({e
.first
, (**dval
)});
333 obj
.insert({e
.first
, (double)(*boost::get
<DNSDistStats::statfunction_t
>(&e
.second
))(e
.first
)});
336 resp
.body
=my_json
.dump();
337 resp
.headers
["Content-Type"] = "application/json";
339 else if(command
=="dynblocklist") {
341 auto nmg
= g_dynblockNMG
.getLocal();
344 for(const auto& e
: *nmg
) {
345 if(now
< e
->second
.until
) {
347 {"reason", e
->second
.reason
},
348 {"seconds", (double)(e
->second
.until
.tv_sec
- now
.tv_sec
)},
349 {"blocks", (double)e
->second
.blocks
},
350 {"action", DNSAction::typeToString(e
->second
.action
!= DNSAction::Action::None
? e
->second
.action
: g_dynBlockAction
) },
351 {"warning", e
->second
.warning
}
353 obj
.insert({e
->first
.toString(), thing
});
357 auto smt
= g_dynblockSMT
.getLocal();
358 smt
->visit([&now
,&obj
](const SuffixMatchTree
<DynBlock
>& node
) {
359 if(now
<node
.d_value
.until
) {
361 if(!node
.d_value
.domain
.empty())
362 dom
= node
.d_value
.domain
.toString();
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
},
367 {"action", DNSAction::typeToString(node
.d_value
.action
!= DNSAction::Action::None
? node
.d_value
.action
: g_dynBlockAction
) }
369 obj
.insert({dom
, thing
});
376 resp
.body
=my_json
.dump();
377 resp
.headers
["Content-Type"] = "application/json";
379 else if(command
=="ebpfblocklist") {
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
) {
389 {"seconds", (double)(std::get
<2>(entry
).tv_sec
- now
.tv_sec
)},
390 {"blocks", (double)(std::get
<1>(entry
))}
392 obj
.insert({std::get
<0>(entry
).toString(), thing
});
395 #endif /* HAVE_EBPF */
397 resp
.body
=my_json
.dump();
398 resp
.headers
["Content-Type"] = "application/json";
404 else if (req
.url
.path
== "/metrics") {
405 handleCORS(req
, resp
);
408 std::ostringstream output
;
409 for (const auto& e
: g_stats
.entries
) {
410 if (e
.first
== "special-memory-usage")
411 continue; // Too expensive for get-all
412 std::string metricName
= std::get
<0>(e
);
414 // Prometheus suggest using '_' instead of '-'
415 std::string prometheusMetricName
= "dnsdist_" + boost::replace_all_copy(metricName
, "-", "_");
417 MetricDefinition metricDetails
;
419 if (!g_metricDefinitions
.getMetricDetails(metricName
, metricDetails
)) {
420 vinfolog("Do not have metric details for %s", metricName
);
424 std::string prometheusTypeName
= g_metricDefinitions
.getPrometheusStringMetricType(metricDetails
.prometheusType
);
426 if (prometheusTypeName
== "") {
427 vinfolog("Unknown Prometheus type for %s", metricName
);
431 // for these we have the help and types encoded in the sources:
432 output
<< "# HELP " << prometheusMetricName
<< " " << metricDetails
.description
<< "\n";
433 output
<< "# TYPE " << prometheusMetricName
<< " " << prometheusTypeName
<< "\n";
434 output
<< prometheusMetricName
<< " ";
436 if (const auto& val
= boost::get
<DNSDistStats::stat_t
*>(&std::get
<1>(e
)))
437 output
<< (*val
)->load();
438 else if (const auto& dval
= boost::get
<double*>(&std::get
<1>(e
)))
441 output
<< (*boost::get
<DNSDistStats::statfunction_t
>(&std::get
<1>(e
)))(std::get
<0>(e
));
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";
462 auto states
= g_dstates
.getLocal();
463 const string statesbase
= "dnsdist_server_";
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";
469 output
<< "# HELP " << statesbase
<< "latency " << "Server's latency when answering questions in miliseconds" << "\n";
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";
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";
496 for (const auto& state
: *states
) {
499 if (state
->name
.empty())
500 serverName
= state
->remote
.toStringWithPort();
502 serverName
= state
->getName();
504 boost::replace_all(serverName
, ".", "_");
506 const std::string label
= boost::str(boost::format("{server=\"%1%\",address=\"%2%\"}")
507 % serverName
% state
->remote
.toStringWithPort());
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";
526 std::map
<std::string
,uint64_t> frontendDuplicates
;
527 for (const auto& front
: g_frontends
) {
528 if (front
->udpFD
== -1 && front
->tcpFD
== -1)
531 string frontName
= front
->local
.toString() + ":" + std::to_string(front
->local
.getPort());
532 string proto
= (front
->udpFD
>= 0 ? "udp" : "tcp");
533 string fullName
= frontName
+ "_" + proto
;
534 auto dupPair
= frontendDuplicates
.insert({fullName
, 1});
535 if (!dupPair
.second
) {
536 frontName
= frontName
+ "_" + std::to_string(dupPair
.first
->second
);
537 ++(dupPair
.first
->second
);
540 output
<< "dnsdist_frontend_queries{frontend=\"" << frontName
<< "\",proto=\"" << proto
541 << "\"} " << front
->queries
.load() << "\n";
544 auto localPools
= g_pools
.getLocal();
545 const string cachebase
= "dnsdist_pool_";
547 for (const auto& entry
: *localPools
) {
548 string poolName
= entry
.first
;
550 if (poolName
.empty()) {
551 poolName
= "_default_";
553 const string label
= "{pool=\"" + poolName
+ "\"}";
554 const std::shared_ptr
<ServerPool
> pool
= entry
.second
;
555 output
<< "dnsdist_pool_servers" << label
<< " " << pool
->countServers(false) << "\n";
556 output
<< "dnsdist_pool_active_servers" << label
<< " " << pool
->countServers(true) << "\n";
558 if (pool
->packetCache
!= nullptr) {
559 const auto& cache
= pool
->packetCache
;
561 output
<< cachebase
<< "cache_size" <<label
<< " " << cache
->getMaxEntries() << "\n";
562 output
<< cachebase
<< "cache_entries" <<label
<< " " << cache
->getEntriesCount() << "\n";
563 output
<< cachebase
<< "cache_hits" <<label
<< " " << cache
->getHits() << "\n";
564 output
<< cachebase
<< "cache_misses" <<label
<< " " << cache
->getMisses() << "\n";
565 output
<< cachebase
<< "cache_deferred_inserts" <<label
<< " " << cache
->getDeferredInserts() << "\n";
566 output
<< cachebase
<< "cache_deferred_lookups" <<label
<< " " << cache
->getDeferredLookups() << "\n";
567 output
<< cachebase
<< "cache_lookup_collisions" <<label
<< " " << cache
->getLookupCollisions() << "\n";
568 output
<< cachebase
<< "cache_insert_collisions" <<label
<< " " << cache
->getInsertCollisions() << "\n";
569 output
<< cachebase
<< "cache_ttl_too_shorts" <<label
<< " " << cache
->getTTLTooShorts() << "\n";
573 resp
.body
= output
.str();
574 resp
.headers
["Content-Type"] = "text/plain";
577 else if(req
.url
.path
=="/api/v1/servers/localhost") {
578 handleCORS(req
, resp
);
582 auto localServers
= g_dstates
.getLocal();
584 for(const auto& a
: *localServers
) {
586 if(a
->availability
== DownstreamState::Availability::Up
)
588 else if(a
->availability
== DownstreamState::Availability::Down
)
591 status
= (a
->upStatus
? "up" : "down");
594 for(const auto& p
: a
->pools
)
600 {"address", a
->remote
.toStringWithPort()},
602 {"qps", (double)a
->queryLoad
},
603 {"qpsLimit", (double)a
->qps
.getRate()},
604 {"outstanding", (double)a
->outstanding
},
605 {"reuseds", (double)a
->reuseds
},
606 {"weight", (double)a
->weight
},
607 {"order", (double)a
->order
},
609 {"latency", (double)(a
->latencyUsec
/1000.0)},
610 {"queries", (double)a
->queries
},
611 {"sendErrors", (double)a
->sendErrors
},
612 {"tcpDiedSendingQuery", (double)a
->tcpDiedSendingQuery
},
613 {"tcpDiedReadingResponse", (double)a
->tcpDiedReadingResponse
},
614 {"tcpGaveUp", (double)a
->tcpGaveUp
},
615 {"tcpReadTimeouts", (double)a
->tcpReadTimeouts
},
616 {"tcpWriteTimeouts", (double)a
->tcpWriteTimeouts
},
617 {"tcpCurrentConnections", (double)a
->tcpCurrentConnections
},
618 {"tcpAvgQueriesPerConnection", (double)a
->tcpAvgQueriesPerConnection
},
619 {"tcpAvgConnectionDuration", (double)a
->tcpAvgConnectionDuration
},
620 {"dropRate", (double)a
->dropRate
}
623 /* sending a latency for a DOWN server doesn't make sense */
624 if (a
->availability
== DownstreamState::Availability::Down
) {
625 server
["latency"] = nullptr;
628 servers
.push_back(server
);
631 Json::array frontends
;
633 for(const auto& front
: g_frontends
) {
634 if (front
->udpFD
== -1 && front
->tcpFD
== -1)
636 Json::object frontend
{
638 { "address", front
->local
.toStringWithPort() },
639 { "udp", front
->udpFD
>= 0 },
640 { "tcp", front
->tcpFD
>= 0 },
641 { "type", front
->getType() },
642 { "queries", (double) front
->queries
.load() },
643 { "tcpDiedReadingQuery", (double) front
->tcpDiedReadingQuery
.load() },
644 { "tcpDiedSendingResponse", (double) front
->tcpDiedSendingResponse
.load() },
645 { "tcpGaveUp", (double) front
->tcpGaveUp
.load() },
646 { "tcpClientTimeouts", (double) front
->tcpClientTimeouts
},
647 { "tcpDownstreamTimeouts", (double) front
->tcpDownstreamTimeouts
},
648 { "tcpCurrentConnections", (double) front
->tcpCurrentConnections
},
649 { "tcpAvgQueriesPerConnection", (double) front
->tcpAvgQueriesPerConnection
},
650 { "tcpAvgConnectionDuration", (double) front
->tcpAvgConnectionDuration
},
652 frontends
.push_back(frontend
);
656 auto localPools
= g_pools
.getLocal();
658 for(const auto& pool
: *localPools
) {
659 const auto& cache
= pool
.second
->packetCache
;
662 { "name", pool
.first
},
663 { "serversCount", (double) pool
.second
->countServers(false) },
664 { "cacheSize", (double) (cache
? cache
->getMaxEntries() : 0) },
665 { "cacheEntries", (double) (cache
? cache
->getEntriesCount() : 0) },
666 { "cacheHits", (double) (cache
? cache
->getHits() : 0) },
667 { "cacheMisses", (double) (cache
? cache
->getMisses() : 0) },
668 { "cacheDeferredInserts", (double) (cache
? cache
->getDeferredInserts() : 0) },
669 { "cacheDeferredLookups", (double) (cache
? cache
->getDeferredLookups() : 0) },
670 { "cacheLookupCollisions", (double) (cache
? cache
->getLookupCollisions() : 0) },
671 { "cacheInsertCollisions", (double) (cache
? cache
->getInsertCollisions() : 0) },
672 { "cacheTTLTooShorts", (double) (cache
? cache
->getTTLTooShorts() : 0) }
674 pools
.push_back(entry
);
678 auto localRules
= g_rulactions
.getLocal();
680 for(const auto& a
: *localRules
) {
683 {"creationOrder", (double)a
.d_creationOrder
},
684 {"uuid", boost::uuids::to_string(a
.d_id
)},
685 {"matches", (double)a
.d_rule
->d_matches
},
686 {"rule", a
.d_rule
->toString()},
687 {"action", a
.d_action
->toString()},
688 {"action-stats", a
.d_action
->getStats()}
690 rules
.push_back(rule
);
693 auto responseRules
= someResponseRulesToJson(&g_resprulactions
);
694 auto cacheHitResponseRules
= someResponseRulesToJson(&g_cachehitresprulactions
);
695 auto selfAnsweredResponseRules
= someResponseRulesToJson(&g_selfansweredresprulactions
);
700 g_ACL
.getLocal()->toStringVector(&vec
);
702 for(const auto& s
: vec
) {
703 if(!acl
.empty()) acl
+= ", ";
706 string localaddresses
;
707 for(const auto& front
: g_frontends
) {
711 if (!localaddresses
.empty()) {
712 localaddresses
+= ", ";
714 localaddresses
+= front
->local
.toStringWithPort();
717 Json my_json
= Json::object
{
718 { "daemon_type", "dnsdist" },
719 { "version", VERSION
},
720 { "servers", servers
},
721 { "frontends", frontends
},
724 { "response-rules", responseRules
},
725 { "cache-hit-response-rules", cacheHitResponseRules
},
726 { "self-answered-response-rules", selfAnsweredResponseRules
},
728 { "local", localaddresses
}
730 resp
.headers
["Content-Type"] = "application/json";
731 resp
.body
=my_json
.dump();
733 else if(req
.url
.path
=="/api/v1/servers/localhost/statistics") {
734 handleCORS(req
, resp
);
738 for(const auto& item
: g_stats
.entries
) {
739 if (item
.first
== "special-memory-usage")
740 continue; // Too expensive for get-all
742 if(const auto& val
= boost::get
<DNSDistStats::stat_t
*>(&item
.second
)) {
743 doc
.push_back(Json::object
{
744 { "type", "StatisticItem" },
745 { "name", item
.first
},
746 { "value", (double)(*val
)->load() }
749 else if (const auto& dval
= boost::get
<double*>(&item
.second
)) {
750 doc
.push_back(Json::object
{
751 { "type", "StatisticItem" },
752 { "name", item
.first
},
753 { "value", (**dval
) }
757 doc
.push_back(Json::object
{
758 { "type", "StatisticItem" },
759 { "name", item
.first
},
760 { "value", (double)(*boost::get
<DNSDistStats::statfunction_t
>(&item
.second
))(item
.first
) }
765 resp
.body
=my_json
.dump();
766 resp
.headers
["Content-Type"] = "application/json";
768 else if(req
.url
.path
=="/api/v1/servers/localhost/config") {
769 handleCORS(req
, resp
);
773 typedef boost::variant
<bool, double, std::string
> configentry_t
;
774 std::vector
<std::pair
<std::string
, configentry_t
> > configEntries
{
775 { "acl", g_ACL
.getLocal()->toString() },
776 { "allow-empty-response", g_allowEmptyResponse
},
777 { "control-socket", g_serverControl
.toStringWithPort() },
778 { "ecs-override", g_ECSOverride
},
779 { "ecs-source-prefix-v4", (double) g_ECSSourcePrefixV4
},
780 { "ecs-source-prefix-v6", (double) g_ECSSourcePrefixV6
},
781 { "fixup-case", g_fixupCase
},
782 { "max-outstanding", (double) g_maxOutstanding
},
783 { "server-policy", g_policy
.getLocal()->name
},
784 { "stale-cache-entries-ttl", (double) g_staleCacheEntriesTTL
},
785 { "tcp-recv-timeout", (double) g_tcpRecvTimeout
},
786 { "tcp-send-timeout", (double) g_tcpSendTimeout
},
787 { "truncate-tc", g_truncateTC
},
788 { "verbose", g_verbose
},
789 { "verbose-health-checks", g_verboseHealthChecks
}
791 for(const auto& item
: configEntries
) {
792 if (const auto& bval
= boost::get
<bool>(&item
.second
)) {
793 doc
.push_back(Json::object
{
794 { "type", "ConfigSetting" },
795 { "name", item
.first
},
799 else if (const auto& sval
= boost::get
<string
>(&item
.second
)) {
800 doc
.push_back(Json::object
{
801 { "type", "ConfigSetting" },
802 { "name", item
.first
},
806 else if (const auto& dval
= boost::get
<double>(&item
.second
)) {
807 doc
.push_back(Json::object
{
808 { "type", "ConfigSetting" },
809 { "name", item
.first
},
815 resp
.body
=my_json
.dump();
816 resp
.headers
["Content-Type"] = "application/json";
818 else if(req
.url
.path
=="/api/v1/servers/localhost/config/allow-from") {
819 handleCORS(req
, resp
);
821 resp
.headers
["Content-Type"] = "application/json";
824 if (req
.method
== "PUT") {
826 Json doc
= Json::parse(req
.body
, err
);
828 if (!doc
.is_null()) {
830 auto aclList
= doc
["value"];
831 if (aclList
.is_array()) {
833 for (auto value
: aclList
.array_items()) {
835 nmg
.addMask(value
.string_value());
836 } catch (NetmaskException
&e
) {
842 if (resp
.status
== 200) {
843 infolog("Updating the ACL via the API to %s", nmg
.toString());
856 if (resp
.status
== 200) {
859 g_ACL
.getLocal()->toStringVector(&vec
);
861 for(const auto& s
: vec
) {
866 { "type", "ConfigSetting" },
867 { "name", "allow-from" },
871 resp
.body
=my_json
.dump();
874 else if(!req
.url
.path
.empty() && g_urlmap
.count(req
.url
.path
.c_str()+1)) {
875 resp
.body
.assign(g_urlmap
[req
.url
.path
.c_str()+1]);
876 vector
<string
> parts
;
877 stringtok(parts
, req
.url
.path
, ".");
878 if(parts
.back() == "html")
879 resp
.headers
["Content-Type"] = "text/html" + charset
;
880 else if(parts
.back() == "css")
881 resp
.headers
["Content-Type"] = "text/css" + charset
;
882 else if(parts
.back() == "js")
883 resp
.headers
["Content-Type"] = "application/javascript" + charset
;
884 else if(parts
.back() == "png")
885 resp
.headers
["Content-Type"] = "image/png";
888 else if(req
.url
.path
=="/") {
889 resp
.body
.assign(g_urlmap
["index.html"]);
890 resp
.headers
["Content-Type"] = "text/html" + charset
;
894 // cerr<<"404 for: "<<req.url.path<<endl;
898 std::ostringstream ofs
;
902 writen2(sock
, done
.c_str(), done
.size());
907 catch(const YaHTTP::ParseError
& e
) {
908 vinfolog("Webserver thread died with parse error exception while processing a request from %s: %s", remote
.toStringWithPort(), e
.what());
911 catch(const std::exception
& e
) {
912 errlog("Webserver thread died with exception while processing a request from %s: %s", remote
.toStringWithPort(), e
.what());
916 errlog("Webserver thread died with exception while processing a request from %s", remote
.toStringWithPort());
921 void setWebserverAPIKey(const boost::optional
<std::string
> apiKey
)
923 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
926 g_webserverConfig
.apiKey
= *apiKey
;
928 g_webserverConfig
.apiKey
.clear();
932 void setWebserverPassword(const std::string
& password
)
934 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
936 g_webserverConfig
.password
= password
;
939 void setWebserverCustomHeaders(const boost::optional
<std::map
<std::string
, std::string
> > customHeaders
)
941 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
943 g_webserverConfig
.customHeaders
= customHeaders
;
946 void dnsdistWebserverThread(int sock
, const ComboAddress
& local
)
948 setThreadName("dnsdist/webserv");
949 warnlog("Webserver launched on %s", local
.toStringWithPort());
952 ComboAddress
remote(local
);
953 int fd
= SAccept(sock
, remote
);
954 vinfolog("Got connection from %s", remote
.toStringWithPort());
955 std::thread
t(connectionThread
, fd
, remote
);
958 catch(std::exception
& e
) {
959 errlog("Had an error accepting new webserver connection: %s", e
.what());