summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--opentracker.c7
-rw-r--r--ot_livesync.c12
-rw-r--r--ot_socket.c7
-rw-r--r--ot_socket.h4
-rw-r--r--ot_udp.c58
-rw-r--r--tests/t_socket.c87
-rw-r--r--trackerlogic.h4
7 files changed, 103 insertions, 76 deletions
diff --git a/opentracker.c b/opentracker.c
index ab4a934..e0f16d7 100644
--- a/opentracker.c
+++ b/opentracker.c
@@ -49,7 +49,9 @@ static void panic(const char *routine) {
49 exit(111); 49 exit(111);
50} 50}
51 51
52/* Say we are stopping, wake the loop, and let main do the work on its own stack. */ 52/* Say we are stopping, wake the loop, and let main do the work on its own
53 stack. Everything other than SIGINT falls through and does nothing, which
54 is what OT_SIGNAL_WAKE wants: returning from the read is all it is for. */
53static void signal_handler(int s) { 55static void signal_handler(int s) {
54 if (s == SIGINT) { 56 if (s == SIGINT) {
55 const char byte = 0; 57 const char byte = 0;
@@ -85,7 +87,8 @@ static void install_signal_handlers(void) {
85 sa.sa_handler = signal_handler; 87 sa.sa_handler = signal_handler;
86 sigemptyset(&sa.sa_mask); 88 sigemptyset(&sa.sa_mask);
87 sa.sa_flags = 0; 89 sa.sa_flags = 0;
88 if ((sigaction(SIGINT, &sa, NULL) == -1) || (sigaction(SIGALRM, &sa, NULL) == -1)) 90 if ((sigaction(SIGINT, &sa, NULL) == -1) || (sigaction(SIGALRM, &sa, NULL) == -1) ||
91 (sigaction(OT_SIGNAL_WAKE, &sa, NULL) == -1))
89 panic("install_signal_handlers"); 92 panic("install_signal_handlers");
90 93
91 sigaddset(&signal_mask, SIGINT); 94 sigaddset(&signal_mask, SIGINT);
diff --git a/ot_livesync.c b/ot_livesync.c
index e6db720..4a0ede1 100644
--- a/ot_livesync.c
+++ b/ot_livesync.c
@@ -32,9 +32,6 @@ ot_ip6 groupip_1 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 224, 0, 23, 5};
32 32
33#define LIVESYNC_MAXDELAY 15 /* seconds */ 33#define LIVESYNC_MAXDELAY 15 /* seconds */
34 34
35/* How long the worker may sit in a read before it looks at the running flag */
36#define LIVESYNC_WORKER_PATIENCE 1 /* seconds */
37
38enum { OT_SYNC_PEER4, OT_SYNC_PEER6 }; 35enum { OT_SYNC_PEER4, OT_SYNC_PEER6 };
39 36
40/* Forward declaration */ 37/* Forward declaration */
@@ -58,6 +55,7 @@ static sync_buffer g_v4_buf;
58 55
59static pthread_t thread_id; 56static pthread_t thread_id;
60static int thread_started; 57static int thread_started;
58static _Atomic int thread_left;
61void livesync_init(void) { 59void livesync_init(void) {
62 uint32_t packet_type6 = htonl(OT_SYNC_PEER6); 60 uint32_t packet_type6 = htonl(OT_SYNC_PEER6);
63 uint32_t packet_type4 = htonl(OT_SYNC_PEER4); 61 uint32_t packet_type4 = htonl(OT_SYNC_PEER4);
@@ -78,13 +76,16 @@ void livesync_init(void) {
78 g_v6_buf.next_packet_time = time_future_seconds(LIVESYNC_MAXDELAY); 76 g_v6_buf.next_packet_time = time_future_seconds(LIVESYNC_MAXDELAY);
79 g_v4_buf.next_packet_time = time_future_seconds(LIVESYNC_MAXDELAY); 77 g_v4_buf.next_packet_time = time_future_seconds(LIVESYNC_MAXDELAY);
80 78
81 socket_set_timeout(g_socket_in, LIVESYNC_WORKER_PATIENCE);
82 thread_started = !pthread_create(&thread_id, NULL, livesync_worker, NULL); 79 thread_started = !pthread_create(&thread_id, NULL, livesync_worker, NULL);
83} 80}
84 81
85void livesync_deinit(void) { 82void livesync_deinit(void) {
86 if (thread_started) { 83 if (thread_started) {
87 shutdown(g_socket_in, SHUT_RD); 84 /* Repeats so it interrupts a read already under way */
85 while (!atomic_load(&thread_left)) {
86 pthread_kill(thread_id, OT_SIGNAL_WAKE);
87 usleep(1000);
88 }
88 pthread_join(thread_id, NULL); 89 pthread_join(thread_id, NULL);
89 thread_started = 0; 90 thread_started = 0;
90 } 91 }
@@ -235,6 +236,7 @@ static void *livesync_worker(void *args) {
235 } 236 }
236 237
237 free(ws.inbuf); 238 free(ws.inbuf);
239 atomic_store(&thread_left, 1);
238 return NULL; 240 return NULL;
239} 241}
240 242
diff --git a/ot_socket.c b/ot_socket.c
index c73738c..158459f 100644
--- a/ot_socket.c
+++ b/ot_socket.c
@@ -9,7 +9,6 @@
9#include <netinet/in.h> 9#include <netinet/in.h>
10#include <string.h> 10#include <string.h>
11#include <sys/socket.h> 11#include <sys/socket.h>
12#include <sys/time.h>
13#include <unistd.h> 12#include <unistd.h>
14 13
15/* Opentracker */ 14/* Opentracker */
@@ -49,12 +48,6 @@ int socket_set_blocking(int s, int blocking) {
49 return fcntl(s, F_SETFL, blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK)); 48 return fcntl(s, F_SETFL, blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK));
50} 49}
51 50
52int socket_set_timeout(int s, int seconds) {
53 struct timeval patience = {seconds, 0};
54
55 return setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &patience, sizeof(patience));
56}
57
58static int set_nonblocking(int s) { return socket_set_blocking(s, 0); } 51static int set_nonblocking(int s) { return socket_set_blocking(s, 0); }
59 52
60/* Close without letting the failure we are about to report be overwritten */ 53/* Close without letting the failure we are about to report be overwritten */
diff --git a/ot_socket.h b/ot_socket.h
index 9340d0d..f73a5c9 100644
--- a/ot_socket.h
+++ b/ot_socket.h
@@ -39,10 +39,6 @@ int socket_is_connected(int s);
39int socket_accept(int s, ot_ip6 ip, uint16_t *port); 39int socket_accept(int s, ot_ip6 ip, uint16_t *port);
40int socket_set_blocking(int s, int blocking); 40int socket_set_blocking(int s, int blocking);
41 41
42/* Bound how long a blocking read waits, so the thread sitting in one gets to
43 look at the running flag now and then */
44int socket_set_timeout(int s, int seconds);
45
46ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port); 42ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port);
47ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port); 43ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port);
48 44
diff --git a/ot_udp.c b/ot_udp.c
index 7aa0e61..1ec5148 100644
--- a/ot_udp.c
+++ b/ot_udp.c
@@ -9,7 +9,7 @@
9#include <stdio.h> 9#include <stdio.h>
10#include <stdlib.h> 10#include <stdlib.h>
11#include <string.h> 11#include <string.h>
12#include <sys/socket.h> 12#include <unistd.h>
13 13
14/* Opentracker */ 14/* Opentracker */
15#include "ot_ip.h" 15#include "ot_ip.h"
@@ -20,16 +20,16 @@
20#include "ot_udp.h" 20#include "ot_udp.h"
21#include "trackerlogic.h" 21#include "trackerlogic.h"
22 22
23/* How long a worker may sit in a read before it looks at the running flag */ 23/* One of these per worker, each allocated on its own so that growing the
24#define OT_UDP_WORKER_PATIENCE 1 24 array below never moves the one a running worker holds. */
25
26struct udp_worker { 25struct udp_worker {
27 pthread_t thread; 26 pthread_t thread;
28 int64_t sock; 27 int64_t sock;
28 _Atomic int left;
29}; 29};
30 30
31static struct udp_worker *g_workers; 31static struct udp_worker **g_workers;
32static size_t g_worker_count; 32static size_t g_worker_count;
33 33
34static uint32_t g_rijndael_round_key[44] = {0}; 34static uint32_t g_rijndael_round_key[44] = {0};
35static volatile uint32_t g_key_of_the_hour[2] = {0}; 35static volatile uint32_t g_key_of_the_hour[2] = {0};
@@ -218,7 +218,7 @@ int handle_udp6(int64_t serversocket, struct ot_workstruct *ws) {
218} 218}
219 219
220static void *udp_worker(void *args) { 220static void *udp_worker(void *args) {
221 int64_t sock = (int64_t)args; 221 struct udp_worker *me = args;
222 struct ot_workstruct ws; 222 struct ot_workstruct ws;
223 memset(&ws, 0, sizeof(ws)); 223 memset(&ws, 0, sizeof(ws));
224 224
@@ -230,7 +230,7 @@ static void *udp_worker(void *args) {
230#endif 230#endif
231 231
232 while (g_opentracker_running) 232 while (g_opentracker_running)
233 handle_udp6(sock, &ws); 233 handle_udp6(me->sock, &ws);
234 234
235 stats_counters_retire(&ws); 235 stats_counters_retire(&ws);
236 236
@@ -239,6 +239,8 @@ static void *udp_worker(void *args) {
239#ifdef _DEBUG_HTTPERROR 239#ifdef _DEBUG_HTTPERROR
240 free(ws.debugbuf); 240 free(ws.debugbuf);
241#endif 241#endif
242
243 atomic_store(&me->left, 1);
242 return NULL; 244 return NULL;
243} 245}
244 246
@@ -248,34 +250,44 @@ void udp_init(void) {
248} 250}
249 251
250void udp_worker_install(int64_t sock) { 252void udp_worker_install(int64_t sock) {
251 struct udp_worker *grown = realloc(g_workers, (g_worker_count + 1) * sizeof(*g_workers)); 253 struct udp_worker **grown = realloc(g_workers, (g_worker_count + 1) * sizeof(*g_workers));
254 struct udp_worker *worker;
252 255
253 if (!grown) { 256 if (!grown || !(worker = calloc(1, sizeof(*worker)))) {
254 fprintf(stderr, "Warning: out of memory installing a udp worker, this socket stays with the workers already running.\n"); 257 fprintf(stderr, "Warning: out of memory installing a udp worker, this socket stays with the workers already running.\n");
258 g_workers = grown ? grown : g_workers;
255 return; 259 return;
256 } 260 }
257 g_workers = grown; 261 g_workers = grown;
262 worker->sock = sock;
258 263
259#ifdef _DEBUG 264#ifdef _DEBUG
260 fprintf(stderr, "Installing a worker on udp socket %ld\n", (unsigned long)sock); 265 fprintf(stderr, "Installing a worker on udp socket %ld\n", (unsigned long)sock);
261#endif 266#endif
262 socket_set_blocking((int)sock, 1); 267 socket_set_blocking((int)sock, 1);
263 socket_set_timeout((int)sock, OT_UDP_WORKER_PATIENCE);
264 268
265 g_workers[g_worker_count].sock = sock; 269 if (pthread_create(&worker->thread, NULL, udp_worker, worker))
266 if (!pthread_create(&g_workers[g_worker_count].thread, NULL, udp_worker, (void *)sock)) 270 free(worker);
267 ++g_worker_count; 271 else
272 g_workers[g_worker_count++] = worker;
268} 273}
269 274
270void udp_workers_deinit(void) { 275void udp_workers_deinit(void) {
271 size_t i; 276 size_t i;
272 277
273 /* The receive timeout bounds this on its own, shutting the socket down 278 for (i = 0; i < g_worker_count; ++i) {
274 cuts it short wherever the platform wakes a blocked reader */ 279 struct udp_worker *worker = g_workers[i];
275 for (i = 0; i < g_worker_count; ++i) 280
276 shutdown((int)g_workers[i].sock, SHUT_RD); 281 /* Repeated because a signal only interrupts a read already under way: a
277 for (i = 0; i < g_worker_count; ++i) 282 worker that was between two reads when the first one arrived is inside
278 pthread_join(g_workers[i].thread, NULL); 283 the next one by now. */
284 while (!atomic_load(&worker->left)) {
285 pthread_kill(worker->thread, OT_SIGNAL_WAKE);
286 usleep(1000);
287 }
288 pthread_join(worker->thread, NULL);
289 free(worker);
290 }
279 291
280 free(g_workers); 292 free(g_workers);
281 g_workers = NULL; 293 g_workers = NULL;
diff --git a/tests/t_socket.c b/tests/t_socket.c
index ede8ae0..ab4b785 100644
--- a/tests/t_socket.c
+++ b/tests/t_socket.c
@@ -8,14 +8,18 @@
8#include <fcntl.h> 8#include <fcntl.h>
9#include <netinet/in.h> 9#include <netinet/in.h>
10#include <poll.h> 10#include <poll.h>
11#include <pthread.h>
12#include <signal.h>
13#include <stdatomic.h>
11#include <string.h> 14#include <string.h>
12#include <sys/socket.h> 15#include <sys/socket.h>
13#include <time.h> 16#include <sys/time.h>
14#include <unistd.h> 17#include <unistd.h>
15 18
16/* Opentracker */ 19/* Opentracker */
17#include "ot_ip.h" 20#include "ot_ip.h"
18#include "ot_socket.h" 21#include "ot_socket.h"
22#include "trackerlogic.h"
19#include "test.h" 23#include "test.h"
20 24
21/* These cases talk to the loopback interface rather than to a mock, because 25/* These cases talk to the loopback interface rather than to a mock, because
@@ -145,42 +149,55 @@ static void case_udp_roundtrip(void) {
145 close(s); 149 close(s);
146} 150}
147 151
148/* udp_workers_deinit relies on a worker parked in a read coming back. The 152/* udp_workers_deinit and livesync_deinit end a read another thread is parked
149 receive timeout is what guarantees that; shutting the socket down is what 153 in by signalling that thread. Which mechanisms wake a blocked reader
150 makes it prompt where the platform allows it. Neither is uniform across the 154 differs between kernels -- shutting the socket down does so on Linux and
151 systems opentracker runs on, so both are checked rather than assumed. */ 155 FreeBSD but not on Darwin, closing it does so on Darwin but not on Linux --
152static void case_blocking_read_comes_back(void) { 156 so the one the tracker relies on is checked rather than assumed. The
153 char buf[32], ip[16], own_ip[16]; 157 watchdog is the test's own: where a reader is not woken this fails with a
154 uint16_t port = 0, own_port = 0; 158 figure to look at instead of hanging the suite. */
155 time_t started; 159static _Atomic int reader_returned;
156 int s; 160static int reader_socket;
157 161
158 test_case("a receive timeout does not delay a datagram that is already there"); 162static void reader_wake(int s) { (void)s; }
159 s = socket_udp_bind(ip_loopback, 0, SOCKET_EXCLUSIVE);
160 CHECK(s != -1);
161 CHECK_INT(socket_set_blocking(s, 1), 0);
162 CHECK_INT(socket_set_timeout(s, 1), 0);
163 bound_address(s, own_ip, &own_port);
164 CHECK_INT(socket_send(s, "opentracker", 11, own_ip, own_port), 11);
165 CHECK_INT(socket_recv(s, buf, sizeof(buf), ip, &port), 11);
166 163
167 test_case("and ends a read that would otherwise never return"); 164static void *blocked_reader(void *args) {
168 started = time(NULL); 165 char buf[32], ip[16];
169 CHECK_INT(socket_recv(s, buf, sizeof(buf), ip, &port), -1); 166 uint16_t port = 0;
170 CHECK(errno == EAGAIN || errno == EWOULDBLOCK);
171 CHECK(time(NULL) - started <= 3);
172 close(s);
173 167
174 test_case("shutting the socket down ends one too"); 168 (void)args;
175 s = socket_udp_bind(ip_loopback, 0, SOCKET_EXCLUSIVE); 169 socket_recv(reader_socket, buf, sizeof(buf), ip, &port);
176 CHECK(s != -1); 170 atomic_store(&reader_returned, 1);
177 CHECK_INT(socket_set_blocking(s, 1), 0); 171 return NULL;
178 CHECK_INT(socket_set_timeout(s, 1), 0); 172}
179 shutdown(s, SHUT_RD); 173
180 started = time(NULL); 174static void case_a_signal_ends_a_blocking_read(void) {
181 CHECK(socket_recv(s, buf, sizeof(buf), ip, &port) <= 0); 175 struct sigaction sa;
182 CHECK(time(NULL) - started <= 3); 176 struct timeval watchdog = {2, 0};
183 close(s); 177 pthread_t thread;
178 int waited;
179
180 test_case("a signal ends a read another thread is parked in");
181 memset(&sa, 0, sizeof(sa));
182 sa.sa_handler = reader_wake;
183 sa.sa_flags = 0; /* SA_RESTART would resume the read instead of ending it */
184 CHECK_INT(sigaction(OT_SIGNAL_WAKE, &sa, NULL), 0);
185
186 atomic_store(&reader_returned, 0);
187 reader_socket = socket_udp_bind(ip_loopback, 0, SOCKET_EXCLUSIVE);
188 CHECK(reader_socket != -1);
189 CHECK_INT(socket_set_blocking(reader_socket, 1), 0);
190 CHECK_INT(setsockopt(reader_socket, SOL_SOCKET, SO_RCVTIMEO, &watchdog, sizeof(watchdog)), 0);
191
192 CHECK_INT(pthread_create(&thread, NULL, blocked_reader, NULL), 0);
193 usleep(100000); /* long enough to be sure it reached the read */
194 CHECK_INT(pthread_kill(thread, OT_SIGNAL_WAKE), 0);
195
196 for (waited = 0; waited < 1000 && !atomic_load(&reader_returned); ++waited)
197 usleep(1000);
198 CHECK(waited < 1000);
199 pthread_join(thread, NULL);
200 close(reader_socket);
184} 201}
185 202
186/* An IPv4 peer must arrive in the v4 mapped form opentracker stores, however 203/* An IPv4 peer must arrive in the v4 mapped form opentracker stores, however
@@ -332,7 +349,7 @@ void test_socket(void) {
332 case_sharing_is_opt_in(); 349 case_sharing_is_opt_in();
333#endif 350#endif
334 case_udp_roundtrip(); 351 case_udp_roundtrip();
335 case_blocking_read_comes_back(); 352 case_a_signal_ends_a_blocking_read();
336 case_udp_v4mapped(); 353 case_udp_v4mapped();
337 case_tcp_roundtrip(); 354 case_tcp_roundtrip();
338 case_connect_refused(); 355 case_connect_refused();
diff --git a/trackerlogic.h b/trackerlogic.h
index ad6c911..7d0693a 100644
--- a/trackerlogic.h
+++ b/trackerlogic.h
@@ -6,6 +6,7 @@
6#ifndef OT_TRACKERLOGIC_H__ 6#ifndef OT_TRACKERLOGIC_H__
7#define OT_TRACKERLOGIC_H__ 7#define OT_TRACKERLOGIC_H__
8 8
9#include <signal.h>
9#include <stdatomic.h> 10#include <stdatomic.h>
10#include <stdint.h> 11#include <stdint.h>
11#include <stdlib.h> 12#include <stdlib.h>
@@ -90,6 +91,9 @@ static inline ot_time time_future_seconds(int span) { return time_now_seconds()
90/* Cleared from the interrupt handler and read by every worker */ 91/* Cleared from the interrupt handler and read by every worker */
91extern _Atomic int g_opentracker_running; 92extern _Atomic int g_opentracker_running;
92 93
94/* Sent to a thread parked in a blocking read */
95#define OT_SIGNAL_WAKE SIGUSR1
96
93 97
94extern uint32_t g_tracker_id; 98extern uint32_t g_tracker_id;
95typedef enum { FLAG_TCP, FLAG_UDP, FLAG_MCA, FLAG_SELFPIPE } PROTO_FLAG; 99typedef enum { FLAG_TCP, FLAG_UDP, FLAG_MCA, FLAG_SELFPIPE } PROTO_FLAG;