]>
Commit | Line | Data |
---|---|---|
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 | 40 | bool g_apiReadWrite{false}; |
80dbd7d2 | 41 | WebserverConfig g_webserverConfig; |
56d68fad RG |
42 | std::string g_apiConfigDirectory; |
43 | ||
44 | static 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 | ||
68 | static 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 | 85 | static 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 | ||
99 | static 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 | ||
122 | static bool isAnAPIRequest(const YaHTTP::Request& req) | |
123 | { | |
124 | return req.url.path.find("/api/") == 0; | |
125 | } | |
126 | ||
127 | static bool isAnAPIRequestAllowedWithWebAuth(const YaHTTP::Request& req) | |
128 | { | |
129 | return req.url.path == "/api/v1/servers/localhost"; | |
130 | } | |
131 | ||
132 | static bool isAStatsRequest(const YaHTTP::Request& req) | |
133 | { | |
37a5c2d5 | 134 | return req.url.path == "/jsonstat" || req.url.path == "/metrics"; |
55afa518 RG |
135 | } |
136 | ||
80dbd7d2 | 137 | static 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 |
158 | static 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 | 171 | static 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 |
194 | static 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 | ||
215 | static 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 |
227 | template<typename T> |
228 | static 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 | 248 | static 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 | |
1079 | void 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 | ||
1090 | void setWebserverPassword(const std::string& password) | |
1091 | { | |
1092 | std::lock_guard<std::mutex> lock(g_webserverConfig.lock); | |
1093 | ||
1094 | g_webserverConfig.password = password; | |
1095 | } | |
1096 | ||
1097 | void 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 | ||
1104 | void 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 | } |