diff options
Diffstat (limited to 'util/netevent.c')
| -rw-r--r-- | util/netevent.c | 192 |
1 files changed, 151 insertions, 41 deletions
diff --git a/util/netevent.c b/util/netevent.c index aedcb5e07a30..432fead1b19d 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -951,6 +951,10 @@ static int consume_pp2_header(struct sldns_buffer* buf, struct comm_reply* rep, { struct sockaddr_in* addr = (struct sockaddr_in*)&rep->client_addr; + if(ntohs(header->len) < PP2_HEADER_LEN_INET) { + verbose(VERB_OPS, "proxy_protocol: header too short for IPv4 address"); + return 0; + } addr->sin_family = AF_INET; addr->sin_addr.s_addr = header->addr.addr4.src_addr; addr->sin_port = header->addr.addr4.src_port; @@ -963,6 +967,10 @@ static int consume_pp2_header(struct sldns_buffer* buf, struct comm_reply* rep, { struct sockaddr_in6* addr = (struct sockaddr_in6*)&rep->client_addr; + if(ntohs(header->len) < PP2_HEADER_LEN_INET6) { + verbose(VERB_OPS, "proxy_protocol: header too short for IPv6 address"); + return 0; + } memset(addr, 0, sizeof(*addr)); addr->sin6_family = AF_INET6; memcpy(&addr->sin6_addr, @@ -1827,7 +1835,6 @@ doq_send_retry(struct comm_point* c, struct doq_pkt_addr* paddr, char host[256], port[32]; struct ngtcp2_cid scid; uint8_t token[NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN]; - ngtcp2_tstamp ts; ngtcp2_ssize tokenlen, ret; if(!doq_print_addr_port(&paddr->addr, paddr->addrlen, host, @@ -1841,12 +1848,10 @@ doq_send_retry(struct comm_point* c, struct doq_pkt_addr* paddr, scid.datalen = c->doq_socket->sv_scidlen; doq_cid_randfill(&scid, scid.datalen, c->doq_socket->rnd); - ts = doq_get_timestamp_nanosec(); - tokenlen = ngtcp2_crypto_generate_retry_token(token, c->doq_socket->static_secret, c->doq_socket->static_secret_len, hd->version, (void*)&paddr->addr, paddr->addrlen, &scid, - &hd->dcid, ts); + &hd->dcid, doq_get_timestamp_nanosec()); if(tokenlen < 0) { log_err("ngtcp2_crypto_generate_retry_token failed: %s", ngtcp2_strerror(tokenlen)); @@ -1895,13 +1900,11 @@ doq_verify_retry_token(struct comm_point* c, struct doq_pkt_addr* paddr, struct ngtcp2_cid* ocid, struct ngtcp2_pkt_hd* hd) { char host[256], port[32]; - ngtcp2_tstamp ts; if(!doq_print_addr_port(&paddr->addr, paddr->addrlen, host, sizeof(host), port, sizeof(port))) { log_err("doq_verify_retry_token failed"); return 0; } - ts = doq_get_timestamp_nanosec(); verbose(VERB_ALGO, "doq: verifying retry token from %s %s", host, port); if(ngtcp2_crypto_verify_retry_token(ocid, @@ -1913,7 +1916,7 @@ doq_verify_retry_token(struct comm_point* c, struct doq_pkt_addr* paddr, c->doq_socket->static_secret, c->doq_socket->static_secret_len, hd->version, (void*)&paddr->addr, paddr->addrlen, &hd->dcid, - 10*NGTCP2_SECONDS, ts) != 0) { + 10*NGTCP2_SECONDS, doq_get_timestamp_nanosec()) != 0) { verbose(VERB_ALGO, "doq: could not verify retry token " "from %s %s", host, port); return 0; @@ -1928,13 +1931,11 @@ doq_verify_token(struct comm_point* c, struct doq_pkt_addr* paddr, struct ngtcp2_pkt_hd* hd) { char host[256], port[32]; - ngtcp2_tstamp ts; if(!doq_print_addr_port(&paddr->addr, paddr->addrlen, host, sizeof(host), port, sizeof(port))) { log_err("doq_verify_token failed"); return 0; } - ts = doq_get_timestamp_nanosec(); verbose(VERB_ALGO, "doq: verifying token from %s %s", host, port); if(ngtcp2_crypto_verify_regular_token( #ifdef HAVE_STRUCT_NGTCP2_PKT_HD_TOKENLEN @@ -1944,7 +1945,7 @@ doq_verify_token(struct comm_point* c, struct doq_pkt_addr* paddr, #endif c->doq_socket->static_secret, c->doq_socket->static_secret_len, (void*)&paddr->addr, paddr->addrlen, 3600*NGTCP2_SECONDS, - ts) != 0) { + doq_get_timestamp_nanosec()) != 0) { verbose(VERB_ALGO, "doq: could not verify token from %s %s", host, port); return 0; @@ -2171,6 +2172,7 @@ doq_pickup_timer(struct comm_point* c) { struct doq_timer* t; struct timeval tv; + ngtcp2_tstamp ts = 0; int have_time = 0; memset(&tv, 0, sizeof(tv)); @@ -2180,27 +2182,24 @@ doq_pickup_timer(struct comm_point* c) t->worker_doq_socket == c->doq_socket) { /* pick up this element */ t->worker_doq_socket = c->doq_socket; + memcpy(&tv, &t->time_real, sizeof(tv)); + ts = t->time_mono; have_time = 1; - memcpy(&tv, &t->time, sizeof(tv)); break; } } lock_rw_unlock(&c->doq_socket->table->lock); - + c->doq_socket->marked_time = ts; if(have_time) { struct timeval rel; timeval_subtract(&rel, &tv, c->doq_socket->now_tv); comm_timer_set(c->doq_socket->timer, &rel); - memcpy(&c->doq_socket->marked_time, &tv, - sizeof(c->doq_socket->marked_time)); verbose(VERB_ALGO, "doq pickup timer at %d.%6.6d in %d.%6.6d", (int)tv.tv_sec, (int)tv.tv_usec, (int)rel.tv_sec, (int)rel.tv_usec); } else { if(comm_timer_is_set(c->doq_socket->timer)) comm_timer_disable(c->doq_socket->timer); - memset(&c->doq_socket->marked_time, 0, - sizeof(c->doq_socket->marked_time)); verbose(VERB_ALGO, "doq timer disabled"); } } @@ -2213,13 +2212,14 @@ doq_done_setup_timer_and_write(struct comm_point* c, struct doq_conn* conn) uint8_t cid[NGTCP2_MAX_CIDLEN]; rbnode_type* node; struct timeval new_tv; + ngtcp2_tstamp new_ts; int write_change = 0, timer_change = 0; /* No longer in callbacks, so the pointer to doq_socket is back * to NULL. */ conn->doq_socket = NULL; - if(doq_conn_check_timer(conn, &new_tv)) + if(doq_conn_check_timer(conn, &new_tv, &new_ts)) timer_change = 1; if( (conn->write_interest && !conn->on_write_list) || (!conn->write_interest && conn->on_write_list)) @@ -2265,7 +2265,7 @@ doq_done_setup_timer_and_write(struct comm_point* c, struct doq_conn* conn) } if(timer_change) { doq_timer_set(c->doq_socket->table, &conn->timer, - c->doq_socket, &new_tv); + c->doq_socket, &new_tv, new_ts); } lock_rw_unlock(&c->doq_socket->table->lock); lock_basic_unlock(&conn->lock); @@ -2429,7 +2429,7 @@ doq_write_blocked_pkt(struct comm_point* c) return 1; } -/** doq find a timer that timeouted and return the conn, locked. */ +/** doq find a timer that timed out and return the conn, locked. */ static struct doq_conn* doq_timer_timeout_conn(struct doq_server_socket* doq_socket) { @@ -2442,7 +2442,7 @@ doq_timer_timeout_conn(struct doq_server_socket* doq_socket) conn = t->conn; /* If now < timer then no further timeouts in tree. */ - if(timeval_smaller(doq_socket->now_tv, &t->time)) { + if(timeval_smaller(doq_socket->now_tv, &t->time_real)) { lock_rw_unlock(&doq_socket->table->lock); return NULL; } @@ -2465,11 +2465,11 @@ doq_timer_erase_marker(struct doq_server_socket* doq_socket) { struct doq_timer* t; lock_rw_wrlock(&doq_socket->table->lock); - t = doq_timer_find_time(doq_socket->table, &doq_socket->marked_time); + t = doq_timer_find_time(doq_socket->table, doq_socket->marked_time); if(t && t->worker_doq_socket == doq_socket) t->worker_doq_socket = NULL; lock_rw_unlock(&doq_socket->table->lock); - memset(&doq_socket->marked_time, 0, sizeof(doq_socket->marked_time)); + doq_socket->marked_time = 0; } void @@ -2723,6 +2723,7 @@ doq_server_socket_create(struct doq_table* table, struct ub_randstate* rnd, { size_t doq_buffer_size = 4096; /* bytes buffer size, for one packet. */ struct doq_server_socket* doq_socket; + log_assert(table != NULL); doq_socket = calloc(1, sizeof(*doq_socket)); if(!doq_socket) { return NULL; @@ -2775,7 +2776,7 @@ doq_server_socket_create(struct doq_table* table, struct ub_randstate* rnd, free(doq_socket); return NULL; } - memset(&doq_socket->marked_time, 0, sizeof(doq_socket->marked_time)); + doq_socket->marked_time = 0; comm_base_timept(base, &doq_socket->now_tt, &doq_socket->now_tv); doq_socket->cfg = cfg; return doq_socket; @@ -2804,6 +2805,7 @@ doq_lookup_repinfo(struct doq_table* table, struct comm_reply* repinfo) { struct doq_conn* conn; struct doq_conn_key key; + log_assert(table != NULL); doq_conn_key_from_repinfo(&key, repinfo); lock_rw_rdlock(&table->lock); conn = doq_conn_find(table, &key.paddr.addr, @@ -2938,6 +2940,8 @@ setup_tcp_handler(struct comm_point* c, int fd, int cur, int max) c->tcp_is_reading = 1; c->tcp_byte_count = 0; c->tcp_keepalive = 0; + /* reset to configured value before applying load-based reduction */ + c->tcp_timeout_msec = c->tcp_parent->tcp_timeout_msec; /* if more than half the tcp handlers are in use, use a shorter * timeout for this TCP connection, we need to make space for * other connections to be able to get attention */ @@ -2973,6 +2977,62 @@ void comm_base_handle_slow_accept(int ATTR_UNUSED(fd), } } +/** out of resources in the accept path: pause all listening for + * NETEVENT_SLOW_ACCEPT_TIME and re-arm via comm_base_handle_slow_accept. + * + * If the routine fails, the socket is accepted and then closed, draining it + * from the waiting list of connections to be accepted. + * @param c: the comm point that is a listening socket. + * @param msec: if 0: uses the slow accept time. Otherwise, sets the time + * to wait. + */ +static void +comm_point_slow_accept(struct comm_point* c, int msec) +{ + struct comm_base* b = c->ev->base; + struct timeval tv; + struct ub_event* slowev; + if(!b->stop_accept) + return; + if(b->eb->slow_accept_enabled) + return; + /* Allocate the event */ + slowev = ub_event_new(b->eb->base, -1, UB_EV_TIMEOUT, + comm_base_handle_slow_accept, b); + if(!slowev) { + /* The slow accept was not enabled yet, to handle + * the allocation failure, instead drain the incoming + * connection. */ + int new_fd = accept(c->fd, NULL, NULL); + if(new_fd != -1) { + verbose(VERB_ALGO, "slow accept: event_new failed, " + "drop connection"); + sock_close(new_fd); + } + return; + } + ub_comm_base_now(b); + if(b->eb->last_slow_log+SLOW_LOG_TIME <= b->eb->secs) { + b->eb->last_slow_log = b->eb->secs; + verbose(VERB_OPS, "out of resources on accept, " + "slow down accept for %d msec", + NETEVENT_SLOW_ACCEPT_TIME); + } + b->eb->slow_accept_enabled = 1; + fptr_ok(fptr_whitelist_stop_accept(b->stop_accept)); + (*b->stop_accept)(b->cb_arg); + /* set timeout, no mallocs */ + if(msec == 0) + msec = NETEVENT_SLOW_ACCEPT_TIME; + tv.tv_sec = msec/1000; + tv.tv_usec = (msec%1000)*1000; + b->eb->slow_accept = slowev; + if(ub_event_add(b->eb->slow_accept, &tv) != 0) { + /* we do not want to log here, + * error: "event_add failed." */ + } +} + int comm_point_perform_accept(struct comm_point* c, struct sockaddr_storage* addr, socklen_t* addrlen) { @@ -3006,6 +3066,14 @@ int comm_point_perform_accept(struct comm_point* c, if(c->ev->base->stop_accept) { struct comm_base* b = c->ev->base; struct timeval tv; + struct ub_event* slowev = ub_event_new( + b->eb->base, -1, UB_EV_TIMEOUT, + comm_base_handle_slow_accept, b); + if(!slowev) { + verbose(VERB_ALGO, "slow accept: " + "event_new failed"); + return -1; + } verbose(VERB_ALGO, "out of file descriptors: " "slow accept"); ub_comm_base_now(b); @@ -3025,15 +3093,8 @@ int comm_point_perform_accept(struct comm_point* c, /* set timeout, no mallocs */ tv.tv_sec = NETEVENT_SLOW_ACCEPT_TIME/1000; tv.tv_usec = (NETEVENT_SLOW_ACCEPT_TIME%1000)*1000; - b->eb->slow_accept = ub_event_new(b->eb->base, - -1, UB_EV_TIMEOUT, - comm_base_handle_slow_accept, b); - if(b->eb->slow_accept == NULL) { - /* we do not want to log here, because - * that would spam the logfiles. - * error: "event_base_set failed." */ - } - else if(ub_event_add(b->eb->slow_accept, &tv) + b->eb->slow_accept = slowev; + if(ub_event_add(b->eb->slow_accept, &tv) != 0) { /* we do not want to log here, * error: "event_add failed." */ @@ -3172,7 +3233,7 @@ static void http2_stream_delete(struct http2_session* h2_session, { if(h2_stream->mesh_state) { mesh_state_remove_reply(h2_stream->mesh, h2_stream->mesh_state, - h2_session->c); + h2_session->c, h2_stream, NULL); h2_stream->mesh_state = NULL; } http2_req_stream_clear(h2_stream); @@ -3214,6 +3275,13 @@ comm_point_tcp_accept_callback(int fd, short event, void* arg) /* find free tcp handler. */ if(!c->tcp_free) { log_warn("accepted too many tcp, connections full"); + /* Wait for a short moment (say 50msec) so that other + * TCP connections can complete. Or timeout, at the busy + * timeout of about 200msec. That stops this routine from + * spinning endlessly, and gives time to complete the other + * requests. But it is not as slow as the 2000msec wait + * time for when the kernel is out of buffers. */ + comm_point_slow_accept(c, NETEVENT_SLOW_ACCEPT_QUEUE_TIME); return; } /* accept incoming connection. */ @@ -3235,6 +3303,7 @@ comm_point_tcp_accept_callback(int fd, short event, void* arg) if(!c_hdl->h2_session || !http2_session_server_create(c_hdl->h2_session)) { log_warn("failed to create nghttp2"); + comm_point_slow_accept(c, 0); return; } if(!c_hdl->h2_session || @@ -3242,6 +3311,7 @@ comm_point_tcp_accept_callback(int fd, short event, void* arg) log_warn("failed to submit http2 settings"); if(c_hdl->h2_session) http2_session_server_delete(c_hdl->h2_session); + comm_point_slow_accept(c, 0); return; } if(!c->ssl) { @@ -3258,11 +3328,12 @@ comm_point_tcp_accept_callback(int fd, short event, void* arg) comm_point_tcp_handle_callback, c_hdl); } if(!c_hdl->ev->ev) { - log_warn("could not ub_event_new, dropped tcp"); + log_warn("could not ub_event_new, for new tcp"); #ifdef HAVE_NGHTTP2 if(c_hdl->type == comm_http && c_hdl->h2_session) http2_session_server_delete(c_hdl->h2_session); #endif + comm_point_slow_accept(c, 0); return; } log_assert(fd != -1); @@ -3276,6 +3347,10 @@ comm_point_tcp_accept_callback(int fd, short event, void* arg) #endif return; } + /* move per-netblock TCP-connection-limit handle to the handler so that + * comm_point_close() on the handler decrements the count on close */ + c_hdl->tcl_addr = c->tcl_addr; + c->tcl_addr = NULL; /* Copy remote_address to client_address. * Simplest way/time for streams to do that. */ c_hdl->repinfo.client_addrlen = c_hdl->repinfo.remote_addrlen; @@ -4868,8 +4943,17 @@ http_process_initial_header(struct comm_point* c) return 0; } } else if(strncasecmp(line, "Content-Length: ", 16) == 0) { - if(!c->http_is_chunked) - c->tcp_byte_count = (size_t)atoi(line+16); + if(!c->http_is_chunked) { + char* end = NULL; + long long cl; + errno = 0; + cl = strtoll(line+16, &end, 10); + if(end == line+16 || errno != 0 || cl < 0) { + verbose(VERB_ALGO, "http invalid Content-Length: " ARG_LL "d", cl); + return 0; /* reject */ + } + c->tcp_byte_count = (size_t)cl; + } } else if(strncasecmp(line, "Transfer-Encoding: chunked", 19+7) == 0) { c->tcp_byte_count = 0; c->http_is_chunked = 1; @@ -4925,9 +5009,15 @@ http_process_chunk_header(struct comm_point* c) if(c->http_in_chunk_headers == 1) { /* read chunked start line */ char* end = NULL; - c->tcp_byte_count = (size_t)strtol(line, &end, 16); - if(end == line) + long chunk_sz; + errno = 0; + chunk_sz = strtol(line, &end, 16); + if(end == line || errno != 0 || chunk_sz < 0) { + verbose(VERB_ALGO, "http invalid chunk size: %ld", + chunk_sz); return 0; + } + c->tcp_byte_count = (size_t)chunk_sz; c->http_in_chunk_headers = 0; /* remove header text from front of buffer */ http_moveover_buffer(c->buffer); @@ -5005,6 +5095,14 @@ http_chunked_segment(struct comm_point* c) c->http_stored = 0; sldns_buffer_skip(c->buffer, (ssize_t)c->tcp_byte_count); sldns_buffer_clear(c->http_temp); + if(sldns_buffer_remaining(c->buffer) > + sldns_buffer_capacity(c->http_temp)) { + verbose(VERB_OPS, "http chunked: surplus %d exceeds " + "temp buffer %d", (int)sldns_buffer_remaining( + c->buffer), (int)sldns_buffer_capacity( + c->http_temp)); + return 0; + } sldns_buffer_write(c->http_temp, sldns_buffer_current(c->buffer), sldns_buffer_remaining(c->buffer)); @@ -5335,6 +5433,13 @@ comm_point_http_handle_read(int fd, struct comm_point* c) if(c->http_in_headers || c->http_in_chunk_headers) { /* if header is done, process the header */ if(!http_header_done(c->buffer)) { + if(sldns_buffer_limit(c->buffer) == + sldns_buffer_capacity(c->buffer)) { + verbose(VERB_OPS, "http header line " + "exceeds %d bytes, transfer " + "failed", (int)sldns_buffer_capacity(c->buffer)); + return 0; + } /* copy remaining data to front of buffer * and set rest for writing into it */ http_moveover_buffer(c->buffer); @@ -5883,6 +5988,7 @@ comm_point_create_doq(struct comm_base *base, int fd, sldns_buffer* buffer, struct comm_point* c = (struct comm_point*)calloc(1, sizeof(struct comm_point)); short evbits; + log_assert(table != NULL); if(!c) return NULL; c->ev = (struct internal_event*)calloc(1, @@ -6574,7 +6680,10 @@ comm_point_close(struct comm_point* c) c->event_added = 0; } } - tcl_close_connection(c->tcl_addr); + if(c->tcl_addr) { + tcl_close_connection(c->tcl_addr); + c->tcl_addr = NULL; + } if(c->tcp_req_info) tcp_req_info_clear(c->tcp_req_info); if(c->h2_session) @@ -6667,7 +6776,9 @@ comm_point_send_reply(struct comm_reply *repinfo) log_assert(repinfo && repinfo->c); #ifdef USE_DNSCRYPT buffer = repinfo->c->dnscrypt_buffer; - if(!dnsc_handle_uncurved_request(repinfo)) { + if(!dnsc_handle_uncurved_request(repinfo, + repinfo->c->tcp_req_info? + repinfo->c->tcp_req_info->spool_buffer:repinfo->c->buffer)) { return; } #else @@ -6728,7 +6839,6 @@ comm_point_send_reply(struct comm_reply *repinfo) tcp_req_info_send_reply(repinfo->c->tcp_req_info); } else if(repinfo->c->use_h2) { if(!http2_submit_dns_response(repinfo->c->h2_session)) { - comm_point_drop_reply(repinfo); return; } repinfo->c->h2_stream = NULL; |
