]>
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 | |
6141572b | 446 | auto states = g_dstates.getLocal(); |
d37db88f | 447 | const string statesbase = "dnsdist_server_"; |
e7fe227b | 448 | |
a6e9e107 RG |
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"; | |
cff9aa03 RG |
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"; | |
80dbd7d2 | 479 | |
6141572b | 480 | for (const auto& state : *states) { |
37a5c2d5 | 481 | string serverName; |
80dbd7d2 | 482 | |
37a5c2d5 | 483 | if (state->name.empty()) |
47be7d7c | 484 | serverName = state->remote.toStringWithPort(); |
37a5c2d5 PO |
485 | else |
486 | serverName = state->getName(); | |
487 | ||
d18e3070 | 488 | boost::replace_all(serverName, ".", "_"); |
37a5c2d5 | 489 | |
76025fec PO |
490 | const std::string label = boost::str(boost::format("{server=\"%1%\",address=\"%2%\"}") |
491 | % serverName % state->remote.toStringWithPort()); | |
492 | ||
cff9aa03 RG |
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"; | |
d18e3070 | 508 | } |
37a5c2d5 PO |
509 | |
510 | for (const auto& front : g_frontends) { | |
d18e3070 PO |
511 | if (front->udpFD == -1 && front->tcpFD == -1) |
512 | continue; | |
513 | ||
514 | string frontName = front->local.toString() + ":" + std::to_string(front->local.getPort()); | |
d18e3070 | 515 | string proto = (front->udpFD >= 0 ? "udp" : "tcp"); |
37a5c2d5 | 516 | |
bead1c22 | 517 | output << "dnsdist_frontend_queries{frontend=\"" << frontName << "\",proto=\"" << proto |
37a5c2d5 | 518 | << "\"} " << front->queries.load() << "\n"; |
d18e3070 | 519 | } |
37a5c2d5 | 520 | |
6141572b | 521 | auto localPools = g_pools.getLocal(); |
d18e3070 | 522 | const string cachebase = "dnsdist_pool_"; |
80dbd7d2 | 523 | |
6141572b | 524 | for (const auto& entry : *localPools) { |
d18e3070 | 525 | string poolName = entry.first; |
80dbd7d2 | 526 | |
d18e3070 PO |
527 | if (poolName.empty()) { |
528 | poolName = "_default_"; | |
529 | } | |
530 | const string label = "{pool=\"" + poolName + "\"}"; | |
531 | const std::shared_ptr<ServerPool> pool = entry.second; | |
3004c35b | 532 | output << "dnsdist_pool_servers" << label << " " << pool->countServers(false) << "\n"; |
5040e507 | 533 | output << "dnsdist_pool_active_servers" << label << " " << pool->countServers(true) << "\n"; |
37a5c2d5 | 534 | |
d18e3070 PO |
535 | if (pool->packetCache != nullptr) { |
536 | const auto& cache = pool->packetCache; | |
37a5c2d5 PO |
537 | |
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"; | |
d18e3070 PO |
547 | } |
548 | } | |
549 | ||
37a5c2d5 | 550 | resp.body = output.str(); |
d18e3070 PO |
551 | resp.headers["Content-Type"] = "text/plain"; |
552 | } | |
553 | ||
46d06a12 | 554 | else if(req.url.path=="/api/v1/servers/localhost") { |
5387cc78 | 555 | handleCORS(req, resp); |
438a189b | 556 | resp.status=200; |
557 | ||
558 | Json::array servers; | |
a9c2e4ab | 559 | auto localServers = g_dstates.getLocal(); |
438a189b | 560 | int num=0; |
a9c2e4ab | 561 | for(const auto& a : *localServers) { |
438a189b | 562 | string status; |
80dbd7d2 | 563 | if(a->availability == DownstreamState::Availability::Up) |
438a189b | 564 | status = "UP"; |
80dbd7d2 | 565 | else if(a->availability == DownstreamState::Availability::Down) |
438a189b | 566 | status = "DOWN"; |
80dbd7d2 | 567 | else |
438a189b | 568 | status = (a->upStatus ? "up" : "down"); |
4ace9fe8 | 569 | |
357c22dd | 570 | Json::array pools; |
438a189b | 571 | for(const auto& p: a->pools) |
357c22dd RG |
572 | pools.push_back(p); |
573 | ||
80dbd7d2 | 574 | Json::object server{ |
357c22dd | 575 | {"id", num++}, |
18eeccc9 | 576 | {"name", a->name}, |
357c22dd RG |
577 | {"address", a->remote.toStringWithPort()}, |
578 | {"state", status}, | |
778fe0a8 RG |
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}, | |
357c22dd | 585 | {"pools", pools}, |
778fe0a8 | 586 | {"latency", (double)(a->latencyUsec/1000.0)}, |
4ace9fe8 | 587 | {"queries", (double)a->queries}, |
684f332e | 588 | {"sendErrors", (double)a->sendErrors}, |
a6e9e107 RG |
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}, | |
cff9aa03 RG |
594 | {"tcpCurrentConnections", (double)a->tcpCurrentConnections}, |
595 | {"tcpAvgQueriesPerConnection", (double)a->tcpAvgQueriesPerConnection}, | |
596 | {"tcpAvgConnectionDuration", (double)a->tcpAvgConnectionDuration}, | |
684f332e | 597 | {"dropRate", (double)a->dropRate} |
4ace9fe8 | 598 | }; |
357c22dd | 599 | |
36927800 RG |
600 | /* sending a latency for a DOWN server doesn't make sense */ |
601 | if (a->availability == DownstreamState::Availability::Down) { | |
602 | server["latency"] = nullptr; | |
603 | } | |
604 | ||
438a189b | 605 | servers.push_back(server); |
606 | } | |
607 | ||
65dec2a3 RG |
608 | Json::array frontends; |
609 | num=0; | |
610 | for(const auto& front : g_frontends) { | |
611 | if (front->udpFD == -1 && front->tcpFD == -1) | |
612 | continue; | |
613 | Json::object frontend{ | |
614 | { "id", num++ }, | |
615 | { "address", front->local.toStringWithPort() }, | |
616 | { "udp", front->udpFD >= 0 }, | |
617 | { "tcp", front->tcpFD >= 0 }, | |
ba7ec340 | 618 | { "type", front->getType() }, |
a6e9e107 RG |
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 }, | |
cff9aa03 RG |
625 | { "tcpCurrentConnections", (double) front->tcpCurrentConnections }, |
626 | { "tcpAvgQueriesPerConnection", (double) front->tcpAvgQueriesPerConnection }, | |
627 | { "tcpAvgConnectionDuration", (double) front->tcpAvgConnectionDuration }, | |
65dec2a3 RG |
628 | }; |
629 | frontends.push_back(frontend); | |
630 | } | |
631 | ||
4ace9fe8 | 632 | Json::array pools; |
a9c2e4ab | 633 | auto localPools = g_pools.getLocal(); |
4ace9fe8 | 634 | num=0; |
a9c2e4ab | 635 | for(const auto& pool : *localPools) { |
4ace9fe8 RG |
636 | const auto& cache = pool.second->packetCache; |
637 | Json::object entry { | |
638 | { "id", num++ }, | |
639 | { "name", pool.first }, | |
778fe0a8 | 640 | { "serversCount", (double) pool.second->countServers(false) }, |
4ace9fe8 RG |
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) } | |
650 | }; | |
651 | pools.push_back(entry); | |
652 | } | |
653 | ||
438a189b | 654 | Json::array rules; |
a9c2e4ab | 655 | auto localRules = g_rulactions.getLocal(); |
438a189b | 656 | num=0; |
a9c2e4ab | 657 | for(const auto& a : *localRules) { |
438a189b | 658 | Json::object rule{ |
4ace9fe8 | 659 | {"id", num++}, |
f8a222ac | 660 | {"creationOrder", (double)a.d_creationOrder}, |
4d5959e6 RG |
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()} | |
1f4059be | 666 | }; |
438a189b | 667 | rules.push_back(rule); |
668 | } | |
80dbd7d2 | 669 | |
d18eab67 CH |
670 | auto responseRules = someResponseRulesToJson(&g_resprulactions); |
671 | auto cacheHitResponseRules = someResponseRulesToJson(&g_cachehitresprulactions); | |
2d4783a8 | 672 | auto selfAnsweredResponseRules = someResponseRulesToJson(&g_selfansweredresprulactions); |
4ace9fe8 | 673 | |
da5d4999 | 674 | string acl; |
675 | ||
676 | vector<string> vec; | |
a9c2e4ab | 677 | g_ACL.getLocal()->toStringVector(&vec); |
da5d4999 | 678 | |
679 | for(const auto& s : vec) { | |
680 | if(!acl.empty()) acl += ", "; | |
4ace9fe8 | 681 | acl+=s; |
da5d4999 | 682 | } |
683 | string localaddresses; | |
684 | for(const auto& loc : g_locals) { | |
685 | if(!localaddresses.empty()) localaddresses += ", "; | |
4c34246d | 686 | localaddresses += std::get<0>(loc).toStringWithPort(); |
da5d4999 | 687 | } |
80dbd7d2 | 688 | |
438a189b | 689 | Json my_json = Json::object { |
4ace9fe8 RG |
690 | { "daemon_type", "dnsdist" }, |
691 | { "version", VERSION}, | |
692 | { "servers", servers}, | |
693 | { "frontends", frontends }, | |
694 | { "pools", pools }, | |
695 | { "rules", rules}, | |
696 | { "response-rules", responseRules}, | |
697 | { "cache-hit-response-rules", cacheHitResponseRules}, | |
2d4783a8 | 698 | { "self-answered-response-rules", selfAnsweredResponseRules}, |
4ace9fe8 RG |
699 | { "acl", acl}, |
700 | { "local", localaddresses} | |
438a189b | 701 | }; |
5d1c98df | 702 | resp.headers["Content-Type"] = "application/json"; |
438a189b | 703 | resp.body=my_json.dump(); |
c8adc34c RG |
704 | } |
705 | else if(req.url.path=="/api/v1/servers/localhost/statistics") { | |
706 | handleCORS(req, resp); | |
707 | resp.status=200; | |
708 | ||
709 | Json::array doc; | |
710 | for(const auto& item : g_stats.entries) { | |
330dcb5c OM |
711 | if (item.first == "special-memory-usage") |
712 | continue; // Too expensive for get-all | |
713 | ||
c8adc34c RG |
714 | if(const auto& val = boost::get<DNSDistStats::stat_t*>(&item.second)) { |
715 | doc.push_back(Json::object { | |
716 | { "type", "StatisticItem" }, | |
717 | { "name", item.first }, | |
718 | { "value", (double)(*val)->load() } | |
719 | }); | |
720 | } | |
af619119 | 721 | else if (const auto& dval = boost::get<double*>(&item.second)) { |
c8adc34c RG |
722 | doc.push_back(Json::object { |
723 | { "type", "StatisticItem" }, | |
724 | { "name", item.first }, | |
af619119 | 725 | { "value", (**dval) } |
c8adc34c RG |
726 | }); |
727 | } | |
728 | else { | |
729 | doc.push_back(Json::object { | |
730 | { "type", "StatisticItem" }, | |
731 | { "name", item.first }, | |
778fe0a8 | 732 | { "value", (double)(*boost::get<DNSDistStats::statfunction_t>(&item.second))(item.first) } |
c8adc34c RG |
733 | }); |
734 | } | |
735 | } | |
736 | Json my_json = doc; | |
737 | resp.body=my_json.dump(); | |
738 | resp.headers["Content-Type"] = "application/json"; | |
739 | } | |
740 | else if(req.url.path=="/api/v1/servers/localhost/config") { | |
741 | handleCORS(req, resp); | |
742 | resp.status=200; | |
50bed881 | 743 | |
c8adc34c RG |
744 | Json::array doc; |
745 | typedef boost::variant<bool, double, std::string> configentry_t; | |
746 | std::vector<std::pair<std::string, configentry_t> > configEntries { | |
a9c2e4ab | 747 | { "acl", g_ACL.getLocal()->toString() }, |
0dffe9e3 | 748 | { "allow-empty-response", g_allowEmptyResponse }, |
c8adc34c RG |
749 | { "control-socket", g_serverControl.toStringWithPort() }, |
750 | { "ecs-override", g_ECSOverride }, | |
751 | { "ecs-source-prefix-v4", (double) g_ECSSourcePrefixV4 }, | |
752 | { "ecs-source-prefix-v6", (double) g_ECSSourcePrefixV6 }, | |
753 | { "fixup-case", g_fixupCase }, | |
754 | { "max-outstanding", (double) g_maxOutstanding }, | |
755 | { "server-policy", g_policy.getLocal()->name }, | |
756 | { "stale-cache-entries-ttl", (double) g_staleCacheEntriesTTL }, | |
757 | { "tcp-recv-timeout", (double) g_tcpRecvTimeout }, | |
758 | { "tcp-send-timeout", (double) g_tcpSendTimeout }, | |
759 | { "truncate-tc", g_truncateTC }, | |
760 | { "verbose", g_verbose }, | |
761 | { "verbose-health-checks", g_verboseHealthChecks } | |
762 | }; | |
763 | for(const auto& item : configEntries) { | |
af619119 | 764 | if (const auto& bval = boost::get<bool>(&item.second)) { |
c8adc34c RG |
765 | doc.push_back(Json::object { |
766 | { "type", "ConfigSetting" }, | |
767 | { "name", item.first }, | |
af619119 | 768 | { "value", *bval } |
c8adc34c RG |
769 | }); |
770 | } | |
af619119 | 771 | else if (const auto& sval = boost::get<string>(&item.second)) { |
c8adc34c RG |
772 | doc.push_back(Json::object { |
773 | { "type", "ConfigSetting" }, | |
774 | { "name", item.first }, | |
af619119 | 775 | { "value", *sval } |
c8adc34c RG |
776 | }); |
777 | } | |
af619119 | 778 | else if (const auto& dval = boost::get<double>(&item.second)) { |
c8adc34c RG |
779 | doc.push_back(Json::object { |
780 | { "type", "ConfigSetting" }, | |
781 | { "name", item.first }, | |
af619119 | 782 | { "value", *dval } |
c8adc34c RG |
783 | }); |
784 | } | |
785 | } | |
786 | Json my_json = doc; | |
787 | resp.body=my_json.dump(); | |
788 | resp.headers["Content-Type"] = "application/json"; | |
438a189b | 789 | } |
56d68fad RG |
790 | else if(req.url.path=="/api/v1/servers/localhost/config/allow-from") { |
791 | handleCORS(req, resp); | |
792 | ||
793 | resp.headers["Content-Type"] = "application/json"; | |
794 | resp.status=200; | |
795 | ||
796 | if (req.method == "PUT") { | |
797 | std::string err; | |
798 | Json doc = Json::parse(req.body, err); | |
799 | ||
800 | if (!doc.is_null()) { | |
801 | NetmaskGroup nmg; | |
802 | auto aclList = doc["value"]; | |
803 | if (aclList.is_array()) { | |
804 | ||
805 | for (auto value : aclList.array_items()) { | |
806 | try { | |
807 | nmg.addMask(value.string_value()); | |
808 | } catch (NetmaskException &e) { | |
809 | resp.status = 400; | |
810 | break; | |
811 | } | |
812 | } | |
813 | ||
814 | if (resp.status == 200) { | |
815 | infolog("Updating the ACL via the API to %s", nmg.toString()); | |
816 | g_ACL.setState(nmg); | |
817 | apiSaveACL(nmg); | |
818 | } | |
819 | } | |
820 | else { | |
821 | resp.status = 400; | |
822 | } | |
823 | } | |
824 | else { | |
825 | resp.status = 400; | |
826 | } | |
827 | } | |
828 | if (resp.status == 200) { | |
829 | Json::array acl; | |
830 | vector<string> vec; | |
a9c2e4ab | 831 | g_ACL.getLocal()->toStringVector(&vec); |
56d68fad RG |
832 | |
833 | for(const auto& s : vec) { | |
834 | acl.push_back(s); | |
835 | } | |
836 | ||
837 | Json::object obj{ | |
838 | { "type", "ConfigSetting" }, | |
839 | { "name", "allow-from" }, | |
840 | { "value", acl } | |
841 | }; | |
842 | Json my_json = obj; | |
843 | resp.body=my_json.dump(); | |
844 | } | |
845 | } | |
e3d76be2 RG |
846 | else if(!req.url.path.empty() && g_urlmap.count(req.url.path.c_str()+1)) { |
847 | resp.body.assign(g_urlmap[req.url.path.c_str()+1]); | |
fe20a1c1 | 848 | vector<string> parts; |
e3d76be2 | 849 | stringtok(parts, req.url.path, "."); |
fe20a1c1 | 850 | if(parts.back() == "html") |
c8c1a4fc | 851 | resp.headers["Content-Type"] = "text/html" + charset; |
fe20a1c1 | 852 | else if(parts.back() == "css") |
c8c1a4fc | 853 | resp.headers["Content-Type"] = "text/css" + charset; |
fe20a1c1 | 854 | else if(parts.back() == "js") |
c8c1a4fc | 855 | resp.headers["Content-Type"] = "application/javascript" + charset; |
864a6641 RG |
856 | else if(parts.back() == "png") |
857 | resp.headers["Content-Type"] = "image/png"; | |
438a189b | 858 | resp.status=200; |
859 | } | |
e3d76be2 | 860 | else if(req.url.path=="/") { |
438a189b | 861 | resp.body.assign(g_urlmap["index.html"]); |
c8c1a4fc | 862 | resp.headers["Content-Type"] = "text/html" + charset; |
438a189b | 863 | resp.status=200; |
864 | } | |
865 | else { | |
e3d76be2 | 866 | // cerr<<"404 for: "<<req.url.path<<endl; |
438a189b | 867 | resp.status=404; |
868 | } | |
50bed881 | 869 | |
438a189b | 870 | std::ostringstream ofs; |
871 | ofs << resp; | |
872 | string done; | |
873 | done=ofs.str(); | |
874 | writen2(sock, done.c_str(), done.size()); | |
50bed881 | 875 | |
56d68fad RG |
876 | close(sock); |
877 | sock = -1; | |
438a189b | 878 | } |
59e9504e RG |
879 | catch(const YaHTTP::ParseError& e) { |
880 | vinfolog("Webserver thread died with parse error exception while processing a request from %s: %s", remote.toStringWithPort(), e.what()); | |
56d68fad | 881 | close(sock); |
59e9504e | 882 | } |
34a6dd76 | 883 | catch(const std::exception& e) { |
59e9504e | 884 | errlog("Webserver thread died with exception while processing a request from %s: %s", remote.toStringWithPort(), e.what()); |
56d68fad | 885 | close(sock); |
34a6dd76 RG |
886 | } |
887 | catch(...) { | |
59e9504e | 888 | errlog("Webserver thread died with exception while processing a request from %s", remote.toStringWithPort()); |
56d68fad | 889 | close(sock); |
34a6dd76 | 890 | } |
50bed881 | 891 | } |
32c97b56 CHB |
892 | |
893 | void setWebserverAPIKey(const boost::optional<std::string> apiKey) | |
80dbd7d2 CHB |
894 | { |
895 | std::lock_guard<std::mutex> lock(g_webserverConfig.lock); | |
896 | ||
80dbd7d2 CHB |
897 | if (apiKey) { |
898 | g_webserverConfig.apiKey = *apiKey; | |
899 | } else { | |
900 | g_webserverConfig.apiKey.clear(); | |
901 | } | |
32c97b56 CHB |
902 | } |
903 | ||
904 | void setWebserverPassword(const std::string& password) | |
905 | { | |
906 | std::lock_guard<std::mutex> lock(g_webserverConfig.lock); | |
907 | ||
908 | g_webserverConfig.password = password; | |
909 | } | |
910 | ||
911 | void setWebserverCustomHeaders(const boost::optional<std::map<std::string, std::string> > customHeaders) | |
912 | { | |
913 | std::lock_guard<std::mutex> lock(g_webserverConfig.lock); | |
914 | ||
80dbd7d2 CHB |
915 | g_webserverConfig.customHeaders = customHeaders; |
916 | } | |
917 | ||
918 | void dnsdistWebserverThread(int sock, const ComboAddress& local) | |
50bed881 | 919 | { |
519f5484 | 920 | setThreadName("dnsdist/webserv"); |
d745cd6f | 921 | warnlog("Webserver launched on %s", local.toStringWithPort()); |
50bed881 | 922 | for(;;) { |
923 | try { | |
924 | ComboAddress remote(local); | |
925 | int fd = SAccept(sock, remote); | |
926 | vinfolog("Got connection from %s", remote.toStringWithPort()); | |
80dbd7d2 | 927 | std::thread t(connectionThread, fd, remote); |
50bed881 | 928 | t.detach(); |
929 | } | |
930 | catch(std::exception& e) { | |
931 | errlog("Had an error accepting new webserver connection: %s", e.what()); | |
932 | } | |
933 | } | |
934 | } |