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