summaryrefslogtreecommitdiff
path: root/ot_tcp.c
diff options
context:
space:
mode:
Diffstat (limited to 'ot_tcp.c')
-rw-r--r--ot_tcp.c238
1 files changed, 238 insertions, 0 deletions
diff --git a/ot_tcp.c b/ot_tcp.c
new file mode 100644
index 0000000..47697ca
--- /dev/null
+++ b/ot_tcp.c
@@ -0,0 +1,238 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <errno.h>
8#include <pthread.h>
9#include <stdatomic.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <unistd.h>
14
15/* Opentracker */
16#include "ot_batch.h"
17#include "ot_buf.h"
18#include "ot_event.h"
19#include "ot_fdstate.h"
20#include "ot_http.h"
21#include "ot_mutex.h"
22#include "ot_socket.h"
23#include "ot_stats.h"
24#include "ot_tcp.h"
25#include "trackerlogic.h"
26
27static ssize_t header_complete(char *request, ssize_t byte_count) {
28 ssize_t i = 0, state = 0;
29
30 for (i = 1; i < byte_count; i += 2)
31 if (request[i] <= 13) {
32 i--;
33 for (state = 0; i < byte_count; ++i) {
34 char c = request[i];
35 if (c == '\r' || c == '\n')
36 state = (state >> 2) | ((c << 6) & 0xc0);
37 else
38 break;
39 if (state >= 0xa0 || state == 0x99)
40 return i + 1;
41 }
42 }
43 return 0;
44}
45
46void tcp_read(const int64_t sock, struct ot_workstruct *ws) {
47 struct http_data *cookie = fdstate_cookie(sock);
48 ssize_t byte_count = read((int)sock, ws->inbuf, G_INBUF_SIZE);
49
50 if (byte_count == 0 || (byte_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) {
51 http_close(sock);
52 return;
53 }
54
55 if (byte_count == -1)
56 return;
57
58 ws->request = NULL;
59 ws->request_size = 0;
60 ws->header_size = 0;
61 ws->keep_alive = 0;
62
63 /* If we get the whole request in one packet, handle it without copying */
64 if (!cookie->request.data) {
65 if ((ws->header_size = header_complete(ws->inbuf, byte_count))) {
66 ws->request = ws->inbuf;
67 ws->request_size = byte_count;
68 http_handle_request(sock, ws);
69 } else if (buf_cat(&cookie->request, ws->inbuf, (size_t)byte_count))
70 http_close(sock);
71 return;
72 }
73
74 if (buf_cat(&cookie->request, ws->inbuf, (size_t)byte_count) || cookie->request.size > G_INBUF_SIZE) {
75 http_issue_error(sock, ws, CODE_HTTPERROR_500);
76 return;
77 }
78
79 while ((ws->header_size = header_complete(cookie->request.data, (ssize_t)cookie->request.size))) {
80 ws->request = cookie->request.data;
81 ws->request_size = (ssize_t)cookie->request.size;
82 http_handle_request(sock, ws);
83#ifdef WANT_KEEPALIVE
84 if (!ws->keep_alive)
85#endif
86 return;
87 }
88}
89
90void tcp_write(const int64_t sock) {
91 struct http_data *cookie = fdstate_cookie(sock);
92 int chunked = 0;
93 int keep_alive = 0;
94
95 if (cookie) {
96 chunked = (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) != 0;
97 keep_alive = (cookie->flag & STRUCT_HTTP_FLAG_KEEPALIVE) != 0;
98
99 if (cookie->batch) {
100 if (batch_send(sock, cookie->batch) == BATCH_FAILED) {
101 http_close(sock);
102 return;
103 }
104 if (batch_pending(cookie->batch))
105 return;
106 }
107 }
108
109 /* In a chunked transfer, wait for the next piece once everything queued has gone out */
110 if (chunked)
111 http_interest_set(sock, cookie, 0);
112 else if (keep_alive) {
113 /* The reply is out and the client asked to keep the connection, so go
114 back to waiting for the next request */
115 http_interest_set(sock, cookie, EVENT_IN);
116 fdstate_set_expiry(sock, time_future_seconds(OT_CLIENT_TIMEOUT));
117 } else
118 http_close(sock);
119}
120
121/* The fast path, assuming that the complete request has usually arrived by the time a
122 connection is accepted, so it is answered here outside the event loop. The more load
123 a tracker has, the more likely it is that by the time it accepts, the roundtrip to
124 the peer has completed.
125 If the request could not be completely handled, the stack cookie is promoted to heap
126 and the connection handed over to the event loop to finish */
127void tcp_accept(const int64_t serversocket, struct ot_workstruct *ws) {
128 int64_t sock;
129 ot_ip6 ip;
130 uint16_t port;
131
132 while ((sock = socket_accept(serversocket, ip, &port)) != -1) {
133 struct http_data cookie;
134 uint32_t generation;
135
136 memset(&cookie, 0, sizeof(cookie));
137 memcpy(cookie.ip, ip, sizeof(ot_ip6));
138 cookie.interest = EVENT_IN;
139 cookie.flag = STRUCT_HTTP_FLAG_ONSTACK;
140
141 generation = fdstate_open(sock, &cookie, time_future_seconds(OT_CLIENT_TIMEOUT));
142 if (!fdstate_claim((int)sock, generation)) {
143 fdstate_close(sock);
144 event_close((int)sock);
145 continue;
146 }
147
148 stats_issue_event(ws, EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip);
149
150 tcp_read(sock, ws);
151
152 /* Gone already if it was answered and closed inline */
153 if (fdstate_generation((int)sock) != generation)
154 continue;
155
156 /* Handling it may already have given it a heap home and handed it to
157 another thread, and then it is no longer this frame's to touch */
158 if (fdstate_cookie((int)sock) != &cookie)
159 fdstate_release((int)sock);
160 else if (!http_adopt(sock))
161 http_close(sock);
162 else
163 fdstate_release((int)sock);
164 }
165}
166
167/* One of these per worker, each allocated on its own so that growing the
168 array below never moves the one a running worker holds. */
169struct tcp_worker {
170 pthread_t thread;
171 int64_t sock;
172 _Atomic int left;
173};
174
175static struct tcp_worker **g_workers;
176static size_t g_worker_count;
177
178/* A worker blocks in accept and nowhere else: the connection it is handed is
179 non blocking, so a client that stops talking is given to the event loop
180 rather than keeping this thread. */
181static void *tcp_worker(void *args) {
182 struct tcp_worker *me = args;
183 struct ot_workstruct ws;
184
185 if (workstruct_init(&ws) == -1) {
186 fprintf(stderr, "Warning: a tcp worker could not get its buffers and will not run.\n");
187 atomic_store(&me->left, 1);
188 return NULL;
189 }
190
191 while (g_opentracker_running)
192 tcp_accept(me->sock, &ws);
193
194 workstruct_deinit(&ws);
195 atomic_store(&me->left, 1);
196 return NULL;
197}
198
199void tcp_worker_install(int64_t sock) {
200 struct tcp_worker **grown = realloc(g_workers, (g_worker_count + 1) * sizeof(*g_workers));
201 struct tcp_worker *worker;
202
203 if (!grown || !(worker = calloc(1, sizeof(*worker)))) {
204 fprintf(stderr, "Warning: out of memory installing a tcp worker, this socket stays with the workers already running.\n");
205 g_workers = grown ? grown : g_workers;
206 return;
207 }
208 g_workers = grown;
209 worker->sock = sock;
210
211 socket_set_blocking((int)sock, 1);
212 socket_defer_accept((int)sock);
213
214 if (pthread_create(&worker->thread, NULL, tcp_worker, worker))
215 free(worker);
216 else
217 g_workers[g_worker_count++] = worker;
218}
219
220void tcp_workers_deinit(void) {
221 size_t i;
222
223 for (i = 0; i < g_worker_count; ++i) {
224 struct tcp_worker *worker = g_workers[i];
225
226 /* Repeated because a signal only interrupts a call already under way */
227 while (!atomic_load(&worker->left)) {
228 pthread_kill(worker->thread, OT_SIGNAL_WAKE);
229 usleep(1000);
230 }
231 pthread_join(worker->thread, NULL);
232 free(worker);
233 }
234
235 free(g_workers);
236 g_workers = NULL;
237 g_worker_count = 0;
238}