1 /* Copyright © 2012 Brandon L Black <blblack@gmail.com>
2 *
3 * This file is part of gdnsd.
4 *
5 * gdnsd is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * gdnsd is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with gdnsd. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20 #include <config.h>
21 #include "statio.h"
22
23 #include "conf.h"
24 #include "socks.h"
25 #include "dnsio_udp.h"
26 #include "dnsio_tcp.h"
27 #include "dnspacket.h"
28
29 #include <gdnsd-prot/mon.h>
30 #include <gdnsd/alloc.h>
31 #include <gdnsd/log.h>
32
33 #include <unistd.h>
34 #include <fcntl.h>
35 #include <time.h>
36 #include <string.h>
37 #include <sys/uio.h>
38 #include <pthread.h>
39
40 // Macro to add an offset to a void* portably...
41 #define ADDVOID(_vstar,_offs) ((void*)(((char*)(_vstar)) + _offs))
42
43 typedef struct {
44 stats_uint_t udp_recvfail;
45 stats_uint_t udp_sendfail;
46 stats_uint_t udp_tc;
47 stats_uint_t udp_edns_big;
48 stats_uint_t udp_edns_tc;
49 stats_uint_t tcp_recvfail;
50 stats_uint_t tcp_sendfail;
51 stats_uint_t dns_noerror;
52 stats_uint_t dns_refused;
53 stats_uint_t dns_nxdomain;
54 stats_uint_t dns_notimp;
55 stats_uint_t dns_badvers;
56 stats_uint_t dns_formerr;
57 stats_uint_t dns_dropped;
58 stats_uint_t dns_v6;
59 stats_uint_t dns_edns;
60 stats_uint_t dns_edns_clientsub;
61 stats_uint_t udp_reqs;
62 stats_uint_t tcp_reqs;
63 } statio_t;
64
65 typedef enum {
66 READING_REQ = 0,
67 WRITING_RES,
68 READING_JUNK
69 } http_state_t;
70
71 // How many bytes of the request we really read()
72 #define HTTP_READ_BYTES 18
73
74 typedef struct {
75 dmn_anysin_t* asin;
76 char read_buffer[HTTP_READ_BYTES];
77 struct iovec outbufs[2];
78 char* hdr_buf;
79 char* data_buf;
80 ev_io* read_watcher;
81 ev_io* write_watcher;
82 ev_timer* timeout_watcher;
83 unsigned read_done;
84 http_state_t state;
85 } http_data_t;
86
87 // After reading the first 8 bytes of the request (all we care
88 // about), we send the response and then linger
89 // draining the remaining input in JUNK_SIZE chunks before
90 // the final SHUT_RDWR/close(). junk_buffer should be per-
91 // thread, but there's only one statio thread and it doesn't
92 // matter if multiple connections step all over each other writing
93 // to this.
94 #define JUNK_SIZE 4096
95 static char* junk_buffer;
96
97 // Various fixed sprintf strings for stats output formats
98
99 static const char log_dns[] =
100 "noerror:%" PRIuPTR " refused:%" PRIuPTR " nxdomain:%" PRIuPTR " notimp:%" PRIuPTR " badvers:%" PRIuPTR " formerr:%" PRIuPTR " dropped:%" PRIuPTR " v6:%" PRIuPTR " edns:%" PRIuPTR " edns_clientsub:%" PRIuPTR;
101 static const char log_udp[] =
102 "udp_reqs:%" PRIuPTR " udp_recvfail:%" PRIuPTR " udp_sendfail:%" PRIuPTR " udp_tc:%" PRIuPTR " udp_edns_big:%" PRIuPTR " udp_edns_tc:%" PRIuPTR;
103 static const char log_tcp[] =
104 "tcp_reqs:%" PRIuPTR " tcp_recvfail:%" PRIuPTR " tcp_sendfail:%" PRIuPTR;
105
106 static const char http_404_hdr[] =
107 "HTTP/1.0 404 Not Found\r\n"
108 "Server: " PACKAGE_NAME "/" PACKAGE_VERSION "\r\n"
109 "Content-type: text/plain; charset=utf-8\r\n"
110 "Content-length: 11\r\n\r\n";
111
112 static const char http_404_data[] = "Not Found\r\n";
113
114 static const char http_headers[] =
115 "HTTP/1.0 200 OK\r\n"
116 "Server: " PACKAGE_NAME "/" PACKAGE_VERSION "\r\n"
117 "Pragma: no-cache\r\n"
118 "Expires: Sat, 26 Jul 1997 05:00:00 GMT\r\n"
119 "Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0\r\n"
120 "Refresh: 60\r\n"
121 "Content-type: %s; charset=utf-8\r\n"
122 "Content-length: %u\r\n\r\n";
123
124 static const char csv_fixed[] =
125 "uptime\r\n"
126 "%" PRIu64 "\r\n"
127 "noerror,refused,nxdomain,notimp,badvers,formerr,dropped,v6,edns,edns_clientsub\r\n"
128 "%" PRIuPTR ",%" PRIuPTR ",%" PRIuPTR ",%" PRIuPTR ",%" PRIuPTR ",%" PRIuPTR ",%" PRIuPTR ",%" PRIuPTR ",%" PRIuPTR ",%" PRIuPTR "\r\n"
129 "udp_reqs,udp_recvfail,udp_sendfail,udp_tc,udp_edns_big,udp_edns_tc\r\n"
130 "%" PRIuPTR ",%" PRIuPTR ",%" PRIuPTR ",%" PRIuPTR ",%" PRIuPTR ",%" PRIuPTR "\r\n"
131 "tcp_reqs,tcp_recvfail,tcp_sendfail\r\n"
132 "%" PRIuPTR ",%" PRIuPTR ",%" PRIuPTR "\r\n";
133
134 static const char json_fixed[] =
135 "{\r\n"
136 "\t\"uptime\": %" PRIu64 ",\r\n"
137 "\t\"stats\": {\r\n"
138 "\t\t\"noerror\": %" PRIuPTR ",\r\n"
139 "\t\t\"refused\": %" PRIuPTR ",\r\n"
140 "\t\t\"nxdomain\": %" PRIuPTR ",\r\n"
141 "\t\t\"notimp\": %" PRIuPTR ",\r\n"
142 "\t\t\"badvers\": %" PRIuPTR ",\r\n"
143 "\t\t\"formerr\": %" PRIuPTR ",\r\n"
144 "\t\t\"dropped\": %" PRIuPTR ",\r\n"
145 "\t\t\"v6\": %" PRIuPTR ",\r\n"
146 "\t\t\"edns\": %" PRIuPTR ",\r\n"
147 "\t\t\"edns_clientsub\": %" PRIuPTR "\r\n"
148 "\t},\r\n"
149 "\t\"udp\": {\r\n"
150 "\t\t\"reqs\": %" PRIuPTR ",\r\n"
151 "\t\t\"recvfail\": %" PRIuPTR ",\r\n"
152 "\t\t\"sendfail\": %" PRIuPTR ",\r\n"
153 "\t\t\"tc\": %" PRIuPTR ",\r\n"
154 "\t\t\"edns_big\": %" PRIuPTR ",\r\n"
155 "\t\t\"edns_tc\": %" PRIuPTR "\r\n"
156 "\t},\r\n"
157 "\t\"tcp\": {\r\n"
158 "\t\t\"reqs\": %" PRIuPTR ",\r\n"
159 "\t\t\"recvfail\": %" PRIuPTR ",\r\n"
160 "\t\t\"sendfail\": %" PRIuPTR "\r\n"
161 "\t}";
162
163 static const char json_footer[] = "}\r\n";
164
165 static const char html_fixed[] =
166 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
167 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\r\n"
168 "<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" xml:lang=\"en\">\r\n"
169 "<head><title>" PACKAGE_NAME "</title><style type='text/css'>\r\n"
170 ".bold { font-weight: bold }\r\n"
171 ".big { font-size: 1.25em; }\r\n"
172 "table { border-width: 2px; border-style: ridge; margin: 0.25em; padding: 1px }\r\n"
173 "th,td { border-width: 2px; border-style: inset }\r\n"
174 "th { background: #CCF; font-weight: bold }\r\n"
175 "td.UP { background: #AFA }\r\n"
176 "td.DOWN { background: #FAA }\r\n"
177 "td.FORCE { background: #FA0 }\r\n"
178 "</style></head><body>\r\n"
179 "<h2>" PACKAGE_NAME "/" PACKAGE_VERSION "</h2>\r\n"
180 "<p class='big'><span class='bold'>Current Time:</span> %s UTC</p>\r\n"
181 "<p class='big'><span class='bold'>Uptime:</span> %s</p>\r\n"
182 "<p><span class='bold big'>Stats:</span></p><table>\r\n"
183 "<tr><th>noerror</th><th>refused</th><th>nxdomain</th><th>notimp</th><th>badvers</th><th>formerr</th><th>dropped</th><th>v6</th><th>edns</th><th>edns_clientsub</th></tr>\r\n"
184 "<tr><td>%" PRIuPTR "</td><td>%" PRIuPTR "</td><td>%" PRIuPTR "</td><td>%" PRIuPTR "</td><td>%" PRIuPTR "</td><td>%" PRIuPTR "</td><td>%" PRIuPTR "</td><td>%" PRIuPTR "</td><td>%" PRIuPTR "</td><td>%" PRIuPTR "</td></tr>\r\n"
185 "</table><table>\r\n"
186 "<tr><th>udp_reqs</th><th>udp_recvfail</th><th>udp_sendfail</th><th>udp_tc</th><th>udp_edns_big</th><th>udp_edns_tc</th></tr>\r\n"
187 "<tr><td>%" PRIuPTR "</td><td>%" PRIuPTR "</td><td>%" PRIuPTR "</td><td>%" PRIuPTR "</td><td>%" PRIuPTR "</td><td>%" PRIuPTR "</td></tr>\r\n"
188 "</table><table>\r\n"
189 "<tr><th>tcp_reqs</th><th>tcp_recvfail</th><th>tcp_sendfail</th></tr>\r\n"
190 "<tr><td>%" PRIuPTR "</td><td>%" PRIuPTR "</td><td>%" PRIuPTR "</td></tr>\r\n"
191 "</table>\r\n";
192
193 static const char html_footer[] =
194 "<p>For machine-readable CSV output, use <a href='/csv'>/csv</a></p>\r\n"
195 "<p>For machine-readable JSON output, use <a href='/json'>/json</a></p>\r\n"
196 "<p>For delta stats (since last such query), append the query param '?f=1'</p>\r\n"
197 "</body></html>\r\n";
198
199 static time_t start_time;
200 static time_t pop_statio_time = 0;
201 static ev_timer* log_watcher = NULL;
202 static ev_io** accept_watchers;
203 static struct ev_loop* statio_loop = NULL;
204 static int* lsocks;
205 static unsigned num_lsocks;
206 static bool* lsocks_bound;
207 static unsigned num_conn_watchers = 0;
208 static unsigned data_buffer_size = 0;
209 static unsigned hdr_buffer_size = 0;
210 static unsigned max_http_clients;
211 static unsigned http_timeout;
212 static unsigned num_dns_threads;
213
214 // This is memset to zero and re-accumulated for every output
215 static statio_t statio;
216
217 // Requests that specify ?f=1 (f for flush) only get the delta since
218 // the last ?f=1 (or daemon start, whichever is more recent).
219 // All ?f=1 clients share one state, so don't have two independent ones!
220 static statio_t flush_hist; // copy of raw stats accum from last f=1
221
222 // coordination for final stats output
223 static ev_async* final_stats_async = NULL;
224 static pthread_mutex_t final_stats_mutex = PTHREAD_MUTEX_INITIALIZER;
225 static pthread_cond_t final_stats_cond = PTHREAD_COND_INITIALIZER;
226 static bool final_stats_done = false;
227
accumulate_statio(unsigned threadnum)228 static void accumulate_statio(unsigned threadnum) {
229 dnspacket_stats_t* this_stats = dnspacket_stats[threadnum];
230 dmn_assert(this_stats);
231
232 const stats_uint_t l_noerror = stats_get(&this_stats->noerror);
233 const stats_uint_t l_refused = stats_get(&this_stats->refused);
234 const stats_uint_t l_nxdomain = stats_get(&this_stats->nxdomain);
235 const stats_uint_t l_notimp = stats_get(&this_stats->notimp);
236 const stats_uint_t l_badvers = stats_get(&this_stats->badvers);
237 const stats_uint_t l_formerr = stats_get(&this_stats->formerr);
238 const stats_uint_t l_dropped = stats_get(&this_stats->dropped);
239 statio.dns_noerror += l_noerror;
240 statio.dns_refused += l_refused;
241 statio.dns_nxdomain += l_nxdomain;
242 statio.dns_notimp += l_notimp;
243 statio.dns_badvers += l_badvers;
244 statio.dns_formerr += l_formerr;
245 statio.dns_dropped += l_dropped;
246
247 const stats_uint_t this_reqs = l_noerror + l_refused + l_nxdomain
248 + l_notimp + l_badvers + l_formerr + l_dropped;
249
250 if(this_stats->is_udp) {
251 statio.udp_reqs += this_reqs;
252 statio.udp_recvfail += stats_get(&this_stats->udp.recvfail);
253 statio.udp_sendfail += stats_get(&this_stats->udp.sendfail);
254 statio.udp_tc += stats_get(&this_stats->udp.tc);
255 statio.udp_edns_big += stats_get(&this_stats->udp.edns_big);
256 statio.udp_edns_tc += stats_get(&this_stats->udp.edns_tc);
257 }
258 else {
259 statio.tcp_reqs += this_reqs;
260 statio.tcp_recvfail += stats_get(&this_stats->tcp.recvfail);
261 statio.tcp_sendfail += stats_get(&this_stats->tcp.sendfail);
262 }
263
264 statio.dns_v6 += stats_get(&this_stats->v6);
265 statio.dns_edns += stats_get(&this_stats->edns);
266 statio.dns_edns_clientsub += stats_get(&this_stats->edns_clientsub);
267 }
268
populate_stats(const bool flush)269 static void populate_stats(const bool flush) {
270 const time_t now = time(NULL);
271 if(gcfg->realtime_stats || now > pop_statio_time) {
272 memset(&statio, 0, sizeof(statio));
273
274 for(unsigned i = 0; i < num_dns_threads; i++)
275 accumulate_statio(i);
276 pop_statio_time = now;
277 if(flush) {
278 // save past history to tmp_hist
279 statio_t tmp_hist;
280 memcpy(&tmp_hist, &flush_hist, sizeof(tmp_hist));
281 // save new values to flush_hist for next time
282 memcpy(&flush_hist, &statio, sizeof(flush_hist));
283 // subtract past history from current counters for output
284 statio.udp_recvfail -= tmp_hist.udp_recvfail;
285 statio.udp_sendfail -= tmp_hist.udp_sendfail;
286 statio.udp_tc -= tmp_hist.udp_tc;
287 statio.udp_edns_big -= tmp_hist.udp_edns_big;
288 statio.udp_edns_tc -= tmp_hist.udp_edns_tc;
289 statio.tcp_recvfail -= tmp_hist.tcp_recvfail;
290 statio.tcp_sendfail -= tmp_hist.tcp_sendfail;
291 statio.dns_noerror -= tmp_hist.dns_noerror;
292 statio.dns_refused -= tmp_hist.dns_refused;
293 statio.dns_nxdomain -= tmp_hist.dns_nxdomain;
294 statio.dns_notimp -= tmp_hist.dns_notimp;
295 statio.dns_badvers -= tmp_hist.dns_badvers;
296 statio.dns_formerr -= tmp_hist.dns_formerr;
297 statio.dns_dropped -= tmp_hist.dns_dropped;
298 statio.dns_v6 -= tmp_hist.dns_v6;
299 statio.dns_edns -= tmp_hist.dns_edns;
300 statio.dns_edns_clientsub -= tmp_hist.dns_edns_clientsub;
301 statio.udp_reqs -= tmp_hist.udp_reqs;
302 statio.tcp_reqs -= tmp_hist.tcp_reqs;
303 }
304 }
305 dmn_assert(pop_statio_time >= start_time);
306 }
307
get_uptime_u64(void)308 static uint64_t get_uptime_u64(void) {
309 dmn_assert(pop_statio_time >= start_time);
310 return (uint64_t)pop_statio_time - (uint64_t)start_time;
311 }
312
313 #define IVAL_BUFSZ 16
314 static char ival_buf[IVAL_BUFSZ];
fmt_uptime(void)315 static const char* fmt_uptime(void) {
316 dmn_assert(pop_statio_time >= start_time);
317 const uint64_t interval = get_uptime_u64();
318 const double dinterval = interval;
319
320 if(interval < 128) // 2m 8s
321 snprintf(ival_buf, IVAL_BUFSZ, "%u secs", (unsigned)interval);
322 else if(interval < 7680) // 2h 8m
323 snprintf(ival_buf, IVAL_BUFSZ, "~ %.1f mins", dinterval / 60.0);
324 else if(interval < 180000) // 50h
325 snprintf(ival_buf, IVAL_BUFSZ, "~ %.1f hours", dinterval / 3600.0);
326 else if(interval < 1209600) // 14d
327 snprintf(ival_buf, IVAL_BUFSZ, "~ %.1f days", dinterval / 86400.0);
328 else if(interval < 7776000) // 90d
329 snprintf(ival_buf, IVAL_BUFSZ, "~ %.1f weeks", dinterval / 604800.0);
330 else if(interval < 52560057) // ~20 months
331 snprintf(ival_buf, IVAL_BUFSZ, "~ %.1f months", dinterval / 2622240.0);
332 else
333 snprintf(ival_buf, IVAL_BUFSZ, "~ %.1f years", dinterval / 31536000.0);
334
335 return ival_buf;
336 }
337
statio_log_stats(void)338 static void statio_log_stats(void) {
339 populate_stats(false);
340 log_info(log_dns, statio.dns_noerror, statio.dns_refused, statio.dns_nxdomain, statio.dns_notimp, statio.dns_badvers, statio.dns_formerr, statio.dns_dropped, statio.dns_v6, statio.dns_edns, statio.dns_edns_clientsub);
341 log_info(log_udp, statio.udp_reqs, statio.udp_recvfail, statio.udp_sendfail, statio.udp_tc, statio.udp_edns_big, statio.udp_edns_tc);
342 log_info(log_tcp, statio.tcp_reqs, statio.tcp_recvfail, statio.tcp_sendfail);
343 }
344
345 F_NONNULL
statio_fill_outbuf_csv(struct iovec * outbufs,const bool flush)346 static void statio_fill_outbuf_csv(struct iovec* outbufs, const bool flush) {
347 populate_stats(flush);
348
349 int snp_rv = snprintf(outbufs[1].iov_base, data_buffer_size, csv_fixed, get_uptime_u64(), statio.dns_noerror, statio.dns_refused, statio.dns_nxdomain, statio.dns_notimp, statio.dns_badvers, statio.dns_formerr, statio.dns_dropped, statio.dns_v6, statio.dns_edns, statio.dns_edns_clientsub, statio.udp_reqs, statio.udp_recvfail, statio.udp_sendfail, statio.udp_tc, statio.udp_edns_big, statio.udp_edns_tc, statio.tcp_reqs, statio.tcp_recvfail, statio.tcp_sendfail);
350 dmn_assert(snp_rv > 0);
351 outbufs[1].iov_len = (unsigned)snp_rv;
352 outbufs[1].iov_len += gdnsd_mon_stats_out_csv(ADDVOID(outbufs[1].iov_base, outbufs[1].iov_len));
353
354 snp_rv = snprintf(outbufs[0].iov_base, hdr_buffer_size, http_headers, "text/plain", (unsigned)outbufs[1].iov_len);
355 dmn_assert(snp_rv > 0);
356 outbufs[0].iov_len = (unsigned)snp_rv;
357 }
358
359 F_NONNULL
statio_fill_outbuf_json(struct iovec * outbufs,const bool flush)360 static void statio_fill_outbuf_json(struct iovec* outbufs, const bool flush) {
361 populate_stats(flush);
362
363 dmn_assert(pop_statio_time >= start_time);
364
365 int snp_rv = snprintf(outbufs[1].iov_base, data_buffer_size, json_fixed, get_uptime_u64(), statio.dns_noerror, statio.dns_refused, statio.dns_nxdomain, statio.dns_notimp, statio.dns_badvers, statio.dns_formerr, statio.dns_dropped, statio.dns_v6, statio.dns_edns, statio.dns_edns_clientsub, statio.udp_reqs, statio.udp_recvfail, statio.udp_sendfail, statio.udp_tc, statio.udp_edns_big, statio.udp_edns_tc, statio.tcp_reqs, statio.tcp_recvfail, statio.tcp_sendfail);
366 dmn_assert(snp_rv > 0);
367 outbufs[1].iov_len = (unsigned)snp_rv;
368 outbufs[1].iov_len += gdnsd_mon_stats_out_json(ADDVOID(outbufs[1].iov_base, outbufs[1].iov_len));
369 memcpy(ADDVOID(outbufs[1].iov_base, outbufs[1].iov_len), json_footer, sizeof(json_footer) - 1U);
370 outbufs[1].iov_len += (sizeof(json_footer) - 1U);
371
372 snp_rv = snprintf(outbufs[0].iov_base, hdr_buffer_size, http_headers, "application/json", (unsigned)outbufs[1].iov_len);
373 dmn_assert(snp_rv > 0);
374 outbufs[0].iov_len = (unsigned)snp_rv;
375 }
376
377 F_NONNULL
statio_fill_outbuf_html(struct iovec * outbufs,const bool flush)378 static void statio_fill_outbuf_html(struct iovec* outbufs, const bool flush) {
379 populate_stats(flush);
380
381 struct tm now_tm;
382 if(!gmtime_r(&pop_statio_time, &now_tm))
383 log_fatal("gmtime_r() failed");
384
385 char now_char[64];
386 if(!strftime(now_char, 63, "%a %b %e %T %Y", &now_tm))
387 log_fatal("strftime() failed");
388
389 int snp_rv = snprintf(outbufs[1].iov_base, data_buffer_size, html_fixed, now_char, fmt_uptime(), statio.dns_noerror, statio.dns_refused, statio.dns_nxdomain, statio.dns_notimp, statio.dns_badvers, statio.dns_formerr, statio.dns_dropped, statio.dns_v6, statio.dns_edns, statio.dns_edns_clientsub, statio.udp_reqs, statio.udp_recvfail, statio.udp_sendfail, statio.udp_tc, statio.udp_edns_big, statio.udp_edns_tc, statio.tcp_reqs, statio.tcp_recvfail, statio.tcp_sendfail);
390 dmn_assert(snp_rv > 0);
391 outbufs[1].iov_len = (unsigned)snp_rv;
392 outbufs[1].iov_len += gdnsd_mon_stats_out_html(ADDVOID(outbufs[1].iov_base, outbufs[1].iov_len));
393 memcpy(ADDVOID(outbufs[1].iov_base, outbufs[1].iov_len), html_footer, sizeof(html_footer) - 1U);
394 outbufs[1].iov_len += (sizeof(html_footer) - 1U);
395
396 snp_rv = snprintf(outbufs[0].iov_base, hdr_buffer_size, http_headers, "application/xhtml+xml", (unsigned)outbufs[1].iov_len);
397 dmn_assert(snp_rv > 0);
398 outbufs[0].iov_len = (unsigned)snp_rv;
399 }
400
401 // Could be merged to a single iov, but this keeps things
402 // "simple", so that the write code always expects to start
403 // out with two iovecs to send.
404 F_NONNULL
statio_fill_outbuf_404(struct iovec * outbufs)405 static void statio_fill_outbuf_404(struct iovec* outbufs) {
406 outbufs[0].iov_len = sizeof(http_404_hdr) - 1;
407 outbufs[1].iov_len = sizeof(http_404_data) - 1;
408 // iov_base=const hack
409 memcpy(&outbufs[0].iov_base, &http_404_hdr[0], sizeof(void*));
410 memcpy(&outbufs[1].iov_base, &http_404_data[0], sizeof(void*));
411 }
412
413 F_NONNULL
log_watcher_cb(struct ev_loop * loop V_UNUSED,ev_timer * t V_UNUSED,int revents V_UNUSED)414 static void log_watcher_cb(struct ev_loop* loop V_UNUSED, ev_timer* t V_UNUSED, int revents V_UNUSED) {
415 statio_log_stats();
416 }
417
418 typedef void (*ob_cb_t)(struct iovec*, const bool);
419
420 static struct {
421 const char* match;
422 const ob_cb_t func;
423 } http_lookup[]
424 = {
425 // if one match is another's leading substring, the longer
426 // one must come first in the list!
427 { "GET /json", statio_fill_outbuf_json },
428 { "GET /csv", statio_fill_outbuf_csv },
429 { "GET /html", statio_fill_outbuf_html },
430 { "GET /", statio_fill_outbuf_html },
431 };
432
433 static const unsigned n_http_lookup = ARRAY_SIZE(http_lookup);
434
435 // This still doesn't even remotely come close to properly parsing
436 // the request, but it does handle a few basic things, and should
437 // be enough to work for these purposes for now. The "f=1" query
438 // param must be the first.
439 F_NONNULL
process_http_query(char * inbuffer,struct iovec * outbufs)440 static void process_http_query(char* inbuffer, struct iovec* outbufs) {
441 bool matched = false;
442 for(unsigned i = 0; i < n_http_lookup; i++) {
443 const unsigned msize = strlen(http_lookup[i].match);
444 dmn_assert(msize + 5 <= HTTP_READ_BYTES); // match + "/?f=1"
445 if(!memcmp(inbuffer, http_lookup[i].match, msize)) {
446 const char* trailptr = &inbuffer[msize];
447 // allow for trailing slash, e.g. "GET /csv/ HTTP/1.0"
448 if(*trailptr == '/')
449 trailptr++;
450 // require termination of the name with space, query, or frag
451 if(strchr(" ?#", *trailptr)) {
452 // check for f=1 only as first query arg
453 const bool flush = !memcmp(trailptr, "?f=1", 4);
454 http_lookup[i].func(outbufs, flush);
455 matched = true;
456 }
457 break;
458 }
459 }
460
461 if(!matched)
462 statio_fill_outbuf_404(outbufs);
463 }
464
465 F_NONNULL
cleanup_conn_watchers(struct ev_loop * loop,http_data_t * tdata)466 static void cleanup_conn_watchers(struct ev_loop* loop, http_data_t* tdata) {
467 shutdown(tdata->read_watcher->fd, SHUT_RDWR);
468 close(tdata->read_watcher->fd);
469 ev_timer_stop(loop, tdata->timeout_watcher);
470 ev_io_stop(loop, tdata->read_watcher);
471 ev_io_stop(loop, tdata->write_watcher);
472 free(tdata->data_buf);
473 free(tdata->hdr_buf);
474 free(tdata->timeout_watcher);
475 free(tdata->read_watcher);
476 free(tdata->write_watcher);
477 free(tdata->asin);
478
479 if((num_conn_watchers-- == max_http_clients))
480 for(unsigned i = 0; i < num_lsocks; i++)
481 ev_io_start(loop, accept_watchers[i]);
482
483 free(tdata);
484 }
485
486 F_NONNULL
timeout_cb(struct ev_loop * loop V_UNUSED,ev_timer * t,const int revents V_UNUSED)487 static void timeout_cb(struct ev_loop* loop V_UNUSED, ev_timer* t, const int revents V_UNUSED) {
488 http_data_t* tdata = t->data;
489 log_debug("HTTP connection timed out while %s %s",
490 tdata->state == READING_REQ
491 ? "reading from"
492 : tdata->state == WRITING_RES
493 ? "writing to"
494 : "lingering with",
495 dmn_logf_anysin(tdata->asin));
496
497 cleanup_conn_watchers(loop, tdata);
498 }
499
500 F_NONNULL
write_cb(struct ev_loop * loop,ev_io * io,const int revents V_UNUSED)501 static void write_cb(struct ev_loop* loop, ev_io* io, const int revents V_UNUSED) {
502 dmn_assert(revents == EV_WRITE);
503
504 http_data_t* tdata = io->data;
505 struct iovec* iovs = tdata->outbufs;
506
507 struct iovec* iovs_writev;
508 int iovcnt_writev;
509 if(iovs[0].iov_len) {
510 iovs_writev = &iovs[0];
511 iovcnt_writev = 2;
512 }
513 else {
514 iovs_writev = &iovs[1];
515 iovcnt_writev = 1;
516 }
517 const ssize_t write_rv = writev(io->fd, iovs_writev, iovcnt_writev);
518
519 if(unlikely(write_rv < 0)) {
520 if(!ERRNO_WOULDBLOCK && errno != EINTR) {
521 log_debug("HTTP writev() failed (%s), dropping response to %s", dmn_logf_errno(), dmn_logf_anysin(tdata->asin));
522 cleanup_conn_watchers(loop, tdata);
523 }
524 return;
525 }
526
527 size_t written = (size_t)write_rv;
528
529 if(iovs[0].iov_len) {
530 if(written >= iovs[0].iov_len) {
531 written -= iovs[0].iov_len;
532 iovs[0].iov_len = 0;
533 // fall through to processing 2nd buffer below
534 }
535 else {
536 iovs[0].iov_base = (char*)iovs[0].iov_base + written;
537 iovs[0].iov_len -= written;
538 return; // we'll send the rest of iovs[0]+iovs[1] on next EV_WRITE
539 }
540 }
541
542 if(written < iovs[1].iov_len) {
543 iovs[1].iov_base = (char*)iovs[1].iov_base + written;
544 iovs[1].iov_len -= written;
545 return; // we'll send the rest of iovs[1] on next EV_WRITE
546 }
547
548 dmn_assert(written == iovs[1].iov_len);
549 tdata->state = READING_JUNK;
550 ev_io_stop(loop, tdata->write_watcher);
551 ev_io_start(loop, tdata->read_watcher);
552 }
553
554 F_NONNULL
read_cb(struct ev_loop * loop,ev_io * io,const int revents V_UNUSED)555 static void read_cb(struct ev_loop* loop, ev_io* io, const int revents V_UNUSED) {
556 dmn_assert(revents == EV_READ);
557 http_data_t* tdata = io->data;
558
559 dmn_assert(tdata);
560 dmn_assert(tdata->state != WRITING_RES);
561
562 if(tdata->state == READING_JUNK) {
563 const ssize_t recv_rv = recv(io->fd, junk_buffer, JUNK_SIZE, 0);
564 if(unlikely(recv_rv < 0)) {
565 if(ERRNO_WOULDBLOCK || errno == EINTR)
566 return;
567 log_debug("HTTP recv() error (lingering) from %s: %s", dmn_logf_anysin(tdata->asin), dmn_logf_errno());
568 }
569 if(recv_rv < 1)
570 cleanup_conn_watchers(loop, tdata);
571 return;
572 }
573
574 dmn_assert(tdata->state == READING_REQ);
575 dmn_assert(tdata->read_done < HTTP_READ_BYTES);
576 char* destination = &tdata->read_buffer[tdata->read_done];
577 const size_t wanted = HTTP_READ_BYTES - tdata->read_done;
578 const ssize_t recv_rv = recv(io->fd, destination, wanted, 0);
579 if(unlikely(recv_rv < 0)) {
580 if(!ERRNO_WOULDBLOCK && errno != EINTR) {
581 log_debug("HTTP recv() error from %s: %s", dmn_logf_anysin(tdata->asin), dmn_logf_errno());
582 cleanup_conn_watchers(loop, tdata);
583 }
584 return;
585 }
586 const size_t recvlen = (size_t)recv_rv;
587 tdata->read_done += recvlen;
588 if(tdata->read_done < HTTP_READ_BYTES) return;
589
590 // We're relying on the OS to buffer the rest of the request while
591 // we write the response. After we're done writing we'll drain
592 // the rest of it for a proper lingering close.
593
594 process_http_query(tdata->read_buffer, tdata->outbufs);
595 tdata->state = WRITING_RES;
596 ev_io_stop(loop, tdata->read_watcher);
597 ev_io_start(loop, tdata->write_watcher);
598 }
599
600 F_NONNULL
accept_cb(struct ev_loop * loop,ev_io * io,int revents V_UNUSED)601 static void accept_cb(struct ev_loop* loop, ev_io* io, int revents V_UNUSED) {
602 dmn_assert(revents == EV_READ);
603
604 dmn_anysin_t* asin = xmalloc(sizeof(dmn_anysin_t));
605 asin->len = DMN_ANYSIN_MAXLEN;
606
607 const int sock = accept(io->fd, &asin->sa, &asin->len);
608
609 if(unlikely(sock < 0)) {
610 free(asin);
611 switch(errno) {
612 case EAGAIN:
613 #if EWOULDBLOCK != EAGAIN
614 case EWOULDBLOCK:
615 #endif
616 case EINTR:
617 break;
618 #ifdef ENONET
619 case ENONET:
620 #endif
621 case ENETDOWN:
622 #ifdef EPROTO
623 case EPROTO:
624 #endif
625 case EHOSTDOWN:
626 case EHOSTUNREACH:
627 case ENETUNREACH:
628 log_debug("HTTP: early tcp socket death: %s", dmn_logf_errno());
629 break;
630 default:
631 log_err("HTTP: accept() error: %s", dmn_logf_errno());
632 }
633 return;
634 }
635
636 log_debug("HTTP: Received connection from %s", dmn_logf_anysin(asin));
637
638 if(fcntl(sock, F_SETFL, (fcntl(sock, F_GETFL, 0)) | O_NONBLOCK) == -1) {
639 free(asin);
640 close(sock);
641 log_err("Failed to set O_NONBLOCK on inbound HTTP socket: %s", dmn_logf_errno());
642 return;
643 }
644
645 ev_io* read_watcher = xmalloc(sizeof(ev_io));
646 ev_io* write_watcher = xmalloc(sizeof(ev_io));
647 ev_timer* timeout_watcher = xmalloc(sizeof(ev_timer));
648
649 http_data_t* tdata = xcalloc(1, sizeof(http_data_t));
650 tdata->state = READING_REQ;
651 tdata->asin = asin;
652 tdata->read_watcher = read_watcher;
653 tdata->write_watcher = write_watcher;
654 tdata->timeout_watcher = timeout_watcher;
655
656 tdata->hdr_buf = tdata->outbufs[0].iov_base = xmalloc(hdr_buffer_size);
657 tdata->data_buf = tdata->outbufs[1].iov_base = xmalloc(data_buffer_size);
658
659 read_watcher->data = tdata;
660 write_watcher->data = tdata;
661 timeout_watcher->data = tdata;
662
663 ev_io_init(tdata->write_watcher, write_cb, sock, EV_WRITE);
664 ev_set_priority(tdata->write_watcher, 1);
665
666 ev_io_init(read_watcher, read_cb, sock, EV_READ);
667 ev_set_priority(read_watcher, 0);
668 ev_io_start(loop, read_watcher);
669
670 ev_timer_init(timeout_watcher, timeout_cb, http_timeout, 0);
671 ev_set_priority(timeout_watcher, -1);
672 ev_timer_start(loop, timeout_watcher);
673
674 if((++num_conn_watchers == max_http_clients)) {
675 log_warn("Stats HTTP connection limit reached");
676 for(unsigned i = 0; i < num_lsocks; i++)
677 ev_io_stop(loop, accept_watchers[i]);
678 }
679
680 #ifdef TCP_DEFER_ACCEPT
681 // Since we use DEFER_ACCEPT, the request is likely already
682 // queued and available at this point, so start read()-ing
683 // without going through the event loop
684 ev_invoke(loop, read_watcher, EV_READ);
685 #endif
686 }
687
statio_init(const socks_cfg_t * socks_cfg)688 void statio_init(const socks_cfg_t* socks_cfg) {
689 start_time = time(NULL);
690
691 // initial flush history
692 memset(&flush_hist, 0, sizeof(flush_hist));
693
694 // the junk buffer
695 junk_buffer = xmalloc(JUNK_SIZE);
696
697 // The largest our output sizes can possibly be:
698 hdr_buffer_size =
699 (sizeof(http_headers) - 1) // http_headers format string
700 + (21 - 2) // "application/xhtml+xml" - "%s"
701 + (10 - 2); // 32-bit len - "%u"
702
703 // stats counters are 32-bit on 32-bit machines, and 64 on 64
704 const unsigned stat_len = sizeof(stats_uint_t) == 8 ? 20 : 10;
705
706 // in the other cases, html is obviously-bigger, but if I have
707 // to count it out to know, may as well automated it...
708 const unsigned fixed = sizeof(html_fixed) > sizeof(json_fixed)
709 ? sizeof(html_fixed) - 1
710 : sizeof(json_fixed) - 1;
711
712 data_buffer_size =
713 fixed // html_fixed format string
714 + (63 - 2) // max strftime output - 2 for the original %s
715 + (IVAL_BUFSZ - 2) // max fmt_uptime output, again - 2 for %s
716 + (19 * (stat_len - strlen(PRIuPTR))) // 19 stats, up to 20 bytes long each
717 + gdnsd_mon_stats_get_max_len() // whatever mon.c tells us...
718 + (sizeof(html_footer) - 1); // html_footer fixed string
719
720 // double it, because it's not that big and this gives us a lot of headroom for
721 // having made any stupid mistakes in the max len calcuations :P
722 data_buffer_size <<= 1U;
723
724 // now set up the normal stuff, like libev event watchers
725 if(gcfg->log_stats) {
726 log_watcher = xmalloc(sizeof(ev_timer));
727 ev_timer_init(log_watcher, log_watcher_cb, gcfg->log_stats, gcfg->log_stats);
728 ev_set_priority(log_watcher, -2);
729 }
730
731 num_lsocks = socks_cfg->num_http_addrs;
732 max_http_clients = socks_cfg->max_http_clients;
733 http_timeout = socks_cfg->http_timeout;
734 num_dns_threads = socks_cfg->num_dns_threads;
735 lsocks = xmalloc(sizeof(int) * num_lsocks);
736 lsocks_bound = xcalloc(num_lsocks, sizeof(bool));
737 accept_watchers = xmalloc(sizeof(ev_io*) * num_lsocks);
738
739 for(unsigned i = 0; i < num_lsocks; i++) {
740 const dmn_anysin_t* asin = &socks_cfg->http_addrs[i];
741 lsocks[i] = tcp_listen_pre_setup(asin, socks_cfg->http_timeout);
742 }
743 }
744
statio_bind_socks(void)745 void statio_bind_socks(void) {
746 for(unsigned i = 0; i < num_lsocks; i++)
747 if(!lsocks_bound[i])
748 if(!socks_helper_bind("TCP stats", lsocks[i], &scfg->http_addrs[i], false))
749 lsocks_bound[i] = true;
750 }
751
statio_check_socks(const socks_cfg_t * socks_cfg,bool soft)752 bool statio_check_socks(const socks_cfg_t* socks_cfg, bool soft) {
753 unsigned rv = false;
754 for(unsigned i = 0; i < num_lsocks; i++)
755 if(!socks_sock_is_bound_to(lsocks[i], &socks_cfg->http_addrs[i]) && !soft)
756 log_fatal("Failed to bind() stats TCP socket to %s", dmn_logf_anysin(&socks_cfg->http_addrs[i]));
757 else
758 rv = true;
759 return rv;
760 }
761
762 // called within our thread/loop to do the final stats output
763 F_NONNULL
final_stats_cb(struct ev_loop * loop,ev_async * w V_UNUSED,int revents V_UNUSED)764 static void final_stats_cb(struct ev_loop* loop, ev_async* w V_UNUSED, int revents V_UNUSED) {
765 // stop further periodic log output and do final output
766 if(log_watcher)
767 ev_timer_stop(loop, log_watcher);
768 statio_log_stats();
769
770 // let mainthread return from statio_final_stats_wait()
771 pthread_mutex_lock(&final_stats_mutex);
772 final_stats_done = true;
773 pthread_cond_signal(&final_stats_cond);
774 pthread_mutex_unlock(&final_stats_mutex);
775 }
776
777 // called from main thread to feed ev_async for final stats
statio_final_stats(void)778 void statio_final_stats(void) {
779 dmn_assert(statio_loop); dmn_assert(final_stats_async);
780 ev_async_send(statio_loop, final_stats_async);
781 }
782
783 // called from main thread to wait on final_stats_cb() completion
statio_final_stats_wait(void)784 void statio_final_stats_wait(void) {
785 pthread_mutex_lock(&final_stats_mutex);
786 while(!final_stats_done)
787 pthread_cond_wait(&final_stats_cond, &final_stats_mutex);
788 pthread_mutex_unlock(&final_stats_mutex);
789 }
790
statio_start(struct ev_loop * statio_loop_arg,const socks_cfg_t * socks_cfg)791 void statio_start(struct ev_loop* statio_loop_arg, const socks_cfg_t* socks_cfg) {
792 statio_loop = statio_loop_arg;
793 if(log_watcher)
794 ev_timer_start(statio_loop, log_watcher);
795
796 final_stats_async = xmalloc(sizeof(ev_async));
797 ev_async_init(final_stats_async, final_stats_cb);
798 ev_async_start(statio_loop, final_stats_async);
799
800 for(unsigned i = 0; i < num_lsocks; i++) {
801 if(listen(lsocks[i], 128) == -1)
802 log_fatal("Failed to listen(s, %i) on stats TCP socket %s: %s", 128, dmn_logf_anysin(&socks_cfg->http_addrs[i]), dmn_logf_errno());
803 accept_watchers[i] = xmalloc(sizeof(ev_io));
804 ev_io_init(accept_watchers[i], accept_cb, lsocks[i], EV_READ);
805 ev_set_priority(accept_watchers[i], -2);
806 ev_io_start(statio_loop, accept_watchers[i]);
807 }
808 }
809