/* See LICENSE file for copyright and license details. */
|
|
#include <errno.h>
|
|
#include <netinet/in.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#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;
|
|
}
|