quark

quark web server
git clone git://git.suckless.org/quark
Log | Files | Refs | LICENSE

connection.c (8277B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include <errno.h>
      3 #include <netinet/in.h>
      4 #include <stdio.h>
      5 #include <string.h>
      6 #include <sys/socket.h>
      7 #include <sys/types.h>
      8 #include <time.h>
      9 #include <unistd.h>
     10 
     11 #include "connection.h"
     12 #include "data.h"
     13 #include "http.h"
     14 #include "server.h"
     15 #include "sock.h"
     16 #include "util.h"
     17 
     18 struct worker_data {
     19 	int insock;
     20 	size_t nslots;
     21 	const struct server *srv;
     22 };
     23 
     24 void
     25 connection_log(const struct connection *c)
     26 {
     27 	char inaddr_str[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */];
     28 	char tstmp[21];
     29 
     30 	/* create timestamp */
     31 	if (!strftime(tstmp, sizeof(tstmp), "%Y-%m-%dT%H:%M:%SZ",
     32 	              gmtime(&(time_t){time(NULL)}))) {
     33 		warn("strftime: Exceeded buffer capacity");
     34 		tstmp[0] = '\0'; /* tstmp contents are undefined on failure */
     35 		/* continue anyway */
     36 	}
     37 
     38 	/* generate address-string */
     39 	if (sock_get_inaddr_str(&c->ia, inaddr_str, LEN(inaddr_str))) {
     40 		warn("sock_get_inaddr_str: Couldn't generate adress-string");
     41 		inaddr_str[0] = '\0';
     42 	}
     43 
     44 	printf("%s\t%s\t%s%.*d\t%s\t%s%s%s%s%s\n",
     45 	       tstmp,
     46 	       inaddr_str,
     47 	       (c->res.status == 0) ? "dropped" : "",
     48 	       (c->res.status == 0) ? 0 : 3,
     49 	       c->res.status,
     50 	       c->req.field[REQ_HOST][0] ? c->req.field[REQ_HOST] : "-",
     51 	       c->req.path[0] ? c->req.path : "-",
     52 	       c->req.query[0] ? "?" : "",
     53 	       c->req.query,
     54 	       c->req.fragment[0] ? "#" : "",
     55 	       c->req.fragment);
     56 }
     57 
     58 void
     59 connection_reset(struct connection *c)
     60 {
     61 	if (c != NULL) {
     62 		shutdown(c->fd, SHUT_RDWR);
     63 		close(c->fd);
     64 		memset(c, 0, sizeof(*c));
     65 	}
     66 }
     67 
     68 void
     69 connection_serve(struct connection *c, const struct server *srv)
     70 {
     71 	enum status s;
     72 	int done;
     73 
     74 	switch (c->state) {
     75 	case C_VACANT:
     76 		/*
     77 		 * we were passed a "fresh" connection which should now
     78 		 * try to receive the header, reset buf beforehand
     79 		 */
     80 		memset(&c->buf, 0, sizeof(c->buf));
     81 
     82 		c->state = C_RECV_HEADER;
     83 		/* fallthrough */
     84 	case C_RECV_HEADER:
     85 		/* receive header */
     86 		done = 0;
     87 		if ((s = http_recv_header(c->fd, &c->buf, &done))) {
     88 			http_prepare_error_response(&c->req, &c->res, s);
     89 			goto response;
     90 		}
     91 		if (!done) {
     92 			/* not done yet */
     93 			return;
     94 		}
     95 
     96 		/* parse header */
     97 		if ((s = http_parse_header(c->buf.data, &c->req))) {
     98 			http_prepare_error_response(&c->req, &c->res, s);
     99 			goto response;
    100 		}
    101 
    102 		/* prepare response struct */
    103 		http_prepare_response(&c->req, &c->res, srv);
    104 response:
    105 		/* generate response header */
    106 		if ((s = http_prepare_header_buf(&c->res, &c->buf))) {
    107 			http_prepare_error_response(&c->req, &c->res, s);
    108 			if ((s = http_prepare_header_buf(&c->res, &c->buf))) {
    109 				/* couldn't generate the header, we failed for good */
    110 				c->res.status = s;
    111 				goto err;
    112 			}
    113 		}
    114 
    115 		c->state = C_SEND_HEADER;
    116 		/* fallthrough */
    117 	case C_SEND_HEADER:
    118 		if ((s = http_send_buf(c->fd, &c->buf))) {
    119 			c->res.status = s;
    120 			goto err;
    121 		}
    122 		if (c->buf.len > 0) {
    123 			/* not done yet */
    124 			return;
    125 		}
    126 
    127 		c->state = C_SEND_BODY;
    128 		/* fallthrough */
    129 	case C_SEND_BODY:
    130 		if (c->req.method == M_GET) {
    131 			if (c->buf.len == 0) {
    132 				/* fill buffer with body data */
    133 				if ((s = data_fct[c->res.type](&c->res, &c->buf,
    134 				                               &c->progress))) {
    135 					/* too late to do any real error handling */
    136 					c->res.status = s;
    137 					goto err;
    138 				}
    139 
    140 				/* if the buffer remains empty, we are done */
    141 				if (c->buf.len == 0) {
    142 					break;
    143 				}
    144 			} else {
    145 				/* send buffer */
    146 				if ((s = http_send_buf(c->fd, &c->buf))) {
    147 					/* too late to do any real error handling */
    148 					c->res.status = s;
    149 					goto err;
    150 				}
    151 			}
    152 			return;
    153 		}
    154 		break;
    155 	default:
    156 		warn("serve: invalid connection state");
    157 		return;
    158 	}
    159 err:
    160 	connection_log(c);
    161 	connection_reset(c);
    162 }
    163 
    164 static struct connection *
    165 connection_get_drop_candidate(struct connection *connection, size_t nslots)
    166 {
    167 	struct connection *c, *minc;
    168 	size_t i, j, maxcnt, cnt;
    169 
    170 	/*
    171 	 * determine the most-unimportant connection 'minc' of the in-address
    172 	 * with most connections; this algorithm has a complexity of O(n²)
    173 	 * in time but is O(1) in space; there are algorithms with O(n) in
    174 	 * time and space, but this would require memory allocation,
    175 	 * which we avoid. Given the simplicity of the inner loop and
    176 	 * relatively small number of slots per thread, this is fine.
    177 	 */
    178 	for (i = 0, minc = NULL, maxcnt = 0; i < nslots; i++) {
    179 		/*
    180 		 * we determine how many connections have the same
    181 		 * in-address as connection[i], but also minimize over
    182 		 * that set with other criteria, yielding a general
    183 		 * minimizer c. We first set it to connection[i] and
    184 		 * update it, if a better candidate shows up, in the inner
    185 		 * loop
    186 		 */
    187 		c = &connection[i];
    188 
    189 		for (j = 0, cnt = 0; j < nslots; j++) {
    190 			if (!sock_same_addr(&connection[i].ia,
    191 			                    &connection[j].ia)) {
    192 				continue;
    193 			}
    194 			cnt++;
    195 
    196 			/* minimize over state */
    197 			if (connection[j].state < c->state) {
    198 				c = &connection[j];
    199 			} else if (connection[j].state == c->state) {
    200 				/* minimize over progress */
    201 				if (c->state == C_SEND_BODY &&
    202 				    connection[i].res.type != c->res.type) {
    203 					/*
    204 					 * mixed response types; progress
    205 					 * is not comparable
    206 					 *
    207 					 * the res-type-enum is ordered as
    208 					 * DIRLISTING, ERROR, FILE, i.e.
    209 					 * in rising priority, because a
    210 					 * file transfer is most important,
    211 					 * followed by error-messages.
    212 					 * Dirlistings as an "interactive"
    213 					 * feature (that take up lots of
    214 					 * resources) have the lowest
    215 					 * priority
    216 					 */
    217 					if (connection[i].res.type <
    218 					    c->res.type) {
    219 						c = &connection[j];
    220 					}
    221 				} else if (connection[j].progress <
    222 				           c->progress) {
    223 					/*
    224 					 * for C_SEND_BODY with same response
    225 					 * type, C_RECV_HEADER and C_SEND_BODY
    226 					 * it is sufficient to compare the
    227 					 * raw progress
    228 					 */
    229 					c = &connection[j];
    230 				}
    231 			}
    232 		}
    233 
    234 		if (cnt > maxcnt) {
    235 			/* this run yielded an even greedier in-address */
    236 			minc = c;
    237 			maxcnt = cnt;
    238 		}
    239 	}
    240 
    241 	return minc;
    242 }
    243 
    244 struct connection *
    245 connection_accept(int insock, struct connection *connection, size_t nslots)
    246 {
    247 	struct connection *c = NULL;
    248 	size_t i;
    249 
    250 	/* find vacant connection (i.e. one with no fd assigned to it) */
    251 	for (i = 0; i < nslots; i++) {
    252 		if (connection[i].fd == 0) {
    253 			c = &connection[i];
    254 			break;
    255 		}
    256 	}
    257 	if (i == nslots) {
    258 		/*
    259 		 * all our connection-slots are occupied and the only
    260 		 * way out is to drop another connection, because not
    261 		 * accepting this connection just kicks this can further
    262 		 * down the road (to the next queue_wait()) without
    263 		 * solving anything.
    264 		 *
    265 		 * This may sound bad, but this case can only be hit
    266 		 * either when there's a (D)DoS-attack or a massive
    267 		 * influx of requests. The latter is impossible to solve
    268 		 * at this moment without expanding resources, but the
    269 		 * former has certain characteristics allowing us to
    270 		 * handle this gracefully.
    271 		 *
    272 		 * During an attack (e.g. Slowloris, R-U-Dead-Yet, Slow
    273 		 * Read or just plain flooding) we can not see who is
    274 		 * waiting to be accept()ed.
    275 		 * However, an attacker usually already has many
    276 		 * connections open (while well-behaved clients could
    277 		 * do everything with just one connection using
    278 		 * keep-alive). Inferring a likely attacker-connection
    279 		 * is an educated guess based on which in-address is
    280 		 * occupying the most connection slots. Among those,
    281 		 * connections in early stages (receiving or sending
    282 		 * headers) are preferred over connections in late
    283 		 * stages (sending body).
    284 		 *
    285 		 * This quantitative approach effectively drops malicious
    286 		 * connections while preserving even long-running
    287 		 * benevolent connections like downloads.
    288 		 */
    289 		c = connection_get_drop_candidate(connection, nslots);
    290 		c->res.status = 0;
    291 		connection_log(c);
    292 		connection_reset(c);
    293 	}
    294 
    295 	/* accept connection */
    296 	if ((c->fd = accept(insock, (struct sockaddr *)&c->ia,
    297 	                    &(socklen_t){sizeof(c->ia)})) < 0) {
    298 		if (errno != EAGAIN && errno != EWOULDBLOCK) {
    299 			/*
    300 			 * this should not happen, as we received the
    301 			 * event that there are pending connections here
    302 			 */
    303 			warn("accept:");
    304 		}
    305 		return NULL;
    306 	}
    307 
    308 	/* set socket to non-blocking mode */
    309 	if (sock_set_nonblocking(c->fd)) {
    310 		/* we can't allow blocking sockets */
    311 		return NULL;
    312 	}
    313 
    314 	return c;
    315 }