connection.c - 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 fail… | |
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-stri… | |
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->bu… | |
109 /* couldn't generate the header, we fail… | |
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, … | |
134 &c->progr… | |
135 /* too late to do any real error… | |
136 c->res.status = s; | |
137 goto err; | |
138 } | |
139 | |
140 /* if the buffer remains empty, we are d… | |
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… | |
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 nslo… | |
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-ad… | |
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) … | |
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 inn… | |
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.typ… | |
203 /* | |
204 * mixed response types; progress | |
205 * is not comparable | |
206 * | |
207 * the res-type-enum is ordered … | |
208 * DIRLISTING, ERROR, FILE, i.e. | |
209 * in rising priority, because a | |
210 * file transfer is most importa… | |
211 * followed by error-messages. | |
212 * Dirlistings as an "interactiv… | |
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 res… | |
225 * type, C_RECV_HEADER and C_SEN… | |
226 * it is sufficient to compare t… | |
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 nslo… | |
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 } |