/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #include #include "connection.h" #include "data.h" #include "http.h" #include "server.h" #include "sock.h" #include "util.h" struct worker_data { int insock; size_t nslots; const struct server *srv; }; void connection_log(const struct connection *c) { char inaddr_str[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */]; char tstmp[21]; /* create timestamp */ if (!strftime(tstmp, sizeof(tstmp), "%Y-%m-%dT%H:%M:%SZ", gmtime(&(time_t){time(NULL)}))) { warn("strftime: Exceeded buffer capacity"); /* continue anyway (we accept the truncation) */ } /* generate address-string */ if (sock_get_inaddr_str(&c->ia, inaddr_str, LEN(inaddr_str))) { warn("sock_get_inaddr_str: Couldn't generate adress-string"); inaddr_str[0] = '\0'; } printf("%s\t%s\t%s%.*d\t%s\t%s%s%s%s%s\n", tstmp, inaddr_str, (c->res.status == 0) ? "dropped" : "", (c->res.status == 0) ? 0 : 3, c->res.status, c->req.field[REQ_HOST][0] ? c->req.field[REQ_HOST] : "-", c->req.path[0] ? c->req.path : "-", c->req.query[0] ? "?" : "", c->req.query, c->req.fragment[0] ? "#" : "", c->req.fragment); } void connection_reset(struct connection *c) { if (c != NULL) { shutdown(c->fd, SHUT_RDWR); close(c->fd); memset(c, 0, sizeof(*c)); } } void connection_serve(struct connection *c, const struct server *srv) { enum status s; int done; switch (c->state) { case C_VACANT: /* * we were passed a "fresh" connection which should now * try to receive the header, reset buf beforehand */ memset(&c->buf, 0, sizeof(c->buf)); c->state = C_RECV_HEADER; /* fallthrough */ case C_RECV_HEADER: /* receive header */ done = 0; if ((s = http_recv_header(c->fd, &c->buf, &done))) { http_prepare_error_response(&c->req, &c->res, s); goto response; } if (!done) { /* not done yet */ return; } /* parse header */ if ((s = http_parse_header(c->buf.data, &c->req))) { http_prepare_error_response(&c->req, &c->res, s); goto response; } /* prepare response struct */ http_prepare_response(&c->req, &c->res, srv); response: /* generate response header */ if ((s = http_prepare_header_buf(&c->res, &c->buf))) { http_prepare_error_response(&c->req, &c->res, s); if ((s = http_prepare_header_buf(&c->res, &c->buf))) { /* couldn't generate the header, we failed for good */ c->res.status = s; goto err; } } c->state = C_SEND_HEADER; /* fallthrough */ case C_SEND_HEADER: if ((s = http_send_buf(c->fd, &c->buf))) { c->res.status = s; goto err; } if (c->buf.len > 0) { /* not done yet */ return; } c->state = C_SEND_BODY; /* fallthrough */ case C_SEND_BODY: if (c->req.method == M_GET) { if (c->buf.len == 0) { /* fill buffer with body data */ if ((s = data_fct[c->res.type](&c->res, &c->buf, &c->progress))) { /* too late to do any real error handling */ c->res.status = s; goto err; } /* if the buffer remains empty, we are done */ if (c->buf.len == 0) { break; } } else { /* send buffer */ if ((s = http_send_buf(c->fd, &c->buf))) { /* too late to do any real error handling */ c->res.status = s; goto err; } } return; } break; default: warn("serve: invalid connection state"); return; } err: connection_log(c); connection_reset(c); } static struct connection * connection_get_drop_candidate(struct connection *connection, size_t nslots) { struct connection *c, *minc; size_t i, j, maxcnt, cnt; /* * determine the most-unimportant connection 'minc' of the in-address * with most connections; this algorithm has a complexity of O(n²) * in time but is O(1) in space; there are algorithms with O(n) in * time and space, but this would require memory allocation, * which we avoid. Given the simplicity of the inner loop and * relatively small number of slots per thread, this is fine. */ for (i = 0, minc = NULL, maxcnt = 0; i < nslots; i++) { /* * we determine how many connections have the same * in-address as connection[i], but also minimize over * that set with other criteria, yielding a general * minimizer c. We first set it to connection[i] and * update it, if a better candidate shows up, in the inner * loop */ c = &connection[i]; for (j = 0, cnt = 0; j < nslots; j++) { if (!sock_same_addr(&connection[i].ia, &connection[j].ia)) { continue; } cnt++; /* minimize over state */ if (connection[j].state < c->state) { c = &connection[j]; } else if (connection[j].state == c->state) { /* minimize over progress */ if (c->state == C_SEND_BODY && connection[i].res.type != c->res.type) { /* * mixed response types; progress * is not comparable * * the res-type-enum is ordered as * DIRLISTING, ERROR, FILE, i.e. * in rising priority, because a * file transfer is most important, * followed by error-messages. * Dirlistings as an "interactive" * feature (that take up lots of * resources) have the lowest * priority */ if (connection[i].res.type < c->res.type) { c = &connection[j]; } } else if (connection[j].progress < c->progress) { /* * for C_SEND_BODY with same response * type, C_RECV_HEADER and C_SEND_BODY * it is sufficient to compare the * raw progress */ c = &connection[j]; } } } if (cnt > maxcnt) { /* this run yielded an even greedier in-address */ minc = c; maxcnt = cnt; } } return minc; } struct connection * connection_accept(int insock, struct connection *connection, size_t nslots) { struct connection *c = NULL; size_t i; /* find vacant connection (i.e. one with no fd assigned to it) */ for (i = 0; i < nslots; i++) { if (connection[i].fd == 0) { c = &connection[i]; break; } } if (i == nslots) { /* * all our connection-slots are occupied and the only * way out is to drop another connection, because not * accepting this connection just kicks this can further * down the road (to the next queue_wait()) without * solving anything. * * This may sound bad, but this case can only be hit * either when there's a (D)DoS-attack or a massive * influx of requests. The latter is impossible to solve * at this moment without expanding resources, but the * former has certain characteristics allowing us to * handle this gracefully. * * During an attack (e.g. Slowloris, R-U-Dead-Yet, Slow * Read or just plain flooding) we can not see who is * waiting to be accept()ed. * However, an attacker usually already has many * connections open (while well-behaved clients could * do everything with just one connection using * keep-alive). Inferring a likely attacker-connection * is an educated guess based on which in-address is * occupying the most connection slots. Among those, * connections in early stages (receiving or sending * headers) are preferred over connections in late * stages (sending body). * * This quantitative approach effectively drops malicious * connections while preserving even long-running * benevolent connections like downloads. */ c = connection_get_drop_candidate(connection, nslots); c->res.status = 0; connection_log(c); connection_reset(c); } /* accept connection */ if ((c->fd = accept(insock, (struct sockaddr *)&c->ia, &(socklen_t){sizeof(c->ia)})) < 0) { if (errno != EAGAIN && errno != EWOULDBLOCK) { /* * this should not happen, as we received the * event that there are pending connections here */ warn("accept:"); } return NULL; } /* set socket to non-blocking mode */ if (sock_set_nonblocking(c->fd)) { /* we can't allow blocking sockets */ return NULL; } return c; }