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 auto states
= g_dstates
.getLocal();
447 const string statesbase
= "dnsdist_server_";
449 output
<< "# HELP " << statesbase
<< "queries " << "Amount of queries relayed to server" << "\n";
450 output
<< "# TYPE " << statesbase
<< "queries " << "counter" << "\n";
451 output
<< "# HELP " << statesbase
<< "drops " << "Amount of queries not answered by server" << "\n";
452 output
<< "# TYPE " << statesbase
<< "drops " << "counter" << "\n";
453 output
<< "# HELP " << statesbase
<< "latency " << "Server's latency when answering questions in miliseconds" << "\n";
454 output
<< "# TYPE " << statesbase
<< "latency " << "gauge" << "\n";
455 output
<< "# HELP " << statesbase
<< "senderrors " << "Total number of OS snd errors while relaying queries" << "\n";
456 output
<< "# TYPE " << statesbase
<< "senderrors " << "counter" << "\n";
457 output
<< "# HELP " << statesbase
<< "outstanding " << "Current number of queries that are waiting for a backend response" << "\n";
458 output
<< "# TYPE " << statesbase
<< "outstanding " << "gauge" << "\n";
459 output
<< "# HELP " << statesbase
<< "order " << "The order in which this server is picked" << "\n";
460 output
<< "# TYPE " << statesbase
<< "order " << "gauge" << "\n";
461 output
<< "# HELP " << statesbase
<< "weight " << "The weight within the order in which this server is picked" << "\n";
462 output
<< "# TYPE " << statesbase
<< "weight " << "gauge" << "\n";
463 output
<< "# HELP " << statesbase
<< "tcpdiedsendingquery " << "The number of TCP I/O errors while sending the query" << "\n";
464 output
<< "# TYPE " << statesbase
<< "tcpdiedsendingquery " << "counter" << "\n";
465 output
<< "# HELP " << statesbase
<< "tcpdiedreadingresponse " << "The number of TCP I/O errors while reading the response" << "\n";
466 output
<< "# TYPE " << statesbase
<< "tcpdiedreadingresponse " << "counter" << "\n";
467 output
<< "# HELP " << statesbase
<< "tcpgaveup " << "The number of TCP connections failing after too many attempts" << "\n";
468 output
<< "# TYPE " << statesbase
<< "tcpgaveup " << "counter" << "\n";
469 output
<< "# HELP " << statesbase
<< "tcpreadtimeouts " << "The number of TCP read timeouts" << "\n";
470 output
<< "# TYPE " << statesbase
<< "tcpreadtimeouts " << "counter" << "\n";
471 output
<< "# HELP " << statesbase
<< "tcpwritetimeouts " << "The number of TCP write timeouts" << "\n";
472 output
<< "# TYPE " << statesbase
<< "tcpwritetimeouts " << "counter" << "\n";
473 output
<< "# HELP " << statesbase
<< "tcpcurrentconnections " << "The number of current TCP connections" << "\n";
474 output
<< "# TYPE " << statesbase
<< "tcpcurrentconnections " << "gauge" << "\n";
475 output
<< "# HELP " << statesbase
<< "tcpavgqueriesperconn " << "The average number of queries per TCP connection" << "\n";
476 output
<< "# TYPE " << statesbase
<< "tcpavgqueriesperconn " << "gauge" << "\n";
477 output
<< "# HELP " << statesbase
<< "tcpavgconnduration " << "The average duration of a TCP connection (ms)" << "\n";
478 output
<< "# TYPE " << statesbase
<< "tcpavgconnduration " << "gauge" << "\n";
480 for (const auto& state
: *states
) {
483 if (state
->name
.empty())
484 serverName
= state
->remote
.toStringWithPort();
486 serverName
= state
->getName();
488 boost::replace_all(serverName
, ".", "_");
490 const std::string label
= boost::str(boost::format("{server=\"%1%\",address=\"%2%\"}")
491 % serverName
% state
->remote
.toStringWithPort());
493 output
<< statesbase
<< "queries" << label
<< " " << state
->queries
.load() << "\n";
494 output
<< statesbase
<< "drops" << label
<< " " << state
->reuseds
.load() << "\n";
495 output
<< statesbase
<< "latency" << label
<< " " << state
->latencyUsec
/1000.0 << "\n";
496 output
<< statesbase
<< "senderrors" << label
<< " " << state
->sendErrors
.load() << "\n";
497 output
<< statesbase
<< "outstanding" << label
<< " " << state
->outstanding
.load() << "\n";
498 output
<< statesbase
<< "order" << label
<< " " << state
->order
<< "\n";
499 output
<< statesbase
<< "weight" << label
<< " " << state
->weight
<< "\n";
500 output
<< statesbase
<< "tcpdiedsendingquery" << label
<< " " << state
->tcpDiedSendingQuery
<< "\n";
501 output
<< statesbase
<< "tcpdiedreadingresponse" << label
<< " " << state
->tcpDiedReadingResponse
<< "\n";
502 output
<< statesbase
<< "tcpgaveup" << label
<< " " << state
->tcpGaveUp
<< "\n";
503 output
<< statesbase
<< "tcpreadtimeouts" << label
<< " " << state
->tcpReadTimeouts
<< "\n";
504 output
<< statesbase
<< "tcpwritetimeouts" << label
<< " " << state
->tcpWriteTimeouts
<< "\n";
505 output
<< statesbase
<< "tcpcurrentconnections" << label
<< " " << state
->tcpCurrentConnections
<< "\n";
506 output
<< statesbase
<< "tcpavgqueriesperconn" << label
<< " " << state
->tcpAvgQueriesPerConnection
<< "\n";
507 output
<< statesbase
<< "tcpavgconnduration" << label
<< " " << state
->tcpAvgConnectionDuration
<< "\n";
510 for (const auto& front
: g_frontends
) {
511 if (front
->udpFD
== -1 && front
->tcpFD
== -1)
514 string frontName
= front
->local
.toString() + ":" + std::to_string(front
->local
.getPort());
515 string proto
= (front
->udpFD
>= 0 ? "udp" : "tcp");
517 output
<< "dnsdist_frontend_queries{frontend=\"" << frontName
<< "\",proto=\"" << proto
518 << "\"} " << front
->queries
.load() << "\n";
521 auto localPools
= g_pools
.getLocal();
522 const string cachebase
= "dnsdist_pool_";
524 for (const auto& entry
: *localPools
) {
525 string poolName
= entry
.first
;
527 if (poolName
.empty()) {
528 poolName
= "_default_";
530 const string label
= "{pool=\"" + poolName
+ "\"}";
531 const std::shared_ptr
<ServerPool
> pool
= entry
.second
;
532 output
<< "dnsdist_pool_servers" << label
<< " " << pool
->countServers(false) << "\n";
533 output
<< "dnsdist_pool_active_servers" << label
<< " " << pool
->countServers(true) << "\n";
535 if (pool
->packetCache
!= nullptr) {
536 const auto& cache
= pool
->packetCache
;
538 output
<< cachebase
<< "cache_size" <<label
<< " " << cache
->getMaxEntries() << "\n";
539 output
<< cachebase
<< "cache_entries" <<label
<< " " << cache
->getEntriesCount() << "\n";
540 output
<< cachebase
<< "cache_hits" <<label
<< " " << cache
->getHits() << "\n";
541 output
<< cachebase
<< "cache_misses" <<label
<< " " << cache
->getMisses() << "\n";
542 output
<< cachebase
<< "cache_deferred_inserts" <<label
<< " " << cache
->getDeferredInserts() << "\n";
543 output
<< cachebase
<< "cache_deferred_lookups" <<label
<< " " << cache
->getDeferredLookups() << "\n";
544 output
<< cachebase
<< "cache_lookup_collisions" <<label
<< " " << cache
->getLookupCollisions() << "\n";
545 output
<< cachebase
<< "cache_insert_collisions" <<label
<< " " << cache
->getInsertCollisions() << "\n";
546 output
<< cachebase
<< "cache_ttl_too_shorts" <<label
<< " " << cache
->getTTLTooShorts() << "\n";
550 resp
.body
= output
.str();
551 resp
.headers
["Content-Type"] = "text/plain";
554 else if(req
.url
.path
=="/api/v1/servers/localhost") {
555 handleCORS(req
, resp
);
559 auto localServers
= g_dstates
.getLocal();
561 for(const auto& a
: *localServers
) {
563 if(a
->availability
== DownstreamState::Availability::Up
)
565 else if(a
->availability
== DownstreamState::Availability::Down
)
568 status
= (a
->upStatus
? "up" : "down");
571 for(const auto& p
: a
->pools
)
577 {"address", a
->remote
.toStringWithPort()},
579 {"qps", (double)a
->queryLoad
},
580 {"qpsLimit", (double)a
->qps
.getRate()},
581 {"outstanding", (double)a
->outstanding
},
582 {"reuseds", (double)a
->reuseds
},
583 {"weight", (double)a
->weight
},
584 {"order", (double)a
->order
},
586 {"latency", (double)(a
->latencyUsec
/1000.0)},
587 {"queries", (double)a
->queries
},
588 {"sendErrors", (double)a
->sendErrors
},
589 {"tcpDiedSendingQuery", (double)a
->tcpDiedSendingQuery
},
590 {"tcpDiedReadingResponse", (double)a
->tcpDiedReadingResponse
},
591 {"tcpGaveUp", (double)a
->tcpGaveUp
},
592 {"tcpReadTimeouts", (double)a
->tcpReadTimeouts
},
593 {"tcpWriteTimeouts", (double)a
->tcpWriteTimeouts
},
594 {"tcpCurrentConnections", (double)a
->tcpCurrentConnections
},
595 {"tcpAvgQueriesPerConnection", (double)a
->tcpAvgQueriesPerConnection
},
596 {"tcpAvgConnectionDuration", (double)a
->tcpAvgConnectionDuration
},
597 {"dropRate", (double)a
->dropRate
}
600 /* sending a latency for a DOWN server doesn't make sense */
601 if (a
->availability
== DownstreamState::Availability::Down
) {
602 server
["latency"] = nullptr;
605 servers
.push_back(server
);
608 Json::array frontends
;
610 for(const auto& front
: g_frontends
) {
611 if (front
->udpFD
== -1 && front
->tcpFD
== -1)
613 Json::object frontend
{
615 { "address", front
->local
.toStringWithPort() },
616 { "udp", front
->udpFD
>= 0 },
617 { "tcp", front
->tcpFD
>= 0 },
618 { "type", front
->getType() },
619 { "queries", (double) front
->queries
.load() },
620 { "tcpDiedReadingQuery", (double) front
->tcpDiedReadingQuery
.load() },
621 { "tcpDiedSendingResponse", (double) front
->tcpDiedSendingResponse
.load() },
622 { "tcpGaveUp", (double) front
->tcpGaveUp
.load() },
623 { "tcpClientTimeouts", (double) front
->tcpClientTimeouts
},
624 { "tcpDownstreamTimeouts", (double) front
->tcpDownstreamTimeouts
},
625 { "tcpCurrentConnections", (double) front
->tcpCurrentConnections
},
626 { "tcpAvgQueriesPerConnection", (double) front
->tcpAvgQueriesPerConnection
},
627 { "tcpAvgConnectionDuration", (double) front
->tcpAvgConnectionDuration
},
629 frontends
.push_back(frontend
);
633 auto localPools
= g_pools
.getLocal();
635 for(const auto& pool
: *localPools
) {
636 const auto& cache
= pool
.second
->packetCache
;
639 { "name", pool
.first
},
640 { "serversCount", (double) pool
.second
->countServers(false) },
641 { "cacheSize", (double) (cache
? cache
->getMaxEntries() : 0) },
642 { "cacheEntries", (double) (cache
? cache
->getEntriesCount() : 0) },
643 { "cacheHits", (double) (cache
? cache
->getHits() : 0) },
644 { "cacheMisses", (double) (cache
? cache
->getMisses() : 0) },
645 { "cacheDeferredInserts", (double) (cache
? cache
->getDeferredInserts() : 0) },
646 { "cacheDeferredLookups", (double) (cache
? cache
->getDeferredLookups() : 0) },
647 { "cacheLookupCollisions", (double) (cache
? cache
->getLookupCollisions() : 0) },
648 { "cacheInsertCollisions", (double) (cache
? cache
->getInsertCollisions() : 0) },
649 { "cacheTTLTooShorts", (double) (cache
? cache
->getTTLTooShorts() : 0) }
651 pools
.push_back(entry
);
655 auto localRules
= g_rulactions
.getLocal();
657 for(const auto& a
: *localRules
) {
660 {"creationOrder", (double)a
.d_creationOrder
},
661 {"uuid", boost::uuids::to_string(a
.d_id
)},
662 {"matches", (double)a
.d_rule
->d_matches
},
663 {"rule", a
.d_rule
->toString()},
664 {"action", a
.d_action
->toString()},
665 {"action-stats", a
.d_action
->getStats()}
667 rules
.push_back(rule
);
670 auto responseRules
= someResponseRulesToJson(&g_resprulactions
);
671 auto cacheHitResponseRules
= someResponseRulesToJson(&g_cachehitresprulactions
);
672 auto selfAnsweredResponseRules
= someResponseRulesToJson(&g_selfansweredresprulactions
);
677 g_ACL
.getLocal()->toStringVector(&vec
);
679 for(const auto& s
: vec
) {
680 if(!acl
.empty()) acl
+= ", ";
683 string localaddresses
;
684 for(const auto& front
: g_frontends
) {
688 if (!localaddresses
.empty()) {
689 localaddresses
+= ", ";
691 localaddresses
+= front
->local
.toStringWithPort();
694 Json my_json
= Json::object
{
695 { "daemon_type", "dnsdist" },
696 { "version", VERSION
},
697 { "servers", servers
},
698 { "frontends", frontends
},
701 { "response-rules", responseRules
},
702 { "cache-hit-response-rules", cacheHitResponseRules
},
703 { "self-answered-response-rules", selfAnsweredResponseRules
},
705 { "local", localaddresses
}
707 resp
.headers
["Content-Type"] = "application/json";
708 resp
.body
=my_json
.dump();
710 else if(req
.url
.path
=="/api/v1/servers/localhost/statistics") {
711 handleCORS(req
, resp
);
715 for(const auto& item
: g_stats
.entries
) {
716 if (item
.first
== "special-memory-usage")
717 continue; // Too expensive for get-all
719 if(const auto& val
= boost::get
<DNSDistStats::stat_t
*>(&item
.second
)) {
720 doc
.push_back(Json::object
{
721 { "type", "StatisticItem" },
722 { "name", item
.first
},
723 { "value", (double)(*val
)->load() }
726 else if (const auto& dval
= boost::get
<double*>(&item
.second
)) {
727 doc
.push_back(Json::object
{
728 { "type", "StatisticItem" },
729 { "name", item
.first
},
730 { "value", (**dval
) }
734 doc
.push_back(Json::object
{
735 { "type", "StatisticItem" },
736 { "name", item
.first
},
737 { "value", (double)(*boost::get
<DNSDistStats::statfunction_t
>(&item
.second
))(item
.first
) }
742 resp
.body
=my_json
.dump();
743 resp
.headers
["Content-Type"] = "application/json";
745 else if(req
.url
.path
=="/api/v1/servers/localhost/config") {
746 handleCORS(req
, resp
);
750 typedef boost::variant
<bool, double, std::string
> configentry_t
;
751 std::vector
<std::pair
<std::string
, configentry_t
> > configEntries
{
752 { "acl", g_ACL
.getLocal()->toString() },
753 { "allow-empty-response", g_allowEmptyResponse
},
754 { "control-socket", g_serverControl
.toStringWithPort() },
755 { "ecs-override", g_ECSOverride
},
756 { "ecs-source-prefix-v4", (double) g_ECSSourcePrefixV4
},
757 { "ecs-source-prefix-v6", (double) g_ECSSourcePrefixV6
},
758 { "fixup-case", g_fixupCase
},
759 { "max-outstanding", (double) g_maxOutstanding
},
760 { "server-policy", g_policy
.getLocal()->name
},
761 { "stale-cache-entries-ttl", (double) g_staleCacheEntriesTTL
},
762 { "tcp-recv-timeout", (double) g_tcpRecvTimeout
},
763 { "tcp-send-timeout", (double) g_tcpSendTimeout
},
764 { "truncate-tc", g_truncateTC
},
765 { "verbose", g_verbose
},
766 { "verbose-health-checks", g_verboseHealthChecks
}
768 for(const auto& item
: configEntries
) {
769 if (const auto& bval
= boost::get
<bool>(&item
.second
)) {
770 doc
.push_back(Json::object
{
771 { "type", "ConfigSetting" },
772 { "name", item
.first
},
776 else if (const auto& sval
= boost::get
<string
>(&item
.second
)) {
777 doc
.push_back(Json::object
{
778 { "type", "ConfigSetting" },
779 { "name", item
.first
},
783 else if (const auto& dval
= boost::get
<double>(&item
.second
)) {
784 doc
.push_back(Json::object
{
785 { "type", "ConfigSetting" },
786 { "name", item
.first
},
792 resp
.body
=my_json
.dump();
793 resp
.headers
["Content-Type"] = "application/json";
795 else if(req
.url
.path
=="/api/v1/servers/localhost/config/allow-from") {
796 handleCORS(req
, resp
);
798 resp
.headers
["Content-Type"] = "application/json";
801 if (req
.method
== "PUT") {
803 Json doc
= Json::parse(req
.body
, err
);
805 if (!doc
.is_null()) {
807 auto aclList
= doc
["value"];
808 if (aclList
.is_array()) {
810 for (auto value
: aclList
.array_items()) {
812 nmg
.addMask(value
.string_value());
813 } catch (NetmaskException
&e
) {
819 if (resp
.status
== 200) {
820 infolog("Updating the ACL via the API to %s", nmg
.toString());
833 if (resp
.status
== 200) {
836 g_ACL
.getLocal()->toStringVector(&vec
);
838 for(const auto& s
: vec
) {
843 { "type", "ConfigSetting" },
844 { "name", "allow-from" },
848 resp
.body
=my_json
.dump();
851 else if(!req
.url
.path
.empty() && g_urlmap
.count(req
.url
.path
.c_str()+1)) {
852 resp
.body
.assign(g_urlmap
[req
.url
.path
.c_str()+1]);
853 vector
<string
> parts
;
854 stringtok(parts
, req
.url
.path
, ".");
855 if(parts
.back() == "html")
856 resp
.headers
["Content-Type"] = "text/html" + charset
;
857 else if(parts
.back() == "css")
858 resp
.headers
["Content-Type"] = "text/css" + charset
;
859 else if(parts
.back() == "js")
860 resp
.headers
["Content-Type"] = "application/javascript" + charset
;
861 else if(parts
.back() == "png")
862 resp
.headers
["Content-Type"] = "image/png";
865 else if(req
.url
.path
=="/") {
866 resp
.body
.assign(g_urlmap
["index.html"]);
867 resp
.headers
["Content-Type"] = "text/html" + charset
;
871 // cerr<<"404 for: "<<req.url.path<<endl;
875 std::ostringstream ofs
;
879 writen2(sock
, done
.c_str(), done
.size());
884 catch(const YaHTTP::ParseError
& e
) {
885 vinfolog("Webserver thread died with parse error exception while processing a request from %s: %s", remote
.toStringWithPort(), e
.what());
888 catch(const std::exception
& e
) {
889 errlog("Webserver thread died with exception while processing a request from %s: %s", remote
.toStringWithPort(), e
.what());
893 errlog("Webserver thread died with exception while processing a request from %s", remote
.toStringWithPort());
898 void setWebserverAPIKey(const boost::optional
<std::string
> apiKey
)
900 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
903 g_webserverConfig
.apiKey
= *apiKey
;
905 g_webserverConfig
.apiKey
.clear();
909 void setWebserverPassword(const std::string
& password
)
911 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
913 g_webserverConfig
.password
= password
;
916 void setWebserverCustomHeaders(const boost::optional
<std::map
<std::string
, std::string
> > customHeaders
)
918 std::lock_guard
<std::mutex
> lock(g_webserverConfig
.lock
);
920 g_webserverConfig
.customHeaders
= customHeaders
;
923 void dnsdistWebserverThread(int sock
, const ComboAddress
& local
)
925 setThreadName("dnsdist/webserv");
926 warnlog("Webserver launched on %s", local
.toStringWithPort());
929 ComboAddress
remote(local
);
930 int fd
= SAccept(sock
, remote
);
931 vinfolog("Got connection from %s", remote
.toStringWithPort());
932 std::thread
t(connectionThread
, fd
, remote
);
935 catch(std::exception
& e
) {
936 errlog("Had an error accepting new webserver connection: %s", e
.what());