F_NONNULL static void mon_read_cb(struct ev_loop* loop, struct ev_io* io, const int revents V_UNUSED) { dmn_assert(loop); dmn_assert(io); dmn_assert(revents == EV_READ); http_events_t* md = (http_events_t*)io->data; dmn_assert(md); dmn_assert(md->hstate == HTTP_STATE_READING); dmn_assert(ev_is_active(md->read_watcher)); dmn_assert(!ev_is_active(md->write_watcher)); dmn_assert(md->sock > -1); bool final_status = false; const int to_recv = 13 - md->done; const int recvd = recv(md->sock, md->res_buf + md->done, to_recv, 0); if(unlikely(recvd == -1)) { switch(errno) { case EAGAIN: case EINTR: return; case ETIMEDOUT: case ENOTCONN: case ECONNRESET: case EPIPE: break; default: log_err("plugin_http_status: read() from monitoring socket failed, possible local problem: %s", logf_errno()); } } else if(recvd < to_recv) { md->done += recvd; return; } else { md->res_buf[13] = '\0'; char code_str[4] = { 0 }; if(1 == sscanf(md->res_buf, "HTTP/1.%*1[01]%*1[ ]%3c%*1[ ]", code_str)) { unsigned long lcode = strtoul(code_str, NULL, 10); for(unsigned i = 0; i < md->http_svc->num_ok_codes; i++) { if(lcode == md->http_svc->ok_codes[i]) { final_status = true; break; } } } } // I don't believe we actually need to read the rest of the response before // shutdown/close in order to avoid bad TCP behavior, but I could be wrong. log_debug("plugin_http_status: State poll of %s %s", md->smgr->desc, final_status ? "succeeded" : "failed"); shutdown(md->sock, SHUT_RDWR); close(md->sock); md->sock = -1; ev_io_stop(loop, md->read_watcher); ev_timer_stop(loop, md->timeout_watcher); md->hstate = HTTP_STATE_WAITING; gdnsd_mon_state_updater(md->smgr, final_status); }
F_NONNULL static void mon_connect_cb(struct ev_loop* loop, struct ev_io* io, const int revents V_UNUSED) { dmn_assert(loop); dmn_assert(io); dmn_assert(revents == EV_WRITE); tcp_events_t* md = io->data; dmn_assert(md); dmn_assert(md->tcp_state == TCP_STATE_CONNECTING); dmn_assert(ev_is_active(md->connect_watcher)); dmn_assert(ev_is_active(md->timeout_watcher) || ev_is_pending(md->timeout_watcher)); dmn_assert(md->sock > -1); // nonblocking connect() just finished, need to check status bool success = false; int sock = md->sock; int so_error = 0; unsigned so_error_len = sizeof(so_error); (void)getsockopt(sock, SOL_SOCKET, SO_ERROR, &so_error, &so_error_len); if(unlikely(so_error)) { switch(so_error) { case EPIPE: case ECONNREFUSED: case ETIMEDOUT: case EHOSTUNREACH: case EHOSTDOWN: case ENETUNREACH: log_debug("plugin_tcp_connect: State poll of %s failed quickly: %s", md->desc, dmn_logf_strerror(so_error)); break; default: log_err("plugin_tcp_connect: Failed to connect() monitoring socket to remote server, possible local problem: %s", dmn_logf_strerror(so_error)); } } else { success = true; } shutdown(sock, SHUT_RDWR); close(sock); md->sock = -1; ev_io_stop(loop, md->connect_watcher); ev_timer_stop(loop, md->timeout_watcher); md->tcp_state = TCP_STATE_WAITING; gdnsd_mon_state_updater(md->idx, success); }
F_NONNULL static void mon_timeout_cb(struct ev_loop* loop, struct ev_timer* t, const int revents V_UNUSED) { dmn_assert(loop); dmn_assert(t); dmn_assert(revents == EV_TIMER); tcp_events_t* md = t->data; dmn_assert(md); dmn_assert(md->sock > -1); dmn_assert(md->tcp_state == TCP_STATE_CONNECTING); dmn_assert(ev_is_active(md->connect_watcher)); log_debug("plugin_tcp_connect: State poll of %s timed out", md->desc); ev_io_stop(loop, md->connect_watcher); shutdown(md->sock, SHUT_RDWR); close(md->sock); md->sock = -1; md->tcp_state = TCP_STATE_WAITING; gdnsd_mon_state_updater(md->idx, false); }
F_NONNULL static void mon_timeout_cb(struct ev_loop* loop, struct ev_timer* t, const int revents V_UNUSED) { dmn_assert(loop); dmn_assert(t); dmn_assert(revents == EV_TIMER); http_events_t* md = (http_events_t*)t->data; dmn_assert(md); dmn_assert(md->sock != -1); dmn_assert( (md->hstate == HTTP_STATE_READING && ev_is_active(md->read_watcher)) || (md->hstate == HTTP_STATE_WRITING && ev_is_active(md->write_watcher)) ); log_debug("plugin_http_status: State poll of %s timed out", md->smgr->desc); if(md->hstate == HTTP_STATE_READING) ev_io_stop(loop, md->read_watcher); else if(md->hstate == HTTP_STATE_WRITING) ev_io_stop(loop, md->write_watcher); shutdown(md->sock, SHUT_RDWR); close(md->sock); md->sock = -1; md->hstate = HTTP_STATE_WAITING; gdnsd_mon_state_updater(md->smgr, false); }
F_NONNULL static void mon_interval_cb(struct ev_loop* loop, struct ev_timer* t, const int revents V_UNUSED) { dmn_assert(loop); dmn_assert(t); dmn_assert(revents == EV_TIMER); tcp_events_t* md = t->data; dmn_assert(md); if(md->tcp_state != TCP_STATE_WAITING) { log_warn("plugin_tcp_connect: A monitoring request attempt seems to have " "lasted longer than the monitoring interval. " "Skipping this round of monitoring - are you " "starved for CPU time?"); return; } dmn_assert(md->sock == -1); dmn_assert(!ev_is_active(md->connect_watcher)); dmn_assert(!ev_is_active(md->timeout_watcher) && !ev_is_pending(md->timeout_watcher)); log_debug("plugin_tcp_connect: Starting state poll of %s", md->desc); const bool isv6 = md->addr.sa.sa_family == AF_INET6; const int sock = socket(isv6 ? PF_INET6 : PF_INET, SOCK_STREAM, gdnsd_getproto_tcp()); if(sock == -1) { log_err("plugin_tcp_connect: Failed to create monitoring socket: %s", dmn_logf_errno()); return; } if(fcntl(sock, F_SETFL, (fcntl(sock, F_GETFL, 0)) | O_NONBLOCK) == -1) { log_err("plugin_tcp_connect: Failed to set O_NONBLOCK on monitoring socket: %s", dmn_logf_errno()); close(sock); return; } bool success = false; if(likely(connect(sock, &md->addr.sa, md->addr.len) == -1)) { switch(errno) { case EINPROGRESS: // this is the normal case, where nonblock connect // wants us to wait for writability... md->sock = sock; md->tcp_state = TCP_STATE_CONNECTING; ev_io_set(md->connect_watcher, sock, EV_WRITE); ev_io_start(loop, md->connect_watcher); ev_timer_set(md->timeout_watcher, md->tcp_svc->timeout, 0); ev_timer_start(loop, md->timeout_watcher); return; // don't do socket/status finishing actions below... break; // redundant case EPIPE: case ECONNREFUSED: case ETIMEDOUT: case EHOSTUNREACH: case EHOSTDOWN: case ENETUNREACH: // fast remote failures, e.g. when remote is local, I hope log_debug("plugin_tcp_connect: State poll of %s failed very quickly", md->desc); break; default: log_err("plugin_tcp_connect: Failed to connect() monitoring socket to remote server, possible local problem: %s", dmn_logf_errno()); } } else { success = true; } close(sock); gdnsd_mon_state_updater(md->idx, success); }
F_NONNULL static void mon_interval_cb(struct ev_loop* loop, struct ev_timer* t, const int revents V_UNUSED) { dmn_assert(loop); dmn_assert(t); dmn_assert(revents == EV_TIMER); http_events_t* md = (http_events_t*)t->data; dmn_assert(md); if(unlikely(md->hstate != HTTP_STATE_WAITING)) { log_warn("plugin_http_status: A monitoring request attempt seems to have " "lasted longer than the monitoring interval. " "Skipping this round of monitoring - are you " "starved for CPU time?"); return; } dmn_assert(md->sock == -1); dmn_assert(!ev_is_active(md->read_watcher)); dmn_assert(!ev_is_active(md->write_watcher)); dmn_assert(!ev_is_active(md->timeout_watcher)); log_debug("plugin_http_status: Starting state poll of %s", md->smgr->desc); do { const bool isv6 = md->addr.sa.sa_family == AF_INET6; const int sock = socket(isv6 ? PF_INET6 : PF_INET, SOCK_STREAM, gdnsd_getproto_tcp()); if(unlikely(sock < 0)) { log_err("plugin_http_status: Failed to create monitoring socket: %s", logf_errno()); break; } if(unlikely(fcntl(sock, F_SETFL, (fcntl(sock, F_GETFL, 0)) | O_NONBLOCK) == -1)) { log_err("plugin_http_status: Failed to set O_NONBLOCK on monitoring socket: %s", logf_errno()); close(sock); break; } md->already_connected = true; if(likely(connect(sock, &md->addr.sa, md->addr.len) == -1)) { if(likely(errno == EINPROGRESS)) { md->already_connected = false; } else { switch(errno) { case EPIPE: case ECONNREFUSED: case ETIMEDOUT: case EHOSTUNREACH: case EHOSTDOWN: case ENETUNREACH: break; default: log_err("plugin_http_status: Failed to connect() monitoring socket to remote server, possible local problem: %s", logf_errno()); } close(sock); break; } } md->sock = sock; md->hstate = HTTP_STATE_WRITING; md->done = 0; ev_io_set(md->write_watcher, sock, EV_WRITE); ev_io_start(loop, md->write_watcher); ev_timer_set(md->timeout_watcher, md->http_svc->timeout, 0); ev_timer_start(loop, md->timeout_watcher); return; } while(0); // This is only reachable via "break"'s above, which indicate an immediate failure log_debug("plugin_http_status: State poll of %s failed very quickly", md->smgr->desc); md->hstate = HTTP_STATE_WAITING; gdnsd_mon_state_updater(md->smgr, false); }
F_NONNULL static void mon_write_cb(struct ev_loop* loop, struct ev_io* io, const int revents V_UNUSED) { dmn_assert(loop); dmn_assert(io); dmn_assert(revents == EV_WRITE); http_events_t* md = (http_events_t*)io->data; dmn_assert(md); dmn_assert(md->hstate == HTTP_STATE_WRITING); dmn_assert(!ev_is_active(md->read_watcher)); dmn_assert(ev_is_active(md->write_watcher)); dmn_assert(ev_is_active(md->timeout_watcher)); dmn_assert(md->sock > -1); int sock = md->sock; if(likely(!md->already_connected)) { // nonblocking connect() just finished, need to check status int so_error = 0; unsigned int so_error_len = sizeof(so_error); (void)getsockopt(sock, SOL_SOCKET, SO_ERROR, &so_error, &so_error_len); if(unlikely(so_error)) { switch(so_error) { case EPIPE: case ECONNREFUSED: case ETIMEDOUT: case EHOSTUNREACH: case EHOSTDOWN: case ENETUNREACH: break; default: log_err("plugin_http_status: Failed to connect() monitoring socket to remote server, possible local problem: %s", logf_errnum(so_error)); } log_debug("plugin_http_status: State poll of %s failed quickly: %s", md->smgr->desc, logf_errnum(so_error)); close(sock); md->sock = -1; ev_io_stop(loop, md->write_watcher); ev_timer_stop(loop, md->timeout_watcher); md->hstate = HTTP_STATE_WAITING; gdnsd_mon_state_updater(md->smgr, false); return; } md->already_connected = true; } const unsigned to_send = md->http_svc->req_data_len - md->done; const int sent = send(sock, md->http_svc->req_data + md->done, md->http_svc->req_data_len, 0); if(unlikely(sent == -1)) { switch(errno) { case EAGAIN: case EINTR: return; case ENOTCONN: case ECONNRESET: case ETIMEDOUT: case EHOSTUNREACH: case ENETUNREACH: case EPIPE: break; default: log_err("plugin_http_status: write() to monitoring socket failed, possible local problem: %s", logf_errno()); } shutdown(sock, SHUT_RDWR); close(sock); md->sock = -1; ev_io_stop(loop, md->write_watcher); ev_timer_stop(loop, md->timeout_watcher); md->hstate = HTTP_STATE_WAITING; gdnsd_mon_state_updater(md->smgr, false); } if(unlikely(sent != (signed)to_send)) { md->done += sent; return; } md->done = 0; md->hstate = HTTP_STATE_READING; ev_io_stop(loop, md->write_watcher); ev_io_set(md->read_watcher, sock, EV_READ); ev_io_start(loop, md->read_watcher); }