summaryrefslogtreecommitdiff
path: root/src/ot_tcp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ot_tcp.c')
-rw-r--r--src/ot_tcp.c180
1 files changed, 180 insertions, 0 deletions
diff --git a/src/ot_tcp.c b/src/ot_tcp.c
new file mode 100644
index 0000000..1351b92
--- /dev/null
+++ b/src/ot_tcp.c
@@ -0,0 +1,180 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#include <errno.h>
6#include <pthread.h>
7#include <stdatomic.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <unistd.h>
12
13/* Opentracker */
14#include "ot_batch.h"
15#include "ot_buf.h"
16#include "ot_event.h"
17#include "ot_fdstate.h"
18#include "ot_http.h"
19#include "ot_mutex.h"
20#include "ot_socket.h"
21#include "ot_worker.h"
22#include "ot_stats.h"
23#include "ot_tcp.h"
24#include "trackerlogic.h"
25
26static ssize_t header_complete(char *request, ssize_t byte_count) {
27 ssize_t i = 0, state = 0;
28
29 for (i = 1; i < byte_count; i += 2)
30 if (request[i] <= 13) {
31 i--;
32 for (state = 0; i < byte_count; ++i) {
33 char c = request[i];
34 if (c == '\r' || c == '\n')
35 state = (state >> 2) | ((c << 6) & 0xc0);
36 else
37 break;
38 if (state >= 0xa0 || state == 0x99)
39 return i + 1;
40 }
41 }
42 return 0;
43}
44
45void tcp_read(const int64_t sock, struct ot_workstruct *ws) {
46 struct http_data *cookie = fdstate_cookie(sock);
47 ssize_t byte_count = read((int)sock, ws->inbuf, G_INBUF_SIZE);
48
49 if (byte_count == 0 || (byte_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) {
50 http_close(sock);
51 return;
52 }
53
54 if (byte_count == -1)
55 return;
56
57 ws->request = NULL;
58 ws->request_size = 0;
59 ws->header_size = 0;
60 ws->keep_alive = 0;
61
62 /* If we get the whole request in one packet, handle it without copying */
63 if (!cookie->request.data) {
64 if ((ws->header_size = header_complete(ws->inbuf, byte_count))) {
65 ws->request = ws->inbuf;
66 ws->request_size = byte_count;
67 http_handle_request(sock, ws);
68 } else if (buf_cat(&cookie->request, ws->inbuf, (size_t)byte_count))
69 http_close(sock);
70 return;
71 }
72
73 if (buf_cat(&cookie->request, ws->inbuf, (size_t)byte_count) || cookie->request.size > G_INBUF_SIZE) {
74 http_issue_error(sock, ws, CODE_HTTPERROR_500);
75 return;
76 }
77
78 while ((ws->header_size = header_complete(cookie->request.data, (ssize_t)cookie->request.size))) {
79 ws->request = cookie->request.data;
80 ws->request_size = (ssize_t)cookie->request.size;
81 http_handle_request(sock, ws);
82#ifdef WANT_KEEPALIVE
83 if (!ws->keep_alive)
84#endif
85 return;
86 }
87}
88
89void tcp_write(const int64_t sock) {
90 struct http_data *cookie = fdstate_cookie(sock);
91 int chunked = 0;
92 int keep_alive = 0;
93
94 if (cookie) {
95 chunked = (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) != 0;
96 keep_alive = (cookie->flag & STRUCT_HTTP_FLAG_KEEPALIVE) != 0;
97
98 if (cookie->batch) {
99 if (batch_send(sock, cookie->batch) == BATCH_FAILED) {
100 http_close(sock);
101 return;
102 }
103 if (batch_pending(cookie->batch))
104 return;
105 }
106 }
107
108 /* In a chunked transfer, wait for the next piece once everything queued has gone out */
109 if (chunked)
110 http_interest_set(sock, cookie, 0);
111 else if (keep_alive) {
112 /* The reply is out and the client asked to keep the connection, so go
113 back to waiting for the next request */
114 http_interest_set(sock, cookie, EVENT_IN);
115 fdstate_set_expiry(sock, time_future_seconds(OT_CLIENT_TIMEOUT));
116 } else
117 http_close(sock);
118}
119
120/* The fast path, assuming that the complete request has usually arrived by the time a
121 connection is accepted, so it is answered here outside the event loop. The more load
122 a tracker has, the more likely it is that by the time it accepts, the roundtrip to
123 the peer has completed.
124 If the request could not be completely handled, the stack cookie is promoted to heap
125 and the connection handed over to the event loop to finish */
126void tcp_accept(const int64_t serversocket, struct ot_workstruct *ws) {
127 int64_t sock;
128 ot_ip6 ip;
129 uint16_t port;
130
131 while ((sock = socket_accept(serversocket, ip, &port)) != -1) {
132 struct http_data cookie;
133 uint32_t generation;
134
135 memset(&cookie, 0, sizeof(cookie));
136 memcpy(cookie.ip, ip, sizeof(ot_ip6));
137 cookie.interest = EVENT_IN;
138 cookie.flag = STRUCT_HTTP_FLAG_ONSTACK;
139
140 generation = fdstate_open(sock, &cookie, time_future_seconds(OT_CLIENT_TIMEOUT));
141 if (!fdstate_claim((int)sock, generation)) {
142 fdstate_close(sock);
143 event_close((int)sock);
144 continue;
145 }
146
147 stats_issue_event(ws, EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip);
148
149 tcp_read(sock, ws);
150
151 /* Gone already if it was answered and closed inline */
152 if (fdstate_generation((int)sock) != generation)
153 continue;
154
155 /* Handling it may already have given it a heap home and handed it to
156 another thread, and then it is no longer this frame's to touch */
157 if (fdstate_cookie((int)sock) != &cookie)
158 fdstate_release((int)sock);
159 else if (!http_adopt(sock))
160 http_close(sock);
161 else
162 fdstate_release((int)sock);
163 }
164}
165
166/* A worker blocks in accept and nowhere else: the connection it is handed is
167 non blocking, so a client that stops talking is given to the event loop
168 rather than keeping this thread. */
169static void tcp_run(struct ot_worker *me) {
170 while (g_opentracker_running)
171 tcp_accept(me->sock, &me->ws);
172}
173
174void tcp_worker_install(int64_t sock) {
175 socket_set_blocking((int)sock, 1);
176 socket_defer_accept((int)sock);
177
178 if (worker_start(tcp_run, sock) == -1)
179 fprintf(stderr, "Warning: could not install a tcp worker, this socket stays with the workers already running.\n");
180}