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 
22 #define GDNSD_PLUGIN_NAME http_status
23 #include <gdnsd/plugin.h>
24 
25 #include <stdbool.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <netinet/in_systm.h>
29 #include <netinet/ip.h>
30 #include <netinet/tcp.h>
31 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <netdb.h>
34 #include <fcntl.h>
35 
36 typedef struct {
37     const char* name;
38     unsigned* ok_codes;
39     char* req_data;
40     unsigned req_data_len;
41     unsigned num_ok_codes;
42     unsigned port;
43     unsigned timeout;
44     unsigned interval;
45 } http_svc_t;
46 
47 typedef enum {
48     HTTP_STATE_WAITING = 0,   // waiting for interval to expire before next send
49     HTTP_STATE_WRITING,   // trying to send the request
50     HTTP_STATE_READING  // trying to receive the response
51 } http_state_t;
52 
53 typedef struct {
54     const char* desc;
55     http_svc_t* http_svc;
56     ev_io* read_watcher;
57     ev_io* write_watcher;
58     ev_timer* timeout_watcher;
59     ev_timer* interval_watcher;
60     unsigned idx;
61     dmn_anysin_t addr;
62     char res_buf[14];
63     int sock;
64     http_state_t hstate;
65     unsigned done;
66     bool already_connected;
67 } http_events_t;
68 
69 static unsigned num_http_svcs = 0;
70 static unsigned num_mons = 0;
71 static http_svc_t* service_types = NULL;
72 static http_events_t** mons = NULL;
73 
74 F_NONNULL
mon_interval_cb(struct ev_loop * loop,struct ev_timer * t,const int revents V_UNUSED)75 static void mon_interval_cb(struct ev_loop* loop, struct ev_timer* t, const int revents V_UNUSED) {
76     dmn_assert(revents == EV_TIMER);
77 
78     http_events_t* md = t->data;
79 
80     dmn_assert(md);
81 
82     if(md->hstate != HTTP_STATE_WAITING) {
83         log_warn("plugin_http_status: A monitoring request attempt seems to have "
84             "lasted longer than the monitoring interval. "
85             "Skipping this round of monitoring - are you "
86             "starved for CPU time?");
87         return;
88     }
89 
90     dmn_assert(md->sock == -1);
91     dmn_assert(!ev_is_active(md->read_watcher));
92     dmn_assert(!ev_is_active(md->write_watcher));
93     dmn_assert(!ev_is_active(md->timeout_watcher) && !ev_is_pending(md->timeout_watcher));
94 
95     log_debug("plugin_http_status: Starting state poll of %s", md->desc);
96 
97     do {
98         const bool isv6 = md->addr.sa.sa_family == AF_INET6;
99 
100         const int sock = socket(isv6 ? PF_INET6 : PF_INET, SOCK_STREAM, gdnsd_getproto_tcp());
101         if(sock < 0) {
102             log_err("plugin_http_status: Failed to create monitoring socket: %s", dmn_logf_errno());
103             break;
104         }
105 
106         if(fcntl(sock, F_SETFL, (fcntl(sock, F_GETFL, 0)) | O_NONBLOCK) == -1) {
107             log_err("plugin_http_status: Failed to set O_NONBLOCK on monitoring socket: %s", dmn_logf_errno());
108             close(sock);
109             break;
110         }
111 
112         md->already_connected = true;
113         if(likely(connect(sock, &md->addr.sa, md->addr.len) == -1)) {
114             if(likely(errno == EINPROGRESS)) { md->already_connected = false; }
115             else {
116                 switch(errno) {
117                     case EPIPE:
118                     case ECONNREFUSED:
119                     case ETIMEDOUT:
120                     case EHOSTUNREACH:
121                     case EHOSTDOWN:
122                     case ENETUNREACH:
123                         break;
124                     default:
125                         log_err("plugin_http_status: Failed to connect() monitoring socket to remote server, possible local problem: %s", dmn_logf_errno());
126                 }
127                 close(sock);
128                 break;
129             }
130         }
131 
132         md->sock = sock;
133         md->hstate = HTTP_STATE_WRITING;
134         md->done = 0;
135         ev_io_set(md->write_watcher, sock, EV_WRITE);
136         ev_io_start(loop, md->write_watcher);
137         ev_timer_set(md->timeout_watcher, md->http_svc->timeout, 0);
138         ev_timer_start(loop, md->timeout_watcher);
139         return;
140     } while(0);
141 
142     // This is only reachable via "break"'s above, which indicate an immediate failure
143     log_debug("plugin_http_status: State poll of %s failed very quickly", md->desc);
144     md->hstate = HTTP_STATE_WAITING;
145     gdnsd_mon_state_updater(md->idx, false);
146 }
147 
148 F_NONNULL
mon_write_cb(struct ev_loop * loop,struct ev_io * io,const int revents V_UNUSED)149 static void mon_write_cb(struct ev_loop* loop, struct ev_io* io, const int revents V_UNUSED) {
150     dmn_assert(revents == EV_WRITE);
151 
152     http_events_t* md = io->data;
153 
154     dmn_assert(md);
155     dmn_assert(md->hstate == HTTP_STATE_WRITING);
156     dmn_assert(!ev_is_active(md->read_watcher));
157     dmn_assert(ev_is_active(md->write_watcher));
158     dmn_assert(ev_is_active(md->timeout_watcher) || ev_is_pending(md->timeout_watcher));
159     dmn_assert(md->sock > -1);
160 
161     int sock = md->sock;
162     if(likely(!md->already_connected)) {
163         // nonblocking connect() just finished, need to check status
164         int so_error = 0;
165         socklen_t so_error_len = sizeof(so_error);
166         (void)getsockopt(sock, SOL_SOCKET, SO_ERROR, &so_error, &so_error_len);
167         if(unlikely(so_error)) {
168             switch(so_error) {
169                 case EPIPE:
170                 case ECONNREFUSED:
171                 case ETIMEDOUT:
172                 case EHOSTUNREACH:
173                 case EHOSTDOWN:
174                 case ENETUNREACH:
175                     break;
176                 default:
177                     log_err("plugin_http_status: Failed to connect() monitoring socket to remote server, possible local problem: %s", dmn_logf_strerror(so_error));
178             }
179 
180             log_debug("plugin_http_status: State poll of %s failed quickly: %s", md->desc, dmn_logf_strerror(so_error));
181             close(sock); md->sock = -1;
182             ev_io_stop(loop, md->write_watcher);
183             ev_timer_stop(loop, md->timeout_watcher);
184             md->hstate = HTTP_STATE_WAITING;
185             gdnsd_mon_state_updater(md->idx, false);
186             return;
187         }
188         md->already_connected = true;
189     }
190 
191     dmn_assert(md->done < md->http_svc->req_data_len);
192     const unsigned to_send = md->http_svc->req_data_len - md->done;
193     dmn_assert(to_send > 0);
194 
195     const ssize_t send_rv = send(sock, md->http_svc->req_data + md->done, to_send, 0);
196     if(unlikely(send_rv < 0)) {
197         switch(errno) {
198             case EAGAIN:
199 #if EWOULDBLOCK != EAGAIN
200             case EWOULDBLOCK:
201 #endif
202             case EINTR:
203                 return;
204             case ENOTCONN:
205             case ECONNRESET:
206             case ETIMEDOUT:
207             case EHOSTUNREACH:
208             case ENETUNREACH:
209             case EPIPE:
210                 break;
211             default:
212                 log_err("plugin_http_status: send() to monitoring socket failed, possible local problem: %s", dmn_logf_errno());
213         }
214         shutdown(sock, SHUT_RDWR);
215         close(sock);
216         md->sock = -1;
217         ev_io_stop(loop, md->write_watcher);
218         ev_timer_stop(loop, md->timeout_watcher);
219         md->hstate = HTTP_STATE_WAITING;
220         gdnsd_mon_state_updater(md->idx, false);
221         return;
222     }
223 
224     const size_t sent = (size_t)send_rv;
225     dmn_assert(sent <= to_send);
226 
227     if(unlikely(sent != to_send)) {
228         md->done += sent;
229         return;
230     }
231 
232     md->done = 0;
233     md->hstate = HTTP_STATE_READING;
234     ev_io_stop(loop, md->write_watcher);
235     ev_io_set(md->read_watcher, sock, EV_READ);
236     ev_io_start(loop, md->read_watcher);
237 }
238 
239 F_NONNULL
mon_read_cb(struct ev_loop * loop,struct ev_io * io,const int revents V_UNUSED)240 static void mon_read_cb(struct ev_loop* loop, struct ev_io* io, const int revents V_UNUSED) {
241     dmn_assert(revents == EV_READ);
242 
243     http_events_t* md = io->data;
244 
245     dmn_assert(md);
246     dmn_assert(md->hstate == HTTP_STATE_READING);
247     dmn_assert(ev_is_active(md->read_watcher));
248     dmn_assert(!ev_is_active(md->write_watcher));
249     dmn_assert(md->sock > -1);
250 
251     bool final_status = false;
252     const unsigned to_recv = 13U - md->done;
253     const ssize_t recv_rv = recv(md->sock, md->res_buf + md->done, to_recv, 0);
254     if(unlikely(recv_rv < 0)) {
255         switch(errno) {
256             case EAGAIN:
257 #if EWOULDBLOCK != EAGAIN
258             case EWOULDBLOCK:
259 #endif
260             case EINTR:
261                 return;
262             case ETIMEDOUT:
263             case ENOTCONN:
264             case ECONNRESET:
265             case EPIPE:
266                 break;
267             default:
268                 log_err("plugin_http_status: read() from monitoring socket failed, possible local problem: %s", dmn_logf_errno());
269         }
270     }
271     else {
272         const size_t recvd = (size_t)recv_rv;
273         if(recvd < to_recv) {
274             md->done += recvd;
275             return;
276         }
277         md->res_buf[13] = '\0';
278         char code_str[4] = { 0 };
279         if(1 == sscanf(md->res_buf, "HTTP/1.%*1[01]%*1[ ]%3c%*1[ ]", code_str)) {
280             unsigned lcode = (unsigned)strtoul(code_str, NULL, 10);
281             for(unsigned i = 0; i < md->http_svc->num_ok_codes; i++) {
282                 if(lcode == md->http_svc->ok_codes[i]) {
283                     final_status = true;
284                     break;
285                 }
286             }
287         }
288     }
289 
290     // I don't believe we actually need to read the rest of the response before
291     //   shutdown/close in order to avoid bad TCP behavior, but I could be wrong.
292 
293     log_debug("plugin_http_status: State poll of %s %s", md->desc, final_status ? "succeeded" : "failed");
294     shutdown(md->sock, SHUT_RDWR);
295     close(md->sock);
296     md->sock = -1;
297     ev_io_stop(loop, md->read_watcher);
298     ev_timer_stop(loop, md->timeout_watcher);
299     md->hstate = HTTP_STATE_WAITING;
300     gdnsd_mon_state_updater(md->idx, final_status);
301 }
302 
303 F_NONNULL
mon_timeout_cb(struct ev_loop * loop,struct ev_timer * t,const int revents V_UNUSED)304 static void mon_timeout_cb(struct ev_loop* loop, struct ev_timer* t, const int revents V_UNUSED) {
305     dmn_assert(revents == EV_TIMER);
306 
307     http_events_t* md = t->data;
308 
309     dmn_assert(md);
310     dmn_assert(md->sock != -1);
311     dmn_assert(
312         (md->hstate == HTTP_STATE_READING && ev_is_active(md->read_watcher))
313      || (md->hstate == HTTP_STATE_WRITING && ev_is_active(md->write_watcher))
314     );
315 
316     log_debug("plugin_http_status: State poll of %s timed out", md->desc);
317     if(md->hstate == HTTP_STATE_READING) ev_io_stop(loop, md->read_watcher);
318     else if(md->hstate == HTTP_STATE_WRITING) ev_io_stop(loop, md->write_watcher);
319     shutdown(md->sock, SHUT_RDWR);
320     close(md->sock);
321     md->sock = -1;
322     md->hstate = HTTP_STATE_WAITING;
323     gdnsd_mon_state_updater(md->idx, false);
324 }
325 
326 #define SVC_OPT_UINT(_hash, _typnam, _loc, _min, _max) \
327     do { \
328         vscf_data_t* _data = vscf_hash_get_data_byconstkey(_hash, #_loc, true); \
329         if(_data) { \
330             unsigned long _val; \
331             if(!vscf_is_simple(_data) \
332             || !vscf_simple_get_as_ulong(_data, &_val)) \
333                 log_fatal("plugin_http_status: Service type '%s': option '%s': Value must be a positive integer", _typnam, #_loc); \
334             if(_val < _min || _val > _max) \
335                 log_fatal("plugin_http_status: Service type '%s': option '%s': Value out of range (%lu, %lu)", _typnam, #_loc, _min, _max); \
336             _loc = (unsigned) _val; \
337         } \
338     } while(0)
339 
340 #define SVC_OPT_STR(_hash, _typnam, _loc) \
341     do { \
342         vscf_data_t* _data = vscf_hash_get_data_byconstkey(_hash, #_loc, true); \
343         if(_data) { \
344             if(!vscf_is_simple(_data)) \
345                 log_fatal("plugin_http_status: Service type '%s': option %s: Wrong type (should be string)", _typnam, #_loc); \
346             _loc = vscf_simple_get_data(_data); \
347         } \
348     } while(0)
349 
350 // _LEN sizes below are without trailing NUL, and without
351 //   and printf templates (%s) either.
352 
353 static const char REQ_TMPL[] = "GET %s HTTP/1.0\r\nUser-Agent: gdnsd-monitor\r\n\r\n";
354 static const unsigned REQ_TMPL_LEN = sizeof(REQ_TMPL) - 2 - 1;
355 
356 static const char REQ_TMPL_VHOST[] = "GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: gdnsd-monitor\r\n\r\n";
357 static const unsigned REQ_TMPL_VHOST_LEN = sizeof(REQ_TMPL_VHOST) - 2 - 2 - 1;
358 
359 F_NONNULLX(1, 2)
make_req_data(http_svc_t * s,const char * url_path,const char * vhost)360 static void make_req_data(http_svc_t* s, const char* url_path, const char* vhost) {
361     const unsigned url_len = strlen(url_path);
362     if(vhost) {
363         s->req_data_len = REQ_TMPL_VHOST_LEN + url_len + strlen(vhost);
364         s->req_data = xmalloc(s->req_data_len + 1);
365         snprintf(s->req_data, s->req_data_len + 1, REQ_TMPL_VHOST, url_path, vhost);
366     }
367     else {
368         s->req_data_len = REQ_TMPL_LEN + url_len;
369         s->req_data = xmalloc(s->req_data_len + 1);
370         snprintf(s->req_data, s->req_data_len + 1, REQ_TMPL, url_path);
371     }
372 }
373 
plugin_http_status_add_svctype(const char * name,vscf_data_t * svc_cfg,const unsigned interval,const unsigned timeout)374 void plugin_http_status_add_svctype(const char* name, vscf_data_t* svc_cfg, const unsigned interval, const unsigned timeout) {
375     // defaults
376     const char* url_path = "/";
377     const char* vhost = NULL;
378     unsigned port = 80;
379 
380     service_types = xrealloc(service_types, (num_http_svcs + 1) * sizeof(http_svc_t));
381     http_svc_t* this_svc = &service_types[num_http_svcs++];
382 
383     this_svc->name = strdup(name);
384     this_svc->num_ok_codes = 0;
385     this_svc->ok_codes = NULL;
386     bool ok_codes_set = false;
387 
388     SVC_OPT_STR(svc_cfg, name, url_path);
389     SVC_OPT_STR(svc_cfg, name, vhost);
390     SVC_OPT_UINT(svc_cfg, name, port, 1LU, 65534LU);
391     vscf_data_t* ok_codes_cfg = vscf_hash_get_data_byconstkey(svc_cfg, "ok_codes", true);
392     if(ok_codes_cfg) {
393         ok_codes_set = true;
394         this_svc->num_ok_codes = vscf_array_get_len(ok_codes_cfg);
395         this_svc->ok_codes = xmalloc(sizeof(unsigned) * this_svc->num_ok_codes);
396         for(unsigned i = 0; i < this_svc->num_ok_codes; i++) {
397             vscf_data_t* code_cfg = vscf_array_get_data(ok_codes_cfg, i);
398             unsigned long tmpcode;
399             if(!vscf_simple_get_as_ulong(code_cfg, &tmpcode))
400                 log_fatal("plugin_http_status: service type '%s': illegal ok_codes value '%s', must be numeric http status code (100-999)", this_svc->name, vscf_simple_get_data(code_cfg));
401             if(tmpcode < 100LU || tmpcode > 999LU)
402                 log_fatal("plugin_http_status: service type '%s': illegal ok_codes value '%lu', must be numeric http status code (100-999)", this_svc->name, tmpcode);
403             this_svc->ok_codes[i] = (unsigned)tmpcode;
404         }
405     }
406 
407     // default the ok_codes array to [ 200 ]
408     if(!ok_codes_set) {
409         this_svc->num_ok_codes = 1;
410         this_svc->ok_codes = xmalloc(sizeof(unsigned));
411         this_svc->ok_codes[0] = 200LU;
412     }
413 
414     make_req_data(this_svc, url_path, vhost);
415     this_svc->port = port;
416     this_svc->timeout = timeout;
417     this_svc->interval = interval;
418 }
419 
plugin_http_status_add_mon_addr(const char * desc,const char * svc_name,const char * cname V_UNUSED,const dmn_anysin_t * addr,const unsigned idx)420 void plugin_http_status_add_mon_addr(const char* desc, const char* svc_name, const char* cname V_UNUSED, const dmn_anysin_t* addr, const unsigned idx) {
421     http_events_t* this_mon = xcalloc(1, sizeof(http_events_t));
422     this_mon->desc = strdup(desc);
423     this_mon->idx = idx;
424 
425     for(unsigned i = 0; i < num_http_svcs; i++) {
426         if(!strcmp(service_types[i].name, svc_name)) {
427             this_mon->http_svc = &service_types[i];
428             break;
429         }
430     }
431 
432     dmn_assert(this_mon->http_svc);
433 
434     memcpy(&this_mon->addr, addr, sizeof(dmn_anysin_t));
435     if(this_mon->addr.sa.sa_family == AF_INET) {
436         this_mon->addr.sin.sin_port = htons(this_mon->http_svc->port);
437     }
438     else {
439         dmn_assert(this_mon->addr.sa.sa_family == AF_INET6);
440         this_mon->addr.sin6.sin6_port = htons(this_mon->http_svc->port);
441     }
442 
443     this_mon->hstate = HTTP_STATE_WAITING;
444     this_mon->sock = -1;
445 
446     this_mon->read_watcher = xmalloc(sizeof(ev_io));
447     ev_io_init(this_mon->read_watcher, &mon_read_cb, -1, 0);
448     this_mon->read_watcher->data = this_mon;
449 
450     this_mon->write_watcher = xmalloc(sizeof(ev_io));
451     ev_io_init(this_mon->write_watcher, &mon_write_cb, -1, 0);
452     this_mon->write_watcher->data = this_mon;
453 
454     this_mon->timeout_watcher = xmalloc(sizeof(ev_timer));
455     ev_timer_init(this_mon->timeout_watcher, &mon_timeout_cb, 0, 0);
456     this_mon->timeout_watcher->data = this_mon;
457 
458     this_mon->interval_watcher = xmalloc(sizeof(ev_timer));
459     ev_timer_init(this_mon->interval_watcher, &mon_interval_cb, 0, 0);
460     this_mon->interval_watcher->data = this_mon;
461 
462     mons = xrealloc(mons, sizeof(http_events_t*) * (num_mons + 1));
463     mons[num_mons++] = this_mon;
464 }
465 
plugin_http_status_init_monitors(struct ev_loop * mon_loop)466 void plugin_http_status_init_monitors(struct ev_loop* mon_loop) {
467     for(unsigned i = 0; i < num_mons; i++) {
468         ev_timer* ival_watcher = mons[i]->interval_watcher;
469         dmn_assert(mons[i]->sock == -1);
470         ev_timer_set(ival_watcher, 0, 0);
471         ev_timer_start(mon_loop, ival_watcher);
472     }
473 }
474 
plugin_http_status_start_monitors(struct ev_loop * mon_loop)475 void plugin_http_status_start_monitors(struct ev_loop* mon_loop) {
476     for(unsigned i = 0; i < num_mons; i++) {
477         http_events_t* mon = mons[i];
478         dmn_assert(mon->sock == -1);
479         const unsigned ival = mon->http_svc->interval;
480         const double stagger = (((double)i) / ((double)num_mons)) * ((double)ival);
481         ev_timer* ival_watcher = mon->interval_watcher;
482         ev_timer_set(ival_watcher, stagger, ival);
483         ev_timer_start(mon_loop, ival_watcher);
484     }
485 }
486