diff options
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | DESIGN | 99 | ||||
| -rw-r--r-- | Makefile | 4 | ||||
| -rw-r--r-- | README | 12 | ||||
| -rw-r--r-- | opentracker.c | 127 | ||||
| -rw-r--r-- | opentracker.xcodeproj/project.pbxproj | 320 | ||||
| -rw-r--r-- | ot_config.c | 26 | ||||
| -rw-r--r-- | ot_event.c | 9 | ||||
| -rw-r--r-- | ot_event.h | 5 | ||||
| -rw-r--r-- | ot_fdstate.c | 7 | ||||
| -rw-r--r-- | ot_fdstate.h | 3 | ||||
| -rw-r--r-- | ot_http.c | 29 | ||||
| -rw-r--r-- | ot_http.h | 7 | ||||
| -rw-r--r-- | ot_tcp.c | 157 | ||||
| -rw-r--r-- | ot_tcp.h | 22 | ||||
| -rw-r--r-- | tests/accept_filter_probe.py | 110 | ||||
| -rw-r--r-- | tests/integration/test_config.py | 29 | ||||
| -rw-r--r-- | tests/integration/test_expiry.py | 7 | ||||
| -rw-r--r-- | tests/t_event.c | 32 |
19 files changed, 540 insertions, 468 deletions
| @@ -1,5 +1,5 @@ | |||
| 1 | *.o | ||
| 2 | *.dSYM | 1 | *.dSYM |
| 2 | *.o | ||
| 3 | opentracker | 3 | opentracker |
| 4 | opentracker.debug | 4 | opentracker.debug |
| 5 | opentracker.integration | 5 | opentracker.integration |
| @@ -7,4 +7,5 @@ opentracker.tsan | |||
| 7 | proxy | 7 | proxy |
| 8 | proxy.debug | 8 | proxy.debug |
| 9 | tests/runner | 9 | tests/runner |
| 10 | tests/runner.poll | ||
| 10 | tests/runner.sanitize | 11 | tests/runner.sanitize |
| @@ -0,0 +1,99 @@ | |||
| 1 | opentracker design notes | ||
| 2 | ======================== | ||
| 3 | |||
| 4 | Decisions that cost measurement to reach, and the alternatives that were | ||
| 5 | tried and rejected. | ||
| 6 | |||
| 7 | What the tracker is optimised for | ||
| 8 | --------------------------------- | ||
| 9 | |||
| 10 | UDP carries most of the traffic. An HTTP request costs about ten times a UDP | ||
| 11 | announce, 26 microseconds of tracker CPU against 2.5. Nearly all of that is | ||
| 12 | the kernel setting up and tearing down a connection rather than tracker | ||
| 13 | code. | ||
| 14 | |||
| 15 | A connection carries one request and closes. Keep-alive works and is rarely | ||
| 16 | asked for. | ||
| 17 | |||
| 18 | Statistics are approximate on purpose. Counters are per thread and summed | ||
| 19 | when read, so a /stats read can under-report once. Nothing drifts. | ||
| 20 | |||
| 21 | |||
| 22 | Decisions | ||
| 23 | --------- | ||
| 24 | |||
| 25 | One UDP socket per worker, where the kernel spreads datagrams over a group | ||
| 26 | bound to one address. Cost per datagram then stays flat as workers are added, | ||
| 27 | 0.71 to 0.72 microseconds from one worker to eight. Sharing one socket grows | ||
| 28 | it eightfold over the same range, 0.71 to 6.07. | ||
| 29 | |||
| 30 | listen.udp.workers defaults to 1. One worker beats none by 19 to 37 per cent | ||
| 31 | everywhere, because a blocking read costs one system call where the event | ||
| 32 | loop costs two. Beyond one the gain depends on the kernel balancing, so | ||
| 33 | raising it is the operator's decision. | ||
| 34 | |||
| 35 | A thread parked in a blocking read is woken by a signal, not by closing or | ||
| 36 | shutting down its socket: | ||
| 37 | |||
| 38 | mechanism Linux FreeBSD macOS | ||
| 39 | close() blocked blocked 0 ms | ||
| 40 | shutdown(SHUT_RD) 1 ms 1 ms blocked | ||
| 41 | SO_RCVTIMEO works works works | ||
| 42 | pthread_kill 1 ms 1 ms 1 ms | ||
| 43 | |||
| 44 | Only the signal works everywhere. A receive timeout also works, but wakes | ||
| 45 | every thread on every tick for the life of the process to catch something | ||
| 46 | that happens once: at eight workers it doubled idle context switches, 255 to | ||
| 47 | 495 over thirty seconds. | ||
| 48 | |||
| 49 | Each binary is built in one compiler invocation. Without object files a build | ||
| 50 | cannot link objects compiled with different FEATURES, which was a real | ||
| 51 | failure mode. It costs incremental rebuilds, 0.075 seconds becoming 1.56. | ||
| 52 | |||
| 53 | A socket is read or written, never both. http_interest_set replaces an | ||
| 54 | add-and-drop pair so that the invariant cannot be broken by accident, and the | ||
| 55 | event layer hears about a change once instead of twice. | ||
| 56 | |||
| 57 | Accept waits until the client's request has arrived: TCP_DEFER_ACCEPT on | ||
| 58 | Linux, the accf_data filter on FreeBSD. Measured against a flood of silent | ||
| 59 | connections, the filter serves more requests than no filter, not fewer, so it | ||
| 60 | is not the denial of service vector that FreeBSD-SA-02:26 once described. | ||
| 61 | |||
| 62 | |||
| 63 | Paths not taken | ||
| 64 | --------------- | ||
| 65 | |||
| 66 | accf_http, the HTTP-aware accept filter. It duplicates a parser the tracker | ||
| 67 | has anyway, puts more kernel code in the path, and holds a connection that | ||
| 68 | sends "GET /" and stops, which accf_data hands straight over. | ||
| 69 | |||
| 70 | recvmmsg and sendmmsg. No measurable gain on either FreeBSD machine, between | ||
| 71 | -4 and +10 per cent, and they do not exist on Darwin. | ||
| 72 | |||
| 73 | Link time optimisation. It cuts .text by 13 per cent, 93,497 bytes to 81,630, | ||
| 74 | and changes throughput not at all. A tracker waits on memory and system | ||
| 75 | calls, not on instruction fetch: the announce path is 6.6 KB of code against | ||
| 76 | a 32 KB instruction cache at worst. | ||
| 77 | |||
| 78 | A directory for object files. bmake has no pattern rules and the Makefile | ||
| 79 | builds under FreeBSD's make today. Building each binary in one invocation | ||
| 80 | clears the objects out of the tree without giving that up. | ||
| 81 | |||
| 82 | A src/ directory. 52 files sharing an ot_ prefix are a module list rather | ||
| 83 | than clutter, and build output was most of what made the tree look crowded. | ||
| 84 | |||
| 85 | |||
| 86 | Platform differences that cost us code | ||
| 87 | -------------------------------------- | ||
| 88 | |||
| 89 | SO_REUSEPORT means three different things. Linux spreads datagrams over the | ||
| 90 | group; FreeBSD permits the bind and needs SO_REUSEPORT_LB to spread. Darwin | ||
| 91 | hands every datagram to whichever socket bound last, so a group there starves | ||
| 92 | all but one reader. | ||
| 93 | |||
| 94 | Waking a blocked reader differs per kernel. see the table above. | ||
| 95 | |||
| 96 | Deferring accept is TCP_DEFER_ACCEPT, or accf_data, or nothing. | ||
| 97 | |||
| 98 | make is bmake on FreeBSD and GNU make elsewhere, and the Makefile has to | ||
| 99 | build under both. | ||
| @@ -69,8 +69,8 @@ CFLAGS_libowfat=-I$(LIBOWFAT_HEADERS) | |||
| 69 | LDFLAGS_libowfat=-L$(LIBOWFAT_LIBRARY) -lowfat | 69 | LDFLAGS_libowfat=-L$(LIBOWFAT_LIBRARY) -lowfat |
| 70 | 70 | ||
| 71 | BINARY =opentracker | 71 | BINARY =opentracker |
| 72 | HEADERS=trackerlogic.h ot_urlencoded.h ot_batch.h ot_buf.h ot_event.h ot_fdstate.h ot_fmt.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h | 72 | HEADERS=trackerlogic.h ot_urlencoded.h ot_batch.h ot_buf.h ot_event.h ot_fdstate.h ot_fmt.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_tcp.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h |
| 73 | SOURCES=opentracker.c trackerlogic.c ot_urlencoded.c ot_batch.c ot_buf.c ot_event.c ot_fdstate.c ot_fmt.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c | 73 | SOURCES=opentracker.c trackerlogic.c ot_urlencoded.c ot_batch.c ot_buf.c ot_event.c ot_fdstate.c ot_fmt.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_tcp.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c |
| 74 | SOURCES_proxy=proxy.c ot_batch.c ot_fdstate.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c | 74 | SOURCES_proxy=proxy.c ot_batch.c ot_fdstate.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c |
| 75 | 75 | ||
| 76 | all: $(BINARY) $(BINARY).debug | 76 | all: $(BINARY) $(BINARY).debug |
| @@ -1,14 +1,8 @@ | |||
| 1 | This is opentracker. An open bittorrent tracker. | 1 | This is opentracker. An open bittorrent tracker. |
| 2 | 2 | ||
| 3 | You need libowfat (http://www.fefe.de/libowfat/). | ||
| 4 | |||
| 5 | Steps to go: | 3 | Steps to go: |
| 6 | 4 | ||
| 7 | cvs -d :pserver:cvs@cvs.fefe.de:/cvs -z9 co libowfat | 5 | git clone git://erdgeist.org/opentracker |
| 8 | cd libowfat | ||
| 9 | make | ||
| 10 | cd .. | ||
| 11 | cvs -d:pserver:anoncvs@cvs.erdgeist.org:/home/cvsroot co opentracker | ||
| 12 | cd opentracker | 6 | cd opentracker |
| 13 | make | 7 | make |
| 14 | ./opentracker | 8 | ./opentracker |
| @@ -24,7 +18,3 @@ sysctl kern.ipc.somaxconn=1024 | |||
| 24 | sysctl kern.ipc.nmbclusters=32768 | 18 | sysctl kern.ipc.nmbclusters=32768 |
| 25 | sysctl net.inet.tcp.msl=10000 | 19 | sysctl net.inet.tcp.msl=10000 |
| 26 | sysctl kern.maxfiles=10240 | 20 | sysctl kern.maxfiles=10240 |
| 27 | |||
| 28 | License information: | ||
| 29 | |||
| 30 | Although the libowfat library is under GPL, Felix von Leitner agreed that the compiled binary may be distributed under the same beer ware license as the source code for opentracker. However, we like to hear from happy customers. | ||
diff --git a/opentracker.c b/opentracker.c index ec92fe0..2eaf988 100644 --- a/opentracker.c +++ b/opentracker.c | |||
| @@ -35,6 +35,7 @@ | |||
| 35 | #include "ot_fdstate.h" | 35 | #include "ot_fdstate.h" |
| 36 | #include "ot_socket.h" | 36 | #include "ot_socket.h" |
| 37 | #include "ot_stats.h" | 37 | #include "ot_stats.h" |
| 38 | #include "ot_tcp.h" | ||
| 38 | #include "ot_udp.h" | 39 | #include "ot_udp.h" |
| 39 | #include "trackerlogic.h" | 40 | #include "trackerlogic.h" |
| 40 | 41 | ||
| @@ -96,126 +97,6 @@ static void install_signal_handlers(void) { | |||
| 96 | } | 97 | } |
| 97 | 98 | ||
| 98 | 99 | ||
| 99 | static ssize_t header_complete(char *request, ssize_t byte_count) { | ||
| 100 | ssize_t i = 0, state = 0; | ||
| 101 | |||
| 102 | for (i = 1; i < byte_count; i += 2) | ||
| 103 | if (request[i] <= 13) { | ||
| 104 | i--; | ||
| 105 | for (state = 0; i < byte_count; ++i) { | ||
| 106 | char c = request[i]; | ||
| 107 | if (c == '\r' || c == '\n') | ||
| 108 | state = (state >> 2) | ((c << 6) & 0xc0); | ||
| 109 | else | ||
| 110 | break; | ||
| 111 | if (state >= 0xa0 || state == 0x99) | ||
| 112 | return i + 1; | ||
| 113 | } | ||
| 114 | } | ||
| 115 | return 0; | ||
| 116 | } | ||
| 117 | |||
| 118 | static void handle_read(const int64_t sock, struct ot_workstruct *ws) { | ||
| 119 | struct http_data *cookie = fdstate_cookie(sock); | ||
| 120 | ssize_t byte_count = read((int)sock, ws->inbuf, G_INBUF_SIZE); | ||
| 121 | |||
| 122 | if (byte_count == 0 || (byte_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) { | ||
| 123 | http_close(sock); | ||
| 124 | return; | ||
| 125 | } | ||
| 126 | |||
| 127 | if (byte_count == -1) | ||
| 128 | return; | ||
| 129 | |||
| 130 | ws->request = NULL; | ||
| 131 | ws->request_size = 0; | ||
| 132 | ws->header_size = 0; | ||
| 133 | ws->keep_alive = 0; | ||
| 134 | |||
| 135 | /* If we get the whole request in one packet, handle it without copying */ | ||
| 136 | if (!cookie->request.data) { | ||
| 137 | if ((ws->header_size = header_complete(ws->inbuf, byte_count))) { | ||
| 138 | ws->request = ws->inbuf; | ||
| 139 | ws->request_size = byte_count; | ||
| 140 | http_handle_request(sock, ws); | ||
| 141 | } else if (buf_cat(&cookie->request, ws->inbuf, (size_t)byte_count)) | ||
| 142 | http_close(sock); | ||
| 143 | return; | ||
| 144 | } | ||
| 145 | |||
| 146 | if (buf_cat(&cookie->request, ws->inbuf, (size_t)byte_count) || cookie->request.size > G_INBUF_SIZE) { | ||
| 147 | http_issue_error(sock, ws, CODE_HTTPERROR_500); | ||
| 148 | return; | ||
| 149 | } | ||
| 150 | |||
| 151 | while ((ws->header_size = header_complete(cookie->request.data, (ssize_t)cookie->request.size))) { | ||
| 152 | ws->request = cookie->request.data; | ||
| 153 | ws->request_size = (ssize_t)cookie->request.size; | ||
| 154 | http_handle_request(sock, ws); | ||
| 155 | #ifdef WANT_KEEPALIVE | ||
| 156 | if (!ws->keep_alive) | ||
| 157 | #endif | ||
| 158 | return; | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 162 | static void handle_write(const int64_t sock) { | ||
| 163 | struct http_data *cookie = fdstate_cookie(sock); | ||
| 164 | int chunked = 0; | ||
| 165 | int keep_alive = 0; | ||
| 166 | |||
| 167 | if (cookie) { | ||
| 168 | chunked = (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) != 0; | ||
| 169 | keep_alive = (cookie->flag & STRUCT_HTTP_FLAG_KEEPALIVE) != 0; | ||
| 170 | |||
| 171 | if (cookie->batch) { | ||
| 172 | if (batch_send(sock, cookie->batch) == BATCH_FAILED) { | ||
| 173 | http_close(sock); | ||
| 174 | return; | ||
| 175 | } | ||
| 176 | if (batch_pending(cookie->batch)) | ||
| 177 | return; | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 181 | /* In a chunked transfer, wait for the next piece once everything queued has gone out */ | ||
| 182 | if (chunked) | ||
| 183 | http_interest_set(sock, cookie, 0); | ||
| 184 | else if (keep_alive) { | ||
| 185 | /* The reply is out and the client asked to keep the connection, so go | ||
| 186 | back to waiting for the next request */ | ||
| 187 | http_interest_set(sock, cookie, EVENT_IN); | ||
| 188 | fdstate_set_expiry(sock, time_future_seconds(OT_CLIENT_TIMEOUT)); | ||
| 189 | } else | ||
| 190 | http_close(sock); | ||
| 191 | } | ||
| 192 | |||
| 193 | static void handle_accept(const int64_t serversocket, struct ot_workstruct *ws) { | ||
| 194 | struct http_data *cookie; | ||
| 195 | int64_t sock; | ||
| 196 | ot_ip6 ip; | ||
| 197 | uint16_t port; | ||
| 198 | |||
| 199 | while ((sock = socket_accept(serversocket, ip, &port)) != -1) { | ||
| 200 | |||
| 201 | if (!(cookie = (struct http_data *)calloc(1, sizeof(struct http_data)))) { | ||
| 202 | close((int)sock); | ||
| 203 | continue; | ||
| 204 | } | ||
| 205 | memcpy(cookie->ip, ip, sizeof(ot_ip6)); | ||
| 206 | cookie->interest = EVENT_IN; | ||
| 207 | |||
| 208 | if (event_add((int)sock, EVENT_IN, fdstate_open(sock, cookie, time_future_seconds(OT_CLIENT_TIMEOUT))) == -1) { | ||
| 209 | fdstate_close(sock); | ||
| 210 | free(cookie); | ||
| 211 | close((int)sock); | ||
| 212 | continue; | ||
| 213 | } | ||
| 214 | |||
| 215 | stats_issue_event(ws, EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip); | ||
| 216 | } | ||
| 217 | } | ||
| 218 | |||
| 219 | /* How many ready descriptors one wait hands back at a time */ | 100 | /* How many ready descriptors one wait hands back at a time */ |
| 220 | #define OT_EVENT_BATCH 512 | 101 | #define OT_EVENT_BATCH 512 |
| 221 | 102 | ||
| @@ -261,7 +142,7 @@ static void *server_mainloop(void *args) { | |||
| 261 | cookie = fdstate_cookie((int)sock); | 142 | cookie = fdstate_cookie((int)sock); |
| 262 | 143 | ||
| 263 | if ((intptr_t)cookie == FLAG_TCP) | 144 | if ((intptr_t)cookie == FLAG_TCP) |
| 264 | handle_accept(sock, &ws); | 145 | tcp_accept(sock, &ws); |
| 265 | else if ((intptr_t)cookie == FLAG_UDP) | 146 | else if ((intptr_t)cookie == FLAG_UDP) |
| 266 | handle_udp6(sock, &ws); | 147 | handle_udp6(sock, &ws); |
| 267 | else if ((intptr_t)cookie == FLAG_SELFPIPE) | 148 | else if ((intptr_t)cookie == FLAG_SELFPIPE) |
| @@ -269,10 +150,10 @@ static void *server_mainloop(void *args) { | |||
| 269 | ; | 150 | ; |
| 270 | else { | 151 | else { |
| 271 | if (ready[i].what & EVENT_IN) | 152 | if (ready[i].what & EVENT_IN) |
| 272 | handle_read(sock, &ws); | 153 | tcp_read(sock, &ws); |
| 273 | /* The read may have closed it, so look again before writing */ | 154 | /* The read may have closed it, so look again before writing */ |
| 274 | if ((ready[i].what & EVENT_OUT) && fdstate_generation((int)sock) == ready[i].generation) | 155 | if ((ready[i].what & EVENT_OUT) && fdstate_generation((int)sock) == ready[i].generation) |
| 275 | handle_write(sock); | 156 | tcp_write(sock); |
| 276 | } | 157 | } |
| 277 | } | 158 | } |
| 278 | 159 | ||
diff --git a/opentracker.xcodeproj/project.pbxproj b/opentracker.xcodeproj/project.pbxproj deleted file mode 100644 index 3706a3f..0000000 --- a/opentracker.xcodeproj/project.pbxproj +++ /dev/null | |||
| @@ -1,320 +0,0 @@ | |||
| 1 | // !$*UTF8*$! | ||
| 2 | { | ||
| 3 | archiveVersion = 1; | ||
| 4 | classes = { | ||
| 5 | }; | ||
| 6 | objectVersion = 44; | ||
| 7 | objects = { | ||
| 8 | |||
| 9 | /* Begin PBXBuildFile section */ | ||
| 10 | 6520B7530D036AAF00A43B1F /* libowfat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6520B7520D036AAF00A43B1F /* libowfat.a */; }; | ||
| 11 | 653A320C0CE7F475007F0D03 /* ot_accesslist.c in Sources */ = {isa = PBXBuildFile; fileRef = 653A320B0CE7F475007F0D03 /* ot_accesslist.c */; }; | ||
| 12 | 653A56B50CE28EC5000CF140 /* ot_iovec.c in Sources */ = {isa = PBXBuildFile; fileRef = 653A56B40CE28EC5000CF140 /* ot_iovec.c */; }; | ||
| 13 | 654A80890CD832FD009035DE /* opentracker.c in Sources */ = {isa = PBXBuildFile; fileRef = 654A80840CD832FC009035DE /* opentracker.c */; }; | ||
| 14 | 654A808A0CD832FD009035DE /* scan_urlencoded_query.c in Sources */ = {isa = PBXBuildFile; fileRef = 654A80850CD832FC009035DE /* scan_urlencoded_query.c */; }; | ||
| 15 | 654A808B0CD832FD009035DE /* trackerlogic.c in Sources */ = {isa = PBXBuildFile; fileRef = 654A80870CD832FC009035DE /* trackerlogic.c */; }; | ||
| 16 | 65542D8B0CE078E800469330 /* ot_vector.c in Sources */ = {isa = PBXBuildFile; fileRef = 65542D8A0CE078E800469330 /* ot_vector.c */; }; | ||
| 17 | 65542D930CE07CED00469330 /* ot_mutex.c in Sources */ = {isa = PBXBuildFile; fileRef = 65542D8F0CE07CED00469330 /* ot_mutex.c */; }; | ||
| 18 | 65542D940CE07CED00469330 /* ot_stats.c in Sources */ = {isa = PBXBuildFile; fileRef = 65542D910CE07CED00469330 /* ot_stats.c */; }; | ||
| 19 | 65542E750CE08B9100469330 /* ot_clean.c in Sources */ = {isa = PBXBuildFile; fileRef = 65542E740CE08B9100469330 /* ot_clean.c */; }; | ||
| 20 | 65542EE80CE0CA6B00469330 /* ot_udp.c in Sources */ = {isa = PBXBuildFile; fileRef = 65542EE70CE0CA6B00469330 /* ot_udp.c */; }; | ||
| 21 | 65542F920CE17CA900469330 /* ot_fullscrape.c in Sources */ = {isa = PBXBuildFile; fileRef = 65542F910CE17CA900469330 /* ot_fullscrape.c */; }; | ||
| 22 | 65B8DF3C0D0310D20017149E /* ot_http.c in Sources */ = {isa = PBXBuildFile; fileRef = 65B8DF3B0D0310D20017149E /* ot_http.c */; }; | ||
| 23 | 65FA33990E7EF09200F7D5A5 /* ot_livesync.c in Sources */ = {isa = PBXBuildFile; fileRef = 65FA33980E7EF09200F7D5A5 /* ot_livesync.c */; }; | ||
| 24 | 8DD76FB00486AB0100D96B5E /* opentracker.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = C6A0FF2C0290799A04C91782 /* opentracker.1 */; }; | ||
| 25 | /* End PBXBuildFile section */ | ||
| 26 | |||
| 27 | /* Begin PBXCopyFilesBuildPhase section */ | ||
| 28 | 8DD76FAF0486AB0100D96B5E /* CopyFiles */ = { | ||
| 29 | isa = PBXCopyFilesBuildPhase; | ||
| 30 | buildActionMask = 8; | ||
| 31 | dstPath = /usr/share/man/man1/; | ||
| 32 | dstSubfolderSpec = 0; | ||
| 33 | files = ( | ||
| 34 | 8DD76FB00486AB0100D96B5E /* opentracker.1 in CopyFiles */, | ||
| 35 | ); | ||
| 36 | runOnlyForDeploymentPostprocessing = 1; | ||
| 37 | }; | ||
| 38 | /* End PBXCopyFilesBuildPhase section */ | ||
| 39 | |||
| 40 | /* Begin PBXFileReference section */ | ||
| 41 | 6520B7520D036AAF00A43B1F /* libowfat.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libowfat.a; path = ../libowfat/libowfat.a; sourceTree = SOURCE_ROOT; }; | ||
| 42 | 653A320A0CE7F475007F0D03 /* ot_accesslist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_accesslist.h; sourceTree = "<group>"; }; | ||
| 43 | 653A320B0CE7F475007F0D03 /* ot_accesslist.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_accesslist.c; sourceTree = "<group>"; }; | ||
| 44 | 653A56AC0CE201FF000CF140 /* opentracker */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = opentracker; sourceTree = BUILT_PRODUCTS_DIR; }; | ||
| 45 | 653A56B30CE28EC5000CF140 /* ot_iovec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_iovec.h; sourceTree = "<group>"; }; | ||
| 46 | 653A56B40CE28EC5000CF140 /* ot_iovec.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_iovec.c; sourceTree = "<group>"; }; | ||
| 47 | 654A80840CD832FC009035DE /* opentracker.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = opentracker.c; sourceTree = "<group>"; }; | ||
| 48 | 654A80850CD832FC009035DE /* scan_urlencoded_query.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = scan_urlencoded_query.c; sourceTree = "<group>"; }; | ||
| 49 | 654A80860CD832FC009035DE /* scan_urlencoded_query.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = scan_urlencoded_query.h; sourceTree = "<group>"; }; | ||
| 50 | 654A80870CD832FC009035DE /* trackerlogic.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = trackerlogic.c; sourceTree = "<group>"; }; | ||
| 51 | 654A80880CD832FC009035DE /* trackerlogic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = trackerlogic.h; sourceTree = "<group>"; }; | ||
| 52 | 65542D890CE078E800469330 /* ot_vector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_vector.h; sourceTree = "<group>"; }; | ||
| 53 | 65542D8A0CE078E800469330 /* ot_vector.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_vector.c; sourceTree = "<group>"; }; | ||
| 54 | 65542D8F0CE07CED00469330 /* ot_mutex.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_mutex.c; sourceTree = "<group>"; }; | ||
| 55 | 65542D900CE07CED00469330 /* ot_mutex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_mutex.h; sourceTree = "<group>"; }; | ||
| 56 | 65542D910CE07CED00469330 /* ot_stats.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_stats.c; sourceTree = "<group>"; }; | ||
| 57 | 65542D920CE07CED00469330 /* ot_stats.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_stats.h; sourceTree = "<group>"; }; | ||
| 58 | 65542E730CE08B9100469330 /* ot_clean.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_clean.h; sourceTree = "<group>"; }; | ||
| 59 | 65542E740CE08B9100469330 /* ot_clean.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_clean.c; sourceTree = "<group>"; }; | ||
| 60 | 65542EE60CE0CA6B00469330 /* ot_udp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_udp.h; sourceTree = "<group>"; }; | ||
| 61 | 65542EE70CE0CA6B00469330 /* ot_udp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_udp.c; sourceTree = "<group>"; }; | ||
| 62 | 65542F900CE17CA900469330 /* ot_fullscrape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_fullscrape.h; sourceTree = "<group>"; }; | ||
| 63 | 65542F910CE17CA900469330 /* ot_fullscrape.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_fullscrape.c; sourceTree = "<group>"; }; | ||
| 64 | 65B8DF3A0D0310D20017149E /* ot_http.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_http.h; sourceTree = "<group>"; }; | ||
| 65 | 65B8DF3B0D0310D20017149E /* ot_http.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_http.c; sourceTree = "<group>"; }; | ||
| 66 | 65FA33970E7EF09200F7D5A5 /* ot_livesync.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ot_livesync.h; sourceTree = "<group>"; }; | ||
| 67 | 65FA33980E7EF09200F7D5A5 /* ot_livesync.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ot_livesync.c; sourceTree = "<group>"; }; | ||
| 68 | C6A0FF2C0290799A04C91782 /* opentracker.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = opentracker.1; sourceTree = "<group>"; }; | ||
| 69 | /* End PBXFileReference section */ | ||
| 70 | |||
| 71 | /* Begin PBXFrameworksBuildPhase section */ | ||
| 72 | 8DD76FAD0486AB0100D96B5E /* Frameworks */ = { | ||
| 73 | isa = PBXFrameworksBuildPhase; | ||
| 74 | buildActionMask = 2147483647; | ||
| 75 | files = ( | ||
| 76 | 6520B7530D036AAF00A43B1F /* libowfat.a in Frameworks */, | ||
| 77 | ); | ||
| 78 | runOnlyForDeploymentPostprocessing = 0; | ||
| 79 | }; | ||
| 80 | /* End PBXFrameworksBuildPhase section */ | ||
| 81 | |||
| 82 | /* Begin PBXGroup section */ | ||
| 83 | 08FB7794FE84155DC02AAC07 /* opentracker */ = { | ||
| 84 | isa = PBXGroup; | ||
| 85 | children = ( | ||
| 86 | 65542D810CE0786F00469330 /* Headers */, | ||
| 87 | 08FB7795FE84155DC02AAC07 /* Source */, | ||
| 88 | 92762AC9104EDED700FDCB60 /* Libraries */, | ||
| 89 | C6A0FF2B0290797F04C91782 /* Documentation */, | ||
| 90 | 653A56AD0CE201FF000CF140 /* Products */, | ||
| 91 | ); | ||
| 92 | name = opentracker; | ||
| 93 | sourceTree = "<group>"; | ||
| 94 | }; | ||
| 95 | 08FB7795FE84155DC02AAC07 /* Source */ = { | ||
| 96 | isa = PBXGroup; | ||
| 97 | children = ( | ||
| 98 | 654A80840CD832FC009035DE /* opentracker.c */, | ||
| 99 | 653A320B0CE7F475007F0D03 /* ot_accesslist.c */, | ||
| 100 | 65542E740CE08B9100469330 /* ot_clean.c */, | ||
| 101 | 65542F910CE17CA900469330 /* ot_fullscrape.c */, | ||
| 102 | 65B8DF3B0D0310D20017149E /* ot_http.c */, | ||
| 103 | 65FA33980E7EF09200F7D5A5 /* ot_livesync.c */, | ||
| 104 | 653A56B40CE28EC5000CF140 /* ot_iovec.c */, | ||
| 105 | 65542D8F0CE07CED00469330 /* ot_mutex.c */, | ||
| 106 | 65542D910CE07CED00469330 /* ot_stats.c */, | ||
| 107 | 65542EE70CE0CA6B00469330 /* ot_udp.c */, | ||
| 108 | 65542D8A0CE078E800469330 /* ot_vector.c */, | ||
| 109 | 654A80850CD832FC009035DE /* scan_urlencoded_query.c */, | ||
| 110 | 654A80870CD832FC009035DE /* trackerlogic.c */, | ||
| 111 | ); | ||
| 112 | name = Source; | ||
| 113 | sourceTree = "<group>"; | ||
| 114 | }; | ||
| 115 | 653A56AD0CE201FF000CF140 /* Products */ = { | ||
| 116 | isa = PBXGroup; | ||
| 117 | children = ( | ||
| 118 | 653A56AC0CE201FF000CF140 /* opentracker */, | ||
| 119 | ); | ||
| 120 | name = Products; | ||
| 121 | sourceTree = "<group>"; | ||
| 122 | }; | ||
| 123 | 65542D810CE0786F00469330 /* Headers */ = { | ||
| 124 | isa = PBXGroup; | ||
| 125 | children = ( | ||
| 126 | 653A320A0CE7F475007F0D03 /* ot_accesslist.h */, | ||
| 127 | 65542E730CE08B9100469330 /* ot_clean.h */, | ||
| 128 | 65542F900CE17CA900469330 /* ot_fullscrape.h */, | ||
| 129 | 65B8DF3A0D0310D20017149E /* ot_http.h */, | ||
| 130 | 653A56B30CE28EC5000CF140 /* ot_iovec.h */, | ||
| 131 | 65FA33970E7EF09200F7D5A5 /* ot_livesync.h */, | ||
| 132 | 65542D900CE07CED00469330 /* ot_mutex.h */, | ||
| 133 | 65542D920CE07CED00469330 /* ot_stats.h */, | ||
| 134 | 65542EE60CE0CA6B00469330 /* ot_udp.h */, | ||
| 135 | 65542D890CE078E800469330 /* ot_vector.h */, | ||
| 136 | 654A80860CD832FC009035DE /* scan_urlencoded_query.h */, | ||
| 137 | 654A80880CD832FC009035DE /* trackerlogic.h */, | ||
| 138 | ); | ||
| 139 | name = Headers; | ||
| 140 | sourceTree = "<group>"; | ||
| 141 | }; | ||
| 142 | 92762AC9104EDED700FDCB60 /* Libraries */ = { | ||
| 143 | isa = PBXGroup; | ||
| 144 | children = ( | ||
| 145 | 6520B7520D036AAF00A43B1F /* libowfat.a */, | ||
| 146 | ); | ||
| 147 | name = Libraries; | ||
| 148 | sourceTree = "<group>"; | ||
| 149 | }; | ||
| 150 | C6A0FF2B0290797F04C91782 /* Documentation */ = { | ||
| 151 | isa = PBXGroup; | ||
| 152 | children = ( | ||
| 153 | C6A0FF2C0290799A04C91782 /* opentracker.1 */, | ||
| 154 | ); | ||
| 155 | name = Documentation; | ||
| 156 | sourceTree = "<group>"; | ||
| 157 | }; | ||
| 158 | /* End PBXGroup section */ | ||
| 159 | |||
| 160 | /* Begin PBXNativeTarget section */ | ||
| 161 | 8DD76FA90486AB0100D96B5E /* opentracker */ = { | ||
| 162 | isa = PBXNativeTarget; | ||
| 163 | buildConfigurationList = 1DEB928508733DD80010E9CD /* Build configuration list for PBXNativeTarget "opentracker" */; | ||
| 164 | buildPhases = ( | ||
| 165 | 8DD76FAB0486AB0100D96B5E /* Sources */, | ||
| 166 | 8DD76FAD0486AB0100D96B5E /* Frameworks */, | ||
| 167 | 8DD76FAF0486AB0100D96B5E /* CopyFiles */, | ||
| 168 | ); | ||
| 169 | buildRules = ( | ||
| 170 | ); | ||
| 171 | dependencies = ( | ||
| 172 | ); | ||
| 173 | name = opentracker; | ||
| 174 | productInstallPath = "$(HOME)/bin"; | ||
| 175 | productName = opentracker; | ||
| 176 | productReference = 653A56AC0CE201FF000CF140 /* opentracker */; | ||
| 177 | productType = "com.apple.product-type.tool"; | ||
| 178 | }; | ||
| 179 | /* End PBXNativeTarget section */ | ||
| 180 | |||
| 181 | /* Begin PBXProject section */ | ||
| 182 | 08FB7793FE84155DC02AAC07 /* Project object */ = { | ||
| 183 | isa = PBXProject; | ||
| 184 | buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "opentracker" */; | ||
| 185 | compatibilityVersion = "Xcode 3.0"; | ||
| 186 | hasScannedForEncodings = 1; | ||
| 187 | mainGroup = 08FB7794FE84155DC02AAC07 /* opentracker */; | ||
| 188 | productRefGroup = 653A56AD0CE201FF000CF140 /* Products */; | ||
| 189 | projectDirPath = ""; | ||
| 190 | projectRoot = ""; | ||
| 191 | targets = ( | ||
| 192 | 8DD76FA90486AB0100D96B5E /* opentracker */, | ||
| 193 | ); | ||
| 194 | }; | ||
| 195 | /* End PBXProject section */ | ||
| 196 | |||
| 197 | /* Begin PBXSourcesBuildPhase section */ | ||
| 198 | 8DD76FAB0486AB0100D96B5E /* Sources */ = { | ||
| 199 | isa = PBXSourcesBuildPhase; | ||
| 200 | buildActionMask = 2147483647; | ||
| 201 | files = ( | ||
| 202 | 654A80890CD832FD009035DE /* opentracker.c in Sources */, | ||
| 203 | 654A808A0CD832FD009035DE /* scan_urlencoded_query.c in Sources */, | ||
| 204 | 654A808B0CD832FD009035DE /* trackerlogic.c in Sources */, | ||
| 205 | 65542D8B0CE078E800469330 /* ot_vector.c in Sources */, | ||
| 206 | 65542D930CE07CED00469330 /* ot_mutex.c in Sources */, | ||
| 207 | 65542D940CE07CED00469330 /* ot_stats.c in Sources */, | ||
| 208 | 65542E750CE08B9100469330 /* ot_clean.c in Sources */, | ||
| 209 | 65542EE80CE0CA6B00469330 /* ot_udp.c in Sources */, | ||
| 210 | 65542F920CE17CA900469330 /* ot_fullscrape.c in Sources */, | ||
| 211 | 653A56B50CE28EC5000CF140 /* ot_iovec.c in Sources */, | ||
| 212 | 653A320C0CE7F475007F0D03 /* ot_accesslist.c in Sources */, | ||
| 213 | 65B8DF3C0D0310D20017149E /* ot_http.c in Sources */, | ||
| 214 | 65FA33990E7EF09200F7D5A5 /* ot_livesync.c in Sources */, | ||
| 215 | ); | ||
| 216 | runOnlyForDeploymentPostprocessing = 0; | ||
| 217 | }; | ||
| 218 | /* End PBXSourcesBuildPhase section */ | ||
| 219 | |||
| 220 | /* Begin XCBuildConfiguration section */ | ||
| 221 | 1DEB928608733DD80010E9CD /* Debug */ = { | ||
| 222 | isa = XCBuildConfiguration; | ||
| 223 | buildSettings = { | ||
| 224 | COPY_PHASE_STRIP = NO; | ||
| 225 | GCC_DYNAMIC_NO_PIC = NO; | ||
| 226 | GCC_ENABLE_FIX_AND_CONTINUE = YES; | ||
| 227 | GCC_MODEL_TUNING = G5; | ||
| 228 | GCC_OPTIMIZATION_LEVEL = 0; | ||
| 229 | INSTALL_PATH = /usr/local/bin; | ||
| 230 | LIBRARY_SEARCH_PATHS = ( | ||
| 231 | "$(inherited)", | ||
| 232 | "\"$(SRCROOT)/../libowfat\"", | ||
| 233 | ); | ||
| 234 | PRODUCT_NAME = opentracker; | ||
| 235 | ZERO_LINK = YES; | ||
| 236 | }; | ||
| 237 | name = Debug; | ||
| 238 | }; | ||
| 239 | 1DEB928708733DD80010E9CD /* Release */ = { | ||
| 240 | isa = XCBuildConfiguration; | ||
| 241 | buildSettings = { | ||
| 242 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; | ||
| 243 | GCC_ENABLE_FIX_AND_CONTINUE = YES; | ||
| 244 | GCC_MODEL_TUNING = G5; | ||
| 245 | INSTALL_PATH = /usr/local/bin; | ||
| 246 | LIBRARY_SEARCH_PATHS = ( | ||
| 247 | "$(inherited)", | ||
| 248 | "\"$(SRCROOT)/../libowfat\"", | ||
| 249 | ); | ||
| 250 | PRODUCT_NAME = opentracker; | ||
| 251 | }; | ||
| 252 | name = Release; | ||
| 253 | }; | ||
| 254 | 1DEB928A08733DD80010E9CD /* Debug */ = { | ||
| 255 | isa = XCBuildConfiguration; | ||
| 256 | buildSettings = { | ||
| 257 | ARCHS = "$(ONLY_ACTIVE_ARCH_PRE_XCODE_3_1)"; | ||
| 258 | GCC_PREPROCESSOR_DEFINITIONS = ( | ||
| 259 | WANT_ACCESSLIST_WHITE, | ||
| 260 | WANT_IP_FROM_QUERY_STRING, | ||
| 261 | WANT_FULLSCRAPE, | ||
| 262 | ); | ||
| 263 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; | ||
| 264 | GCC_WARN_ABOUT_RETURN_TYPE = YES; | ||
| 265 | GCC_WARN_UNUSED_VARIABLE = YES; | ||
| 266 | HEADER_SEARCH_PATHS = ../libowfat/; | ||
| 267 | LIBRARY_SEARCH_PATHS = ../libowfat/; | ||
| 268 | ONLY_ACTIVE_ARCH_PRE_XCODE_3_1 = "$(NATIVE_ARCH_ACTUAL)"; | ||
| 269 | PREBINDING = NO; | ||
| 270 | SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.6.sdk"; | ||
| 271 | }; | ||
| 272 | name = Debug; | ||
| 273 | }; | ||
| 274 | 1DEB928B08733DD80010E9CD /* Release */ = { | ||
| 275 | isa = XCBuildConfiguration; | ||
| 276 | buildSettings = { | ||
| 277 | ARCHS = "$(ONLY_ACTIVE_ARCH_PRE_XCODE_3_1)"; | ||
| 278 | DEAD_CODE_STRIPPING = NO; | ||
| 279 | GCC_PREPROCESSOR_DEFINITIONS = ( | ||
| 280 | WANT_ACCESSLIST_WHITE, | ||
| 281 | WANT_IP_FROM_QUERY_STRING, | ||
| 282 | WANT_FULLSCRAPE, | ||
| 283 | ); | ||
| 284 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; | ||
| 285 | GCC_WARN_ABOUT_RETURN_TYPE = YES; | ||
| 286 | GCC_WARN_UNUSED_VARIABLE = YES; | ||
| 287 | HEADER_SEARCH_PATHS = ../libowfat/; | ||
| 288 | LIBRARY_SEARCH_PATHS = ../libowfat/; | ||
| 289 | ONLY_ACTIVE_ARCH_PRE_XCODE_3_1 = "$(NATIVE_ARCH_ACTUAL)"; | ||
| 290 | PREBINDING = NO; | ||
| 291 | SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.6.sdk"; | ||
| 292 | ZERO_LINK = NO; | ||
| 293 | }; | ||
| 294 | name = Release; | ||
| 295 | }; | ||
| 296 | /* End XCBuildConfiguration section */ | ||
| 297 | |||
| 298 | /* Begin XCConfigurationList section */ | ||
| 299 | 1DEB928508733DD80010E9CD /* Build configuration list for PBXNativeTarget "opentracker" */ = { | ||
| 300 | isa = XCConfigurationList; | ||
| 301 | buildConfigurations = ( | ||
| 302 | 1DEB928608733DD80010E9CD /* Debug */, | ||
| 303 | 1DEB928708733DD80010E9CD /* Release */, | ||
| 304 | ); | ||
| 305 | defaultConfigurationIsVisible = 0; | ||
| 306 | defaultConfigurationName = Release; | ||
| 307 | }; | ||
| 308 | 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "opentracker" */ = { | ||
| 309 | isa = XCConfigurationList; | ||
| 310 | buildConfigurations = ( | ||
| 311 | 1DEB928A08733DD80010E9CD /* Debug */, | ||
| 312 | 1DEB928B08733DD80010E9CD /* Release */, | ||
| 313 | ); | ||
| 314 | defaultConfigurationIsVisible = 0; | ||
| 315 | defaultConfigurationName = Release; | ||
| 316 | }; | ||
| 317 | /* End XCConfigurationList section */ | ||
| 318 | }; | ||
| 319 | rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; | ||
| 320 | } | ||
diff --git a/ot_config.c b/ot_config.c index a0dbef1..272acf1 100644 --- a/ot_config.c +++ b/ot_config.c | |||
| @@ -14,6 +14,7 @@ | |||
| 14 | 14 | ||
| 15 | /* Opentracker */ | 15 | /* Opentracker */ |
| 16 | #include "ot_accesslist.h" | 16 | #include "ot_accesslist.h" |
| 17 | #include "ot_event.h" | ||
| 17 | #include "ot_config.h" | 18 | #include "ot_config.h" |
| 18 | #include "ot_http.h" | 19 | #include "ot_http.h" |
| 19 | #include "ot_ip.h" | 20 | #include "ot_ip.h" |
| @@ -32,7 +33,8 @@ const char *g_statefile; | |||
| 32 | static ot_listen *g_listen; | 33 | static ot_listen *g_listen; |
| 33 | static size_t g_listen_count; | 34 | static size_t g_listen_count; |
| 34 | static int g_listen_shared; | 35 | static int g_listen_shared; |
| 35 | static int g_listen_workers = 1; | 36 | static int g_listen_workers_udp = 1; |
| 37 | static int g_listen_workers_tcp = 0; | ||
| 36 | 38 | ||
| 37 | static void panic(const char *routine) __attribute__((noreturn)); | 39 | static void panic(const char *routine) __attribute__((noreturn)); |
| 38 | static void panic(const char *routine) { | 40 | static void panic(const char *routine) { |
| @@ -196,6 +198,7 @@ typedef enum { | |||
| 196 | CONFIG_LISTEN_TCP, | 198 | CONFIG_LISTEN_TCP, |
| 197 | CONFIG_LISTEN_UDP, | 199 | CONFIG_LISTEN_UDP, |
| 198 | CONFIG_LISTEN_UDP_WORKERS, | 200 | CONFIG_LISTEN_UDP_WORKERS, |
| 201 | CONFIG_LISTEN_TCP_WORKERS, | ||
| 199 | CONFIG_LISTEN_SHARED, | 202 | CONFIG_LISTEN_SHARED, |
| 200 | CONFIG_ACCESS_WHITELIST, | 203 | CONFIG_ACCESS_WHITELIST, |
| 201 | CONFIG_ACCESS_BLACKLIST, | 204 | CONFIG_ACCESS_BLACKLIST, |
| @@ -224,6 +227,7 @@ static const config_key config_keys[] = { | |||
| 224 | CONFIG_KEY("listen.tcp", CONFIG_LISTEN_TCP), | 227 | CONFIG_KEY("listen.tcp", CONFIG_LISTEN_TCP), |
| 225 | CONFIG_KEY("listen.udp", CONFIG_LISTEN_UDP), | 228 | CONFIG_KEY("listen.udp", CONFIG_LISTEN_UDP), |
| 226 | CONFIG_KEY("listen.udp.workers", CONFIG_LISTEN_UDP_WORKERS), | 229 | CONFIG_KEY("listen.udp.workers", CONFIG_LISTEN_UDP_WORKERS), |
| 230 | CONFIG_KEY("listen.tcp.workers", CONFIG_LISTEN_TCP_WORKERS), | ||
| 227 | CONFIG_KEY("listen.shared", CONFIG_LISTEN_SHARED), | 231 | CONFIG_KEY("listen.shared", CONFIG_LISTEN_SHARED), |
| 228 | #ifdef WANT_ACCESSLIST_WHITE | 232 | #ifdef WANT_ACCESSLIST_WHITE |
| 229 | CONFIG_KEY("access.whitelist", CONFIG_ACCESS_WHITELIST), | 233 | CONFIG_KEY("access.whitelist", CONFIG_ACCESS_WHITELIST), |
| @@ -318,7 +322,7 @@ static int parse_configfile(char *config_filename) { | |||
| 318 | if (!off || !listen_scan_options(p + off, &shared, &workers)) | 322 | if (!off || !listen_scan_options(p + off, &shared, &workers)) |
| 319 | goto parse_error; | 323 | goto parse_error; |
| 320 | if (kind != CONFIG_LISTEN_UDP) | 324 | if (kind != CONFIG_LISTEN_UDP) |
| 321 | listen_append(tmpip, tmpport, FLAG_TCP, shared, OT_LISTEN_INHERIT), ++bound; | 325 | listen_append(tmpip, tmpport, FLAG_TCP, shared, workers), ++bound; |
| 322 | if (kind != CONFIG_LISTEN_TCP) | 326 | if (kind != CONFIG_LISTEN_TCP) |
| 323 | listen_append(tmpip, tmpport, FLAG_UDP, shared, workers), ++bound; | 327 | listen_append(tmpip, tmpport, FLAG_UDP, shared, workers), ++bound; |
| 324 | break; | 328 | break; |
| @@ -326,7 +330,12 @@ static int parse_configfile(char *config_filename) { | |||
| 326 | case CONFIG_LISTEN_UDP_WORKERS: | 330 | case CONFIG_LISTEN_UDP_WORKERS: |
| 327 | if (!scan_uint(p, &value, UINT_MAX)) | 331 | if (!scan_uint(p, &value, UINT_MAX)) |
| 328 | goto parse_error; | 332 | goto parse_error; |
| 329 | g_listen_workers = (int)value; | 333 | g_listen_workers_udp = (int)value; |
| 334 | break; | ||
| 335 | case CONFIG_LISTEN_TCP_WORKERS: | ||
| 336 | if (!scan_uint(p, &value, UINT_MAX)) | ||
| 337 | goto parse_error; | ||
| 338 | g_listen_workers_tcp = (int)value; | ||
| 330 | break; | 339 | break; |
| 331 | case CONFIG_LISTEN_SHARED: | 340 | case CONFIG_LISTEN_SHARED: |
| 332 | if (!scan_uint(p, &value, 1)) | 341 | if (!scan_uint(p, &value, 1)) |
| @@ -446,7 +455,16 @@ static void listen_resolve_defaults(void) { | |||
| 446 | if (entry->shared == OT_LISTEN_INHERIT) | 455 | if (entry->shared == OT_LISTEN_INHERIT) |
| 447 | entry->shared = g_listen_shared; | 456 | entry->shared = g_listen_shared; |
| 448 | if (entry->workers == OT_LISTEN_INHERIT) | 457 | if (entry->workers == OT_LISTEN_INHERIT) |
| 449 | entry->workers = g_listen_workers; | 458 | entry->workers = entry->proto == FLAG_TCP ? g_listen_workers_tcp : g_listen_workers_udp; |
| 459 | |||
| 460 | /* A tcp worker hands a connection it cannot finish to the event loop, | ||
| 461 | which only one thread may touch where the set is ours rather than the | ||
| 462 | kernel's */ | ||
| 463 | if (entry->proto == FLAG_TCP && entry->workers > 1 && !event_can_share()) { | ||
| 464 | fprintf(stderr, "Warning: Multiple workers impossible on this platform with event mechanism %s chosen.\n", | ||
| 465 | event_mechanism()); | ||
| 466 | entry->workers = 1; | ||
| 467 | } | ||
| 450 | } | 468 | } |
| 451 | } | 469 | } |
| 452 | 470 | ||
| @@ -40,6 +40,9 @@ static struct epoll_event *g_ready; | |||
| 40 | static int g_ready_space; | 40 | static int g_ready_space; |
| 41 | 41 | ||
| 42 | const char *event_mechanism(void) { return "epoll"; } | 42 | const char *event_mechanism(void) { return "epoll"; } |
| 43 | /* The set lives in the kernel, and epoll_ctl during another thread's | ||
| 44 | epoll_wait is what it is for */ | ||
| 45 | int event_can_share(void) { return 1; } | ||
| 43 | 46 | ||
| 44 | int event_init(void) { | 47 | int event_init(void) { |
| 45 | g_ready = NULL; | 48 | g_ready = NULL; |
| @@ -118,6 +121,8 @@ static struct kevent *g_ready; | |||
| 118 | static int g_ready_space; | 121 | static int g_ready_space; |
| 119 | 122 | ||
| 120 | const char *event_mechanism(void) { return "kqueue"; } | 123 | const char *event_mechanism(void) { return "kqueue"; } |
| 124 | /* The set lives in the kernel, and kevent may be called from any thread */ | ||
| 125 | int event_can_share(void) { return 1; } | ||
| 121 | 126 | ||
| 122 | int event_init(void) { | 127 | int event_init(void) { |
| 123 | g_ready = NULL; | 128 | g_ready = NULL; |
| @@ -171,7 +176,7 @@ static int event_change(int fd, int what, uint32_t generation, int deleting) { | |||
| 171 | return 0; | 176 | return 0; |
| 172 | } | 177 | } |
| 173 | 178 | ||
| 174 | int event_add(int fd, int what, uint32_t generation) { return event_change(fd, what, generation, 0); } | 179 | int event_add(int fd, int what, uint32_t generation) { return what ? event_change(fd, what, generation, 0) : 0; } |
| 175 | int event_modify(int fd, int what, uint32_t generation) { return event_change(fd, what, generation, 0); } | 180 | int event_modify(int fd, int what, uint32_t generation) { return event_change(fd, what, generation, 0); } |
| 176 | int event_del(int fd) { return event_change(fd, 0, 0, 1); } | 181 | int event_del(int fd) { return event_change(fd, 0, 0, 1); } |
| 177 | 182 | ||
| @@ -229,6 +234,8 @@ static int *g_where; | |||
| 229 | static int g_count, g_space, g_where_space; | 234 | static int g_count, g_space, g_where_space; |
| 230 | 235 | ||
| 231 | const char *event_mechanism(void) { return "poll"; } | 236 | const char *event_mechanism(void) { return "poll"; } |
| 237 | /* The arrays below are ours and nothing guards them */ | ||
| 238 | int event_can_share(void) { return 0; } | ||
| 232 | 239 | ||
| 233 | int event_init(void) { | 240 | int event_init(void) { |
| 234 | g_pfds = NULL, g_generation = NULL, g_where = NULL; | 241 | g_pfds = NULL, g_generation = NULL, g_where = NULL; |
| @@ -38,6 +38,11 @@ int event_close(int fd); | |||
| 38 | out with up to max ready descriptors. Returns how many, or -1. */ | 38 | out with up to max ready descriptors. Returns how many, or -1. */ |
| 39 | int event_wait(ot_event *out, int max, int timeout_ms); | 39 | int event_wait(ot_event *out, int max, int timeout_ms); |
| 40 | 40 | ||
| 41 | /* Whether the registered set may be changed from another thread while this | ||
| 42 | one is waiting on it. True where the kernel keeps the set, false where | ||
| 43 | this module keeps it in memory of its own. */ | ||
| 44 | int event_can_share(void); | ||
| 45 | |||
| 41 | /* Which mechanism was compiled in, for the version string */ | 46 | /* Which mechanism was compiled in, for the version string */ |
| 42 | const char *event_mechanism(void); | 47 | const char *event_mechanism(void); |
| 43 | 48 | ||
diff --git a/ot_fdstate.c b/ot_fdstate.c index 537bcb0..7d742da 100644 --- a/ot_fdstate.c +++ b/ot_fdstate.c | |||
| @@ -186,6 +186,13 @@ void *fdstate_cookie(int fd) { | |||
| 186 | return slot ? atomic_load_explicit(&slot->cookie, memory_order_relaxed) : NULL; | 186 | return slot ? atomic_load_explicit(&slot->cookie, memory_order_relaxed) : NULL; |
| 187 | } | 187 | } |
| 188 | 188 | ||
| 189 | void fdstate_set_cookie(int fd, void *cookie) { | ||
| 190 | ot_fdslot *slot = fdstate_slot(fd); | ||
| 191 | |||
| 192 | if (slot) | ||
| 193 | atomic_store_explicit(&slot->cookie, cookie, memory_order_relaxed); | ||
| 194 | } | ||
| 195 | |||
| 189 | void fdstate_set_expiry(int fd, ot_time expiry) { | 196 | void fdstate_set_expiry(int fd, ot_time expiry) { |
| 190 | ot_fdslot *slot = fdstate_slot(fd); | 197 | ot_fdslot *slot = fdstate_slot(fd); |
| 191 | 198 | ||
diff --git a/ot_fdstate.h b/ot_fdstate.h index c09f018..93890d1 100644 --- a/ot_fdstate.h +++ b/ot_fdstate.h | |||
| @@ -56,6 +56,9 @@ int fdstate_next_expired(ot_time now, int *cursor); | |||
| 56 | 56 | ||
| 57 | /* Both only valid while the caller holds the descriptor. */ | 57 | /* Both only valid while the caller holds the descriptor. */ |
| 58 | void *fdstate_cookie(int fd); | 58 | void *fdstate_cookie(int fd); |
| 59 | |||
| 60 | /* Hand the slot a different cookie, for a connection whose state has moved */ | ||
| 61 | void fdstate_set_cookie(int fd, void *cookie); | ||
| 59 | void fdstate_set_expiry(int fd, ot_time expiry); | 62 | void fdstate_set_expiry(int fd, ot_time expiry); |
| 60 | 63 | ||
| 61 | #endif | 64 | #endif |
| @@ -39,9 +39,25 @@ ssize_t g_stats_path_len; | |||
| 39 | 39 | ||
| 40 | enum { SUCCESS_HTTP_HEADER_LENGTH = 80, SUCCESS_HTTP_SIZE_OFF = 17 }; | 40 | enum { SUCCESS_HTTP_HEADER_LENGTH = 80, SUCCESS_HTTP_SIZE_OFF = 17 }; |
| 41 | 41 | ||
| 42 | /* Give up a connection: the one place that knows everything hanging off it. | 42 | int http_adopt(const int64_t sock, struct http_data *cookie) { |
| 43 | The slot is released before anything is freed, so that a late event or the | 43 | struct http_data *heap = malloc(sizeof(*heap)); |
| 44 | expiry sweep finds a closed descriptor rather than a freed cookie. */ | 44 | |
| 45 | if (!heap) | ||
| 46 | return -1; | ||
| 47 | |||
| 48 | *heap = *cookie; | ||
| 49 | heap->flag &= ~STRUCT_HTTP_FLAG_ONSTACK; | ||
| 50 | fdstate_set_cookie((int)sock, heap); | ||
| 51 | |||
| 52 | if (event_add((int)sock, heap->interest, fdstate_generation((int)sock)) == -1) { | ||
| 53 | fdstate_set_cookie((int)sock, cookie); | ||
| 54 | free(heap); | ||
| 55 | return -1; | ||
| 56 | } | ||
| 57 | return 0; | ||
| 58 | } | ||
| 59 | |||
| 60 | /* Give up a connection: the one place that knows everything hanging off it. */ | ||
| 45 | void http_close(const int64_t sock) { | 61 | void http_close(const int64_t sock) { |
| 46 | struct http_data *cookie = fdstate_cookie(sock); | 62 | struct http_data *cookie = fdstate_cookie(sock); |
| 47 | 63 | ||
| @@ -54,7 +70,8 @@ void http_close(const int64_t sock) { | |||
| 54 | buf_reset(&cookie->request); | 70 | buf_reset(&cookie->request); |
| 55 | if (cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)) | 71 | if (cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)) |
| 56 | mutex_workqueue_canceltask(sock); | 72 | mutex_workqueue_canceltask(sock); |
| 57 | free(cookie); | 73 | if (!(cookie->flag & STRUCT_HTTP_FLAG_ONSTACK)) |
| 74 | free(cookie); | ||
| 58 | } | 75 | } |
| 59 | 76 | ||
| 60 | event_close((int)sock); | 77 | event_close((int)sock); |
| @@ -140,6 +157,10 @@ void http_interest_set(const int64_t sock, struct http_data *cookie, int what) { | |||
| 140 | if (cookie->interest == what) | 157 | if (cookie->interest == what) |
| 141 | return; | 158 | return; |
| 142 | cookie->interest = what; | 159 | cookie->interest = what; |
| 160 | /* Still on a stack, so not in the event loop yet: whoever adopts this | ||
| 161 | connection registers whatever it asked for last */ | ||
| 162 | if (cookie->flag & STRUCT_HTTP_FLAG_ONSTACK) | ||
| 163 | return; | ||
| 143 | event_modify((int)sock, what, fdstate_generation((int)sock)); | 164 | event_modify((int)sock, what, fdstate_generation((int)sock)); |
| 144 | } | 165 | } |
| 145 | 166 | ||
| @@ -22,7 +22,8 @@ typedef enum { | |||
| 22 | STRUCT_HTTP_FLAG_ZSTD = 8, | 22 | STRUCT_HTTP_FLAG_ZSTD = 8, |
| 23 | STRUCT_HTTP_FLAG_CHUNKED = 16, | 23 | STRUCT_HTTP_FLAG_CHUNKED = 16, |
| 24 | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER = 32, | 24 | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER = 32, |
| 25 | STRUCT_HTTP_FLAG_KEEPALIVE = 64 | 25 | STRUCT_HTTP_FLAG_KEEPALIVE = 64, |
| 26 | STRUCT_HTTP_FLAG_ONSTACK = 128 | ||
| 26 | } STRUCT_HTTP_FLAG; | 27 | } STRUCT_HTTP_FLAG; |
| 27 | 28 | ||
| 28 | struct http_data { | 29 | struct http_data { |
| @@ -39,6 +40,10 @@ void http_interest_set(const int64_t s, struct http_data *cookie, int | |||
| 39 | /* Close a connection and release everything that belongs to it */ | 40 | /* Close a connection and release everything that belongs to it */ |
| 40 | void http_close(const int64_t s); | 41 | void http_close(const int64_t s); |
| 41 | 42 | ||
| 43 | /* Promote a stack cookie to a heap one for persistence and handing it over | ||
| 44 | to the event loop. */ | ||
| 45 | int http_adopt(const int64_t s, struct http_data *cookie); | ||
| 46 | |||
| 42 | ssize_t http_handle_request(const int64_t s, struct ot_workstruct *ws); | 47 | ssize_t http_handle_request(const int64_t s, struct ot_workstruct *ws); |
| 43 | ssize_t http_sendiovecdata(const int64_t s, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial); | 48 | ssize_t http_sendiovecdata(const int64_t s, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial); |
| 44 | ssize_t http_issue_error(const int64_t s, struct ot_workstruct *ws, int code); | 49 | ssize_t http_issue_error(const int64_t s, struct ot_workstruct *ws, int code); |
diff --git a/ot_tcp.c b/ot_tcp.c new file mode 100644 index 0000000..8647a47 --- /dev/null +++ b/ot_tcp.c | |||
| @@ -0,0 +1,157 @@ | |||
| 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 <string.h> | ||
| 9 | #include <unistd.h> | ||
| 10 | |||
| 11 | /* Opentracker */ | ||
| 12 | #include "ot_batch.h" | ||
| 13 | #include "ot_buf.h" | ||
| 14 | #include "ot_event.h" | ||
| 15 | #include "ot_fdstate.h" | ||
| 16 | #include "ot_http.h" | ||
| 17 | #include "ot_mutex.h" | ||
| 18 | #include "ot_socket.h" | ||
| 19 | #include "ot_stats.h" | ||
| 20 | #include "ot_tcp.h" | ||
| 21 | #include "trackerlogic.h" | ||
| 22 | |||
| 23 | static ssize_t header_complete(char *request, ssize_t byte_count) { | ||
| 24 | ssize_t i = 0, state = 0; | ||
| 25 | |||
| 26 | for (i = 1; i < byte_count; i += 2) | ||
| 27 | if (request[i] <= 13) { | ||
| 28 | i--; | ||
| 29 | for (state = 0; i < byte_count; ++i) { | ||
| 30 | char c = request[i]; | ||
| 31 | if (c == '\r' || c == '\n') | ||
| 32 | state = (state >> 2) | ((c << 6) & 0xc0); | ||
| 33 | else | ||
| 34 | break; | ||
| 35 | if (state >= 0xa0 || state == 0x99) | ||
| 36 | return i + 1; | ||
| 37 | } | ||
| 38 | } | ||
| 39 | return 0; | ||
| 40 | } | ||
| 41 | |||
| 42 | void tcp_read(const int64_t sock, struct ot_workstruct *ws) { | ||
| 43 | struct http_data *cookie = fdstate_cookie(sock); | ||
| 44 | ssize_t byte_count = read((int)sock, ws->inbuf, G_INBUF_SIZE); | ||
| 45 | |||
| 46 | if (byte_count == 0 || (byte_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) { | ||
| 47 | http_close(sock); | ||
| 48 | return; | ||
| 49 | } | ||
| 50 | |||
| 51 | if (byte_count == -1) | ||
| 52 | return; | ||
| 53 | |||
| 54 | ws->request = NULL; | ||
| 55 | ws->request_size = 0; | ||
| 56 | ws->header_size = 0; | ||
| 57 | ws->keep_alive = 0; | ||
| 58 | |||
| 59 | /* If we get the whole request in one packet, handle it without copying */ | ||
| 60 | if (!cookie->request.data) { | ||
| 61 | if ((ws->header_size = header_complete(ws->inbuf, byte_count))) { | ||
| 62 | ws->request = ws->inbuf; | ||
| 63 | ws->request_size = byte_count; | ||
| 64 | http_handle_request(sock, ws); | ||
| 65 | } else if (buf_cat(&cookie->request, ws->inbuf, (size_t)byte_count)) | ||
| 66 | http_close(sock); | ||
| 67 | return; | ||
| 68 | } | ||
| 69 | |||
| 70 | if (buf_cat(&cookie->request, ws->inbuf, (size_t)byte_count) || cookie->request.size > G_INBUF_SIZE) { | ||
| 71 | http_issue_error(sock, ws, CODE_HTTPERROR_500); | ||
| 72 | return; | ||
| 73 | } | ||
| 74 | |||
| 75 | while ((ws->header_size = header_complete(cookie->request.data, (ssize_t)cookie->request.size))) { | ||
| 76 | ws->request = cookie->request.data; | ||
| 77 | ws->request_size = (ssize_t)cookie->request.size; | ||
| 78 | http_handle_request(sock, ws); | ||
| 79 | #ifdef WANT_KEEPALIVE | ||
| 80 | if (!ws->keep_alive) | ||
| 81 | #endif | ||
| 82 | return; | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | void tcp_write(const int64_t sock) { | ||
| 87 | struct http_data *cookie = fdstate_cookie(sock); | ||
| 88 | int chunked = 0; | ||
| 89 | int keep_alive = 0; | ||
| 90 | |||
| 91 | if (cookie) { | ||
| 92 | chunked = (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) != 0; | ||
| 93 | keep_alive = (cookie->flag & STRUCT_HTTP_FLAG_KEEPALIVE) != 0; | ||
| 94 | |||
| 95 | if (cookie->batch) { | ||
| 96 | if (batch_send(sock, cookie->batch) == BATCH_FAILED) { | ||
| 97 | http_close(sock); | ||
| 98 | return; | ||
| 99 | } | ||
| 100 | if (batch_pending(cookie->batch)) | ||
| 101 | return; | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | /* In a chunked transfer, wait for the next piece once everything queued has gone out */ | ||
| 106 | if (chunked) | ||
| 107 | http_interest_set(sock, cookie, 0); | ||
| 108 | else if (keep_alive) { | ||
| 109 | /* The reply is out and the client asked to keep the connection, so go | ||
| 110 | back to waiting for the next request */ | ||
| 111 | http_interest_set(sock, cookie, EVENT_IN); | ||
| 112 | fdstate_set_expiry(sock, time_future_seconds(OT_CLIENT_TIMEOUT)); | ||
| 113 | } else | ||
| 114 | http_close(sock); | ||
| 115 | } | ||
| 116 | |||
| 117 | /* The fast path, assuming that the complete request has usually arrived by the time a | ||
| 118 | connection is accepted, so it is answered here outside the event loop. The more load | ||
| 119 | a tracker has, the more likely it is that by the time it accepts, the roundtrip to | ||
| 120 | the peer has completed. | ||
| 121 | If the request could not be completely handled, the stack cookie is promoted to heap | ||
| 122 | and the connection handed over to the event loop to finish */ | ||
| 123 | void tcp_accept(const int64_t serversocket, struct ot_workstruct *ws) { | ||
| 124 | int64_t sock; | ||
| 125 | ot_ip6 ip; | ||
| 126 | uint16_t port; | ||
| 127 | |||
| 128 | while ((sock = socket_accept(serversocket, ip, &port)) != -1) { | ||
| 129 | struct http_data cookie; | ||
| 130 | uint32_t generation; | ||
| 131 | |||
| 132 | memset(&cookie, 0, sizeof(cookie)); | ||
| 133 | memcpy(cookie.ip, ip, sizeof(ot_ip6)); | ||
| 134 | cookie.interest = EVENT_IN; | ||
| 135 | cookie.flag = STRUCT_HTTP_FLAG_ONSTACK; | ||
| 136 | |||
| 137 | generation = fdstate_open(sock, &cookie, time_future_seconds(OT_CLIENT_TIMEOUT)); | ||
| 138 | if (!fdstate_claim((int)sock, generation)) { | ||
| 139 | fdstate_close(sock); | ||
| 140 | event_close((int)sock); | ||
| 141 | continue; | ||
| 142 | } | ||
| 143 | |||
| 144 | stats_issue_event(ws, EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip); | ||
| 145 | |||
| 146 | tcp_read(sock, ws); | ||
| 147 | |||
| 148 | /* Gone already if it was answered and closed inline */ | ||
| 149 | if (fdstate_generation((int)sock) != generation) | ||
| 150 | continue; | ||
| 151 | |||
| 152 | if (http_adopt(sock, &cookie) == -1) | ||
| 153 | http_close(sock); | ||
| 154 | else | ||
| 155 | fdstate_release((int)sock); | ||
| 156 | } | ||
| 157 | } | ||
diff --git a/ot_tcp.h b/ot_tcp.h new file mode 100644 index 0000000..b0a93f4 --- /dev/null +++ b/ot_tcp.h | |||
| @@ -0,0 +1,22 @@ | |||
| 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 | #ifndef OT_TCP_H__ | ||
| 7 | #define OT_TCP_H__ | ||
| 8 | |||
| 9 | #include <stdint.h> | ||
| 10 | |||
| 11 | #include "trackerlogic.h" | ||
| 12 | |||
| 13 | /* Take whatever connections are waiting on a listening socket and answer | ||
| 14 | them, each on this thread's stack, handing a connection that cannot be | ||
| 15 | finished to the event loop */ | ||
| 16 | void tcp_accept(const int64_t serversocket, struct ot_workstruct *ws); | ||
| 17 | |||
| 18 | /* Handler connections in the event loop has become readable or writable */ | ||
| 19 | void tcp_read(const int64_t sock, struct ot_workstruct *ws); | ||
| 20 | void tcp_write(const int64_t sock); | ||
| 21 | |||
| 22 | #endif | ||
diff --git a/tests/accept_filter_probe.py b/tests/accept_filter_probe.py new file mode 100644 index 0000000..22f585a --- /dev/null +++ b/tests/accept_filter_probe.py | |||
| @@ -0,0 +1,110 @@ | |||
| 1 | """What a deferred accept does to connections that never send anything. | ||
| 2 | |||
| 3 | Those connections never reach opentracker, so its own client timeout cannot | ||
| 4 | clean them up: whatever bound exists is the kernel's. This measures it, and | ||
| 5 | then asks the question that follows from it -- whether enough of them keep | ||
| 6 | the tracker from answering anybody else. | ||
| 7 | |||
| 8 | make opentracker | ||
| 9 | python3 tests/accept_filter_probe.py | ||
| 10 | |||
| 11 | On FreeBSD the filter does nothing unless accf_data is loaded, so run it both | ||
| 12 | ways to see whether the numbers differ: | ||
| 13 | |||
| 14 | kldload accf_data | ||
| 15 | """ | ||
| 16 | |||
| 17 | import os | ||
| 18 | import socket | ||
| 19 | import signal | ||
| 20 | import subprocess | ||
| 21 | import sys | ||
| 22 | import tempfile | ||
| 23 | import time | ||
| 24 | import urllib.error | ||
| 25 | import urllib.request | ||
| 26 | |||
| 27 | TRACKER = os.environ.get("OPENTRACKER", "./opentracker") | ||
| 28 | IDLE = int(os.environ.get("IDLE", "300")) | ||
| 29 | PATIENCE = float(os.environ.get("PATIENCE", "90")) | ||
| 30 | HASH = "".join("%%%02x" % b for b in range(1, 21)) | ||
| 31 | |||
| 32 | |||
| 33 | def start(port): | ||
| 34 | directory = tempfile.mkdtemp(prefix="ot-accf-") | ||
| 35 | with open(os.path.join(directory, "conf"), "w") as handle: | ||
| 36 | handle.write("listen.tcp 127.0.0.1:%d\nlisten.udp 127.0.0.1:%d\n" % (port, port)) | ||
| 37 | log = open(os.path.join(directory, "log"), "w+b") | ||
| 38 | tracker = subprocess.Popen([TRACKER, "-f", os.path.join(directory, "conf")], stdout=log, stderr=log) | ||
| 39 | for _ in range(300): | ||
| 40 | try: | ||
| 41 | socket.create_connection(("127.0.0.1", port), timeout=0.2).close() | ||
| 42 | return tracker, log | ||
| 43 | except OSError: | ||
| 44 | time.sleep(0.02) | ||
| 45 | tracker.kill() | ||
| 46 | raise SystemExit("tracker never listened on %d" % port) | ||
| 47 | |||
| 48 | |||
| 49 | def announce(port, timeout=5): | ||
| 50 | """One ordinary request, so we can see whether the tracker still serves.""" | ||
| 51 | started = time.time() | ||
| 52 | try: | ||
| 53 | urllib.request.urlopen( | ||
| 54 | "http://127.0.0.1:%d/announce?info_hash=%s&peer_id=-OT0001-probe0000001&port=6881&left=0" % (port, HASH), | ||
| 55 | timeout=timeout, | ||
| 56 | ).read() | ||
| 57 | return time.time() - started, None | ||
| 58 | except Exception as problem: # noqa: BLE001 - reported, not swallowed | ||
| 59 | return time.time() - started, problem | ||
| 60 | |||
| 61 | |||
| 62 | def main(): | ||
| 63 | port = int(os.environ.get("PORT", "28000")) | ||
| 64 | tracker, log = start(port) | ||
| 65 | try: | ||
| 66 | print(" how long a connection that says nothing is held") | ||
| 67 | quiet = socket.create_connection(("127.0.0.1", port), timeout=5) | ||
| 68 | quiet.settimeout(PATIENCE) | ||
| 69 | started = time.time() | ||
| 70 | try: | ||
| 71 | held = "dropped after %.1f s" % (time.time() - started) if quiet.recv(1) == b"" else "sent us something" | ||
| 72 | except socket.timeout: | ||
| 73 | held = "still open after %.0f s" % PATIENCE | ||
| 74 | except OSError as problem: | ||
| 75 | held = "ended after %.1f s: %s" % (time.time() - started, problem) | ||
| 76 | quiet.close() | ||
| 77 | print(" %s" % held) | ||
| 78 | |||
| 79 | print(" and whether %d of them keep anybody else out" % IDLE) | ||
| 80 | before, problem = announce(port) | ||
| 81 | print(" a request before: %.0f ms%s" % (before * 1000, "" if problem is None else " (%s)" % problem)) | ||
| 82 | |||
| 83 | idlers = [] | ||
| 84 | for _ in range(IDLE): | ||
| 85 | try: | ||
| 86 | idlers.append(socket.create_connection(("127.0.0.1", port), timeout=2)) | ||
| 87 | except OSError as problem: | ||
| 88 | print(" could only open %d of them: %s" % (len(idlers), problem)) | ||
| 89 | break | ||
| 90 | print(" holding %d connections that have sent nothing" % len(idlers)) | ||
| 91 | |||
| 92 | during, problem = announce(port, timeout=10) | ||
| 93 | print(" a request during: %.0f ms%s" % (during * 1000, "" if problem is None else " REFUSED: %s" % problem)) | ||
| 94 | |||
| 95 | for idler in idlers: | ||
| 96 | idler.close() | ||
| 97 | after, problem = announce(port) | ||
| 98 | print(" a request after: %.0f ms%s" % (after * 1000, "" if problem is None else " (%s)" % problem)) | ||
| 99 | finally: | ||
| 100 | tracker.send_signal(signal.SIGINT) | ||
| 101 | try: | ||
| 102 | tracker.wait(timeout=10) | ||
| 103 | except subprocess.TimeoutExpired: | ||
| 104 | tracker.kill() | ||
| 105 | tracker.wait() | ||
| 106 | log.close() | ||
| 107 | |||
| 108 | |||
| 109 | if __name__ == "__main__": | ||
| 110 | main() | ||
diff --git a/tests/integration/test_config.py b/tests/integration/test_config.py new file mode 100644 index 0000000..1a84fa7 --- /dev/null +++ b/tests/integration/test_config.py | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | """Settings the tracker has to accept, and still work with. | ||
| 2 | |||
| 3 | An unrecognised key makes opentracker refuse to start, so a test that it | ||
| 4 | comes up at all is a test that the key was understood. | ||
| 5 | """ | ||
| 6 | |||
| 7 | import unittest | ||
| 8 | |||
| 9 | from harness import Tracker, TrackerTestCase, free_port | ||
| 10 | |||
| 11 | |||
| 12 | class TcpWorkers(TrackerTestCase): | ||
| 13 | config = "listen.tcp.workers 2\n" | ||
| 14 | |||
| 15 | def test_the_tracker_serves_with_tcp_workers_asked_for(self): | ||
| 16 | self.assertEqual(self.tracker.announce(self.hash_for("tcpworkers"))[b"complete"], 1) | ||
| 17 | |||
| 18 | |||
| 19 | class WorkersOnAListenStatement(unittest.TestCase): | ||
| 20 | def test_a_listen_statement_may_carry_a_worker_count(self): | ||
| 21 | """The option is read off the statement rather than inherited.""" | ||
| 22 | second = free_port() | ||
| 23 | tracker = Tracker("listen.tcp 127.0.0.1:%d workers=2\n" % second) | ||
| 24 | try: | ||
| 25 | self.assertEqual(tracker.announce(b"listenopts\0\0\0\0\0\0\0\0\0\0")[b"complete"], 1) | ||
| 26 | complaints = tracker.stop() | ||
| 27 | self.assertEqual(complaints, [], "the tracker reported:\n" + "\n".join(complaints)) | ||
| 28 | finally: | ||
| 29 | tracker._cleanup() | ||
diff --git a/tests/integration/test_expiry.py b/tests/integration/test_expiry.py index 5a21e57..5e6dc0a 100644 --- a/tests/integration/test_expiry.py +++ b/tests/integration/test_expiry.py | |||
| @@ -27,6 +27,11 @@ class ExpirySweep(TrackerTestCase): | |||
| 27 | def test_the_sweep_takes_the_idle_and_leaves_the_answered(self): | 27 | def test_the_sweep_takes_the_idle_and_leaves_the_answered(self): |
| 28 | idle = socket.create_connection(("127.0.0.1", self.tracker.port), timeout=5) | 28 | idle = socket.create_connection(("127.0.0.1", self.tracker.port), timeout=5) |
| 29 | try: | 29 | try: |
| 30 | # Enough for a kernel that holds a connection back until data | ||
| 31 | # arrives to hand it over, and far too little to be answered, so | ||
| 32 | # the tracker has accepted it and is waiting for the rest | ||
| 33 | idle.sendall(b"GET /ann") | ||
| 34 | |||
| 30 | self.assertEqual(self.tracker.announce(self.hash_for("expiry-sweep"))[b"complete"], 1) | 35 | self.assertEqual(self.tracker.announce(self.hash_for("expiry-sweep"))[b"complete"], 1) |
| 31 | 36 | ||
| 32 | time.sleep(CLIENT_TIMEOUT + 3 * CHECK_INTERVAL) | 37 | time.sleep(CLIENT_TIMEOUT + 3 * CHECK_INTERVAL) |
| @@ -34,6 +39,6 @@ class ExpirySweep(TrackerTestCase): | |||
| 34 | self.assertEqual(self.tracker.torrent_count(), 1, "the tracker stopped serving while sweeping") | 39 | self.assertEqual(self.tracker.torrent_count(), 1, "the tracker stopped serving while sweeping") |
| 35 | 40 | ||
| 36 | idle.settimeout(5) | 41 | idle.settimeout(5) |
| 37 | self.assertEqual(idle.recv(64), b"", "a connection that never sent a request was not swept") | 42 | self.assertEqual(idle.recv(64), b"", "a connection that went quiet mid-request was not swept") |
| 38 | finally: | 43 | finally: |
| 39 | idle.close() | 44 | idle.close() |
diff --git a/tests/t_event.c b/tests/t_event.c index ad3948f..1908dc9 100644 --- a/tests/t_event.c +++ b/tests/t_event.c | |||
| @@ -241,9 +241,40 @@ static void case_close_takes_it_out(void) { | |||
| 241 | pair_close(); | 241 | pair_close(); |
| 242 | } | 242 | } |
| 243 | 243 | ||
| 244 | /* A descriptor can be taken on before anybody waits for anything on it: the | ||
| 245 | full scrape parks a connection while a worker thread builds the reply. The | ||
| 246 | mechanisms disagree about what registering nothing means, so what is | ||
| 247 | checked is that asking for a filter afterwards still works. */ | ||
| 248 | static void case_add_nothing_then_ask(void) { | ||
| 249 | test_case("a descriptor added with no interest can be given some later"); | ||
| 250 | pair_open(); | ||
| 251 | CHECK(sv[0] != -1); | ||
| 252 | CHECK_INT(event_add(sv[0], 0, 7), 0); | ||
| 253 | CHECK_INT((int)write(sv[1], "x", 1), 1); | ||
| 254 | CHECK_INT(wait_for(sv[0], EVENT_IN, 200, NULL), 0); | ||
| 255 | |||
| 256 | CHECK_INT(event_modify(sv[0], EVENT_IN, 7), 0); | ||
| 257 | { | ||
| 258 | uint32_t generation = 0; | ||
| 259 | CHECK_INT(wait_for(sv[0], EVENT_IN, 500, &generation), 1); | ||
| 260 | CHECK_INT((int)generation, 7); | ||
| 261 | } | ||
| 262 | event_del(sv[0]); | ||
| 263 | pair_close(); | ||
| 264 | } | ||
| 265 | |||
| 266 | /* Whether a worker thread may register a descriptor while this one waits | ||
| 267 | depends on who keeps the set. Tying the answer to the mechanism's name | ||
| 268 | here means a backend added later cannot quietly forget to say. */ | ||
| 269 | static void case_sharing_matches_the_mechanism(void) { | ||
| 270 | test_case("only a set the kernel keeps may be changed from another thread"); | ||
| 271 | CHECK_INT(event_can_share(), strcmp(event_mechanism(), "poll") ? 1 : 0); | ||
| 272 | } | ||
| 273 | |||
| 244 | void test_event(void) { | 274 | void test_event(void) { |
| 245 | CHECK_INT(event_init(), 0); | 275 | CHECK_INT(event_init(), 0); |
| 246 | CHECK(event_mechanism() != NULL); | 276 | CHECK(event_mechanism() != NULL); |
| 277 | case_sharing_matches_the_mechanism(); | ||
| 247 | 278 | ||
| 248 | case_readable(); | 279 | case_readable(); |
| 249 | case_writable(); | 280 | case_writable(); |
| @@ -254,6 +285,7 @@ void test_event(void) { | |||
| 254 | case_timeout(); | 285 | case_timeout(); |
| 255 | case_unknown_descriptor(); | 286 | case_unknown_descriptor(); |
| 256 | case_close_takes_it_out(); | 287 | case_close_takes_it_out(); |
| 288 | case_add_nothing_then_ask(); | ||
| 257 | 289 | ||
| 258 | event_deinit(); | 290 | event_deinit(); |
| 259 | } | 291 | } |
