diff options
Diffstat (limited to 'src')
56 files changed, 10281 insertions, 0 deletions
diff --git a/src/opentracker.c b/src/opentracker.c new file mode 100644 index 0000000..60a6d8c --- /dev/null +++ b/src/opentracker.c | |||
| @@ -0,0 +1,443 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. | ||
| 3 | Some of the stuff below is stolen from Fefes example libowfat httpd. */ | ||
| 4 | |||
| 5 | /* System */ | ||
| 6 | #include <ctype.h> | ||
| 7 | #include <errno.h> | ||
| 8 | #include <limits.h> | ||
| 9 | #include <pthread.h> | ||
| 10 | #include <pwd.h> | ||
| 11 | #include <signal.h> | ||
| 12 | #include <stdint.h> | ||
| 13 | #include <stdio.h> | ||
| 14 | #include <stdlib.h> | ||
| 15 | #include <string.h> | ||
| 16 | #include <sys/socket.h> | ||
| 17 | #include <unistd.h> | ||
| 18 | #ifdef WANT_SYSLOGS | ||
| 19 | #include <syslog.h> | ||
| 20 | #endif | ||
| 21 | |||
| 22 | /* Opentracker */ | ||
| 23 | #include "ot_accesslist.h" | ||
| 24 | #include "ot_config.h" | ||
| 25 | #include "ot_hex.h" | ||
| 26 | #include "ot_http.h" | ||
| 27 | #include "ot_ip.h" | ||
| 28 | #include "ot_livesync.h" | ||
| 29 | #include "ot_mutex.h" | ||
| 30 | #include "ot_workqueue.h" | ||
| 31 | #include "ot_random.h" | ||
| 32 | #include "ot_scan.h" | ||
| 33 | #include "ot_event.h" | ||
| 34 | #include "ot_fdstate.h" | ||
| 35 | #include "ot_socket.h" | ||
| 36 | #include "ot_stats.h" | ||
| 37 | #include "ot_tcp.h" | ||
| 38 | #include "ot_udp.h" | ||
| 39 | #include "ot_worker.h" | ||
| 40 | #include "trackerlogic.h" | ||
| 41 | |||
| 42 | /* Globals */ | ||
| 43 | uint32_t g_tracker_id; | ||
| 44 | _Atomic int g_opentracker_running = 1; | ||
| 45 | int g_self_pipe[2]; | ||
| 46 | |||
| 47 | static void panic(const char *routine) __attribute__((noreturn)); | ||
| 48 | static void panic(const char *routine) { | ||
| 49 | fprintf(stderr, "%s: %s\n", routine, strerror(errno)); | ||
| 50 | exit(111); | ||
| 51 | } | ||
| 52 | |||
| 53 | /* Say we are stopping, wake the loop, and let main do the work on its own | ||
| 54 | stack. Everything other than SIGINT falls through and does nothing, which | ||
| 55 | is what OT_SIGNAL_WAKE wants: returning from the read is all it is for. */ | ||
| 56 | static void signal_handler(int s) { | ||
| 57 | if (s == SIGINT) { | ||
| 58 | const char byte = 0; | ||
| 59 | int saved = errno; | ||
| 60 | |||
| 61 | /* Any new interrupt signal quits the application */ | ||
| 62 | signal(SIGINT, SIG_DFL); | ||
| 63 | |||
| 64 | /* Tell all other threads to not acquire any new lock on a bucket | ||
| 65 | but cancel their operations and return */ | ||
| 66 | g_opentracker_running = 0; | ||
| 67 | |||
| 68 | (void)!write(g_self_pipe[1], &byte, 1); | ||
| 69 | errno = saved; | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | static void defaul_signal_handlers(void) { | ||
| 74 | sigset_t signal_mask; | ||
| 75 | sigemptyset(&signal_mask); | ||
| 76 | sigaddset(&signal_mask, SIGPIPE); | ||
| 77 | sigaddset(&signal_mask, SIGHUP); | ||
| 78 | sigaddset(&signal_mask, SIGINT); | ||
| 79 | sigaddset(&signal_mask, SIGALRM); | ||
| 80 | pthread_sigmask(SIG_BLOCK, &signal_mask, NULL); | ||
| 81 | } | ||
| 82 | |||
| 83 | static void install_signal_handlers(void) { | ||
| 84 | struct sigaction sa; | ||
| 85 | sigset_t signal_mask; | ||
| 86 | sigemptyset(&signal_mask); | ||
| 87 | |||
| 88 | sa.sa_handler = signal_handler; | ||
| 89 | sigemptyset(&sa.sa_mask); | ||
| 90 | sa.sa_flags = 0; | ||
| 91 | if ((sigaction(SIGINT, &sa, NULL) == -1) || (sigaction(SIGALRM, &sa, NULL) == -1) || | ||
| 92 | (sigaction(OT_SIGNAL_WAKE, &sa, NULL) == -1)) | ||
| 93 | panic("install_signal_handlers"); | ||
| 94 | |||
| 95 | sigaddset(&signal_mask, SIGINT); | ||
| 96 | pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL); | ||
| 97 | } | ||
| 98 | |||
| 99 | |||
| 100 | /* How many ready descriptors one wait hands back at a time */ | ||
| 101 | #define OT_EVENT_BATCH 512 | ||
| 102 | |||
| 103 | static void *server_mainloop(void *args) { | ||
| 104 | struct ot_workstruct ws; | ||
| 105 | time_t next_timeout_check = time_future_seconds(OT_CLIENT_TIMEOUT_CHECKINTERVAL); | ||
| 106 | struct iovec *iovector; | ||
| 107 | size_t iovec_entries; | ||
| 108 | int is_partial; | ||
| 109 | |||
| 110 | (void)args; | ||
| 111 | |||
| 112 | if (workstruct_init(&ws, G_INBUF_SIZE) == -1) | ||
| 113 | panic("Initializing worker failed"); | ||
| 114 | |||
| 115 | while (g_opentracker_running) { | ||
| 116 | ot_event ready[OT_EVENT_BATCH]; | ||
| 117 | int64_t sock; | ||
| 118 | int count, i; | ||
| 119 | |||
| 120 | count = event_wait(ready, OT_EVENT_BATCH, OT_CLIENT_TIMEOUT_CHECKINTERVAL * 1000); | ||
| 121 | |||
| 122 | for (i = 0; i < count; ++i) { | ||
| 123 | const void *cookie; | ||
| 124 | |||
| 125 | sock = ready[i].fd; | ||
| 126 | /* A descriptor closed earlier in this same batch may already have been | ||
| 127 | handed out again by accept, so an event is only ours if the | ||
| 128 | generation it was registered under is still the one in place */ | ||
| 129 | if (fdstate_generation((int)sock) != ready[i].generation) | ||
| 130 | continue; | ||
| 131 | cookie = fdstate_cookie((int)sock); | ||
| 132 | |||
| 133 | if ((intptr_t)cookie == FLAG_TCP) | ||
| 134 | tcp_accept(sock, &ws); | ||
| 135 | else if ((intptr_t)cookie == FLAG_UDP) | ||
| 136 | handle_udp6(sock, &ws); | ||
| 137 | else if ((intptr_t)cookie == FLAG_SELFPIPE) | ||
| 138 | while (read((int)sock, ws.inbuf, G_INBUF_SIZE) > 0) | ||
| 139 | ; | ||
| 140 | else if (fdstate_claim((int)sock, ready[i].generation)) { | ||
| 141 | if (ready[i].what & EVENT_IN) | ||
| 142 | tcp_read(sock, &ws); | ||
| 143 | /* The read may have closed it, so look again before writing */ | ||
| 144 | if ((ready[i].what & EVENT_OUT) && fdstate_generation((int)sock) == ready[i].generation) | ||
| 145 | tcp_write(sock); | ||
| 146 | if (fdstate_generation((int)sock) == ready[i].generation) | ||
| 147 | fdstate_release((int)sock); | ||
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | while ((sock = workqueue_popresult(&iovec_entries, &iovector, &is_partial)) != -1) | ||
| 152 | http_sendiovecdata(sock, &ws, iovec_entries, iovector, is_partial); | ||
| 153 | |||
| 154 | if (time_now_seconds() > next_timeout_check) { | ||
| 155 | int cursor = 0; | ||
| 156 | |||
| 157 | while ((sock = fdstate_next_expired(time_now_seconds(), &cursor)) != -1) | ||
| 158 | http_close(sock); | ||
| 159 | next_timeout_check = time_future_seconds(OT_CLIENT_TIMEOUT_CHECKINTERVAL); | ||
| 160 | } | ||
| 161 | |||
| 162 | livesync_ticker(); | ||
| 163 | } | ||
| 164 | |||
| 165 | /* This work struct is about to go out of scope, so hand its tally over | ||
| 166 | before anything can read it there */ | ||
| 167 | workstruct_deinit(&ws); | ||
| 168 | return 0; | ||
| 169 | } | ||
| 170 | |||
| 171 | |||
| 172 | /* A group of sockets sharing one address only spreads datagrams if every | ||
| 173 | member asked for it before binding, the first one included, so whether | ||
| 174 | this entry will have workers has to be known here. */ | ||
| 175 | static socket_sharing listen_sharing_for(const ot_listen *entry) { | ||
| 176 | if (entry->proto != FLAG_MCA && entry->workers > 1 && socket_can_balance()) | ||
| 177 | return SOCKET_BALANCED; | ||
| 178 | return entry->shared ? SOCKET_SHARED : SOCKET_EXCLUSIVE; | ||
| 179 | } | ||
| 180 | |||
| 181 | static void listen_bind_one(ot_listen *entry) { | ||
| 182 | socket_sharing sharing = listen_sharing_for(entry); | ||
| 183 | |||
| 184 | #ifdef _DEBUG | ||
| 185 | { | ||
| 186 | const char *protos[] = {"TCP", "UDP", "UDP mcast"}; | ||
| 187 | char _debug[512]; | ||
| 188 | int off = snprintf(_debug, sizeof(_debug), "Binding socket type %s to address [", protos[entry->proto]); | ||
| 189 | off += ip_format(_debug + off, entry->ip); | ||
| 190 | snprintf(_debug + off, sizeof(_debug) - off, "]:%d...", entry->port); | ||
| 191 | fputs(_debug, stderr); | ||
| 192 | } | ||
| 193 | #endif | ||
| 194 | |||
| 195 | #ifdef WANT_SYNC_LIVE | ||
| 196 | if (entry->proto == FLAG_MCA) { | ||
| 197 | livesync_bind_mcast(entry->ip, entry->port); | ||
| 198 | #ifdef _DEBUG | ||
| 199 | fputs(" success.\n", stderr); | ||
| 200 | #endif | ||
| 201 | return; | ||
| 202 | } | ||
| 203 | #endif | ||
| 204 | |||
| 205 | if (entry->proto == FLAG_TCP) { | ||
| 206 | if ((entry->sock = socket_tcp_listen(entry->ip, entry->port, sharing)) == -1) | ||
| 207 | panic("socket_tcp_listen"); | ||
| 208 | socket_defer_accept((int)entry->sock); | ||
| 209 | } else { | ||
| 210 | if ((entry->sock = socket_udp_bind(entry->ip, entry->port, sharing)) == -1) | ||
| 211 | panic("socket_udp_bind"); | ||
| 212 | } | ||
| 213 | |||
| 214 | if (event_add((int)entry->sock, EVENT_IN, fdstate_open(entry->sock, (void *)entry->proto, 0)) == -1) | ||
| 215 | panic("event_add"); | ||
| 216 | |||
| 217 | #ifdef _DEBUG | ||
| 218 | fputs(" success.\n", stderr); | ||
| 219 | #endif | ||
| 220 | } | ||
| 221 | |||
| 222 | /* Bound in the order the entries were given. | ||
| 223 | Note: On macOS SO_REUSEPORT delivers everything to the socket that bound last. */ | ||
| 224 | static void listen_bind_all(void) { | ||
| 225 | size_t count, i; | ||
| 226 | ot_listen *entries = config_listen_entries(&count); | ||
| 227 | |||
| 228 | for (i = 0; i < count; ++i) | ||
| 229 | listen_bind_one(entries + i); | ||
| 230 | } | ||
| 231 | |||
| 232 | /* The socket the event loop was using is passed to the first worker. | ||
| 233 | If start up tests show that the kernel can spread connections over | ||
| 234 | a group bound to one address, every worker binds one of its own. | ||
| 235 | If not, all workers call accept on the first. */ | ||
| 236 | static void listen_start_tcp_workers(void) { | ||
| 237 | size_t count, i; | ||
| 238 | ot_listen *entries = config_listen_entries(&count); | ||
| 239 | |||
| 240 | for (i = 0; i < count; ++i) { | ||
| 241 | ot_listen *entry = entries + i; | ||
| 242 | int balance = listen_sharing_for(entry) == SOCKET_BALANCED; | ||
| 243 | int worker; | ||
| 244 | |||
| 245 | if (entry->proto != FLAG_TCP || entry->workers <= 0) | ||
| 246 | continue; | ||
| 247 | |||
| 248 | event_del((int)entry->sock); | ||
| 249 | tcp_worker_install(entry->sock); | ||
| 250 | |||
| 251 | for (worker = 1; worker < entry->workers; ++worker) { | ||
| 252 | int64_t sock = entry->sock; | ||
| 253 | |||
| 254 | if (balance) { | ||
| 255 | sock = socket_tcp_listen(entry->ip, entry->port, SOCKET_BALANCED); | ||
| 256 | if (sock == -1) { | ||
| 257 | fprintf(stderr, "Warning: could not bind another socket for tcp worker %d: %s. Sharing the first instead.\n", worker, | ||
| 258 | strerror(errno)); | ||
| 259 | sock = entry->sock; | ||
| 260 | } | ||
| 261 | } | ||
| 262 | tcp_worker_install(sock); | ||
| 263 | } | ||
| 264 | } | ||
| 265 | } | ||
| 266 | |||
| 267 | static void listen_start_udp_workers(void) { | ||
| 268 | size_t count, i; | ||
| 269 | ot_listen *entries = config_listen_entries(&count); | ||
| 270 | |||
| 271 | udp_init(); | ||
| 272 | |||
| 273 | for (i = 0; i < count; ++i) { | ||
| 274 | ot_listen *entry = entries + i; | ||
| 275 | int balance = listen_sharing_for(entry) == SOCKET_BALANCED; | ||
| 276 | int worker; | ||
| 277 | |||
| 278 | if (entry->proto != FLAG_UDP || entry->workers <= 0) | ||
| 279 | continue; | ||
| 280 | |||
| 281 | event_del((int)entry->sock); | ||
| 282 | udp_worker_install(entry->sock); | ||
| 283 | |||
| 284 | for (worker = 1; worker < entry->workers; ++worker) { | ||
| 285 | int64_t sock = entry->sock; | ||
| 286 | |||
| 287 | if (balance) { | ||
| 288 | sock = socket_udp_bind(entry->ip, entry->port, SOCKET_BALANCED); | ||
| 289 | if (sock == -1) { | ||
| 290 | fprintf(stderr, "Warning: could not bind another socket for udp worker %d: %s. Sharing the first instead.\n", worker, strerror(errno)); | ||
| 291 | sock = entry->sock; | ||
| 292 | } | ||
| 293 | } | ||
| 294 | udp_worker_install(sock); | ||
| 295 | } | ||
| 296 | } | ||
| 297 | } | ||
| 298 | |||
| 299 | void load_state(const char *const state_filename) { | ||
| 300 | FILE *state_filehandle; | ||
| 301 | char inbuf[512]; | ||
| 302 | ot_hash infohash; | ||
| 303 | uint64_t base, downcount; | ||
| 304 | int consumed; | ||
| 305 | |||
| 306 | state_filehandle = fopen(state_filename, "r"); | ||
| 307 | |||
| 308 | if (state_filehandle == NULL) { | ||
| 309 | fprintf(stderr, "Warning: Can't open config file: %s.", state_filename); | ||
| 310 | return; | ||
| 311 | } | ||
| 312 | |||
| 313 | /* We do ignore anything that is not of the form "^[:xdigit:]:\d+:\d+" */ | ||
| 314 | while (fgets(inbuf, sizeof(inbuf), state_filehandle)) { | ||
| 315 | size_t i; | ||
| 316 | |||
| 317 | if (!hex_to_hash(infohash, inbuf)) | ||
| 318 | continue; | ||
| 319 | i = 2 * sizeof(ot_hash); | ||
| 320 | |||
| 321 | if (inbuf[i++] != ':' || !(consumed = scan_uint(inbuf + i, &base, UINT64_MAX))) | ||
| 322 | continue; | ||
| 323 | i += consumed; | ||
| 324 | if (inbuf[i++] != ':' || !(consumed = scan_uint(inbuf + i, &downcount, UINT64_MAX))) | ||
| 325 | continue; | ||
| 326 | add_torrent_from_saved_state(infohash, base, downcount); | ||
| 327 | } | ||
| 328 | |||
| 329 | fclose(state_filehandle); | ||
| 330 | } | ||
| 331 | |||
| 332 | int drop_privileges(const char *const serveruser, const char *const serverdir) { | ||
| 333 | struct passwd *pws = NULL; | ||
| 334 | |||
| 335 | #ifdef _DEBUG | ||
| 336 | if (!geteuid()) | ||
| 337 | fprintf(stderr, "Dropping to user %s.\n", serveruser); | ||
| 338 | if (serverdir) | ||
| 339 | fprintf(stderr, "ch%s'ing to directory %s.\n", geteuid() ? "dir" : "root", serverdir); | ||
| 340 | #endif | ||
| 341 | |||
| 342 | /* Grab pws entry before chrooting */ | ||
| 343 | pws = getpwnam(serveruser); | ||
| 344 | endpwent(); | ||
| 345 | |||
| 346 | if (geteuid() == 0) { | ||
| 347 | /* Running as root: chroot and drop privileges */ | ||
| 348 | if (serverdir && chroot(serverdir)) { | ||
| 349 | fprintf(stderr, "Could not chroot to %s, because: %s\n", serverdir, strerror(errno)); | ||
| 350 | return -1; | ||
| 351 | } | ||
| 352 | |||
| 353 | if (chdir("/")) | ||
| 354 | panic("chdir() failed after chrooting: "); | ||
| 355 | |||
| 356 | /* If we can't find server user, revert to nobody's default uid */ | ||
| 357 | if (!pws) { | ||
| 358 | fprintf(stderr, "Warning: Could not get password entry for %s. Reverting to uid -2.\n", serveruser); | ||
| 359 | if (setegid((gid_t)-2) || setgid((gid_t)-2) || setuid((uid_t)-2) || seteuid((uid_t)-2)) | ||
| 360 | panic("Could not set uid to value -2"); | ||
| 361 | } else { | ||
| 362 | if (setegid(pws->pw_gid) || setgid(pws->pw_gid) || setuid(pws->pw_uid) || seteuid(pws->pw_uid)) | ||
| 363 | panic("Could not set uid to specified value"); | ||
| 364 | } | ||
| 365 | |||
| 366 | if (geteuid() == 0 || getegid() == 0) | ||
| 367 | panic("Still running with root privileges?!"); | ||
| 368 | } else { | ||
| 369 | /* Normal user, just chdir() */ | ||
| 370 | if (serverdir && chdir(serverdir)) { | ||
| 371 | fprintf(stderr, "Could not chroot to %s, because: %s\n", serverdir, strerror(errno)); | ||
| 372 | return -1; | ||
| 373 | } | ||
| 374 | } | ||
| 375 | |||
| 376 | return 0; | ||
| 377 | } | ||
| 378 | |||
| 379 | /* Maintain our copy of the clock. time() on BSDs is very expensive. */ | ||
| 380 | int main(int argc, char **argv) { | ||
| 381 | |||
| 382 | random_init(); | ||
| 383 | if (fdstate_init() == -1) | ||
| 384 | panic("fdstate_init"); | ||
| 385 | if (event_init() == -1) | ||
| 386 | panic("event_init"); | ||
| 387 | config_parse(argc, argv); | ||
| 388 | |||
| 389 | listen_bind_all(); | ||
| 390 | |||
| 391 | defaul_signal_handlers(); | ||
| 392 | |||
| 393 | #ifdef WANT_SYSLOGS | ||
| 394 | openlog("opentracker", 0, LOG_USER); | ||
| 395 | setlogmask(LOG_UPTO(LOG_INFO)); | ||
| 396 | #endif | ||
| 397 | |||
| 398 | if (drop_privileges(g_serveruser ? g_serveruser : "nobody", g_serverdir) == -1) | ||
| 399 | panic("drop_privileges failed, exiting. Last error"); | ||
| 400 | |||
| 401 | |||
| 402 | /* Create our self pipe which allows us to interrupt mainloops | ||
| 403 | wait in case some data is available to send out */ | ||
| 404 | if (pipe(g_self_pipe) == -1) | ||
| 405 | panic("selfpipe failed: "); | ||
| 406 | /* Both ends have to be non blocking: the loop drains the read end until it | ||
| 407 | is empty, and a worker nudging a full pipe has already made its point */ | ||
| 408 | socket_set_blocking(g_self_pipe[0], 0); | ||
| 409 | socket_set_blocking(g_self_pipe[1], 0); | ||
| 410 | if (event_add(g_self_pipe[0], EVENT_IN, fdstate_open(g_self_pipe[0], (void *)FLAG_SELFPIPE, 0)) == -1) | ||
| 411 | panic("selfpipe event_add"); | ||
| 412 | |||
| 413 | /* Init all sub systems. This call may fail with an exit() */ | ||
| 414 | trackerlogic_init(); | ||
| 415 | |||
| 416 | #ifdef _DEBUG_RANDOMTORRENTS | ||
| 417 | fprintf(stderr, "DEBUG: Generating %d random peers on random torrents. This may take a while. (Setting RANDOMTORRENTS in trackerlogic.h)\n", RANDOMTORRENTS); | ||
| 418 | trackerlogic_add_random_torrents(RANDOMTORRENTS); | ||
| 419 | fprintf(stderr, "... done.\n"); | ||
| 420 | #endif | ||
| 421 | |||
| 422 | if (g_statefile) | ||
| 423 | load_state(g_statefile); | ||
| 424 | |||
| 425 | install_signal_handlers(); | ||
| 426 | |||
| 427 | listen_start_tcp_workers(); | ||
| 428 | listen_start_udp_workers(); | ||
| 429 | |||
| 430 | server_mainloop(0); | ||
| 431 | worker_stop_all(); | ||
| 432 | |||
| 433 | /* No thread is serving any more, so the teardown can run where a debugger | ||
| 434 | and the sanitizers can follow it */ | ||
| 435 | trackerlogic_deinit(); | ||
| 436 | #ifdef WANT_SYSLOGS | ||
| 437 | closelog(); | ||
| 438 | #endif | ||
| 439 | |||
| 440 | event_deinit(); | ||
| 441 | fdstate_deinit(); | ||
| 442 | return 0; | ||
| 443 | } | ||
diff --git a/src/ot_accesslist.c b/src/ot_accesslist.c new file mode 100644 index 0000000..86f9a7d --- /dev/null +++ b/src/ot_accesslist.c | |||
| @@ -0,0 +1,601 @@ | |||
| 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 <pthread.h> | ||
| 6 | #include <signal.h> | ||
| 7 | #include <stdio.h> | ||
| 8 | #include <stdlib.h> | ||
| 9 | #include <string.h> | ||
| 10 | #include <unistd.h> | ||
| 11 | #if defined(WANT_ACCESSLIST_BLACK) || defined(WANT_ACCESSLIST_WHITE) | ||
| 12 | #include <fcntl.h> | ||
| 13 | #include <stdint.h> | ||
| 14 | #include <sys/mman.h> | ||
| 15 | #include <sys/stat.h> | ||
| 16 | #include <sys/types.h> | ||
| 17 | #endif | ||
| 18 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 19 | #include <errno.h> | ||
| 20 | #endif | ||
| 21 | |||
| 22 | /* Opentracker */ | ||
| 23 | #include "ot_accesslist.h" | ||
| 24 | #include "ot_hex.h" | ||
| 25 | #include "ot_ip.h" | ||
| 26 | #include "ot_vector.h" | ||
| 27 | #include "trackerlogic.h" | ||
| 28 | |||
| 29 | /* GLOBAL VARIABLES */ | ||
| 30 | #ifdef WANT_ACCESSLIST | ||
| 31 | const char *g_accesslist_filename = NULL; | ||
| 32 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 33 | const char *g_accesslist_pipe_add = NULL; | ||
| 34 | const char *g_accesslist_pipe_delete = NULL; | ||
| 35 | #endif | ||
| 36 | static pthread_mutex_t g_accesslist_mutex; | ||
| 37 | |||
| 38 | /* Accesslists are lock free linked lists. We can not make them locking, because every announce | ||
| 39 | would try to acquire the mutex, making it the most contested mutex in the whole of opentracker, | ||
| 40 | basically creating a central performance choke point. | ||
| 41 | |||
| 42 | The idea is that updating the list heads happens under the g_accesslist_mutex guard and is | ||
| 43 | done atomically, while consumers might potentially still hold pointers deeper inside the list. | ||
| 44 | |||
| 45 | Consumers (for now only via accesslist_hashisvalid) will always fetch the list head pointer | ||
| 46 | that is guaranteed to live for at least five minutes. This should be many orders of magnitudes | ||
| 47 | more than how long it will be needed by the bsearch done on the list. */ | ||
| 48 | struct ot_accesslist; | ||
| 49 | typedef struct ot_accesslist ot_accesslist; | ||
| 50 | struct ot_accesslist { | ||
| 51 | ot_hash *list; | ||
| 52 | size_t size; | ||
| 53 | ot_time base; | ||
| 54 | ot_accesslist *next; | ||
| 55 | }; | ||
| 56 | static ot_accesslist *_Atomic g_accesslist = NULL; | ||
| 57 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 58 | static ot_accesslist *_Atomic g_accesslist_add = NULL; | ||
| 59 | static ot_accesslist *_Atomic g_accesslist_delete = NULL; | ||
| 60 | #endif | ||
| 61 | |||
| 62 | /* Helpers to work on access lists */ | ||
| 63 | static int vector_compare_hash(const void *hash1, const void *hash2) { return memcmp(hash1, hash2, OT_HASH_COMPARE_SIZE); } | ||
| 64 | |||
| 65 | static ot_accesslist *accesslist_free(ot_accesslist *accesslist) { | ||
| 66 | while (accesslist) { | ||
| 67 | ot_accesslist *this_accesslist = accesslist; | ||
| 68 | accesslist = this_accesslist->next; | ||
| 69 | free(this_accesslist->list); | ||
| 70 | free(this_accesslist); | ||
| 71 | } | ||
| 72 | return NULL; | ||
| 73 | } | ||
| 74 | |||
| 75 | static ot_accesslist *accesslist_make(ot_accesslist *next, size_t size) { | ||
| 76 | ot_accesslist *accesslist_new = malloc(sizeof(ot_accesslist)); | ||
| 77 | if (accesslist_new) { | ||
| 78 | accesslist_new->list = size ? malloc(sizeof(ot_hash) * size) : NULL; | ||
| 79 | accesslist_new->size = size; | ||
| 80 | accesslist_new->base = 0; | ||
| 81 | accesslist_new->next = next; | ||
| 82 | if (size && !accesslist_new->list) { | ||
| 83 | free(accesslist_new); | ||
| 84 | accesslist_new = NULL; | ||
| 85 | } | ||
| 86 | } | ||
| 87 | return accesslist_new; | ||
| 88 | } | ||
| 89 | |||
| 90 | /* This must be called with g_accesslist_mutex held. | ||
| 91 | This will never delete head, because that might still be in use. */ | ||
| 92 | static void accesslist_clean(ot_accesslist *accesslist) { | ||
| 93 | while (accesslist && accesslist->next) { | ||
| 94 | if (accesslist->next->base + 5 < time_now_minutes()) | ||
| 95 | accesslist->next = accesslist_free(accesslist->next); | ||
| 96 | accesslist = accesslist->next; | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | /* A hash needs forty bytes, and all but a final one are followed by at least | ||
| 101 | one separator, so n hashes need at least 41n - 1 bytes. (len + 1) / 41 is | ||
| 102 | therefore an exact upper bound on what a buffer of len bytes can hold. */ | ||
| 103 | size_t accesslist_parse_hashes(ot_hash *dest, size_t capacity, const char *buf, size_t len) { | ||
| 104 | const char *end = buf + len; | ||
| 105 | const char *offs = buf; | ||
| 106 | size_t count = 0; | ||
| 107 | |||
| 108 | while (count < capacity && end - offs >= 40) { | ||
| 109 | if (hex_to_hash(dest[count], offs)) { | ||
| 110 | offs += 40; | ||
| 111 | |||
| 112 | if (offs == end || !hex_isdigit((unsigned char)*offs)) | ||
| 113 | ++count; | ||
| 114 | } | ||
| 115 | |||
| 116 | /* Find start of next line */ | ||
| 117 | while (offs < end && *(offs++) != '\n') {} | ||
| 118 | } | ||
| 119 | |||
| 120 | return count; | ||
| 121 | } | ||
| 122 | |||
| 123 | /* Map a file read only. Returns 0 and sets map and maplen on success, -1 if | ||
| 124 | the file cannot be opened or mapped. */ | ||
| 125 | static int accesslist_map(const char *filename, const char **map, size_t *maplen) { | ||
| 126 | struct stat st; | ||
| 127 | void *mapped; | ||
| 128 | int fd = open(filename, O_RDONLY); | ||
| 129 | |||
| 130 | if (fd < 0) | ||
| 131 | return -1; | ||
| 132 | |||
| 133 | if (fstat(fd, &st) < 0 || st.st_size < 0 || (uintmax_t)st.st_size > (uintmax_t)SIZE_MAX) { | ||
| 134 | close(fd); | ||
| 135 | return -1; | ||
| 136 | } | ||
| 137 | |||
| 138 | if (st.st_size == 0) { | ||
| 139 | close(fd); | ||
| 140 | *map = NULL; | ||
| 141 | *maplen = 0; | ||
| 142 | return 0; | ||
| 143 | } | ||
| 144 | |||
| 145 | mapped = mmap(NULL, (size_t)st.st_size, PROT_READ, MAP_SHARED, fd, 0); | ||
| 146 | close(fd); | ||
| 147 | |||
| 148 | if (mapped == MAP_FAILED) | ||
| 149 | return -1; | ||
| 150 | |||
| 151 | *map = mapped; | ||
| 152 | *maplen = (size_t)st.st_size; | ||
| 153 | return 0; | ||
| 154 | } | ||
| 155 | |||
| 156 | static void accesslist_readfile(void) { | ||
| 157 | ot_accesslist *accesslist_new; | ||
| 158 | const char *map; | ||
| 159 | size_t maplen, capacity, found; | ||
| 160 | |||
| 161 | if (!g_accesslist_filename) | ||
| 162 | return; | ||
| 163 | |||
| 164 | if (accesslist_map(g_accesslist_filename, &map, &maplen) < 0) { | ||
| 165 | char *wd = getcwd(NULL, 0); | ||
| 166 | fprintf(stderr, "Warning: Can't open accesslist file: %s (but will try to create it later, if necessary and possible).\nPWD: %s\n", g_accesslist_filename, wd); | ||
| 167 | free(wd); | ||
| 168 | return; | ||
| 169 | } | ||
| 170 | |||
| 171 | /* Make room for the most info_hashes this file could possibly hold */ | ||
| 172 | capacity = (maplen + 1) / 41; | ||
| 173 | accesslist_new = accesslist_make(g_accesslist, capacity); | ||
| 174 | if (!accesslist_new) { | ||
| 175 | fprintf(stderr, "Warning: Not enough memory to allocate %zd bytes for accesslist buffer. May succeed later.\n", capacity * sizeof(ot_hash)); | ||
| 176 | if (map) | ||
| 177 | munmap((void *)map, maplen); | ||
| 178 | return; | ||
| 179 | } | ||
| 180 | |||
| 181 | found = maplen ? accesslist_parse_hashes(accesslist_new->list, capacity, map, maplen) : 0; | ||
| 182 | #ifdef _DEBUG | ||
| 183 | fprintf(stderr, "Added %zd info_hashes to accesslist\n", found); | ||
| 184 | #endif | ||
| 185 | |||
| 186 | if (map) | ||
| 187 | munmap((void *)map, maplen); | ||
| 188 | |||
| 189 | if (found) | ||
| 190 | qsort(accesslist_new->list, found, sizeof(ot_hash), vector_compare_hash); | ||
| 191 | accesslist_new->size = found; | ||
| 192 | |||
| 193 | /* Now exchange the accesslist vector in the least race condition prone way */ | ||
| 194 | pthread_mutex_lock(&g_accesslist_mutex); | ||
| 195 | |||
| 196 | /* Mark old accesslist as "needs to live another five minutes" */ | ||
| 197 | if (g_accesslist) | ||
| 198 | g_accesslist->base = time_now_minutes(); | ||
| 199 | accesslist_new->next = g_accesslist; | ||
| 200 | g_accesslist = accesslist_new; /* Only now set a new list */ | ||
| 201 | |||
| 202 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 203 | /* If we have dynamic accesslists, reloading a new one will always void the add/delete lists. | ||
| 204 | Insert empty ones at the list head */ | ||
| 205 | if (g_accesslist_add && (accesslist_new = accesslist_make(g_accesslist_add, 0)) != NULL) { | ||
| 206 | g_accesslist_add->base = time_now_minutes(); | ||
| 207 | g_accesslist_add = accesslist_new; | ||
| 208 | } | ||
| 209 | if (g_accesslist_delete && (accesslist_new = accesslist_make(g_accesslist_delete, 0)) != NULL) { | ||
| 210 | g_accesslist_delete->base = time_now_minutes(); | ||
| 211 | g_accesslist_delete = accesslist_new; | ||
| 212 | } | ||
| 213 | #endif | ||
| 214 | |||
| 215 | accesslist_clean(g_accesslist); | ||
| 216 | |||
| 217 | pthread_mutex_unlock(&g_accesslist_mutex); | ||
| 218 | } | ||
| 219 | |||
| 220 | int accesslist_hashisvalid(const ot_hash hash) { | ||
| 221 | /* Get working copy of current access list */ | ||
| 222 | ot_accesslist *accesslist = g_accesslist; | ||
| 223 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 224 | ot_accesslist *accesslist_add, *accesslist_delete; | ||
| 225 | #endif | ||
| 226 | void *exactmatch = NULL; | ||
| 227 | |||
| 228 | if (accesslist && accesslist->size) | ||
| 229 | exactmatch = bsearch(hash, accesslist->list, accesslist->size, OT_HASH_COMPARE_SIZE, vector_compare_hash); | ||
| 230 | |||
| 231 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 232 | /* If we had no match on the main list, scan the list of dynamically added hashes */ | ||
| 233 | accesslist_add = g_accesslist_add; | ||
| 234 | if ((exactmatch == NULL) && accesslist_add && accesslist_add->size) | ||
| 235 | exactmatch = bsearch(hash, accesslist_add->list, accesslist_add->size, OT_HASH_COMPARE_SIZE, vector_compare_hash); | ||
| 236 | |||
| 237 | /* If we found a matching hash on the main list, scan the list of dynamically deleted hashes */ | ||
| 238 | accesslist_delete = g_accesslist_delete; | ||
| 239 | if ((exactmatch != NULL) && accesslist_delete && accesslist_delete->size && bsearch(hash, accesslist_delete->list, accesslist_delete->size, OT_HASH_COMPARE_SIZE, vector_compare_hash)) | ||
| 240 | exactmatch = NULL; | ||
| 241 | #endif | ||
| 242 | |||
| 243 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 244 | return exactmatch == NULL; | ||
| 245 | #else | ||
| 246 | return exactmatch != NULL; | ||
| 247 | #endif | ||
| 248 | } | ||
| 249 | |||
| 250 | static void *accesslist_worker(void *args) { | ||
| 251 | int sig; | ||
| 252 | sigset_t signal_mask; | ||
| 253 | |||
| 254 | sigemptyset(&signal_mask); | ||
| 255 | sigaddset(&signal_mask, SIGHUP); | ||
| 256 | |||
| 257 | (void)args; | ||
| 258 | |||
| 259 | while (1) { | ||
| 260 | if (!g_opentracker_running) | ||
| 261 | return NULL; | ||
| 262 | |||
| 263 | /* Initial attempt to read accesslist */ | ||
| 264 | accesslist_readfile(); | ||
| 265 | |||
| 266 | /* Wait for signals */ | ||
| 267 | while (sigwait(&signal_mask, &sig) != 0 && sig != SIGHUP) | ||
| 268 | ; | ||
| 269 | } | ||
| 270 | return NULL; | ||
| 271 | } | ||
| 272 | |||
| 273 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 274 | static pthread_t thread_adder_id, thread_deleter_id; | ||
| 275 | static _Atomic int adder_left, deleter_left; | ||
| 276 | static void *accesslist_adddel_worker(const char *fifoname, ot_accesslist *_Atomic *adding_to, ot_accesslist *_Atomic *removing_from) { | ||
| 277 | struct stat st; | ||
| 278 | |||
| 279 | if (!stat(fifoname, &st)) { | ||
| 280 | if (!S_ISFIFO(st.st_mode)) { | ||
| 281 | fprintf(stderr, "Error when starting dynamic accesslists: Found Non-FIFO file at %s.\nPlease remove it and restart opentracker.\n", fifoname); | ||
| 282 | return NULL; | ||
| 283 | } | ||
| 284 | } else { | ||
| 285 | int error = mkfifo(fifoname, 0755); | ||
| 286 | if (error && error != EEXIST) { | ||
| 287 | fprintf(stderr, "Error when starting dynamic accesslists: Couldn't create FIFO at %s, error: %s\n", fifoname, strerror(errno)); | ||
| 288 | return NULL; | ||
| 289 | } | ||
| 290 | } | ||
| 291 | |||
| 292 | while (g_opentracker_running) { | ||
| 293 | FILE *fifo = fopen(fifoname, "r"); | ||
| 294 | char *line = NULL; | ||
| 295 | size_t linecap = 0; | ||
| 296 | ssize_t linelen; | ||
| 297 | |||
| 298 | if (!fifo) { | ||
| 299 | /* Woken on purpose: the loop condition above decides what happens next */ | ||
| 300 | if (errno == EINTR) | ||
| 301 | continue; | ||
| 302 | fprintf(stderr, "Error when reading dynamic accesslists: Couldn't open FIFO at %s, error: %s\n", fifoname, strerror(errno)); | ||
| 303 | return NULL; | ||
| 304 | } | ||
| 305 | |||
| 306 | while ((linelen = getline(&line, &linecap, fifo)) > 0) { | ||
| 307 | ot_hash info_hash; | ||
| 308 | |||
| 309 | printf("Got line %*s", (int)linelen, line); | ||
| 310 | /* We do ignore anything that is not of the form "^[:xdigit:]{40}[^:xdigit:].*" | ||
| 311 | If there's not enough characters for an info_hash in the line, skip it. */ | ||
| 312 | if (linelen < 41) | ||
| 313 | continue; | ||
| 314 | |||
| 315 | if (!hex_to_hash(info_hash, line)) | ||
| 316 | continue; | ||
| 317 | |||
| 318 | /* From now on we modify g_accesslist_add and g_accesslist_delete, so prevent the | ||
| 319 | other worker threads from doing the same */ | ||
| 320 | pthread_mutex_lock(&g_accesslist_mutex); | ||
| 321 | |||
| 322 | /* If the info hash is in the removing_from list, create a new head without that entry */ | ||
| 323 | if (*removing_from && (*removing_from)->list) { | ||
| 324 | ot_hash *exactmatch = bsearch(info_hash, (*removing_from)->list, (*removing_from)->size, OT_HASH_COMPARE_SIZE, vector_compare_hash); | ||
| 325 | if (exactmatch) { | ||
| 326 | ptrdiff_t off = exactmatch - (*removing_from)->list; | ||
| 327 | ot_accesslist *accesslist_new = accesslist_make(*removing_from, (*removing_from)->size - 1); | ||
| 328 | if (accesslist_new) { | ||
| 329 | size_t after = (*removing_from)->size - off - 1; | ||
| 330 | |||
| 331 | /* Removing the only entry leaves a list with no room at all, and | ||
| 332 | memcpy may not be handed that even for nothing */ | ||
| 333 | if (off) | ||
| 334 | memcpy(accesslist_new->list, (*removing_from)->list, sizeof(ot_hash) * off); | ||
| 335 | if (after) | ||
| 336 | memcpy(accesslist_new->list + off, (*removing_from)->list + off + 1, sizeof(ot_hash) * after); | ||
| 337 | (*removing_from)->base = time_now_minutes(); | ||
| 338 | *removing_from = accesslist_new; | ||
| 339 | } | ||
| 340 | } | ||
| 341 | } | ||
| 342 | |||
| 343 | /* Simple case: there's no adding_to list yet, create one with one member */ | ||
| 344 | if (!*adding_to) { | ||
| 345 | ot_accesslist *accesslist_new = accesslist_make(NULL, 1); | ||
| 346 | if (accesslist_new) { | ||
| 347 | memcpy(accesslist_new->list, info_hash, sizeof(ot_hash)); | ||
| 348 | *adding_to = accesslist_new; | ||
| 349 | } | ||
| 350 | } else { | ||
| 351 | int exactmatch = 0; | ||
| 352 | ot_hash *insert_point = vector_binary_search(info_hash, (*adding_to)->list, (*adding_to)->size, sizeof(ot_hash), OT_HASH_COMPARE_SIZE, &exactmatch); | ||
| 353 | |||
| 354 | /* Only if the info hash is not in the adding_to list, create a new head with that entry */ | ||
| 355 | if (!exactmatch) { | ||
| 356 | ot_accesslist *accesslist_new = accesslist_make(*adding_to, (*adding_to)->size + 1); | ||
| 357 | ptrdiff_t off = insert_point - (*adding_to)->list; | ||
| 358 | if (accesslist_new) { | ||
| 359 | memcpy(accesslist_new->list, (*adding_to)->list, sizeof(ot_hash) * off); | ||
| 360 | memcpy(accesslist_new->list + off, info_hash, sizeof(info_hash)); | ||
| 361 | memcpy(accesslist_new->list + off + 1, (*adding_to)->list + off, sizeof(ot_hash) * ((*adding_to)->size - off)); | ||
| 362 | (*adding_to)->base = time_now_minutes(); | ||
| 363 | *adding_to = accesslist_new; | ||
| 364 | } | ||
| 365 | } | ||
| 366 | } | ||
| 367 | |||
| 368 | pthread_mutex_unlock(&g_accesslist_mutex); | ||
| 369 | } | ||
| 370 | |||
| 371 | free(line); | ||
| 372 | fclose(fifo); | ||
| 373 | } | ||
| 374 | return NULL; | ||
| 375 | } | ||
| 376 | |||
| 377 | static void *accesslist_adder_worker(void *args) { | ||
| 378 | (void)args; | ||
| 379 | accesslist_adddel_worker(g_accesslist_pipe_add, &g_accesslist_add, &g_accesslist_delete); | ||
| 380 | atomic_store(&adder_left, 1); | ||
| 381 | return NULL; | ||
| 382 | } | ||
| 383 | static void *accesslist_deleter_worker(void *args) { | ||
| 384 | (void)args; | ||
| 385 | accesslist_adddel_worker(g_accesslist_pipe_delete, &g_accesslist_delete, &g_accesslist_add); | ||
| 386 | atomic_store(&deleter_left, 1); | ||
| 387 | return NULL; | ||
| 388 | } | ||
| 389 | |||
| 390 | /* These sit in a blocking open or read on a FIFO, so they are woken the way | ||
| 391 | the other workers are, and the signal is repeated because it only | ||
| 392 | interrupts a call already under way. The pipe name is what says whether | ||
| 393 | the thread was ever started. */ | ||
| 394 | static void accesslist_stop_fifo_worker(pthread_t thread, _Atomic int *left, const char *pipe_name) { | ||
| 395 | if (!pipe_name) | ||
| 396 | return; | ||
| 397 | |||
| 398 | while (!atomic_load(left)) { | ||
| 399 | pthread_kill(thread, OT_SIGNAL_WAKE); | ||
| 400 | usleep(1000); | ||
| 401 | } | ||
| 402 | pthread_join(thread, NULL); | ||
| 403 | } | ||
| 404 | #endif | ||
| 405 | |||
| 406 | static pthread_t thread_id; | ||
| 407 | void accesslist_init(void) { | ||
| 408 | pthread_mutex_init(&g_accesslist_mutex, NULL); | ||
| 409 | pthread_create(&thread_id, NULL, accesslist_worker, NULL); | ||
| 410 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 411 | if (g_accesslist_pipe_add) | ||
| 412 | pthread_create(&thread_adder_id, NULL, accesslist_adder_worker, NULL); | ||
| 413 | if (g_accesslist_pipe_delete) | ||
| 414 | pthread_create(&thread_deleter_id, NULL, accesslist_deleter_worker, NULL); | ||
| 415 | #endif | ||
| 416 | } | ||
| 417 | |||
| 418 | void accesslist_deinit(void) { | ||
| 419 | /* Wake the worker and wait for it to notice that the tracker is stopping. */ | ||
| 420 | pthread_kill(thread_id, SIGHUP); | ||
| 421 | pthread_join(thread_id, NULL); | ||
| 422 | |||
| 423 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 424 | /* Before the lists they are reading are freed below */ | ||
| 425 | accesslist_stop_fifo_worker(thread_adder_id, &adder_left, g_accesslist_pipe_add); | ||
| 426 | accesslist_stop_fifo_worker(thread_deleter_id, &deleter_left, g_accesslist_pipe_delete); | ||
| 427 | #endif | ||
| 428 | |||
| 429 | pthread_mutex_lock(&g_accesslist_mutex); | ||
| 430 | |||
| 431 | g_accesslist = accesslist_free(g_accesslist); | ||
| 432 | |||
| 433 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 434 | g_accesslist_add = accesslist_free(g_accesslist_add); | ||
| 435 | g_accesslist_delete = accesslist_free(g_accesslist_delete); | ||
| 436 | #endif | ||
| 437 | |||
| 438 | pthread_mutex_unlock(&g_accesslist_mutex); | ||
| 439 | pthread_mutex_destroy(&g_accesslist_mutex); | ||
| 440 | } | ||
| 441 | |||
| 442 | void accesslist_cleanup(void) { | ||
| 443 | pthread_mutex_lock(&g_accesslist_mutex); | ||
| 444 | |||
| 445 | accesslist_clean(g_accesslist); | ||
| 446 | #if WANT_DYNAMIC_ACCESSLIST | ||
| 447 | accesslist_clean(g_accesslist_add); | ||
| 448 | accesslist_clean(g_accesslist_delete); | ||
| 449 | #endif | ||
| 450 | |||
| 451 | pthread_mutex_unlock(&g_accesslist_mutex); | ||
| 452 | } | ||
| 453 | #endif | ||
| 454 | |||
| 455 | int accesslist_address_in_net(const ot_ip6 address, const ot_net *net) { | ||
| 456 | int bits = net->bits, checkbits = (0x7f00 >> (bits & 7)); | ||
| 457 | int result = memcmp(address, &net->address, bits >> 3); | ||
| 458 | if (!result && (bits & 7)) | ||
| 459 | result = (checkbits & address[bits >> 3]) - (checkbits & net->address[bits >> 3]); | ||
| 460 | return result == 0; | ||
| 461 | } | ||
| 462 | |||
| 463 | void *accesslist_set_value_for_net(const ot_net *net, ot_vector *vector, const void *value, const size_t member_size) { | ||
| 464 | size_t i; | ||
| 465 | int exactmatch; | ||
| 466 | |||
| 467 | /* Caller must have a concept of ot_net in it's member */ | ||
| 468 | if (member_size < sizeof(ot_net)) | ||
| 469 | return 0; | ||
| 470 | |||
| 471 | /* Check each net in vector for overlap */ | ||
| 472 | uint8_t *member = ((uint8_t *)vector->data); | ||
| 473 | for (i = 0; i < vector->size; ++i) { | ||
| 474 | if (accesslist_address_in_net(*(ot_ip6 *)member, net) || accesslist_address_in_net(net->address, (ot_net *)member)) | ||
| 475 | return 0; | ||
| 476 | member += member_size; | ||
| 477 | } | ||
| 478 | |||
| 479 | member = vector_find_or_insert(vector, (void *)net, member_size, sizeof(ot_net), &exactmatch); | ||
| 480 | if (member) { | ||
| 481 | memcpy(member, net, sizeof(ot_net)); | ||
| 482 | memcpy(member + sizeof(ot_net), value, member_size - sizeof(ot_net)); | ||
| 483 | } | ||
| 484 | |||
| 485 | return member; | ||
| 486 | } | ||
| 487 | |||
| 488 | /* Takes a vector filled with { ot_net net, uint8_t[x] value }; | ||
| 489 | Returns value associated with the net, or NULL if not found */ | ||
| 490 | void *accesslist_get_value_for_net(const ot_ip6 address, const ot_vector *vector, const size_t member_size) { | ||
| 491 | int exactmatch; | ||
| 492 | /* This binary search will return a pointer to the first non-containing network... */ | ||
| 493 | ot_net *net = vector_binary_search(address, vector->data, vector->size, member_size, sizeof(ot_ip6), &exactmatch); | ||
| 494 | if (!net) | ||
| 495 | return NULL; | ||
| 496 | /* ... so we'll need to move back one step unless we've exactly hit the first address in network */ | ||
| 497 | if (!exactmatch && ((void *)net > vector->data)) | ||
| 498 | --net; | ||
| 499 | if (!accesslist_address_in_net(address, net)) | ||
| 500 | return NULL; | ||
| 501 | return (void *)net; | ||
| 502 | } | ||
| 503 | |||
| 504 | #ifdef WANT_IP_FROM_PROXY | ||
| 505 | typedef struct { | ||
| 506 | ot_net *proxy; | ||
| 507 | ot_vector networks; | ||
| 508 | } ot_proxymap; | ||
| 509 | |||
| 510 | static ot_vector g_proxies_list; | ||
| 511 | static pthread_mutex_t g_proxies_list_mutex = PTHREAD_MUTEX_INITIALIZER; | ||
| 512 | |||
| 513 | int proxylist_add_network(const ot_net *proxy, const ot_net *net) { | ||
| 514 | ot_proxymap *map; | ||
| 515 | int exactmatch, result = 1; | ||
| 516 | pthread_mutex_lock(&g_proxies_list_mutex); | ||
| 517 | |||
| 518 | /* If we have a direct hit, use and extend the vector there */ | ||
| 519 | map = vector_binary_search(proxy, g_proxies_list.data, g_proxies_list.size, sizeof(ot_proxymap), sizeof(ot_net), &exactmatch); | ||
| 520 | |||
| 521 | if (!map || !exactmatch) { | ||
| 522 | /* else see, if we've got overlapping networks | ||
| 523 | and get a new empty vector if not */ | ||
| 524 | ot_vector empty; | ||
| 525 | memset(&empty, 0, sizeof(ot_vector)); | ||
| 526 | map = accesslist_set_value_for_net(proxy, &g_proxies_list, &empty, sizeof(ot_proxymap)); | ||
| 527 | } | ||
| 528 | |||
| 529 | if (map && accesslist_set_value_for_net(net, &map->networks, NULL, sizeof(ot_net))) | ||
| 530 | result = 1; | ||
| 531 | |||
| 532 | pthread_mutex_unlock(&g_proxies_list_mutex); | ||
| 533 | return result; | ||
| 534 | } | ||
| 535 | |||
| 536 | int proxylist_check_proxy(const ot_ip6 proxy, const ot_ip6 address) { | ||
| 537 | int result = 0; | ||
| 538 | ot_proxymap *map; | ||
| 539 | |||
| 540 | pthread_mutex_lock(&g_proxies_list_mutex); | ||
| 541 | |||
| 542 | if ((map = accesslist_get_value_for_net(proxy, &g_proxies_list, sizeof(ot_proxymap)))) | ||
| 543 | if (!address || accesslist_get_value_for_net(address, &map->networks, sizeof(ot_net))) | ||
| 544 | result = 1; | ||
| 545 | |||
| 546 | pthread_mutex_unlock(&g_proxies_list_mutex); | ||
| 547 | return result; | ||
| 548 | } | ||
| 549 | |||
| 550 | #endif | ||
| 551 | |||
| 552 | static ot_net g_admin_nets[OT_ADMINIP_MAX]; | ||
| 553 | static ot_permissions g_admin_nets_permissions[OT_ADMINIP_MAX]; | ||
| 554 | static size_t g_admin_nets_count = 0; | ||
| 555 | |||
| 556 | int accesslist_bless_net(ot_net *net, ot_permissions permissions) { | ||
| 557 | if (g_admin_nets_count >= OT_ADMINIP_MAX) | ||
| 558 | return -1; | ||
| 559 | |||
| 560 | memcpy(g_admin_nets + g_admin_nets_count, net, sizeof(ot_net)); | ||
| 561 | g_admin_nets_permissions[g_admin_nets_count++] = permissions; | ||
| 562 | |||
| 563 | #ifdef _DEBUG | ||
| 564 | { | ||
| 565 | char _debug[512]; | ||
| 566 | int off = snprintf(_debug, sizeof(_debug), "Blessing ip net "); | ||
| 567 | off += ip_format(_debug + off, net->address); | ||
| 568 | if (net->bits < 128) { | ||
| 569 | _debug[off++] = '/'; | ||
| 570 | if (ip_is_v4mapped(net->address)) | ||
| 571 | off += snprintf(_debug + off, sizeof(_debug) - off, "%d", net->bits - 96); | ||
| 572 | else | ||
| 573 | off += snprintf(_debug + off, sizeof(_debug) - off, "%d", net->bits); | ||
| 574 | } | ||
| 575 | |||
| 576 | if (permissions & OT_PERMISSION_MAY_STAT) | ||
| 577 | off += snprintf(_debug + off, 512 - off, " may_fetch_stats"); | ||
| 578 | if (permissions & OT_PERMISSION_MAY_LIVESYNC) | ||
| 579 | off += snprintf(_debug + off, 512 - off, " may_sync_live"); | ||
| 580 | if (permissions & OT_PERMISSION_MAY_FULLSCRAPE) | ||
| 581 | off += snprintf(_debug + off, 512 - off, " may_fetch_fullscrapes"); | ||
| 582 | if (permissions & OT_PERMISSION_MAY_PROXY) | ||
| 583 | off += snprintf(_debug + off, 512 - off, " may_proxy"); | ||
| 584 | if (!permissions) | ||
| 585 | off += snprintf(_debug + off, sizeof(_debug) - off, " nothing"); | ||
| 586 | _debug[off++] = '.'; | ||
| 587 | _debug[off++] = '\n'; | ||
| 588 | (void)write(2, _debug, off); | ||
| 589 | } | ||
| 590 | #endif | ||
| 591 | |||
| 592 | return 0; | ||
| 593 | } | ||
| 594 | |||
| 595 | int accesslist_is_blessed(ot_ip6 ip, ot_permissions permissions) { | ||
| 596 | size_t i; | ||
| 597 | for (i = 0; i < g_admin_nets_count; ++i) | ||
| 598 | if (accesslist_address_in_net(ip, g_admin_nets + i) && (g_admin_nets_permissions[i] & permissions)) | ||
| 599 | return 1; | ||
| 600 | return 0; | ||
| 601 | } | ||
diff --git a/src/ot_accesslist.h b/src/ot_accesslist.h new file mode 100644 index 0000000..68c88dc --- /dev/null +++ b/src/ot_accesslist.h | |||
| @@ -0,0 +1,77 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_ACCESSLIST_H__ | ||
| 5 | #define OT_ACCESSLIST_H__ | ||
| 6 | |||
| 7 | #include "trackerlogic.h" | ||
| 8 | |||
| 9 | #if defined(WANT_ACCESSLIST_BLACK) && defined(WANT_ACCESSLIST_WHITE) | ||
| 10 | #error WANT_ACCESSLIST_BLACK and WANT_ACCESSLIST_WHITE are exclusive. | ||
| 11 | #endif | ||
| 12 | |||
| 13 | #if defined(WANT_ACCESSLIST_BLACK) || defined(WANT_ACCESSLIST_WHITE) | ||
| 14 | #define WANT_ACCESSLIST | ||
| 15 | /* Starts a thread that rereads the list when sent SIGHUP, and is stopped by | ||
| 16 | being sent one, so the caller blocks SIGHUP process wide beforehand */ | ||
| 17 | void accesslist_init(void); | ||
| 18 | void accesslist_deinit(void); | ||
| 19 | int accesslist_hashisvalid(const ot_hash hash); | ||
| 20 | void accesslist_cleanup(void); | ||
| 21 | |||
| 22 | /* Scan buf for up to capacity info_hashes in hexadecimal notation, one per | ||
| 23 | line, and return how many were written to dest */ | ||
| 24 | size_t accesslist_parse_hashes(ot_hash *dest, size_t capacity, const char *buf, size_t len); | ||
| 25 | |||
| 26 | extern const char *g_accesslist_filename; | ||
| 27 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 28 | extern const char *g_accesslist_pipe_add; | ||
| 29 | extern const char *g_accesslist_pipe_delete; | ||
| 30 | #endif | ||
| 31 | |||
| 32 | #else | ||
| 33 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 34 | #error WANT_DYNAMIC_ACCESSLIST needs either WANT_ACCESSLIST_BLACK or WANT_ACCESSLIST_WHITE | ||
| 35 | #endif | ||
| 36 | |||
| 37 | #define accesslist_init(accesslist_filename) | ||
| 38 | #define accesslist_deinit() | ||
| 39 | #define accesslist_hashisvalid(hash) 1 | ||
| 40 | #endif | ||
| 41 | |||
| 42 | /* Test if an address is subset of an ot_net, return value is considered a bool */ | ||
| 43 | int accesslist_address_in_net(const ot_ip6 address, const ot_net *net); | ||
| 44 | |||
| 45 | /* Store a value into a vector of struct { ot_net net, uint8_t[x] value } member; | ||
| 46 | returns NULL | ||
| 47 | if member_size is too small, or | ||
| 48 | if one of the nets inside the vector are a subnet of _net_, or | ||
| 49 | if _net_ is a subnet of one of the nets inside the vector, or | ||
| 50 | if the vector could not be resized | ||
| 51 | returns pointer to new member in vector for success | ||
| 52 | member_size can be sizeof(ot_net) to reduce the lookup to a boolean mapping | ||
| 53 | */ | ||
| 54 | void *accesslist_set_value_for_net(const ot_net *net, ot_vector *vector, const void *value, const size_t member_size); | ||
| 55 | |||
| 56 | /* Takes a vector filled with struct { ot_net net, uint8_t[x] value } member; | ||
| 57 | Returns pointer to _member_ associated with the net, or NULL if not found | ||
| 58 | member_size can be sizeof(ot_net) to reduce the lookup to a boolean mapping | ||
| 59 | */ | ||
| 60 | void *accesslist_get_value_for_net(const ot_ip6 address, const ot_vector *vector, const size_t member_size); | ||
| 61 | |||
| 62 | #ifdef WANT_IP_FROM_PROXY | ||
| 63 | int proxylist_add_network(const ot_net *proxy, const ot_net *net); | ||
| 64 | int proxylist_check_network(const ot_ip6 *proxy, const ot_ip6 address /* can be NULL to only check proxy */); | ||
| 65 | #endif | ||
| 66 | |||
| 67 | typedef enum { | ||
| 68 | OT_PERMISSION_MAY_FULLSCRAPE = 0x1, | ||
| 69 | OT_PERMISSION_MAY_STAT = 0x2, | ||
| 70 | OT_PERMISSION_MAY_LIVESYNC = 0x4, | ||
| 71 | OT_PERMISSION_MAY_PROXY = 0x8 | ||
| 72 | } ot_permissions; | ||
| 73 | |||
| 74 | int accesslist_bless_net(ot_net *net, ot_permissions permissions); | ||
| 75 | int accesslist_is_blessed(ot_ip6 ip, ot_permissions permissions); | ||
| 76 | |||
| 77 | #endif | ||
diff --git a/src/ot_batch.c b/src/ot_batch.c new file mode 100644 index 0000000..e4dda0b --- /dev/null +++ b/src/ot_batch.c | |||
| @@ -0,0 +1,156 @@ | |||
| 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 <limits.h> | ||
| 7 | #include <stdlib.h> | ||
| 8 | #include <string.h> | ||
| 9 | #include <sys/socket.h> | ||
| 10 | #include <sys/uio.h> | ||
| 11 | #include <unistd.h> | ||
| 12 | |||
| 13 | /* Opentracker */ | ||
| 14 | #include "ot_batch.h" | ||
| 15 | |||
| 16 | #ifndef IOV_MAX | ||
| 17 | #define IOV_MAX 1024 | ||
| 18 | #endif | ||
| 19 | |||
| 20 | #define OT_BATCH_MIN_ENTRIES 16 | ||
| 21 | |||
| 22 | static int batch_room_for(ot_batch *batch, size_t wanted) { | ||
| 23 | size_t space = batch->space ? batch->space : OT_BATCH_MIN_ENTRIES; | ||
| 24 | struct iovec *grown; | ||
| 25 | |||
| 26 | if (batch->count + wanted <= batch->space) | ||
| 27 | return 0; | ||
| 28 | |||
| 29 | /* During a chunked transfer entries are appended while earlier ones are | ||
| 30 | still going out, so the front of the array frees up as we go. Slide the | ||
| 31 | rest down before asking for more. */ | ||
| 32 | if (batch->first && batch->count - batch->first + wanted <= batch->space) { | ||
| 33 | memmove(batch->entries, batch->entries + batch->first, (batch->count - batch->first) * sizeof(struct iovec)); | ||
| 34 | batch->count -= batch->first; | ||
| 35 | batch->first = 0; | ||
| 36 | return 0; | ||
| 37 | } | ||
| 38 | |||
| 39 | while (space < batch->count + wanted) | ||
| 40 | space *= 2; | ||
| 41 | |||
| 42 | if (!(grown = realloc(batch->entries, space * sizeof(struct iovec)))) | ||
| 43 | return -1; | ||
| 44 | |||
| 45 | batch->entries = grown; | ||
| 46 | batch->space = space; | ||
| 47 | return 0; | ||
| 48 | } | ||
| 49 | |||
| 50 | int batch_add(ot_batch *batch, void *buf, size_t len) { | ||
| 51 | if (batch_room_for(batch, 1)) { | ||
| 52 | free(buf); | ||
| 53 | return -1; | ||
| 54 | } | ||
| 55 | |||
| 56 | batch->entries[batch->count].iov_base = buf; | ||
| 57 | batch->entries[batch->count].iov_len = len; | ||
| 58 | ++batch->count; | ||
| 59 | return 0; | ||
| 60 | } | ||
| 61 | |||
| 62 | int batch_add_vector(ot_batch *batch, const struct iovec *entries, size_t count) { | ||
| 63 | if (batch_room_for(batch, count)) { | ||
| 64 | while (count--) | ||
| 65 | free(entries[count].iov_base); | ||
| 66 | return -1; | ||
| 67 | } | ||
| 68 | |||
| 69 | memcpy(batch->entries + batch->count, entries, count * sizeof(struct iovec)); | ||
| 70 | batch->count += count; | ||
| 71 | return 0; | ||
| 72 | } | ||
| 73 | |||
| 74 | /* Drop everything the kernel accepted, freeing each buffer as it goes */ | ||
| 75 | static void batch_consume(ot_batch *batch, size_t written) { | ||
| 76 | while (written) { | ||
| 77 | struct iovec *entry = batch->entries + batch->first; | ||
| 78 | size_t left = entry->iov_len - batch->offset; | ||
| 79 | |||
| 80 | if (written < left) { | ||
| 81 | batch->offset += written; | ||
| 82 | return; | ||
| 83 | } | ||
| 84 | |||
| 85 | free(entry->iov_base); | ||
| 86 | written -= left; | ||
| 87 | batch->offset = 0; | ||
| 88 | ++batch->first; | ||
| 89 | } | ||
| 90 | |||
| 91 | /* Nothing is outstanding, so the array can start over from the front */ | ||
| 92 | if (batch->first == batch->count) | ||
| 93 | batch->first = batch->count = 0; | ||
| 94 | } | ||
| 95 | |||
| 96 | /* A peer that has gone away must come back as an error rather than as a | ||
| 97 | signal that kills the tracker, so ask the kernel not to raise one where it | ||
| 98 | can be asked. macOS cannot, and relies on SIGPIPE being blocked. */ | ||
| 99 | static ssize_t batch_writev(int s, struct iovec *entries, int count) { | ||
| 100 | #ifdef MSG_NOSIGNAL | ||
| 101 | struct msghdr msg; | ||
| 102 | |||
| 103 | memset(&msg, 0, sizeof(msg)); | ||
| 104 | msg.msg_iov = entries; | ||
| 105 | msg.msg_iovlen = count; | ||
| 106 | return sendmsg(s, &msg, MSG_NOSIGNAL); | ||
| 107 | #else | ||
| 108 | return writev(s, entries, count); | ||
| 109 | #endif | ||
| 110 | } | ||
| 111 | |||
| 112 | ssize_t batch_send(int s, ot_batch *batch) { | ||
| 113 | size_t entries = batch->count - batch->first; | ||
| 114 | ssize_t written; | ||
| 115 | |||
| 116 | if (!batch_pending(batch)) | ||
| 117 | return BATCH_DONE; | ||
| 118 | |||
| 119 | if (entries > IOV_MAX) | ||
| 120 | entries = IOV_MAX; | ||
| 121 | |||
| 122 | /* The first entry may be half way out already */ | ||
| 123 | batch->entries[batch->first].iov_base = (char *)batch->entries[batch->first].iov_base + batch->offset; | ||
| 124 | batch->entries[batch->first].iov_len -= batch->offset; | ||
| 125 | written = batch_writev(s, batch->entries + batch->first, (int)entries); | ||
| 126 | batch->entries[batch->first].iov_base = (char *)batch->entries[batch->first].iov_base - batch->offset; | ||
| 127 | batch->entries[batch->first].iov_len += batch->offset; | ||
| 128 | |||
| 129 | if (written < 0) | ||
| 130 | return (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) ? BATCH_AGAIN : BATCH_FAILED; | ||
| 131 | if (!written) | ||
| 132 | return BATCH_FAILED; | ||
| 133 | |||
| 134 | batch_consume(batch, (size_t)written); | ||
| 135 | return written; | ||
| 136 | } | ||
| 137 | |||
| 138 | void batch_reset(ot_batch *batch) { | ||
| 139 | while (batch->first < batch->count) | ||
| 140 | free(batch->entries[batch->first++].iov_base); | ||
| 141 | |||
| 142 | free(batch->entries); | ||
| 143 | batch->entries = NULL; | ||
| 144 | batch->count = batch->first = batch->offset = batch->space = 0; | ||
| 145 | } | ||
| 146 | |||
| 147 | /* For the few constants the chunked encoding needs, which the batch frees | ||
| 148 | along with everything else */ | ||
| 149 | int batch_add_copy(ot_batch *batch, const void *buf, size_t len) { | ||
| 150 | void *copy = malloc(len); | ||
| 151 | |||
| 152 | if (!copy) | ||
| 153 | return -1; | ||
| 154 | memcpy(copy, buf, len); | ||
| 155 | return batch_add(batch, copy, len); | ||
| 156 | } | ||
diff --git a/src/ot_batch.h b/src/ot_batch.h new file mode 100644 index 0000000..910bea7 --- /dev/null +++ b/src/ot_batch.h | |||
| @@ -0,0 +1,46 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_BATCH_H__ | ||
| 5 | #define OT_BATCH_H__ | ||
| 6 | |||
| 7 | #include <stddef.h> | ||
| 8 | #include <sys/types.h> | ||
| 9 | #include <sys/uio.h> | ||
| 10 | |||
| 11 | /* A queue of buffers on their way to one socket. The batch owns every buffer | ||
| 12 | handed to it and frees each one as soon as it has left, so a client that | ||
| 13 | reads slowly costs memory only for what it has not received yet. | ||
| 14 | |||
| 15 | A zeroed ot_batch is ready to use. */ | ||
| 16 | typedef struct { | ||
| 17 | struct iovec *entries; | ||
| 18 | size_t space; | ||
| 19 | size_t count; | ||
| 20 | size_t first; /* entries before this one are sent and freed */ | ||
| 21 | size_t offset; /* bytes of entries[first] already sent */ | ||
| 22 | } ot_batch; | ||
| 23 | |||
| 24 | static inline int batch_pending(const ot_batch *batch) { return batch->first != batch->count; } | ||
| 25 | |||
| 26 | /* What batch_send reports instead of a byte count */ | ||
| 27 | enum { BATCH_DONE = 0, BATCH_AGAIN = -1, BATCH_FAILED = -2 }; | ||
| 28 | |||
| 29 | /* Queue buf and take ownership of it. Returns 0, or -1 having freed buf. */ | ||
| 30 | int batch_add(ot_batch *batch, void *buf, size_t len); | ||
| 31 | |||
| 32 | /* Write as much as the socket takes. Returns the number of bytes written, or | ||
| 33 | BATCH_DONE, BATCH_AGAIN or BATCH_FAILED. */ | ||
| 34 | ssize_t batch_send(int s, ot_batch *batch); | ||
| 35 | |||
| 36 | /* Queue every entry of a vector, taking ownership of each buffer but not of | ||
| 37 | the vector itself. Returns 0, or -1 having freed all of them. */ | ||
| 38 | int batch_add_vector(ot_batch *batch, const struct iovec *entries, size_t count); | ||
| 39 | |||
| 40 | /* Queue a copy of buf, which the batch then owns. Returns 0, or -1. */ | ||
| 41 | int batch_add_copy(ot_batch *batch, const void *buf, size_t len); | ||
| 42 | |||
| 43 | /* Free whatever has not left yet, along with the batch's own array */ | ||
| 44 | void batch_reset(ot_batch *batch); | ||
| 45 | |||
| 46 | #endif | ||
diff --git a/src/ot_buf.c b/src/ot_buf.c new file mode 100644 index 0000000..ecd3f8d --- /dev/null +++ b/src/ot_buf.c | |||
| @@ -0,0 +1,39 @@ | |||
| 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 <stdlib.h> | ||
| 6 | #include <string.h> | ||
| 7 | |||
| 8 | /* Opentracker */ | ||
| 9 | #include "ot_buf.h" | ||
| 10 | |||
| 11 | int buf_cat(ot_buf *buf, const void *src, size_t len) { | ||
| 12 | char *data; | ||
| 13 | |||
| 14 | if (!len) | ||
| 15 | return 0; | ||
| 16 | |||
| 17 | if (buf->size + len < buf->size) | ||
| 18 | return -1; | ||
| 19 | |||
| 20 | if (!(data = realloc(buf->data, buf->size + len))) | ||
| 21 | return -1; | ||
| 22 | |||
| 23 | buf->data = data; | ||
| 24 | memcpy(data + buf->size, src, len); | ||
| 25 | buf->size += len; | ||
| 26 | |||
| 27 | return 0; | ||
| 28 | } | ||
| 29 | |||
| 30 | void buf_truncate(ot_buf *buf, size_t len) { | ||
| 31 | if (len < buf->size) | ||
| 32 | buf->size = len; | ||
| 33 | } | ||
| 34 | |||
| 35 | void buf_reset(ot_buf *buf) { | ||
| 36 | free(buf->data); | ||
| 37 | buf->data = NULL; | ||
| 38 | buf->size = 0; | ||
| 39 | } | ||
diff --git a/src/ot_buf.h b/src/ot_buf.h new file mode 100644 index 0000000..7fb944e --- /dev/null +++ b/src/ot_buf.h | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_BUF_H__ | ||
| 5 | #define OT_BUF_H__ | ||
| 6 | |||
| 7 | #include <stddef.h> | ||
| 8 | |||
| 9 | /* A growable byte buffer, holding an HTTP request that did not arrive in a | ||
| 10 | single packet. Requests that arrive whole are served straight from the | ||
| 11 | worker's input buffer and never come near this. */ | ||
| 12 | typedef struct { | ||
| 13 | char *data; | ||
| 14 | size_t size; | ||
| 15 | } ot_buf; | ||
| 16 | |||
| 17 | /* Append len bytes from src. Returns 0, or -1 if the memory could not be | ||
| 18 | had, in which case the buffer keeps whatever it already held. */ | ||
| 19 | int buf_cat(ot_buf *buf, const void *src, size_t len); | ||
| 20 | |||
| 21 | void buf_truncate(ot_buf *buf, size_t len); | ||
| 22 | |||
| 23 | void buf_reset(ot_buf *buf); | ||
| 24 | |||
| 25 | #endif | ||
diff --git a/src/ot_clean.c b/src/ot_clean.c new file mode 100644 index 0000000..f181e83 --- /dev/null +++ b/src/ot_clean.c | |||
| @@ -0,0 +1,176 @@ | |||
| 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 <string.h> | ||
| 8 | #include <unistd.h> | ||
| 9 | |||
| 10 | /* Opentracker */ | ||
| 11 | #include "ot_accesslist.h" | ||
| 12 | #include "ot_clean.h" | ||
| 13 | #include "ot_mutex.h" | ||
| 14 | #include "ot_stats.h" | ||
| 15 | #include "ot_vector.h" | ||
| 16 | #include "trackerlogic.h" | ||
| 17 | |||
| 18 | /* Returns amount of removed peers */ | ||
| 19 | static ssize_t clean_single_bucket(ot_peer *peers, size_t peer_count, size_t peer_size, time_t timedout, size_t *removed_seeders) { | ||
| 20 | ot_peer *last_peer = peers + peer_count * peer_size, *insert_point; | ||
| 21 | |||
| 22 | /* Two scan modes: unless there is one peer removed, just increase ot_peertime */ | ||
| 23 | while (peers < last_peer) { | ||
| 24 | time_t timediff = timedout + OT_PEERTIME(peers, peer_size); | ||
| 25 | if (timediff >= OT_PEER_TIMEOUT) | ||
| 26 | break; | ||
| 27 | OT_PEERTIME(peers, peer_size) = timediff; | ||
| 28 | peers += peer_size; | ||
| 29 | } | ||
| 30 | |||
| 31 | /* If we at least remove one peer, we have to copy */ | ||
| 32 | for (insert_point = peers; peers < last_peer; peers += peer_size) { | ||
| 33 | time_t timediff = timedout + OT_PEERTIME(peers, peer_size); | ||
| 34 | |||
| 35 | if (timediff < OT_PEER_TIMEOUT) { | ||
| 36 | OT_PEERTIME(peers, peer_size) = timediff; | ||
| 37 | memcpy(insert_point, peers, peer_size); | ||
| 38 | insert_point += peer_size; | ||
| 39 | } else if (OT_PEERFLAG_D(peers, peer_size) & PEER_FLAG_SEEDING) | ||
| 40 | (*removed_seeders)++; | ||
| 41 | } | ||
| 42 | |||
| 43 | return (peers - insert_point) / peer_size; | ||
| 44 | } | ||
| 45 | |||
| 46 | int clean_single_peer_list(ot_peerlist *peer_list, size_t peer_size) { | ||
| 47 | ot_vector *peer_vector = &peer_list->peers; | ||
| 48 | time_t timedout = (time_t)(time_now_minutes() - peer_list->base); | ||
| 49 | size_t num_buckets = 1, removed_seeders = 0; | ||
| 50 | |||
| 51 | /* No need to clean empty torrent */ | ||
| 52 | if (!timedout) | ||
| 53 | return 0; | ||
| 54 | |||
| 55 | /* Torrent has idled out */ | ||
| 56 | if (timedout > OT_TORRENT_TIMEOUT) | ||
| 57 | return 1; | ||
| 58 | |||
| 59 | /* Nothing to be cleaned here? Test if torrent is worth keeping */ | ||
| 60 | if (timedout > OT_PEER_TIMEOUT) { | ||
| 61 | if (!peer_list->peer_count) | ||
| 62 | return peer_list->down_count ? 0 : 1; | ||
| 63 | timedout = OT_PEER_TIMEOUT; | ||
| 64 | } | ||
| 65 | |||
| 66 | if (OT_PEERLIST_HASBUCKETS(peer_list)) { | ||
| 67 | num_buckets = peer_vector->size; | ||
| 68 | peer_vector = (ot_vector *)peer_vector->data; | ||
| 69 | } | ||
| 70 | |||
| 71 | while (num_buckets--) { | ||
| 72 | size_t removed_peers = clean_single_bucket(peer_vector->data, peer_vector->size, peer_size, timedout, &removed_seeders); | ||
| 73 | peer_list->peer_count -= removed_peers; | ||
| 74 | peer_vector->size -= removed_peers; | ||
| 75 | if (removed_peers) | ||
| 76 | vector_fixup_peers(peer_vector, peer_size); | ||
| 77 | |||
| 78 | /* Skip to next bucket, a vector containing peers */ | ||
| 79 | ++peer_vector; | ||
| 80 | } | ||
| 81 | |||
| 82 | peer_list->seed_count -= removed_seeders; | ||
| 83 | |||
| 84 | /* See if we need to convert a torrent from simple vector to bucket list */ | ||
| 85 | if ((peer_list->peer_count > OT_PEER_BUCKET_MINCOUNT) || OT_PEERLIST_HASBUCKETS(peer_list)) | ||
| 86 | vector_redistribute_buckets(peer_list, peer_size); | ||
| 87 | |||
| 88 | if (peer_list->peer_count) | ||
| 89 | peer_list->base = time_now_minutes(); | ||
| 90 | else { | ||
| 91 | /* When we got here, the last time that torrent | ||
| 92 | has been touched is OT_PEER_TIMEOUT Minutes before */ | ||
| 93 | peer_list->base = time_now_minutes() - OT_PEER_TIMEOUT; | ||
| 94 | } | ||
| 95 | return 0; | ||
| 96 | } | ||
| 97 | |||
| 98 | /* Clean a single torrent | ||
| 99 | return 1 if torrent timed out | ||
| 100 | */ | ||
| 101 | int clean_single_torrent(ot_torrent *torrent) { | ||
| 102 | return clean_single_peer_list(torrent->peer_list6, OT_PEER_SIZE6) * clean_single_peer_list(torrent->peer_list4, OT_PEER_SIZE4); | ||
| 103 | } | ||
| 104 | |||
| 105 | static pthread_mutex_t g_clean_mutex = PTHREAD_MUTEX_INITIALIZER; | ||
| 106 | static pthread_cond_t g_clean_wakeup = PTHREAD_COND_INITIALIZER; | ||
| 107 | |||
| 108 | static void clean_sleep(void) { | ||
| 109 | struct timespec deadline; | ||
| 110 | |||
| 111 | clock_gettime(CLOCK_REALTIME, &deadline); | ||
| 112 | deadline.tv_sec += OT_CLEAN_SLEEP / 1000000; | ||
| 113 | deadline.tv_nsec += (OT_CLEAN_SLEEP % 1000000) * 1000; | ||
| 114 | if (deadline.tv_nsec >= 1000000000) { | ||
| 115 | ++deadline.tv_sec; | ||
| 116 | deadline.tv_nsec -= 1000000000; | ||
| 117 | } | ||
| 118 | |||
| 119 | /* Both sides hold the mutex, so the flag cannot be cleared and signalled | ||
| 120 | in the window between testing it and waiting on it */ | ||
| 121 | pthread_mutex_lock(&g_clean_mutex); | ||
| 122 | while (g_opentracker_running) | ||
| 123 | if (pthread_cond_timedwait(&g_clean_wakeup, &g_clean_mutex, &deadline) == ETIMEDOUT) | ||
| 124 | break; | ||
| 125 | pthread_mutex_unlock(&g_clean_mutex); | ||
| 126 | } | ||
| 127 | |||
| 128 | /* Clean up all peers in one bucket, remove timedout pools and torrents */ | ||
| 129 | static void clean_bucket(size_t bucket) { | ||
| 130 | ot_vector *torrents_list = mutex_bucket_lock((int)bucket); | ||
| 131 | size_t toffs; | ||
| 132 | int delta_torrentcount = 0; | ||
| 133 | |||
| 134 | for (toffs = 0; toffs < torrents_list->size; ++toffs) { | ||
| 135 | ot_torrent *torrent = ((ot_torrent *)(torrents_list->data)) + toffs; | ||
| 136 | if (clean_single_torrent(torrent)) { | ||
| 137 | vector_remove_torrent(torrents_list, torrent); | ||
| 138 | --delta_torrentcount; | ||
| 139 | --toffs; | ||
| 140 | } | ||
| 141 | } | ||
| 142 | mutex_bucket_unlock((int)bucket, delta_torrentcount); | ||
| 143 | } | ||
| 144 | |||
| 145 | static void *clean_worker(void *args) { | ||
| 146 | (void)args; | ||
| 147 | while (1) { | ||
| 148 | size_t bucket = OT_BUCKET_COUNT; | ||
| 149 | while (bucket) { | ||
| 150 | size_t todo = bucket < OT_CLEAN_BUCKETS_PER_WAKE ? bucket : OT_CLEAN_BUCKETS_PER_WAKE; | ||
| 151 | |||
| 152 | /* One bucket is locked at a time, however many a wake gets through */ | ||
| 153 | while (todo--) | ||
| 154 | clean_bucket(--bucket); | ||
| 155 | |||
| 156 | if (!g_opentracker_running) | ||
| 157 | return NULL; | ||
| 158 | clean_sleep(); | ||
| 159 | } | ||
| 160 | stats_cleanup(); | ||
| 161 | #ifdef WANT_ACCESSLIST | ||
| 162 | accesslist_cleanup(); | ||
| 163 | #endif | ||
| 164 | } | ||
| 165 | return NULL; | ||
| 166 | } | ||
| 167 | |||
| 168 | static pthread_t thread_id; | ||
| 169 | void clean_init(void) { pthread_create(&thread_id, NULL, clean_worker, NULL); } | ||
| 170 | |||
| 171 | void clean_deinit(void) { | ||
| 172 | pthread_mutex_lock(&g_clean_mutex); | ||
| 173 | pthread_cond_signal(&g_clean_wakeup); | ||
| 174 | pthread_mutex_unlock(&g_clean_mutex); | ||
| 175 | pthread_join(thread_id, NULL); | ||
| 176 | } | ||
diff --git a/src/ot_clean.h b/src/ot_clean.h new file mode 100644 index 0000000..0592791 --- /dev/null +++ b/src/ot_clean.h | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_CLEAN_H__ | ||
| 5 | #define OT_CLEAN_H__ | ||
| 6 | |||
| 7 | #include "trackerlogic.h" | ||
| 8 | |||
| 9 | /* The amount of time a clean cycle should take */ | ||
| 10 | #define OT_CLEAN_INTERVAL_MINUTES 2 | ||
| 11 | |||
| 12 | /* How often the cleaner may wake. A full sweep takes OT_CLEAN_INTERVAL_MINUTES. | ||
| 13 | The clean operations per wake is goverend by OT_BUCKET_COUNT_BITS, | ||
| 14 | set by the Makefile. | ||
| 15 | At the default of 1024 buckets this works out at one bucket every | ||
| 16 | 117 ms. */ | ||
| 17 | #define OT_CLEAN_MAX_WAKES_PER_SECOND 10 | ||
| 18 | |||
| 19 | #define OT_CLEAN_WAKE_BUDGET ((OT_CLEAN_INTERVAL_MINUTES) * 60 * (OT_CLEAN_MAX_WAKES_PER_SECOND)) | ||
| 20 | #define OT_CLEAN_BUCKETS_PER_WAKE (((OT_BUCKET_COUNT) + (OT_CLEAN_WAKE_BUDGET) - 1) / (OT_CLEAN_WAKE_BUDGET)) | ||
| 21 | #define OT_CLEAN_WAKES ((OT_BUCKET_COUNT) / (OT_CLEAN_BUCKETS_PER_WAKE)) | ||
| 22 | #define OT_CLEAN_SLEEP (((OT_CLEAN_INTERVAL_MINUTES) * 60 * 1000000) / (OT_CLEAN_WAKES)) | ||
| 23 | |||
| 24 | void clean_init(void); | ||
| 25 | void clean_deinit(void); | ||
| 26 | int clean_single_torrent(ot_torrent *torrent); | ||
| 27 | |||
| 28 | #endif | ||
diff --git a/src/ot_config.c b/src/ot_config.c new file mode 100644 index 0000000..4064d1b --- /dev/null +++ b/src/ot_config.c | |||
| @@ -0,0 +1,592 @@ | |||
| 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 <ctype.h> | ||
| 6 | #include <errno.h> | ||
| 7 | #include <limits.h> | ||
| 8 | #include <stdio.h> | ||
| 9 | #include <stdlib.h> | ||
| 10 | #include <string.h> | ||
| 11 | #include <unistd.h> | ||
| 12 | |||
| 13 | /* Opentracker */ | ||
| 14 | #include "ot_accesslist.h" | ||
| 15 | #include "ot_event.h" | ||
| 16 | #include "ot_config.h" | ||
| 17 | #include "ot_http.h" | ||
| 18 | #include "ot_ip.h" | ||
| 19 | #include "ot_livesync.h" | ||
| 20 | #include "ot_scan.h" | ||
| 21 | #include "ot_stats.h" | ||
| 22 | #include "trackerlogic.h" | ||
| 23 | |||
| 24 | const char *g_redirecturl; | ||
| 25 | const char *g_serverdir; | ||
| 26 | const char *g_serveruser; | ||
| 27 | const char *g_statefile; | ||
| 28 | |||
| 29 | #define OT_LISTEN_INHERIT (-1) | ||
| 30 | |||
| 31 | static ot_listen *g_listen; | ||
| 32 | static size_t g_listen_count; | ||
| 33 | static int g_listen_shared; | ||
| 34 | static int g_listen_workers_udp = 1; | ||
| 35 | static int g_listen_workers_tcp = 0; | ||
| 36 | |||
| 37 | static void panic(const char *routine) __attribute__((noreturn)); | ||
| 38 | static void panic(const char *routine) { | ||
| 39 | fprintf(stderr, "%s: %s\n", routine, strerror(errno)); | ||
| 40 | exit(111); | ||
| 41 | } | ||
| 42 | |||
| 43 | static void listen_append(const ot_ip6 ip, uint16_t port, PROTO_FLAG proto, int shared, int workers) { | ||
| 44 | ot_listen *grown = realloc(g_listen, (g_listen_count + 1) * sizeof(ot_listen)); | ||
| 45 | ot_listen *entry; | ||
| 46 | |||
| 47 | if (!grown) | ||
| 48 | panic("listen_append"); | ||
| 49 | g_listen = grown; | ||
| 50 | |||
| 51 | entry = g_listen + g_listen_count++; | ||
| 52 | memcpy(entry->ip, ip, sizeof(ot_ip6)); | ||
| 53 | entry->port = port; | ||
| 54 | entry->proto = proto; | ||
| 55 | entry->shared = shared; | ||
| 56 | entry->workers = workers; | ||
| 57 | entry->sock = -1; | ||
| 58 | } | ||
| 59 | |||
| 60 | typedef enum { LISTEN_OPTION_UNKNOWN, LISTEN_OPTION_SHARED, LISTEN_OPTION_WORKERS } listen_option_kind; | ||
| 61 | |||
| 62 | typedef struct { | ||
| 63 | const char *name; | ||
| 64 | size_t len; | ||
| 65 | listen_option_kind kind; | ||
| 66 | int takes_value; | ||
| 67 | } listen_option_key; | ||
| 68 | |||
| 69 | #define LISTEN_OPTION_KEY(s, k, v) {s, sizeof(s) - 1, k, v} | ||
| 70 | |||
| 71 | static const listen_option_key listen_option_keys[] = { | ||
| 72 | LISTEN_OPTION_KEY("shared", LISTEN_OPTION_SHARED, 0), | ||
| 73 | LISTEN_OPTION_KEY("workers", LISTEN_OPTION_WORKERS, 1), | ||
| 74 | }; | ||
| 75 | static const size_t listen_option_count = sizeof(listen_option_keys) / sizeof(listen_option_keys[0]); | ||
| 76 | |||
| 77 | static listen_option_kind parse_listen_option(char **p) { | ||
| 78 | size_t i; | ||
| 79 | |||
| 80 | for (i = 0; i < listen_option_count; ++i) { | ||
| 81 | const listen_option_key *key = &listen_option_keys[i]; | ||
| 82 | char delimiter = (*p)[key->len]; | ||
| 83 | |||
| 84 | if (strncmp(*p, key->name, key->len)) | ||
| 85 | continue; | ||
| 86 | if (key->takes_value ? delimiter != '=' : (delimiter && !isspace((unsigned char)delimiter))) | ||
| 87 | continue; | ||
| 88 | |||
| 89 | *p += key->len + (key->takes_value ? 1 : 0); | ||
| 90 | return key->kind; | ||
| 91 | } | ||
| 92 | return LISTEN_OPTION_UNKNOWN; | ||
| 93 | } | ||
| 94 | |||
| 95 | /* Options trailing an address in a listen statement, in any order */ | ||
| 96 | static int listen_scan_options(char *p, int *shared, int *workers) { | ||
| 97 | uint64_t value; | ||
| 98 | |||
| 99 | for (;;) { | ||
| 100 | while (isspace((unsigned char)*p)) | ||
| 101 | ++p; | ||
| 102 | if (!*p) | ||
| 103 | return 1; | ||
| 104 | |||
| 105 | switch (parse_listen_option(&p)) { | ||
| 106 | case LISTEN_OPTION_SHARED: | ||
| 107 | *shared = 1; | ||
| 108 | break; | ||
| 109 | case LISTEN_OPTION_WORKERS: { | ||
| 110 | size_t off = scan_uint(p, &value, UINT_MAX); | ||
| 111 | |||
| 112 | if (!off) | ||
| 113 | return 0; | ||
| 114 | *workers = (int)value; | ||
| 115 | p += off; | ||
| 116 | break; | ||
| 117 | } | ||
| 118 | default: | ||
| 119 | return 0; | ||
| 120 | } | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | |||
| 125 | static const char *set_config_option(const char **option, char *value) { | ||
| 126 | #ifdef _DEBUG | ||
| 127 | fprintf(stderr, "Setting config option: %s\n", value); | ||
| 128 | #endif | ||
| 129 | while (isspace(*value)) | ||
| 130 | ++value; | ||
| 131 | free((char *)*option); | ||
| 132 | return *option = strdup(value); | ||
| 133 | } | ||
| 134 | |||
| 135 | static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) { | ||
| 136 | const char *s = src; | ||
| 137 | uint64_t value; | ||
| 138 | int off, bracket = 0; | ||
| 139 | while (isspace(*s)) | ||
| 140 | ++s; | ||
| 141 | if (*s == '[') | ||
| 142 | ++s, ++bracket; /* for v6 style notation */ | ||
| 143 | if (!(off = ip_parse(s, ip))) | ||
| 144 | return 0; | ||
| 145 | s += off; | ||
| 146 | if (bracket && *s == ']') | ||
| 147 | ++s; | ||
| 148 | if (*s == 0 || isspace(*s)) | ||
| 149 | return s - src; | ||
| 150 | if (!ip_is_v4mapped(ip)) { | ||
| 151 | if (*s != ':' && *s != '.') | ||
| 152 | return 0; | ||
| 153 | if (!bracket && *(s) == ':') | ||
| 154 | return 0; | ||
| 155 | s++; | ||
| 156 | } else { | ||
| 157 | if (*(s++) != ':') | ||
| 158 | return 0; | ||
| 159 | } | ||
| 160 | if (!(off = scan_uint(s, &value, 65535))) | ||
| 161 | return 0; | ||
| 162 | *port = (uint16_t)value; | ||
| 163 | return off + s - src; | ||
| 164 | } | ||
| 165 | |||
| 166 | static int scan_ip6_net(const char *src, ot_net *net) { | ||
| 167 | const char *s = src; | ||
| 168 | uint64_t value; | ||
| 169 | int off; | ||
| 170 | while (isspace(*s)) | ||
| 171 | ++s; | ||
| 172 | if (!(off = ip_parse(s, net->address))) | ||
| 173 | return 0; | ||
| 174 | s += off; | ||
| 175 | if (*s != '/') | ||
| 176 | net->bits = 128; | ||
| 177 | else { | ||
| 178 | s++; | ||
| 179 | if (!(off = scan_uint(s, &value, 128))) | ||
| 180 | return 0; | ||
| 181 | net->bits = (int)value; | ||
| 182 | if (ip_is_v4mapped(net->address)) | ||
| 183 | net->bits += 96; | ||
| 184 | if (net->bits > 128) | ||
| 185 | return 0; | ||
| 186 | s += off; | ||
| 187 | } | ||
| 188 | return off + s - src; | ||
| 189 | } | ||
| 190 | |||
| 191 | typedef enum { | ||
| 192 | CONFIG_UNKNOWN, | ||
| 193 | CONFIG_TRACKER_ROOTDIR, | ||
| 194 | CONFIG_TRACKER_USER, | ||
| 195 | CONFIG_LISTEN_TCP_UDP, | ||
| 196 | CONFIG_LISTEN_TCP, | ||
| 197 | CONFIG_LISTEN_UDP, | ||
| 198 | CONFIG_LISTEN_UDP_WORKERS, | ||
| 199 | CONFIG_LISTEN_TCP_WORKERS, | ||
| 200 | CONFIG_LISTEN_SHARED, | ||
| 201 | CONFIG_ACCESS_WHITELIST, | ||
| 202 | CONFIG_ACCESS_BLACKLIST, | ||
| 203 | CONFIG_ACCESS_FIFO_ADD, | ||
| 204 | CONFIG_ACCESS_FIFO_DELETE, | ||
| 205 | CONFIG_ACCESS_STATS, | ||
| 206 | CONFIG_ACCESS_STATS_PATH, | ||
| 207 | CONFIG_ACCESS_PROXY, | ||
| 208 | CONFIG_TRACKER_REDIRECT_URL, | ||
| 209 | CONFIG_LIVESYNC_CLUSTER_NODE_IP, | ||
| 210 | CONFIG_LIVESYNC_CLUSTER_LISTEN, | ||
| 211 | CONFIG_LIVESYNC_CLUSTER_GROUP, | ||
| 212 | } config_kind; | ||
| 213 | |||
| 214 | typedef struct { | ||
| 215 | const char *name; | ||
| 216 | size_t len; | ||
| 217 | config_kind kind; | ||
| 218 | } config_key; | ||
| 219 | |||
| 220 | #define CONFIG_KEY(s, k) { s, sizeof(s) - 1, k } | ||
| 221 | |||
| 222 | static const config_key config_keys[] = { | ||
| 223 | CONFIG_KEY("tracker.rootdir", CONFIG_TRACKER_ROOTDIR), | ||
| 224 | CONFIG_KEY("tracker.user", CONFIG_TRACKER_USER), | ||
| 225 | CONFIG_KEY("listen.tcp_udp", CONFIG_LISTEN_TCP_UDP), | ||
| 226 | CONFIG_KEY("listen.tcp", CONFIG_LISTEN_TCP), | ||
| 227 | CONFIG_KEY("listen.udp", CONFIG_LISTEN_UDP), | ||
| 228 | CONFIG_KEY("listen.udp.workers", CONFIG_LISTEN_UDP_WORKERS), | ||
| 229 | CONFIG_KEY("listen.tcp.workers", CONFIG_LISTEN_TCP_WORKERS), | ||
| 230 | CONFIG_KEY("listen.shared", CONFIG_LISTEN_SHARED), | ||
| 231 | #ifdef WANT_ACCESSLIST_WHITE | ||
| 232 | CONFIG_KEY("access.whitelist", CONFIG_ACCESS_WHITELIST), | ||
| 233 | #elif defined(WANT_ACCESSLIST_BLACK) | ||
| 234 | CONFIG_KEY("access.blacklist", CONFIG_ACCESS_BLACKLIST), | ||
| 235 | #endif | ||
| 236 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 237 | CONFIG_KEY("access.fifo_add", CONFIG_ACCESS_FIFO_ADD), | ||
| 238 | CONFIG_KEY("access.fifo_delete", CONFIG_ACCESS_FIFO_DELETE), | ||
| 239 | #endif | ||
| 240 | #ifdef WANT_RESTRICT_STATS | ||
| 241 | CONFIG_KEY("access.stats", CONFIG_ACCESS_STATS), | ||
| 242 | #endif | ||
| 243 | CONFIG_KEY("access.stats_path", CONFIG_ACCESS_STATS_PATH), | ||
| 244 | #ifdef WANT_IP_FROM_PROXY | ||
| 245 | CONFIG_KEY("access.proxy", CONFIG_ACCESS_PROXY), | ||
| 246 | #endif | ||
| 247 | CONFIG_KEY("tracker.redirect_url", CONFIG_TRACKER_REDIRECT_URL), | ||
| 248 | #ifdef WANT_SYNC_LIVE | ||
| 249 | CONFIG_KEY("livesync.cluster.node_ip", CONFIG_LIVESYNC_CLUSTER_NODE_IP), | ||
| 250 | CONFIG_KEY("livesync.cluster.listen", CONFIG_LIVESYNC_CLUSTER_LISTEN), | ||
| 251 | CONFIG_KEY("livesync.cluster.group", CONFIG_LIVESYNC_CLUSTER_GROUP), | ||
| 252 | #endif | ||
| 253 | }; | ||
| 254 | static const size_t config_entry_count = sizeof(config_keys) / sizeof(config_keys[0]); | ||
| 255 | |||
| 256 | static config_kind parse_config_key(char **p) { | ||
| 257 | size_t i; | ||
| 258 | for (i = 0; i < config_entry_count; ++i) { | ||
| 259 | const config_key *key = &config_keys[i]; | ||
| 260 | if (strncmp(*p, key->name, key->len) == 0 && isspace((unsigned char)(*p)[key->len])) { | ||
| 261 | *p += key->len + 1; | ||
| 262 | /* Skip white spaces */ | ||
| 263 | while (isspace((unsigned char)**p)) | ||
| 264 | ++(*p); | ||
| 265 | return key->kind; | ||
| 266 | } | ||
| 267 | } | ||
| 268 | return CONFIG_UNKNOWN; | ||
| 269 | } | ||
| 270 | |||
| 271 | static int parse_configfile(char *config_filename) { | ||
| 272 | FILE *config_file_handle; | ||
| 273 | char inbuf[512]; | ||
| 274 | ot_ip6 tmpip; | ||
| 275 | #if defined(WANT_RESTRICT_STATS) || defined(WANT_IP_FROM_PROXY) || defined(WANT_SYNC_LIVE) | ||
| 276 | ot_net tmpnet; | ||
| 277 | #endif | ||
| 278 | int bound = 0; | ||
| 279 | |||
| 280 | config_file_handle = fopen(config_filename, "r"); | ||
| 281 | |||
| 282 | if (config_file_handle == NULL) { | ||
| 283 | fprintf(stderr, "Warning: Can't open config file: %s.", config_filename); | ||
| 284 | return 0; | ||
| 285 | } | ||
| 286 | |||
| 287 | while (fgets(inbuf, sizeof(inbuf), config_file_handle)) { | ||
| 288 | char *p = inbuf, *end; | ||
| 289 | uint64_t value; | ||
| 290 | uint16_t tmpport = 6969; | ||
| 291 | config_kind kind; | ||
| 292 | |||
| 293 | /* Skip white spaces */ | ||
| 294 | while (isspace((unsigned char)*p)) | ||
| 295 | ++p; | ||
| 296 | |||
| 297 | /* Ignore comments and empty lines */ | ||
| 298 | if ((*p == '#') || (*p == '\n') || (*p == 0)) | ||
| 299 | continue; | ||
| 300 | |||
| 301 | /* consume trailing new lines and spaces */ | ||
| 302 | end = p + strlen(p); | ||
| 303 | while (end > p && isspace((unsigned char)end[-1])) | ||
| 304 | *--end = 0; | ||
| 305 | |||
| 306 | /* Scan for commands */ | ||
| 307 | kind = parse_config_key(&p); | ||
| 308 | switch (kind) { | ||
| 309 | case CONFIG_TRACKER_ROOTDIR: | ||
| 310 | set_config_option(&g_serverdir, p); | ||
| 311 | break; | ||
| 312 | case CONFIG_TRACKER_USER: | ||
| 313 | set_config_option(&g_serveruser, p); | ||
| 314 | break; | ||
| 315 | case CONFIG_LISTEN_TCP_UDP: | ||
| 316 | case CONFIG_LISTEN_TCP: | ||
| 317 | case CONFIG_LISTEN_UDP: { | ||
| 318 | int shared = OT_LISTEN_INHERIT; | ||
| 319 | int workers = OT_LISTEN_INHERIT; | ||
| 320 | int off = scan_ip6_port(p, tmpip, &tmpport); | ||
| 321 | |||
| 322 | if (!off || !listen_scan_options(p + off, &shared, &workers)) | ||
| 323 | goto parse_error; | ||
| 324 | if (kind != CONFIG_LISTEN_UDP) | ||
| 325 | listen_append(tmpip, tmpport, FLAG_TCP, shared, workers), ++bound; | ||
| 326 | if (kind != CONFIG_LISTEN_TCP) | ||
| 327 | listen_append(tmpip, tmpport, FLAG_UDP, shared, workers), ++bound; | ||
| 328 | break; | ||
| 329 | } | ||
| 330 | case CONFIG_LISTEN_UDP_WORKERS: | ||
| 331 | if (!scan_uint(p, &value, UINT_MAX)) | ||
| 332 | goto parse_error; | ||
| 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; | ||
| 339 | break; | ||
| 340 | case CONFIG_LISTEN_SHARED: | ||
| 341 | if (!scan_uint(p, &value, 1)) | ||
| 342 | goto parse_error; | ||
| 343 | g_listen_shared = (int)value; | ||
| 344 | break; | ||
| 345 | #ifdef WANT_ACCESSLIST_WHITE | ||
| 346 | case CONFIG_ACCESS_WHITELIST: | ||
| 347 | set_config_option(&g_accesslist_filename, p); | ||
| 348 | break; | ||
| 349 | #elif defined(WANT_ACCESSLIST_BLACK) | ||
| 350 | case CONFIG_ACCESS_BLACKLIST: | ||
| 351 | set_config_option(&g_accesslist_filename, p); | ||
| 352 | break; | ||
| 353 | #endif | ||
| 354 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 355 | case CONFIG_ACCESS_FIFO_ADD: | ||
| 356 | set_config_option(&g_accesslist_pipe_add, p); | ||
| 357 | break; | ||
| 358 | case CONFIG_ACCESS_FIFO_DELETE: | ||
| 359 | set_config_option(&g_accesslist_pipe_delete, p); | ||
| 360 | break; | ||
| 361 | #endif | ||
| 362 | #ifdef WANT_RESTRICT_STATS | ||
| 363 | case CONFIG_ACCESS_STATS: | ||
| 364 | if (!scan_ip6_net(p, &tmpnet)) | ||
| 365 | goto parse_error; | ||
| 366 | accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_STAT); | ||
| 367 | break; | ||
| 368 | #endif | ||
| 369 | case CONFIG_ACCESS_STATS_PATH: | ||
| 370 | set_config_option(&g_stats_path, p); | ||
| 371 | break; | ||
| 372 | #ifdef WANT_IP_FROM_PROXY | ||
| 373 | case CONFIG_ACCESS_PROXY: | ||
| 374 | if (!scan_ip6_net(p, &tmpnet)) | ||
| 375 | goto parse_error; | ||
| 376 | accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_PROXY); | ||
| 377 | break; | ||
| 378 | #endif | ||
| 379 | case CONFIG_TRACKER_REDIRECT_URL: | ||
| 380 | set_config_option(&g_redirecturl, p); | ||
| 381 | break; | ||
| 382 | #ifdef WANT_SYNC_LIVE | ||
| 383 | case CONFIG_LIVESYNC_CLUSTER_GROUP: | ||
| 384 | if (!ip_parse(p, tmpip) || livesync_set_group(tmpip) == -1) | ||
| 385 | goto parse_error; | ||
| 386 | break; | ||
| 387 | case CONFIG_LIVESYNC_CLUSTER_NODE_IP: | ||
| 388 | if (!scan_ip6_net(p, &tmpnet)) | ||
| 389 | goto parse_error; | ||
| 390 | accesslist_bless_net(&tmpnet, OT_PERMISSION_MAY_LIVESYNC); | ||
| 391 | break; | ||
| 392 | case CONFIG_LIVESYNC_CLUSTER_LISTEN: | ||
| 393 | tmpport = LIVESYNC_PORT; | ||
| 394 | { | ||
| 395 | int shared = OT_LISTEN_INHERIT, dummy = OT_LISTEN_INHERIT; | ||
| 396 | int off = scan_ip6_port(p, tmpip, &tmpport); | ||
| 397 | |||
| 398 | if (!off || !listen_scan_options(p + off, &shared, &dummy)) | ||
| 399 | goto parse_error; | ||
| 400 | listen_append(tmpip, tmpport, FLAG_MCA, shared, OT_LISTEN_INHERIT); | ||
| 401 | } | ||
| 402 | break; | ||
| 403 | #endif | ||
| 404 | default: | ||
| 405 | fprintf(stderr, "Unhandled line in config file: %s\n", inbuf); | ||
| 406 | break; | ||
| 407 | parse_error: | ||
| 408 | fprintf(stderr, "Parse error in config file: %s\n", inbuf); | ||
| 409 | } | ||
| 410 | } | ||
| 411 | fclose(config_file_handle); | ||
| 412 | return bound; | ||
| 413 | } | ||
| 414 | |||
| 415 | static void usage(char *name) { | ||
| 416 | fprintf(stderr, | ||
| 417 | "Usage: %s [-i ip] [-p port] [-P port] [-r redirect] [-d dir] [-u user] [-A ip[/bits]] [-f config] [-s livesyncport]" | ||
| 418 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 419 | " [-b blacklistfile]" | ||
| 420 | #elif defined(WANT_ACCESSLIST_WHITE) | ||
| 421 | " [-w whitelistfile]" | ||
| 422 | #endif | ||
| 423 | "\n", | ||
| 424 | name); | ||
| 425 | } | ||
| 426 | |||
| 427 | #define HELPLINE(opt, desc) fprintf(stderr, "\t%-10s%s\n", opt, desc) | ||
| 428 | static void help(char *name) { | ||
| 429 | usage(name); | ||
| 430 | |||
| 431 | HELPLINE("-f config", "include and execute the config file"); | ||
| 432 | HELPLINE("-i ip", "specify ip to bind to with next -[pP] (default: any, overrides preceeding ones)"); | ||
| 433 | HELPLINE("-p port", "do bind to tcp port (default: 6969, you may specify more than one)"); | ||
| 434 | HELPLINE("-P port", "do bind to udp port (default: 6969, you may specify more than one)"); | ||
| 435 | HELPLINE("-r redirecturl", "specify url where / should be redirected to (default none)"); | ||
| 436 | HELPLINE("-d dir", "specify directory to try to chroot to (default: \".\")"); | ||
| 437 | HELPLINE("-u user", "specify user under whose privileges opentracker should run (default: \"nobody\")"); | ||
| 438 | HELPLINE("-A ip[/bits]", "bless an ip address or net as admin address (e.g. to allow syncs from this address)"); | ||
| 439 | HELPLINE("-v", "report version, event mechanism and the feature flags opentracker was built with"); | ||
| 440 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 441 | HELPLINE("-b file", "specify blacklist file."); | ||
| 442 | #elif defined(WANT_ACCESSLIST_WHITE) | ||
| 443 | HELPLINE("-w file", "specify whitelist file."); | ||
| 444 | #endif | ||
| 445 | |||
| 446 | fprintf(stderr, "\nExample: ./opentracker -i 127.0.0.1 -p 6969 -P 6969 -f ./opentracker.conf -i 10.1.1.23 -p 2710 -p 80\n"); | ||
| 447 | fprintf(stderr, " Here -i 127.0.0.1 selects the ip address for the next -p 6969 and -P 6969.\n"); | ||
| 448 | fprintf(stderr, " If no port is bound from config file or command line, the last address given\n"); | ||
| 449 | fprintf(stderr, " (or ::1 if none is set) will be used on port 6969.\n"); | ||
| 450 | } | ||
| 451 | #undef HELPLINE | ||
| 452 | |||
| 453 | |||
| 454 | static void listen_resolve_defaults(void) { | ||
| 455 | size_t i; | ||
| 456 | |||
| 457 | for (i = 0; i < g_listen_count; ++i) { | ||
| 458 | ot_listen *entry = g_listen + i; | ||
| 459 | |||
| 460 | if (entry->shared == OT_LISTEN_INHERIT) | ||
| 461 | entry->shared = g_listen_shared; | ||
| 462 | if (entry->workers == OT_LISTEN_INHERIT) | ||
| 463 | entry->workers = entry->proto == FLAG_TCP ? g_listen_workers_tcp : g_listen_workers_udp; | ||
| 464 | |||
| 465 | /* A tcp worker hands a connection it cannot finish to the event loop, | ||
| 466 | which only one thread may touch where the set is ours rather than the | ||
| 467 | kernel's */ | ||
| 468 | if (entry->proto == FLAG_TCP && entry->workers > 1 && !event_can_share()) { | ||
| 469 | fprintf(stderr, "Warning: Multiple workers impossible on this platform with event mechanism %s chosen.\n", | ||
| 470 | event_mechanism()); | ||
| 471 | entry->workers = 1; | ||
| 472 | } | ||
| 473 | } | ||
| 474 | } | ||
| 475 | |||
| 476 | ot_listen *config_listen_entries(size_t *count) { | ||
| 477 | *count = g_listen_count; | ||
| 478 | return g_listen; | ||
| 479 | } | ||
| 480 | |||
| 481 | void config_parse(int argc, char **argv) { | ||
| 482 | ot_ip6 serverip; | ||
| 483 | ot_net tmpnet; | ||
| 484 | int bound = 0, scanon = 1; | ||
| 485 | uint64_t value; | ||
| 486 | |||
| 487 | memset(serverip, 0, sizeof(ot_ip6)); | ||
| 488 | #ifdef WANT_V4_ONLY | ||
| 489 | serverip[10] = serverip[11] = -1; | ||
| 490 | #endif | ||
| 491 | |||
| 492 | while (scanon) { | ||
| 493 | switch (getopt(argc, argv, | ||
| 494 | ":i:p:A:P:d:u:r:s:f:l:v" | ||
| 495 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 496 | "b:" | ||
| 497 | #elif defined(WANT_ACCESSLIST_WHITE) | ||
| 498 | "w:" | ||
| 499 | #endif | ||
| 500 | "h")) { | ||
| 501 | case -1: | ||
| 502 | scanon = 0; | ||
| 503 | break; | ||
| 504 | case 'i': | ||
| 505 | if (!ip_parse(optarg, serverip)) { | ||
| 506 | usage(argv[0]); | ||
| 507 | exit(1); | ||
| 508 | } | ||
| 509 | break; | ||
| 510 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 511 | case 'b': | ||
| 512 | set_config_option(&g_accesslist_filename, optarg); | ||
| 513 | break; | ||
| 514 | #elif defined(WANT_ACCESSLIST_WHITE) | ||
| 515 | case 'w': | ||
| 516 | set_config_option(&g_accesslist_filename, optarg); | ||
| 517 | break; | ||
| 518 | #endif | ||
| 519 | case 'p': | ||
| 520 | if (!scan_uint(optarg, &value, 65535)) { | ||
| 521 | usage(argv[0]); | ||
| 522 | exit(1); | ||
| 523 | } | ||
| 524 | listen_append(serverip, (uint16_t)value, FLAG_TCP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); | ||
| 525 | bound++; | ||
| 526 | break; | ||
| 527 | case 'P': | ||
| 528 | if (!scan_uint(optarg, &value, 65535)) { | ||
| 529 | usage(argv[0]); | ||
| 530 | exit(1); | ||
| 531 | } | ||
| 532 | listen_append(serverip, (uint16_t)value, FLAG_UDP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); | ||
| 533 | bound++; | ||
| 534 | break; | ||
| 535 | #ifdef WANT_SYNC_LIVE | ||
| 536 | case 's': | ||
| 537 | if (!scan_uint(optarg, &value, 65535)) { | ||
| 538 | usage(argv[0]); | ||
| 539 | exit(1); | ||
| 540 | } | ||
| 541 | listen_append(serverip, (uint16_t)value, FLAG_MCA, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); | ||
| 542 | break; | ||
| 543 | #endif | ||
| 544 | case 'd': | ||
| 545 | set_config_option(&g_serverdir, optarg); | ||
| 546 | break; | ||
| 547 | case 'u': | ||
| 548 | set_config_option(&g_serveruser, optarg); | ||
| 549 | break; | ||
| 550 | case 'r': | ||
| 551 | set_config_option(&g_redirecturl, optarg); | ||
| 552 | break; | ||
| 553 | case 'l': | ||
| 554 | g_statefile = optarg; | ||
| 555 | break; | ||
| 556 | case 'A': | ||
| 557 | if (!scan_ip6_net(optarg, &tmpnet)) { | ||
| 558 | usage(argv[0]); | ||
| 559 | exit(1); | ||
| 560 | } | ||
| 561 | accesslist_bless_net(&tmpnet, 0xffff); /* Allow everything for now */ | ||
| 562 | break; | ||
| 563 | case 'f': | ||
| 564 | bound += parse_configfile(optarg); | ||
| 565 | break; | ||
| 566 | case 'h': | ||
| 567 | help(argv[0]); | ||
| 568 | exit(0); | ||
| 569 | case 'v': { | ||
| 570 | char buffer[8192]; | ||
| 571 | char *cursor = buffer; | ||
| 572 | |||
| 573 | stats_return_tracker_version(&cursor, buffer + sizeof(buffer) - 1); | ||
| 574 | stats_return_tracker_features(&cursor, buffer + sizeof(buffer) - 1); | ||
| 575 | *cursor = 0; | ||
| 576 | fputs(buffer, stderr); | ||
| 577 | exit(0); | ||
| 578 | } | ||
| 579 | default: | ||
| 580 | case '?': | ||
| 581 | usage(argv[0]); | ||
| 582 | exit(1); | ||
| 583 | } | ||
| 584 | } | ||
| 585 | |||
| 586 | if (!bound) { | ||
| 587 | listen_append(serverip, 6969, FLAG_TCP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); | ||
| 588 | listen_append(serverip, 6969, FLAG_UDP, OT_LISTEN_INHERIT, OT_LISTEN_INHERIT); | ||
| 589 | } | ||
| 590 | |||
| 591 | listen_resolve_defaults(); | ||
| 592 | } | ||
diff --git a/src/ot_config.h b/src/ot_config.h new file mode 100644 index 0000000..79187ee --- /dev/null +++ b/src/ot_config.h | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_CONFIG_H__ | ||
| 5 | #define OT_CONFIG_H__ | ||
| 6 | |||
| 7 | #include <stdint.h> | ||
| 8 | |||
| 9 | #include "trackerlogic.h" | ||
| 10 | |||
| 11 | /* An address opentracker was asked to listen on. Options the statement did | ||
| 12 | not name have been filled in from the file wide defaults by the time these | ||
| 13 | are handed out. sock is filled in by whoever binds them. */ | ||
| 14 | typedef struct { | ||
| 15 | ot_ip6 ip; | ||
| 16 | uint16_t port; | ||
| 17 | PROTO_FLAG proto; | ||
| 18 | int shared; | ||
| 19 | int workers; | ||
| 20 | int64_t sock; | ||
| 21 | } ot_listen; | ||
| 22 | |||
| 23 | extern const char *g_redirecturl; | ||
| 24 | extern const char *g_serverdir; | ||
| 25 | extern const char *g_serveruser; | ||
| 26 | extern const char *g_statefile; | ||
| 27 | |||
| 28 | /* Reads the command line and every config file it names. Exits on error. */ | ||
| 29 | void config_parse(int argc, char **argv); | ||
| 30 | |||
| 31 | ot_listen *config_listen_entries(size_t *count); | ||
| 32 | |||
| 33 | #endif | ||
diff --git a/src/ot_event.c b/src/ot_event.c new file mode 100644 index 0000000..6062f70 --- /dev/null +++ b/src/ot_event.c | |||
| @@ -0,0 +1,357 @@ | |||
| 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 <stdlib.h> | ||
| 7 | #include <string.h> | ||
| 8 | #include <unistd.h> | ||
| 9 | |||
| 10 | /* Opentracker */ | ||
| 11 | #include "ot_event.h" | ||
| 12 | |||
| 13 | /* One caller waits, so the scratch the mechanism needs is kept here rather | ||
| 14 | than allocated per call. */ | ||
| 15 | |||
| 16 | /* poll works everywhere and is what the BSDs get until kqueue is written. | ||
| 17 | Defining OT_EVENT_FORCE_POLL picks it on a platform that would otherwise | ||
| 18 | use something better, which is how both are exercised by the tests. */ | ||
| 19 | #if defined(OT_EVENT_FORCE_POLL) | ||
| 20 | #define OT_EVENT_POLL | ||
| 21 | #elif defined(OT_EVENT_FORCE_KQUEUE) | ||
| 22 | #define OT_EVENT_KQUEUE | ||
| 23 | #elif defined(__linux__) | ||
| 24 | #define OT_EVENT_EPOLL | ||
| 25 | #elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__) | ||
| 26 | #define OT_EVENT_KQUEUE | ||
| 27 | #else | ||
| 28 | #define OT_EVENT_POLL | ||
| 29 | #endif | ||
| 30 | |||
| 31 | #ifdef OT_EVENT_EPOLL | ||
| 32 | /* ------------------------------------------------------------- epoll */ | ||
| 33 | |||
| 34 | #include <sys/epoll.h> | ||
| 35 | |||
| 36 | static int g_epoll = -1; | ||
| 37 | static struct epoll_event *g_ready; | ||
| 38 | static int g_ready_space; | ||
| 39 | |||
| 40 | const char *event_mechanism(void) { return "epoll"; } | ||
| 41 | /* The set lives in the kernel, and epoll_ctl during another thread's | ||
| 42 | epoll_wait is what it is for */ | ||
| 43 | int event_can_share(void) { return 1; } | ||
| 44 | |||
| 45 | int event_init(void) { | ||
| 46 | g_ready = NULL; | ||
| 47 | g_ready_space = 0; | ||
| 48 | return (g_epoll = epoll_create1(EPOLL_CLOEXEC)) == -1 ? -1 : 0; | ||
| 49 | } | ||
| 50 | |||
| 51 | void event_deinit(void) { | ||
| 52 | if (g_epoll != -1) | ||
| 53 | close(g_epoll); | ||
| 54 | free(g_ready); | ||
| 55 | g_epoll = -1; | ||
| 56 | g_ready = NULL; | ||
| 57 | g_ready_space = 0; | ||
| 58 | } | ||
| 59 | |||
| 60 | static int event_ctl(int op, int fd, int what, uint32_t generation) { | ||
| 61 | struct epoll_event ev; | ||
| 62 | |||
| 63 | memset(&ev, 0, sizeof(ev)); | ||
| 64 | ev.events = (what & EVENT_IN ? (uint32_t)EPOLLIN : 0) | (what & EVENT_OUT ? (uint32_t)EPOLLOUT : 0); | ||
| 65 | /* The generation rides along inside the kernel, so an event says which | ||
| 66 | connection it belongs to without anybody looking it up */ | ||
| 67 | ev.data.u64 = ((uint64_t)generation << 32) | (uint32_t)fd; | ||
| 68 | return epoll_ctl(g_epoll, op, fd, &ev); | ||
| 69 | } | ||
| 70 | |||
| 71 | int event_add(int fd, int what, uint32_t generation) { return event_ctl(EPOLL_CTL_ADD, fd, what, generation); } | ||
| 72 | int event_modify(int fd, int what, uint32_t generation) { return event_ctl(EPOLL_CTL_MOD, fd, what, generation); } | ||
| 73 | int event_del(int fd) { return epoll_ctl(g_epoll, EPOLL_CTL_DEL, fd, NULL); } | ||
| 74 | |||
| 75 | /* A descriptor leaves the interest list when the last reference to its open | ||
| 76 | file description is closed, and the tracker never duplicates one */ | ||
| 77 | int event_close(int fd) { return close(fd); } | ||
| 78 | |||
| 79 | int event_wait(ot_event *out, int max, int timeout_ms) { | ||
| 80 | int count, i; | ||
| 81 | |||
| 82 | if (max > g_ready_space) { | ||
| 83 | struct epoll_event *grown = realloc(g_ready, (size_t)max * sizeof(struct epoll_event)); | ||
| 84 | |||
| 85 | if (!grown) | ||
| 86 | return -1; | ||
| 87 | g_ready = grown; | ||
| 88 | g_ready_space = max; | ||
| 89 | } | ||
| 90 | |||
| 91 | /* An interrupted wait is reported as no events rather than retried here, | ||
| 92 | so that the caller gets to look at whatever the signal was about */ | ||
| 93 | if ((count = epoll_wait(g_epoll, g_ready, max, timeout_ms)) == -1) | ||
| 94 | return errno == EINTR ? 0 : -1; | ||
| 95 | for (i = 0; i < count; ++i) { | ||
| 96 | uint32_t events = g_ready[i].events; | ||
| 97 | |||
| 98 | out[i].fd = (int)(uint32_t)g_ready[i].data.u64; | ||
| 99 | out[i].generation = (uint32_t)(g_ready[i].data.u64 >> 32); | ||
| 100 | /* A hangup or an error is reported as both, so whichever side the caller | ||
| 101 | was waiting on gets to notice */ | ||
| 102 | out[i].what = (events & (EPOLLIN | EPOLLHUP | EPOLLERR) ? EVENT_IN : 0) | | ||
| 103 | (events & (EPOLLOUT | EPOLLHUP | EPOLLERR) ? EVENT_OUT : 0); | ||
| 104 | } | ||
| 105 | return count; | ||
| 106 | } | ||
| 107 | |||
| 108 | #endif /* OT_EVENT_EPOLL */ | ||
| 109 | |||
| 110 | #ifdef OT_EVENT_KQUEUE | ||
| 111 | /* ------------------------------------------------------------ kqueue */ | ||
| 112 | |||
| 113 | #include <sys/event.h> | ||
| 114 | #include <sys/time.h> | ||
| 115 | #include <sys/types.h> | ||
| 116 | |||
| 117 | static int g_kqueue = -1; | ||
| 118 | static struct kevent *g_ready; | ||
| 119 | static int g_ready_space; | ||
| 120 | |||
| 121 | const char *event_mechanism(void) { return "kqueue"; } | ||
| 122 | /* The set lives in the kernel, and kevent may be called from any thread */ | ||
| 123 | int event_can_share(void) { return 1; } | ||
| 124 | |||
| 125 | int event_init(void) { | ||
| 126 | g_ready = NULL; | ||
| 127 | g_ready_space = 0; | ||
| 128 | return (g_kqueue = kqueue()) == -1 ? -1 : 0; | ||
| 129 | } | ||
| 130 | |||
| 131 | void event_deinit(void) { | ||
| 132 | if (g_kqueue != -1) | ||
| 133 | close(g_kqueue); | ||
| 134 | free(g_ready); | ||
| 135 | g_kqueue = -1; | ||
| 136 | g_ready = NULL; | ||
| 137 | g_ready_space = 0; | ||
| 138 | } | ||
| 139 | |||
| 140 | /* kqueue keeps one registration per filter, so reading and writing are two | ||
| 141 | entries. A change of interest is an add of the one wanted and a delete | ||
| 142 | of the one not. The generation travels in udata, which the kernel passes | ||
| 143 | back untouched. */ | ||
| 144 | static int event_change(int fd, int what, uint32_t generation, int deleting) { | ||
| 145 | struct kevent change[2], result[2]; | ||
| 146 | void *udata = (void *)(uintptr_t)(((uint64_t)generation << 32) | (uint32_t)fd); | ||
| 147 | int succeeded = 0, count, i; | ||
| 148 | |||
| 149 | EV_SET(&change[0], (uintptr_t)fd, EVFILT_READ, | ||
| 150 | EV_RECEIPT | ((deleting || !(what & EVENT_IN)) ? EV_DELETE : EV_ADD), 0, 0, udata); | ||
| 151 | EV_SET(&change[1], (uintptr_t)fd, EVFILT_WRITE, | ||
| 152 | EV_RECEIPT | ((deleting || !(what & EVENT_OUT)) ? EV_DELETE : EV_ADD), 0, 0, udata); | ||
| 153 | |||
| 154 | if ((count = kevent(g_kqueue, change, 2, result, 2, NULL)) == -1) | ||
| 155 | return -1; | ||
| 156 | |||
| 157 | for (i = 0; i < count && i < 2; ++i) { | ||
| 158 | if (!result[i].data) { | ||
| 159 | ++succeeded; | ||
| 160 | continue; | ||
| 161 | } | ||
| 162 | /* Dropping interest we never registered is not a failure */ | ||
| 163 | if ((change[i].flags & EV_DELETE) && result[i].data == ENOENT) | ||
| 164 | continue; | ||
| 165 | errno = (int)result[i].data; | ||
| 166 | return -1; | ||
| 167 | } | ||
| 168 | |||
| 169 | /* Nothing at all was registered or removed, so the descriptor is unknown */ | ||
| 170 | if (!succeeded) { | ||
| 171 | errno = ENOENT; | ||
| 172 | return -1; | ||
| 173 | } | ||
| 174 | return 0; | ||
| 175 | } | ||
| 176 | |||
| 177 | int event_add(int fd, int what, uint32_t generation) { return what ? event_change(fd, what, generation, 0) : 0; } | ||
| 178 | int event_modify(int fd, int what, uint32_t generation) { return event_change(fd, what, generation, 0); } | ||
| 179 | int event_del(int fd) { return event_change(fd, 0, 0, 1); } | ||
| 180 | |||
| 181 | /* Closing a descriptor removes every kevent that references it */ | ||
| 182 | int event_close(int fd) { return close(fd); } | ||
| 183 | |||
| 184 | int event_wait(ot_event *out, int max, int timeout_ms) { | ||
| 185 | struct timespec timeout; | ||
| 186 | struct timespec *deadline = NULL; | ||
| 187 | int count, i; | ||
| 188 | |||
| 189 | if (max > g_ready_space) { | ||
| 190 | struct kevent *grown = realloc(g_ready, (size_t)max * sizeof(struct kevent)); | ||
| 191 | |||
| 192 | if (!grown) | ||
| 193 | return -1; | ||
| 194 | g_ready = grown; | ||
| 195 | g_ready_space = max; | ||
| 196 | } | ||
| 197 | |||
| 198 | if (timeout_ms >= 0) { | ||
| 199 | timeout.tv_sec = timeout_ms / 1000; | ||
| 200 | timeout.tv_nsec = (long)(timeout_ms % 1000) * 1000000; | ||
| 201 | deadline = &timeout; | ||
| 202 | } | ||
| 203 | |||
| 204 | /* An interrupted wait is reported as no events rather than retried here, | ||
| 205 | so that the caller gets to look at whatever the signal was about */ | ||
| 206 | if ((count = kevent(g_kqueue, NULL, 0, g_ready, max, deadline)) == -1) | ||
| 207 | return errno == EINTR ? 0 : -1; | ||
| 208 | for (i = 0; i < count; ++i) { | ||
| 209 | uint64_t udata = (uint64_t)(uintptr_t)g_ready[i].udata; | ||
| 210 | int eof = (g_ready[i].flags & (EV_EOF | EV_ERROR)) != 0; | ||
| 211 | |||
| 212 | out[i].fd = (int)(uint32_t)udata; | ||
| 213 | out[i].generation = (uint32_t)(udata >> 32); | ||
| 214 | out[i].what = ((g_ready[i].filter == EVFILT_READ || eof) ? EVENT_IN : 0) | | ||
| 215 | ((g_ready[i].filter == EVFILT_WRITE || eof) ? EVENT_OUT : 0); | ||
| 216 | } | ||
| 217 | return count; | ||
| 218 | } | ||
| 219 | |||
| 220 | #endif /* OT_EVENT_KQUEUE */ | ||
| 221 | |||
| 222 | #ifdef OT_EVENT_POLL | ||
| 223 | /* -------------------------------------------------------------- poll */ | ||
| 224 | |||
| 225 | #include <poll.h> | ||
| 226 | |||
| 227 | /* poll wants its descriptors packed, so the registered ones live in one | ||
| 228 | array and a second maps a descriptor number to its place in it. */ | ||
| 229 | static struct pollfd *g_pfds; | ||
| 230 | static uint32_t *g_generation; | ||
| 231 | static int *g_where; | ||
| 232 | static int g_count, g_space, g_where_space; | ||
| 233 | |||
| 234 | const char *event_mechanism(void) { return "poll"; } | ||
| 235 | /* The arrays below are ours and nothing guards them */ | ||
| 236 | int event_can_share(void) { return 0; } | ||
| 237 | |||
| 238 | int event_init(void) { | ||
| 239 | g_pfds = NULL, g_generation = NULL, g_where = NULL; | ||
| 240 | g_count = g_space = g_where_space = 0; | ||
| 241 | return 0; | ||
| 242 | } | ||
| 243 | |||
| 244 | void event_deinit(void) { | ||
| 245 | free(g_pfds), free(g_generation), free(g_where); | ||
| 246 | g_pfds = NULL, g_generation = NULL, g_where = NULL; | ||
| 247 | g_count = g_space = g_where_space = 0; | ||
| 248 | } | ||
| 249 | |||
| 250 | static short event_to_poll(int what) { | ||
| 251 | return (short)((what & EVENT_IN ? POLLIN : 0) | (what & EVENT_OUT ? POLLOUT : 0)); | ||
| 252 | } | ||
| 253 | |||
| 254 | static int event_room_for(int fd) { | ||
| 255 | if (fd >= g_where_space) { | ||
| 256 | int space = g_where_space ? g_where_space : 64; | ||
| 257 | int *grown; | ||
| 258 | |||
| 259 | while (space <= fd) | ||
| 260 | space *= 2; | ||
| 261 | if (!(grown = realloc(g_where, (size_t)space * sizeof(int)))) | ||
| 262 | return -1; | ||
| 263 | while (g_where_space < space) | ||
| 264 | grown[g_where_space++] = -1; | ||
| 265 | g_where = grown; | ||
| 266 | } | ||
| 267 | |||
| 268 | if (g_count == g_space) { | ||
| 269 | int space = g_space ? g_space * 2 : 64; | ||
| 270 | struct pollfd *pfds = realloc(g_pfds, (size_t)space * sizeof(struct pollfd)); | ||
| 271 | uint32_t *gens; | ||
| 272 | |||
| 273 | if (!pfds) | ||
| 274 | return -1; | ||
| 275 | g_pfds = pfds; | ||
| 276 | if (!(gens = realloc(g_generation, (size_t)space * sizeof(uint32_t)))) | ||
| 277 | return -1; | ||
| 278 | g_generation = gens; | ||
| 279 | g_space = space; | ||
| 280 | } | ||
| 281 | return 0; | ||
| 282 | } | ||
| 283 | |||
| 284 | int event_add(int fd, int what, uint32_t generation) { | ||
| 285 | if (fd < 0 || event_room_for(fd) == -1) | ||
| 286 | return -1; | ||
| 287 | if (g_where[fd] != -1) { | ||
| 288 | errno = EEXIST; | ||
| 289 | return -1; | ||
| 290 | } | ||
| 291 | |||
| 292 | g_pfds[g_count].fd = fd; | ||
| 293 | g_pfds[g_count].events = event_to_poll(what); | ||
| 294 | g_pfds[g_count].revents = 0; | ||
| 295 | g_generation[g_count] = generation; | ||
| 296 | g_where[fd] = g_count++; | ||
| 297 | return 0; | ||
| 298 | } | ||
| 299 | |||
| 300 | int event_modify(int fd, int what, uint32_t generation) { | ||
| 301 | if (fd < 0 || fd >= g_where_space || g_where[fd] == -1) { | ||
| 302 | errno = ENOENT; | ||
| 303 | return -1; | ||
| 304 | } | ||
| 305 | g_pfds[g_where[fd]].events = event_to_poll(what); | ||
| 306 | g_generation[g_where[fd]] = generation; | ||
| 307 | return 0; | ||
| 308 | } | ||
| 309 | |||
| 310 | int event_del(int fd) { | ||
| 311 | int at; | ||
| 312 | |||
| 313 | if (fd < 0 || fd >= g_where_space || (at = g_where[fd]) == -1) { | ||
| 314 | errno = ENOENT; | ||
| 315 | return -1; | ||
| 316 | } | ||
| 317 | |||
| 318 | /* The last one moves into the gap, so the array stays packed */ | ||
| 319 | g_pfds[at] = g_pfds[--g_count]; | ||
| 320 | g_generation[at] = g_generation[g_count]; | ||
| 321 | g_where[g_pfds[at].fd] = at; | ||
| 322 | g_where[fd] = -1; | ||
| 323 | return 0; | ||
| 324 | } | ||
| 325 | |||
| 326 | /* Nothing here lives in the kernel, so the entry has to come out by hand | ||
| 327 | before the descriptor goes, or poll reports POLLNVAL for it forever */ | ||
| 328 | int event_close(int fd) { | ||
| 329 | event_del(fd); | ||
| 330 | return close(fd); | ||
| 331 | } | ||
| 332 | |||
| 333 | int event_wait(ot_event *out, int max, int timeout_ms) { | ||
| 334 | int ready, found = 0, i; | ||
| 335 | |||
| 336 | /* An interrupted wait is reported as no events rather than retried here, | ||
| 337 | so that the caller gets to look at whatever the signal was about */ | ||
| 338 | if ((ready = poll(g_pfds, (nfds_t)g_count, timeout_ms)) == -1) | ||
| 339 | return errno == EINTR ? 0 : -1; | ||
| 340 | if (ready <= 0) | ||
| 341 | return ready; | ||
| 342 | |||
| 343 | for (i = 0; i < g_count && found < max; ++i) { | ||
| 344 | short revents = g_pfds[i].revents; | ||
| 345 | |||
| 346 | if (!revents) | ||
| 347 | continue; | ||
| 348 | out[found].fd = g_pfds[i].fd; | ||
| 349 | out[found].generation = g_generation[i]; | ||
| 350 | out[found].what = (revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL) ? EVENT_IN : 0) | | ||
| 351 | (revents & (POLLOUT | POLLHUP | POLLERR | POLLNVAL) ? EVENT_OUT : 0); | ||
| 352 | ++found; | ||
| 353 | } | ||
| 354 | return found; | ||
| 355 | } | ||
| 356 | |||
| 357 | #endif /* OT_EVENT_POLL */ | ||
diff --git a/src/ot_event.h b/src/ot_event.h new file mode 100644 index 0000000..cb84ca8 --- /dev/null +++ b/src/ot_event.h | |||
| @@ -0,0 +1,47 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_EVENT_H__ | ||
| 5 | #define OT_EVENT_H__ | ||
| 6 | |||
| 7 | #include <stdint.h> | ||
| 8 | |||
| 9 | /* Which descriptors have become readable or writable, asked of whichever | ||
| 10 | mechanism the kernel offers. The generation a descriptor was registered | ||
| 11 | with is handed back with every event, so a caller can tell an event for | ||
| 12 | the connection it is holding from one for a connection that has since been | ||
| 13 | closed and had its number reused. */ | ||
| 14 | |||
| 15 | enum { EVENT_IN = 1, EVENT_OUT = 2 }; | ||
| 16 | |||
| 17 | typedef struct { | ||
| 18 | int fd; | ||
| 19 | uint32_t generation; | ||
| 20 | int what; | ||
| 21 | } ot_event; | ||
| 22 | |||
| 23 | int event_init(void); | ||
| 24 | void event_deinit(void); | ||
| 25 | |||
| 26 | int event_add(int fd, int what, uint32_t generation); | ||
| 27 | int event_modify(int fd, int what, uint32_t generation); | ||
| 28 | int event_del(int fd); | ||
| 29 | |||
| 30 | /* Stop watching a descriptor and close it. Where the kernel drops a closed | ||
| 31 | descriptor from its set by itself, this is one system call where doing the | ||
| 32 | two separately would be two. */ | ||
| 33 | int event_close(int fd); | ||
| 34 | |||
| 35 | /* Waits at most timeout_ms, or indefinitely when that is negative, and fills | ||
| 36 | out with up to max ready descriptors. Returns how many, or -1. */ | ||
| 37 | int event_wait(ot_event *out, int max, int timeout_ms); | ||
| 38 | |||
| 39 | /* Whether the registered set may be changed from another thread while this | ||
| 40 | one is waiting on it. True where the kernel keeps the set, false where | ||
| 41 | this module keeps it in memory of its own. */ | ||
| 42 | int event_can_share(void); | ||
| 43 | |||
| 44 | /* Which mechanism was compiled in, for the version string */ | ||
| 45 | const char *event_mechanism(void); | ||
| 46 | |||
| 47 | #endif | ||
diff --git a/src/ot_fdstate.c b/src/ot_fdstate.c new file mode 100644 index 0000000..8e4bd89 --- /dev/null +++ b/src/ot_fdstate.c | |||
| @@ -0,0 +1,202 @@ | |||
| 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 <stdatomic.h> | ||
| 6 | #include <stdint.h> | ||
| 7 | #include <stdlib.h> | ||
| 8 | #include <sys/mman.h> | ||
| 9 | #include <sys/resource.h> | ||
| 10 | #include <sys/types.h> | ||
| 11 | |||
| 12 | /* Opentracker */ | ||
| 13 | #include "ot_fdstate.h" | ||
| 14 | |||
| 15 | enum { FD_FREE, FD_IDLE, FD_BUSY, FD_CLOSING }; | ||
| 16 | |||
| 17 | /* Generation and state share one word so that taking the state is also a | ||
| 18 | check that the generation has not moved */ | ||
| 19 | #define FD_GEN(w) ((uint32_t)((w) >> 32)) | ||
| 20 | #define FD_ST(w) ((uint32_t)(((w) >> 24) & 0xff)) | ||
| 21 | #define FD_WORD(g, s) (((uint64_t)(g) << 32) | ((uint64_t)(s) << 24)) | ||
| 22 | #define FD_SET_ST(w, s) (((w) & ~(uint64_t)0xff000000) | ((uint64_t)(s) << 24)) | ||
| 23 | |||
| 24 | typedef struct { | ||
| 25 | _Atomic uint64_t state; | ||
| 26 | _Atomic(void *) cookie; | ||
| 27 | _Atomic ot_time expiry; | ||
| 28 | } ot_fdslot; | ||
| 29 | |||
| 30 | /* A descriptor the kernel can hand out is below the limit it was handed out | ||
| 31 | under, but the limit can be raised after we sized the table, so an out of | ||
| 32 | range descriptor is rejected */ | ||
| 33 | #define FD_SLOTS_MAX (1u << 20) | ||
| 34 | |||
| 35 | static ot_fdslot *g_slots; | ||
| 36 | static size_t g_slot_count; | ||
| 37 | /* Descriptors are handed out lowest first, so a sweep only has to walk as | ||
| 38 | far as the highest one ever seen rather than the whole table. */ | ||
| 39 | static _Atomic size_t g_slot_highest; | ||
| 40 | |||
| 41 | int fdstate_init(void) { | ||
| 42 | struct rlimit limit; | ||
| 43 | size_t count = FD_SLOTS_MAX; | ||
| 44 | |||
| 45 | if (getrlimit(RLIMIT_NOFILE, &limit) == 0 && limit.rlim_cur != RLIM_INFINITY && limit.rlim_cur < FD_SLOTS_MAX) | ||
| 46 | count = (size_t)limit.rlim_cur; | ||
| 47 | |||
| 48 | g_slots = mmap(NULL, count * sizeof(ot_fdslot), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); | ||
| 49 | if (g_slots == MAP_FAILED) { | ||
| 50 | g_slots = NULL; | ||
| 51 | return -1; | ||
| 52 | } | ||
| 53 | |||
| 54 | g_slot_count = count; | ||
| 55 | atomic_store(&g_slot_highest, 0); | ||
| 56 | return 0; | ||
| 57 | } | ||
| 58 | |||
| 59 | void fdstate_deinit(void) { | ||
| 60 | if (g_slots) | ||
| 61 | munmap(g_slots, g_slot_count * sizeof(ot_fdslot)); | ||
| 62 | g_slots = NULL; | ||
| 63 | g_slot_count = 0; | ||
| 64 | atomic_store(&g_slot_highest, 0); | ||
| 65 | } | ||
| 66 | |||
| 67 | size_t fdstate_slots(void) { return g_slot_count; } | ||
| 68 | |||
| 69 | static ot_fdslot *fdstate_slot(int fd) { | ||
| 70 | if (fd < 0 || (size_t)fd >= g_slot_count) | ||
| 71 | return NULL; | ||
| 72 | return g_slots + fd; | ||
| 73 | } | ||
| 74 | |||
| 75 | uint32_t fdstate_open(int fd, void *cookie, ot_time expiry) { | ||
| 76 | ot_fdslot *slot = fdstate_slot(fd); | ||
| 77 | uint64_t word; | ||
| 78 | |||
| 79 | if (!slot) | ||
| 80 | return 0; | ||
| 81 | |||
| 82 | { | ||
| 83 | size_t highest = atomic_load_explicit(&g_slot_highest, memory_order_relaxed); | ||
| 84 | |||
| 85 | while ((size_t)fd > highest && | ||
| 86 | !atomic_compare_exchange_weak_explicit(&g_slot_highest, &highest, (size_t)fd, memory_order_relaxed, | ||
| 87 | memory_order_relaxed)) | ||
| 88 | ; | ||
| 89 | } | ||
| 90 | |||
| 91 | word = atomic_load_explicit(&slot->state, memory_order_relaxed); | ||
| 92 | atomic_store_explicit(&slot->cookie, cookie, memory_order_release); | ||
| 93 | atomic_store_explicit(&slot->expiry, expiry, memory_order_relaxed); | ||
| 94 | /* Publishes the two above to whoever claims the slot next */ | ||
| 95 | atomic_store_explicit(&slot->state, FD_WORD(FD_GEN(word), FD_IDLE), memory_order_release); | ||
| 96 | |||
| 97 | return FD_GEN(word); | ||
| 98 | } | ||
| 99 | |||
| 100 | uint32_t fdstate_generation(int fd) { | ||
| 101 | ot_fdslot *slot = fdstate_slot(fd); | ||
| 102 | |||
| 103 | return slot ? FD_GEN(atomic_load_explicit(&slot->state, memory_order_relaxed)) : 0; | ||
| 104 | } | ||
| 105 | |||
| 106 | int fdstate_claim(int fd, uint32_t generation) { | ||
| 107 | ot_fdslot *slot = fdstate_slot(fd); | ||
| 108 | uint64_t word; | ||
| 109 | |||
| 110 | if (!slot) | ||
| 111 | return 0; | ||
| 112 | |||
| 113 | word = atomic_load_explicit(&slot->state, memory_order_acquire); | ||
| 114 | for (;;) { | ||
| 115 | if (FD_GEN(word) != generation || FD_ST(word) != FD_IDLE) | ||
| 116 | return 0; | ||
| 117 | if (atomic_compare_exchange_weak_explicit(&slot->state, &word, FD_SET_ST(word, FD_BUSY), memory_order_acquire, | ||
| 118 | memory_order_relaxed)) | ||
| 119 | return 1; | ||
| 120 | /* word now holds what was really there, so the test above runs again */ | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | void fdstate_release(int fd) { | ||
| 125 | ot_fdslot *slot = fdstate_slot(fd); | ||
| 126 | uint64_t word; | ||
| 127 | |||
| 128 | if (!slot) | ||
| 129 | return; | ||
| 130 | |||
| 131 | word = atomic_load_explicit(&slot->state, memory_order_relaxed); | ||
| 132 | atomic_store_explicit(&slot->state, FD_SET_ST(word, FD_IDLE), memory_order_release); | ||
| 133 | } | ||
| 134 | |||
| 135 | int fdstate_claim_expired(int fd, ot_time now) { | ||
| 136 | ot_fdslot *slot = fdstate_slot(fd); | ||
| 137 | uint64_t word; | ||
| 138 | ot_time expiry; | ||
| 139 | |||
| 140 | if (!slot) | ||
| 141 | return 0; | ||
| 142 | |||
| 143 | /* Reading before asking for the slot keeps the common case, a descriptor | ||
| 144 | that has not expired, off the bus entirely */ | ||
| 145 | word = atomic_load_explicit(&slot->state, memory_order_relaxed); | ||
| 146 | if (FD_ST(word) != FD_IDLE) | ||
| 147 | return 0; | ||
| 148 | expiry = atomic_load_explicit(&slot->expiry, memory_order_relaxed); | ||
| 149 | if (!expiry || expiry > now) | ||
| 150 | return 0; | ||
| 151 | |||
| 152 | /* Nothing can move the expiry while the slot sits at FD_IDLE, so winning | ||
| 153 | the slot is the last word */ | ||
| 154 | return atomic_compare_exchange_strong_explicit(&slot->state, &word, FD_SET_ST(word, FD_CLOSING), memory_order_acquire, | ||
| 155 | memory_order_relaxed); | ||
| 156 | } | ||
| 157 | |||
| 158 | void fdstate_close(int fd) { | ||
| 159 | ot_fdslot *slot = fdstate_slot(fd); | ||
| 160 | uint64_t word; | ||
| 161 | |||
| 162 | if (!slot) | ||
| 163 | return; | ||
| 164 | |||
| 165 | word = atomic_load_explicit(&slot->state, memory_order_relaxed); | ||
| 166 | atomic_store_explicit(&slot->cookie, NULL, memory_order_relaxed); | ||
| 167 | atomic_store_explicit(&slot->expiry, 0, memory_order_relaxed); | ||
| 168 | atomic_store_explicit(&slot->state, FD_WORD(FD_GEN(word) + 1, FD_FREE), memory_order_release); | ||
| 169 | } | ||
| 170 | |||
| 171 | int fdstate_next_expired(ot_time now, int *cursor) { | ||
| 172 | size_t highest = atomic_load_explicit(&g_slot_highest, memory_order_relaxed); | ||
| 173 | |||
| 174 | for (; (size_t)*cursor <= highest; ++*cursor) | ||
| 175 | if (fdstate_claim_expired(*cursor, now)) | ||
| 176 | return (*cursor)++; | ||
| 177 | |||
| 178 | return -1; | ||
| 179 | } | ||
| 180 | |||
| 181 | void *fdstate_cookie(int fd) { | ||
| 182 | ot_fdslot *slot = fdstate_slot(fd); | ||
| 183 | |||
| 184 | /* Pairs with the release in fdstate_set_cookie and fdstate_open */ | ||
| 185 | return slot ? atomic_load_explicit(&slot->cookie, memory_order_acquire) : NULL; | ||
| 186 | } | ||
| 187 | |||
| 188 | void fdstate_set_cookie(int fd, void *cookie) { | ||
| 189 | ot_fdslot *slot = fdstate_slot(fd); | ||
| 190 | |||
| 191 | /* Publishes what the cookie points at to whoever reads it next, which may | ||
| 192 | be another thread entirely */ | ||
| 193 | if (slot) | ||
| 194 | atomic_store_explicit(&slot->cookie, cookie, memory_order_release); | ||
| 195 | } | ||
| 196 | |||
| 197 | void fdstate_set_expiry(int fd, ot_time expiry) { | ||
| 198 | ot_fdslot *slot = fdstate_slot(fd); | ||
| 199 | |||
| 200 | if (slot) | ||
| 201 | atomic_store_explicit(&slot->expiry, expiry, memory_order_relaxed); | ||
| 202 | } | ||
diff --git a/src/ot_fdstate.h b/src/ot_fdstate.h new file mode 100644 index 0000000..60fe070 --- /dev/null +++ b/src/ot_fdstate.h | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_FDSTATE_H__ | ||
| 5 | #define OT_FDSTATE_H__ | ||
| 6 | |||
| 7 | #include <stdint.h> | ||
| 8 | |||
| 9 | #include "trackerlogic.h" | ||
| 10 | |||
| 11 | /* What the tracker knows about a file descriptor: who owns it, when it | ||
| 12 | should be given up on, and whether anyone is working on it right now. | ||
| 13 | |||
| 14 | A descriptor number says nothing about identity, since the kernel hands | ||
| 15 | the same number out again as soon as it is closed. Every slot therefore | ||
| 16 | carries a generation that is bumped when the descriptor is given up, and | ||
| 17 | every caller that was told about a descriptor has to say which generation | ||
| 18 | it was told about. A caller holding a stale generation is turned away | ||
| 19 | instead of being handed somebody else's connection. | ||
| 20 | |||
| 21 | The table is sized once from RLIMIT_NOFILE and never moves, so looking a | ||
| 22 | descriptor up needs no synchronisation at all. */ | ||
| 23 | |||
| 24 | int fdstate_init(void); | ||
| 25 | void fdstate_deinit(void); | ||
| 26 | size_t fdstate_slots(void); | ||
| 27 | |||
| 28 | /* Take a freshly accepted descriptor into the table and return the | ||
| 29 | generation to quote when asking for it back. expiry of 0 never expires. */ | ||
| 30 | uint32_t fdstate_open(int fd, void *cookie, ot_time expiry); | ||
| 31 | |||
| 32 | /* Which generation a descriptor currently carries, to check an event that | ||
| 33 | was handed out earlier against the connection living there now. */ | ||
| 34 | uint32_t fdstate_generation(int fd); | ||
| 35 | |||
| 36 | /* Ask for exclusive use. Returns 0 if the descriptor has been recycled since | ||
| 37 | generation was handed out, or if somebody else is using it. */ | ||
| 38 | int fdstate_claim(int fd, uint32_t generation); | ||
| 39 | void fdstate_release(int fd); | ||
| 40 | |||
| 41 | /* The cleaner's side of the same thing: takes the descriptor only if nobody | ||
| 42 | is working on it and it really has expired. Never waits. */ | ||
| 43 | int fdstate_claim_expired(int fd, ot_time now); | ||
| 44 | |||
| 45 | /* Give the descriptor up. The generation is bumped here, which has to happen | ||
| 46 | before close(2) so that the kernel cannot reissue the number while events | ||
| 47 | quoting the old generation are still in flight. */ | ||
| 48 | void fdstate_close(int fd); | ||
| 49 | |||
| 50 | /* Walks the table for descriptors that have expired, handing back one at a | ||
| 51 | time until it returns -1. The cursor belongs to the caller, so several | ||
| 52 | sweepers can run without sharing anything. */ | ||
| 53 | int fdstate_next_expired(ot_time now, int *cursor); | ||
| 54 | |||
| 55 | /* Both only valid while the caller holds the descriptor. */ | ||
| 56 | void *fdstate_cookie(int fd); | ||
| 57 | |||
| 58 | /* Hand the slot a different cookie, for a connection whose state has moved */ | ||
| 59 | void fdstate_set_cookie(int fd, void *cookie); | ||
| 60 | void fdstate_set_expiry(int fd, ot_time expiry); | ||
| 61 | |||
| 62 | #endif | ||
diff --git a/src/ot_fmt.c b/src/ot_fmt.c new file mode 100644 index 0000000..362c9b5 --- /dev/null +++ b/src/ot_fmt.c | |||
| @@ -0,0 +1,40 @@ | |||
| 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 <stdarg.h> | ||
| 6 | #include <stdio.h> | ||
| 7 | #include <string.h> | ||
| 8 | |||
| 9 | /* Opentracker */ | ||
| 10 | #include "ot_fmt.h" | ||
| 11 | |||
| 12 | void fmt_append(char **cursor, const char *end, const char *format, ...) { | ||
| 13 | va_list arguments; | ||
| 14 | size_t room; | ||
| 15 | int written; | ||
| 16 | |||
| 17 | if (*cursor >= end) { | ||
| 18 | *cursor = (char *)end; | ||
| 19 | return; | ||
| 20 | } | ||
| 21 | |||
| 22 | room = (size_t)(end - *cursor); | ||
| 23 | va_start(arguments, format); | ||
| 24 | written = vsnprintf(*cursor, room, format, arguments); | ||
| 25 | va_end(arguments); | ||
| 26 | |||
| 27 | if (written < 0) | ||
| 28 | return; | ||
| 29 | *cursor = (size_t)written >= room ? (char *)end : *cursor + written; | ||
| 30 | } | ||
| 31 | |||
| 32 | void fmt_bytes(char **cursor, const char *end, const void *bytes, size_t length) { | ||
| 33 | if (*cursor >= end || (size_t)(end - *cursor) < length) { | ||
| 34 | *cursor = (char *)end; | ||
| 35 | return; | ||
| 36 | } | ||
| 37 | |||
| 38 | memcpy(*cursor, bytes, length); | ||
| 39 | *cursor += length; | ||
| 40 | } | ||
diff --git a/src/ot_fmt.h b/src/ot_fmt.h new file mode 100644 index 0000000..214cbc4 --- /dev/null +++ b/src/ot_fmt.h | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_FMT_H__ | ||
| 5 | #define OT_FMT_H__ | ||
| 6 | |||
| 7 | #include <stddef.h> | ||
| 8 | |||
| 9 | /* Appending to a cursor that walks a fixed buffer. Both take the cursor by | ||
| 10 | reference and advance it, so a reply is written as a run of statements and | ||
| 11 | no caller does pointer arithmetic of its own. */ | ||
| 12 | |||
| 13 | void fmt_append(char **cursor, const char *end, const char *format, ...) __attribute__((format(printf, 3, 4))); | ||
| 14 | |||
| 15 | /* Append raw bytes for the binary stretches of a bencoded reply. | ||
| 16 | Either everything is appended or nothing at all */ | ||
| 17 | void fmt_bytes(char **cursor, const char *end, const void *bytes, size_t length); | ||
| 18 | |||
| 19 | #endif | ||
diff --git a/src/ot_fullscrape.c b/src/ot_fullscrape.c new file mode 100644 index 0000000..9afad92 --- /dev/null +++ b/src/ot_fullscrape.c | |||
| @@ -0,0 +1,455 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifdef WANT_FULLSCRAPE | ||
| 5 | |||
| 6 | /* System */ | ||
| 7 | #include <arpa/inet.h> | ||
| 8 | #include <pthread.h> | ||
| 9 | #include <stdio.h> | ||
| 10 | #include <string.h> | ||
| 11 | #ifdef WANT_COMPRESSION_GZIP | ||
| 12 | #include <zlib.h> | ||
| 13 | #endif | ||
| 14 | #ifdef WANT_COMPRESSION_ZSTD | ||
| 15 | #include <zstd.h> | ||
| 16 | #endif | ||
| 17 | |||
| 18 | /* Opentracker */ | ||
| 19 | #include "ot_fmt.h" | ||
| 20 | #include "ot_hex.h" | ||
| 21 | #include "ot_fullscrape.h" | ||
| 22 | #include "ot_iovec.h" | ||
| 23 | #include "ot_mutex.h" | ||
| 24 | #include "ot_workqueue.h" | ||
| 25 | #include "ot_urlencoded.h" | ||
| 26 | #include "trackerlogic.h" | ||
| 27 | |||
| 28 | /* Fetch full scrape info for all torrents | ||
| 29 | Full scrapes usually are huge and one does not want to | ||
| 30 | allocate more memory. So lets get them in 512k units | ||
| 31 | */ | ||
| 32 | #define OT_SCRAPE_CHUNK_SIZE (1024 * 1024) | ||
| 33 | |||
| 34 | /* "d8:completei%zde10:downloadedi%zde10:incompletei%zdee" */ | ||
| 35 | #define OT_SCRAPE_MAXENTRYLEN 256 | ||
| 36 | |||
| 37 | /* Forward declaration */ | ||
| 38 | static void fullscrape_make(int taskid, ot_tasktype mode); | ||
| 39 | #ifdef WANT_COMPRESSION_GZIP | ||
| 40 | static void fullscrape_make_gzip(int taskid, ot_tasktype mode); | ||
| 41 | #endif | ||
| 42 | #ifdef WANT_COMPRESSION_ZSTD | ||
| 43 | static void fullscrape_make_zstd(int taskid, ot_tasktype mode); | ||
| 44 | #endif | ||
| 45 | |||
| 46 | /* Converter function from memory to human readable hex strings | ||
| 47 | XXX - Duplicated from ot_stats. Needs fix. */ | ||
| 48 | /* This is the entry point into this worker thread | ||
| 49 | It grabs tasks from mutex_tasklist and delivers results back | ||
| 50 | */ | ||
| 51 | static void *fullscrape_worker(void *args) { | ||
| 52 | (void)args; | ||
| 53 | |||
| 54 | while (g_opentracker_running) { | ||
| 55 | ot_tasktype tasktype = TASK_FULLSCRAPE; | ||
| 56 | ot_taskid taskid = workqueue_poptask(&tasktype); | ||
| 57 | if (!taskid) | ||
| 58 | break; | ||
| 59 | #ifdef WANT_COMPRESSION_ZSTD | ||
| 60 | if (tasktype & TASK_FLAG_ZSTD) | ||
| 61 | fullscrape_make_zstd(taskid, tasktype); | ||
| 62 | else | ||
| 63 | #endif | ||
| 64 | #ifdef WANT_COMPRESSION_GZIP | ||
| 65 | if (tasktype & TASK_FLAG_GZIP) | ||
| 66 | fullscrape_make_gzip(taskid, tasktype); | ||
| 67 | else | ||
| 68 | #endif | ||
| 69 | fullscrape_make(taskid, tasktype); | ||
| 70 | workqueue_pushchunked(taskid, NULL); | ||
| 71 | } | ||
| 72 | return NULL; | ||
| 73 | } | ||
| 74 | |||
| 75 | static pthread_t thread_id; | ||
| 76 | void fullscrape_init(void) { | ||
| 77 | pthread_create( &thread_id, NULL, fullscrape_worker, NULL ); | ||
| 78 | } | ||
| 79 | |||
| 80 | void fullscrape_deinit(void) { | ||
| 81 | workqueue_wakeall(); | ||
| 82 | pthread_join( thread_id, NULL ); | ||
| 83 | } | ||
| 84 | |||
| 85 | void fullscrape_deliver( int64_t sock, ot_tasktype tasktype ) { | ||
| 86 | workqueue_pushtask( sock, TASK_FULLSCRAPE | tasktype ); | ||
| 87 | } | ||
| 88 | |||
| 89 | static void fullscrape_write_one( ot_tasktype mode, char **cursor, ot_torrent *torrent, ot_hash *hash ) { | ||
| 90 | /* Callers guarantee this much room: the chunk writer keeps its low | ||
| 91 | watermark that far from the end, the compressors hand over a buffer of | ||
| 92 | exactly that size */ | ||
| 93 | char *r = *cursor; | ||
| 94 | const char *r_end = r + OT_SCRAPE_MAXENTRYLEN; | ||
| 95 | size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; | ||
| 96 | size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; | ||
| 97 | size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count; | ||
| 98 | |||
| 99 | switch (mode & TASK_TASK_MASK) { | ||
| 100 | case TASK_FULLSCRAPE: | ||
| 101 | default: | ||
| 102 | /* push hash as bencoded string */ | ||
| 103 | *r++ = '2'; | ||
| 104 | *r++ = '0'; | ||
| 105 | *r++ = ':'; | ||
| 106 | memcpy(r, hash, sizeof(ot_hash)); | ||
| 107 | r += sizeof(ot_hash); | ||
| 108 | /* push rest of the scrape string */ | ||
| 109 | fmt_append(&r, r_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", seed_count, down_count, peer_count - seed_count); | ||
| 110 | |||
| 111 | break; | ||
| 112 | case TASK_FULLSCRAPE_TPB_ASCII: | ||
| 113 | hex_from_hash(r, *hash); | ||
| 114 | r += 2 * sizeof(ot_hash); | ||
| 115 | fmt_append(&r, r_end, ":%zd:%zd\n", seed_count, peer_count - seed_count); | ||
| 116 | break; | ||
| 117 | case TASK_FULLSCRAPE_TPB_ASCII_PLUS: | ||
| 118 | hex_from_hash(r, *hash); | ||
| 119 | r += 2 * sizeof(ot_hash); | ||
| 120 | fmt_append(&r, r_end, ":%zd:%zd:%zd\n", seed_count, peer_count - seed_count, down_count); | ||
| 121 | break; | ||
| 122 | case TASK_FULLSCRAPE_TPB_BINARY: | ||
| 123 | memcpy(r, *hash, sizeof(ot_hash)); | ||
| 124 | r += sizeof(ot_hash); | ||
| 125 | *(uint32_t *)(r + 0) = htonl((uint32_t)seed_count); | ||
| 126 | *(uint32_t *)(r + 4) = htonl((uint32_t)(peer_count - seed_count)); | ||
| 127 | r += 8; | ||
| 128 | break; | ||
| 129 | case TASK_FULLSCRAPE_TPB_URLENCODED: | ||
| 130 | r += urlencoded_format(r, *hash, sizeof(ot_hash)); | ||
| 131 | fmt_append(&r, r_end, ":%zd:%zd\n", seed_count, peer_count - seed_count); | ||
| 132 | break; | ||
| 133 | case TASK_FULLSCRAPE_TRACKERSTATE: | ||
| 134 | hex_from_hash(r, *hash); | ||
| 135 | r += 2 * sizeof(ot_hash); | ||
| 136 | fmt_append(&r, r_end, ":%zd:%zd\n", torrent->peer_list6->base, down_count); | ||
| 137 | break; | ||
| 138 | } | ||
| 139 | *cursor = r; | ||
| 140 | } | ||
| 141 | |||
| 142 | static void fullscrape_make(int taskid, ot_tasktype mode) { | ||
| 143 | size_t bucket; | ||
| 144 | char *r, *re; | ||
| 145 | struct iovec iovector = {NULL, 0}; | ||
| 146 | |||
| 147 | /* Setup return vector... */ | ||
| 148 | r = iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE); | ||
| 149 | if (!r) | ||
| 150 | return; | ||
| 151 | |||
| 152 | /* re points to low watermark */ | ||
| 153 | re = r + OT_SCRAPE_CHUNK_SIZE - OT_SCRAPE_MAXENTRYLEN; | ||
| 154 | |||
| 155 | if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) | ||
| 156 | fmt_append(&r, (char *)iovector.iov_base + OT_SCRAPE_CHUNK_SIZE, "d5:filesd"); | ||
| 157 | |||
| 158 | /* For each bucket... */ | ||
| 159 | for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { | ||
| 160 | /* Get exclusive access to that bucket */ | ||
| 161 | ot_vector *torrents_list = mutex_bucket_lock(bucket); | ||
| 162 | ot_torrent *torrents = (ot_torrent *)(torrents_list->data); | ||
| 163 | size_t i; | ||
| 164 | |||
| 165 | /* For each torrent in this bucket.. */ | ||
| 166 | for (i = 0; i < torrents_list->size; ++i) { | ||
| 167 | fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash); | ||
| 168 | |||
| 169 | if (r > re) { | ||
| 170 | iovector.iov_len = r - (char *)iovector.iov_base; | ||
| 171 | |||
| 172 | if (workqueue_pushchunked(taskid, &iovector)) { | ||
| 173 | free(iovector.iov_base); | ||
| 174 | mutex_bucket_unlock(bucket, 0); | ||
| 175 | return; | ||
| 176 | } | ||
| 177 | /* Allocate a fresh output buffer */ | ||
| 178 | r = iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE); | ||
| 179 | if (!r) { | ||
| 180 | mutex_bucket_unlock(bucket, 0); | ||
| 181 | return; | ||
| 182 | } | ||
| 183 | |||
| 184 | /* re points to low watermark */ | ||
| 185 | re = r + OT_SCRAPE_CHUNK_SIZE - OT_SCRAPE_MAXENTRYLEN; | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | /* All torrents done: release lock on current bucket */ | ||
| 190 | mutex_bucket_unlock(bucket, 0); | ||
| 191 | |||
| 192 | /* Parent thread died? */ | ||
| 193 | if (!g_opentracker_running) | ||
| 194 | return; | ||
| 195 | } | ||
| 196 | |||
| 197 | if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) | ||
| 198 | fmt_append(&r, (char *)iovector.iov_base + OT_SCRAPE_CHUNK_SIZE, "ee"); | ||
| 199 | |||
| 200 | /* Send rest of data */ | ||
| 201 | iovector.iov_len = r - (char *)iovector.iov_base; | ||
| 202 | if (workqueue_pushchunked(taskid, &iovector)) | ||
| 203 | free(iovector.iov_base); | ||
| 204 | } | ||
| 205 | |||
| 206 | #ifdef WANT_COMPRESSION_GZIP | ||
| 207 | |||
| 208 | static void fullscrape_make_gzip(int taskid, ot_tasktype mode) { | ||
| 209 | size_t bucket; | ||
| 210 | char *r; | ||
| 211 | struct iovec iovector = {NULL, 0}; | ||
| 212 | int zres; | ||
| 213 | z_stream strm; | ||
| 214 | /* Setup return vector... */ | ||
| 215 | iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE); | ||
| 216 | if (!iovector.iov_base) | ||
| 217 | return; | ||
| 218 | |||
| 219 | memset(&strm, 0, sizeof(strm)); | ||
| 220 | strm.next_out = (uint8_t *)iovector.iov_base; | ||
| 221 | strm.avail_out = OT_SCRAPE_CHUNK_SIZE; | ||
| 222 | if (deflateInit2(&strm, 7, Z_DEFLATED, 31, 9, Z_DEFAULT_STRATEGY) != Z_OK) | ||
| 223 | fprintf(stderr, "not ok.\n"); | ||
| 224 | |||
| 225 | if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) { | ||
| 226 | strm.next_in = (uint8_t *)"d5:filesd"; | ||
| 227 | strm.avail_in = strlen("d5:filesd"); | ||
| 228 | zres = deflate(&strm, Z_NO_FLUSH); | ||
| 229 | } | ||
| 230 | |||
| 231 | /* For each bucket... */ | ||
| 232 | for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { | ||
| 233 | /* Get exclusive access to that bucket */ | ||
| 234 | ot_vector *torrents_list = mutex_bucket_lock(bucket); | ||
| 235 | ot_torrent *torrents = (ot_torrent *)(torrents_list->data); | ||
| 236 | size_t i; | ||
| 237 | |||
| 238 | /* For each torrent in this bucket.. */ | ||
| 239 | for (i = 0; i < torrents_list->size; ++i) { | ||
| 240 | char compress_buffer[OT_SCRAPE_MAXENTRYLEN]; | ||
| 241 | r = compress_buffer; | ||
| 242 | fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash); | ||
| 243 | strm.next_in = (uint8_t *)compress_buffer; | ||
| 244 | strm.avail_in = r - compress_buffer; | ||
| 245 | zres = deflate(&strm, Z_NO_FLUSH); | ||
| 246 | if ((zres < Z_OK) && (zres != Z_BUF_ERROR)) | ||
| 247 | fprintf(stderr, "deflate() failed while in fullscrape_make().\n"); | ||
| 248 | |||
| 249 | /* Check if there still is enough buffer left */ | ||
| 250 | while (!strm.avail_out) { | ||
| 251 | iovector.iov_len = (char *)strm.next_out - (char *)iovector.iov_base; | ||
| 252 | |||
| 253 | if (workqueue_pushchunked(taskid, &iovector)) { | ||
| 254 | free(iovector.iov_base); | ||
| 255 | mutex_bucket_unlock(bucket, 0); | ||
| 256 | return; | ||
| 257 | } | ||
| 258 | /* Allocate a fresh output buffer */ | ||
| 259 | iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE); | ||
| 260 | if (!iovector.iov_base) { | ||
| 261 | fprintf(stderr, "Out of memory trying to claim ouput buffer\n"); | ||
| 262 | deflateEnd(&strm); | ||
| 263 | mutex_bucket_unlock(bucket, 0); | ||
| 264 | return; | ||
| 265 | } | ||
| 266 | strm.next_out = (uint8_t *)iovector.iov_base; | ||
| 267 | strm.avail_out = OT_SCRAPE_CHUNK_SIZE; | ||
| 268 | zres = deflate(&strm, Z_NO_FLUSH); | ||
| 269 | if ((zres < Z_OK) && (zres != Z_BUF_ERROR)) | ||
| 270 | fprintf(stderr, "deflate() failed while in fullscrape_make().\n"); | ||
| 271 | } | ||
| 272 | } | ||
| 273 | |||
| 274 | /* All torrents done: release lock on current bucket */ | ||
| 275 | mutex_bucket_unlock(bucket, 0); | ||
| 276 | |||
| 277 | /* Parent thread died? */ | ||
| 278 | if (!g_opentracker_running) { | ||
| 279 | deflateEnd(&strm); | ||
| 280 | return; | ||
| 281 | } | ||
| 282 | } | ||
| 283 | |||
| 284 | if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) { | ||
| 285 | strm.next_in = (uint8_t *)"ee"; | ||
| 286 | strm.avail_in = strlen("ee"); | ||
| 287 | } | ||
| 288 | |||
| 289 | if (deflate(&strm, Z_FINISH) < Z_OK) | ||
| 290 | fprintf(stderr, "deflate() failed while in fullscrape_make()'s endgame.\n"); | ||
| 291 | |||
| 292 | iovector.iov_len = (char *)strm.next_out - (char *)iovector.iov_base; | ||
| 293 | if (workqueue_pushchunked(taskid, &iovector)) { | ||
| 294 | free(iovector.iov_base); | ||
| 295 | deflateEnd(&strm); | ||
| 296 | return; | ||
| 297 | } | ||
| 298 | |||
| 299 | /* Check if there's a last batch of data in the zlib buffer */ | ||
| 300 | if (!strm.avail_out) { | ||
| 301 | /* Allocate a fresh output buffer */ | ||
| 302 | iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE); | ||
| 303 | |||
| 304 | if (!iovector.iov_base) { | ||
| 305 | fprintf(stderr, "Problem with iovec_next_chunk\n"); | ||
| 306 | deflateEnd(&strm); | ||
| 307 | return; | ||
| 308 | } | ||
| 309 | strm.next_out = iovector.iov_base; | ||
| 310 | strm.avail_out = OT_SCRAPE_CHUNK_SIZE; | ||
| 311 | if (deflate(&strm, Z_FINISH) < Z_OK) | ||
| 312 | fprintf(stderr, "deflate() failed while in fullscrape_make()'s endgame.\n"); | ||
| 313 | |||
| 314 | /* Only pass the new buffer if there actually was some data left in the buffer */ | ||
| 315 | iovector.iov_len = (char *)strm.next_out - (char *)iovector.iov_base; | ||
| 316 | if (!iovector.iov_len || workqueue_pushchunked(taskid, &iovector)) | ||
| 317 | free(iovector.iov_base); | ||
| 318 | } | ||
| 319 | |||
| 320 | deflateEnd(&strm); | ||
| 321 | } | ||
| 322 | /* WANT_COMPRESSION_GZIP */ | ||
| 323 | #endif | ||
| 324 | |||
| 325 | #ifdef WANT_COMPRESSION_ZSTD | ||
| 326 | |||
| 327 | static void fullscrape_make_zstd(int taskid, ot_tasktype mode) { | ||
| 328 | int bucket; | ||
| 329 | char *r; | ||
| 330 | struct iovec iovector = {NULL, 0}; | ||
| 331 | ZSTD_CCtx *zstream = ZSTD_createCCtx(); | ||
| 332 | ZSTD_inBuffer inbuf; | ||
| 333 | ZSTD_outBuffer outbuf; | ||
| 334 | size_t more_bytes; | ||
| 335 | |||
| 336 | if (!zstream) | ||
| 337 | return; | ||
| 338 | |||
| 339 | /* Setup return vector... */ | ||
| 340 | iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE); | ||
| 341 | if (!iovector.iov_base) { | ||
| 342 | ZSTD_freeCCtx(zstream); | ||
| 343 | return; | ||
| 344 | } | ||
| 345 | |||
| 346 | /* Working with a compression level 6 is half as fast as level 3, but | ||
| 347 | seems to be the last reasonable bump that's worth extra cpu */ | ||
| 348 | ZSTD_CCtx_setParameter(zstream, ZSTD_c_compressionLevel, 6); | ||
| 349 | |||
| 350 | outbuf.dst = iovector.iov_base; | ||
| 351 | outbuf.size = OT_SCRAPE_CHUNK_SIZE; | ||
| 352 | outbuf.pos = 0; | ||
| 353 | |||
| 354 | if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) { | ||
| 355 | inbuf.src = (const void *)"d5:filesd"; | ||
| 356 | inbuf.size = strlen("d5:filesd"); | ||
| 357 | inbuf.pos = 0; | ||
| 358 | ZSTD_compressStream2(zstream, &outbuf, &inbuf, ZSTD_e_continue); | ||
| 359 | } | ||
| 360 | |||
| 361 | /* For each bucket... */ | ||
| 362 | for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { | ||
| 363 | /* Get exclusive access to that bucket */ | ||
| 364 | ot_vector *torrents_list = mutex_bucket_lock(bucket); | ||
| 365 | ot_torrent *torrents = (ot_torrent *)(torrents_list->data); | ||
| 366 | size_t i; | ||
| 367 | |||
| 368 | /* For each torrent in this bucket.. */ | ||
| 369 | for (i = 0; i < torrents_list->size; ++i) { | ||
| 370 | char compress_buffer[OT_SCRAPE_MAXENTRYLEN]; | ||
| 371 | r = compress_buffer; | ||
| 372 | fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash); | ||
| 373 | inbuf.src = compress_buffer; | ||
| 374 | inbuf.size = r - compress_buffer; | ||
| 375 | inbuf.pos = 0; | ||
| 376 | ZSTD_compressStream2(zstream, &outbuf, &inbuf, ZSTD_e_continue); | ||
| 377 | |||
| 378 | /* Check if there still is enough buffer left */ | ||
| 379 | while (outbuf.pos + OT_SCRAPE_MAXENTRYLEN > outbuf.size) { | ||
| 380 | iovector.iov_len = outbuf.size; | ||
| 381 | |||
| 382 | if (workqueue_pushchunked(taskid, &iovector)) { | ||
| 383 | free(iovector.iov_base); | ||
| 384 | ZSTD_freeCCtx(zstream); | ||
| 385 | return mutex_bucket_unlock(bucket, 0); | ||
| 386 | } | ||
| 387 | /* Allocate a fresh output buffer */ | ||
| 388 | iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE); | ||
| 389 | if (!iovector.iov_base) { | ||
| 390 | fprintf(stderr, "Out of memory trying to claim ouput buffer\n"); | ||
| 391 | ZSTD_freeCCtx(zstream); | ||
| 392 | return mutex_bucket_unlock(bucket, 0); | ||
| 393 | } | ||
| 394 | |||
| 395 | outbuf.dst = iovector.iov_base; | ||
| 396 | outbuf.size = OT_SCRAPE_CHUNK_SIZE; | ||
| 397 | outbuf.pos = 0; | ||
| 398 | |||
| 399 | ZSTD_compressStream2(zstream, &outbuf, &inbuf, ZSTD_e_continue); | ||
| 400 | } | ||
| 401 | } | ||
| 402 | |||
| 403 | /* All torrents done: release lock on current bucket */ | ||
| 404 | mutex_bucket_unlock(bucket, 0); | ||
| 405 | |||
| 406 | /* Parent thread died? */ | ||
| 407 | if (!g_opentracker_running) | ||
| 408 | return; | ||
| 409 | } | ||
| 410 | |||
| 411 | if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) { | ||
| 412 | inbuf.src = (const void *)"ee"; | ||
| 413 | inbuf.size = strlen("ee"); | ||
| 414 | inbuf.pos = 0; | ||
| 415 | } | ||
| 416 | |||
| 417 | more_bytes = ZSTD_compressStream2(zstream, &outbuf, &inbuf, ZSTD_e_end); | ||
| 418 | |||
| 419 | iovector.iov_len = outbuf.pos; | ||
| 420 | if (workqueue_pushchunked(taskid, &iovector)) { | ||
| 421 | free(iovector.iov_base); | ||
| 422 | ZSTD_freeCCtx(zstream); | ||
| 423 | return; | ||
| 424 | } | ||
| 425 | |||
| 426 | /* Check if there's a last batch of data in the zlib buffer */ | ||
| 427 | if (more_bytes) { | ||
| 428 | /* Allocate a fresh output buffer */ | ||
| 429 | iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE); | ||
| 430 | |||
| 431 | if (!iovector.iov_base) { | ||
| 432 | fprintf(stderr, "Problem with iovec_next_chunk\n"); | ||
| 433 | ZSTD_freeCCtx(zstream); | ||
| 434 | return; | ||
| 435 | } | ||
| 436 | |||
| 437 | outbuf.dst = iovector.iov_base; | ||
| 438 | outbuf.size = OT_SCRAPE_CHUNK_SIZE; | ||
| 439 | outbuf.pos = 0; | ||
| 440 | |||
| 441 | ZSTD_compressStream2(zstream, &outbuf, &inbuf, ZSTD_e_end); | ||
| 442 | |||
| 443 | /* Only pass the new buffer if there actually was some data left in the buffer */ | ||
| 444 | iovector.iov_len = outbuf.pos; | ||
| 445 | if (!iovector.iov_len || workqueue_pushchunked(taskid, &iovector)) | ||
| 446 | free(iovector.iov_base); | ||
| 447 | } | ||
| 448 | |||
| 449 | ZSTD_freeCCtx(zstream); | ||
| 450 | } | ||
| 451 | /* WANT_COMPRESSION_ZSTD */ | ||
| 452 | #endif | ||
| 453 | |||
| 454 | /* WANT_FULLSCRAPE */ | ||
| 455 | #endif | ||
diff --git a/src/ot_fullscrape.h b/src/ot_fullscrape.h new file mode 100644 index 0000000..8d11cd3 --- /dev/null +++ b/src/ot_fullscrape.h | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_FULLSCRAPE_H__ | ||
| 5 | #define OT_FULLSCRAPE_H__ | ||
| 6 | |||
| 7 | #ifdef WANT_FULLSCRAPE | ||
| 8 | |||
| 9 | #include "ot_mutex.h" | ||
| 10 | #include "ot_workqueue.h" | ||
| 11 | |||
| 12 | void fullscrape_init(void); | ||
| 13 | void fullscrape_deinit(void); | ||
| 14 | void fullscrape_deliver(int64_t sock, ot_tasktype tasktype); | ||
| 15 | |||
| 16 | #else | ||
| 17 | |||
| 18 | #define fullscrape_init() | ||
| 19 | #define fullscrape_deinit() | ||
| 20 | |||
| 21 | #endif | ||
| 22 | |||
| 23 | #endif | ||
diff --git a/src/ot_hex.c b/src/ot_hex.c new file mode 100644 index 0000000..b537094 --- /dev/null +++ b/src/ot_hex.c | |||
| @@ -0,0 +1,56 @@ | |||
| 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 <stdint.h> | ||
| 6 | #include <string.h> | ||
| 7 | |||
| 8 | /* Opentracker */ | ||
| 9 | #include "ot_hex.h" | ||
| 10 | |||
| 11 | int hex_value(int c) { | ||
| 12 | unsigned char u = (unsigned char)c; | ||
| 13 | |||
| 14 | if (u >= '0' && u <= '9') | ||
| 15 | return u - '0'; | ||
| 16 | u |= 0x20; /* fold upper case onto lower, digits are already handled */ | ||
| 17 | if (u >= 'a' && u <= 'f') | ||
| 18 | return u - 'a' + 10; | ||
| 19 | return -1; | ||
| 20 | } | ||
| 21 | |||
| 22 | int hex_isdigit(int c) { return hex_value(c) >= 0; } | ||
| 23 | |||
| 24 | char *hex_from_hash(char *dest, const uint8_t *hash) { | ||
| 25 | static const char digits[] = "0123456789ABCDEF"; | ||
| 26 | char *d = dest; | ||
| 27 | size_t i; | ||
| 28 | |||
| 29 | for (i = 0; i < sizeof(ot_hash); ++i) { | ||
| 30 | *d++ = digits[hash[i] >> 4]; | ||
| 31 | *d++ = digits[hash[i] & 15]; | ||
| 32 | } | ||
| 33 | *d = 0; | ||
| 34 | return dest; | ||
| 35 | } | ||
| 36 | |||
| 37 | int hex_to_hash(ot_hash hash, const char *src) { | ||
| 38 | uint8_t decoded[sizeof(ot_hash)]; | ||
| 39 | size_t i; | ||
| 40 | |||
| 41 | for (i = 0; i < sizeof(ot_hash); ++i) { | ||
| 42 | int high, low; | ||
| 43 | |||
| 44 | /* Tested one character at a time so that a string ending early stops us | ||
| 45 | at its terminator rather than one byte past it */ | ||
| 46 | if ((high = hex_value((unsigned char)src[2 * i])) < 0) | ||
| 47 | return 0; | ||
| 48 | if ((low = hex_value((unsigned char)src[2 * i + 1])) < 0) | ||
| 49 | return 0; | ||
| 50 | |||
| 51 | decoded[i] = (uint8_t)((high << 4) | low); | ||
| 52 | } | ||
| 53 | |||
| 54 | memcpy(hash, decoded, sizeof(decoded)); | ||
| 55 | return 1; | ||
| 56 | } | ||
diff --git a/src/ot_hex.h b/src/ot_hex.h new file mode 100644 index 0000000..ddbbb44 --- /dev/null +++ b/src/ot_hex.h | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_HEX_H__ | ||
| 5 | #define OT_HEX_H__ | ||
| 6 | |||
| 7 | #include <stdint.h> | ||
| 8 | |||
| 9 | #include "trackerlogic.h" | ||
| 10 | |||
| 11 | /* Write hash as forty upper case hex digits followed by a terminating zero. | ||
| 12 | dest must have room for forty one bytes. Returns dest. */ | ||
| 13 | char *hex_from_hash(char *dest, const uint8_t *hash); | ||
| 14 | |||
| 15 | /* Read forty hex digits from src into hash. Returns non-zero on success. | ||
| 16 | On failure hash is left untouched and no byte of src beyond the offending | ||
| 17 | character is read, so src need not be forty characters long. */ | ||
| 18 | int hex_to_hash(ot_hash hash, const char *src); | ||
| 19 | |||
| 20 | /* Non-zero if c is a hexadecimal digit, in either case. */ | ||
| 21 | int hex_isdigit(int c); | ||
| 22 | |||
| 23 | /* The value of a hexadecimal digit, or -1 if c is not one. Shared with the | ||
| 24 | address scanner so that both agree on what a digit is. */ | ||
| 25 | int hex_value(int c); | ||
| 26 | |||
| 27 | #endif | ||
diff --git a/src/ot_http.c b/src/ot_http.c new file mode 100644 index 0000000..96b8f4e --- /dev/null +++ b/src/ot_http.c | |||
| @@ -0,0 +1,810 @@ | |||
| 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 | #define _GNU_SOURCE | ||
| 6 | #include <arpa/inet.h> | ||
| 7 | #include <errno.h> | ||
| 8 | #include <pthread.h> | ||
| 9 | #include <stdio.h> | ||
| 10 | #include <stdlib.h> | ||
| 11 | #include <string.h> | ||
| 12 | #include <strings.h> | ||
| 13 | #include <sys/types.h> | ||
| 14 | #include <unistd.h> | ||
| 15 | |||
| 16 | /* Opentracker */ | ||
| 17 | #include "ot_accesslist.h" | ||
| 18 | #include "ot_fullscrape.h" | ||
| 19 | #include "ot_http.h" | ||
| 20 | #include "ot_iovec.h" | ||
| 21 | #include "ot_ip.h" | ||
| 22 | #include "ot_mutex.h" | ||
| 23 | #include "ot_workqueue.h" | ||
| 24 | #include "ot_scan.h" | ||
| 25 | #include "ot_event.h" | ||
| 26 | #include "ot_fdstate.h" | ||
| 27 | #include "ot_vector.h" | ||
| 28 | #include "ot_fmt.h" | ||
| 29 | #include "ot_stats.h" | ||
| 30 | #include "ot_urlencoded.h" | ||
| 31 | #include "trackerlogic.h" | ||
| 32 | |||
| 33 | #define OT_MAXMULTISCRAPE_COUNT 64 | ||
| 34 | extern const char *g_redirecturl; | ||
| 35 | |||
| 36 | const char *g_stats_path; | ||
| 37 | ssize_t g_stats_path_len; | ||
| 38 | |||
| 39 | enum { SUCCESS_HTTP_HEADER_LENGTH = 80, SUCCESS_HTTP_SIZE_OFF = 17 }; | ||
| 40 | |||
| 41 | struct http_data *http_adopt(const int64_t sock) { | ||
| 42 | struct http_data *cookie = fdstate_cookie(sock), *heap; | ||
| 43 | int interest; | ||
| 44 | |||
| 45 | if (!cookie || !(cookie->flag & STRUCT_HTTP_FLAG_ONSTACK)) | ||
| 46 | return cookie; | ||
| 47 | |||
| 48 | if (!(heap = malloc(sizeof(*heap)))) | ||
| 49 | return NULL; | ||
| 50 | |||
| 51 | *heap = *cookie; | ||
| 52 | heap->flag &= ~STRUCT_HTTP_FLAG_ONSTACK; | ||
| 53 | |||
| 54 | /* Publishing the cookie is what lets another thread reach this connection, | ||
| 55 | so nothing may touch it afterwards, not even to read what it asked for */ | ||
| 56 | interest = heap->interest; | ||
| 57 | fdstate_set_cookie((int)sock, heap); | ||
| 58 | |||
| 59 | if (event_add((int)sock, interest, fdstate_generation((int)sock)) == -1) { | ||
| 60 | fdstate_set_cookie((int)sock, cookie); | ||
| 61 | free(heap); | ||
| 62 | return NULL; | ||
| 63 | } | ||
| 64 | return heap; | ||
| 65 | } | ||
| 66 | |||
| 67 | /* Give up a connection: the one place that knows everything hanging off it. */ | ||
| 68 | void http_close(const int64_t sock) { | ||
| 69 | struct http_data *cookie = fdstate_cookie(sock); | ||
| 70 | |||
| 71 | fdstate_close((int)sock); | ||
| 72 | |||
| 73 | if (cookie) { | ||
| 74 | if (cookie->batch) | ||
| 75 | batch_reset(cookie->batch); | ||
| 76 | free(cookie->batch); | ||
| 77 | buf_reset(&cookie->request); | ||
| 78 | if (cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)) | ||
| 79 | workqueue_canceltask(sock); | ||
| 80 | if (!(cookie->flag & STRUCT_HTTP_FLAG_ONSTACK)) | ||
| 81 | free(cookie); | ||
| 82 | } | ||
| 83 | |||
| 84 | event_close((int)sock); | ||
| 85 | } | ||
| 86 | |||
| 87 | static void http_senddata(const int64_t sock, struct ot_workstruct *ws) { | ||
| 88 | struct http_data *cookie = fdstate_cookie(sock); | ||
| 89 | ssize_t written_size; | ||
| 90 | |||
| 91 | if (!cookie) { | ||
| 92 | http_close(sock); | ||
| 93 | return; | ||
| 94 | } | ||
| 95 | |||
| 96 | /* whoever sends data is not interested in its input buffer */ | ||
| 97 | if (ws->keep_alive && ws->header_size != ws->request_size) { | ||
| 98 | size_t rest = ws->request_size - ws->header_size; | ||
| 99 | if (cookie->request.data) { | ||
| 100 | memmove(cookie->request.data, ws->request + ws->header_size, rest); | ||
| 101 | buf_truncate(&cookie->request, rest); | ||
| 102 | } else if (buf_cat(&cookie->request, ws->request + ws->header_size, rest)) { | ||
| 103 | /* Without room for what the client pipelined behind this request, answer | ||
| 104 | this one and let the connection close, rather than keeping it open | ||
| 105 | around a request we have silently dropped */ | ||
| 106 | buf_reset(&cookie->request); | ||
| 107 | ws->keep_alive = 0; | ||
| 108 | } | ||
| 109 | } else | ||
| 110 | buf_reset(&cookie->request); | ||
| 111 | |||
| 112 | written_size = write(sock, ws->reply, ws->reply_size); | ||
| 113 | /* A send buffer with no room in it is not a failure: nothing went out, so | ||
| 114 | the whole reply goes into the batch below and leaves when the socket | ||
| 115 | drains. Only a real error ends the connection here. */ | ||
| 116 | if (written_size < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) | ||
| 117 | written_size = 0; | ||
| 118 | |||
| 119 | if ((written_size < 0) || ((written_size == ws->reply_size) && !ws->keep_alive)) { | ||
| 120 | http_close(sock); | ||
| 121 | return; | ||
| 122 | } | ||
| 123 | |||
| 124 | if (written_size < ws->reply_size) { | ||
| 125 | char *outbuf; | ||
| 126 | |||
| 127 | if (!(outbuf = malloc(ws->reply_size - written_size))) { | ||
| 128 | http_close(sock); | ||
| 129 | return; | ||
| 130 | } | ||
| 131 | |||
| 132 | memcpy(outbuf, ws->reply + written_size, ws->reply_size - written_size); | ||
| 133 | if (!cookie->batch) { | ||
| 134 | cookie->batch = calloc(1, sizeof(ot_batch)); | ||
| 135 | if (!cookie->batch) { | ||
| 136 | free(outbuf); | ||
| 137 | http_close(sock); | ||
| 138 | return; | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | batch_add(cookie->batch, outbuf, ws->reply_size - written_size); | ||
| 143 | |||
| 144 | /* writeable short data sockets just have a tcp timeout */ | ||
| 145 | if (!ws->keep_alive) | ||
| 146 | fdstate_set_expiry(sock, 0); | ||
| 147 | http_interest_set(sock, cookie, EVENT_OUT); | ||
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | #define HTTPERROR_302 return http_issue_error(sock, ws, CODE_HTTPERROR_302) | ||
| 152 | #define HTTPERROR_400 return http_issue_error(sock, ws, CODE_HTTPERROR_400) | ||
| 153 | #define HTTPERROR_400_PARAM return http_issue_error(sock, ws, CODE_HTTPERROR_400_PARAM) | ||
| 154 | #define HTTPERROR_400_COMPACT return http_issue_error(sock, ws, CODE_HTTPERROR_400_COMPACT) | ||
| 155 | #define HTTPERROR_400_DOUBLEHASH return http_issue_error(sock, ws, CODE_HTTPERROR_400_PARAM) | ||
| 156 | #define HTTPERROR_402_NOTMODEST return http_issue_error(sock, ws, CODE_HTTPERROR_402_NOTMODEST) | ||
| 157 | #define HTTPERROR_403_IP return http_issue_error(sock, ws, CODE_HTTPERROR_403_IP) | ||
| 158 | #define HTTPERROR_404 return http_issue_error(sock, ws, CODE_HTTPERROR_404) | ||
| 159 | #define HTTPERROR_500 return http_issue_error(sock, ws, CODE_HTTPERROR_500) | ||
| 160 | /* A connection is either being read or being written, never both, so what it | ||
| 161 | waits for is set outright rather than added to and taken away again. The | ||
| 162 | event layer only hears about it when the answer changes. */ | ||
| 163 | void http_interest_set(const int64_t sock, struct http_data *cookie, int what) { | ||
| 164 | if (cookie->interest == what) | ||
| 165 | return; | ||
| 166 | cookie->interest = what; | ||
| 167 | /* Still on a stack, so not in the event loop yet: whoever adopts this | ||
| 168 | connection registers whatever it asked for last */ | ||
| 169 | if (cookie->flag & STRUCT_HTTP_FLAG_ONSTACK) | ||
| 170 | return; | ||
| 171 | event_modify((int)sock, what, fdstate_generation((int)sock)); | ||
| 172 | } | ||
| 173 | |||
| 174 | ssize_t http_issue_error(const int64_t sock, struct ot_workstruct *ws, int code) { | ||
| 175 | const char *error_code[] = {"302 Found", "400 Invalid Request", "400 Invalid Request", "400 Invalid Request", "402 Payment Required", | ||
| 176 | "403 Not Modest", "403 Access Denied", "404 Not Found", "500 Internal Server Error"}; | ||
| 177 | const char *title = error_code[code]; | ||
| 178 | |||
| 179 | ws->reply = ws->outbuf; | ||
| 180 | if (code == CODE_HTTPERROR_302) | ||
| 181 | ws->reply_size = snprintf(ws->reply, G_OUTBUF_SIZE, "HTTP/1.0 302 Found\r\nContent-Length: 0\r\nLocation: %s\r\n\r\n", g_redirecturl); | ||
| 182 | else | ||
| 183 | ws->reply_size = snprintf(ws->reply, G_OUTBUF_SIZE, "HTTP/1.0 %s\r\nContent-Type: text/html\r\nContent-Length: %zd\r\n\r\n<title>%s</title>\n", title, | ||
| 184 | strlen(title) + 16 - 4, title + 4); | ||
| 185 | |||
| 186 | #ifdef _DEBUG_HTTPERROR | ||
| 187 | fprintf(stderr, "DEBUG: invalid request was: %s\n", ws->debugbuf); | ||
| 188 | #endif | ||
| 189 | stats_issue_event(ws, EVENT_FAILED, FLAG_TCP, code); | ||
| 190 | http_senddata(sock, ws); | ||
| 191 | return ws->reply_size = -2; | ||
| 192 | } | ||
| 193 | |||
| 194 | ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial) { | ||
| 195 | struct http_data *cookie = fdstate_cookie(sock); | ||
| 196 | char *header; | ||
| 197 | const char *encoding = ""; | ||
| 198 | size_t header_size, size = iovec_length(&iovec_entries, (const struct iovec **)&iovector); | ||
| 199 | |||
| 200 | /* No cookie? Bad socket. Leave. */ | ||
| 201 | if (!cookie) { | ||
| 202 | iovec_free(&iovec_entries, &iovector); | ||
| 203 | HTTPERROR_500; | ||
| 204 | } | ||
| 205 | |||
| 206 | /* Descriptor numbers get recycled, so a result can only belong to this | ||
| 207 | connection if this connection is in fact waiting for one. */ | ||
| 208 | if (!(cookie->flag & (STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER))) { | ||
| 209 | iovec_free(&iovec_entries, &iovector); | ||
| 210 | return 0; | ||
| 211 | } | ||
| 212 | |||
| 213 | /* If this socket collected request in a buffer, free it now */ | ||
| 214 | buf_reset(&cookie->request); | ||
| 215 | |||
| 216 | /* If we came here, wait for the answer is over */ | ||
| 217 | if (cookie->flag & STRUCT_HTTP_FLAG_WAITINGFORTASK) { | ||
| 218 | http_interest_set(sock, cookie, 0); | ||
| 219 | cookie->flag &= ~STRUCT_HTTP_FLAG_WAITINGFORTASK; | ||
| 220 | } | ||
| 221 | |||
| 222 | if (iovec_entries) { | ||
| 223 | |||
| 224 | if (cookie->flag & STRUCT_HTTP_FLAG_ZSTD) | ||
| 225 | encoding = "Content-Encoding: zstd\r\n"; | ||
| 226 | else if (cookie->flag & STRUCT_HTTP_FLAG_GZIP) | ||
| 227 | encoding = "Content-Encoding: gzip\r\n"; | ||
| 228 | else if (cookie->flag & STRUCT_HTTP_FLAG_BZIP2) | ||
| 229 | encoding = "Content-Encoding: bzip2\r\n"; | ||
| 230 | |||
| 231 | if (!(cookie->flag & STRUCT_HTTP_FLAG_CHUNKED)) | ||
| 232 | header_size = asprintf(&header, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n%sContent-Length: %zd\r\n\r\n", encoding, size); | ||
| 233 | else { | ||
| 234 | if (!(cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER)) { | ||
| 235 | header_size = | ||
| 236 | asprintf(&header, "HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\n%sTransfer-Encoding: chunked\r\n\r\n%zx\r\n", encoding, size); | ||
| 237 | cookie->flag |= STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER; | ||
| 238 | } else | ||
| 239 | header_size = asprintf(&header, "%zx\r\n", size); | ||
| 240 | } | ||
| 241 | if (!header) { | ||
| 242 | iovec_free(&iovec_entries, &iovector); | ||
| 243 | HTTPERROR_500; | ||
| 244 | } | ||
| 245 | |||
| 246 | if (!cookie->batch) { | ||
| 247 | cookie->batch = calloc(1, sizeof(ot_batch)); | ||
| 248 | if (!cookie->batch) { | ||
| 249 | free(header); | ||
| 250 | iovec_free(&iovec_entries, &iovector); | ||
| 251 | HTTPERROR_500; | ||
| 252 | } | ||
| 253 | } | ||
| 254 | |||
| 255 | batch_add(cookie->batch, header, header_size); | ||
| 256 | batch_add_vector(cookie->batch, iovector, iovec_entries); | ||
| 257 | free(iovector); | ||
| 258 | |||
| 259 | if (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) | ||
| 260 | batch_add_copy(cookie->batch, "\r\n", 2); | ||
| 261 | } | ||
| 262 | |||
| 263 | if ((cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) && cookie->batch && !is_partial) { | ||
| 264 | batch_add_copy(cookie->batch, "0\r\n\r\n", 5); | ||
| 265 | cookie->flag &= ~STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER; | ||
| 266 | } | ||
| 267 | |||
| 268 | fdstate_set_expiry(sock, time_future_seconds(OT_CLIENT_TIMEOUT_SEND)); | ||
| 269 | http_interest_set(sock, cookie, EVENT_OUT); | ||
| 270 | return 0; | ||
| 271 | } | ||
| 272 | |||
| 273 | static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) { | ||
| 274 | static const ot_keywords keywords_main[] = {{"mode", 1}, {"format", 2}, {"info_hash", 3}, {NULL, -3}}; | ||
| 275 | static const ot_keywords keywords_mode[] = {{"peer", TASK_STATS_PEERS}, | ||
| 276 | {"conn", TASK_STATS_CONNS}, | ||
| 277 | {"scrp", TASK_STATS_SCRAPE}, | ||
| 278 | {"udp4", TASK_STATS_UDP}, | ||
| 279 | {"tcp4", TASK_STATS_TCP}, | ||
| 280 | {"torr", TASK_STATS_TORRENTS}, | ||
| 281 | {"fscr", TASK_STATS_FULLSCRAPE}, | ||
| 282 | {"s24s", TASK_STATS_SLASH24S}, | ||
| 283 | {"tpbs", TASK_STATS_TPB}, | ||
| 284 | {"herr", TASK_STATS_HTTPERRORS}, | ||
| 285 | {"completed", TASK_STATS_COMPLETED}, | ||
| 286 | {"top100", TASK_STATS_TOP100}, | ||
| 287 | {"top10", TASK_STATS_TOP10}, | ||
| 288 | {"renew", TASK_STATS_RENEW}, | ||
| 289 | {"syncs", TASK_STATS_SYNCS}, | ||
| 290 | {"everything", TASK_STATS_EVERYTHING}, | ||
| 291 | {"statedump", TASK_FULLSCRAPE_TRACKERSTATE}, | ||
| 292 | {"woodpeckers", TASK_STATS_WOODPECKERS}, | ||
| 293 | #ifdef WANT_LOG_NUMWANT | ||
| 294 | {"numwants", TASK_STATS_NUMWANTS}, | ||
| 295 | #endif | ||
| 296 | {NULL, -3}}; | ||
| 297 | static const ot_keywords keywords_format[] = {{"bin", TASK_FULLSCRAPE_TPB_BINARY}, {"ben", TASK_FULLSCRAPE}, | ||
| 298 | {"url", TASK_FULLSCRAPE_TPB_URLENCODED}, {"txt", TASK_FULLSCRAPE_TPB_ASCII}, | ||
| 299 | {"txtp", TASK_FULLSCRAPE_TPB_ASCII_PLUS}, {NULL, -3}}; | ||
| 300 | |||
| 301 | struct http_data *cookie = fdstate_cookie(sock); | ||
| 302 | char *reply; | ||
| 303 | int mode = TASK_STATS_PEERS, scanon = 1, format = 0; | ||
| 304 | |||
| 305 | /* A socket without one is a dead socket, which is what http_sendiovecdata | ||
| 306 | already answers 500 to. */ | ||
| 307 | if (!cookie) | ||
| 308 | HTTPERROR_500; | ||
| 309 | |||
| 310 | #ifdef WANT_RESTRICT_STATS | ||
| 311 | if (!accesslist_is_blessed(cookie->ip, OT_PERMISSION_MAY_STAT)) | ||
| 312 | HTTPERROR_403_IP; | ||
| 313 | #endif | ||
| 314 | |||
| 315 | while (scanon) { | ||
| 316 | switch (urlencoded_match_keyword(keywords_main, &read_ptr, SCAN_SEARCHPATH_PARAM)) { | ||
| 317 | case -2: | ||
| 318 | scanon = 0; | ||
| 319 | break; /* TERMINATOR */ | ||
| 320 | case -1: | ||
| 321 | HTTPERROR_400_PARAM; /* PARSE ERROR */ | ||
| 322 | case -3: | ||
| 323 | urlencoded_skip_value(&read_ptr); | ||
| 324 | break; | ||
| 325 | case 1: /* matched "mode" */ | ||
| 326 | if ((mode = urlencoded_match_keyword(keywords_mode, &read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0) | ||
| 327 | HTTPERROR_400_PARAM; | ||
| 328 | break; | ||
| 329 | case 2: /* matched "format" */ | ||
| 330 | if ((format = urlencoded_match_keyword(keywords_format, &read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0) | ||
| 331 | HTTPERROR_400_PARAM; | ||
| 332 | break; | ||
| 333 | case 3: | ||
| 334 | HTTPERROR_400_PARAM; /* If the stats URL was mistakenly added as announce URL, return a 400 */ | ||
| 335 | } | ||
| 336 | } | ||
| 337 | |||
| 338 | #ifdef WANT_FULLSCRAPE | ||
| 339 | if (mode == TASK_FULLSCRAPE_TRACKERSTATE) { | ||
| 340 | format = mode; | ||
| 341 | mode = TASK_STATS_TPB; | ||
| 342 | } | ||
| 343 | |||
| 344 | if (mode == TASK_STATS_TPB) { | ||
| 345 | #ifdef WANT_COMPRESSION_GZIP | ||
| 346 | ws->request[ws->request_size - 1] = 0; | ||
| 347 | #ifndef WANT_COMPRESSION_GZIP_ALWAYS | ||
| 348 | if (strstr(read_ptr - 1, "gzip")) { | ||
| 349 | #endif | ||
| 350 | cookie->flag |= STRUCT_HTTP_FLAG_GZIP; | ||
| 351 | format |= TASK_FLAG_GZIP; | ||
| 352 | #ifndef WANT_COMPRESSION_GZIP_ALWAYS | ||
| 353 | } | ||
| 354 | #endif | ||
| 355 | #endif | ||
| 356 | cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED; | ||
| 357 | |||
| 358 | /* Clients waiting for us should not easily timeout */ | ||
| 359 | fdstate_set_expiry(sock, 0); | ||
| 360 | http_interest_set(sock, cookie, 0); | ||
| 361 | |||
| 362 | /* Everything this connection needs has been written, so it can be given | ||
| 363 | a home of its own and only then be made reachable by a worker thread */ | ||
| 364 | if (!http_adopt(sock)) | ||
| 365 | HTTPERROR_500; | ||
| 366 | fullscrape_deliver(sock, format); | ||
| 367 | return ws->reply_size = -2; | ||
| 368 | } | ||
| 369 | #endif | ||
| 370 | |||
| 371 | /* default format for now */ | ||
| 372 | if ((mode & TASK_CLASS_MASK) == TASK_STATS) { | ||
| 373 | |||
| 374 | cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK; | ||
| 375 | |||
| 376 | /* Complex stats also include expensive memory debugging tools */ | ||
| 377 | fdstate_set_expiry(sock, 0); | ||
| 378 | |||
| 379 | if (!http_adopt(sock)) | ||
| 380 | HTTPERROR_500; | ||
| 381 | stats_deliver(sock, mode); | ||
| 382 | return ws->reply_size = -2; | ||
| 383 | } | ||
| 384 | |||
| 385 | /* Simple stats can be answerred immediately */ | ||
| 386 | reply = ws->reply; | ||
| 387 | return_stats_for_tracker(&reply, ws->outbuf + G_OUTBUF_SIZE, mode, 0); | ||
| 388 | return ws->reply_size = reply - ws->reply; | ||
| 389 | } | ||
| 390 | |||
| 391 | #ifdef WANT_MODEST_FULLSCRAPES | ||
| 392 | static pthread_mutex_t g_modest_fullscrape_mutex = PTHREAD_MUTEX_INITIALIZER; | ||
| 393 | static ot_vector g_modest_fullscrape_timeouts; | ||
| 394 | typedef struct { | ||
| 395 | ot_ip6 ip; | ||
| 396 | ot_time last_fullscrape; | ||
| 397 | } ot_scrape_log; | ||
| 398 | #endif | ||
| 399 | |||
| 400 | #ifdef WANT_FULLSCRAPE | ||
| 401 | static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct *ws) { | ||
| 402 | struct http_data *cookie = fdstate_cookie(sock); | ||
| 403 | int format = 0; | ||
| 404 | |||
| 405 | #ifdef WANT_MODEST_FULLSCRAPES | ||
| 406 | { | ||
| 407 | ot_scrape_log this_peer, *new_peer; | ||
| 408 | int exactmatch; | ||
| 409 | memcpy(this_peer.ip, cookie->ip, sizeof(ot_ip6)); | ||
| 410 | this_peer.last_fullscrape = time_now_seconds(); | ||
| 411 | pthread_mutex_lock(&g_modest_fullscrape_mutex); | ||
| 412 | new_peer = vector_find_or_insert(&g_modest_fullscrape_timeouts, &this_peer, sizeof(ot_scrape_log), sizeof(ot_ip6), &exactmatch); | ||
| 413 | if (!new_peer) { | ||
| 414 | pthread_mutex_unlock(&g_modest_fullscrape_mutex); | ||
| 415 | HTTPERROR_500; | ||
| 416 | } | ||
| 417 | if (exactmatch && (this_peer.last_fullscrape - new_peer->last_fullscrape) < OT_MODEST_PEER_TIMEOUT) { | ||
| 418 | pthread_mutex_unlock(&g_modest_fullscrape_mutex); | ||
| 419 | HTTPERROR_402_NOTMODEST; | ||
| 420 | } | ||
| 421 | memcpy(new_peer, &this_peer, sizeof(ot_scrape_log)); | ||
| 422 | pthread_mutex_unlock(&g_modest_fullscrape_mutex); | ||
| 423 | } | ||
| 424 | #endif | ||
| 425 | |||
| 426 | |||
| 427 | #if defined(WANT_COMPRESSION_GZIP) || defined(WANT_COMPRESSION_ZSTD) | ||
| 428 | ws->request[ws->request_size - 1] = 0; | ||
| 429 | #ifdef WANT_COMPRESSION_GZIP | ||
| 430 | if (strstr(ws->request, "gzip")) { | ||
| 431 | cookie->flag |= STRUCT_HTTP_FLAG_GZIP; | ||
| 432 | format |= TASK_FLAG_GZIP; | ||
| 433 | } | ||
| 434 | #endif | ||
| 435 | #ifdef WANT_COMPRESSION_ZSTD | ||
| 436 | if (strstr(ws->request, "zstd")) { | ||
| 437 | cookie->flag |= STRUCT_HTTP_FLAG_ZSTD; | ||
| 438 | format |= TASK_FLAG_ZSTD; | ||
| 439 | } | ||
| 440 | #endif | ||
| 441 | |||
| 442 | #if defined(WANT_COMPRESSION_ZSTD) && defined(WANT_COMPRESSION_ZSTD_ALWAYS) | ||
| 443 | cookie->flag |= STRUCT_HTTP_FLAG_ZSTD; | ||
| 444 | format |= TASK_FLAG_ZSTD; | ||
| 445 | #endif | ||
| 446 | |||
| 447 | #if defined(WANT_COMPRESSION_GZIP) && defined(WANT_COMPRESSION_GZIP_ALWAYS) | ||
| 448 | cookie->flag |= STRUCT_HTTP_FLAG_GZIP; | ||
| 449 | format |= TASK_FLAG_GZIP; | ||
| 450 | #endif | ||
| 451 | #endif | ||
| 452 | |||
| 453 | stats_issue_event(ws, EVENT_FULLSCRAPE_REQUEST, 0, (uintptr_t)cookie->ip); | ||
| 454 | |||
| 455 | #ifdef _DEBUG_HTTPERROR | ||
| 456 | fprintf(stderr, "%s", ws->debugbuf); | ||
| 457 | #endif | ||
| 458 | |||
| 459 | /* Pass this task to the worker thread */ | ||
| 460 | cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED; | ||
| 461 | /* Clients waiting for us should not easily timeout */ | ||
| 462 | fdstate_set_expiry(sock, 0); | ||
| 463 | http_interest_set(sock, cookie, 0); | ||
| 464 | |||
| 465 | if (!http_adopt(sock)) | ||
| 466 | HTTPERROR_500; | ||
| 467 | fullscrape_deliver(sock, format); | ||
| 468 | return ws->reply_size = -2; | ||
| 469 | } | ||
| 470 | #endif | ||
| 471 | |||
| 472 | static int compare_hash(const void *left, const void *right) { return memcmp(left, right, sizeof(ot_hash)); } | ||
| 473 | |||
| 474 | static ssize_t http_handle_scrape(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) { | ||
| 475 | static const ot_keywords keywords_scrape[] = {{"info_hash", 1}, {NULL, -3}}; | ||
| 476 | |||
| 477 | ot_hash *multiscrape_buf = (ot_hash *)ws->request; | ||
| 478 | char *reply = ws->reply; | ||
| 479 | int scanon = 1; | ||
| 480 | size_t i, unique, numwant = 0; | ||
| 481 | |||
| 482 | /* This is to hack around stupid clients that send "scrape ?info_hash" */ | ||
| 483 | if (read_ptr[-1] != '?') { | ||
| 484 | while ((*read_ptr != '?') && (*read_ptr != '\n')) | ||
| 485 | ++read_ptr; | ||
| 486 | if (*read_ptr == '\n') | ||
| 487 | HTTPERROR_400_PARAM; | ||
| 488 | ++read_ptr; | ||
| 489 | } | ||
| 490 | |||
| 491 | while (scanon) { | ||
| 492 | switch (urlencoded_match_keyword(keywords_scrape, &read_ptr, SCAN_SEARCHPATH_PARAM)) { | ||
| 493 | case -2: | ||
| 494 | scanon = 0; | ||
| 495 | break; /* TERMINATOR */ | ||
| 496 | default: | ||
| 497 | HTTPERROR_400_PARAM; /* PARSE ERROR */ | ||
| 498 | case -3: | ||
| 499 | urlencoded_skip_value(&read_ptr); | ||
| 500 | break; | ||
| 501 | case 1: /* matched "info_hash" */ | ||
| 502 | /* ignore this, when we have less than 20 bytes */ | ||
| 503 | if (urlencoded_parse(&read_ptr, (char *)(multiscrape_buf + numwant++), SCAN_SEARCHPATH_VALUE) != (ssize_t)sizeof(ot_hash)) | ||
| 504 | HTTPERROR_400_PARAM; | ||
| 505 | break; | ||
| 506 | } | ||
| 507 | } | ||
| 508 | |||
| 509 | /* No info_hash found? Inform user */ | ||
| 510 | if (!numwant) | ||
| 511 | HTTPERROR_400_PARAM; | ||
| 512 | |||
| 513 | /* Limit number of hashes to process */ | ||
| 514 | if (numwant > OT_MAXMULTISCRAPE_COUNT) | ||
| 515 | numwant = OT_MAXMULTISCRAPE_COUNT; | ||
| 516 | |||
| 517 | /* A bencoded dictionary has its keys in order and names each one once, so | ||
| 518 | the answer cannot just follow the order the client asked in */ | ||
| 519 | qsort(multiscrape_buf, numwant, sizeof(ot_hash), compare_hash); | ||
| 520 | for (i = unique = 1; i < numwant; ++i) | ||
| 521 | if (memcmp(multiscrape_buf[i], multiscrape_buf[unique - 1], sizeof(ot_hash))) | ||
| 522 | memcpy(multiscrape_buf[unique++], multiscrape_buf[i], sizeof(ot_hash)); | ||
| 523 | numwant = unique; | ||
| 524 | |||
| 525 | /* Enough for http header + whole scrape string */ | ||
| 526 | return_tcp_scrape_for_torrent((const ot_hash *)multiscrape_buf, numwant, &reply, ws->outbuf + G_OUTBUF_SIZE); | ||
| 527 | ws->reply_size = reply - ws->reply; | ||
| 528 | stats_issue_event(ws, EVENT_SCRAPE, FLAG_TCP, ws->reply_size); | ||
| 529 | return ws->reply_size; | ||
| 530 | } | ||
| 531 | |||
| 532 | #ifdef WANT_LOG_NUMWANT | ||
| 533 | unsigned long long numwants[201]; | ||
| 534 | #endif | ||
| 535 | |||
| 536 | #if defined(WANT_KEEPALIVE) || defined(WANT_IP_FROM_PROXY) | ||
| 537 | static char *http_header(char *data, size_t byte_count, const char *header) { | ||
| 538 | size_t i; | ||
| 539 | long sl = strlen(header); | ||
| 540 | for (i = 0; i + sl + 2 < byte_count; ++i) { | ||
| 541 | if (data[i] != '\n' || data[i + sl + 1] != ':') | ||
| 542 | continue; | ||
| 543 | if (strncasecmp(data + i + 1, header, sl)) | ||
| 544 | continue; | ||
| 545 | data += i + sl + 2; | ||
| 546 | while (*data == ' ' || *data == '\t') | ||
| 547 | ++data; | ||
| 548 | return data; | ||
| 549 | } | ||
| 550 | return 0; | ||
| 551 | } | ||
| 552 | #endif | ||
| 553 | |||
| 554 | static ot_keywords keywords_announce[] = {{"port", 1}, {"left", 2}, {"event", 3}, {"numwant", 4}, {"compact", 5}, {"compact6", 5}, {"info_hash", 6}, | ||
| 555 | #ifdef WANT_IP_FROM_QUERY_STRING | ||
| 556 | {"ip", 7}, | ||
| 557 | #endif | ||
| 558 | {"peer_id", 8}, {NULL, -3}}; | ||
| 559 | static ot_keywords keywords_announce_event[] = {{"completed", 1}, {"stopped", 2}, {NULL, -3}}; | ||
| 560 | |||
| 561 | /* For the replies that are one fixed string and nothing else, where the only | ||
| 562 | thing the caller wants back is how long it turned out */ | ||
| 563 | static ssize_t reply_literal(struct ot_workstruct *ws, const char *text) { | ||
| 564 | char *reply = ws->reply; | ||
| 565 | |||
| 566 | fmt_bytes(&reply, ws->outbuf + G_OUTBUF_SIZE, text, strlen(text)); | ||
| 567 | return reply - ws->reply; | ||
| 568 | } | ||
| 569 | |||
| 570 | static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) { | ||
| 571 | size_t numwant; | ||
| 572 | int64_t tmp; | ||
| 573 | int scanon; | ||
| 574 | unsigned short port = 0; | ||
| 575 | char *write_ptr; | ||
| 576 | ssize_t len; | ||
| 577 | struct http_data *cookie = fdstate_cookie(sock); | ||
| 578 | |||
| 579 | /* This is to hack around stupid clients that send "announce ?info_hash" */ | ||
| 580 | if (read_ptr[-1] != '?') { | ||
| 581 | while ((*read_ptr != '?') && (*read_ptr != '\n')) | ||
| 582 | ++read_ptr; | ||
| 583 | if (*read_ptr == '\n') | ||
| 584 | HTTPERROR_400_PARAM; | ||
| 585 | ++read_ptr; | ||
| 586 | } | ||
| 587 | |||
| 588 | #ifdef WANT_IP_FROM_PROXY | ||
| 589 | if (accesslist_is_blessed(cookie->ip, OT_PERMISSION_MAY_PROXY)) { | ||
| 590 | ot_ip6 proxied_ip; | ||
| 591 | char *fwd = http_header(ws->request, ws->header_size, "x-forwarded-for"); | ||
| 592 | if (fwd && ip_parse(fwd, proxied_ip)) { | ||
| 593 | OT_SETIP(ws->peer, proxied_ip); | ||
| 594 | } else | ||
| 595 | OT_SETIP(ws->peer, cookie->ip); | ||
| 596 | } else | ||
| 597 | #endif | ||
| 598 | OT_SETIP(ws->peer, cookie->ip); | ||
| 599 | |||
| 600 | ws->peer_id = NULL; | ||
| 601 | ws->hash = NULL; | ||
| 602 | |||
| 603 | OT_SETPORT(ws->peer, &port); | ||
| 604 | OT_PEERFLAG(ws->peer) = 0; | ||
| 605 | numwant = 50; | ||
| 606 | scanon = 1; | ||
| 607 | |||
| 608 | while (scanon) { | ||
| 609 | switch (urlencoded_match_keyword(keywords_announce, &read_ptr, SCAN_SEARCHPATH_PARAM)) { | ||
| 610 | case -2: | ||
| 611 | scanon = 0; | ||
| 612 | break; /* TERMINATOR */ | ||
| 613 | case -1: | ||
| 614 | HTTPERROR_400_PARAM; /* PARSE ERROR */ | ||
| 615 | case -3: | ||
| 616 | urlencoded_skip_value(&read_ptr); | ||
| 617 | break; | ||
| 618 | case 1: /* matched "port" */ | ||
| 619 | len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE); | ||
| 620 | if ((len <= 0) || scan_int(write_ptr, len, &tmp) || (tmp > 0xffff)) | ||
| 621 | HTTPERROR_400_PARAM; | ||
| 622 | port = htons((uint16_t)tmp); | ||
| 623 | OT_SETPORT(&ws->peer, &port); | ||
| 624 | break; | ||
| 625 | case 2: /* matched "left" */ | ||
| 626 | if ((len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0) | ||
| 627 | HTTPERROR_400_PARAM; | ||
| 628 | if (scan_int(write_ptr, len, &tmp)) | ||
| 629 | tmp = 0; | ||
| 630 | if (!tmp) | ||
| 631 | OT_PEERFLAG(&ws->peer) |= PEER_FLAG_SEEDING; | ||
| 632 | break; | ||
| 633 | case 3: /* matched "event" */ | ||
| 634 | switch (urlencoded_match_keyword(keywords_announce_event, &read_ptr, SCAN_SEARCHPATH_VALUE)) { | ||
| 635 | case -1: | ||
| 636 | HTTPERROR_400_PARAM; | ||
| 637 | case 1: /* matched "completed" */ | ||
| 638 | OT_PEERFLAG(&ws->peer) |= PEER_FLAG_COMPLETED; | ||
| 639 | break; | ||
| 640 | case 2: /* matched "stopped" */ | ||
| 641 | OT_PEERFLAG(&ws->peer) |= PEER_FLAG_STOPPED; | ||
| 642 | break; | ||
| 643 | default: | ||
| 644 | break; | ||
| 645 | } | ||
| 646 | break; | ||
| 647 | case 4: /* matched "numwant" */ | ||
| 648 | len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE); | ||
| 649 | if ((len <= 0) || scan_int(write_ptr, len, &tmp)) | ||
| 650 | HTTPERROR_400_PARAM; | ||
| 651 | if (tmp < 0) | ||
| 652 | tmp = 50; | ||
| 653 | if (tmp > 200) | ||
| 654 | tmp = 200; | ||
| 655 | numwant = (size_t)tmp; | ||
| 656 | break; | ||
| 657 | case 5: /* matched "compact" */ | ||
| 658 | len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE); | ||
| 659 | if ((len <= 0) || scan_int(write_ptr, len, &tmp)) | ||
| 660 | HTTPERROR_400_PARAM; | ||
| 661 | if (!tmp) | ||
| 662 | HTTPERROR_400_COMPACT; | ||
| 663 | break; | ||
| 664 | case 6: /* matched "info_hash" */ | ||
| 665 | if (ws->hash) | ||
| 666 | HTTPERROR_400_DOUBLEHASH; | ||
| 667 | /* ignore this, when we have less than 20 bytes */ | ||
| 668 | if (urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE) != 20) | ||
| 669 | HTTPERROR_400_PARAM; | ||
| 670 | ws->hash = (ot_hash *)write_ptr; | ||
| 671 | break; | ||
| 672 | #ifdef WANT_IP_FROM_QUERY_STRING | ||
| 673 | case 7: /* matched "ip" */ | ||
| 674 | { | ||
| 675 | char *tmp_buf1 = ws->reply, *tmp_buf2 = ws->reply + 16; | ||
| 676 | len = urlencoded_parse(&read_ptr, tmp_buf2, SCAN_SEARCHPATH_VALUE); | ||
| 677 | tmp_buf2[len] = 0; | ||
| 678 | if ((len <= 0) || ip_parse(tmp_buf2, tmp_buf1) != (size_t)len) | ||
| 679 | HTTPERROR_400_PARAM; | ||
| 680 | OT_SETIP(&ws->peer, tmp_buf1); | ||
| 681 | } break; | ||
| 682 | #endif | ||
| 683 | case 8: /* matched "peer_id" */ | ||
| 684 | /* ignore this, when we have less than 20 bytes */ | ||
| 685 | if (urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE) != 20) | ||
| 686 | HTTPERROR_400_PARAM; | ||
| 687 | ws->peer_id = write_ptr; | ||
| 688 | break; | ||
| 689 | } | ||
| 690 | } | ||
| 691 | |||
| 692 | #ifdef WANT_LOG_NUMWANT | ||
| 693 | numwants[numwant]++; | ||
| 694 | #endif | ||
| 695 | |||
| 696 | /* XXX DEBUG | ||
| 697 | stats_issue_event(ws, EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ws->reply ); | ||
| 698 | */ | ||
| 699 | |||
| 700 | /* Scanned whole query string */ | ||
| 701 | if (!ws->hash) | ||
| 702 | return ws->reply_size = reply_literal(ws, "d14:failure reason80:Your client forgot to send your torrent's info_hash. Please upgrade your client.e"); | ||
| 703 | |||
| 704 | if (OT_PEERFLAG(&ws->peer) & PEER_FLAG_STOPPED) | ||
| 705 | ws->reply_size = remove_peer_from_torrent(FLAG_TCP, ws); | ||
| 706 | else | ||
| 707 | ws->reply_size = add_peer_to_torrent_and_return_peers(FLAG_TCP, ws, numwant); | ||
| 708 | |||
| 709 | stats_issue_event(ws, EVENT_ANNOUNCE, FLAG_TCP, ws->reply_size); | ||
| 710 | return ws->reply_size; | ||
| 711 | } | ||
| 712 | |||
| 713 | ssize_t http_handle_request(const int64_t sock, struct ot_workstruct *ws) { | ||
| 714 | ssize_t reply_off, len; | ||
| 715 | char *read_ptr = ws->request, *write_ptr; | ||
| 716 | |||
| 717 | #ifdef _DEBUG_HTTPERROR | ||
| 718 | reply_off = ws->request_size; | ||
| 719 | if (ws->request_size >= G_DEBUGBUF_SIZE) | ||
| 720 | reply_off = G_DEBUGBUF_SIZE - 1; | ||
| 721 | memcpy(ws->debugbuf, ws->request, reply_off); | ||
| 722 | ws->debugbuf[reply_off] = 0; | ||
| 723 | #endif | ||
| 724 | |||
| 725 | /* Tell subroutines where to put reply data */ | ||
| 726 | ws->reply = ws->outbuf + SUCCESS_HTTP_HEADER_LENGTH; | ||
| 727 | |||
| 728 | /* Find out if the client wants to keep this connection alive, before any | ||
| 729 | subroutine gets to answer: an error reply finishes with the connection | ||
| 730 | on its way out, and ws outlives the connection it was serving */ | ||
| 731 | ws->keep_alive = 0; | ||
| 732 | #ifdef WANT_KEEPALIVE | ||
| 733 | if ((write_ptr = http_header(ws->request, ws->header_size, "connection")) && (*write_ptr == 'K' || *write_ptr == 'k')) | ||
| 734 | ws->keep_alive = 1; | ||
| 735 | #endif | ||
| 736 | { | ||
| 737 | struct http_data *connection = fdstate_cookie(sock); | ||
| 738 | if (connection) { | ||
| 739 | if (ws->keep_alive) | ||
| 740 | connection->flag |= STRUCT_HTTP_FLAG_KEEPALIVE; | ||
| 741 | else | ||
| 742 | connection->flag &= ~STRUCT_HTTP_FLAG_KEEPALIVE; | ||
| 743 | } | ||
| 744 | } | ||
| 745 | |||
| 746 | /* This one implicitely tests strlen < 5, too -- remember, it is \n terminated */ | ||
| 747 | if (memcmp(read_ptr, "GET /", 5)) | ||
| 748 | HTTPERROR_400; | ||
| 749 | |||
| 750 | /* Skip leading '/' */ | ||
| 751 | for (read_ptr += 4; *read_ptr == '/'; ++read_ptr) | ||
| 752 | ; | ||
| 753 | |||
| 754 | /* Try to parse the request. | ||
| 755 | In reality we abandoned requiring the url to be correct. This now | ||
| 756 | only decodes url encoded characters, we check for announces and | ||
| 757 | scrapes by looking for "a*" or "sc" */ | ||
| 758 | len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_PATH); | ||
| 759 | |||
| 760 | /* If parsing returned an error, leave with not found */ | ||
| 761 | if (g_redirecturl && (len == -2)) | ||
| 762 | HTTPERROR_302; | ||
| 763 | if (len <= 0) | ||
| 764 | HTTPERROR_404; | ||
| 765 | |||
| 766 | /* This is the hardcore match for announce*/ | ||
| 767 | if ((*write_ptr == 'a') || (*write_ptr == '?')) | ||
| 768 | http_handle_announce(sock, ws, read_ptr); | ||
| 769 | #ifdef WANT_FULLSCRAPE | ||
| 770 | else if (!memcmp(write_ptr, "scrape HTTP/", 12)) | ||
| 771 | http_handle_fullscrape(sock, ws); | ||
| 772 | #endif | ||
| 773 | /* This is the hardcore match for scrape */ | ||
| 774 | else if (!memcmp(write_ptr, "sc", 2)) | ||
| 775 | http_handle_scrape(sock, ws, read_ptr); | ||
| 776 | /* All the rest is matched the standard way */ | ||
| 777 | else if (len == g_stats_path_len && !memcmp(write_ptr, g_stats_path, len)) | ||
| 778 | http_handle_stats(sock, ws, read_ptr); | ||
| 779 | else | ||
| 780 | HTTPERROR_404; | ||
| 781 | |||
| 782 | /* If routines handled sending themselves, just return */ | ||
| 783 | if (ws->reply_size == -2) | ||
| 784 | return 0; | ||
| 785 | /* If routine failed, let http error take over */ | ||
| 786 | if (ws->reply_size <= 0) | ||
| 787 | HTTPERROR_500; | ||
| 788 | |||
| 789 | /* This one is rather ugly, so I take you step by step through it. | ||
| 790 | |||
| 791 | 1. In order to avoid having two buffers, one for header and one for content, we allow all above functions from trackerlogic to | ||
| 792 | write to a fixed location, leaving SUCCESS_HTTP_HEADER_LENGTH bytes in our work buffer, which is enough for the static string | ||
| 793 | plus dynamic space needed to expand our Content-Length value. We reserve SUCCESS_HTTP_SIZE_OFF for its expansion and calculate | ||
| 794 | the space NOT needed to expand in reply_off | ||
| 795 | */ | ||
| 796 | reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf(ws->outbuf, 0, "%zd", ws->reply_size); | ||
| 797 | ws->reply = ws->outbuf + reply_off; | ||
| 798 | |||
| 799 | /* 2. Now we write the header so that its terminating '\0' lands exactly one byte before the content starts. The bound is the | ||
| 800 | space step 1 reserved, so the '\0' falls where step 3 expects it. Complete packet size is increased by size of header plus | ||
| 801 | one byte '\n', we will copy over '\0' in next step */ | ||
| 802 | ws->reply_size += 1 + snprintf(ws->reply, (size_t)(SUCCESS_HTTP_HEADER_LENGTH - reply_off), | ||
| 803 | "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r", ws->reply_size); | ||
| 804 | |||
| 805 | /* 3. Finally we join both blocks neatly */ | ||
| 806 | ws->outbuf[SUCCESS_HTTP_HEADER_LENGTH - 1] = '\n'; | ||
| 807 | |||
| 808 | http_senddata(sock, ws); | ||
| 809 | return ws->reply_size; | ||
| 810 | } | ||
diff --git a/src/ot_http.h b/src/ot_http.h new file mode 100644 index 0000000..fb56c64 --- /dev/null +++ b/src/ot_http.h | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_HTTP_H__ | ||
| 5 | #define OT_HTTP_H__ | ||
| 6 | |||
| 7 | #include <stdint.h> | ||
| 8 | #include <sys/types.h> | ||
| 9 | #include <sys/uio.h> | ||
| 10 | |||
| 11 | #include "ot_batch.h" | ||
| 12 | #include "ot_event.h" | ||
| 13 | #include "ot_buf.h" | ||
| 14 | #include "trackerlogic.h" | ||
| 15 | |||
| 16 | typedef enum { | ||
| 17 | STRUCT_HTTP_FLAG_WAITINGFORTASK = 1, | ||
| 18 | STRUCT_HTTP_FLAG_GZIP = 2, | ||
| 19 | STRUCT_HTTP_FLAG_BZIP2 = 4, | ||
| 20 | STRUCT_HTTP_FLAG_ZSTD = 8, | ||
| 21 | STRUCT_HTTP_FLAG_CHUNKED = 16, | ||
| 22 | STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER = 32, | ||
| 23 | STRUCT_HTTP_FLAG_KEEPALIVE = 64, | ||
| 24 | STRUCT_HTTP_FLAG_ONSTACK = 128 | ||
| 25 | } STRUCT_HTTP_FLAG; | ||
| 26 | |||
| 27 | struct http_data { | ||
| 28 | ot_buf request; | ||
| 29 | int interest; /* EVENT_IN and EVENT_OUT as asked for */ | ||
| 30 | ot_batch *batch; | ||
| 31 | ot_ip6 ip; | ||
| 32 | STRUCT_HTTP_FLAG flag; | ||
| 33 | }; | ||
| 34 | |||
| 35 | /* What this connection is waiting for: EVENT_IN, EVENT_OUT, or nothing */ | ||
| 36 | void http_interest_set(const int64_t s, struct http_data *cookie, int what); | ||
| 37 | |||
| 38 | /* Close a connection and release everything that belongs to it */ | ||
| 39 | void http_close(const int64_t s); | ||
| 40 | |||
| 41 | /* Promote a stack cookie to a heap one, for persistence and for handing it | ||
| 42 | over to the event loop or to another thread. Does nothing to one that is | ||
| 43 | already on the heap. Returns the connection's state, or NULL. */ | ||
| 44 | struct http_data *http_adopt(const int64_t s); | ||
| 45 | |||
| 46 | ssize_t http_handle_request(const int64_t s, struct ot_workstruct *ws); | ||
| 47 | 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_issue_error(const int64_t s, struct ot_workstruct *ws, int code); | ||
| 49 | |||
| 50 | extern const char *g_stats_path; | ||
| 51 | extern ssize_t g_stats_path_len; | ||
| 52 | |||
| 53 | #endif | ||
diff --git a/src/ot_iovec.c b/src/ot_iovec.c new file mode 100644 index 0000000..328f160 --- /dev/null +++ b/src/ot_iovec.c | |||
| @@ -0,0 +1,87 @@ | |||
| 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 <stdlib.h> | ||
| 6 | #include <sys/uio.h> | ||
| 7 | |||
| 8 | /* Opentracker */ | ||
| 9 | #include "ot_iovec.h" | ||
| 10 | |||
| 11 | void *iovec_new_chunk(size_t *iovec_entries, struct iovec **iovector, size_t new_alloc) { | ||
| 12 | void *new_data; | ||
| 13 | size_t new_entries = 1 + *iovec_entries; | ||
| 14 | struct iovec *new_vec = realloc(*iovector, new_entries * sizeof(struct iovec)); | ||
| 15 | |||
| 16 | if (!new_vec) | ||
| 17 | return NULL; | ||
| 18 | |||
| 19 | *iovector = new_vec; | ||
| 20 | |||
| 21 | /* Only allocate after we have a place to store the pointer */ | ||
| 22 | new_data = malloc(new_alloc); | ||
| 23 | if (!new_data) | ||
| 24 | return NULL; | ||
| 25 | |||
| 26 | new_vec[new_entries - 1].iov_base = new_data; | ||
| 27 | new_vec[new_entries - 1].iov_len = new_alloc; | ||
| 28 | |||
| 29 | ++*iovec_entries; | ||
| 30 | return new_data; | ||
| 31 | } | ||
| 32 | |||
| 33 | void *iovec_append(size_t *iovec_entries, struct iovec **iovector, struct iovec *append_iovector) { | ||
| 34 | size_t new_entries = *iovec_entries + 1; | ||
| 35 | struct iovec *new_vec = realloc(*iovector, new_entries * sizeof(struct iovec)); | ||
| 36 | if (!new_vec) | ||
| 37 | return NULL; | ||
| 38 | |||
| 39 | /* Take over data from appended iovec */ | ||
| 40 | new_vec[*iovec_entries].iov_base = append_iovector->iov_base; | ||
| 41 | new_vec[*iovec_entries].iov_len = append_iovector->iov_len; | ||
| 42 | |||
| 43 | append_iovector->iov_base = NULL; | ||
| 44 | append_iovector->iov_len = 0; | ||
| 45 | |||
| 46 | *iovector = new_vec; | ||
| 47 | *iovec_entries = new_entries; | ||
| 48 | |||
| 49 | return new_vec; | ||
| 50 | } | ||
| 51 | |||
| 52 | void iovec_free(size_t *iovec_entries, struct iovec **iovector) { | ||
| 53 | size_t i; | ||
| 54 | for (i = 0; i < *iovec_entries; ++i) | ||
| 55 | free(((*iovector)[i]).iov_base); | ||
| 56 | free(*iovector); | ||
| 57 | *iovector = NULL; | ||
| 58 | *iovec_entries = 0; | ||
| 59 | } | ||
| 60 | |||
| 61 | void iovec_trim_last(size_t *iovec_entries, struct iovec **iovector, void *last_ptr) { | ||
| 62 | if (*iovec_entries) { | ||
| 63 | char *base = (char *)((*iovector)[*iovec_entries - 1]).iov_base; | ||
| 64 | size_t new_alloc = ((char *)last_ptr) - base; | ||
| 65 | |||
| 66 | ((*iovector)[*iovec_entries - 1]).iov_base = realloc(base, new_alloc); | ||
| 67 | ((*iovector)[*iovec_entries - 1]).iov_len = new_alloc; | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | void *iovec_next_chunk(size_t *iovec_entries, struct iovec **iovector, void *last_ptr, size_t new_alloc) { | ||
| 72 | void *new_data; | ||
| 73 | |||
| 74 | iovec_trim_last(iovec_entries, iovector, last_ptr); | ||
| 75 | |||
| 76 | if (!(new_data = iovec_new_chunk(iovec_entries, iovector, new_alloc))) | ||
| 77 | iovec_free(iovec_entries, iovector); | ||
| 78 | |||
| 79 | return new_data; | ||
| 80 | } | ||
| 81 | |||
| 82 | size_t iovec_length(const size_t *iovec_entries, const struct iovec **iovector) { | ||
| 83 | size_t i, length = 0; | ||
| 84 | for (i = 0; i < *iovec_entries; ++i) | ||
| 85 | length += ((*iovector)[i]).iov_len; | ||
| 86 | return length; | ||
| 87 | } | ||
diff --git a/src/ot_iovec.h b/src/ot_iovec.h new file mode 100644 index 0000000..4a8abbb --- /dev/null +++ b/src/ot_iovec.h | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_IOVEC_H__ | ||
| 5 | #define OT_IOVEC_H__ | ||
| 6 | |||
| 7 | #include <sys/uio.h> | ||
| 8 | |||
| 9 | void *iovec_new_chunk(size_t *iovec_entries, struct iovec **iovector, size_t new_alloc); | ||
| 10 | void *iovec_append(size_t *iovec_entries, struct iovec **iovector, struct iovec *append_iovector); | ||
| 11 | void iovec_trim_last(size_t *iovec_entries, struct iovec **iovector, void *last_ptr); | ||
| 12 | void iovec_free(size_t *iovec_entries, struct iovec **iovector); | ||
| 13 | |||
| 14 | size_t iovec_length(const size_t *iovec_entries, const struct iovec **iovector); | ||
| 15 | |||
| 16 | void *iovec_next_chunk(size_t *iovec_entries, struct iovec **iovector, void *last_ptr, size_t new_alloc); | ||
| 17 | |||
| 18 | #endif | ||
diff --git a/src/ot_ip.c b/src/ot_ip.c new file mode 100644 index 0000000..9c76290 --- /dev/null +++ b/src/ot_ip.c | |||
| @@ -0,0 +1,199 @@ | |||
| 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 <stdint.h> | ||
| 6 | #include <string.h> | ||
| 7 | |||
| 8 | /* Opentracker */ | ||
| 9 | #include "ot_hex.h" | ||
| 10 | #include "ot_ip.h" | ||
| 11 | |||
| 12 | const char ip_v4mapped_prefix[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}; | ||
| 13 | |||
| 14 | /* The dotted quad is only ever reached through the v4 mapped form, so both | ||
| 15 | halves of it stay in here */ | ||
| 16 | |||
| 17 | static size_t ip4_format(char *dest, const uint8_t *ip) { | ||
| 18 | char *d = dest; | ||
| 19 | int i; | ||
| 20 | |||
| 21 | for (i = 0; i < 4; ++i) { | ||
| 22 | unsigned int octet = ip[i]; | ||
| 23 | |||
| 24 | if (i) | ||
| 25 | *d++ = '.'; | ||
| 26 | if (octet >= 100) | ||
| 27 | *d++ = (char)('0' + octet / 100); | ||
| 28 | if (octet >= 10) | ||
| 29 | *d++ = (char)('0' + (octet / 10) % 10); | ||
| 30 | *d++ = (char)('0' + octet % 10); | ||
| 31 | } | ||
| 32 | |||
| 33 | return (size_t)(d - dest); | ||
| 34 | } | ||
| 35 | |||
| 36 | static size_t ip4_scan(const char *src, uint8_t ip[4]) { | ||
| 37 | uint8_t octets[4]; | ||
| 38 | const char *s = src; | ||
| 39 | int i; | ||
| 40 | |||
| 41 | for (i = 0; i < 4; ++i) { | ||
| 42 | unsigned int value = 0; | ||
| 43 | int digits = 0; | ||
| 44 | |||
| 45 | if (i && *s++ != '.') | ||
| 46 | return 0; | ||
| 47 | |||
| 48 | /* Leading zeros are tolerated, a value beyond one octet is not */ | ||
| 49 | while (*s >= '0' && *s <= '9' && digits < 4) { | ||
| 50 | value = value * 10 + (unsigned int)(*s++ - '0'); | ||
| 51 | ++digits; | ||
| 52 | } | ||
| 53 | if (!digits || value > 255) | ||
| 54 | return 0; | ||
| 55 | |||
| 56 | octets[i] = (uint8_t)value; | ||
| 57 | } | ||
| 58 | |||
| 59 | memcpy(ip, octets, sizeof(octets)); | ||
| 60 | return (size_t)(s - src); | ||
| 61 | } | ||
| 62 | |||
| 63 | static unsigned int group_at(const char *ip, int n) { return ((unsigned int)(uint8_t)ip[2 * n] << 8) | (uint8_t)ip[2 * n + 1]; } | ||
| 64 | |||
| 65 | size_t ip_format(char *dest, const ot_ip6 ip) { | ||
| 66 | static const char hexdigit[] = "0123456789abcdef"; | ||
| 67 | char *d = dest; | ||
| 68 | int best_start = 0, best_len = 0, i = 0; | ||
| 69 | |||
| 70 | if (ip_is_v4mapped(ip)) | ||
| 71 | return ip4_format(dest, (const uint8_t *)ip + sizeof(ip_v4mapped_prefix)); | ||
| 72 | |||
| 73 | /* RFC 5952 4.2: the longest run of zero groups becomes "::", never a run | ||
| 74 | of one, and the first of two equal runs wins */ | ||
| 75 | while (i < 8) { | ||
| 76 | int j = i; | ||
| 77 | |||
| 78 | if (group_at(ip, i)) { | ||
| 79 | ++i; | ||
| 80 | continue; | ||
| 81 | } | ||
| 82 | while (j < 8 && !group_at(ip, j)) | ||
| 83 | ++j; | ||
| 84 | if (j - i > best_len) { | ||
| 85 | best_len = j - i; | ||
| 86 | best_start = i; | ||
| 87 | } | ||
| 88 | i = j; | ||
| 89 | } | ||
| 90 | if (best_len < 2) | ||
| 91 | best_len = 0; | ||
| 92 | |||
| 93 | for (i = 0; i < 8; ++i) { | ||
| 94 | unsigned int group; | ||
| 95 | int shift = 12; | ||
| 96 | |||
| 97 | if (best_len && i == best_start) { | ||
| 98 | *d++ = ':'; | ||
| 99 | if (i + best_len == 8) | ||
| 100 | *d++ = ':'; /* nothing follows, so close the run */ | ||
| 101 | i += best_len - 1; | ||
| 102 | continue; | ||
| 103 | } | ||
| 104 | |||
| 105 | if (i) | ||
| 106 | *d++ = ':'; | ||
| 107 | |||
| 108 | group = group_at(ip, i); | ||
| 109 | while (shift && !(group >> shift)) | ||
| 110 | shift -= 4; /* RFC 5952 4.1: no leading zeros */ | ||
| 111 | for (;; shift -= 4) { | ||
| 112 | *d++ = hexdigit[(group >> shift) & 15]; | ||
| 113 | if (!shift) | ||
| 114 | break; | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | return (size_t)(d - dest); | ||
| 119 | } | ||
| 120 | |||
| 121 | size_t ip_parse(const char *src, ot_ip6 ip) { | ||
| 122 | uint8_t prefix[16], suffix[16]; | ||
| 123 | size_t prefixlen = 0, suffixlen = 0; | ||
| 124 | const char *s = src; | ||
| 125 | int have_gap = 0; | ||
| 126 | int need_colon = 0; | ||
| 127 | |||
| 128 | /* A plain IPv4 address is an address too, stored v4 mapped */ | ||
| 129 | { | ||
| 130 | size_t consumed = ip4_scan(src, prefix); | ||
| 131 | if (consumed) { | ||
| 132 | memcpy(ip, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix)); | ||
| 133 | memcpy(ip + sizeof(ip_v4mapped_prefix), prefix, 4); | ||
| 134 | return consumed; | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | for (;;) { | ||
| 139 | unsigned int value = 0; | ||
| 140 | int digits = 0, nibble; | ||
| 141 | uint8_t *into = have_gap ? suffix : prefix; | ||
| 142 | size_t *fill = have_gap ? &suffixlen : &prefixlen; | ||
| 143 | |||
| 144 | /* RFC 4291 2.2: one run of zero groups may be written "::" */ | ||
| 145 | if (s[0] == ':' && s[1] == ':') { | ||
| 146 | if (have_gap) | ||
| 147 | return 0; | ||
| 148 | have_gap = 1; | ||
| 149 | need_colon = 0; | ||
| 150 | s += 2; | ||
| 151 | continue; | ||
| 152 | } | ||
| 153 | |||
| 154 | if (need_colon) { | ||
| 155 | if (*s != ':') | ||
| 156 | break; | ||
| 157 | ++s; | ||
| 158 | } | ||
| 159 | |||
| 160 | /* RFC 4291 2.2 form 3: the final two groups may be a dotted quad */ | ||
| 161 | if (prefixlen + suffixlen + 4 <= 16) { | ||
| 162 | size_t consumed = ip4_scan(s, into + *fill); | ||
| 163 | if (consumed) { | ||
| 164 | *fill += 4; | ||
| 165 | s += consumed; | ||
| 166 | break; | ||
| 167 | } | ||
| 168 | } | ||
| 169 | |||
| 170 | while (digits < 4 && (nibble = hex_value(*s)) >= 0) { | ||
| 171 | value = value * 16 + (unsigned int)nibble; | ||
| 172 | ++digits; | ||
| 173 | ++s; | ||
| 174 | } | ||
| 175 | if (!digits) { | ||
| 176 | if (need_colon) | ||
| 177 | --s; /* leave the separator we ate for the caller */ | ||
| 178 | break; | ||
| 179 | } | ||
| 180 | |||
| 181 | if (prefixlen + suffixlen + 2 > 16) | ||
| 182 | return 0; | ||
| 183 | |||
| 184 | into[(*fill)++] = (uint8_t)(value >> 8); | ||
| 185 | into[(*fill)++] = (uint8_t)value; | ||
| 186 | need_colon = 1; | ||
| 187 | } | ||
| 188 | |||
| 189 | if (!prefixlen && !suffixlen && !have_gap) | ||
| 190 | return 0; | ||
| 191 | if (!have_gap && prefixlen + suffixlen != 16) | ||
| 192 | return 0; /* without "::" every group has to be spelled out */ | ||
| 193 | |||
| 194 | memset(ip, 0, 16); | ||
| 195 | memcpy(ip, prefix, prefixlen); | ||
| 196 | memcpy(ip + 16 - suffixlen, suffix, suffixlen); | ||
| 197 | |||
| 198 | return (size_t)(s - src); | ||
| 199 | } | ||
diff --git a/src/ot_ip.h b/src/ot_ip.h new file mode 100644 index 0000000..533403e --- /dev/null +++ b/src/ot_ip.h | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_IP_H__ | ||
| 5 | #define OT_IP_H__ | ||
| 6 | |||
| 7 | #include <stddef.h> | ||
| 8 | #include <string.h> | ||
| 9 | |||
| 10 | #include "trackerlogic.h" | ||
| 11 | |||
| 12 | /* opentracker holds every address as sixteen bytes, writing IPv4 as the v4 | ||
| 13 | mapped form of RFC 4291 section 2.5.5.2. */ | ||
| 14 | extern const char ip_v4mapped_prefix[12]; | ||
| 15 | |||
| 16 | static inline int ip_is_v4mapped(const void *ip) { return !memcmp(ip, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix)); } | ||
| 17 | |||
| 18 | /* Read an address and return how many characters it took, or zero if src | ||
| 19 | does not start with one. A plain IPv4 address is accepted and stored in | ||
| 20 | its v4 mapped form. | ||
| 21 | |||
| 22 | The count lets the caller carry on where the address ended, which is how | ||
| 23 | "address:port" and "address/bits" are read. */ | ||
| 24 | size_t ip_parse(const char *src, ot_ip6 ip); | ||
| 25 | |||
| 26 | /* Write an address and return how many characters it took. Nothing is | ||
| 27 | terminated, so a caller may keep appending. A v4 mapped address is written | ||
| 28 | as the plain dotted quad rather than "::ffff:1.2.3.4". */ | ||
| 29 | size_t ip_format(char *dest, const ot_ip6 ip); | ||
| 30 | |||
| 31 | #endif | ||
diff --git a/src/ot_livesync.c b/src/ot_livesync.c new file mode 100644 index 0000000..85577f7 --- /dev/null +++ b/src/ot_livesync.c | |||
| @@ -0,0 +1,267 @@ | |||
| 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 <arpa/inet.h> | ||
| 6 | #include <pthread.h> | ||
| 7 | #include <stdio.h> | ||
| 8 | #include <stdlib.h> | ||
| 9 | #include <string.h> | ||
| 10 | #include <sys/socket.h> | ||
| 11 | #include <sys/types.h> | ||
| 12 | #include <unistd.h> | ||
| 13 | |||
| 14 | /* Opentracker */ | ||
| 15 | #include "ot_accesslist.h" | ||
| 16 | #include "ot_ip.h" | ||
| 17 | #include "ot_socket.h" | ||
| 18 | #include "ot_livesync.h" | ||
| 19 | #include "ot_mutex.h" | ||
| 20 | #include "ot_stats.h" | ||
| 21 | #include "trackerlogic.h" | ||
| 22 | |||
| 23 | #ifdef WANT_SYNC_LIVE | ||
| 24 | |||
| 25 | /* The multicast group, legacy opentrackers joined by default 224.0.23.5, so it | ||
| 26 | stays the default */ | ||
| 27 | static ot_ip6 g_group = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 224, 0, 23, 5}; | ||
| 28 | static uint16_t g_port = LIVESYNC_PORT; | ||
| 29 | |||
| 30 | #define LIVESYNC_INCOMING_BUFFSIZE (256 * 256) | ||
| 31 | |||
| 32 | #define LIVESYNC_OUTGOING_BUFFSIZE_PEERS 1480 | ||
| 33 | #define LIVESYNC_OUTGOING_WATERMARK_PEERS (sizeof(ot_peer) + sizeof(ot_hash)) | ||
| 34 | |||
| 35 | #define LIVESYNC_MAXDELAY 15 /* seconds */ | ||
| 36 | |||
| 37 | enum { OT_SYNC_PEER4, OT_SYNC_PEER6 }; | ||
| 38 | |||
| 39 | /* Forward declaration */ | ||
| 40 | static void *livesync_worker(void *args); | ||
| 41 | |||
| 42 | /* For incoming packets */ | ||
| 43 | static int g_socket_in = -1; | ||
| 44 | |||
| 45 | /* For outgoing packets */ | ||
| 46 | static int g_socket_out = -1; | ||
| 47 | |||
| 48 | static pthread_mutex_t g_outbuf_mutex = PTHREAD_MUTEX_INITIALIZER; | ||
| 49 | typedef struct { | ||
| 50 | uint8_t data[LIVESYNC_OUTGOING_BUFFSIZE_PEERS]; | ||
| 51 | size_t fill; | ||
| 52 | ot_time next_packet_time; | ||
| 53 | } sync_buffer; | ||
| 54 | |||
| 55 | static sync_buffer g_v6_buf; | ||
| 56 | static sync_buffer g_v4_buf; | ||
| 57 | |||
| 58 | static pthread_t thread_id; | ||
| 59 | static int thread_started; | ||
| 60 | static _Atomic int thread_left; | ||
| 61 | void livesync_init(void) { | ||
| 62 | uint32_t packet_type6 = htonl(OT_SYNC_PEER6); | ||
| 63 | uint32_t packet_type4 = htonl(OT_SYNC_PEER4); | ||
| 64 | |||
| 65 | if (g_socket_in == -1) | ||
| 66 | return; | ||
| 67 | |||
| 68 | /* Prepare outgoing peers buffer */ | ||
| 69 | memcpy(g_v6_buf.data, &g_tracker_id, sizeof(g_tracker_id)); | ||
| 70 | memcpy(g_v4_buf.data, &g_tracker_id, sizeof(g_tracker_id)); | ||
| 71 | |||
| 72 | memcpy(g_v6_buf.data + sizeof(g_tracker_id), &packet_type6, sizeof(packet_type6)); | ||
| 73 | memcpy(g_v4_buf.data + sizeof(g_tracker_id), &packet_type4, sizeof(packet_type4)); | ||
| 74 | |||
| 75 | g_v6_buf.fill = sizeof(g_tracker_id) + sizeof(uint32_t); | ||
| 76 | g_v4_buf.fill = sizeof(g_tracker_id) + sizeof(uint32_t); | ||
| 77 | |||
| 78 | g_v6_buf.next_packet_time = time_future_seconds(LIVESYNC_MAXDELAY); | ||
| 79 | g_v4_buf.next_packet_time = time_future_seconds(LIVESYNC_MAXDELAY); | ||
| 80 | |||
| 81 | thread_started = !pthread_create(&thread_id, NULL, livesync_worker, NULL); | ||
| 82 | } | ||
| 83 | |||
| 84 | void livesync_deinit(void) { | ||
| 85 | if (thread_started) { | ||
| 86 | /* Repeats so it interrupts a read already under way */ | ||
| 87 | while (!atomic_load(&thread_left)) { | ||
| 88 | pthread_kill(thread_id, OT_SIGNAL_WAKE); | ||
| 89 | usleep(1000); | ||
| 90 | } | ||
| 91 | pthread_join(thread_id, NULL); | ||
| 92 | thread_started = 0; | ||
| 93 | } | ||
| 94 | |||
| 95 | if (g_socket_in != -1) | ||
| 96 | close(g_socket_in); | ||
| 97 | if (g_socket_out != -1) | ||
| 98 | close(g_socket_out); | ||
| 99 | } | ||
| 100 | |||
| 101 | int livesync_set_group(const ot_ip6 group) { | ||
| 102 | /* v4 multicast is 224.0.0.0/4, and the socket layer has nothing else */ | ||
| 103 | if (!ip_is_v4mapped(group) || (group[12] & 0xf0) != 0xe0) | ||
| 104 | return -1; | ||
| 105 | memcpy(g_group, group, sizeof(ot_ip6)); | ||
| 106 | return 0; | ||
| 107 | } | ||
| 108 | |||
| 109 | void livesync_bind_mcast(ot_ip6 ip, uint16_t port) { | ||
| 110 | if (!ip_is_v4mapped(ip)) | ||
| 111 | exerr("v6 mcast support not yet available."); | ||
| 112 | |||
| 113 | if (g_socket_in != -1) | ||
| 114 | exerr("Error: Livesync listen ip specified twice."); | ||
| 115 | |||
| 116 | g_port = port; | ||
| 117 | |||
| 118 | if ((g_socket_in = socket_mcast_join(g_group, ip, port)) == -1) | ||
| 119 | exerr("Error: Cant bind live sync incoming socket."); | ||
| 120 | |||
| 121 | if ((g_socket_out = socket_mcast_bind(ip, port, 0)) == -1) | ||
| 122 | exerr("Error: Cant bind live sync outgoing socket."); | ||
| 123 | } | ||
| 124 | |||
| 125 | /* Caller MUST hold g_outbuf_mutex. Returns with g_outbuf_mutex unlocked */ | ||
| 126 | static void livesync_issue_peersync(sync_buffer *buf) { | ||
| 127 | char mycopy[LIVESYNC_OUTGOING_BUFFSIZE_PEERS]; | ||
| 128 | size_t fill = buf->fill; | ||
| 129 | |||
| 130 | memcpy(mycopy, buf->data, fill); | ||
| 131 | buf->fill = sizeof(g_tracker_id) + sizeof(uint32_t); | ||
| 132 | buf->next_packet_time = time_future_seconds(LIVESYNC_MAXDELAY); | ||
| 133 | |||
| 134 | /* From now this thread has a local copy of the buffer and | ||
| 135 | has modified the protected element */ | ||
| 136 | pthread_mutex_unlock(&g_outbuf_mutex); | ||
| 137 | |||
| 138 | socket_mcast_send(g_socket_out, mycopy, fill, g_group, g_port); | ||
| 139 | } | ||
| 140 | |||
| 141 | static void livesync_handle_peersync(struct ot_workstruct *ws, size_t peer_size) { | ||
| 142 | size_t off = sizeof(g_tracker_id) + sizeof(uint32_t); | ||
| 143 | |||
| 144 | /* Prepare peer record for v4mapped expansion */ | ||
| 145 | if (peer_size == OT_PEER_SIZE4) | ||
| 146 | memcpy(&ws->peer, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix)); | ||
| 147 | |||
| 148 | /* Now basic sanity checks have been done on the live sync packet | ||
| 149 | We might add more testing and logging. */ | ||
| 150 | while ((ssize_t)(off + sizeof(ot_hash) + peer_size) <= ws->request_size) { | ||
| 151 | char *peer_ip = ws->request + off + sizeof(ot_hash); | ||
| 152 | if (peer_size == OT_PEER_SIZE4) | ||
| 153 | memcpy(&ws->peer[0] + sizeof(ip_v4mapped_prefix), peer_ip, peer_size); | ||
| 154 | else | ||
| 155 | memcpy(&ws->peer, peer_ip, peer_size); | ||
| 156 | ws->hash = (ot_hash *)(ws->request + off); | ||
| 157 | |||
| 158 | if (!g_opentracker_running) | ||
| 159 | return; | ||
| 160 | |||
| 161 | if (OT_PEERFLAG(ws->peer) & PEER_FLAG_STOPPED) | ||
| 162 | remove_peer_from_torrent(FLAG_MCA, ws); | ||
| 163 | else | ||
| 164 | add_peer_to_torrent_and_return_peers(FLAG_MCA, ws, /* amount = */ 0); | ||
| 165 | |||
| 166 | off += sizeof(ot_hash) + peer_size; | ||
| 167 | } | ||
| 168 | |||
| 169 | stats_issue_event(ws, EVENT_SYNC, 0, (ws->request_size - sizeof(g_tracker_id) - sizeof(uint32_t)) / ((ssize_t)sizeof(ot_hash) + peer_size)); | ||
| 170 | } | ||
| 171 | |||
| 172 | /* Tickle the live sync module from time to time, so no events get | ||
| 173 | stuck when there's not enough traffic to fill udp packets fast | ||
| 174 | enough */ | ||
| 175 | void livesync_ticker(void) { | ||
| 176 | if (g_socket_out == -1) | ||
| 177 | return; | ||
| 178 | |||
| 179 | /* livesync_issue_peersync sets g_next_packet_time */ | ||
| 180 | pthread_mutex_lock(&g_outbuf_mutex); | ||
| 181 | if (time_now_seconds() > g_v6_buf.next_packet_time && g_v6_buf.fill > sizeof(g_tracker_id) + sizeof(uint32_t)) | ||
| 182 | livesync_issue_peersync(&g_v6_buf); | ||
| 183 | else | ||
| 184 | pthread_mutex_unlock(&g_outbuf_mutex); | ||
| 185 | |||
| 186 | pthread_mutex_lock(&g_outbuf_mutex); | ||
| 187 | if (time_now_seconds() > g_v4_buf.next_packet_time && g_v4_buf.fill > sizeof(g_tracker_id) + sizeof(uint32_t)) | ||
| 188 | livesync_issue_peersync(&g_v4_buf); | ||
| 189 | else | ||
| 190 | pthread_mutex_unlock(&g_outbuf_mutex); | ||
| 191 | } | ||
| 192 | |||
| 193 | /* Inform live sync about whats going on. */ | ||
| 194 | void livesync_tell(struct ot_workstruct *ws) { | ||
| 195 | size_t peer_size; /* initialized in next line */ | ||
| 196 | ot_peer *peer_src; | ||
| 197 | sync_buffer *dest_buf; | ||
| 198 | |||
| 199 | if (g_socket_out == -1) | ||
| 200 | return; | ||
| 201 | |||
| 202 | peer_src = peer_from_peer6(&ws->peer, &peer_size); | ||
| 203 | dest_buf = peer_size == OT_PEER_SIZE6 ? &g_v6_buf : &g_v4_buf; | ||
| 204 | |||
| 205 | pthread_mutex_lock(&g_outbuf_mutex); | ||
| 206 | |||
| 207 | memcpy(dest_buf->data + dest_buf->fill, ws->hash, sizeof(ot_hash)); | ||
| 208 | dest_buf->fill += sizeof(ot_hash); | ||
| 209 | |||
| 210 | memcpy(dest_buf->data + dest_buf->fill, peer_src, peer_size); | ||
| 211 | dest_buf->fill += peer_size; | ||
| 212 | |||
| 213 | if (dest_buf->fill >= LIVESYNC_OUTGOING_BUFFSIZE_PEERS - LIVESYNC_OUTGOING_WATERMARK_PEERS) | ||
| 214 | livesync_issue_peersync(dest_buf); | ||
| 215 | else | ||
| 216 | pthread_mutex_unlock(&g_outbuf_mutex); | ||
| 217 | } | ||
| 218 | |||
| 219 | static void *livesync_worker(void *args) { | ||
| 220 | struct ot_workstruct ws; | ||
| 221 | ot_ip6 in_ip; | ||
| 222 | uint16_t in_port; | ||
| 223 | |||
| 224 | (void)args; | ||
| 225 | |||
| 226 | if (workstruct_init(&ws, LIVESYNC_INCOMING_BUFFSIZE) == -1) { | ||
| 227 | fprintf(stderr, "Warning: live sync could not get its buffers and will not run.\n"); | ||
| 228 | atomic_store(&thread_left, 1); | ||
| 229 | return NULL; | ||
| 230 | } | ||
| 231 | ws.request = ws.inbuf; | ||
| 232 | |||
| 233 | while (g_opentracker_running) { | ||
| 234 | uint32_t packet_type; | ||
| 235 | |||
| 236 | ws.request_size = socket_recv(g_socket_in, ws.inbuf, LIVESYNC_INCOMING_BUFFSIZE, in_ip, &in_port); | ||
| 237 | |||
| 238 | /* Expect at least tracker id and packet type */ | ||
| 239 | if (ws.request_size <= (ssize_t)(sizeof(g_tracker_id) + sizeof(uint32_t))) | ||
| 240 | continue; | ||
| 241 | if (!accesslist_is_blessed(in_ip, OT_PERMISSION_MAY_LIVESYNC)) | ||
| 242 | continue; | ||
| 243 | if (!memcmp(ws.inbuf, &g_tracker_id, sizeof(g_tracker_id))) { | ||
| 244 | /* TODO: log packet coming from ourselves */ | ||
| 245 | continue; | ||
| 246 | } | ||
| 247 | |||
| 248 | memcpy(&packet_type, ws.inbuf + sizeof(g_tracker_id), sizeof(packet_type)); | ||
| 249 | |||
| 250 | switch (ntohl(packet_type)) { | ||
| 251 | case OT_SYNC_PEER6: | ||
| 252 | livesync_handle_peersync(&ws, OT_PEER_SIZE6); | ||
| 253 | break; | ||
| 254 | case OT_SYNC_PEER4: | ||
| 255 | livesync_handle_peersync(&ws, OT_PEER_SIZE4); | ||
| 256 | break; | ||
| 257 | default: | ||
| 258 | break; | ||
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 262 | workstruct_deinit(&ws); | ||
| 263 | atomic_store(&thread_left, 1); | ||
| 264 | return NULL; | ||
| 265 | } | ||
| 266 | |||
| 267 | #endif | ||
diff --git a/src/ot_livesync.h b/src/ot_livesync.h new file mode 100644 index 0000000..cf3c64c --- /dev/null +++ b/src/ot_livesync.h | |||
| @@ -0,0 +1,80 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_LIVESYNC_H__ | ||
| 5 | #define OT_LIVESYNC_H__ | ||
| 6 | |||
| 7 | #include "trackerlogic.h" | ||
| 8 | |||
| 9 | /* | ||
| 10 | Syncing is done as udp packets in the multicast group 224.0.23.5, on the | ||
| 11 | port livesync.cluster.listen names, 9696 by default | ||
| 12 | |||
| 13 | Each tracker should join the multicast group and send its live sync packets | ||
| 14 | to that group, using a ttl of 1 | ||
| 15 | |||
| 16 | Format of all sync packets is straight forward, packet type determines | ||
| 17 | which kind of packet this is: | ||
| 18 | |||
| 19 | 0x0000 0x04 id of tracker instance | ||
| 20 | 0x0004 0x04 packet type | ||
| 21 | |||
| 22 | ######## | ||
| 23 | ######## PEER SYNC PROTOCOL ######## | ||
| 24 | ######## | ||
| 25 | |||
| 26 | Each tracker instance accumulates announce requests until its buffer is | ||
| 27 | full or a timeout is reached. Then it broadcasts its live sync packer: | ||
| 28 | |||
| 29 | packet type SYNC_LIVE4 | ||
| 30 | [ 0x0008 0x14 info_hash | ||
| 31 | 0x001c 0x04 peer's ipv4 address | ||
| 32 | 0x0020 0x02 peer's port | ||
| 33 | 0x0024 0x02 peer flags v1 ( SEEDING = 0x80, COMPLETE = 0x40, STOPPED = 0x20 ) | ||
| 34 | ]* | ||
| 35 | |||
| 36 | packet type SYNC_LIVE6 | ||
| 37 | [ 0x0008 0x14 info_hash | ||
| 38 | 0x001c 0x10 peer's ipv6 address | ||
| 39 | 0x002c 0x02 peer's port | ||
| 40 | 0x002e 0x02 peer flags v1 ( SEEDING = 0x80, COMPLETE = 0x40, STOPPED = 0x20 ) | ||
| 41 | ]* | ||
| 42 | */ | ||
| 43 | |||
| 44 | #ifdef WANT_SYNC_LIVE | ||
| 45 | |||
| 46 | #define LIVESYNC_PORT 9696 | ||
| 47 | |||
| 48 | void livesync_init(void); | ||
| 49 | void livesync_deinit(void); | ||
| 50 | |||
| 51 | /* The group to join and to send to, (defaults to 224.0.23.5). | ||
| 52 | Returns -1 if group is not a v4 multicast address. */ | ||
| 53 | int livesync_set_group(const ot_ip6 group); | ||
| 54 | |||
| 55 | /* Join multicast group for listening and create sending socket */ | ||
| 56 | void livesync_bind_mcast(ot_ip6 ip, uint16_t port); | ||
| 57 | |||
| 58 | /* Inform live sync about whats going on. */ | ||
| 59 | void livesync_tell(struct ot_workstruct *ws); | ||
| 60 | |||
| 61 | /* Tickle the live sync module from time to time, so no events get | ||
| 62 | stuck when there's not enough traffic to fill udp packets fast | ||
| 63 | enough */ | ||
| 64 | void livesync_ticker(void); | ||
| 65 | |||
| 66 | /* Handle an incoming live sync packet */ | ||
| 67 | void handle_livesync(const int64_t sock); | ||
| 68 | |||
| 69 | #else | ||
| 70 | |||
| 71 | /* If no syncing is required, save calling code from #ifdef | ||
| 72 | constructions */ | ||
| 73 | #define livesync_deinit() | ||
| 74 | #define livesync_init() | ||
| 75 | #define livesync_ticker() | ||
| 76 | #define handle_livesync(a) | ||
| 77 | |||
| 78 | #endif | ||
| 79 | |||
| 80 | #endif | ||
diff --git a/src/ot_mutex.c b/src/ot_mutex.c new file mode 100644 index 0000000..884b74c --- /dev/null +++ b/src/ot_mutex.c | |||
| @@ -0,0 +1,70 @@ | |||
| 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 <arpa/inet.h> | ||
| 6 | #include <pthread.h> | ||
| 7 | #include <stdatomic.h> | ||
| 8 | #include <stdio.h> | ||
| 9 | #include <stdlib.h> | ||
| 10 | #include <unistd.h> | ||
| 11 | #include <string.h> | ||
| 12 | #include <sys/uio.h> | ||
| 13 | |||
| 14 | |||
| 15 | /* Opentracker */ | ||
| 16 | #include "ot_iovec.h" | ||
| 17 | #include "ot_mutex.h" | ||
| 18 | #include "ot_stats.h" | ||
| 19 | #include "trackerlogic.h" | ||
| 20 | |||
| 21 | /* #define MTX_DBG( STRING ) fprintf( stderr, STRING ) */ | ||
| 22 | #define MTX_DBG(STRING) | ||
| 23 | |||
| 24 | /* Our global all torrents list */ | ||
| 25 | static ot_vector all_torrents[OT_BUCKET_COUNT]; | ||
| 26 | static pthread_mutex_t bucket_mutex[OT_BUCKET_COUNT]; | ||
| 27 | /* Every bucket's unlock adjusts this, and a bucket lock only keeps other | ||
| 28 | threads out of that one bucket, so the counter needs its own protection */ | ||
| 29 | static _Atomic size_t g_torrent_count; | ||
| 30 | |||
| 31 | ot_vector *mutex_bucket_lock(int bucket) { | ||
| 32 | pthread_mutex_lock(bucket_mutex + bucket); | ||
| 33 | return all_torrents + bucket; | ||
| 34 | } | ||
| 35 | |||
| 36 | /* Torrents are spread over buckets by the first four bytes of their info_hash, | ||
| 37 | which may sit at any alignment, hence the memcpy */ | ||
| 38 | static int bucket_from_hash(ot_hash const hash) { | ||
| 39 | uint32_t bucket; | ||
| 40 | memcpy(&bucket, hash, sizeof(bucket)); | ||
| 41 | return ntohl(bucket) >> OT_BUCKET_COUNT_SHIFT; | ||
| 42 | } | ||
| 43 | |||
| 44 | ot_vector *mutex_bucket_lock_by_hash(ot_hash const hash) { return mutex_bucket_lock(bucket_from_hash(hash)); } | ||
| 45 | |||
| 46 | void mutex_bucket_unlock(int bucket, int delta_torrentcount) { | ||
| 47 | pthread_mutex_unlock(bucket_mutex + bucket); | ||
| 48 | if (delta_torrentcount) | ||
| 49 | atomic_fetch_add_explicit(&g_torrent_count, (size_t)delta_torrentcount, memory_order_relaxed); | ||
| 50 | } | ||
| 51 | |||
| 52 | void mutex_bucket_unlock_by_hash(ot_hash const hash, int delta_torrentcount) { | ||
| 53 | mutex_bucket_unlock(bucket_from_hash(hash), delta_torrentcount); | ||
| 54 | } | ||
| 55 | |||
| 56 | size_t mutex_get_torrent_count(void) { return atomic_load_explicit(&g_torrent_count, memory_order_relaxed); } | ||
| 57 | |||
| 58 | void mutex_init(void) { | ||
| 59 | size_t i; | ||
| 60 | for (i = 0; i < OT_BUCKET_COUNT; ++i) | ||
| 61 | pthread_mutex_init(bucket_mutex + i, NULL); | ||
| 62 | memset(all_torrents, 0, sizeof(all_torrents)); | ||
| 63 | } | ||
| 64 | |||
| 65 | void mutex_deinit(void) { | ||
| 66 | size_t i; | ||
| 67 | for (i = 0; i < OT_BUCKET_COUNT; ++i) | ||
| 68 | pthread_mutex_destroy(bucket_mutex + i); | ||
| 69 | memset(all_torrents, 0, sizeof(all_torrents)); | ||
| 70 | } | ||
diff --git a/src/ot_mutex.h b/src/ot_mutex.h new file mode 100644 index 0000000..d7ded92 --- /dev/null +++ b/src/ot_mutex.h | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_MUTEX_H__ | ||
| 5 | #define OT_MUTEX_H__ | ||
| 6 | |||
| 7 | #include <sys/uio.h> | ||
| 8 | #include "trackerlogic.h" | ||
| 9 | |||
| 10 | void mutex_init(void); | ||
| 11 | void mutex_deinit(void); | ||
| 12 | |||
| 13 | ot_vector *mutex_bucket_lock(int bucket); | ||
| 14 | ot_vector *mutex_bucket_lock_by_hash(ot_hash const hash); | ||
| 15 | |||
| 16 | void mutex_bucket_unlock(int bucket, int delta_torrentcount); | ||
| 17 | void mutex_bucket_unlock_by_hash(ot_hash const hash, int delta_torrentcount); | ||
| 18 | |||
| 19 | size_t mutex_get_torrent_count(void); | ||
| 20 | |||
| 21 | #endif | ||
diff --git a/src/ot_random.c b/src/ot_random.c new file mode 100644 index 0000000..43f91bb --- /dev/null +++ b/src/ot_random.c | |||
| @@ -0,0 +1,51 @@ | |||
| 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 <stdint.h> | ||
| 6 | #include <stdlib.h> | ||
| 7 | |||
| 8 | /* Opentracker */ | ||
| 9 | #include "ot_random.h" | ||
| 10 | |||
| 11 | #ifdef WANT_INSECURE_RANDOM_FALLBACK | ||
| 12 | |||
| 13 | #warning WANT_INSECURE_RANDOM_FALLBACK builds without arc4random. UDP connection ids become predictable and announces can be forged. Do not ship this. | ||
| 14 | |||
| 15 | #include <time.h> | ||
| 16 | |||
| 17 | void random_init(void) { | ||
| 18 | #ifdef WANT_DEV_RANDOM | ||
| 19 | srandomdev(); | ||
| 20 | #else | ||
| 21 | srandom(time(NULL)); | ||
| 22 | #endif | ||
| 23 | } | ||
| 24 | |||
| 25 | /* random() yields 31 bits, so one draw can never set the top bit. Combine | ||
| 26 | two of them rather than hand out a value that is quietly half as wide as | ||
| 27 | its type claims. */ | ||
| 28 | uint32_t random_uint32(void) { return ((uint32_t)random() << 16) ^ (uint32_t)random(); } | ||
| 29 | |||
| 30 | void random_bytes(void *buf, size_t len) { | ||
| 31 | uint8_t *out = buf; | ||
| 32 | /* random() leaves the top bit clear and its lowest bits are the weakest, | ||
| 33 | so take a byte from the middle of each draw */ | ||
| 34 | while (len--) | ||
| 35 | *out++ = (uint8_t)(random() >> 8); | ||
| 36 | } | ||
| 37 | |||
| 38 | #else | ||
| 39 | |||
| 40 | #if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) && !defined(__DragonFly__) && !defined(__APPLE__) && \ | ||
| 41 | !(defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 36))) | ||
| 42 | #include <bsd/stdlib.h> | ||
| 43 | #endif | ||
| 44 | |||
| 45 | void random_init(void) {} | ||
| 46 | |||
| 47 | uint32_t random_uint32(void) { return arc4random(); } | ||
| 48 | |||
| 49 | void random_bytes(void *buf, size_t len) { arc4random_buf(buf, len); } | ||
| 50 | |||
| 51 | #endif | ||
diff --git a/src/ot_random.h b/src/ot_random.h new file mode 100644 index 0000000..c2d0387 --- /dev/null +++ b/src/ot_random.h | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_RANDOM_H__ | ||
| 5 | #define OT_RANDOM_H__ | ||
| 6 | |||
| 7 | #include <stddef.h> | ||
| 8 | #include <stdint.h> | ||
| 9 | |||
| 10 | /* Seed whatever generator the build settled on. A no-op when backed by | ||
| 11 | arc4random, which seeds itself from the kernel. */ | ||
| 12 | void random_init(void); | ||
| 13 | |||
| 14 | uint32_t random_uint32(void); | ||
| 15 | void random_bytes(void *buf, size_t len); | ||
| 16 | |||
| 17 | #endif | ||
diff --git a/src/ot_rijndael.c b/src/ot_rijndael.c new file mode 100644 index 0000000..3f36bde --- /dev/null +++ b/src/ot_rijndael.c | |||
| @@ -0,0 +1,488 @@ | |||
| 1 | /** | ||
| 2 | * rijndael-alg-fst.c | ||
| 3 | * | ||
| 4 | * @version 3.0 (December 2000) | ||
| 5 | * | ||
| 6 | * Optimised ANSI C code for the Rijndael cipher (now AES) | ||
| 7 | * | ||
| 8 | * @author Vincent Rijmen <vincent.rijmen@esat.kuleuven.ac.be> | ||
| 9 | * @author Antoon Bosselaers <antoon.bosselaers@esat.kuleuven.ac.be> | ||
| 10 | * @author Paulo Barreto <paulo.barreto@terra.com.br> | ||
| 11 | * | ||
| 12 | * This code is hereby placed in the public domain. | ||
| 13 | * | ||
| 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS | ||
| 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
| 16 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
| 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE | ||
| 18 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
| 19 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
| 20 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR | ||
| 21 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | ||
| 22 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | ||
| 23 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, | ||
| 24 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| 25 | */ | ||
| 26 | #include "ot_rijndael.h" | ||
| 27 | |||
| 28 | static const uint32_t Te0[256] = { | ||
| 29 | 0xc66363a5U, 0xf87c7c84U, 0xee777799U, 0xf67b7b8dU, | ||
| 30 | 0xfff2f20dU, 0xd66b6bbdU, 0xde6f6fb1U, 0x91c5c554U, | ||
| 31 | 0x60303050U, 0x02010103U, 0xce6767a9U, 0x562b2b7dU, | ||
| 32 | 0xe7fefe19U, 0xb5d7d762U, 0x4dababe6U, 0xec76769aU, | ||
| 33 | 0x8fcaca45U, 0x1f82829dU, 0x89c9c940U, 0xfa7d7d87U, | ||
| 34 | 0xeffafa15U, 0xb25959ebU, 0x8e4747c9U, 0xfbf0f00bU, | ||
| 35 | 0x41adadecU, 0xb3d4d467U, 0x5fa2a2fdU, 0x45afafeaU, | ||
| 36 | 0x239c9cbfU, 0x53a4a4f7U, 0xe4727296U, 0x9bc0c05bU, | ||
| 37 | 0x75b7b7c2U, 0xe1fdfd1cU, 0x3d9393aeU, 0x4c26266aU, | ||
| 38 | 0x6c36365aU, 0x7e3f3f41U, 0xf5f7f702U, 0x83cccc4fU, | ||
| 39 | 0x6834345cU, 0x51a5a5f4U, 0xd1e5e534U, 0xf9f1f108U, | ||
| 40 | 0xe2717193U, 0xabd8d873U, 0x62313153U, 0x2a15153fU, | ||
| 41 | 0x0804040cU, 0x95c7c752U, 0x46232365U, 0x9dc3c35eU, | ||
| 42 | 0x30181828U, 0x379696a1U, 0x0a05050fU, 0x2f9a9ab5U, | ||
| 43 | 0x0e070709U, 0x24121236U, 0x1b80809bU, 0xdfe2e23dU, | ||
| 44 | 0xcdebeb26U, 0x4e272769U, 0x7fb2b2cdU, 0xea75759fU, | ||
| 45 | 0x1209091bU, 0x1d83839eU, 0x582c2c74U, 0x341a1a2eU, | ||
| 46 | 0x361b1b2dU, 0xdc6e6eb2U, 0xb45a5aeeU, 0x5ba0a0fbU, | ||
| 47 | 0xa45252f6U, 0x763b3b4dU, 0xb7d6d661U, 0x7db3b3ceU, | ||
| 48 | 0x5229297bU, 0xdde3e33eU, 0x5e2f2f71U, 0x13848497U, | ||
| 49 | 0xa65353f5U, 0xb9d1d168U, 0x00000000U, 0xc1eded2cU, | ||
| 50 | 0x40202060U, 0xe3fcfc1fU, 0x79b1b1c8U, 0xb65b5bedU, | ||
| 51 | 0xd46a6abeU, 0x8dcbcb46U, 0x67bebed9U, 0x7239394bU, | ||
| 52 | 0x944a4adeU, 0x984c4cd4U, 0xb05858e8U, 0x85cfcf4aU, | ||
| 53 | 0xbbd0d06bU, 0xc5efef2aU, 0x4faaaae5U, 0xedfbfb16U, | ||
| 54 | 0x864343c5U, 0x9a4d4dd7U, 0x66333355U, 0x11858594U, | ||
| 55 | 0x8a4545cfU, 0xe9f9f910U, 0x04020206U, 0xfe7f7f81U, | ||
| 56 | 0xa05050f0U, 0x783c3c44U, 0x259f9fbaU, 0x4ba8a8e3U, | ||
| 57 | 0xa25151f3U, 0x5da3a3feU, 0x804040c0U, 0x058f8f8aU, | ||
| 58 | 0x3f9292adU, 0x219d9dbcU, 0x70383848U, 0xf1f5f504U, | ||
| 59 | 0x63bcbcdfU, 0x77b6b6c1U, 0xafdada75U, 0x42212163U, | ||
| 60 | 0x20101030U, 0xe5ffff1aU, 0xfdf3f30eU, 0xbfd2d26dU, | ||
| 61 | 0x81cdcd4cU, 0x180c0c14U, 0x26131335U, 0xc3ecec2fU, | ||
| 62 | 0xbe5f5fe1U, 0x359797a2U, 0x884444ccU, 0x2e171739U, | ||
| 63 | 0x93c4c457U, 0x55a7a7f2U, 0xfc7e7e82U, 0x7a3d3d47U, | ||
| 64 | 0xc86464acU, 0xba5d5de7U, 0x3219192bU, 0xe6737395U, | ||
| 65 | 0xc06060a0U, 0x19818198U, 0x9e4f4fd1U, 0xa3dcdc7fU, | ||
| 66 | 0x44222266U, 0x542a2a7eU, 0x3b9090abU, 0x0b888883U, | ||
| 67 | 0x8c4646caU, 0xc7eeee29U, 0x6bb8b8d3U, 0x2814143cU, | ||
| 68 | 0xa7dede79U, 0xbc5e5ee2U, 0x160b0b1dU, 0xaddbdb76U, | ||
| 69 | 0xdbe0e03bU, 0x64323256U, 0x743a3a4eU, 0x140a0a1eU, | ||
| 70 | 0x924949dbU, 0x0c06060aU, 0x4824246cU, 0xb85c5ce4U, | ||
| 71 | 0x9fc2c25dU, 0xbdd3d36eU, 0x43acacefU, 0xc46262a6U, | ||
| 72 | 0x399191a8U, 0x319595a4U, 0xd3e4e437U, 0xf279798bU, | ||
| 73 | 0xd5e7e732U, 0x8bc8c843U, 0x6e373759U, 0xda6d6db7U, | ||
| 74 | 0x018d8d8cU, 0xb1d5d564U, 0x9c4e4ed2U, 0x49a9a9e0U, | ||
| 75 | 0xd86c6cb4U, 0xac5656faU, 0xf3f4f407U, 0xcfeaea25U, | ||
| 76 | 0xca6565afU, 0xf47a7a8eU, 0x47aeaee9U, 0x10080818U, | ||
| 77 | 0x6fbabad5U, 0xf0787888U, 0x4a25256fU, 0x5c2e2e72U, | ||
| 78 | 0x381c1c24U, 0x57a6a6f1U, 0x73b4b4c7U, 0x97c6c651U, | ||
| 79 | 0xcbe8e823U, 0xa1dddd7cU, 0xe874749cU, 0x3e1f1f21U, | ||
| 80 | 0x964b4bddU, 0x61bdbddcU, 0x0d8b8b86U, 0x0f8a8a85U, | ||
| 81 | 0xe0707090U, 0x7c3e3e42U, 0x71b5b5c4U, 0xcc6666aaU, | ||
| 82 | 0x904848d8U, 0x06030305U, 0xf7f6f601U, 0x1c0e0e12U, | ||
| 83 | 0xc26161a3U, 0x6a35355fU, 0xae5757f9U, 0x69b9b9d0U, | ||
| 84 | 0x17868691U, 0x99c1c158U, 0x3a1d1d27U, 0x279e9eb9U, | ||
| 85 | 0xd9e1e138U, 0xebf8f813U, 0x2b9898b3U, 0x22111133U, | ||
| 86 | 0xd26969bbU, 0xa9d9d970U, 0x078e8e89U, 0x339494a7U, | ||
| 87 | 0x2d9b9bb6U, 0x3c1e1e22U, 0x15878792U, 0xc9e9e920U, | ||
| 88 | 0x87cece49U, 0xaa5555ffU, 0x50282878U, 0xa5dfdf7aU, | ||
| 89 | 0x038c8c8fU, 0x59a1a1f8U, 0x09898980U, 0x1a0d0d17U, | ||
| 90 | 0x65bfbfdaU, 0xd7e6e631U, 0x844242c6U, 0xd06868b8U, | ||
| 91 | 0x824141c3U, 0x299999b0U, 0x5a2d2d77U, 0x1e0f0f11U, | ||
| 92 | 0x7bb0b0cbU, 0xa85454fcU, 0x6dbbbbd6U, 0x2c16163aU, | ||
| 93 | }; | ||
| 94 | static const uint32_t Te1[256] = { | ||
| 95 | 0xa5c66363U, 0x84f87c7cU, 0x99ee7777U, 0x8df67b7bU, | ||
| 96 | 0x0dfff2f2U, 0xbdd66b6bU, 0xb1de6f6fU, 0x5491c5c5U, | ||
| 97 | 0x50603030U, 0x03020101U, 0xa9ce6767U, 0x7d562b2bU, | ||
| 98 | 0x19e7fefeU, 0x62b5d7d7U, 0xe64dababU, 0x9aec7676U, | ||
| 99 | 0x458fcacaU, 0x9d1f8282U, 0x4089c9c9U, 0x87fa7d7dU, | ||
| 100 | 0x15effafaU, 0xebb25959U, 0xc98e4747U, 0x0bfbf0f0U, | ||
| 101 | 0xec41adadU, 0x67b3d4d4U, 0xfd5fa2a2U, 0xea45afafU, | ||
| 102 | 0xbf239c9cU, 0xf753a4a4U, 0x96e47272U, 0x5b9bc0c0U, | ||
| 103 | 0xc275b7b7U, 0x1ce1fdfdU, 0xae3d9393U, 0x6a4c2626U, | ||
| 104 | 0x5a6c3636U, 0x417e3f3fU, 0x02f5f7f7U, 0x4f83ccccU, | ||
| 105 | 0x5c683434U, 0xf451a5a5U, 0x34d1e5e5U, 0x08f9f1f1U, | ||
| 106 | 0x93e27171U, 0x73abd8d8U, 0x53623131U, 0x3f2a1515U, | ||
| 107 | 0x0c080404U, 0x5295c7c7U, 0x65462323U, 0x5e9dc3c3U, | ||
| 108 | 0x28301818U, 0xa1379696U, 0x0f0a0505U, 0xb52f9a9aU, | ||
| 109 | 0x090e0707U, 0x36241212U, 0x9b1b8080U, 0x3ddfe2e2U, | ||
| 110 | 0x26cdebebU, 0x694e2727U, 0xcd7fb2b2U, 0x9fea7575U, | ||
| 111 | 0x1b120909U, 0x9e1d8383U, 0x74582c2cU, 0x2e341a1aU, | ||
| 112 | 0x2d361b1bU, 0xb2dc6e6eU, 0xeeb45a5aU, 0xfb5ba0a0U, | ||
| 113 | 0xf6a45252U, 0x4d763b3bU, 0x61b7d6d6U, 0xce7db3b3U, | ||
| 114 | 0x7b522929U, 0x3edde3e3U, 0x715e2f2fU, 0x97138484U, | ||
| 115 | 0xf5a65353U, 0x68b9d1d1U, 0x00000000U, 0x2cc1ededU, | ||
| 116 | 0x60402020U, 0x1fe3fcfcU, 0xc879b1b1U, 0xedb65b5bU, | ||
| 117 | 0xbed46a6aU, 0x468dcbcbU, 0xd967bebeU, 0x4b723939U, | ||
| 118 | 0xde944a4aU, 0xd4984c4cU, 0xe8b05858U, 0x4a85cfcfU, | ||
| 119 | 0x6bbbd0d0U, 0x2ac5efefU, 0xe54faaaaU, 0x16edfbfbU, | ||
| 120 | 0xc5864343U, 0xd79a4d4dU, 0x55663333U, 0x94118585U, | ||
| 121 | 0xcf8a4545U, 0x10e9f9f9U, 0x06040202U, 0x81fe7f7fU, | ||
| 122 | 0xf0a05050U, 0x44783c3cU, 0xba259f9fU, 0xe34ba8a8U, | ||
| 123 | 0xf3a25151U, 0xfe5da3a3U, 0xc0804040U, 0x8a058f8fU, | ||
| 124 | 0xad3f9292U, 0xbc219d9dU, 0x48703838U, 0x04f1f5f5U, | ||
| 125 | 0xdf63bcbcU, 0xc177b6b6U, 0x75afdadaU, 0x63422121U, | ||
| 126 | 0x30201010U, 0x1ae5ffffU, 0x0efdf3f3U, 0x6dbfd2d2U, | ||
| 127 | 0x4c81cdcdU, 0x14180c0cU, 0x35261313U, 0x2fc3ececU, | ||
| 128 | 0xe1be5f5fU, 0xa2359797U, 0xcc884444U, 0x392e1717U, | ||
| 129 | 0x5793c4c4U, 0xf255a7a7U, 0x82fc7e7eU, 0x477a3d3dU, | ||
| 130 | 0xacc86464U, 0xe7ba5d5dU, 0x2b321919U, 0x95e67373U, | ||
| 131 | 0xa0c06060U, 0x98198181U, 0xd19e4f4fU, 0x7fa3dcdcU, | ||
| 132 | 0x66442222U, 0x7e542a2aU, 0xab3b9090U, 0x830b8888U, | ||
| 133 | 0xca8c4646U, 0x29c7eeeeU, 0xd36bb8b8U, 0x3c281414U, | ||
| 134 | 0x79a7dedeU, 0xe2bc5e5eU, 0x1d160b0bU, 0x76addbdbU, | ||
| 135 | 0x3bdbe0e0U, 0x56643232U, 0x4e743a3aU, 0x1e140a0aU, | ||
| 136 | 0xdb924949U, 0x0a0c0606U, 0x6c482424U, 0xe4b85c5cU, | ||
| 137 | 0x5d9fc2c2U, 0x6ebdd3d3U, 0xef43acacU, 0xa6c46262U, | ||
| 138 | 0xa8399191U, 0xa4319595U, 0x37d3e4e4U, 0x8bf27979U, | ||
| 139 | 0x32d5e7e7U, 0x438bc8c8U, 0x596e3737U, 0xb7da6d6dU, | ||
| 140 | 0x8c018d8dU, 0x64b1d5d5U, 0xd29c4e4eU, 0xe049a9a9U, | ||
| 141 | 0xb4d86c6cU, 0xfaac5656U, 0x07f3f4f4U, 0x25cfeaeaU, | ||
| 142 | 0xafca6565U, 0x8ef47a7aU, 0xe947aeaeU, 0x18100808U, | ||
| 143 | 0xd56fbabaU, 0x88f07878U, 0x6f4a2525U, 0x725c2e2eU, | ||
| 144 | 0x24381c1cU, 0xf157a6a6U, 0xc773b4b4U, 0x5197c6c6U, | ||
| 145 | 0x23cbe8e8U, 0x7ca1ddddU, 0x9ce87474U, 0x213e1f1fU, | ||
| 146 | 0xdd964b4bU, 0xdc61bdbdU, 0x860d8b8bU, 0x850f8a8aU, | ||
| 147 | 0x90e07070U, 0x427c3e3eU, 0xc471b5b5U, 0xaacc6666U, | ||
| 148 | 0xd8904848U, 0x05060303U, 0x01f7f6f6U, 0x121c0e0eU, | ||
| 149 | 0xa3c26161U, 0x5f6a3535U, 0xf9ae5757U, 0xd069b9b9U, | ||
| 150 | 0x91178686U, 0x5899c1c1U, 0x273a1d1dU, 0xb9279e9eU, | ||
| 151 | 0x38d9e1e1U, 0x13ebf8f8U, 0xb32b9898U, 0x33221111U, | ||
| 152 | 0xbbd26969U, 0x70a9d9d9U, 0x89078e8eU, 0xa7339494U, | ||
| 153 | 0xb62d9b9bU, 0x223c1e1eU, 0x92158787U, 0x20c9e9e9U, | ||
| 154 | 0x4987ceceU, 0xffaa5555U, 0x78502828U, 0x7aa5dfdfU, | ||
| 155 | 0x8f038c8cU, 0xf859a1a1U, 0x80098989U, 0x171a0d0dU, | ||
| 156 | 0xda65bfbfU, 0x31d7e6e6U, 0xc6844242U, 0xb8d06868U, | ||
| 157 | 0xc3824141U, 0xb0299999U, 0x775a2d2dU, 0x111e0f0fU, | ||
| 158 | 0xcb7bb0b0U, 0xfca85454U, 0xd66dbbbbU, 0x3a2c1616U, | ||
| 159 | }; | ||
| 160 | static const uint32_t Te2[256] = { | ||
| 161 | 0x63a5c663U, 0x7c84f87cU, 0x7799ee77U, 0x7b8df67bU, | ||
| 162 | 0xf20dfff2U, 0x6bbdd66bU, 0x6fb1de6fU, 0xc55491c5U, | ||
| 163 | 0x30506030U, 0x01030201U, 0x67a9ce67U, 0x2b7d562bU, | ||
| 164 | 0xfe19e7feU, 0xd762b5d7U, 0xabe64dabU, 0x769aec76U, | ||
| 165 | 0xca458fcaU, 0x829d1f82U, 0xc94089c9U, 0x7d87fa7dU, | ||
| 166 | 0xfa15effaU, 0x59ebb259U, 0x47c98e47U, 0xf00bfbf0U, | ||
| 167 | 0xadec41adU, 0xd467b3d4U, 0xa2fd5fa2U, 0xafea45afU, | ||
| 168 | 0x9cbf239cU, 0xa4f753a4U, 0x7296e472U, 0xc05b9bc0U, | ||
| 169 | 0xb7c275b7U, 0xfd1ce1fdU, 0x93ae3d93U, 0x266a4c26U, | ||
| 170 | 0x365a6c36U, 0x3f417e3fU, 0xf702f5f7U, 0xcc4f83ccU, | ||
| 171 | 0x345c6834U, 0xa5f451a5U, 0xe534d1e5U, 0xf108f9f1U, | ||
| 172 | 0x7193e271U, 0xd873abd8U, 0x31536231U, 0x153f2a15U, | ||
| 173 | 0x040c0804U, 0xc75295c7U, 0x23654623U, 0xc35e9dc3U, | ||
| 174 | 0x18283018U, 0x96a13796U, 0x050f0a05U, 0x9ab52f9aU, | ||
| 175 | 0x07090e07U, 0x12362412U, 0x809b1b80U, 0xe23ddfe2U, | ||
| 176 | 0xeb26cdebU, 0x27694e27U, 0xb2cd7fb2U, 0x759fea75U, | ||
| 177 | 0x091b1209U, 0x839e1d83U, 0x2c74582cU, 0x1a2e341aU, | ||
| 178 | 0x1b2d361bU, 0x6eb2dc6eU, 0x5aeeb45aU, 0xa0fb5ba0U, | ||
| 179 | 0x52f6a452U, 0x3b4d763bU, 0xd661b7d6U, 0xb3ce7db3U, | ||
| 180 | 0x297b5229U, 0xe33edde3U, 0x2f715e2fU, 0x84971384U, | ||
| 181 | 0x53f5a653U, 0xd168b9d1U, 0x00000000U, 0xed2cc1edU, | ||
| 182 | 0x20604020U, 0xfc1fe3fcU, 0xb1c879b1U, 0x5bedb65bU, | ||
| 183 | 0x6abed46aU, 0xcb468dcbU, 0xbed967beU, 0x394b7239U, | ||
| 184 | 0x4ade944aU, 0x4cd4984cU, 0x58e8b058U, 0xcf4a85cfU, | ||
| 185 | 0xd06bbbd0U, 0xef2ac5efU, 0xaae54faaU, 0xfb16edfbU, | ||
| 186 | 0x43c58643U, 0x4dd79a4dU, 0x33556633U, 0x85941185U, | ||
| 187 | 0x45cf8a45U, 0xf910e9f9U, 0x02060402U, 0x7f81fe7fU, | ||
| 188 | 0x50f0a050U, 0x3c44783cU, 0x9fba259fU, 0xa8e34ba8U, | ||
| 189 | 0x51f3a251U, 0xa3fe5da3U, 0x40c08040U, 0x8f8a058fU, | ||
| 190 | 0x92ad3f92U, 0x9dbc219dU, 0x38487038U, 0xf504f1f5U, | ||
| 191 | 0xbcdf63bcU, 0xb6c177b6U, 0xda75afdaU, 0x21634221U, | ||
| 192 | 0x10302010U, 0xff1ae5ffU, 0xf30efdf3U, 0xd26dbfd2U, | ||
| 193 | 0xcd4c81cdU, 0x0c14180cU, 0x13352613U, 0xec2fc3ecU, | ||
| 194 | 0x5fe1be5fU, 0x97a23597U, 0x44cc8844U, 0x17392e17U, | ||
| 195 | 0xc45793c4U, 0xa7f255a7U, 0x7e82fc7eU, 0x3d477a3dU, | ||
| 196 | 0x64acc864U, 0x5de7ba5dU, 0x192b3219U, 0x7395e673U, | ||
| 197 | 0x60a0c060U, 0x81981981U, 0x4fd19e4fU, 0xdc7fa3dcU, | ||
| 198 | 0x22664422U, 0x2a7e542aU, 0x90ab3b90U, 0x88830b88U, | ||
| 199 | 0x46ca8c46U, 0xee29c7eeU, 0xb8d36bb8U, 0x143c2814U, | ||
| 200 | 0xde79a7deU, 0x5ee2bc5eU, 0x0b1d160bU, 0xdb76addbU, | ||
| 201 | 0xe03bdbe0U, 0x32566432U, 0x3a4e743aU, 0x0a1e140aU, | ||
| 202 | 0x49db9249U, 0x060a0c06U, 0x246c4824U, 0x5ce4b85cU, | ||
| 203 | 0xc25d9fc2U, 0xd36ebdd3U, 0xacef43acU, 0x62a6c462U, | ||
| 204 | 0x91a83991U, 0x95a43195U, 0xe437d3e4U, 0x798bf279U, | ||
| 205 | 0xe732d5e7U, 0xc8438bc8U, 0x37596e37U, 0x6db7da6dU, | ||
| 206 | 0x8d8c018dU, 0xd564b1d5U, 0x4ed29c4eU, 0xa9e049a9U, | ||
| 207 | 0x6cb4d86cU, 0x56faac56U, 0xf407f3f4U, 0xea25cfeaU, | ||
| 208 | 0x65afca65U, 0x7a8ef47aU, 0xaee947aeU, 0x08181008U, | ||
| 209 | 0xbad56fbaU, 0x7888f078U, 0x256f4a25U, 0x2e725c2eU, | ||
| 210 | 0x1c24381cU, 0xa6f157a6U, 0xb4c773b4U, 0xc65197c6U, | ||
| 211 | 0xe823cbe8U, 0xdd7ca1ddU, 0x749ce874U, 0x1f213e1fU, | ||
| 212 | 0x4bdd964bU, 0xbddc61bdU, 0x8b860d8bU, 0x8a850f8aU, | ||
| 213 | 0x7090e070U, 0x3e427c3eU, 0xb5c471b5U, 0x66aacc66U, | ||
| 214 | 0x48d89048U, 0x03050603U, 0xf601f7f6U, 0x0e121c0eU, | ||
| 215 | 0x61a3c261U, 0x355f6a35U, 0x57f9ae57U, 0xb9d069b9U, | ||
| 216 | 0x86911786U, 0xc15899c1U, 0x1d273a1dU, 0x9eb9279eU, | ||
| 217 | 0xe138d9e1U, 0xf813ebf8U, 0x98b32b98U, 0x11332211U, | ||
| 218 | 0x69bbd269U, 0xd970a9d9U, 0x8e89078eU, 0x94a73394U, | ||
| 219 | 0x9bb62d9bU, 0x1e223c1eU, 0x87921587U, 0xe920c9e9U, | ||
| 220 | 0xce4987ceU, 0x55ffaa55U, 0x28785028U, 0xdf7aa5dfU, | ||
| 221 | 0x8c8f038cU, 0xa1f859a1U, 0x89800989U, 0x0d171a0dU, | ||
| 222 | 0xbfda65bfU, 0xe631d7e6U, 0x42c68442U, 0x68b8d068U, | ||
| 223 | 0x41c38241U, 0x99b02999U, 0x2d775a2dU, 0x0f111e0fU, | ||
| 224 | 0xb0cb7bb0U, 0x54fca854U, 0xbbd66dbbU, 0x163a2c16U, | ||
| 225 | }; | ||
| 226 | static const uint32_t Te3[256] = { | ||
| 227 | |||
| 228 | 0x6363a5c6U, 0x7c7c84f8U, 0x777799eeU, 0x7b7b8df6U, | ||
| 229 | 0xf2f20dffU, 0x6b6bbdd6U, 0x6f6fb1deU, 0xc5c55491U, | ||
| 230 | 0x30305060U, 0x01010302U, 0x6767a9ceU, 0x2b2b7d56U, | ||
| 231 | 0xfefe19e7U, 0xd7d762b5U, 0xababe64dU, 0x76769aecU, | ||
| 232 | 0xcaca458fU, 0x82829d1fU, 0xc9c94089U, 0x7d7d87faU, | ||
| 233 | 0xfafa15efU, 0x5959ebb2U, 0x4747c98eU, 0xf0f00bfbU, | ||
| 234 | 0xadadec41U, 0xd4d467b3U, 0xa2a2fd5fU, 0xafafea45U, | ||
| 235 | 0x9c9cbf23U, 0xa4a4f753U, 0x727296e4U, 0xc0c05b9bU, | ||
| 236 | 0xb7b7c275U, 0xfdfd1ce1U, 0x9393ae3dU, 0x26266a4cU, | ||
| 237 | 0x36365a6cU, 0x3f3f417eU, 0xf7f702f5U, 0xcccc4f83U, | ||
| 238 | 0x34345c68U, 0xa5a5f451U, 0xe5e534d1U, 0xf1f108f9U, | ||
| 239 | 0x717193e2U, 0xd8d873abU, 0x31315362U, 0x15153f2aU, | ||
| 240 | 0x04040c08U, 0xc7c75295U, 0x23236546U, 0xc3c35e9dU, | ||
| 241 | 0x18182830U, 0x9696a137U, 0x05050f0aU, 0x9a9ab52fU, | ||
| 242 | 0x0707090eU, 0x12123624U, 0x80809b1bU, 0xe2e23ddfU, | ||
| 243 | 0xebeb26cdU, 0x2727694eU, 0xb2b2cd7fU, 0x75759feaU, | ||
| 244 | 0x09091b12U, 0x83839e1dU, 0x2c2c7458U, 0x1a1a2e34U, | ||
| 245 | 0x1b1b2d36U, 0x6e6eb2dcU, 0x5a5aeeb4U, 0xa0a0fb5bU, | ||
| 246 | 0x5252f6a4U, 0x3b3b4d76U, 0xd6d661b7U, 0xb3b3ce7dU, | ||
| 247 | 0x29297b52U, 0xe3e33eddU, 0x2f2f715eU, 0x84849713U, | ||
| 248 | 0x5353f5a6U, 0xd1d168b9U, 0x00000000U, 0xeded2cc1U, | ||
| 249 | 0x20206040U, 0xfcfc1fe3U, 0xb1b1c879U, 0x5b5bedb6U, | ||
| 250 | 0x6a6abed4U, 0xcbcb468dU, 0xbebed967U, 0x39394b72U, | ||
| 251 | 0x4a4ade94U, 0x4c4cd498U, 0x5858e8b0U, 0xcfcf4a85U, | ||
| 252 | 0xd0d06bbbU, 0xefef2ac5U, 0xaaaae54fU, 0xfbfb16edU, | ||
| 253 | 0x4343c586U, 0x4d4dd79aU, 0x33335566U, 0x85859411U, | ||
| 254 | 0x4545cf8aU, 0xf9f910e9U, 0x02020604U, 0x7f7f81feU, | ||
| 255 | 0x5050f0a0U, 0x3c3c4478U, 0x9f9fba25U, 0xa8a8e34bU, | ||
| 256 | 0x5151f3a2U, 0xa3a3fe5dU, 0x4040c080U, 0x8f8f8a05U, | ||
| 257 | 0x9292ad3fU, 0x9d9dbc21U, 0x38384870U, 0xf5f504f1U, | ||
| 258 | 0xbcbcdf63U, 0xb6b6c177U, 0xdada75afU, 0x21216342U, | ||
| 259 | 0x10103020U, 0xffff1ae5U, 0xf3f30efdU, 0xd2d26dbfU, | ||
| 260 | 0xcdcd4c81U, 0x0c0c1418U, 0x13133526U, 0xecec2fc3U, | ||
| 261 | 0x5f5fe1beU, 0x9797a235U, 0x4444cc88U, 0x1717392eU, | ||
| 262 | 0xc4c45793U, 0xa7a7f255U, 0x7e7e82fcU, 0x3d3d477aU, | ||
| 263 | 0x6464acc8U, 0x5d5de7baU, 0x19192b32U, 0x737395e6U, | ||
| 264 | 0x6060a0c0U, 0x81819819U, 0x4f4fd19eU, 0xdcdc7fa3U, | ||
| 265 | 0x22226644U, 0x2a2a7e54U, 0x9090ab3bU, 0x8888830bU, | ||
| 266 | 0x4646ca8cU, 0xeeee29c7U, 0xb8b8d36bU, 0x14143c28U, | ||
| 267 | 0xdede79a7U, 0x5e5ee2bcU, 0x0b0b1d16U, 0xdbdb76adU, | ||
| 268 | 0xe0e03bdbU, 0x32325664U, 0x3a3a4e74U, 0x0a0a1e14U, | ||
| 269 | 0x4949db92U, 0x06060a0cU, 0x24246c48U, 0x5c5ce4b8U, | ||
| 270 | 0xc2c25d9fU, 0xd3d36ebdU, 0xacacef43U, 0x6262a6c4U, | ||
| 271 | 0x9191a839U, 0x9595a431U, 0xe4e437d3U, 0x79798bf2U, | ||
| 272 | 0xe7e732d5U, 0xc8c8438bU, 0x3737596eU, 0x6d6db7daU, | ||
| 273 | 0x8d8d8c01U, 0xd5d564b1U, 0x4e4ed29cU, 0xa9a9e049U, | ||
| 274 | 0x6c6cb4d8U, 0x5656faacU, 0xf4f407f3U, 0xeaea25cfU, | ||
| 275 | 0x6565afcaU, 0x7a7a8ef4U, 0xaeaee947U, 0x08081810U, | ||
| 276 | 0xbabad56fU, 0x787888f0U, 0x25256f4aU, 0x2e2e725cU, | ||
| 277 | 0x1c1c2438U, 0xa6a6f157U, 0xb4b4c773U, 0xc6c65197U, | ||
| 278 | 0xe8e823cbU, 0xdddd7ca1U, 0x74749ce8U, 0x1f1f213eU, | ||
| 279 | 0x4b4bdd96U, 0xbdbddc61U, 0x8b8b860dU, 0x8a8a850fU, | ||
| 280 | 0x707090e0U, 0x3e3e427cU, 0xb5b5c471U, 0x6666aaccU, | ||
| 281 | 0x4848d890U, 0x03030506U, 0xf6f601f7U, 0x0e0e121cU, | ||
| 282 | 0x6161a3c2U, 0x35355f6aU, 0x5757f9aeU, 0xb9b9d069U, | ||
| 283 | 0x86869117U, 0xc1c15899U, 0x1d1d273aU, 0x9e9eb927U, | ||
| 284 | 0xe1e138d9U, 0xf8f813ebU, 0x9898b32bU, 0x11113322U, | ||
| 285 | 0x6969bbd2U, 0xd9d970a9U, 0x8e8e8907U, 0x9494a733U, | ||
| 286 | 0x9b9bb62dU, 0x1e1e223cU, 0x87879215U, 0xe9e920c9U, | ||
| 287 | 0xcece4987U, 0x5555ffaaU, 0x28287850U, 0xdfdf7aa5U, | ||
| 288 | 0x8c8c8f03U, 0xa1a1f859U, 0x89898009U, 0x0d0d171aU, | ||
| 289 | 0xbfbfda65U, 0xe6e631d7U, 0x4242c684U, 0x6868b8d0U, | ||
| 290 | 0x4141c382U, 0x9999b029U, 0x2d2d775aU, 0x0f0f111eU, | ||
| 291 | 0xb0b0cb7bU, 0x5454fca8U, 0xbbbbd66dU, 0x16163a2cU, | ||
| 292 | }; | ||
| 293 | static const uint32_t Te4[256] = { | ||
| 294 | 0x63636363U, 0x7c7c7c7cU, 0x77777777U, 0x7b7b7b7bU, | ||
| 295 | 0xf2f2f2f2U, 0x6b6b6b6bU, 0x6f6f6f6fU, 0xc5c5c5c5U, | ||
| 296 | 0x30303030U, 0x01010101U, 0x67676767U, 0x2b2b2b2bU, | ||
| 297 | 0xfefefefeU, 0xd7d7d7d7U, 0xababababU, 0x76767676U, | ||
| 298 | 0xcacacacaU, 0x82828282U, 0xc9c9c9c9U, 0x7d7d7d7dU, | ||
| 299 | 0xfafafafaU, 0x59595959U, 0x47474747U, 0xf0f0f0f0U, | ||
| 300 | 0xadadadadU, 0xd4d4d4d4U, 0xa2a2a2a2U, 0xafafafafU, | ||
| 301 | 0x9c9c9c9cU, 0xa4a4a4a4U, 0x72727272U, 0xc0c0c0c0U, | ||
| 302 | 0xb7b7b7b7U, 0xfdfdfdfdU, 0x93939393U, 0x26262626U, | ||
| 303 | 0x36363636U, 0x3f3f3f3fU, 0xf7f7f7f7U, 0xccccccccU, | ||
| 304 | 0x34343434U, 0xa5a5a5a5U, 0xe5e5e5e5U, 0xf1f1f1f1U, | ||
| 305 | 0x71717171U, 0xd8d8d8d8U, 0x31313131U, 0x15151515U, | ||
| 306 | 0x04040404U, 0xc7c7c7c7U, 0x23232323U, 0xc3c3c3c3U, | ||
| 307 | 0x18181818U, 0x96969696U, 0x05050505U, 0x9a9a9a9aU, | ||
| 308 | 0x07070707U, 0x12121212U, 0x80808080U, 0xe2e2e2e2U, | ||
| 309 | 0xebebebebU, 0x27272727U, 0xb2b2b2b2U, 0x75757575U, | ||
| 310 | 0x09090909U, 0x83838383U, 0x2c2c2c2cU, 0x1a1a1a1aU, | ||
| 311 | 0x1b1b1b1bU, 0x6e6e6e6eU, 0x5a5a5a5aU, 0xa0a0a0a0U, | ||
| 312 | 0x52525252U, 0x3b3b3b3bU, 0xd6d6d6d6U, 0xb3b3b3b3U, | ||
| 313 | 0x29292929U, 0xe3e3e3e3U, 0x2f2f2f2fU, 0x84848484U, | ||
| 314 | 0x53535353U, 0xd1d1d1d1U, 0x00000000U, 0xededededU, | ||
| 315 | 0x20202020U, 0xfcfcfcfcU, 0xb1b1b1b1U, 0x5b5b5b5bU, | ||
| 316 | 0x6a6a6a6aU, 0xcbcbcbcbU, 0xbebebebeU, 0x39393939U, | ||
| 317 | 0x4a4a4a4aU, 0x4c4c4c4cU, 0x58585858U, 0xcfcfcfcfU, | ||
| 318 | 0xd0d0d0d0U, 0xefefefefU, 0xaaaaaaaaU, 0xfbfbfbfbU, | ||
| 319 | 0x43434343U, 0x4d4d4d4dU, 0x33333333U, 0x85858585U, | ||
| 320 | 0x45454545U, 0xf9f9f9f9U, 0x02020202U, 0x7f7f7f7fU, | ||
| 321 | 0x50505050U, 0x3c3c3c3cU, 0x9f9f9f9fU, 0xa8a8a8a8U, | ||
| 322 | 0x51515151U, 0xa3a3a3a3U, 0x40404040U, 0x8f8f8f8fU, | ||
| 323 | 0x92929292U, 0x9d9d9d9dU, 0x38383838U, 0xf5f5f5f5U, | ||
| 324 | 0xbcbcbcbcU, 0xb6b6b6b6U, 0xdadadadaU, 0x21212121U, | ||
| 325 | 0x10101010U, 0xffffffffU, 0xf3f3f3f3U, 0xd2d2d2d2U, | ||
| 326 | 0xcdcdcdcdU, 0x0c0c0c0cU, 0x13131313U, 0xececececU, | ||
| 327 | 0x5f5f5f5fU, 0x97979797U, 0x44444444U, 0x17171717U, | ||
| 328 | 0xc4c4c4c4U, 0xa7a7a7a7U, 0x7e7e7e7eU, 0x3d3d3d3dU, | ||
| 329 | 0x64646464U, 0x5d5d5d5dU, 0x19191919U, 0x73737373U, | ||
| 330 | 0x60606060U, 0x81818181U, 0x4f4f4f4fU, 0xdcdcdcdcU, | ||
| 331 | 0x22222222U, 0x2a2a2a2aU, 0x90909090U, 0x88888888U, | ||
| 332 | 0x46464646U, 0xeeeeeeeeU, 0xb8b8b8b8U, 0x14141414U, | ||
| 333 | 0xdedededeU, 0x5e5e5e5eU, 0x0b0b0b0bU, 0xdbdbdbdbU, | ||
| 334 | 0xe0e0e0e0U, 0x32323232U, 0x3a3a3a3aU, 0x0a0a0a0aU, | ||
| 335 | 0x49494949U, 0x06060606U, 0x24242424U, 0x5c5c5c5cU, | ||
| 336 | 0xc2c2c2c2U, 0xd3d3d3d3U, 0xacacacacU, 0x62626262U, | ||
| 337 | 0x91919191U, 0x95959595U, 0xe4e4e4e4U, 0x79797979U, | ||
| 338 | 0xe7e7e7e7U, 0xc8c8c8c8U, 0x37373737U, 0x6d6d6d6dU, | ||
| 339 | 0x8d8d8d8dU, 0xd5d5d5d5U, 0x4e4e4e4eU, 0xa9a9a9a9U, | ||
| 340 | 0x6c6c6c6cU, 0x56565656U, 0xf4f4f4f4U, 0xeaeaeaeaU, | ||
| 341 | 0x65656565U, 0x7a7a7a7aU, 0xaeaeaeaeU, 0x08080808U, | ||
| 342 | 0xbabababaU, 0x78787878U, 0x25252525U, 0x2e2e2e2eU, | ||
| 343 | 0x1c1c1c1cU, 0xa6a6a6a6U, 0xb4b4b4b4U, 0xc6c6c6c6U, | ||
| 344 | 0xe8e8e8e8U, 0xddddddddU, 0x74747474U, 0x1f1f1f1fU, | ||
| 345 | 0x4b4b4b4bU, 0xbdbdbdbdU, 0x8b8b8b8bU, 0x8a8a8a8aU, | ||
| 346 | 0x70707070U, 0x3e3e3e3eU, 0xb5b5b5b5U, 0x66666666U, | ||
| 347 | 0x48484848U, 0x03030303U, 0xf6f6f6f6U, 0x0e0e0e0eU, | ||
| 348 | 0x61616161U, 0x35353535U, 0x57575757U, 0xb9b9b9b9U, | ||
| 349 | 0x86868686U, 0xc1c1c1c1U, 0x1d1d1d1dU, 0x9e9e9e9eU, | ||
| 350 | 0xe1e1e1e1U, 0xf8f8f8f8U, 0x98989898U, 0x11111111U, | ||
| 351 | 0x69696969U, 0xd9d9d9d9U, 0x8e8e8e8eU, 0x94949494U, | ||
| 352 | 0x9b9b9b9bU, 0x1e1e1e1eU, 0x87878787U, 0xe9e9e9e9U, | ||
| 353 | 0xcecececeU, 0x55555555U, 0x28282828U, 0xdfdfdfdfU, | ||
| 354 | 0x8c8c8c8cU, 0xa1a1a1a1U, 0x89898989U, 0x0d0d0d0dU, | ||
| 355 | 0xbfbfbfbfU, 0xe6e6e6e6U, 0x42424242U, 0x68686868U, | ||
| 356 | 0x41414141U, 0x99999999U, 0x2d2d2d2dU, 0x0f0f0f0fU, | ||
| 357 | 0xb0b0b0b0U, 0x54545454U, 0xbbbbbbbbU, 0x16161616U, | ||
| 358 | }; | ||
| 359 | static const uint32_t rcon[] = { | ||
| 360 | 0x01000000, 0x02000000, 0x04000000, 0x08000000, | ||
| 361 | 0x10000000, 0x20000000, 0x40000000, 0x80000000, | ||
| 362 | 0x1B000000, 0x36000000, /* for 128-bit blocks, Rijndael never uses more than 10 rcon values */ | ||
| 363 | }; | ||
| 364 | |||
| 365 | #define GETU32(pt) (((uint32_t)(pt)[0] << 24) ^ ((uint32_t)(pt)[1] << 16) ^ ((uint32_t)(pt)[2] << 8) ^ ((uint32_t)(pt)[3])) | ||
| 366 | #define PUTU32(ct, st) { (ct)[0] = (uint8_t)((st) >> 24); (ct)[1] = (uint8_t)((st) >> 16); (ct)[2] = (uint8_t)((st) >> 8); (ct)[3] = (uint8_t)(st); } | ||
| 367 | |||
| 368 | /** | ||
| 369 | * Expand the cipher key into the encryption key schedule. | ||
| 370 | * | ||
| 371 | * @return the number of rounds for the given cipher key size. | ||
| 372 | */ | ||
| 373 | int rijndaelKeySetupEnc128(uint32_t rk[44], const uint8_t cipherKey[] ) { | ||
| 374 | int i = 0; | ||
| 375 | uint32_t temp; | ||
| 376 | |||
| 377 | rk[0] = GETU32(cipherKey ); | ||
| 378 | rk[1] = GETU32(cipherKey + 4); | ||
| 379 | rk[2] = GETU32(cipherKey + 8); | ||
| 380 | rk[3] = GETU32(cipherKey + 12); | ||
| 381 | for (;;) { | ||
| 382 | temp = rk[3]; | ||
| 383 | rk[4] = rk[0] ^ | ||
| 384 | (Te4[(temp >> 16) & 0xff] & 0xff000000) ^ | ||
| 385 | (Te4[(temp >> 8) & 0xff] & 0x00ff0000) ^ | ||
| 386 | (Te4[(temp ) & 0xff] & 0x0000ff00) ^ | ||
| 387 | (Te4[(temp >> 24) ] & 0x000000ff) ^ | ||
| 388 | rcon[i]; | ||
| 389 | rk[5] = rk[1] ^ rk[4]; | ||
| 390 | rk[6] = rk[2] ^ rk[5]; | ||
| 391 | rk[7] = rk[3] ^ rk[6]; | ||
| 392 | if (++i == 10) { | ||
| 393 | return 10; | ||
| 394 | } | ||
| 395 | rk += 4; | ||
| 396 | } | ||
| 397 | } | ||
| 398 | |||
| 399 | void rijndaelEncrypt128(const uint32_t rk[44], const uint8_t pt[16], uint8_t ct[16]) { | ||
| 400 | uint32_t s0, s1, s2, s3, t0, t1, t2, t3; | ||
| 401 | |||
| 402 | /* | ||
| 403 | * map byte array block to cipher state | ||
| 404 | * and add initial round key: | ||
| 405 | */ | ||
| 406 | s0 = GETU32(pt ) ^ rk[0]; | ||
| 407 | s1 = GETU32(pt + 4) ^ rk[1]; | ||
| 408 | s2 = GETU32(pt + 8) ^ rk[2]; | ||
| 409 | s3 = GETU32(pt + 12) ^ rk[3]; | ||
| 410 | /* round 1: */ | ||
| 411 | t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[ 4]; | ||
| 412 | t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[ 5]; | ||
| 413 | t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[ 6]; | ||
| 414 | t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[ 7]; | ||
| 415 | /* round 2: */ | ||
| 416 | s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[ 8]; | ||
| 417 | s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[ 9]; | ||
| 418 | s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[10]; | ||
| 419 | s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[11]; | ||
| 420 | /* round 3: */ | ||
| 421 | t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[12]; | ||
| 422 | t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[13]; | ||
| 423 | t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[14]; | ||
| 424 | t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[15]; | ||
| 425 | /* round 4: */ | ||
| 426 | s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[16]; | ||
| 427 | s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[17]; | ||
| 428 | s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[18]; | ||
| 429 | s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[19]; | ||
| 430 | /* round 5: */ | ||
| 431 | t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[20]; | ||
| 432 | t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[21]; | ||
| 433 | t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[22]; | ||
| 434 | t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[23]; | ||
| 435 | /* round 6: */ | ||
| 436 | s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[24]; | ||
| 437 | s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[25]; | ||
| 438 | s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[26]; | ||
| 439 | s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[27]; | ||
| 440 | /* round 7: */ | ||
| 441 | t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[28]; | ||
| 442 | t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[29]; | ||
| 443 | t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[30]; | ||
| 444 | t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[31]; | ||
| 445 | /* round 8: */ | ||
| 446 | s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[32]; | ||
| 447 | s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[33]; | ||
| 448 | s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[34]; | ||
| 449 | s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[35]; | ||
| 450 | /* round 9: */ | ||
| 451 | t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[36]; | ||
| 452 | t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[37]; | ||
| 453 | t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[38]; | ||
| 454 | t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[39]; | ||
| 455 | |||
| 456 | /* | ||
| 457 | * apply last round and | ||
| 458 | * map cipher state to byte array block: | ||
| 459 | */ | ||
| 460 | s0 = | ||
| 461 | (Te4[(t0 >> 24) ] & 0xff000000) ^ | ||
| 462 | (Te4[(t1 >> 16) & 0xff] & 0x00ff0000) ^ | ||
| 463 | (Te4[(t2 >> 8) & 0xff] & 0x0000ff00) ^ | ||
| 464 | (Te4[(t3 ) & 0xff] & 0x000000ff) ^ | ||
| 465 | rk[40]; | ||
| 466 | PUTU32(ct , s0); | ||
| 467 | s1 = | ||
| 468 | (Te4[(t1 >> 24) ] & 0xff000000) ^ | ||
| 469 | (Te4[(t2 >> 16) & 0xff] & 0x00ff0000) ^ | ||
| 470 | (Te4[(t3 >> 8) & 0xff] & 0x0000ff00) ^ | ||
| 471 | (Te4[(t0 ) & 0xff] & 0x000000ff) ^ | ||
| 472 | rk[41]; | ||
| 473 | PUTU32(ct + 4, s1); | ||
| 474 | s2 = | ||
| 475 | (Te4[(t2 >> 24) ] & 0xff000000) ^ | ||
| 476 | (Te4[(t3 >> 16) & 0xff] & 0x00ff0000) ^ | ||
| 477 | (Te4[(t0 >> 8) & 0xff] & 0x0000ff00) ^ | ||
| 478 | (Te4[(t1 ) & 0xff] & 0x000000ff) ^ | ||
| 479 | rk[42]; | ||
| 480 | PUTU32(ct + 8, s2); | ||
| 481 | s3 = | ||
| 482 | (Te4[(t3 >> 24) ] & 0xff000000) ^ | ||
| 483 | (Te4[(t0 >> 16) & 0xff] & 0x00ff0000) ^ | ||
| 484 | (Te4[(t1 >> 8) & 0xff] & 0x0000ff00) ^ | ||
| 485 | (Te4[(t2 ) & 0xff] & 0x000000ff) ^ | ||
| 486 | rk[43]; | ||
| 487 | PUTU32(ct + 12, s3); | ||
| 488 | } | ||
diff --git a/src/ot_rijndael.h b/src/ot_rijndael.h new file mode 100644 index 0000000..1235215 --- /dev/null +++ b/src/ot_rijndael.h | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. | ||
| 3 | |||
| 4 | The rijndael implementation was taken from | ||
| 5 | |||
| 6 | http://www.cs.ucdavis.edu/~rogaway/ocb/ocb-ref/rijndael-alg-fst.c | ||
| 7 | |||
| 8 | and modified to work with 128 bits (this is 10 rounds) only. */ | ||
| 9 | |||
| 10 | #ifndef OT_RIJNDAEL_H__ | ||
| 11 | #define OT_RIJNDAEL_H__ | ||
| 12 | |||
| 13 | #include <stdint.h> | ||
| 14 | |||
| 15 | int rijndaelKeySetupEnc128(uint32_t rk[44], const uint8_t cipherKey[] ); | ||
| 16 | void rijndaelEncrypt128(const uint32_t rk[44], const uint8_t pt[16], uint8_t ct[16]); | ||
| 17 | |||
| 18 | extern const char *g_version_rijndael_c; | ||
| 19 | |||
| 20 | #endif | ||
diff --git a/src/ot_scan.c b/src/ot_scan.c new file mode 100644 index 0000000..3b8e87c --- /dev/null +++ b/src/ot_scan.c | |||
| @@ -0,0 +1,65 @@ | |||
| 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 <stdint.h> | ||
| 7 | #include <stdlib.h> | ||
| 8 | |||
| 9 | /* Opentracker */ | ||
| 10 | #include "ot_scan.h" | ||
| 11 | |||
| 12 | size_t scan_uint(const char *src, uint64_t *dest, uint64_t max) { | ||
| 13 | unsigned long long value; | ||
| 14 | char *end; | ||
| 15 | |||
| 16 | if (*src < '0' || *src > '9') | ||
| 17 | return 0; | ||
| 18 | |||
| 19 | errno = 0; | ||
| 20 | value = strtoull(src, &end, 10); | ||
| 21 | |||
| 22 | if (errno == ERANGE || value > (unsigned long long)max) | ||
| 23 | return 0; | ||
| 24 | |||
| 25 | *dest = (uint64_t)value; | ||
| 26 | return (size_t)(end - src); | ||
| 27 | } | ||
| 28 | |||
| 29 | size_t scan_uint_bencode(const char *src, uint64_t *dest, uint64_t max) { | ||
| 30 | uint64_t value; | ||
| 31 | size_t consumed = scan_uint(src, &value, max); | ||
| 32 | |||
| 33 | /* bencode writes no leading zeros, so anything longer than a single digit | ||
| 34 | must not begin with one. */ | ||
| 35 | if (consumed > 1 && *src == '0') | ||
| 36 | return 0; | ||
| 37 | |||
| 38 | if (consumed) | ||
| 39 | *dest = value; | ||
| 40 | |||
| 41 | return consumed; | ||
| 42 | } | ||
| 43 | |||
| 44 | int scan_int(const char *src, size_t len, int64_t *dest) { | ||
| 45 | int64_t value = 0; | ||
| 46 | int negate = 0; | ||
| 47 | |||
| 48 | if (len && *src == '-') | ||
| 49 | --len, ++src, negate = 1; | ||
| 50 | if (!len) | ||
| 51 | return -1; | ||
| 52 | |||
| 53 | while (len--) { | ||
| 54 | if (*src < '0' || *src > '9') | ||
| 55 | return -1; | ||
| 56 | /* A twenty character field of nines does not fit, and silently wrapping | ||
| 57 | would magically heal a nonsense announce */ | ||
| 58 | if (value > (INT64_MAX - (*src - '0')) / 10) | ||
| 59 | return -1; | ||
| 60 | value = 10 * value + (*src++ - '0'); | ||
| 61 | } | ||
| 62 | |||
| 63 | *dest = negate ? -value : value; | ||
| 64 | return 0; | ||
| 65 | } | ||
diff --git a/src/ot_scan.h b/src/ot_scan.h new file mode 100644 index 0000000..a79ec13 --- /dev/null +++ b/src/ot_scan.h | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_SCAN_H__ | ||
| 5 | #define OT_SCAN_H__ | ||
| 6 | |||
| 7 | #include <stddef.h> | ||
| 8 | #include <stddef.h> | ||
| 9 | #include <stdint.h> | ||
| 10 | |||
| 11 | /* Read an unsigned decimal number from src into dest and return how many | ||
| 12 | characters were consumed. Returns zero and leaves dest untouched when src | ||
| 13 | does not begin with a digit, or when the value would exceed max. */ | ||
| 14 | size_t scan_uint(const char *src, uint64_t *dest, uint64_t max); | ||
| 15 | |||
| 16 | /* As scan_uint, but following the bencode integer grammar, which permits a | ||
| 17 | leading zero on zero alone. */ | ||
| 18 | size_t scan_uint_bencode(const char *src, uint64_t *dest, uint64_t max); | ||
| 19 | |||
| 20 | /* Read a signed decimal number that has to take up exactly len characters, | ||
| 21 | which is how a value lifted out of a query string arrives. Returns 0, or | ||
| 22 | -1 leaving dest untouched. */ | ||
| 23 | int scan_int(const char *src, size_t len, int64_t *dest); | ||
| 24 | |||
| 25 | #endif | ||
diff --git a/src/ot_socket.c b/src/ot_socket.c new file mode 100644 index 0000000..79bb01a --- /dev/null +++ b/src/ot_socket.c | |||
| @@ -0,0 +1,310 @@ | |||
| 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 | #define _GNU_SOURCE /* accept4 on glibc */ | ||
| 6 | #include <errno.h> | ||
| 7 | #include <fcntl.h> | ||
| 8 | #include <netinet/in.h> | ||
| 9 | #include <netinet/tcp.h> | ||
| 10 | #include <string.h> | ||
| 11 | #include <sys/socket.h> | ||
| 12 | #include <unistd.h> | ||
| 13 | |||
| 14 | /* Opentracker */ | ||
| 15 | #include "ot_ip.h" | ||
| 16 | #include "ot_socket.h" | ||
| 17 | |||
| 18 | static void sockaddr_from_ip(struct sockaddr_in6 *sa, const ot_ip6 ip, uint16_t port) { | ||
| 19 | memset(sa, 0, sizeof(*sa)); | ||
| 20 | sa->sin6_family = AF_INET6; | ||
| 21 | sa->sin6_port = htons(port); | ||
| 22 | memcpy(&sa->sin6_addr, ip, sizeof(ot_ip6)); | ||
| 23 | } | ||
| 24 | |||
| 25 | /* Linux reports an IPv4 peer on a dual stack socket as a v4 mapped | ||
| 26 | sockaddr_in6, the BSDs and macOS hand back a plain sockaddr_in instead, so | ||
| 27 | both forms have to be read. */ | ||
| 28 | static void ip_from_sockaddr(ot_ip6 ip, uint16_t *port, const struct sockaddr_storage *ss) { | ||
| 29 | if (ss->ss_family == AF_INET) { | ||
| 30 | const struct sockaddr_in *sa = (const struct sockaddr_in *)ss; | ||
| 31 | |||
| 32 | memcpy(ip, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix)); | ||
| 33 | memcpy(ip + sizeof(ip_v4mapped_prefix), &sa->sin_addr, 4); | ||
| 34 | *port = ntohs(sa->sin_port); | ||
| 35 | } else { | ||
| 36 | const struct sockaddr_in6 *sa = (const struct sockaddr_in6 *)ss; | ||
| 37 | |||
| 38 | memcpy(ip, &sa->sin6_addr, sizeof(ot_ip6)); | ||
| 39 | *port = ntohs(sa->sin6_port); | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | int socket_set_blocking(int s, int blocking) { | ||
| 44 | int flags = fcntl(s, F_GETFL, 0); | ||
| 45 | |||
| 46 | if (flags == -1) | ||
| 47 | return -1; | ||
| 48 | return fcntl(s, F_SETFL, blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK)); | ||
| 49 | } | ||
| 50 | |||
| 51 | static int set_nonblocking(int s) { return socket_set_blocking(s, 0); } | ||
| 52 | |||
| 53 | /* Close without letting the failure we are about to report be overwritten */ | ||
| 54 | static int close_and_fail(int s) { | ||
| 55 | int saved = errno; | ||
| 56 | close(s); | ||
| 57 | errno = saved; | ||
| 58 | return -1; | ||
| 59 | } | ||
| 60 | |||
| 61 | static int socket_v6(int type) { | ||
| 62 | int s = socket(PF_INET6, type, 0); | ||
| 63 | int zero = 0; | ||
| 64 | |||
| 65 | if (s == -1) | ||
| 66 | return -1; | ||
| 67 | |||
| 68 | /* A tracker that answered only one family would be half a tracker */ | ||
| 69 | if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)) == -1) | ||
| 70 | return close_and_fail(s); | ||
| 71 | |||
| 72 | if (set_nonblocking(s) == -1) | ||
| 73 | return close_and_fail(s); | ||
| 74 | |||
| 75 | return s; | ||
| 76 | } | ||
| 77 | |||
| 78 | /* SO_REUSEPORT means three different things. Linux spreads datagrams over | ||
| 79 | the group. FreeBSD permits the bind and spreads nothing, which is what | ||
| 80 | SO_REUSEPORT_LB was added for. Darwin hands every datagram to whichever | ||
| 81 | socket bound last, so a group there starves all but one reader. */ | ||
| 82 | #if defined(SO_REUSEPORT_LB) | ||
| 83 | #define OT_BALANCE_OPTION SO_REUSEPORT_LB | ||
| 84 | #elif defined(SO_REUSEPORT) && !defined(__APPLE__) | ||
| 85 | #define OT_BALANCE_OPTION SO_REUSEPORT | ||
| 86 | #endif | ||
| 87 | |||
| 88 | int socket_can_balance(void) { | ||
| 89 | #ifdef OT_BALANCE_OPTION | ||
| 90 | return 1; | ||
| 91 | #else | ||
| 92 | return 0; | ||
| 93 | #endif | ||
| 94 | } | ||
| 95 | |||
| 96 | /* The only place in the tree that needs to know what a kernel calls this */ | ||
| 97 | static void socket_set_sharing(int s, socket_sharing sharing) { | ||
| 98 | int one = 1; | ||
| 99 | |||
| 100 | setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); | ||
| 101 | #ifdef OT_BALANCE_OPTION | ||
| 102 | if (sharing == SOCKET_BALANCED) { | ||
| 103 | setsockopt(s, SOL_SOCKET, OT_BALANCE_OPTION, &one, sizeof(one)); | ||
| 104 | return; | ||
| 105 | } | ||
| 106 | #endif | ||
| 107 | #ifdef SO_REUSEPORT | ||
| 108 | if (sharing != SOCKET_EXCLUSIVE) | ||
| 109 | setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); | ||
| 110 | #endif | ||
| 111 | } | ||
| 112 | |||
| 113 | static int socket_bind_v6(int s, const ot_ip6 ip, uint16_t port, socket_sharing sharing) { | ||
| 114 | struct sockaddr_in6 sa; | ||
| 115 | |||
| 116 | socket_set_sharing(s, sharing); | ||
| 117 | |||
| 118 | sockaddr_from_ip(&sa, ip, port); | ||
| 119 | return bind(s, (struct sockaddr *)&sa, sizeof(sa)); | ||
| 120 | } | ||
| 121 | |||
| 122 | /* Defer the listening socket reporting it to be ready to accept until enough | ||
| 123 | sensible data is available for an accept+read pair. Where available. */ | ||
| 124 | int socket_defer_accept(int s) { | ||
| 125 | #if defined(TCP_DEFER_ACCEPT) | ||
| 126 | int patience = OT_CLIENT_TIMEOUT; | ||
| 127 | |||
| 128 | return setsockopt(s, IPPROTO_TCP, TCP_DEFER_ACCEPT, &patience, sizeof(patience)); | ||
| 129 | #elif defined(SO_ACCEPTFILTER) | ||
| 130 | struct accept_filter_arg filter; | ||
| 131 | const char name[sizeof(filter.af_name)] = "dataready"; | ||
| 132 | |||
| 133 | memset(&filter, 0, sizeof(filter)); | ||
| 134 | memcpy(filter.af_name, name, sizeof(name)); | ||
| 135 | return setsockopt(s, SOL_SOCKET, SO_ACCEPTFILTER, &filter, sizeof(filter)); | ||
| 136 | #else | ||
| 137 | (void)s; | ||
| 138 | return -1; | ||
| 139 | #endif | ||
| 140 | } | ||
| 141 | |||
| 142 | int socket_tcp_listen(const ot_ip6 ip, uint16_t port, socket_sharing sharing) { | ||
| 143 | int s = socket_v6(SOCK_STREAM); | ||
| 144 | |||
| 145 | if (s == -1) | ||
| 146 | return -1; | ||
| 147 | if (socket_bind_v6(s, ip, port, sharing) == -1 || listen(s, SOMAXCONN) == -1) | ||
| 148 | return close_and_fail(s); | ||
| 149 | |||
| 150 | return s; | ||
| 151 | } | ||
| 152 | |||
| 153 | int socket_udp_bind(const ot_ip6 ip, uint16_t port, socket_sharing sharing) { | ||
| 154 | int s = socket_v6(SOCK_DGRAM); | ||
| 155 | |||
| 156 | if (s == -1) | ||
| 157 | return -1; | ||
| 158 | if (socket_bind_v6(s, ip, port, sharing) == -1) | ||
| 159 | return close_and_fail(s); | ||
| 160 | |||
| 161 | return s; | ||
| 162 | } | ||
| 163 | |||
| 164 | int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport) { | ||
| 165 | struct sockaddr_in6 sa; | ||
| 166 | int s = socket_v6(SOCK_STREAM); | ||
| 167 | |||
| 168 | if (s == -1) | ||
| 169 | return -1; | ||
| 170 | if (socket_bind_v6(s, srcip, srcport, SOCKET_EXCLUSIVE) == -1) | ||
| 171 | return close_and_fail(s); | ||
| 172 | |||
| 173 | sockaddr_from_ip(&sa, dstip, dstport); | ||
| 174 | if (connect(s, (struct sockaddr *)&sa, sizeof(sa)) == -1 && errno != EINPROGRESS && errno != EWOULDBLOCK) | ||
| 175 | return close_and_fail(s); | ||
| 176 | |||
| 177 | return s; | ||
| 178 | } | ||
| 179 | |||
| 180 | int socket_is_connected(int s) { | ||
| 181 | int error = 0; | ||
| 182 | socklen_t len = sizeof(error); | ||
| 183 | |||
| 184 | if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) == -1) | ||
| 185 | return 0; | ||
| 186 | if (error) { | ||
| 187 | errno = error; | ||
| 188 | return 0; | ||
| 189 | } | ||
| 190 | return 1; | ||
| 191 | } | ||
| 192 | |||
| 193 | /* Linux hands over a blocking socket whatever the listening socket was, the | ||
| 194 | BSDs and macOS inherit the listener's flags, so the accepted socket has to | ||
| 195 | be told either way. accept4 carries the flag itself, which saves the two | ||
| 196 | fcntl calls it otherwise takes. Darwin has neither accept4 nor the flag. */ | ||
| 197 | int socket_accept(int s, ot_ip6 ip, uint16_t *port) { | ||
| 198 | struct sockaddr_storage ss; | ||
| 199 | socklen_t len = sizeof(ss); | ||
| 200 | int fd; | ||
| 201 | |||
| 202 | memset(&ss, 0, sizeof(ss)); | ||
| 203 | #ifdef SOCK_NONBLOCK | ||
| 204 | if ((fd = accept4(s, (struct sockaddr *)&ss, &len, SOCK_NONBLOCK)) == -1) | ||
| 205 | return -1; | ||
| 206 | #else | ||
| 207 | if ((fd = accept(s, (struct sockaddr *)&ss, &len)) == -1) | ||
| 208 | return -1; | ||
| 209 | if (set_nonblocking(fd) == -1) | ||
| 210 | return close_and_fail(fd); | ||
| 211 | #endif | ||
| 212 | |||
| 213 | ip_from_sockaddr(ip, port, &ss); | ||
| 214 | return fd; | ||
| 215 | } | ||
| 216 | |||
| 217 | ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port) { | ||
| 218 | struct sockaddr_storage ss; | ||
| 219 | socklen_t sslen = sizeof(ss); | ||
| 220 | ssize_t received; | ||
| 221 | |||
| 222 | memset(&ss, 0, sizeof(ss)); | ||
| 223 | received = recvfrom(s, buf, len, 0, (struct sockaddr *)&ss, &sslen); | ||
| 224 | if (received == -1) | ||
| 225 | return -1; | ||
| 226 | |||
| 227 | ip_from_sockaddr(ip, port, &ss); | ||
| 228 | return received; | ||
| 229 | } | ||
| 230 | |||
| 231 | ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port) { | ||
| 232 | struct sockaddr_in6 sa; | ||
| 233 | |||
| 234 | sockaddr_from_ip(&sa, ip, port); | ||
| 235 | return sendto(s, buf, len, 0, (struct sockaddr *)&sa, sizeof(sa)); | ||
| 236 | } | ||
| 237 | |||
| 238 | #ifdef WANT_SYNC_LIVE | ||
| 239 | static void sockaddr4_from_ip(struct sockaddr_in *sa, const ot_ip6 ip, uint16_t port) { | ||
| 240 | memset(sa, 0, sizeof(*sa)); | ||
| 241 | sa->sin_family = AF_INET; | ||
| 242 | sa->sin_port = htons(port); | ||
| 243 | memcpy(&sa->sin_addr, ip + sizeof(ip_v4mapped_prefix), 4); | ||
| 244 | } | ||
| 245 | |||
| 246 | /* A multicast group is meant to have several listeners, so these are always | ||
| 247 | shared */ | ||
| 248 | static int socket_v4_bind(const ot_ip6 ip, uint16_t port) { | ||
| 249 | struct sockaddr_in sa; | ||
| 250 | int s; | ||
| 251 | |||
| 252 | if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1) | ||
| 253 | return -1; | ||
| 254 | |||
| 255 | socket_set_sharing(s, SOCKET_SHARED); | ||
| 256 | |||
| 257 | sockaddr4_from_ip(&sa, ip, port); | ||
| 258 | if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1) | ||
| 259 | return close_and_fail(s); | ||
| 260 | |||
| 261 | return s; | ||
| 262 | } | ||
| 263 | |||
| 264 | int socket_mcast_join(const ot_ip6 group, const ot_ip6 interface, uint16_t port) { | ||
| 265 | struct ip_mreq mreq; | ||
| 266 | ot_ip6 any; | ||
| 267 | int s; | ||
| 268 | |||
| 269 | /* The group is what is listened to, so the socket takes the wildcard and | ||
| 270 | the join names the interface to receive it on */ | ||
| 271 | memcpy(any, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix)); | ||
| 272 | memset(any + sizeof(ip_v4mapped_prefix), 0, 4); | ||
| 273 | |||
| 274 | if ((s = socket_v4_bind(any, port)) == -1) | ||
| 275 | return -1; | ||
| 276 | |||
| 277 | memset(&mreq, 0, sizeof(mreq)); | ||
| 278 | memcpy(&mreq.imr_multiaddr, group + sizeof(ip_v4mapped_prefix), 4); | ||
| 279 | memcpy(&mreq.imr_interface, interface + sizeof(ip_v4mapped_prefix), 4); | ||
| 280 | if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1) | ||
| 281 | return close_and_fail(s); | ||
| 282 | |||
| 283 | return s; | ||
| 284 | } | ||
| 285 | |||
| 286 | int socket_mcast_bind(const ot_ip6 interface, uint16_t port, int loopback) { | ||
| 287 | /* The BSDs and macOS want a byte for the IPv4 multicast options, Linux | ||
| 288 | accepts either that or an int. */ | ||
| 289 | unsigned char ttl = 1, loop = (unsigned char)(loopback != 0); | ||
| 290 | int s = socket_v4_bind(interface, port); | ||
| 291 | |||
| 292 | if (s == -1) | ||
| 293 | return -1; | ||
| 294 | if (set_nonblocking(s) == -1) | ||
| 295 | return close_and_fail(s); | ||
| 296 | |||
| 297 | if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) == -1 || | ||
| 298 | setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) == -1) | ||
| 299 | return close_and_fail(s); | ||
| 300 | |||
| 301 | return s; | ||
| 302 | } | ||
| 303 | |||
| 304 | ssize_t socket_mcast_send(int s, const void *buf, size_t len, const ot_ip6 group, uint16_t port) { | ||
| 305 | struct sockaddr_in sa; | ||
| 306 | |||
| 307 | sockaddr4_from_ip(&sa, group, port); | ||
| 308 | return sendto(s, buf, len, 0, (struct sockaddr *)&sa, sizeof(sa)); | ||
| 309 | } | ||
| 310 | #endif | ||
diff --git a/src/ot_socket.h b/src/ot_socket.h new file mode 100644 index 0000000..5c1d840 --- /dev/null +++ b/src/ot_socket.h | |||
| @@ -0,0 +1,56 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_SOCKET_H__ | ||
| 5 | #define OT_SOCKET_H__ | ||
| 6 | |||
| 7 | #include <stdint.h> | ||
| 8 | #include <sys/types.h> | ||
| 9 | |||
| 10 | #include "trackerlogic.h" | ||
| 11 | |||
| 12 | /* Every socket opentracker opens is an AF_INET6 socket with IPV6_V6ONLY | ||
| 13 | cleared, so one descriptor serves both families and an IPv4 peer arrives | ||
| 14 | as the v4 mapped address */ | ||
| 15 | |||
| 16 | /* How an address may be shared with other sockets. Which socket option says | ||
| 17 | so differs between kernels, which is socket_set_sharing's business. | ||
| 18 | |||
| 19 | SHARED lets another socket bind the same address. BALANCED asks the kernel | ||
| 20 | to spread arriving datagrams over the sockets that did, which is a | ||
| 21 | different thing and not everywhere available: ask socket_can_balance | ||
| 22 | before binding a group that depends on it. */ | ||
| 23 | typedef enum { SOCKET_EXCLUSIVE, SOCKET_SHARED, SOCKET_BALANCED } socket_sharing; | ||
| 24 | |||
| 25 | /* Whether this kernel spreads datagrams over a group of sockets bound to one | ||
| 26 | address. Where it does not, binding a group is worse than one socket with | ||
| 27 | several readers, so the caller wants to know first. */ | ||
| 28 | int socket_can_balance(void); | ||
| 29 | |||
| 30 | /* Create, bind and, for TCP, listen. The returned socket is non blocking | ||
| 31 | Returns -1 on failure with errno set */ | ||
| 32 | int socket_tcp_listen(const ot_ip6 ip, uint16_t port, socket_sharing sharing); | ||
| 33 | int socket_udp_bind(const ot_ip6 ip, uint16_t port, socket_sharing sharing); | ||
| 34 | |||
| 35 | int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport); | ||
| 36 | int socket_is_connected(int s); | ||
| 37 | int socket_accept(int s, ot_ip6 ip, uint16_t *port); | ||
| 38 | int socket_set_blocking(int s, int blocking); | ||
| 39 | int socket_defer_accept(int s); | ||
| 40 | |||
| 41 | ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port); | ||
| 42 | ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port); | ||
| 43 | |||
| 44 | #ifdef WANT_SYNC_LIVE | ||
| 45 | /* | ||
| 46 | socket_mcast_join takes the wildcard address and names the interface in the | ||
| 47 | join, and hands back a blocking socket, since the thread that reads it does | ||
| 48 | nothing else. socket_mcast_bind hands back a non blocking one, because the | ||
| 49 | thread that sends from it is also serving clients. Incoming datagrams are | ||
| 50 | read with socket_recv */ | ||
| 51 | int socket_mcast_join(const ot_ip6 group, const ot_ip6 interface, uint16_t port); | ||
| 52 | int socket_mcast_bind(const ot_ip6 interface, uint16_t port, int loopback); | ||
| 53 | ssize_t socket_mcast_send(int s, const void *buf, size_t len, const ot_ip6 group, uint16_t port); | ||
| 54 | #endif | ||
| 55 | |||
| 56 | #endif | ||
diff --git a/src/ot_stats.c b/src/ot_stats.c new file mode 100644 index 0000000..2d6335b --- /dev/null +++ b/src/ot_stats.c | |||
| @@ -0,0 +1,866 @@ | |||
| 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 <pthread.h> | ||
| 6 | #include <stdatomic.h> | ||
| 7 | #include <stdio.h> | ||
| 8 | #include <stdlib.h> | ||
| 9 | #include <string.h> | ||
| 10 | #include <sys/types.h> | ||
| 11 | #include <sys/uio.h> | ||
| 12 | #include <unistd.h> | ||
| 13 | #ifdef WANT_SYSLOGS | ||
| 14 | #include <syslog.h> | ||
| 15 | #endif | ||
| 16 | |||
| 17 | /* Opentracker */ | ||
| 18 | #include "ot_accesslist.h" | ||
| 19 | #include "ot_event.h" | ||
| 20 | #include "ot_hex.h" | ||
| 21 | #include "ot_iovec.h" | ||
| 22 | #include "ot_ip.h" | ||
| 23 | #include "ot_mutex.h" | ||
| 24 | #include "ot_workqueue.h" | ||
| 25 | #include "ot_fmt.h" | ||
| 26 | #include "ot_stats.h" | ||
| 27 | #include "trackerlogic.h" | ||
| 28 | |||
| 29 | #ifndef NO_FULLSCRAPE_LOGGING | ||
| 30 | #define LOG_TO_STDERR(...) fprintf(stderr, __VA_ARGS__) | ||
| 31 | #else | ||
| 32 | #define LOG_TO_STDERR(...) | ||
| 33 | #endif | ||
| 34 | |||
| 35 | /* Forward declaration */ | ||
| 36 | static void stats_make(size_t *iovec_entries, struct iovec **iovector, ot_tasktype mode); | ||
| 37 | #define OT_STATS_TMPSIZE 8192 | ||
| 38 | |||
| 39 | /* Each serving thread keeps its own statistics tally. The list is linked | ||
| 40 | through intrusive next pointers within the workstruct. | ||
| 41 | |||
| 42 | Registration happens once per thread and the adding up once per statistics | ||
| 43 | request, so this lock is never on anybody's hot path. */ | ||
| 44 | static struct ot_workstruct *g_counter_contexts; | ||
| 45 | static ot_counter g_retired[OT_COUNTER_COUNT]; | ||
| 46 | static pthread_mutex_t g_counter_mutex = PTHREAD_MUTEX_INITIALIZER; | ||
| 47 | |||
| 48 | void stats_counters_register(struct ot_workstruct *ws) { | ||
| 49 | pthread_mutex_lock(&g_counter_mutex); | ||
| 50 | ws->counters_next = g_counter_contexts; | ||
| 51 | g_counter_contexts = ws; | ||
| 52 | pthread_mutex_unlock(&g_counter_mutex); | ||
| 53 | } | ||
| 54 | |||
| 55 | void stats_counters_retire(struct ot_workstruct *ws) { | ||
| 56 | struct ot_workstruct **link; | ||
| 57 | size_t i; | ||
| 58 | |||
| 59 | pthread_mutex_lock(&g_counter_mutex); | ||
| 60 | for (link = &g_counter_contexts; *link; link = &(*link)->counters_next) | ||
| 61 | if (*link == ws) { | ||
| 62 | *link = ws->counters_next; | ||
| 63 | break; | ||
| 64 | } | ||
| 65 | |||
| 66 | /* When retiring a worker thread, collect the counter tally in the global */ | ||
| 67 | for (i = 0; i < OT_COUNTER_COUNT; ++i) | ||
| 68 | atomic_fetch_add_explicit(g_retired + i, atomic_load_explicit(ws->counters + i, memory_order_relaxed), memory_order_relaxed); | ||
| 69 | pthread_mutex_unlock(&g_counter_mutex); | ||
| 70 | } | ||
| 71 | |||
| 72 | /* What every thread has counted so far, added together */ | ||
| 73 | static void counters_total(unsigned long long *total) { | ||
| 74 | struct ot_workstruct *ws; | ||
| 75 | size_t i; | ||
| 76 | |||
| 77 | pthread_mutex_lock(&g_counter_mutex); | ||
| 78 | for (i = 0; i < OT_COUNTER_COUNT; ++i) | ||
| 79 | total[i] = atomic_load_explicit(g_retired + i, memory_order_relaxed); | ||
| 80 | for (ws = g_counter_contexts; ws; ws = ws->counters_next) | ||
| 81 | for (i = 0; i < OT_COUNTER_COUNT; ++i) | ||
| 82 | total[i] += atomic_load_explicit(ws->counters + i, memory_order_relaxed); | ||
| 83 | pthread_mutex_unlock(&g_counter_mutex); | ||
| 84 | } | ||
| 85 | |||
| 86 | #define COUNT(index, by) atomic_fetch_add_explicit(ws->counters + (index), (unsigned long long)(by), memory_order_relaxed) | ||
| 87 | |||
| 88 | static const char *ot_failed_request_names[] = { | ||
| 89 | "302 Redirect", "400 Parse Error", "400 Invalid Parameter", "400 Invalid Parameter (compact=0)", "400 Not Modest", | ||
| 90 | "402 Payment Required", "403 Access Denied", "404 Not found", "500 Internal Server Error"}; | ||
| 91 | |||
| 92 | static time_t ot_start_time; | ||
| 93 | |||
| 94 | #define STATS_NETWORK_NODE_BITWIDTH 4 | ||
| 95 | #define STATS_NETWORK_NODE_COUNT (1 << STATS_NETWORK_NODE_BITWIDTH) | ||
| 96 | |||
| 97 | #define __BYTE(P, D) (((uint8_t *)P)[D / 8]) | ||
| 98 | #define __MSK (STATS_NETWORK_NODE_COUNT - 1) | ||
| 99 | #define __SHFT(D) ((D ^ STATS_NETWORK_NODE_BITWIDTH) & STATS_NETWORK_NODE_BITWIDTH) | ||
| 100 | |||
| 101 | #define __LDR(P, D) ((__BYTE((P), (D)) >> __SHFT((D))) & __MSK) | ||
| 102 | #define __STR(P, D, V) __BYTE((P), (D)) = (__BYTE((P), (D)) & ~(__MSK << __SHFT((D)))) | ((V) << __SHFT((D))) | ||
| 103 | |||
| 104 | #if 0 | ||
| 105 | // XXX | ||
| 106 | #define STATS_NETWORK_NODE_MAXDEPTH (68 - STATS_NETWORK_NODE_BITWIDTH) | ||
| 107 | #define STATS_NETWORK_NODE_LIMIT (48 - STATS_NETWORK_NODE_BITWIDTH) | ||
| 108 | #endif | ||
| 109 | #define STATS_NETWORK_NODE_MAXDEPTH (28 - STATS_NETWORK_NODE_BITWIDTH) | ||
| 110 | #define STATS_NETWORK_NODE_LIMIT (24 - STATS_NETWORK_NODE_BITWIDTH) | ||
| 111 | |||
| 112 | typedef union stats_network_node stats_network_node; | ||
| 113 | union stats_network_node { | ||
| 114 | size_t counters[STATS_NETWORK_NODE_COUNT]; | ||
| 115 | stats_network_node *children[STATS_NETWORK_NODE_COUNT]; | ||
| 116 | }; | ||
| 117 | |||
| 118 | static int stat_increase_network_count(stats_network_node **pnode, int depth, uintptr_t ip) { | ||
| 119 | int foo = __LDR(ip, depth); | ||
| 120 | stats_network_node *node; | ||
| 121 | |||
| 122 | if (!*pnode) { | ||
| 123 | *pnode = malloc(sizeof(stats_network_node)); | ||
| 124 | if (!*pnode) | ||
| 125 | return -1; | ||
| 126 | memset(*pnode, 0, sizeof(stats_network_node)); | ||
| 127 | } | ||
| 128 | node = *pnode; | ||
| 129 | |||
| 130 | if (depth < STATS_NETWORK_NODE_MAXDEPTH) | ||
| 131 | return stat_increase_network_count(node->children + foo, depth + STATS_NETWORK_NODE_BITWIDTH, ip); | ||
| 132 | |||
| 133 | node->counters[foo]++; | ||
| 134 | return 0; | ||
| 135 | } | ||
| 136 | |||
| 137 | static int stats_shift_down_network_count(stats_network_node **node, int depth, int shift) { | ||
| 138 | size_t i, rest = 0; | ||
| 139 | |||
| 140 | if (!*node) | ||
| 141 | return 0; | ||
| 142 | |||
| 143 | for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i) | ||
| 144 | if (depth < STATS_NETWORK_NODE_MAXDEPTH) | ||
| 145 | rest += stats_shift_down_network_count((*node)->children + i, depth + STATS_NETWORK_NODE_BITWIDTH, shift); | ||
| 146 | else | ||
| 147 | rest += (*node)->counters[i] >>= shift; | ||
| 148 | |||
| 149 | if (!rest) { | ||
| 150 | free(*node); | ||
| 151 | *node = NULL; | ||
| 152 | } | ||
| 153 | |||
| 154 | return rest; | ||
| 155 | } | ||
| 156 | |||
| 157 | static size_t stats_get_highscore_networks(stats_network_node *node, int depth, ot_ip6 node_value, size_t *scores, ot_ip6 *networks, int network_count, | ||
| 158 | int limit) { | ||
| 159 | size_t i, score = 0; | ||
| 160 | |||
| 161 | if (!node) | ||
| 162 | return 0; | ||
| 163 | |||
| 164 | if (depth < limit) { | ||
| 165 | for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i) | ||
| 166 | if (node->children[i]) { | ||
| 167 | __STR(node_value, depth, i); | ||
| 168 | score += stats_get_highscore_networks(node->children[i], depth + STATS_NETWORK_NODE_BITWIDTH, node_value, scores, networks, network_count, limit); | ||
| 169 | } | ||
| 170 | return score; | ||
| 171 | } | ||
| 172 | |||
| 173 | if (depth > limit && depth < STATS_NETWORK_NODE_MAXDEPTH) { | ||
| 174 | for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i) | ||
| 175 | if (node->children[i]) | ||
| 176 | score += stats_get_highscore_networks(node->children[i], depth + STATS_NETWORK_NODE_BITWIDTH, node_value, scores, networks, network_count, limit); | ||
| 177 | return score; | ||
| 178 | } | ||
| 179 | |||
| 180 | if (depth > limit && depth == STATS_NETWORK_NODE_MAXDEPTH) { | ||
| 181 | for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i) | ||
| 182 | score += node->counters[i]; | ||
| 183 | return score; | ||
| 184 | } | ||
| 185 | |||
| 186 | /* if( depth == limit ) */ | ||
| 187 | for (i = 0; i < STATS_NETWORK_NODE_COUNT; ++i) { | ||
| 188 | int j = 1; | ||
| 189 | size_t node_score; | ||
| 190 | |||
| 191 | if (depth == STATS_NETWORK_NODE_MAXDEPTH) | ||
| 192 | node_score = node->counters[i]; | ||
| 193 | else | ||
| 194 | node_score = stats_get_highscore_networks(node->children[i], depth + STATS_NETWORK_NODE_BITWIDTH, node_value, scores, networks, network_count, limit); | ||
| 195 | |||
| 196 | score += node_score; | ||
| 197 | |||
| 198 | if (node_score <= scores[0]) | ||
| 199 | continue; | ||
| 200 | |||
| 201 | __STR(node_value, depth, i); | ||
| 202 | while (j < network_count && node_score > scores[j]) | ||
| 203 | ++j; | ||
| 204 | --j; | ||
| 205 | |||
| 206 | memmove(scores, scores + 1, j * sizeof(*scores)); | ||
| 207 | memmove(networks, networks + 1, j * sizeof(*networks)); | ||
| 208 | scores[j] = node_score; | ||
| 209 | memcpy(networks + j, node_value, sizeof(*networks)); | ||
| 210 | } | ||
| 211 | |||
| 212 | return score; | ||
| 213 | } | ||
| 214 | |||
| 215 | static void stats_return_busy_networks(char **reply, const char *reply_end, stats_network_node *tree, size_t amount, int limit) { | ||
| 216 | ot_ip6 networks[amount]; | ||
| 217 | ot_ip6 node_value; | ||
| 218 | size_t scores[amount]; | ||
| 219 | size_t i; | ||
| 220 | |||
| 221 | memset(scores, 0, sizeof(scores)); | ||
| 222 | memset(networks, 0, sizeof(networks)); | ||
| 223 | memset(node_value, 0, sizeof(node_value)); | ||
| 224 | |||
| 225 | stats_get_highscore_networks(tree, 0, node_value, scores, networks, amount, limit); | ||
| 226 | |||
| 227 | fmt_append(reply, reply_end, "Networks, limit /%d:\n", limit + STATS_NETWORK_NODE_BITWIDTH); | ||
| 228 | for (i = amount; i > 0; --i) { | ||
| 229 | if (scores[i - 1]) { | ||
| 230 | char ip_readable[64]; | ||
| 231 | |||
| 232 | ip_readable[ip_format(ip_readable, networks[i - 1])] = 0; | ||
| 233 | fmt_append(reply, reply_end, "%08zd: %s\n", scores[i - 1], ip_readable); | ||
| 234 | } | ||
| 235 | } | ||
| 236 | fmt_append(reply, reply_end, "\n"); | ||
| 237 | } | ||
| 238 | |||
| 239 | static void stats_slash24s_txt(char **reply, const char *reply_end, size_t amount) { | ||
| 240 | stats_network_node *slash24s_network_counters_root = NULL; | ||
| 241 | size_t i, bucket, peer_size = OT_PEER_SIZE4; | ||
| 242 | |||
| 243 | for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { | ||
| 244 | ot_vector *torrents_list = mutex_bucket_lock(bucket); | ||
| 245 | for (i = 0; i < torrents_list->size; ++i) { | ||
| 246 | ot_peerlist *peer_list = (((ot_torrent *)(torrents_list->data))[i]).peer_list4; | ||
| 247 | ot_vector *bucket_list = &peer_list->peers; | ||
| 248 | int num_buckets = 1; | ||
| 249 | |||
| 250 | if (OT_PEERLIST_HASBUCKETS(peer_list)) { | ||
| 251 | num_buckets = bucket_list->size; | ||
| 252 | bucket_list = (ot_vector *)bucket_list->data; | ||
| 253 | } | ||
| 254 | |||
| 255 | while (num_buckets--) { | ||
| 256 | ot_peer *peers = (ot_peer *)bucket_list->data; | ||
| 257 | size_t numpeers = bucket_list->size; | ||
| 258 | while (numpeers--) { | ||
| 259 | if (stat_increase_network_count(&slash24s_network_counters_root, 0, (uintptr_t)(peers))) | ||
| 260 | goto bailout_unlock; | ||
| 261 | peers += peer_size; | ||
| 262 | } | ||
| 263 | ++bucket_list; | ||
| 264 | } | ||
| 265 | } | ||
| 266 | mutex_bucket_unlock(bucket, 0); | ||
| 267 | if (!g_opentracker_running) | ||
| 268 | goto done; | ||
| 269 | } | ||
| 270 | |||
| 271 | /* The tree is built. Now analyze */ | ||
| 272 | stats_return_busy_networks(reply, reply_end, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_MAXDEPTH); | ||
| 273 | stats_return_busy_networks(reply, reply_end, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_LIMIT); | ||
| 274 | goto done; | ||
| 275 | |||
| 276 | bailout_unlock: | ||
| 277 | mutex_bucket_unlock(bucket, 0); | ||
| 278 | done: | ||
| 279 | stats_shift_down_network_count(&slash24s_network_counters_root, 0, sizeof(int) * 8 - 1); | ||
| 280 | } | ||
| 281 | |||
| 282 | #ifdef WANT_SPOT_WOODPECKER | ||
| 283 | static stats_network_node *stats_woodpeckers_tree; | ||
| 284 | static pthread_mutex_t g_woodpeckers_mutex = PTHREAD_MUTEX_INITIALIZER; | ||
| 285 | |||
| 286 | static void stats_return_woodpeckers(char **reply, const char *reply_end, int amount) { | ||
| 287 | pthread_mutex_lock(&g_woodpeckers_mutex); | ||
| 288 | stats_return_busy_networks(reply, reply_end, stats_woodpeckers_tree, amount, STATS_NETWORK_NODE_MAXDEPTH); | ||
| 289 | pthread_mutex_unlock(&g_woodpeckers_mutex); | ||
| 290 | } | ||
| 291 | #endif | ||
| 292 | |||
| 293 | typedef struct { | ||
| 294 | unsigned long long torrent_count; | ||
| 295 | unsigned long long peer_count; | ||
| 296 | unsigned long long seed_count; | ||
| 297 | } torrent_stats; | ||
| 298 | |||
| 299 | static int torrent_statter(ot_torrent *torrent, uintptr_t data) { | ||
| 300 | torrent_stats *stats = (torrent_stats *)data; | ||
| 301 | stats->torrent_count++; | ||
| 302 | stats->peer_count += torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; | ||
| 303 | stats->seed_count += torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; | ||
| 304 | return 0; | ||
| 305 | } | ||
| 306 | |||
| 307 | /* Converter function from memory to human readable hex strings */ | ||
| 308 | typedef struct { | ||
| 309 | size_t val; | ||
| 310 | ot_hash hash; | ||
| 311 | } ot_record; | ||
| 312 | |||
| 313 | static inline void stats_insert_into_highscore(size_t val, ot_record* list, size_t amount, ot_hash* hash) { | ||
| 314 | size_t i; | ||
| 315 | for (i = 0; (i < amount) && (list[i].val > val); ++i) | ||
| 316 | {} | ||
| 317 | /* if we score better than a record in the list, we need to make room in the high | ||
| 318 | score array by moving lower scoring entries up */ | ||
| 319 | if (i < amount) { | ||
| 320 | memmove(list + i + 1, list + i, (amount - i - 1) * sizeof(ot_record)); | ||
| 321 | |||
| 322 | memcpy(&list[i].hash, hash, sizeof(ot_hash)); | ||
| 323 | list[i].val = val; | ||
| 324 | } | ||
| 325 | } | ||
| 326 | |||
| 327 | |||
| 328 | /* Fetches stats from tracker */ | ||
| 329 | void stats_top_txt(char **reply, const char *reply_end, size_t amount) { | ||
| 330 | size_t j, idx, bucket; | ||
| 331 | ot_record top100s[100], top100c[100], top100l[100]; | ||
| 332 | char hex_out[42]; | ||
| 333 | |||
| 334 | if (amount > 100) | ||
| 335 | amount = 100; | ||
| 336 | |||
| 337 | memset(top100s, 0, sizeof(top100s)); | ||
| 338 | memset(top100c, 0, sizeof(top100c)); | ||
| 339 | memset(top100l, 0, sizeof(top100l)); | ||
| 340 | |||
| 341 | /* Iterate over complete torrent list */ | ||
| 342 | for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { | ||
| 343 | ot_vector *torrents_list = mutex_bucket_lock(bucket); | ||
| 344 | for (j = 0; j < torrents_list->size; ++j) { | ||
| 345 | ot_torrent *torrent = (ot_torrent *)(torrents_list->data) + j; | ||
| 346 | size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; | ||
| 347 | size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; | ||
| 348 | size_t leech_count = peer_count - seed_count; | ||
| 349 | |||
| 350 | stats_insert_into_highscore(peer_count, top100c, amount, &torrent->hash); | ||
| 351 | stats_insert_into_highscore(seed_count, top100s, amount, &torrent->hash); | ||
| 352 | stats_insert_into_highscore(leech_count, top100l, amount, &torrent->hash); | ||
| 353 | } | ||
| 354 | mutex_bucket_unlock(bucket, 0); | ||
| 355 | if (!g_opentracker_running) | ||
| 356 | return; | ||
| 357 | } | ||
| 358 | |||
| 359 | fmt_append(reply, reply_end, "Top %zd torrents by peers:\n", amount); | ||
| 360 | for (idx = 0; idx < amount; ++idx) | ||
| 361 | if (top100c[idx].val) | ||
| 362 | fmt_append(reply, reply_end, "\t%zd\t%s\n", top100c[idx].val, hex_from_hash(hex_out, top100c[idx].hash)); | ||
| 363 | fmt_append(reply, reply_end, "Top %zd torrents by seeds:\n", amount); | ||
| 364 | for (idx = 0; idx < amount; ++idx) | ||
| 365 | if (top100s[idx].val) | ||
| 366 | fmt_append(reply, reply_end, "\t%zd\t%s\n", top100s[idx].val, hex_from_hash(hex_out, top100s[idx].hash)); | ||
| 367 | fmt_append(reply, reply_end, "Top %zd torrents by leechers:\n", amount); | ||
| 368 | for (idx = 0; idx < amount; ++idx) | ||
| 369 | if (top100l[idx].val) | ||
| 370 | fmt_append(reply, reply_end, "\t%zd\t%s\n", top100l[idx].val, hex_from_hash(hex_out, top100l[idx].hash)); | ||
| 371 | } | ||
| 372 | |||
| 373 | static unsigned long events_per_time(unsigned long long events, time_t t) { return events / ((unsigned int)t ? (unsigned int)t : 1); } | ||
| 374 | |||
| 375 | static void stats_connections_mrtg(char **reply, const char *reply_end) { | ||
| 376 | ot_time t = time(NULL) - ot_start_time; | ||
| 377 | unsigned long long total[OT_COUNTER_COUNT]; | ||
| 378 | |||
| 379 | counters_total(total); | ||
| 380 | |||
| 381 | fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.", | ||
| 382 | total[OT_COUNTER_TCP_CONNECTIONS] + total[OT_COUNTER_UDP_CONNECTIONS], | ||
| 383 | total[OT_COUNTER_TCP_ANNOUNCES] + total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], (int)t, (int)(t / 3600), | ||
| 384 | events_per_time(total[OT_COUNTER_TCP_CONNECTIONS] + total[OT_COUNTER_UDP_CONNECTIONS], t), | ||
| 385 | events_per_time(total[OT_COUNTER_TCP_ANNOUNCES] + total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], t)); | ||
| 386 | } | ||
| 387 | |||
| 388 | static void stats_udpconnections_mrtg(char **reply, const char *reply_end) { | ||
| 389 | ot_time t = time(NULL) - ot_start_time; | ||
| 390 | unsigned long long total[OT_COUNTER_COUNT]; | ||
| 391 | |||
| 392 | counters_total(total); | ||
| 393 | |||
| 394 | fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker udp4 stats, %lu conns/s :: %lu success/s.", total[OT_COUNTER_UDP_CONNECTIONS], | ||
| 395 | total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], (int)t, (int)(t / 3600), | ||
| 396 | events_per_time(total[OT_COUNTER_UDP_CONNECTIONS], t), | ||
| 397 | events_per_time(total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], t)); | ||
| 398 | } | ||
| 399 | |||
| 400 | static void stats_tcpconnections_mrtg(char **reply, const char *reply_end) { | ||
| 401 | time_t t = time(NULL) - ot_start_time; | ||
| 402 | unsigned long long total[OT_COUNTER_COUNT]; | ||
| 403 | |||
| 404 | counters_total(total); | ||
| 405 | |||
| 406 | fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker tcp4 stats, %lu conns/s :: %lu success/s.", total[OT_COUNTER_TCP_CONNECTIONS], | ||
| 407 | total[OT_COUNTER_TCP_ANNOUNCES], (int)t, (int)(t / 3600), events_per_time(total[OT_COUNTER_TCP_CONNECTIONS], t), | ||
| 408 | events_per_time(total[OT_COUNTER_TCP_ANNOUNCES], t)); | ||
| 409 | } | ||
| 410 | |||
| 411 | static void stats_scrape_mrtg(char **reply, const char *reply_end) { | ||
| 412 | time_t t = time(NULL) - ot_start_time; | ||
| 413 | unsigned long long total[OT_COUNTER_COUNT]; | ||
| 414 | |||
| 415 | counters_total(total); | ||
| 416 | |||
| 417 | fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker scrape stats, %lu scrape/s (tcp and udp)", | ||
| 418 | total[OT_COUNTER_TCP_SCRAPES], total[OT_COUNTER_UDP_SCRAPES], (int)t, (int)(t / 3600), | ||
| 419 | events_per_time((total[OT_COUNTER_TCP_SCRAPES] + total[OT_COUNTER_UDP_SCRAPES]), t)); | ||
| 420 | } | ||
| 421 | |||
| 422 | static void stats_fullscrapes_mrtg(char **reply, const char *reply_end) { | ||
| 423 | ot_time t = time(NULL) - ot_start_time; | ||
| 424 | unsigned long long total[OT_COUNTER_COUNT]; | ||
| 425 | |||
| 426 | counters_total(total); | ||
| 427 | |||
| 428 | fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker full scrape stats, %lu conns/s :: %lu bytes/s.", | ||
| 429 | total[OT_COUNTER_FULLSCRAPES] * 1000, total[OT_COUNTER_FULLSCRAPE_BYTES], (int)t, (int)(t / 3600), | ||
| 430 | events_per_time(total[OT_COUNTER_FULLSCRAPES], t), | ||
| 431 | events_per_time(total[OT_COUNTER_FULLSCRAPE_BYTES], t)); | ||
| 432 | } | ||
| 433 | |||
| 434 | static void stats_peers_mrtg(char **reply, const char *reply_end) { | ||
| 435 | torrent_stats stats = {0, 0, 0}; | ||
| 436 | |||
| 437 | iterate_all_torrents(torrent_statter, (uintptr_t)&stats); | ||
| 438 | |||
| 439 | fmt_append(reply, reply_end, "%llu\n%llu\nopentracker serving %llu torrents\nopentracker", stats.peer_count, stats.seed_count, | ||
| 440 | stats.torrent_count); | ||
| 441 | } | ||
| 442 | |||
| 443 | static void stats_torrents_mrtg(char **reply, const char *reply_end) { | ||
| 444 | size_t torrent_count = mutex_get_torrent_count(); | ||
| 445 | |||
| 446 | fmt_append(reply, reply_end, "%zd\n%zd\nopentracker serving %zd torrents\nopentracker", torrent_count, (size_t)0, torrent_count); | ||
| 447 | } | ||
| 448 | |||
| 449 | static void stats_httperrors_txt(char **reply, const char *reply_end) { | ||
| 450 | unsigned long long total[OT_COUNTER_COUNT]; | ||
| 451 | |||
| 452 | counters_total(total); | ||
| 453 | |||
| 454 | fmt_append(reply, reply_end, "302 RED %llu\n400 ... %llu\n400 PAR %llu\n400 COM %llu\n403 IP %llu\n404 INV %llu\n500 SRV %llu\n", | ||
| 455 | total[OT_COUNTER_FAILED + 0], total[OT_COUNTER_FAILED + 1], total[OT_COUNTER_FAILED + 2], total[OT_COUNTER_FAILED + 3], | ||
| 456 | total[OT_COUNTER_FAILED + 4], total[OT_COUNTER_FAILED + 5], total[OT_COUNTER_FAILED + 6]); | ||
| 457 | } | ||
| 458 | |||
| 459 | static void stats_return_renew_bucket(char **reply, const char *reply_end) { | ||
| 460 | unsigned long long total[OT_COUNTER_COUNT]; | ||
| 461 | |||
| 462 | counters_total(total); | ||
| 463 | |||
| 464 | int i; | ||
| 465 | |||
| 466 | for (i = 0; i < OT_PEER_TIMEOUT; ++i) | ||
| 467 | fmt_append(reply, reply_end, "%02i %llu\n", i, total[OT_COUNTER_RENEWED + i]); | ||
| 468 | } | ||
| 469 | |||
| 470 | static void stats_return_sync_mrtg(char **reply, const char *reply_end) { | ||
| 471 | ot_time t = time(NULL) - ot_start_time; | ||
| 472 | unsigned long long total[OT_COUNTER_COUNT]; | ||
| 473 | |||
| 474 | counters_total(total); | ||
| 475 | |||
| 476 | fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.", total[OT_COUNTER_SYNCS], 0LL, | ||
| 477 | (int)t, (int)(t / 3600), events_per_time(total[OT_COUNTER_TCP_CONNECTIONS] + total[OT_COUNTER_UDP_CONNECTIONS], t), | ||
| 478 | events_per_time(total[OT_COUNTER_TCP_ANNOUNCES] + total[OT_COUNTER_UDP_ANNOUNCES] + total[OT_COUNTER_UDP_CONNECTS], t)); | ||
| 479 | } | ||
| 480 | |||
| 481 | static void stats_return_completed_mrtg(char **reply, const char *reply_end) { | ||
| 482 | ot_time t = time(NULL) - ot_start_time; | ||
| 483 | unsigned long long total[OT_COUNTER_COUNT]; | ||
| 484 | |||
| 485 | counters_total(total); | ||
| 486 | |||
| 487 | |||
| 488 | fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker, %lu completed/h.", total[OT_COUNTER_COMPLETED], 0LL, (int)t, (int)(t / 3600), | ||
| 489 | events_per_time(total[OT_COUNTER_COMPLETED], t / 3600)); | ||
| 490 | } | ||
| 491 | |||
| 492 | #ifdef WANT_LOG_NUMWANT | ||
| 493 | extern unsigned long long numwants[201]; | ||
| 494 | static void stats_return_numwants(char **reply, const char *reply_end) { | ||
| 495 | int i; | ||
| 496 | for (i = 0; i <= 200; ++i) | ||
| 497 | fmt_append(reply, reply_end, "%03d => %lld\n", i, numwants[i]); | ||
| 498 | } | ||
| 499 | #endif | ||
| 500 | |||
| 501 | static void stats_return_everything(char **reply, const char *reply_end) { | ||
| 502 | unsigned long long total[OT_COUNTER_COUNT]; | ||
| 503 | |||
| 504 | counters_total(total); | ||
| 505 | |||
| 506 | torrent_stats stats = {0, 0, 0}; | ||
| 507 | int i; | ||
| 508 | |||
| 509 | iterate_all_torrents(torrent_statter, (uintptr_t)&stats); | ||
| 510 | |||
| 511 | fmt_append(reply, reply_end, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); | ||
| 512 | fmt_append(reply, reply_end, "<stats>\n"); | ||
| 513 | fmt_append(reply, reply_end, " <uptime>%llu</uptime>\n", (unsigned long long)(time(NULL) - ot_start_time)); | ||
| 514 | fmt_append(reply, reply_end, " <torrents>\n"); | ||
| 515 | fmt_append(reply, reply_end, " <count_mutex>%zd</count_mutex>\n", mutex_get_torrent_count()); | ||
| 516 | fmt_append(reply, reply_end, " <count_iterator>%llu</count_iterator>\n", stats.torrent_count); | ||
| 517 | fmt_append(reply, reply_end, " </torrents>\n"); | ||
| 518 | fmt_append(reply, reply_end, " <peers>\n <count>%llu</count>\n </peers>\n", stats.peer_count); | ||
| 519 | fmt_append(reply, reply_end, " <seeds>\n <count>%llu</count>\n </seeds>\n", stats.seed_count); | ||
| 520 | fmt_append(reply, reply_end, " <completed>\n <count>%llu</count>\n </completed>\n", total[OT_COUNTER_COMPLETED]); | ||
| 521 | fmt_append(reply, reply_end, " <connections>\n"); | ||
| 522 | fmt_append(reply, reply_end, " <tcp>\n <accept>%llu</accept>\n <announce>%llu</announce>\n <scrape>%llu</scrape>\n </tcp>\n", | ||
| 523 | total[OT_COUNTER_TCP_CONNECTIONS], total[OT_COUNTER_TCP_ANNOUNCES], total[OT_COUNTER_TCP_SCRAPES]); | ||
| 524 | fmt_append(reply, reply_end, " <udp>\n <overall>%llu</overall>\n <connect>%llu</connect>\n <announce>%llu</announce>\n <scrape>%llu</scrape>\n <missmatch>%llu</missmatch>\n </udp>\n", | ||
| 525 | total[OT_COUNTER_UDP_CONNECTIONS], total[OT_COUNTER_UDP_CONNECTS], total[OT_COUNTER_UDP_ANNOUNCES], total[OT_COUNTER_UDP_SCRAPES], | ||
| 526 | total[OT_COUNTER_UDP_CONNID_MISSMATCH]); | ||
| 527 | fmt_append(reply, reply_end, " <livesync>\n <count>%llu</count>\n </livesync>\n", total[OT_COUNTER_SYNCS]); | ||
| 528 | fmt_append(reply, reply_end, " </connections>\n"); | ||
| 529 | fmt_append(reply, reply_end, " <debug>\n"); | ||
| 530 | fmt_append(reply, reply_end, " <renew>\n"); | ||
| 531 | for (i = 0; i < OT_PEER_TIMEOUT; ++i) | ||
| 532 | fmt_append(reply, reply_end, " <count interval=\"%02i\">%llu</count>\n", i, total[OT_COUNTER_RENEWED + i]); | ||
| 533 | fmt_append(reply, reply_end, " </renew>\n"); | ||
| 534 | fmt_append(reply, reply_end, " <http_error>\n"); | ||
| 535 | for (i = 0; i < CODE_HTTPERROR_COUNT; ++i) | ||
| 536 | fmt_append(reply, reply_end, " <count code=\"%s\">%llu</count>\n", ot_failed_request_names[i], total[OT_COUNTER_FAILED + i]); | ||
| 537 | fmt_append(reply, reply_end, " </http_error>\n"); | ||
| 538 | fmt_append(reply, reply_end, " <mutex_stall>\n <count>%llu</count>\n </mutex_stall>\n", total[OT_COUNTER_STALLS]); | ||
| 539 | fmt_append(reply, reply_end, " </debug>\n"); | ||
| 540 | fmt_append(reply, reply_end, "</stats>"); | ||
| 541 | } | ||
| 542 | |||
| 543 | /* The names as the Makefile spells them, so that what a binary reports maps | ||
| 544 | straight back onto the FEATURES lines it was built from */ | ||
| 545 | static const char *const build_features[] = { | ||
| 546 | #ifdef WANT_V4_ONLY | ||
| 547 | "WANT_V4_ONLY", | ||
| 548 | #endif | ||
| 549 | #ifdef WANT_ACCESSLIST_BLACK | ||
| 550 | "WANT_ACCESSLIST_BLACK", | ||
| 551 | #endif | ||
| 552 | #ifdef WANT_ACCESSLIST_WHITE | ||
| 553 | "WANT_ACCESSLIST_WHITE", | ||
| 554 | #endif | ||
| 555 | #ifdef WANT_DYNAMIC_ACCESSLIST | ||
| 556 | "WANT_DYNAMIC_ACCESSLIST", | ||
| 557 | #endif | ||
| 558 | #ifdef WANT_SYNC_LIVE | ||
| 559 | "WANT_SYNC_LIVE", | ||
| 560 | #endif | ||
| 561 | #ifdef WANT_IP_FROM_QUERY_STRING | ||
| 562 | "WANT_IP_FROM_QUERY_STRING", | ||
| 563 | #endif | ||
| 564 | #ifdef WANT_IP_FROM_PROXY | ||
| 565 | "WANT_IP_FROM_PROXY", | ||
| 566 | #endif | ||
| 567 | #ifdef WANT_COMPRESSION_GZIP | ||
| 568 | "WANT_COMPRESSION_GZIP", | ||
| 569 | #endif | ||
| 570 | #ifdef WANT_COMPRESSION_GZIP_ALWAYS | ||
| 571 | "WANT_COMPRESSION_GZIP_ALWAYS", | ||
| 572 | #endif | ||
| 573 | #ifdef WANT_COMPRESSION_ZSTD | ||
| 574 | "WANT_COMPRESSION_ZSTD", | ||
| 575 | #endif | ||
| 576 | #ifdef WANT_COMPRESSION_ZSTD_ALWAYS | ||
| 577 | "WANT_COMPRESSION_ZSTD_ALWAYS", | ||
| 578 | #endif | ||
| 579 | #ifdef WANT_FULLSCRAPE | ||
| 580 | "WANT_FULLSCRAPE", | ||
| 581 | #endif | ||
| 582 | #ifdef WANT_MODEST_FULLSCRAPES | ||
| 583 | "WANT_MODEST_FULLSCRAPES", | ||
| 584 | #endif | ||
| 585 | #ifdef WANT_KEEPALIVE | ||
| 586 | "WANT_KEEPALIVE", | ||
| 587 | #endif | ||
| 588 | #ifdef WANT_RESTRICT_STATS | ||
| 589 | "WANT_RESTRICT_STATS", | ||
| 590 | #endif | ||
| 591 | #ifdef WANT_LOG_NUMWANT | ||
| 592 | "WANT_LOG_NUMWANT", | ||
| 593 | #endif | ||
| 594 | #ifdef WANT_SPOT_WOODPECKER | ||
| 595 | "WANT_SPOT_WOODPECKER", | ||
| 596 | #endif | ||
| 597 | #ifdef WANT_SYSLOGS | ||
| 598 | "WANT_SYSLOGS", | ||
| 599 | #endif | ||
| 600 | #ifdef WANT_DEV_RANDOM | ||
| 601 | "WANT_DEV_RANDOM", | ||
| 602 | #endif | ||
| 603 | #ifdef WANT_INSECURE_RANDOM_FALLBACK | ||
| 604 | "WANT_INSECURE_RANDOM_FALLBACK", | ||
| 605 | #endif | ||
| 606 | #ifdef _DEBUG | ||
| 607 | "_DEBUG", | ||
| 608 | #endif | ||
| 609 | #ifdef _DEBUG_HTTPERROR | ||
| 610 | "_DEBUG_HTTPERROR", | ||
| 611 | #endif | ||
| 612 | #ifdef _DEBUG_RANDOMTORRENTS | ||
| 613 | "_DEBUG_RANDOMTORRENTS", | ||
| 614 | #endif | ||
| 615 | NULL}; | ||
| 616 | |||
| 617 | void stats_return_tracker_features(char **reply, const char *reply_end) { | ||
| 618 | size_t i; | ||
| 619 | |||
| 620 | fmt_append(reply, reply_end, "events: %s\n", event_mechanism()); | ||
| 621 | fmt_append(reply, reply_end, "features:"); | ||
| 622 | for (i = 0; build_features[i]; ++i) | ||
| 623 | fmt_append(reply, reply_end, " %s", build_features[i]); | ||
| 624 | if (!build_features[0]) | ||
| 625 | fmt_append(reply, reply_end, " none"); | ||
| 626 | fmt_append(reply, reply_end, "\n"); | ||
| 627 | } | ||
| 628 | |||
| 629 | void stats_return_tracker_version(char **reply, const char *reply_end) { | ||
| 630 | fmt_append(reply, reply_end, "opentracker %s", OT_VERSION); | ||
| 631 | if (*GIT_VERSION && strcmp(GIT_VERSION, OT_TAG)) | ||
| 632 | fmt_append(reply, reply_end, " (%s)", GIT_VERSION); | ||
| 633 | fmt_append(reply, reply_end, "\n"); | ||
| 634 | if (*OT_TAG) | ||
| 635 | fmt_append(reply, reply_end, "https://erdgeist.org/gitweb/opentracker/tag/?h=%s\n", OT_TAG); | ||
| 636 | } | ||
| 637 | |||
| 638 | void return_stats_for_tracker(char **reply, const char *reply_end, int mode, int format) { | ||
| 639 | (void)format; | ||
| 640 | switch (mode & TASK_TASK_MASK) { | ||
| 641 | case TASK_STATS_CONNS: | ||
| 642 | stats_connections_mrtg(reply, reply_end); | ||
| 643 | break; | ||
| 644 | case TASK_STATS_SCRAPE: | ||
| 645 | stats_scrape_mrtg(reply, reply_end); | ||
| 646 | break; | ||
| 647 | case TASK_STATS_UDP: | ||
| 648 | stats_udpconnections_mrtg(reply, reply_end); | ||
| 649 | break; | ||
| 650 | case TASK_STATS_TCP: | ||
| 651 | stats_tcpconnections_mrtg(reply, reply_end); | ||
| 652 | break; | ||
| 653 | case TASK_STATS_FULLSCRAPE: | ||
| 654 | stats_fullscrapes_mrtg(reply, reply_end); | ||
| 655 | break; | ||
| 656 | case TASK_STATS_COMPLETED: | ||
| 657 | stats_return_completed_mrtg(reply, reply_end); | ||
| 658 | break; | ||
| 659 | case TASK_STATS_HTTPERRORS: | ||
| 660 | stats_httperrors_txt(reply, reply_end); | ||
| 661 | break; | ||
| 662 | case TASK_STATS_RENEW: | ||
| 663 | stats_return_renew_bucket(reply, reply_end); | ||
| 664 | break; | ||
| 665 | case TASK_STATS_SYNCS: | ||
| 666 | stats_return_sync_mrtg(reply, reply_end); | ||
| 667 | break; | ||
| 668 | #ifdef WANT_LOG_NUMWANT | ||
| 669 | case TASK_STATS_NUMWANTS: | ||
| 670 | stats_return_numwants(reply, reply_end); | ||
| 671 | break; | ||
| 672 | #endif | ||
| 673 | default: | ||
| 674 | break; | ||
| 675 | } | ||
| 676 | } | ||
| 677 | |||
| 678 | static void stats_make(size_t *iovec_entries, struct iovec **iovector, ot_tasktype mode) { | ||
| 679 | char *r; | ||
| 680 | const char *r_end; | ||
| 681 | |||
| 682 | *iovec_entries = 0; | ||
| 683 | *iovector = NULL; | ||
| 684 | if (!(r = iovec_new_chunk(iovec_entries, iovector, OT_STATS_TMPSIZE))) | ||
| 685 | return; | ||
| 686 | r_end = r + OT_STATS_TMPSIZE; | ||
| 687 | |||
| 688 | switch (mode & TASK_TASK_MASK) { | ||
| 689 | case TASK_STATS_TORRENTS: | ||
| 690 | stats_torrents_mrtg(&r, r_end); | ||
| 691 | break; | ||
| 692 | case TASK_STATS_PEERS: | ||
| 693 | stats_peers_mrtg(&r, r_end); | ||
| 694 | break; | ||
| 695 | case TASK_STATS_SLASH24S: | ||
| 696 | stats_slash24s_txt(&r, r_end, 128); | ||
| 697 | break; | ||
| 698 | case TASK_STATS_TOP10: | ||
| 699 | stats_top_txt(&r, r_end, 10); | ||
| 700 | break; | ||
| 701 | case TASK_STATS_TOP100: | ||
| 702 | r = iovec_next_chunk(iovec_entries, iovector, r, 4 * OT_STATS_TMPSIZE); | ||
| 703 | if (!r) | ||
| 704 | return; | ||
| 705 | r_end = r + 4 * OT_STATS_TMPSIZE; | ||
| 706 | stats_top_txt(&r, r_end, 100); | ||
| 707 | break; | ||
| 708 | case TASK_STATS_EVERYTHING: | ||
| 709 | r = iovec_next_chunk(iovec_entries, iovector, r, OT_STATS_TMPSIZE + 64 * OT_PEER_TIMEOUT); | ||
| 710 | if (!r) | ||
| 711 | return; | ||
| 712 | r_end = r + OT_STATS_TMPSIZE + 64 * OT_PEER_TIMEOUT; | ||
| 713 | stats_return_everything(&r, r_end); | ||
| 714 | break; | ||
| 715 | #ifdef WANT_SPOT_WOODPECKER | ||
| 716 | case TASK_STATS_WOODPECKERS: | ||
| 717 | stats_return_woodpeckers(&r, r_end, 128); | ||
| 718 | break; | ||
| 719 | #endif | ||
| 720 | default: | ||
| 721 | iovec_free(iovec_entries, iovector); | ||
| 722 | return; | ||
| 723 | } | ||
| 724 | iovec_trim_last(iovec_entries, iovector, r); | ||
| 725 | } | ||
| 726 | |||
| 727 | void stats_issue_event(struct ot_workstruct *ws, ot_status_event event, PROTO_FLAG proto, uintptr_t event_data) { | ||
| 728 | if (!ws) | ||
| 729 | return; | ||
| 730 | |||
| 731 | switch (event) { | ||
| 732 | case EVENT_ACCEPT: | ||
| 733 | if (proto == FLAG_TCP) | ||
| 734 | COUNT(OT_COUNTER_TCP_CONNECTIONS, 1); | ||
| 735 | else | ||
| 736 | COUNT(OT_COUNTER_UDP_CONNECTIONS, 1); | ||
| 737 | break; | ||
| 738 | case EVENT_ANNOUNCE: | ||
| 739 | if (proto == FLAG_TCP) | ||
| 740 | COUNT(OT_COUNTER_TCP_ANNOUNCES, 1); | ||
| 741 | else | ||
| 742 | COUNT(OT_COUNTER_UDP_ANNOUNCES, 1); | ||
| 743 | break; | ||
| 744 | case EVENT_CONNECT: | ||
| 745 | if (proto == FLAG_TCP) | ||
| 746 | COUNT(OT_COUNTER_TCP_CONNECTS, 1); | ||
| 747 | else | ||
| 748 | COUNT(OT_COUNTER_UDP_CONNECTS, 1); | ||
| 749 | break; | ||
| 750 | case EVENT_COMPLETED: | ||
| 751 | #ifdef WANT_SYSLOGS | ||
| 752 | { | ||
| 753 | char timestring[64]; | ||
| 754 | char hash_hex[42], peerid_hex[42], ip_readable[64]; | ||
| 755 | struct tm time_now; | ||
| 756 | time_t ttt; | ||
| 757 | |||
| 758 | time(&ttt); | ||
| 759 | localtime_r(&ttt, &time_now); | ||
| 760 | strftime(timestring, sizeof(timestring), "%FT%T%z", &time_now); | ||
| 761 | |||
| 762 | hex_from_hash(hash_hex, *ws->hash); | ||
| 763 | if (ws->peer_id) | ||
| 764 | hex_from_hash(peerid_hex, (uint8_t *)ws->peer_id); | ||
| 765 | else { | ||
| 766 | *peerid_hex = 0; | ||
| 767 | } | ||
| 768 | |||
| 769 | ip_readable[ip_format(ip_readable, (char *)&ws->peer)] = 0; | ||
| 770 | syslog(LOG_INFO, "time=%s event=completed info_hash=%s peer_id=%s ip=%s", timestring, hash_hex, peerid_hex, ip_readable); | ||
| 771 | } | ||
| 772 | #endif | ||
| 773 | COUNT(OT_COUNTER_COMPLETED, 1); | ||
| 774 | break; | ||
| 775 | case EVENT_SCRAPE: | ||
| 776 | if (proto == FLAG_TCP) | ||
| 777 | COUNT(OT_COUNTER_TCP_SCRAPES, 1); | ||
| 778 | else | ||
| 779 | COUNT(OT_COUNTER_UDP_SCRAPES, 1); | ||
| 780 | break; | ||
| 781 | case EVENT_FULLSCRAPE: | ||
| 782 | COUNT(OT_COUNTER_FULLSCRAPES, 1); | ||
| 783 | COUNT(OT_COUNTER_FULLSCRAPE_BYTES, event_data); | ||
| 784 | break; | ||
| 785 | case EVENT_FULLSCRAPE_REQUEST: { | ||
| 786 | ot_ip6 *ip = (ot_ip6 *)event_data; /* ugly hack to transfer ip to stats */ | ||
| 787 | char _debug[512]; | ||
| 788 | int off = snprintf(_debug, sizeof(_debug), "[%08d] scrp: ", (unsigned int)(time_now_seconds() - ot_start_time) / 60); | ||
| 789 | off += ip_format(_debug + off, *ip); | ||
| 790 | off += snprintf(_debug + off, sizeof(_debug) - off, " - FULL SCRAPE\n"); | ||
| 791 | (void)write(2, _debug, off); | ||
| 792 | COUNT(OT_COUNTER_FULLSCRAPE_REQUESTS, 1); | ||
| 793 | } break; | ||
| 794 | case EVENT_FULLSCRAPE_REQUEST_GZIP: { | ||
| 795 | ot_ip6 *ip = (ot_ip6 *)event_data; /* ugly hack to transfer ip to stats */ | ||
| 796 | char _debug[512]; | ||
| 797 | int off = snprintf(_debug, sizeof(_debug), "[%08d] scrp: ", (unsigned int)(time_now_seconds() - ot_start_time) / 60); | ||
| 798 | off += ip_format(_debug + off, *ip); | ||
| 799 | off += snprintf(_debug + off, sizeof(_debug) - off, " - FULL SCRAPE\n"); | ||
| 800 | (void)write(2, _debug, off); | ||
| 801 | COUNT(OT_COUNTER_FULLSCRAPE_REQUESTS, 1); | ||
| 802 | } break; | ||
| 803 | case EVENT_FAILED: | ||
| 804 | COUNT(OT_COUNTER_FAILED + event_data, 1); | ||
| 805 | break; | ||
| 806 | case EVENT_RENEW: | ||
| 807 | COUNT(OT_COUNTER_RENEWED + event_data, 1); | ||
| 808 | break; | ||
| 809 | case EVENT_SYNC: | ||
| 810 | COUNT(OT_COUNTER_SYNCS, event_data); | ||
| 811 | break; | ||
| 812 | case EVENT_BUCKET_LOCKED: | ||
| 813 | COUNT(OT_COUNTER_STALLS, 1); | ||
| 814 | break; | ||
| 815 | #ifdef WANT_SPOT_WOODPECKER | ||
| 816 | case EVENT_WOODPECKER: | ||
| 817 | pthread_mutex_lock(&g_woodpeckers_mutex); | ||
| 818 | stat_increase_network_count(&stats_woodpeckers_tree, 0, event_data); | ||
| 819 | pthread_mutex_unlock(&g_woodpeckers_mutex); | ||
| 820 | break; | ||
| 821 | #endif | ||
| 822 | case EVENT_CONNID_MISSMATCH: | ||
| 823 | COUNT(OT_COUNTER_UDP_CONNID_MISSMATCH, 1); | ||
| 824 | default: | ||
| 825 | break; | ||
| 826 | } | ||
| 827 | } | ||
| 828 | |||
| 829 | void stats_cleanup(void) { | ||
| 830 | #ifdef WANT_SPOT_WOODPECKER | ||
| 831 | pthread_mutex_lock(&g_woodpeckers_mutex); | ||
| 832 | stats_shift_down_network_count(&stats_woodpeckers_tree, 0, 1); | ||
| 833 | pthread_mutex_unlock(&g_woodpeckers_mutex); | ||
| 834 | #endif | ||
| 835 | } | ||
| 836 | |||
| 837 | static void *stats_worker(void *args) { | ||
| 838 | size_t iovec_entries; | ||
| 839 | struct iovec *iovector; | ||
| 840 | |||
| 841 | (void)args; | ||
| 842 | |||
| 843 | while (g_opentracker_running) { | ||
| 844 | ot_tasktype tasktype = TASK_STATS; | ||
| 845 | ot_taskid taskid = workqueue_poptask(&tasktype); | ||
| 846 | if (!taskid) | ||
| 847 | break; | ||
| 848 | stats_make(&iovec_entries, &iovector, tasktype); | ||
| 849 | if (workqueue_pushresult(taskid, iovec_entries, iovector)) | ||
| 850 | iovec_free(&iovec_entries, &iovector); | ||
| 851 | } | ||
| 852 | return NULL; | ||
| 853 | } | ||
| 854 | |||
| 855 | void stats_deliver(int64_t sock, int tasktype) { workqueue_pushtask(sock, tasktype); } | ||
| 856 | |||
| 857 | static pthread_t thread_id; | ||
| 858 | void stats_init(void) { | ||
| 859 | ot_start_time = time_now_seconds(); | ||
| 860 | pthread_create(&thread_id, NULL, stats_worker, NULL); | ||
| 861 | } | ||
| 862 | |||
| 863 | void stats_deinit(void) { | ||
| 864 | workqueue_wakeall(); | ||
| 865 | pthread_join(thread_id, NULL); | ||
| 866 | } | ||
diff --git a/src/ot_stats.h b/src/ot_stats.h new file mode 100644 index 0000000..45a547e --- /dev/null +++ b/src/ot_stats.h | |||
| @@ -0,0 +1,40 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_STATS_H__ | ||
| 5 | #define OT_STATS_H__ | ||
| 6 | |||
| 7 | #include "trackerlogic.h" | ||
| 8 | |||
| 9 | typedef enum { | ||
| 10 | EVENT_ACCEPT, | ||
| 11 | EVENT_READ, | ||
| 12 | EVENT_CONNECT, /* UDP only */ | ||
| 13 | EVENT_ANNOUNCE, | ||
| 14 | EVENT_COMPLETED, | ||
| 15 | EVENT_RENEW, | ||
| 16 | EVENT_SYNC, | ||
| 17 | EVENT_SCRAPE, | ||
| 18 | EVENT_FULLSCRAPE_REQUEST, | ||
| 19 | EVENT_FULLSCRAPE_REQUEST_GZIP, | ||
| 20 | EVENT_FULLSCRAPE_REQUEST_ZSTD, | ||
| 21 | EVENT_FULLSCRAPE, /* TCP only */ | ||
| 22 | EVENT_FAILED, | ||
| 23 | EVENT_BUCKET_LOCKED, | ||
| 24 | EVENT_WOODPECKER, | ||
| 25 | EVENT_CONNID_MISSMATCH | ||
| 26 | } ot_status_event; | ||
| 27 | |||
| 28 | void stats_counters_register(struct ot_workstruct *ws); | ||
| 29 | void stats_counters_retire(struct ot_workstruct *ws); | ||
| 30 | |||
| 31 | void stats_issue_event(struct ot_workstruct *ws, ot_status_event event, PROTO_FLAG proto, uintptr_t event_data); | ||
| 32 | void stats_deliver(int64_t sock, int tasktype); | ||
| 33 | void stats_cleanup(void); | ||
| 34 | void return_stats_for_tracker(char **reply, const char *reply_end, int mode, int format); | ||
| 35 | void stats_return_tracker_version(char **reply, const char *reply_end); | ||
| 36 | void stats_return_tracker_features(char **reply, const char *reply_end); | ||
| 37 | void stats_init(void); | ||
| 38 | void stats_deinit(void); | ||
| 39 | |||
| 40 | #endif | ||
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 | |||
| 26 | static 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 | |||
| 45 | void 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 | |||
| 89 | void 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 */ | ||
| 126 | void 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. */ | ||
| 169 | static void tcp_run(struct ot_worker *me) { | ||
| 170 | while (g_opentracker_running) | ||
| 171 | tcp_accept(me->sock, &me->ws); | ||
| 172 | } | ||
| 173 | |||
| 174 | void 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 | } | ||
diff --git a/src/ot_tcp.h b/src/ot_tcp.h new file mode 100644 index 0000000..afed142 --- /dev/null +++ b/src/ot_tcp.h | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_TCP_H__ | ||
| 5 | #define OT_TCP_H__ | ||
| 6 | |||
| 7 | #include <stdint.h> | ||
| 8 | |||
| 9 | #include "trackerlogic.h" | ||
| 10 | |||
| 11 | /* Take whatever connections are waiting on a listening socket and answer | ||
| 12 | them, each on this thread's stack, handing a connection that cannot be | ||
| 13 | finished to the event loop */ | ||
| 14 | void tcp_accept(const int64_t serversocket, struct ot_workstruct *ws); | ||
| 15 | |||
| 16 | /* Handler connections in the event loop has become readable or writable */ | ||
| 17 | void tcp_read(const int64_t sock, struct ot_workstruct *ws); | ||
| 18 | void tcp_write(const int64_t sock); | ||
| 19 | |||
| 20 | /* One thread accepting on one socket. Called once per worker. */ | ||
| 21 | void tcp_worker_install(int64_t sock); | ||
| 22 | |||
| 23 | |||
| 24 | #endif | ||
diff --git a/src/ot_udp.c b/src/ot_udp.c new file mode 100644 index 0000000..644b71d --- /dev/null +++ b/src/ot_udp.c | |||
| @@ -0,0 +1,226 @@ | |||
| 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 <arpa/inet.h> | ||
| 6 | #include <pthread.h> | ||
| 7 | #include <stdio.h> | ||
| 8 | #include <stdlib.h> | ||
| 9 | #include <string.h> | ||
| 10 | #include <unistd.h> | ||
| 11 | |||
| 12 | /* Opentracker */ | ||
| 13 | #include "ot_ip.h" | ||
| 14 | #include "ot_random.h" | ||
| 15 | #include "ot_rijndael.h" | ||
| 16 | #include "ot_socket.h" | ||
| 17 | #include "ot_worker.h" | ||
| 18 | #include "ot_stats.h" | ||
| 19 | #include "ot_udp.h" | ||
| 20 | #include "trackerlogic.h" | ||
| 21 | |||
| 22 | static uint32_t g_rijndael_round_key[44] = {0}; | ||
| 23 | static volatile uint32_t g_key_of_the_hour[2] = {0}; | ||
| 24 | static volatile ot_time g_hour_of_the_key; | ||
| 25 | |||
| 26 | |||
| 27 | static void udp_generate_rijndael_round_key(void) { | ||
| 28 | uint32_t key[16]; | ||
| 29 | random_bytes(key, sizeof(key)); | ||
| 30 | rijndaelKeySetupEnc128(g_rijndael_round_key, (uint8_t *)key); | ||
| 31 | |||
| 32 | g_key_of_the_hour[0] = random_uint32(); | ||
| 33 | g_hour_of_the_key = time_now_minutes(); | ||
| 34 | } | ||
| 35 | |||
| 36 | /* Generate current and previous connection id for ip */ | ||
| 37 | static void udp_make_connectionid(uint32_t connid[2], const ot_ip6 remoteip, int age) { | ||
| 38 | uint32_t plain[4], crypt[4]; | ||
| 39 | int i; | ||
| 40 | uint32_t current_key_of_the_hour; | ||
| 41 | |||
| 42 | if (time_now_minutes() - g_hour_of_the_key >= 60) { | ||
| 43 | uint32_t old_key_of_the_hour = g_key_of_the_hour[0]; | ||
| 44 | uint32_t new_key_of_the_hour = random_uint32(); | ||
| 45 | /* If in the meantime another thread has performed | ||
| 46 | key rotation, do not overwrite their results */ | ||
| 47 | if (time_now_minutes() - g_hour_of_the_key >= 60) { | ||
| 48 | /* Upgrade en bloc */ | ||
| 49 | g_hour_of_the_key = time_now_minutes(); | ||
| 50 | g_key_of_the_hour[0] = new_key_of_the_hour; | ||
| 51 | g_key_of_the_hour[1] = old_key_of_the_hour; | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | memcpy(plain, remoteip, sizeof(plain)); | ||
| 56 | current_key_of_the_hour = g_key_of_the_hour[age]; | ||
| 57 | for (i = 0; i < 4; ++i) | ||
| 58 | plain[i] ^= current_key_of_the_hour; | ||
| 59 | rijndaelEncrypt128(g_rijndael_round_key, (uint8_t *)plain, (uint8_t *)crypt); | ||
| 60 | connid[0] = crypt[0] ^ crypt[1]; | ||
| 61 | connid[1] = crypt[2] ^ crypt[3]; | ||
| 62 | } | ||
| 63 | |||
| 64 | /* UDP implementation according to http://xbtt.sourceforge.net/udp_tracker_protocol.html */ | ||
| 65 | int handle_udp6(int64_t serversocket, struct ot_workstruct *ws) { | ||
| 66 | ot_ip6 remoteip; | ||
| 67 | uint32_t *inpacket = (uint32_t *)ws->inbuf; | ||
| 68 | uint32_t *outpacket = (uint32_t *)ws->outbuf; | ||
| 69 | uint32_t left, event; | ||
| 70 | uint32_t connid[2]; | ||
| 71 | uint32_t action; | ||
| 72 | uint16_t port, remoteport; | ||
| 73 | size_t byte_count, scrape_count; | ||
| 74 | |||
| 75 | ssize_t result = socket_recv(serversocket, ws->inbuf, G_INBUF_SIZE, remoteip, &remoteport); | ||
| 76 | if (result <= 0) | ||
| 77 | return 0; | ||
| 78 | byte_count = (size_t)result; | ||
| 79 | |||
| 80 | stats_issue_event(ws, EVENT_ACCEPT, FLAG_UDP, (uintptr_t)remoteip); | ||
| 81 | stats_issue_event(ws, EVENT_READ, FLAG_UDP, byte_count); | ||
| 82 | |||
| 83 | /* Minimum udp tracker packet size, also catches error */ | ||
| 84 | if (byte_count < 16) | ||
| 85 | return 1; | ||
| 86 | |||
| 87 | /* Get action to take. Ignore error messages and broken packets */ | ||
| 88 | action = ntohl(inpacket[2]); | ||
| 89 | if (action > 2) | ||
| 90 | return 1; | ||
| 91 | |||
| 92 | /* Generate the connection id we give out and expect to and from | ||
| 93 | the requesting ip address, this prevents udp spoofing */ | ||
| 94 | udp_make_connectionid(connid, remoteip, 0); | ||
| 95 | |||
| 96 | /* Initialise hash pointer */ | ||
| 97 | ws->hash = NULL; | ||
| 98 | ws->peer_id = NULL; | ||
| 99 | |||
| 100 | /* If action is not 0 (connect), then we expect the derived | ||
| 101 | connection id in first 64 bit */ | ||
| 102 | if ((action > 0) && (inpacket[0] != connid[0] || inpacket[1] != connid[1])) { | ||
| 103 | /* If connection id does not match, try the one that was | ||
| 104 | valid in the previous hour. Only if this also does not | ||
| 105 | match, return an error packet */ | ||
| 106 | udp_make_connectionid(connid, remoteip, 1); | ||
| 107 | if (inpacket[0] != connid[0] || inpacket[1] != connid[1]) { | ||
| 108 | const char err_idmiss[] = "Connection ID missmatch."; | ||
| 109 | size_t s = sizeof(err_idmiss); | ||
| 110 | outpacket[0] = htonl(3); | ||
| 111 | outpacket[1] = inpacket[3]; | ||
| 112 | memcpy(&outpacket[2], &err_idmiss[0], s); | ||
| 113 | socket_send(serversocket, ws->outbuf, 8 + s, remoteip, remoteport); | ||
| 114 | stats_issue_event(ws, EVENT_CONNID_MISSMATCH, FLAG_UDP, 8 + s); | ||
| 115 | return 1; | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | switch (action) { | ||
| 120 | case 0: /* This is a connect action */ | ||
| 121 | /* look for udp bittorrent magic id */ | ||
| 122 | if ((ntohl(inpacket[0]) != 0x00000417) || (ntohl(inpacket[1]) != 0x27101980)) | ||
| 123 | return 1; | ||
| 124 | |||
| 125 | outpacket[0] = 0; | ||
| 126 | outpacket[1] = inpacket[3]; | ||
| 127 | outpacket[2] = connid[0]; | ||
| 128 | outpacket[3] = connid[1]; | ||
| 129 | |||
| 130 | socket_send(serversocket, ws->outbuf, 16, remoteip, remoteport); | ||
| 131 | stats_issue_event(ws, EVENT_CONNECT, FLAG_UDP, 16); | ||
| 132 | break; | ||
| 133 | case 1: /* This is an announce action */ | ||
| 134 | /* Minimum udp announce packet size */ | ||
| 135 | if (byte_count < 98) | ||
| 136 | return 1; | ||
| 137 | |||
| 138 | /* We do only want to know, if it is zero */ | ||
| 139 | left = inpacket[64 / 4] | inpacket[68 / 4]; | ||
| 140 | |||
| 141 | event = ntohl(inpacket[80 / 4]); | ||
| 142 | port = *(uint16_t *)(((char *)inpacket) + 96); | ||
| 143 | ws->hash = (ot_hash *)(((char *)inpacket) + 16); | ||
| 144 | |||
| 145 | OT_SETIP(ws->peer, remoteip); | ||
| 146 | OT_SETPORT(ws->peer, &port); | ||
| 147 | OT_PEERFLAG(ws->peer) = 0; | ||
| 148 | |||
| 149 | switch (event) { | ||
| 150 | case 1: | ||
| 151 | OT_PEERFLAG(ws->peer) |= PEER_FLAG_COMPLETED; | ||
| 152 | break; | ||
| 153 | case 3: | ||
| 154 | OT_PEERFLAG(ws->peer) |= PEER_FLAG_STOPPED; | ||
| 155 | break; | ||
| 156 | default: | ||
| 157 | break; | ||
| 158 | } | ||
| 159 | |||
| 160 | if (!left) | ||
| 161 | OT_PEERFLAG(ws->peer) |= PEER_FLAG_SEEDING; | ||
| 162 | |||
| 163 | outpacket[0] = htonl(1); /* announce action */ | ||
| 164 | outpacket[1] = inpacket[12 / 4]; | ||
| 165 | |||
| 166 | if (OT_PEERFLAG(ws->peer) & PEER_FLAG_STOPPED) { /* Peer is gone. */ | ||
| 167 | ws->reply = ws->outbuf; | ||
| 168 | ws->reply_size = remove_peer_from_torrent(FLAG_UDP, ws); | ||
| 169 | } else { | ||
| 170 | /* Limit amount of peers to OT_MAX_PEERS_UDP */ | ||
| 171 | uint32_t numwant = ntohl(inpacket[92 / 4]); | ||
| 172 | size_t max_peers = ip_is_v4mapped(remoteip) ? OT_MAX_PEERS_UDP4 : OT_MAX_PEERS_UDP6; | ||
| 173 | if (numwant > max_peers) | ||
| 174 | numwant = max_peers; | ||
| 175 | |||
| 176 | ws->reply = ws->outbuf + 8; | ||
| 177 | ws->reply_size = add_peer_to_torrent_and_return_peers(FLAG_UDP, ws, numwant); | ||
| 178 | |||
| 179 | if (!ws->reply_size) { | ||
| 180 | const char err_unauth[] = "Requested download is not authorized for use with this tracker."; | ||
| 181 | size_t s = sizeof(err_unauth); | ||
| 182 | outpacket[0] = htonl(3); | ||
| 183 | memcpy(&outpacket[2], &err_unauth[0], s); | ||
| 184 | socket_send(serversocket, ws->outbuf, 8 + s, remoteip, remoteport); | ||
| 185 | return 1; | ||
| 186 | } | ||
| 187 | ws->reply_size += 8; | ||
| 188 | } | ||
| 189 | |||
| 190 | socket_send(serversocket, ws->outbuf, ws->reply_size, remoteip, remoteport); | ||
| 191 | stats_issue_event(ws, EVENT_ANNOUNCE, FLAG_UDP, ws->reply_size); | ||
| 192 | break; | ||
| 193 | |||
| 194 | case 2: /* This is a scrape action */ | ||
| 195 | outpacket[0] = htonl(2); /* scrape action */ | ||
| 196 | outpacket[1] = inpacket[12 / 4]; | ||
| 197 | |||
| 198 | for (scrape_count = 0; (scrape_count * 20 < byte_count - 16) && (scrape_count <= 74); scrape_count++) | ||
| 199 | return_udp_scrape_for_torrent(*(ot_hash *)(((char *)inpacket) + 16 + 20 * scrape_count), ((char *)outpacket) + 8 + 12 * scrape_count); | ||
| 200 | |||
| 201 | socket_send(serversocket, ws->outbuf, 8 + 12 * scrape_count, remoteip, remoteport); | ||
| 202 | stats_issue_event(ws, EVENT_SCRAPE, FLAG_UDP, scrape_count); | ||
| 203 | break; | ||
| 204 | } | ||
| 205 | return 1; | ||
| 206 | } | ||
| 207 | |||
| 208 | static void udp_run(struct ot_worker *me) { | ||
| 209 | while (g_opentracker_running) | ||
| 210 | handle_udp6(me->sock, &me->ws); | ||
| 211 | } | ||
| 212 | |||
| 213 | void udp_init(void) { | ||
| 214 | if (!g_rijndael_round_key[0]) | ||
| 215 | udp_generate_rijndael_round_key(); | ||
| 216 | } | ||
| 217 | |||
| 218 | void udp_worker_install(int64_t sock) { | ||
| 219 | #ifdef _DEBUG | ||
| 220 | fprintf(stderr, "Installing a worker on udp socket %ld\n", (unsigned long)sock); | ||
| 221 | #endif | ||
| 222 | socket_set_blocking((int)sock, 1); | ||
| 223 | |||
| 224 | if (worker_start(udp_run, sock) == -1) | ||
| 225 | fprintf(stderr, "Warning: could not install a udp worker, this socket stays with the workers already running.\n"); | ||
| 226 | } | ||
diff --git a/src/ot_udp.h b/src/ot_udp.h new file mode 100644 index 0000000..2710744 --- /dev/null +++ b/src/ot_udp.h | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_UDP_H__ | ||
| 5 | #define OT_UDP_H__ | ||
| 6 | |||
| 7 | #include <stdint.h> | ||
| 8 | |||
| 9 | #include "trackerlogic.h" | ||
| 10 | |||
| 11 | void udp_init(void); | ||
| 12 | /* One thread reading one socket. Called once per worker. */ | ||
| 13 | void udp_worker_install(int64_t sock); | ||
| 14 | |||
| 15 | int handle_udp6(int64_t serversocket, struct ot_workstruct *ws); | ||
| 16 | |||
| 17 | #endif | ||
diff --git a/src/ot_urlencoded.c b/src/ot_urlencoded.c new file mode 100644 index 0000000..4109bb5 --- /dev/null +++ b/src/ot_urlencoded.c | |||
| @@ -0,0 +1,168 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | /* Opentracker */ | ||
| 5 | #include "ot_urlencoded.h" | ||
| 6 | |||
| 7 | /* System */ | ||
| 8 | #include <string.h> | ||
| 9 | |||
| 10 | /* Idea is to do a in place replacement or guarantee at least | ||
| 11 | strlen( string ) bytes in deststring | ||
| 12 | watch http://www.ietf.org/rfc/rfc2396.txt | ||
| 13 | unreserved = alphanum | mark | ||
| 14 | mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" | ||
| 15 | we add '%' to the matrix to not stop at encoded chars. | ||
| 16 | After losing too many requests to being too strict, add the following characters to reserved matrix | ||
| 17 | relax = "+" | "," | "/" | ";" | "<" | ">" | ":" | ||
| 18 | */ | ||
| 19 | |||
| 20 | /* This matrix holds for each ascii character the information, | ||
| 21 | whether it is a non-terminating character for one of the three | ||
| 22 | scan states we are in, that is 'path', 'param' and 'value' from | ||
| 23 | /path?param=value¶m=value, it is encoded in bit 0, 1 and 2 | ||
| 24 | respectively | ||
| 25 | |||
| 26 | The top bit of lower nibble indicates, whether this character is | ||
| 27 | a hard terminator, ie. \0, \n or \s, where the whole scanning | ||
| 28 | process should terminate | ||
| 29 | */ | ||
| 30 | static const unsigned char is_unreserved[256] = { | ||
| 31 | 8,0,0,0,0,0,0,0,0,0,8,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, | ||
| 32 | 8,7,8,8,8,7,0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,4,7,6, | ||
| 33 | 4,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,8,8,8,8,7, | ||
| 34 | 8,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,8,8,8,7,0, | ||
| 35 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, | ||
| 36 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, | ||
| 37 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, | ||
| 38 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 | ||
| 39 | }; | ||
| 40 | |||
| 41 | /* Do a fast nibble to hex representation conversion */ | ||
| 42 | static unsigned char fromhex(unsigned char x) { | ||
| 43 | x -= '0'; | ||
| 44 | if (x <= 9) | ||
| 45 | return x; | ||
| 46 | x &= ~0x20; | ||
| 47 | x -= 'A' - '0'; | ||
| 48 | if (x < 6) | ||
| 49 | return x + 10; | ||
| 50 | return 0xff; | ||
| 51 | } | ||
| 52 | |||
| 53 | /* Skip the value of a param=value pair */ | ||
| 54 | void urlencoded_skip_value(char **string) { | ||
| 55 | const unsigned char *s = *(const unsigned char **)string; | ||
| 56 | unsigned char f; | ||
| 57 | |||
| 58 | /* Since we are asked to skip the 'value', we assume to stop at | ||
| 59 | terminators for a 'value' string position */ | ||
| 60 | while ((f = is_unreserved[*s++]) & SCAN_SEARCHPATH_VALUE) | ||
| 61 | ; | ||
| 62 | |||
| 63 | /* If we stopped at a hard terminator like \0 or \n, make the | ||
| 64 | next urlencoded_parse encounter it again */ | ||
| 65 | if (f & SCAN_SEARCHPATH_TERMINATOR) | ||
| 66 | --s; | ||
| 67 | |||
| 68 | *string = (char *)s; | ||
| 69 | } | ||
| 70 | |||
| 71 | int urlencoded_match_keyword(const ot_keywords *keywords, char **string, SCAN_SEARCHPATH_FLAG flags) { | ||
| 72 | char *deststring = *string; | ||
| 73 | ssize_t match_length = urlencoded_parse(string, deststring, flags); | ||
| 74 | |||
| 75 | if (match_length < 0) | ||
| 76 | return match_length; | ||
| 77 | if (match_length == 0) | ||
| 78 | return -3; | ||
| 79 | |||
| 80 | while (keywords->key) { | ||
| 81 | if (!strncmp(keywords->key, deststring, match_length) && !keywords->key[match_length]) | ||
| 82 | return keywords->value; | ||
| 83 | keywords++; | ||
| 84 | } | ||
| 85 | |||
| 86 | return -3; | ||
| 87 | } | ||
| 88 | |||
| 89 | ssize_t urlencoded_parse(char **string, char *deststring, SCAN_SEARCHPATH_FLAG flags) { | ||
| 90 | const unsigned char *s = *(const unsigned char **)string; | ||
| 91 | unsigned char *d = (unsigned char *)deststring; | ||
| 92 | unsigned char b, c; | ||
| 93 | |||
| 94 | /* This is the main decoding loop. | ||
| 95 | 'flag' determines, which characters are non-terminating in current context | ||
| 96 | (ie. stop at '=' and '&' if scanning for a 'param'; stop at '?' if scanning for the path ) | ||
| 97 | */ | ||
| 98 | while (is_unreserved[c = *s++] & flags) { | ||
| 99 | |||
| 100 | /* When encountering an url escaped character, try to decode */ | ||
| 101 | if (c == '%') { | ||
| 102 | if ((b = fromhex(*s++)) == 0xff) | ||
| 103 | return -1; | ||
| 104 | if ((c = fromhex(*s++)) == 0xff) | ||
| 105 | return -1; | ||
| 106 | c |= (b << 4); | ||
| 107 | } | ||
| 108 | |||
| 109 | /* Write (possibly decoded) character to output */ | ||
| 110 | *d++ = c; | ||
| 111 | } | ||
| 112 | |||
| 113 | switch (c) { | ||
| 114 | case 0: | ||
| 115 | case '\r': | ||
| 116 | case '\n': | ||
| 117 | case ' ': | ||
| 118 | /* If we started scanning on a hard terminator, indicate we've finished */ | ||
| 119 | if (d == (unsigned char *)deststring) | ||
| 120 | return -2; | ||
| 121 | |||
| 122 | /* Else make the next call to urlencoded_parse encounter it again */ | ||
| 123 | --s; | ||
| 124 | break; | ||
| 125 | case '?': | ||
| 126 | if (flags != SCAN_PATH) | ||
| 127 | return -1; | ||
| 128 | break; | ||
| 129 | case '=': | ||
| 130 | if (flags != SCAN_SEARCHPATH_PARAM) | ||
| 131 | return -1; | ||
| 132 | break; | ||
| 133 | case '&': | ||
| 134 | if (flags == SCAN_PATH) | ||
| 135 | return -1; | ||
| 136 | if (flags == SCAN_SEARCHPATH_PARAM) | ||
| 137 | --s; | ||
| 138 | break; | ||
| 139 | default: | ||
| 140 | return -1; | ||
| 141 | } | ||
| 142 | |||
| 143 | *string = (char *)s; | ||
| 144 | return d - (unsigned char *)deststring; | ||
| 145 | } | ||
| 146 | |||
| 147 | |||
| 148 | size_t urlencoded_format(char *dest, const uint8_t *src, size_t len) { | ||
| 149 | static const char safe[] = "$/.=~-_"; | ||
| 150 | static const char hexdigit[] = "0123456789abcdef"; | ||
| 151 | size_t written = 0, i; | ||
| 152 | |||
| 153 | for (i = 0; i < len; ++i) { | ||
| 154 | uint8_t c = src[i]; | ||
| 155 | |||
| 156 | /* strchr would find the terminator, so a zero byte never counts as safe */ | ||
| 157 | if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c && strchr(safe, c))) { | ||
| 158 | dest[written++] = (char)c; | ||
| 159 | } else { | ||
| 160 | dest[written] = '%'; | ||
| 161 | dest[written + 1] = hexdigit[c >> 4]; | ||
| 162 | dest[written + 2] = hexdigit[c & 15]; | ||
| 163 | written += 3; | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | return written; | ||
| 168 | } | ||
diff --git a/src/ot_urlencoded.h b/src/ot_urlencoded.h new file mode 100644 index 0000000..9882468 --- /dev/null +++ b/src/ot_urlencoded.h | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef SCAN_URLENCODED_QUERY_H__ | ||
| 5 | #define SCAN_URLENCODED_QUERY_H__ | ||
| 6 | |||
| 7 | #include <stddef.h> | ||
| 8 | #include <stdint.h> | ||
| 9 | #include <sys/types.h> | ||
| 10 | |||
| 11 | typedef struct { | ||
| 12 | const char *key; | ||
| 13 | int value; | ||
| 14 | } ot_keywords; | ||
| 15 | |||
| 16 | typedef enum { | ||
| 17 | SCAN_PATH = 1, | ||
| 18 | SCAN_SEARCHPATH_PARAM = 2, | ||
| 19 | SCAN_SEARCHPATH_VALUE = 4, | ||
| 20 | SCAN_SEARCHPATH_TERMINATOR = 8 | ||
| 21 | } SCAN_SEARCHPATH_FLAG; | ||
| 22 | |||
| 23 | /* string in: pointer to source | ||
| 24 | out: pointer to next scan position | ||
| 25 | deststring pointer to destination | ||
| 26 | flags determines, what to parse | ||
| 27 | returns number of valid converted characters in deststring | ||
| 28 | or -1 for parse error | ||
| 29 | or -2 for terminator found | ||
| 30 | */ | ||
| 31 | ssize_t urlencoded_parse(char **string, char *deststring, SCAN_SEARCHPATH_FLAG flags); | ||
| 32 | |||
| 33 | /* string in: pointer to source | ||
| 34 | out: pointer to next scan position | ||
| 35 | flags determines, what to parse | ||
| 36 | returns value for matched keyword | ||
| 37 | or -1 for parse error | ||
| 38 | or -2 for terminator found | ||
| 39 | or -3 for no keyword matched | ||
| 40 | */ | ||
| 41 | int urlencoded_match_keyword(const ot_keywords *keywords, char **string, SCAN_SEARCHPATH_FLAG flags); | ||
| 42 | |||
| 43 | /* string in: pointer to value of a param=value pair to skip | ||
| 44 | out: pointer to next scan position on return | ||
| 45 | */ | ||
| 46 | void urlencoded_skip_value(char **string); | ||
| 47 | |||
| 48 | |||
| 49 | /* The other direction: percent encode len bytes from src into dest and return | ||
| 50 | how many characters were written. dest needs room for three times len. */ | ||
| 51 | size_t urlencoded_format(char *dest, const uint8_t *src, size_t len); | ||
| 52 | |||
| 53 | #endif | ||
diff --git a/src/ot_vector.c b/src/ot_vector.c new file mode 100644 index 0000000..ab0566c --- /dev/null +++ b/src/ot_vector.c | |||
| @@ -0,0 +1,287 @@ | |||
| 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 <stddef.h> | ||
| 6 | #include <stdint.h> | ||
| 7 | #include <stdlib.h> | ||
| 8 | #include <string.h> | ||
| 9 | |||
| 10 | /* Opentracker */ | ||
| 11 | #include "trackerlogic.h" | ||
| 12 | |||
| 13 | static int vector_compare_peer6(const void *peer1, const void *peer2) { return memcmp(peer1, peer2, OT_PEER_COMPARE_SIZE6); } | ||
| 14 | static int vector_compare_peer4(const void *peer1, const void *peer2) { return memcmp(peer1, peer2, OT_PEER_COMPARE_SIZE4); } | ||
| 15 | |||
| 16 | /* This function gives us a binary search that returns a pointer, even if | ||
| 17 | no exact match is found. In that case it sets exactmatch 0 and gives | ||
| 18 | calling functions the chance to insert data | ||
| 19 | */ | ||
| 20 | void *vector_binary_search(const void *const key, const void *base, const size_t member_count, const size_t member_size, size_t compare_size, int *exactmatch) { | ||
| 21 | size_t interval = member_count; | ||
| 22 | |||
| 23 | while (interval) { | ||
| 24 | uint8_t *lookat = ((uint8_t *)base) + member_size * (interval / 2); | ||
| 25 | int cmp = memcmp(lookat, key, compare_size); | ||
| 26 | if (cmp == 0) { | ||
| 27 | base = lookat; | ||
| 28 | break; | ||
| 29 | } | ||
| 30 | if (cmp < 0) { | ||
| 31 | base = lookat + member_size; | ||
| 32 | interval--; | ||
| 33 | } | ||
| 34 | interval /= 2; | ||
| 35 | } | ||
| 36 | |||
| 37 | *exactmatch = interval; | ||
| 38 | return (void *)base; | ||
| 39 | } | ||
| 40 | |||
| 41 | static uint8_t vector_hash_peer(ot_peer const *peer, size_t compare_size, size_t bucket_count) { | ||
| 42 | unsigned int hash = 5381; | ||
| 43 | uint8_t *p = (uint8_t *)peer; | ||
| 44 | while (compare_size--) | ||
| 45 | hash += (hash << 5) + *(p++); | ||
| 46 | return hash % bucket_count; | ||
| 47 | } | ||
| 48 | |||
| 49 | /* This is the generic insert operation for our vector type. | ||
| 50 | It tries to locate the object at "key" with size "member_size" by comparing its first "compare_size" bytes with | ||
| 51 | those of objects in vector. Our special "vector_binary_search" function does that and either returns the match or a | ||
| 52 | pointer to where the object is to be inserted. vector_find_or_insert makes space for the object and copies it, | ||
| 53 | if it wasn't found in vector. Caller needs to check the passed "exactmatch" variable to see, whether an insert | ||
| 54 | took place. If resizing the vector failed, NULL is returned, else the pointer to the object in vector. | ||
| 55 | */ | ||
| 56 | void *vector_find_or_insert(ot_vector *vector, void *key, size_t member_size, size_t compare_size, int *exactmatch) { | ||
| 57 | uint8_t *match = vector_binary_search(key, vector->data, vector->size, member_size, compare_size, exactmatch); | ||
| 58 | size_t match_offset, bytes_after; | ||
| 59 | |||
| 60 | if (*exactmatch) | ||
| 61 | return match; | ||
| 62 | |||
| 63 | /* Hold the insertion point as an offset rather than a pointer: it survives | ||
| 64 | the realloc below, and an empty vector has no data pointer to offset from | ||
| 65 | in the first place */ | ||
| 66 | match_offset = vector->size ? (size_t)(match - (uint8_t *)vector->data) : 0; | ||
| 67 | bytes_after = member_size * vector->size - match_offset; | ||
| 68 | |||
| 69 | if (vector->size + 1 > vector->space) { | ||
| 70 | size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS; | ||
| 71 | uint8_t *new_data = realloc(vector->data, new_space * member_size); | ||
| 72 | |||
| 73 | if (!new_data) | ||
| 74 | return NULL; | ||
| 75 | |||
| 76 | vector->data = new_data; | ||
| 77 | vector->space = new_space; | ||
| 78 | } | ||
| 79 | |||
| 80 | match = (uint8_t *)vector->data + match_offset; | ||
| 81 | memmove(match + member_size, match, bytes_after); | ||
| 82 | |||
| 83 | vector->size++; | ||
| 84 | return match; | ||
| 85 | } | ||
| 86 | |||
| 87 | ot_peer *vector_find_or_insert_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size, int *exactmatch) { | ||
| 88 | ot_peer *match; | ||
| 89 | const size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size); | ||
| 90 | size_t match_offset, bytes_after; | ||
| 91 | |||
| 92 | /* If space is zero but size is set, we're dealing with a list of vector->size buckets */ | ||
| 93 | if (vector->space < vector->size) | ||
| 94 | vector = ((ot_vector *)vector->data) + vector_hash_peer(peer, compare_size, vector->size); | ||
| 95 | match = vector_binary_search(peer, vector->data, vector->size, peer_size, compare_size, exactmatch); | ||
| 96 | |||
| 97 | if (*exactmatch) | ||
| 98 | return match; | ||
| 99 | |||
| 100 | /* Hold the insertion point as an offset rather than a pointer: it survives | ||
| 101 | the realloc below, and an empty vector has no data pointer to offset from | ||
| 102 | in the first place. bytes_after is what has to move up by peer_size to | ||
| 103 | make room for the new peer */ | ||
| 104 | match_offset = vector->size ? (size_t)(match - (ot_peer *)vector->data) : 0; | ||
| 105 | bytes_after = vector->size * peer_size - match_offset; | ||
| 106 | |||
| 107 | if (vector->size + 1 > vector->space) { | ||
| 108 | size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS; | ||
| 109 | ot_peer *new_data = realloc(vector->data, new_space * peer_size); | ||
| 110 | |||
| 111 | if (!new_data) | ||
| 112 | return NULL; | ||
| 113 | |||
| 114 | vector->data = new_data; | ||
| 115 | vector->space = new_space; | ||
| 116 | } | ||
| 117 | |||
| 118 | match = (ot_peer *)vector->data + match_offset; | ||
| 119 | memmove(match + peer_size, match, bytes_after); | ||
| 120 | |||
| 121 | vector->size++; | ||
| 122 | return match; | ||
| 123 | } | ||
| 124 | |||
| 125 | /* This is the non-generic delete from vector-operation specialized for peers in pools. | ||
| 126 | It returns 0 if no peer was found (and thus not removed) | ||
| 127 | 1 if a non-seeding peer was removed | ||
| 128 | 2 if a seeding peer was removed | ||
| 129 | */ | ||
| 130 | int vector_remove_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size) { | ||
| 131 | int exactmatch, was_seeder; | ||
| 132 | ot_peer *match, *end; | ||
| 133 | size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size); | ||
| 134 | |||
| 135 | if (!vector->size) | ||
| 136 | return 0; | ||
| 137 | |||
| 138 | /* If space is zero but size is set, we're dealing with a list of vector->size buckets */ | ||
| 139 | if (vector->space < vector->size) | ||
| 140 | vector = ((ot_vector *)vector->data) + vector_hash_peer(peer, compare_size, vector->size); | ||
| 141 | |||
| 142 | end = ((ot_peer *)vector->data) + peer_size * vector->size; | ||
| 143 | match = (ot_peer *)vector_binary_search(peer, vector->data, vector->size, peer_size, compare_size, &exactmatch); | ||
| 144 | if (!exactmatch) | ||
| 145 | return 0; | ||
| 146 | |||
| 147 | was_seeder = (OT_PEERFLAG_D(match, peer_size) & PEER_FLAG_SEEDING) ? 2 : 1; | ||
| 148 | memmove(match, match + peer_size, end - match - peer_size); | ||
| 149 | |||
| 150 | vector->size--; | ||
| 151 | vector_fixup_peers(vector, peer_size); | ||
| 152 | return was_seeder; | ||
| 153 | } | ||
| 154 | |||
| 155 | void vector_remove_torrent(ot_vector *vector, ot_torrent *match) { | ||
| 156 | ot_torrent *end; | ||
| 157 | |||
| 158 | if (!vector->size) | ||
| 159 | return; | ||
| 160 | |||
| 161 | end = ((ot_torrent *)vector->data) + vector->size; | ||
| 162 | |||
| 163 | free_peerlist(match->peer_list6); | ||
| 164 | free_peerlist(match->peer_list4); | ||
| 165 | |||
| 166 | memmove(match, match + 1, sizeof(ot_torrent) * (end - match - 1)); | ||
| 167 | if ((--vector->size * OT_VECTOR_SHRINK_THRESH < vector->space) && (vector->space >= OT_VECTOR_SHRINK_RATIO * OT_VECTOR_MIN_MEMBERS)) { | ||
| 168 | vector->space /= OT_VECTOR_SHRINK_RATIO; | ||
| 169 | vector->data = realloc(vector->data, vector->space * sizeof(ot_torrent)); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | void vector_clean_list(ot_vector *vector, size_t num_buckets) { | ||
| 174 | while (num_buckets--) | ||
| 175 | free(vector[num_buckets].data); | ||
| 176 | free(vector); | ||
| 177 | return; | ||
| 178 | } | ||
| 179 | |||
| 180 | void vector_redistribute_buckets(ot_peerlist *peer_list, size_t peer_size) { | ||
| 181 | size_t bucket, bucket_size_new, num_buckets_new, num_buckets_old = 1; | ||
| 182 | ot_vector *bucket_list_new, *bucket_list_old = &peer_list->peers; | ||
| 183 | int (*sort_func)(const void *, const void *) = peer_size == OT_PEER_SIZE6 ? &vector_compare_peer6 : &vector_compare_peer4; | ||
| 184 | |||
| 185 | if (OT_PEERLIST_HASBUCKETS(peer_list)) { | ||
| 186 | num_buckets_old = peer_list->peers.size; | ||
| 187 | bucket_list_old = peer_list->peers.data; | ||
| 188 | } | ||
| 189 | |||
| 190 | if (peer_list->peer_count < 255) | ||
| 191 | num_buckets_new = 1; | ||
| 192 | else if (peer_list->peer_count > 8192) | ||
| 193 | num_buckets_new = 64; | ||
| 194 | else if (peer_list->peer_count >= 512 && peer_list->peer_count < 4096) | ||
| 195 | num_buckets_new = 16; | ||
| 196 | else if (peer_list->peer_count < 512 && num_buckets_old <= 16) | ||
| 197 | num_buckets_new = num_buckets_old; | ||
| 198 | else if (peer_list->peer_count < 512) | ||
| 199 | num_buckets_new = 1; | ||
| 200 | else if (peer_list->peer_count < 8192 && num_buckets_old > 1) | ||
| 201 | num_buckets_new = num_buckets_old; | ||
| 202 | else | ||
| 203 | num_buckets_new = 16; | ||
| 204 | |||
| 205 | if (num_buckets_new == num_buckets_old) | ||
| 206 | return; | ||
| 207 | |||
| 208 | /* Assume near perfect distribution */ | ||
| 209 | bucket_list_new = calloc(1, num_buckets_new * sizeof(ot_vector)); | ||
| 210 | if (!bucket_list_new) | ||
| 211 | return; | ||
| 212 | |||
| 213 | bucket_size_new = OT_VECTOR_MIN_MEMBERS; | ||
| 214 | while (bucket_size_new * num_buckets_new < peer_list->peer_count) | ||
| 215 | bucket_size_new *= OT_VECTOR_GROW_RATIO; | ||
| 216 | |||
| 217 | /* preallocate vectors to hold all peers */ | ||
| 218 | for (bucket = 0; bucket < num_buckets_new; ++bucket) { | ||
| 219 | bucket_list_new[bucket].space = bucket_size_new; | ||
| 220 | bucket_list_new[bucket].data = malloc(bucket_size_new * peer_size); | ||
| 221 | if (!bucket_list_new[bucket].data) { | ||
| 222 | vector_clean_list(bucket_list_new, num_buckets_new); | ||
| 223 | return; | ||
| 224 | } | ||
| 225 | } | ||
| 226 | |||
| 227 | /* Now sort them into the correct bucket */ | ||
| 228 | for (bucket = 0; bucket < num_buckets_old; ++bucket) { | ||
| 229 | ot_peer *peers_old = bucket_list_old[bucket].data; | ||
| 230 | size_t peer_count_old = bucket_list_old[bucket].size; | ||
| 231 | while (peer_count_old--) { | ||
| 232 | ot_vector *bucket_dest = bucket_list_new; | ||
| 233 | if (num_buckets_new > 1) | ||
| 234 | bucket_dest += vector_hash_peer(peers_old, OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size), num_buckets_new); | ||
| 235 | if (bucket_dest->size + 1 > bucket_dest->space) { | ||
| 236 | void *tmp = realloc(bucket_dest->data, peer_size * OT_VECTOR_GROW_RATIO * bucket_dest->space); | ||
| 237 | if (!tmp) { | ||
| 238 | vector_clean_list(bucket_list_new, num_buckets_new); | ||
| 239 | return; | ||
| 240 | } | ||
| 241 | bucket_dest->data = tmp; | ||
| 242 | bucket_dest->space *= OT_VECTOR_GROW_RATIO; | ||
| 243 | } | ||
| 244 | memcpy((ot_peer *)bucket_dest->data + peer_size * bucket_dest->size++, peers_old, peer_size); | ||
| 245 | peers_old += peer_size; | ||
| 246 | } | ||
| 247 | } | ||
| 248 | |||
| 249 | /* Now sort each bucket to later allow bsearch */ | ||
| 250 | for (bucket = 0; bucket < num_buckets_new; ++bucket) | ||
| 251 | qsort(bucket_list_new[bucket].data, bucket_list_new[bucket].size, peer_size, sort_func); | ||
| 252 | |||
| 253 | /* Everything worked fine. Now link new bucket_list to peer_list */ | ||
| 254 | if (OT_PEERLIST_HASBUCKETS(peer_list)) | ||
| 255 | vector_clean_list((ot_vector *)peer_list->peers.data, peer_list->peers.size); | ||
| 256 | else | ||
| 257 | free(peer_list->peers.data); | ||
| 258 | |||
| 259 | if (num_buckets_new > 1) { | ||
| 260 | peer_list->peers.data = bucket_list_new; | ||
| 261 | peer_list->peers.size = num_buckets_new; | ||
| 262 | peer_list->peers.space = 0; /* Magic marker for "is list of buckets" */ | ||
| 263 | } else { | ||
| 264 | peer_list->peers.data = bucket_list_new->data; | ||
| 265 | peer_list->peers.size = bucket_list_new->size; | ||
| 266 | peer_list->peers.space = bucket_list_new->space; | ||
| 267 | free(bucket_list_new); | ||
| 268 | } | ||
| 269 | } | ||
| 270 | |||
| 271 | void vector_fixup_peers(ot_vector *vector, size_t peer_size) { | ||
| 272 | int need_fix = 0; | ||
| 273 | |||
| 274 | if (!vector->size) { | ||
| 275 | free(vector->data); | ||
| 276 | vector->data = NULL; | ||
| 277 | vector->space = 0; | ||
| 278 | return; | ||
| 279 | } | ||
| 280 | |||
| 281 | while ((vector->size * OT_VECTOR_SHRINK_THRESH < vector->space) && (vector->space >= OT_VECTOR_SHRINK_RATIO * OT_VECTOR_MIN_MEMBERS)) { | ||
| 282 | vector->space /= OT_VECTOR_SHRINK_RATIO; | ||
| 283 | need_fix++; | ||
| 284 | } | ||
| 285 | if (need_fix) | ||
| 286 | vector->data = realloc(vector->data, vector->space * peer_size); | ||
| 287 | } | ||
diff --git a/src/ot_vector.h b/src/ot_vector.h new file mode 100644 index 0000000..8a5ba86 --- /dev/null +++ b/src/ot_vector.h | |||
| @@ -0,0 +1,39 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_VECTOR_H__ | ||
| 5 | #define OT_VECTOR_H__ | ||
| 6 | |||
| 7 | #include <stddef.h> | ||
| 8 | |||
| 9 | /* These defines control vectors behaviour */ | ||
| 10 | #define OT_VECTOR_MIN_MEMBERS 2 | ||
| 11 | #define OT_VECTOR_GROW_RATIO 2 | ||
| 12 | #define OT_VECTOR_SHRINK_THRESH 4 | ||
| 13 | #define OT_VECTOR_SHRINK_RATIO 2 | ||
| 14 | |||
| 15 | #define OT_PEER_BUCKET_MINCOUNT 512 | ||
| 16 | #define OT_PEER_BUCKET_MAXCOUNT 256 | ||
| 17 | |||
| 18 | typedef struct { | ||
| 19 | void *data; | ||
| 20 | size_t size; | ||
| 21 | size_t space; | ||
| 22 | } ot_vector; | ||
| 23 | |||
| 24 | /* trackerlogic.h needs ot_vector, break circular dependency here */ | ||
| 25 | #include "trackerlogic.h" | ||
| 26 | |||
| 27 | void *vector_binary_search(const void *const key, const void *base, const size_t member_count, const size_t member_size, size_t compare_size, int *exactmatch); | ||
| 28 | void *vector_find_or_insert(ot_vector *vector, void *key, size_t member_size, size_t compare_size, int *exactmatch); | ||
| 29 | ot_peer *vector_find_or_insert_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size, int *exactmatch); | ||
| 30 | |||
| 31 | int vector_remove_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size); | ||
| 32 | void vector_remove_torrent(ot_vector *vector, ot_torrent *match); | ||
| 33 | |||
| 34 | /* For ot_clean.c */ | ||
| 35 | void vector_redistribute_buckets(ot_peerlist *peer_list, size_t peer_size); | ||
| 36 | void vector_fixup_peers(ot_vector *vector, size_t peer_size); | ||
| 37 | void vector_clean_list(ot_vector *vector, size_t num_buckets); | ||
| 38 | |||
| 39 | #endif | ||
diff --git a/src/ot_worker.c b/src/ot_worker.c new file mode 100644 index 0000000..ef50945 --- /dev/null +++ b/src/ot_worker.c | |||
| @@ -0,0 +1,73 @@ | |||
| 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 <pthread.h> | ||
| 6 | #include <stdatomic.h> | ||
| 7 | #include <stdio.h> | ||
| 8 | #include <stdlib.h> | ||
| 9 | #include <unistd.h> | ||
| 10 | |||
| 11 | /* Opentracker */ | ||
| 12 | #include "ot_worker.h" | ||
| 13 | #include "trackerlogic.h" | ||
| 14 | |||
| 15 | static struct ot_worker **g_workers; | ||
| 16 | static size_t g_worker_count; | ||
| 17 | |||
| 18 | /* Everything a worker needs doing around its own loop, in one place, so that | ||
| 19 | a new kind of worker cannot miss a piece of it. */ | ||
| 20 | static void *worker_trampoline(void *args) { | ||
| 21 | struct ot_worker *me = args; | ||
| 22 | |||
| 23 | if (workstruct_init(&me->ws, G_INBUF_SIZE) == -1) | ||
| 24 | fprintf(stderr, "Warning: a worker could not get its buffers and will not run.\n"); | ||
| 25 | else { | ||
| 26 | me->run(me); | ||
| 27 | workstruct_deinit(&me->ws); | ||
| 28 | } | ||
| 29 | |||
| 30 | atomic_store(&me->left, 1); | ||
| 31 | return NULL; | ||
| 32 | } | ||
| 33 | |||
| 34 | int worker_start(ot_worker_run run, int64_t sock) { | ||
| 35 | struct ot_worker **grown = realloc(g_workers, (g_worker_count + 1) * sizeof(*g_workers)); | ||
| 36 | struct ot_worker *worker; | ||
| 37 | |||
| 38 | if (!grown) | ||
| 39 | return -1; | ||
| 40 | g_workers = grown; | ||
| 41 | |||
| 42 | if (!(worker = calloc(1, sizeof(*worker)))) | ||
| 43 | return -1; | ||
| 44 | worker->sock = sock; | ||
| 45 | worker->run = run; | ||
| 46 | |||
| 47 | if (pthread_create(&worker->thread, NULL, worker_trampoline, worker)) { | ||
| 48 | free(worker); | ||
| 49 | return -1; | ||
| 50 | } | ||
| 51 | g_workers[g_worker_count++] = worker; | ||
| 52 | return 0; | ||
| 53 | } | ||
| 54 | |||
| 55 | void worker_stop_all(void) { | ||
| 56 | size_t i; | ||
| 57 | |||
| 58 | for (i = 0; i < g_worker_count; ++i) { | ||
| 59 | struct ot_worker *worker = g_workers[i]; | ||
| 60 | |||
| 61 | /* Repeated because a signal only interrupts a call already under way */ | ||
| 62 | while (!atomic_load(&worker->left)) { | ||
| 63 | pthread_kill(worker->thread, OT_SIGNAL_WAKE); | ||
| 64 | usleep(1000); | ||
| 65 | } | ||
| 66 | pthread_join(worker->thread, NULL); | ||
| 67 | free(worker); | ||
| 68 | } | ||
| 69 | |||
| 70 | free(g_workers); | ||
| 71 | g_workers = NULL; | ||
| 72 | g_worker_count = 0; | ||
| 73 | } | ||
diff --git a/src/ot_worker.h b/src/ot_worker.h new file mode 100644 index 0000000..4954773 --- /dev/null +++ b/src/ot_worker.h | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_WORKER_H__ | ||
| 5 | #define OT_WORKER_H__ | ||
| 6 | |||
| 7 | #include <pthread.h> | ||
| 8 | #include <stdatomic.h> | ||
| 9 | #include <stdint.h> | ||
| 10 | |||
| 11 | #include "trackerlogic.h" | ||
| 12 | |||
| 13 | struct ot_worker; | ||
| 14 | |||
| 15 | /* What a worker does between being started and being told to stop. It reads | ||
| 16 | worker->sock and works out of worker->ws; everything around it, from the | ||
| 17 | buffers to the counters to saying goodbye, is handled here. */ | ||
| 18 | typedef void (*ot_worker_run)(struct ot_worker *worker); | ||
| 19 | |||
| 20 | /* A thread that serves requests on a socket of its own, with the buffers and | ||
| 21 | counters it needs. Each is allocated on its own, so taking on another one | ||
| 22 | never moves the record a running worker holds. */ | ||
| 23 | struct ot_worker { | ||
| 24 | pthread_t thread; | ||
| 25 | _Atomic int left; | ||
| 26 | int64_t sock; | ||
| 27 | ot_worker_run run; | ||
| 28 | struct ot_workstruct ws; | ||
| 29 | }; | ||
| 30 | |||
| 31 | /* Start one. Returns -1 if it could not be started. */ | ||
| 32 | int worker_start(ot_worker_run run, int64_t sock); | ||
| 33 | |||
| 34 | /* Wake every worker until it has left, then join it */ | ||
| 35 | void worker_stop_all(void); | ||
| 36 | |||
| 37 | #endif | ||
diff --git a/src/ot_workqueue.c b/src/ot_workqueue.c new file mode 100644 index 0000000..56a7cd2 --- /dev/null +++ b/src/ot_workqueue.c | |||
| @@ -0,0 +1,241 @@ | |||
| 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 <pthread.h> | ||
| 6 | #include <stdlib.h> | ||
| 7 | #include <string.h> | ||
| 8 | #include <sys/uio.h> | ||
| 9 | #include <unistd.h> | ||
| 10 | |||
| 11 | /* Opentracker */ | ||
| 12 | #include "ot_iovec.h" | ||
| 13 | #include "ot_workqueue.h" | ||
| 14 | #include "trackerlogic.h" | ||
| 15 | |||
| 16 | /* Pushing a result wakes the thread that will send it */ | ||
| 17 | extern int g_self_pipe[2]; | ||
| 18 | |||
| 19 | struct ot_task { | ||
| 20 | ot_taskid taskid; | ||
| 21 | ot_tasktype tasktype; | ||
| 22 | int64_t sock; | ||
| 23 | size_t iovec_entries; | ||
| 24 | struct iovec *iovec; | ||
| 25 | struct ot_task *next; | ||
| 26 | }; | ||
| 27 | |||
| 28 | static ot_taskid next_free_taskid = 1; | ||
| 29 | static struct ot_task *tasklist; | ||
| 30 | static pthread_mutex_t tasklist_mutex; | ||
| 31 | static pthread_cond_t tasklist_being_filled; | ||
| 32 | |||
| 33 | int workqueue_pushtask(int64_t sock, ot_tasktype tasktype) { | ||
| 34 | struct ot_task **tmptask, *task; | ||
| 35 | |||
| 36 | task = malloc(sizeof(struct ot_task)); | ||
| 37 | if (!task) | ||
| 38 | return -1; | ||
| 39 | |||
| 40 | task->taskid = 0; | ||
| 41 | task->tasktype = tasktype; | ||
| 42 | task->sock = sock; | ||
| 43 | task->iovec_entries = 0; | ||
| 44 | task->iovec = NULL; | ||
| 45 | task->next = 0; | ||
| 46 | |||
| 47 | /* Want exclusive access to tasklist */ | ||
| 48 | pthread_mutex_lock(&tasklist_mutex); | ||
| 49 | |||
| 50 | /* Skip to end of list */ | ||
| 51 | tmptask = &tasklist; | ||
| 52 | while (*tmptask) | ||
| 53 | tmptask = &(*tmptask)->next; | ||
| 54 | *tmptask = task; | ||
| 55 | |||
| 56 | /* Inform waiting workers and release lock */ | ||
| 57 | pthread_cond_broadcast(&tasklist_being_filled); | ||
| 58 | pthread_mutex_unlock(&tasklist_mutex); | ||
| 59 | return 0; | ||
| 60 | } | ||
| 61 | |||
| 62 | void workqueue_canceltask(int64_t sock) { | ||
| 63 | struct ot_task **task; | ||
| 64 | |||
| 65 | /* Want exclusive access to tasklist */ | ||
| 66 | pthread_mutex_lock(&tasklist_mutex); | ||
| 67 | |||
| 68 | for (task = &tasklist; *task; task = &((*task)->next)) | ||
| 69 | if ((*task)->sock == sock) { | ||
| 70 | struct iovec *iovec = (*task)->iovec; | ||
| 71 | struct ot_task *ptask = *task; | ||
| 72 | size_t i; | ||
| 73 | |||
| 74 | /* Free task's iovec, the buffers it points at and the vector itself */ | ||
| 75 | for (i = 0; i < (*task)->iovec_entries; ++i) | ||
| 76 | free(iovec[i].iov_base); | ||
| 77 | free(iovec); | ||
| 78 | |||
| 79 | *task = (*task)->next; | ||
| 80 | free(ptask); | ||
| 81 | break; | ||
| 82 | } | ||
| 83 | |||
| 84 | /* Release lock */ | ||
| 85 | pthread_mutex_unlock(&tasklist_mutex); | ||
| 86 | } | ||
| 87 | |||
| 88 | ot_taskid workqueue_poptask(ot_tasktype *tasktype) { | ||
| 89 | struct ot_task *task; | ||
| 90 | ot_taskid taskid = 0; | ||
| 91 | |||
| 92 | /* Want exclusive access to tasklist */ | ||
| 93 | pthread_mutex_lock(&tasklist_mutex); | ||
| 94 | |||
| 95 | while (!taskid) { | ||
| 96 | /* Skip to the first unassigned task this worker wants to do */ | ||
| 97 | for (task = tasklist; task; task = task->next) | ||
| 98 | if (!task->taskid && (TASK_CLASS_MASK & task->tasktype) == *tasktype) { | ||
| 99 | /* If we found an outstanding task, assign a taskid to it | ||
| 100 | and leave the loop */ | ||
| 101 | task->taskid = taskid = ++next_free_taskid; | ||
| 102 | *tasktype = task->tasktype; | ||
| 103 | break; | ||
| 104 | } | ||
| 105 | |||
| 106 | /* Wait until the next task is being fed, unless the tracker is on its | ||
| 107 | way out, in which case the worker is meant to leave rather than wait */ | ||
| 108 | if (!taskid) { | ||
| 109 | if (!g_opentracker_running) | ||
| 110 | break; | ||
| 111 | pthread_cond_wait(&tasklist_being_filled, &tasklist_mutex); | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | /* Release lock */ | ||
| 116 | pthread_mutex_unlock(&tasklist_mutex); | ||
| 117 | |||
| 118 | return taskid; | ||
| 119 | } | ||
| 120 | |||
| 121 | void workqueue_pushsuccess(ot_taskid taskid) { | ||
| 122 | struct ot_task **task; | ||
| 123 | |||
| 124 | /* Want exclusive access to tasklist */ | ||
| 125 | pthread_mutex_lock(&tasklist_mutex); | ||
| 126 | |||
| 127 | for (task = &tasklist; *task; task = &((*task)->next)) | ||
| 128 | if ((*task)->taskid == taskid) { | ||
| 129 | struct ot_task *ptask = *task; | ||
| 130 | *task = (*task)->next; | ||
| 131 | free(ptask); | ||
| 132 | break; | ||
| 133 | } | ||
| 134 | |||
| 135 | /* Release lock */ | ||
| 136 | pthread_mutex_unlock(&tasklist_mutex); | ||
| 137 | } | ||
| 138 | |||
| 139 | int workqueue_pushresult(ot_taskid taskid, size_t iovec_entries, struct iovec *iovec) { | ||
| 140 | struct ot_task *task; | ||
| 141 | const char byte = 'o'; | ||
| 142 | |||
| 143 | /* Want exclusive access to tasklist */ | ||
| 144 | pthread_mutex_lock(&tasklist_mutex); | ||
| 145 | |||
| 146 | for (task = tasklist; task; task = task->next) | ||
| 147 | if (task->taskid == taskid) { | ||
| 148 | task->iovec_entries = iovec_entries; | ||
| 149 | task->iovec = iovec; | ||
| 150 | task->tasktype = TASK_DONE; | ||
| 151 | break; | ||
| 152 | } | ||
| 153 | |||
| 154 | /* Release lock */ | ||
| 155 | pthread_mutex_unlock(&tasklist_mutex); | ||
| 156 | |||
| 157 | /* A full pipe has already done this nudge's job, so a failure is fine */ | ||
| 158 | (void)!write(g_self_pipe[1], &byte, 1); | ||
| 159 | |||
| 160 | /* Indicate whether the worker has to throw away results */ | ||
| 161 | return task ? 0 : -1; | ||
| 162 | } | ||
| 163 | |||
| 164 | int workqueue_pushchunked(ot_taskid taskid, struct iovec *iovec) { | ||
| 165 | struct ot_task *task; | ||
| 166 | const char byte = 'o'; | ||
| 167 | |||
| 168 | /* Want exclusive access to tasklist */ | ||
| 169 | pthread_mutex_lock(&tasklist_mutex); | ||
| 170 | |||
| 171 | for (task = tasklist; task; task = task->next) | ||
| 172 | if (task->taskid == taskid) { | ||
| 173 | if (iovec) { | ||
| 174 | if (iovec_append(&task->iovec_entries, &task->iovec, iovec)) | ||
| 175 | task->tasktype = TASK_DONE_PARTIAL; | ||
| 176 | else | ||
| 177 | task = NULL; | ||
| 178 | } else | ||
| 179 | task->tasktype = TASK_DONE; | ||
| 180 | break; | ||
| 181 | } | ||
| 182 | |||
| 183 | /* Release lock */ | ||
| 184 | pthread_mutex_unlock(&tasklist_mutex); | ||
| 185 | |||
| 186 | /* A full pipe has already done this nudge's job, so a failure is fine */ | ||
| 187 | (void)!write(g_self_pipe[1], &byte, 1); | ||
| 188 | |||
| 189 | /* Indicate whether the worker has to throw away results */ | ||
| 190 | return task ? 0 : -1; | ||
| 191 | } | ||
| 192 | |||
| 193 | int64_t workqueue_popresult(size_t *iovec_entries, struct iovec **iovec, int *is_partial) { | ||
| 194 | struct ot_task **task; | ||
| 195 | int64_t sock = -1; | ||
| 196 | |||
| 197 | *is_partial = 0; | ||
| 198 | |||
| 199 | /* Want exclusive access to tasklist */ | ||
| 200 | pthread_mutex_lock(&tasklist_mutex); | ||
| 201 | |||
| 202 | for (task = &tasklist; *task; task = &((*task)->next)) | ||
| 203 | if (((*task)->tasktype & TASK_CLASS_MASK) == TASK_DONE) { | ||
| 204 | struct ot_task *ptask = *task; | ||
| 205 | *iovec_entries = ptask->iovec_entries; | ||
| 206 | *iovec = ptask->iovec; | ||
| 207 | sock = ptask->sock; | ||
| 208 | |||
| 209 | if ((*task)->tasktype == TASK_DONE) { | ||
| 210 | *task = ptask->next; | ||
| 211 | free(ptask); | ||
| 212 | } else { | ||
| 213 | ptask->iovec_entries = 0; | ||
| 214 | ptask->iovec = NULL; | ||
| 215 | *is_partial = 1; | ||
| 216 | /* Prevent task from showing up immediately again unless new data was added */ | ||
| 217 | (*task)->tasktype = TASK_FULLSCRAPE; | ||
| 218 | } | ||
| 219 | break; | ||
| 220 | } | ||
| 221 | |||
| 222 | /* Release lock */ | ||
| 223 | pthread_mutex_unlock(&tasklist_mutex); | ||
| 224 | return sock; | ||
| 225 | } | ||
| 226 | |||
| 227 | void workqueue_wakeall(void) { | ||
| 228 | pthread_mutex_lock(&tasklist_mutex); | ||
| 229 | pthread_cond_broadcast(&tasklist_being_filled); | ||
| 230 | pthread_mutex_unlock(&tasklist_mutex); | ||
| 231 | } | ||
| 232 | |||
| 233 | void workqueue_init(void) { | ||
| 234 | pthread_mutex_init(&tasklist_mutex, NULL); | ||
| 235 | pthread_cond_init(&tasklist_being_filled, NULL); | ||
| 236 | } | ||
| 237 | |||
| 238 | void workqueue_deinit(void) { | ||
| 239 | pthread_mutex_destroy(&tasklist_mutex); | ||
| 240 | pthread_cond_destroy(&tasklist_being_filled); | ||
| 241 | } | ||
diff --git a/src/ot_workqueue.h b/src/ot_workqueue.h new file mode 100644 index 0000000..82ae29b --- /dev/null +++ b/src/ot_workqueue.h | |||
| @@ -0,0 +1,74 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_WORKQUEUE_H__ | ||
| 5 | #define OT_WORKQUEUE_H__ | ||
| 6 | |||
| 7 | #include <stdint.h> | ||
| 8 | #include <sys/uio.h> | ||
| 9 | |||
| 10 | #include "trackerlogic.h" | ||
| 11 | |||
| 12 | /* Work the tracker hands to a thread of its own: a full scrape or an | ||
| 13 | expensive stats page, which take too long to build while a client waits. | ||
| 14 | The task goes in here, the reply comes back out. */ | ||
| 15 | |||
| 16 | typedef enum { | ||
| 17 | TASK_STATS_CONNS = 0x0001, | ||
| 18 | TASK_STATS_TCP = 0x0002, | ||
| 19 | TASK_STATS_UDP = 0x0003, | ||
| 20 | TASK_STATS_SCRAPE = 0x0004, | ||
| 21 | TASK_STATS_FULLSCRAPE = 0x0005, | ||
| 22 | TASK_STATS_TPB = 0x0006, | ||
| 23 | TASK_STATS_HTTPERRORS = 0x0007, | ||
| 24 | TASK_STATS_RENEW = 0x000a, | ||
| 25 | TASK_STATS_SYNCS = 0x000b, | ||
| 26 | TASK_STATS_COMPLETED = 0x000c, | ||
| 27 | TASK_STATS_NUMWANTS = 0x000d, | ||
| 28 | |||
| 29 | TASK_STATS = 0x0100, /* Mask */ | ||
| 30 | TASK_STATS_TORRENTS = 0x0101, | ||
| 31 | TASK_STATS_PEERS = 0x0102, | ||
| 32 | TASK_STATS_SLASH24S = 0x0103, | ||
| 33 | TASK_STATS_TOP10 = 0x0104, | ||
| 34 | TASK_STATS_TOP100 = 0x0105, | ||
| 35 | TASK_STATS_EVERYTHING = 0x0106, | ||
| 36 | TASK_STATS_WOODPECKERS = 0x0108, | ||
| 37 | |||
| 38 | TASK_FULLSCRAPE = 0x0200, /* Default mode */ | ||
| 39 | TASK_FULLSCRAPE_TPB_BINARY = 0x0201, | ||
| 40 | TASK_FULLSCRAPE_TPB_ASCII = 0x0202, | ||
| 41 | TASK_FULLSCRAPE_TPB_ASCII_PLUS = 0x0203, | ||
| 42 | TASK_FULLSCRAPE_TPB_URLENCODED = 0x0204, | ||
| 43 | TASK_FULLSCRAPE_TRACKERSTATE = 0x0205, | ||
| 44 | |||
| 45 | TASK_DMEM = 0x0300, | ||
| 46 | |||
| 47 | TASK_DONE = 0x0f00, | ||
| 48 | TASK_DONE_PARTIAL = 0x0f01, | ||
| 49 | |||
| 50 | TASK_FLAG_GZIP = 0x1000, | ||
| 51 | TASK_FLAG_BZIP2 = 0x2000, | ||
| 52 | TASK_FLAG_ZSTD = 0x4000, | ||
| 53 | TASK_FLAG_CHUNKED = 0x8000, | ||
| 54 | |||
| 55 | TASK_TASK_MASK = 0x0fff, | ||
| 56 | TASK_CLASS_MASK = 0x0f00, | ||
| 57 | TASK_FLAGS_MASK = 0xf000 | ||
| 58 | } ot_tasktype; | ||
| 59 | |||
| 60 | typedef unsigned long ot_taskid; | ||
| 61 | |||
| 62 | int workqueue_pushtask(int64_t sock, ot_tasktype tasktype); | ||
| 63 | void workqueue_canceltask(int64_t sock); | ||
| 64 | void workqueue_pushsuccess(ot_taskid taskid); | ||
| 65 | ot_taskid workqueue_poptask(ot_tasktype *tasktype); | ||
| 66 | int workqueue_pushresult(ot_taskid taskid, size_t iovec_entries, struct iovec *iovector); | ||
| 67 | int workqueue_pushchunked(ot_taskid taskid, struct iovec *iovec); | ||
| 68 | int64_t workqueue_popresult(size_t *iovec_entries, struct iovec **iovector, int *is_partial); | ||
| 69 | |||
| 70 | void workqueue_init(void); | ||
| 71 | void workqueue_deinit(void); | ||
| 72 | void workqueue_wakeall(void); | ||
| 73 | |||
| 74 | #endif | ||
diff --git a/src/proxy.c b/src/proxy.c new file mode 100644 index 0000000..8589db5 --- /dev/null +++ b/src/proxy.c | |||
| @@ -0,0 +1,871 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | /* This relays sync traffic between trackers, and between multicast domains | ||
| 5 | that have no route to each other, so that no tracker has to reach out to | ||
| 6 | the outside world by itself. Defunc. */ | ||
| 7 | |||
| 8 | /* System */ | ||
| 9 | #include <arpa/inet.h> | ||
| 10 | #include <ctype.h> | ||
| 11 | #include <errno.h> | ||
| 12 | #include <pthread.h> | ||
| 13 | #include <stdint.h> | ||
| 14 | #include <stdio.h> | ||
| 15 | #include <stdlib.h> | ||
| 16 | #include <string.h> | ||
| 17 | #include <unistd.h> | ||
| 18 | |||
| 19 | /* Libowfat */ | ||
| 20 | #include "io.h" | ||
| 21 | |||
| 22 | /* Opentracker */ | ||
| 23 | #include "ot_batch.h" | ||
| 24 | #include "ot_fdstate.h" | ||
| 25 | #include "ot_ip.h" | ||
| 26 | #include "ot_mutex.h" | ||
| 27 | #include "ot_random.h" | ||
| 28 | #include "ot_scan.h" | ||
| 29 | #include "ot_socket.h" | ||
| 30 | #include "ot_stats.h" | ||
| 31 | #include "ot_vector.h" | ||
| 32 | #include "trackerlogic.h" | ||
| 33 | |||
| 34 | #include "ot_livesync.h" | ||
| 35 | |||
| 36 | ot_ip6 g_serverip; | ||
| 37 | uint16_t g_serverport = 9009; | ||
| 38 | uint32_t g_tracker_id; | ||
| 39 | ot_ip6 groupip_1 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 224, 0, 23, 5}; | ||
| 40 | int g_self_pipe[2]; | ||
| 41 | |||
| 42 | /* If you have more than 10 peers, don't use this proxy | ||
| 43 | Use 20 slots for 10 peers to have room for 10 incoming connection slots | ||
| 44 | */ | ||
| 45 | #define MAX_PEERS 20 | ||
| 46 | |||
| 47 | #define LIVESYNC_INCOMING_BUFFSIZE (256 * 256) | ||
| 48 | #define STREAMSYNC_OUTGOING_BUFFSIZE (256 * 256) | ||
| 49 | |||
| 50 | #define LIVESYNC_OUTGOING_BUFFSIZE_PEERS 1480 | ||
| 51 | #define LIVESYNC_OUTGOING_WATERMARK_PEERS (sizeof(ot_peer) + sizeof(ot_hash)) | ||
| 52 | #define LIVESYNC_MAXDELAY 15 /* seconds */ | ||
| 53 | |||
| 54 | /* The amount of time a complete sync cycle should take */ | ||
| 55 | #define OT_SYNC_INTERVAL_MINUTES 2 | ||
| 56 | |||
| 57 | /* So after each bucket wait 1 / OT_BUCKET_COUNT intervals */ | ||
| 58 | #define OT_SYNC_SLEEP (((OT_SYNC_INTERVAL_MINUTES) * 60 * 1000000) / (OT_BUCKET_COUNT)) | ||
| 59 | |||
| 60 | enum { OT_SYNC_PEER4, OT_SYNC_PEER6 }; | ||
| 61 | enum { FLAG_SERVERSOCKET = 1 }; | ||
| 62 | |||
| 63 | /* For incoming packets */ | ||
| 64 | static int g_socket_in = -1; | ||
| 65 | static uint8_t g_inbuffer[LIVESYNC_INCOMING_BUFFSIZE]; | ||
| 66 | |||
| 67 | /* For outgoing packets */ | ||
| 68 | static int g_socket_out = -1; | ||
| 69 | static uint8_t g_peerbuffer_start[LIVESYNC_OUTGOING_BUFFSIZE_PEERS]; | ||
| 70 | static uint8_t *g_peerbuffer_pos; | ||
| 71 | static uint8_t *g_peerbuffer_highwater = g_peerbuffer_start + LIVESYNC_OUTGOING_BUFFSIZE_PEERS - LIVESYNC_OUTGOING_WATERMARK_PEERS; | ||
| 72 | static ot_time g_next_packet_time; | ||
| 73 | |||
| 74 | static void *livesync_worker(void *args); | ||
| 75 | static void *streamsync_worker(void *args); | ||
| 76 | static void livesync_proxytell(uint8_t prefix, uint8_t *info_hash, uint8_t *peer); | ||
| 77 | |||
| 78 | void exerr(const char *message) { | ||
| 79 | fprintf(stderr, "%s\n", message); | ||
| 80 | exit(111); | ||
| 81 | } | ||
| 82 | |||
| 83 | void stats_issue_event(struct ot_workstruct *ws, ot_status_event event, PROTO_FLAG proto, uintptr_t event_data) { | ||
| 84 | (void)ws; | ||
| 85 | (void)event; | ||
| 86 | (void)proto; | ||
| 87 | (void)event_data; | ||
| 88 | } | ||
| 89 | |||
| 90 | void livesync_bind_mcast(ot_ip6 ip, uint16_t port) { | ||
| 91 | if (!ip_is_v4mapped(ip)) | ||
| 92 | exerr("v6 mcast support not yet available."); | ||
| 93 | |||
| 94 | if (g_socket_in != -1) | ||
| 95 | exerr("Error: Livesync listen ip specified twice."); | ||
| 96 | |||
| 97 | if ((g_socket_in = socket_mcast_join(groupip_1, ip, port)) == -1) | ||
| 98 | exerr("Error: Cant bind live sync incoming socket."); | ||
| 99 | |||
| 100 | if ((g_socket_out = socket_mcast_bind(ip, port, 1)) == -1) | ||
| 101 | exerr("Error: Cant bind live sync outgoing socket."); | ||
| 102 | } | ||
| 103 | |||
| 104 | size_t add_peer_to_torrent_proxy(ot_hash hash, ot_peer *peer, size_t peer_size) { | ||
| 105 | int exactmatch; | ||
| 106 | ot_torrent *torrent; | ||
| 107 | ot_peerlist *peer_list; | ||
| 108 | ot_peer *peer_dest; | ||
| 109 | ot_vector *torrents_list = mutex_bucket_lock_by_hash(hash); | ||
| 110 | size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size); | ||
| 111 | |||
| 112 | torrent = vector_find_or_insert(torrents_list, (void *)hash, sizeof(ot_torrent), compare_size, &exactmatch); | ||
| 113 | if (!torrent) | ||
| 114 | return -1; | ||
| 115 | |||
| 116 | if (!exactmatch) { | ||
| 117 | /* Create a new torrent entry, then */ | ||
| 118 | memcpy(torrent->hash, hash, sizeof(ot_hash)); | ||
| 119 | |||
| 120 | if (!(torrent->peer_list6 = malloc(sizeof(ot_peerlist))) || !(torrent->peer_list4 = malloc(sizeof(ot_peerlist)))) { | ||
| 121 | vector_remove_torrent(torrents_list, torrent); | ||
| 122 | mutex_bucket_unlock_by_hash(hash, 0); | ||
| 123 | return -1; | ||
| 124 | } | ||
| 125 | |||
| 126 | memset(torrent->peer_list6, 0, sizeof(ot_peerlist)); | ||
| 127 | memset(torrent->peer_list4, 0, sizeof(ot_peerlist)); | ||
| 128 | } | ||
| 129 | |||
| 130 | peer_list = peer_size == OT_PEER_SIZE6 ? torrent->peer_list6 : torrent->peer_list4; | ||
| 131 | |||
| 132 | /* Check for peer in torrent */ | ||
| 133 | peer_dest = vector_find_or_insert_peer(&(peer_list->peers), peer, peer_size, &exactmatch); | ||
| 134 | if (!peer_dest) { | ||
| 135 | mutex_bucket_unlock_by_hash(hash, 0); | ||
| 136 | return -1; | ||
| 137 | } | ||
| 138 | /* Tell peer that it's fresh */ | ||
| 139 | OT_PEERTIME(peer, peer_size) = 0; | ||
| 140 | |||
| 141 | /* If we hadn't had a match create peer there */ | ||
| 142 | if (!exactmatch) { | ||
| 143 | peer_list->peer_count++; | ||
| 144 | if (OT_PEERFLAG_D(peer, peer_size) & PEER_FLAG_SEEDING) | ||
| 145 | peer_list->seed_count++; | ||
| 146 | } | ||
| 147 | memcpy(peer_dest, peer, peer_size); | ||
| 148 | mutex_bucket_unlock_by_hash(hash, 0); | ||
| 149 | return 0; | ||
| 150 | } | ||
| 151 | |||
| 152 | size_t remove_peer_from_torrent_proxy(ot_hash hash, ot_peer *peer, size_t peer_size) { | ||
| 153 | int exactmatch; | ||
| 154 | ot_vector *torrents_list = mutex_bucket_lock_by_hash(hash); | ||
| 155 | ot_torrent *torrent = vector_binary_search(hash, torrents_list->data, torrents_list->size, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch); | ||
| 156 | |||
| 157 | if (exactmatch) { | ||
| 158 | ot_peerlist *peer_list = peer_list = peer_size == OT_PEER_SIZE6 ? torrent->peer_list6 : torrent->peer_list4; | ||
| 159 | switch (vector_remove_peer(&peer_list->peers, peer, peer_size)) { | ||
| 160 | case 2: | ||
| 161 | peer_list->seed_count--; /* Intentional fallthrough */ | ||
| 162 | case 1: | ||
| 163 | peer_list->peer_count--; /* Intentional fallthrough */ | ||
| 164 | default: | ||
| 165 | break; | ||
| 166 | } | ||
| 167 | } | ||
| 168 | |||
| 169 | mutex_bucket_unlock_by_hash(hash, 0); | ||
| 170 | return 0; | ||
| 171 | } | ||
| 172 | |||
| 173 | void free_peerlist(ot_peerlist *peer_list) { | ||
| 174 | if (peer_list->peers.data) { | ||
| 175 | if (OT_PEERLIST_HASBUCKETS(peer_list)) { | ||
| 176 | ot_vector *bucket_list = (ot_vector *)(peer_list->peers.data); | ||
| 177 | |||
| 178 | while (peer_list->peers.size--) | ||
| 179 | free(bucket_list++->data); | ||
| 180 | } | ||
| 181 | free(peer_list->peers.data); | ||
| 182 | } | ||
| 183 | free(peer_list); | ||
| 184 | } | ||
| 185 | |||
| 186 | static void livesync_handle_peersync(ssize_t datalen, size_t peer_size) { | ||
| 187 | int off = sizeof(g_tracker_id) + sizeof(uint32_t); | ||
| 188 | |||
| 189 | fprintf(stderr, "."); | ||
| 190 | |||
| 191 | while ((ssize_t)(off + sizeof(ot_hash) + peer_size) <= datalen) { | ||
| 192 | ot_peer *peer = (ot_peer *)(g_inbuffer + off + sizeof(ot_hash)); | ||
| 193 | ot_hash *hash = (ot_hash *)(g_inbuffer + off); | ||
| 194 | |||
| 195 | if (OT_PEERFLAG_D(peer, peer_size) & PEER_FLAG_STOPPED) | ||
| 196 | remove_peer_from_torrent_proxy(*hash, peer, peer_size); | ||
| 197 | else | ||
| 198 | add_peer_to_torrent_proxy(*hash, peer, peer_size); | ||
| 199 | |||
| 200 | off += sizeof(ot_hash) + peer_size; | ||
| 201 | } | ||
| 202 | } | ||
| 203 | |||
| 204 | int usage(char *self) { | ||
| 205 | fprintf(stderr, "Usage: %s -L <livesync_iface_ip> -l <listenip>:<listenport> -c <connectip>:<connectport>\n", self); | ||
| 206 | return 0; | ||
| 207 | } | ||
| 208 | |||
| 209 | enum { | ||
| 210 | FLAG_OUTGOING = 0x80, | ||
| 211 | |||
| 212 | FLAG_DISCONNECTED = 0x00, | ||
| 213 | FLAG_CONNECTING = 0x01, | ||
| 214 | FLAG_WAITTRACKERID = 0x02, | ||
| 215 | FLAG_CONNECTED = 0x03, | ||
| 216 | |||
| 217 | FLAG_MASK = 0x07 | ||
| 218 | }; | ||
| 219 | |||
| 220 | #define PROXYPEER_NEEDSCONNECT(flag) ((flag) == FLAG_OUTGOING) | ||
| 221 | #define PROXYPEER_ISCONNECTED(flag) (((flag) & FLAG_MASK) == FLAG_CONNECTED) | ||
| 222 | #define PROXYPEER_SETDISCONNECTED(flag) (flag) = (((flag) & FLAG_OUTGOING) | FLAG_DISCONNECTED) | ||
| 223 | #define PROXYPEER_SETCONNECTING(flag) (flag) = (((flag) & FLAG_OUTGOING) | FLAG_CONNECTING) | ||
| 224 | #define PROXYPEER_SETWAITTRACKERID(flag) (flag) = (((flag) & FLAG_OUTGOING) | FLAG_WAITTRACKERID) | ||
| 225 | #define PROXYPEER_SETCONNECTED(flag) (flag) = (((flag) & FLAG_OUTGOING) | FLAG_CONNECTED) | ||
| 226 | |||
| 227 | typedef struct { | ||
| 228 | int state; /* Whether we want to connect, how far our handshake is, etc. */ | ||
| 229 | ot_ip6 ip; /* The peer to connect to */ | ||
| 230 | uint16_t port; /* The peers port */ | ||
| 231 | uint8_t indata[8192 * 16]; /* Any data not processed yet */ | ||
| 232 | size_t indata_length; /* Length of unprocessed data */ | ||
| 233 | uint32_t tracker_id; /* How the other end greeted */ | ||
| 234 | int64_t fd; /* A file handle, if connected, <= 0 is disconnected (0 initially, -1 else) */ | ||
| 235 | ot_batch outdata; /* The batch containing our sync data */ | ||
| 236 | |||
| 237 | size_t packet_tcount; /* Number of unprocessed torrents in packet we currently receive */ | ||
| 238 | uint8_t packet_tprefix; /* Prefix byte for all torrents in current packet */ | ||
| 239 | uint8_t packet_type; /* Type of current packet */ | ||
| 240 | uint32_t packet_tid; /* Tracker id for current packet */ | ||
| 241 | |||
| 242 | } proxy_peer; | ||
| 243 | static void process_indata(proxy_peer *peer); | ||
| 244 | |||
| 245 | void reset_info_block(proxy_peer *peer) { | ||
| 246 | peer->indata_length = 0; | ||
| 247 | peer->tracker_id = 0; | ||
| 248 | peer->fd = -1; | ||
| 249 | peer->packet_tcount = 0; | ||
| 250 | batch_reset(&peer->outdata); | ||
| 251 | PROXYPEER_SETDISCONNECTED(peer->state); | ||
| 252 | } | ||
| 253 | |||
| 254 | /* Number of connections to peers | ||
| 255 | * If a peer's IP is set, we try to reconnect, when the connection drops | ||
| 256 | * If we already have a connected tracker_id in our records for an _incoming_ connection, drop it | ||
| 257 | * Multiple connections to/from the same ip are okay, if tracker_id doesn't match | ||
| 258 | * Reconnect attempts occur only twice a minute | ||
| 259 | */ | ||
| 260 | static size_t g_connection_count; | ||
| 261 | static ot_time g_connection_reconn; | ||
| 262 | static proxy_peer g_connections[MAX_PEERS]; | ||
| 263 | |||
| 264 | static void handle_reconnects(void) { | ||
| 265 | size_t i; | ||
| 266 | for (i = 0; i < g_connection_count; ++i) | ||
| 267 | if (PROXYPEER_NEEDSCONNECT(g_connections[i].state)) { | ||
| 268 | int64_t newfd = socket_tcp_connect(g_serverip, g_serverport, g_connections[i].ip, g_connections[i].port); | ||
| 269 | fprintf(stderr, "(Re)connecting to peer..."); | ||
| 270 | if (newfd == -1) | ||
| 271 | continue; /* No socket for you */ | ||
| 272 | io_fd(newfd); | ||
| 273 | io_wantwrite(newfd); /* So we will be informed when it is connected */ | ||
| 274 | fdstate_open(newfd, g_connections + i, 0); | ||
| 275 | |||
| 276 | /* Prepare connection info block */ | ||
| 277 | reset_info_block(g_connections + i); | ||
| 278 | g_connections[i].fd = newfd; | ||
| 279 | PROXYPEER_SETCONNECTING(g_connections[i].state); | ||
| 280 | } | ||
| 281 | g_connection_reconn = time(NULL) + 30; | ||
| 282 | } | ||
| 283 | |||
| 284 | /* Handle incoming connection requests, check against whitelist */ | ||
| 285 | static void handle_accept(int64_t serversocket) { | ||
| 286 | int64_t newfd; | ||
| 287 | ot_ip6 ip; | ||
| 288 | uint16_t port; | ||
| 289 | |||
| 290 | while ((newfd = socket_accept(serversocket, ip, &port)) != -1) { | ||
| 291 | |||
| 292 | /* XXX some access control */ | ||
| 293 | |||
| 294 | /* Put fd into a non-blocking mode */ | ||
| 295 | io_nonblock(newfd); | ||
| 296 | |||
| 297 | if (!io_fd(newfd)) | ||
| 298 | io_close(newfd); | ||
| 299 | else { | ||
| 300 | /* Find a new home for our incoming connection */ | ||
| 301 | int i; | ||
| 302 | for (i = 0; i < MAX_PEERS; ++i) | ||
| 303 | if (g_connections[i].state == FLAG_DISCONNECTED) | ||
| 304 | break; | ||
| 305 | if (i == MAX_PEERS) { | ||
| 306 | fprintf(stderr, "No room for incoming connection."); | ||
| 307 | close(newfd); | ||
| 308 | continue; | ||
| 309 | } | ||
| 310 | |||
| 311 | /* Prepare connection info block */ | ||
| 312 | reset_info_block(g_connections + i); | ||
| 313 | PROXYPEER_SETCONNECTING(g_connections[i].state); | ||
| 314 | g_connections[i].port = port; | ||
| 315 | g_connections[i].fd = newfd; | ||
| 316 | |||
| 317 | fdstate_open(newfd, g_connections + i, 0); | ||
| 318 | |||
| 319 | /* We expect the connecting side to begin with its tracker_id */ | ||
| 320 | io_wantread(newfd); | ||
| 321 | } | ||
| 322 | } | ||
| 323 | |||
| 324 | return; | ||
| 325 | } | ||
| 326 | |||
| 327 | /* New sync data on the stream */ | ||
| 328 | static void handle_read(int64_t peersocket) { | ||
| 329 | int i; | ||
| 330 | int64_t datalen; | ||
| 331 | uint32_t tracker_id; | ||
| 332 | proxy_peer *peer = fdstate_cookie(peersocket); | ||
| 333 | |||
| 334 | if (!peer) { | ||
| 335 | /* Can't happen ;) */ | ||
| 336 | io_close(peersocket); | ||
| 337 | return; | ||
| 338 | } | ||
| 339 | switch (peer->state & FLAG_MASK) { | ||
| 340 | case FLAG_DISCONNECTED: | ||
| 341 | io_close(peersocket); | ||
| 342 | break; /* Shouldnt happen */ | ||
| 343 | case FLAG_CONNECTING: | ||
| 344 | case FLAG_WAITTRACKERID: | ||
| 345 | /* We want at least the first four bytes to come at once, to avoid keeping extra states (for now) | ||
| 346 | This also catches 0 bytes reads == EOF and negative values, denoting connection errors */ | ||
| 347 | if (io_tryread(peersocket, (void *)&tracker_id, sizeof(tracker_id)) != sizeof(tracker_id)) | ||
| 348 | goto close_socket; | ||
| 349 | |||
| 350 | /* See, if we already have a connection to that peer */ | ||
| 351 | for (i = 0; i < MAX_PEERS; ++i) | ||
| 352 | if ((g_connections[i].state & FLAG_MASK) == FLAG_CONNECTED && g_connections[i].tracker_id == tracker_id) { | ||
| 353 | fprintf(stderr, "Peer already connected. Closing connection.\n"); | ||
| 354 | goto close_socket; | ||
| 355 | } | ||
| 356 | |||
| 357 | /* Also no need for soliloquy */ | ||
| 358 | if (tracker_id == g_tracker_id) | ||
| 359 | goto close_socket; | ||
| 360 | |||
| 361 | /* The new connection is good, send our tracker_id on incoming connections */ | ||
| 362 | if (peer->state == FLAG_CONNECTING) | ||
| 363 | if (io_trywrite(peersocket, (void *)&g_tracker_id, sizeof(g_tracker_id)) != sizeof(g_tracker_id)) | ||
| 364 | goto close_socket; | ||
| 365 | |||
| 366 | peer->tracker_id = tracker_id; | ||
| 367 | PROXYPEER_SETCONNECTED(peer->state); | ||
| 368 | |||
| 369 | if (peer->state & FLAG_OUTGOING) | ||
| 370 | fprintf(stderr, "succeeded.\n"); | ||
| 371 | else | ||
| 372 | fprintf(stderr, "Incoming connection successful.\n"); | ||
| 373 | |||
| 374 | break; | ||
| 375 | close_socket: | ||
| 376 | fprintf(stderr, "Handshake incomplete, closing socket\n"); | ||
| 377 | io_close(peersocket); | ||
| 378 | reset_info_block(peer); | ||
| 379 | break; | ||
| 380 | case FLAG_CONNECTED: | ||
| 381 | /* Here we acutally expect data from peer | ||
| 382 | indata_length should be less than 20+256*7 bytes, for incomplete torrent entries */ | ||
| 383 | datalen = io_tryread(peersocket, (void *)(peer->indata + peer->indata_length), sizeof(peer->indata) - peer->indata_length); | ||
| 384 | if (!datalen || datalen < -1) { | ||
| 385 | fprintf(stderr, "Connection closed by remote peer.\n"); | ||
| 386 | io_close(peersocket); | ||
| 387 | reset_info_block(peer); | ||
| 388 | } else if (datalen > 0) { | ||
| 389 | peer->indata_length += datalen; | ||
| 390 | process_indata(peer); | ||
| 391 | } | ||
| 392 | break; | ||
| 393 | } | ||
| 394 | } | ||
| 395 | |||
| 396 | /* Can write new sync data to the stream */ | ||
| 397 | static void handle_write(int64_t peersocket) { | ||
| 398 | proxy_peer *peer = fdstate_cookie(peersocket); | ||
| 399 | |||
| 400 | if (!peer) { | ||
| 401 | /* Can't happen ;) */ | ||
| 402 | io_close(peersocket); | ||
| 403 | return; | ||
| 404 | } | ||
| 405 | |||
| 406 | switch (peer->state & FLAG_MASK) { | ||
| 407 | case FLAG_DISCONNECTED: | ||
| 408 | default: /* Should not happen */ | ||
| 409 | io_close(peersocket); | ||
| 410 | break; | ||
| 411 | case FLAG_CONNECTING: | ||
| 412 | /* Ensure that the connection is established and handle connection error */ | ||
| 413 | if (peer->state & FLAG_OUTGOING && !socket_is_connected(peersocket)) { | ||
| 414 | fprintf(stderr, "failed\n"); | ||
| 415 | reset_info_block(peer); | ||
| 416 | io_close(peersocket); | ||
| 417 | break; | ||
| 418 | } | ||
| 419 | |||
| 420 | if (io_trywrite(peersocket, (void *)&g_tracker_id, sizeof(g_tracker_id)) == sizeof(g_tracker_id)) { | ||
| 421 | PROXYPEER_SETWAITTRACKERID(peer->state); | ||
| 422 | io_dontwantwrite(peersocket); | ||
| 423 | io_wantread(peersocket); | ||
| 424 | } else { | ||
| 425 | fprintf(stderr, "Handshake incomplete, closing socket\n"); | ||
| 426 | io_close(peersocket); | ||
| 427 | reset_info_block(peer); | ||
| 428 | } | ||
| 429 | break; | ||
| 430 | case FLAG_CONNECTED: | ||
| 431 | switch (batch_send(peersocket, &peer->outdata)) { | ||
| 432 | case BATCH_DONE: | ||
| 433 | io_dontwantwrite(peersocket); | ||
| 434 | break; | ||
| 435 | case BATCH_FAILED: | ||
| 436 | io_close(peersocket); | ||
| 437 | reset_info_block(peer); | ||
| 438 | break; | ||
| 439 | default: /* Normal operation or eagain */ | ||
| 440 | break; | ||
| 441 | } | ||
| 442 | break; | ||
| 443 | } | ||
| 444 | |||
| 445 | return; | ||
| 446 | } | ||
| 447 | |||
| 448 | static void server_mainloop() { | ||
| 449 | int64_t sock; | ||
| 450 | uint32_t packet_type; | ||
| 451 | |||
| 452 | /* inlined livesync_init() */ | ||
| 453 | memset(g_peerbuffer_start, 0, sizeof(g_peerbuffer_start)); | ||
| 454 | g_peerbuffer_pos = g_peerbuffer_start; | ||
| 455 | memcpy(g_peerbuffer_pos, &g_tracker_id, sizeof(g_tracker_id)); | ||
| 456 | packet_type = htonl(OT_SYNC_PEER); | ||
| 457 | memcpy(g_peerbuffer_pos + sizeof(g_tracker_id), &packet_type, sizeof(packet_type)); | ||
| 458 | g_peerbuffer_pos += sizeof(g_tracker_id) + sizeof(uint32_t); | ||
| 459 | g_next_packet_time = time(NULL) + LIVESYNC_MAXDELAY; | ||
| 460 | |||
| 461 | while (1) { | ||
| 462 | /* See if we need to connect to anyone */ | ||
| 463 | if (time(NULL) > g_connection_reconn) | ||
| 464 | handle_reconnects(); | ||
| 465 | |||
| 466 | /* Wait for io events until next approx reconn check time */ | ||
| 467 | io_waituntil2(30 * 1000); | ||
| 468 | |||
| 469 | /* Loop over readable sockets */ | ||
| 470 | while ((sock = io_canread()) != -1) { | ||
| 471 | const void *cookie = fdstate_cookie(sock); | ||
| 472 | if ((uintptr_t)cookie == FLAG_SERVERSOCKET) | ||
| 473 | handle_accept(sock); | ||
| 474 | else | ||
| 475 | handle_read(sock); | ||
| 476 | } | ||
| 477 | |||
| 478 | /* Loop over writable sockets */ | ||
| 479 | while ((sock = io_canwrite()) != -1) | ||
| 480 | handle_write(sock); | ||
| 481 | |||
| 482 | livesync_ticker(); | ||
| 483 | } | ||
| 484 | } | ||
| 485 | |||
| 486 | static void panic(const char *routine) { | ||
| 487 | fprintf(stderr, "%s: %s\n", routine, strerror(errno)); | ||
| 488 | exit(111); | ||
| 489 | } | ||
| 490 | |||
| 491 | static int64_t ot_try_bind(ot_ip6 ip, uint16_t port) { | ||
| 492 | int64_t sock = socket_tcp_listen(ip, port, SOCKET_EXCLUSIVE); | ||
| 493 | |||
| 494 | if (sock == -1) | ||
| 495 | panic("socket_tcp_listen"); | ||
| 496 | |||
| 497 | if (!io_fd(sock)) | ||
| 498 | panic("io_fd"); | ||
| 499 | |||
| 500 | fdstate_open(sock, (void *)FLAG_SERVERSOCKET, 0); | ||
| 501 | io_wantread(sock); | ||
| 502 | return sock; | ||
| 503 | } | ||
| 504 | |||
| 505 | static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) { | ||
| 506 | const char *s = src; | ||
| 507 | uint64_t value; | ||
| 508 | int off, bracket = 0; | ||
| 509 | while (isspace(*s)) | ||
| 510 | ++s; | ||
| 511 | if (*s == '[') | ||
| 512 | ++s, ++bracket; /* for v6 style notation */ | ||
| 513 | if (!(off = ip_parse(s, ip))) | ||
| 514 | return 0; | ||
| 515 | s += off; | ||
| 516 | if (*s == 0 || isspace(*s)) | ||
| 517 | return s - src; | ||
| 518 | if (*s == ']' && bracket) | ||
| 519 | ++s; | ||
| 520 | if (!ip_is_v4mapped(ip)) { | ||
| 521 | if ((bracket && *(s) != ':') || (*(s) != '.')) | ||
| 522 | return 0; | ||
| 523 | s++; | ||
| 524 | } else { | ||
| 525 | if (*(s++) != ':') | ||
| 526 | return 0; | ||
| 527 | } | ||
| 528 | if (!(off = scan_uint(s, &value, 65535))) | ||
| 529 | return 0; | ||
| 530 | *port = (uint16_t)value; | ||
| 531 | return off + s - src; | ||
| 532 | } | ||
| 533 | |||
| 534 | int main(int argc, char **argv) { | ||
| 535 | static pthread_t sync_in_thread_id; | ||
| 536 | static pthread_t sync_out_thread_id; | ||
| 537 | ot_ip6 serverip; | ||
| 538 | uint16_t tmpport; | ||
| 539 | int scanon = 1, lbound = 0, sbound = 0; | ||
| 540 | |||
| 541 | random_init(); | ||
| 542 | if (fdstate_init() == -1) | ||
| 543 | panic("fdstate_init"); | ||
| 544 | g_tracker_id = random_uint32(); | ||
| 545 | |||
| 546 | while (scanon) { | ||
| 547 | switch (getopt(argc, argv, ":l:c:L:h")) { | ||
| 548 | case -1: | ||
| 549 | scanon = 0; | ||
| 550 | break; | ||
| 551 | case 'l': | ||
| 552 | tmpport = 0; | ||
| 553 | if (!scan_ip6_port(optarg, serverip, &tmpport) || !tmpport) { | ||
| 554 | usage(argv[0]); | ||
| 555 | exit(1); | ||
| 556 | } | ||
| 557 | ot_try_bind(serverip, tmpport); | ||
| 558 | ++sbound; | ||
| 559 | break; | ||
| 560 | case 'c': | ||
| 561 | if (g_connection_count > MAX_PEERS / 2) | ||
| 562 | exerr("Connection limit exceeded.\n"); | ||
| 563 | tmpport = 0; | ||
| 564 | if (!scan_ip6_port(optarg, g_connections[g_connection_count].ip, &g_connections[g_connection_count].port) || !g_connections[g_connection_count].port) { | ||
| 565 | usage(argv[0]); | ||
| 566 | exit(1); | ||
| 567 | } | ||
| 568 | g_connections[g_connection_count++].state = FLAG_OUTGOING; | ||
| 569 | break; | ||
| 570 | case 'L': | ||
| 571 | tmpport = 9696; | ||
| 572 | if (!scan_ip6_port(optarg, serverip, &tmpport) || !tmpport) { | ||
| 573 | usage(argv[0]); | ||
| 574 | exit(1); | ||
| 575 | } | ||
| 576 | livesync_bind_mcast(serverip, tmpport); | ||
| 577 | ++lbound; | ||
| 578 | break; | ||
| 579 | default: | ||
| 580 | case '?': | ||
| 581 | usage(argv[0]); | ||
| 582 | exit(1); | ||
| 583 | } | ||
| 584 | } | ||
| 585 | |||
| 586 | if (!lbound) | ||
| 587 | exerr("No livesync port bound."); | ||
| 588 | if (!g_connection_count && !sbound) | ||
| 589 | exerr("No streamsync port bound."); | ||
| 590 | pthread_create(&sync_in_thread_id, NULL, livesync_worker, NULL); | ||
| 591 | pthread_create(&sync_out_thread_id, NULL, streamsync_worker, NULL); | ||
| 592 | |||
| 593 | server_mainloop(); | ||
| 594 | return 0; | ||
| 595 | } | ||
| 596 | |||
| 597 | static void *streamsync_worker(void *args) { | ||
| 598 | (void)args; | ||
| 599 | while (1) { | ||
| 600 | size_t bucket; | ||
| 601 | /* For each bucket... */ | ||
| 602 | for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { | ||
| 603 | /* Get exclusive access to that bucket */ | ||
| 604 | ot_vector *torrents_list = mutex_bucket_lock(bucket); | ||
| 605 | size_t tor_offset, count_def = 0, count_one = 0, count_two = 0, count_peers = 0; | ||
| 606 | size_t mem, mem_a = 0, mem_b = 0; | ||
| 607 | uint8_t *ptr = 0, *ptr_a, *ptr_b, *ptr_c; | ||
| 608 | |||
| 609 | if (!torrents_list->size) | ||
| 610 | goto unlock_continue; | ||
| 611 | |||
| 612 | /* For each torrent in this bucket.. */ | ||
| 613 | for (tor_offset = 0; tor_offset < torrents_list->size; ++tor_offset) { | ||
| 614 | /* Address torrents members */ | ||
| 615 | ot_peerlist *peer_list = (((ot_torrent *)(torrents_list->data))[tor_offset]).peer_list; | ||
| 616 | switch (peer_list->peer_count) { | ||
| 617 | case 2: | ||
| 618 | count_two++; | ||
| 619 | break; | ||
| 620 | case 1: | ||
| 621 | count_one++; | ||
| 622 | break; | ||
| 623 | case 0: | ||
| 624 | break; | ||
| 625 | default: | ||
| 626 | count_def++; | ||
| 627 | count_peers += peer_list->peer_count; | ||
| 628 | } | ||
| 629 | } | ||
| 630 | |||
| 631 | /* Maximal memory requirement: max 3 blocks, max torrents * 20 + max peers * 7 */ | ||
| 632 | mem = 3 * (1 + 1 + 2) + (count_one + count_two) * (19 + 1) + count_def * (19 + 8) + (count_one + 2 * count_two + count_peers) * 7; | ||
| 633 | |||
| 634 | fprintf(stderr, "Mem: %zd\n", mem); | ||
| 635 | |||
| 636 | ptr = ptr_a = ptr_b = ptr_c = malloc(mem); | ||
| 637 | if (!ptr) | ||
| 638 | goto unlock_continue; | ||
| 639 | |||
| 640 | if (count_one > 4 || !count_def) { | ||
| 641 | mem_a = 1 + 1 + 2 + count_one * (19 + 7); | ||
| 642 | ptr_b += mem_a; | ||
| 643 | ptr_c += mem_a; | ||
| 644 | ptr_a[0] = 1; /* Offset 0: packet type 1 */ | ||
| 645 | ptr_a[1] = (bucket << 8) >> OT_BUCKET_COUNT_BITS; /* Offset 1: the shared prefix */ | ||
| 646 | ptr_a[2] = count_one >> 8; | ||
| 647 | ptr_a[3] = count_one & 255; | ||
| 648 | ptr_a += 4; | ||
| 649 | } else | ||
| 650 | count_def += count_one; | ||
| 651 | |||
| 652 | if (count_two > 4 || !count_def) { | ||
| 653 | mem_b = 1 + 1 + 2 + count_two * (19 + 14); | ||
| 654 | ptr_c += mem_b; | ||
| 655 | ptr_b[0] = 2; /* Offset 0: packet type 2 */ | ||
| 656 | ptr_b[1] = (bucket << 8) >> OT_BUCKET_COUNT_BITS; /* Offset 1: the shared prefix */ | ||
| 657 | ptr_b[2] = count_two >> 8; | ||
| 658 | ptr_b[3] = count_two & 255; | ||
| 659 | ptr_b += 4; | ||
| 660 | } else | ||
| 661 | count_def += count_two; | ||
| 662 | |||
| 663 | if (count_def) { | ||
| 664 | ptr_c[0] = 0; /* Offset 0: packet type 0 */ | ||
| 665 | ptr_c[1] = (bucket << 8) >> OT_BUCKET_COUNT_BITS; /* Offset 1: the shared prefix */ | ||
| 666 | ptr_c[2] = count_def >> 8; | ||
| 667 | ptr_c[3] = count_def & 255; | ||
| 668 | ptr_c += 4; | ||
| 669 | } | ||
| 670 | |||
| 671 | /* For each torrent in this bucket.. */ | ||
| 672 | for (tor_offset = 0; tor_offset < torrents_list->size; ++tor_offset) { | ||
| 673 | /* Address torrents members */ | ||
| 674 | ot_torrent *torrent = ((ot_torrent *)(torrents_list->data)) + tor_offset; | ||
| 675 | ot_peerlist *peer_list = torrent->peer_list; | ||
| 676 | ot_peer *peers = (ot_peer *)(peer_list->peers.data); | ||
| 677 | uint8_t **dst; | ||
| 678 | |||
| 679 | /* Determine destination slot */ | ||
| 680 | count_peers = peer_list->peer_count; | ||
| 681 | switch (count_peers) { | ||
| 682 | case 0: | ||
| 683 | continue; | ||
| 684 | case 1: | ||
| 685 | dst = mem_a ? &ptr_a : &ptr_c; | ||
| 686 | break; | ||
| 687 | case 2: | ||
| 688 | dst = mem_b ? &ptr_b : &ptr_c; | ||
| 689 | break; | ||
| 690 | default: | ||
| 691 | dst = &ptr_c; | ||
| 692 | break; | ||
| 693 | } | ||
| 694 | |||
| 695 | /* Copy tail of info_hash, advance pointer */ | ||
| 696 | memcpy(*dst, ((uint8_t *)torrent->hash) + 1, sizeof(ot_hash) - 1); | ||
| 697 | *dst += sizeof(ot_hash) - 1; | ||
| 698 | |||
| 699 | /* Encode peer count */ | ||
| 700 | if (dst == &ptr_c) | ||
| 701 | while (count_peers) { | ||
| 702 | if (count_peers <= 0x7f) | ||
| 703 | *(*dst)++ = count_peers; | ||
| 704 | else | ||
| 705 | *(*dst)++ = 0x80 | (count_peers & 0x7f); | ||
| 706 | count_peers >>= 7; | ||
| 707 | } | ||
| 708 | |||
| 709 | /* Copy peers */ | ||
| 710 | count_peers = peer_list->peer_count; | ||
| 711 | while (count_peers--) { | ||
| 712 | memcpy(*dst, peers++, OT_IP_SIZE + 3); | ||
| 713 | *dst += OT_IP_SIZE + 3; | ||
| 714 | } | ||
| 715 | free_peerlist(peer_list); | ||
| 716 | } | ||
| 717 | |||
| 718 | free(torrents_list->data); | ||
| 719 | memset(torrents_list, 0, sizeof(*torrents_list)); | ||
| 720 | unlock_continue: | ||
| 721 | mutex_bucket_unlock(bucket, 0); | ||
| 722 | |||
| 723 | if (ptr) { | ||
| 724 | int i; | ||
| 725 | |||
| 726 | if (ptr_b > ptr_c) | ||
| 727 | ptr_c = ptr_b; | ||
| 728 | if (ptr_a > ptr_c) | ||
| 729 | ptr_c = ptr_a; | ||
| 730 | mem = ptr_c - ptr; | ||
| 731 | |||
| 732 | for (i = 0; i < MAX_PEERS; ++i) { | ||
| 733 | if (PROXYPEER_ISCONNECTED(g_connections[i].state)) { | ||
| 734 | void *tmp = malloc(mem); | ||
| 735 | if (tmp) { | ||
| 736 | memcpy(tmp, ptr, mem); | ||
| 737 | batch_add(&g_connections[i].outdata, tmp, mem); | ||
| 738 | io_wantwrite(g_connections[i].fd); | ||
| 739 | } | ||
| 740 | } | ||
| 741 | } | ||
| 742 | |||
| 743 | free(ptr); | ||
| 744 | } | ||
| 745 | usleep(OT_SYNC_SLEEP); | ||
| 746 | } | ||
| 747 | } | ||
| 748 | return 0; | ||
| 749 | } | ||
| 750 | |||
| 751 | static void livesync_issue_peersync() { | ||
| 752 | socket_mcast_send(g_socket_out, g_peerbuffer_start, (size_t)(g_peerbuffer_pos - g_peerbuffer_start), groupip_1, LIVESYNC_PORT); | ||
| 753 | g_peerbuffer_pos = g_peerbuffer_start + sizeof(g_tracker_id) + sizeof(uint32_t); | ||
| 754 | g_next_packet_time = time(NULL) + LIVESYNC_MAXDELAY; | ||
| 755 | } | ||
| 756 | |||
| 757 | void livesync_ticker() { | ||
| 758 | /* livesync_issue_peersync sets g_next_packet_time */ | ||
| 759 | if (time(NULL) > g_next_packet_time && g_peerbuffer_pos > g_peerbuffer_start + sizeof(g_tracker_id)) | ||
| 760 | livesync_issue_peersync(); | ||
| 761 | } | ||
| 762 | |||
| 763 | static void livesync_proxytell(uint8_t prefix, uint8_t *info_hash, uint8_t *peer) { | ||
| 764 | // unsigned int i; | ||
| 765 | |||
| 766 | *g_peerbuffer_pos = prefix; | ||
| 767 | memcpy(g_peerbuffer_pos + 1, info_hash, sizeof(ot_hash) - 1); | ||
| 768 | memcpy(g_peerbuffer_pos + sizeof(ot_hash), peer, sizeof(ot_peer) - 1); | ||
| 769 | |||
| 770 | #if 0 | ||
| 771 | /* Dump info_hash */ | ||
| 772 | for( i=0; i<sizeof(ot_hash); ++i ) | ||
| 773 | printf( "%02X", g_peerbuffer_pos[i] ); | ||
| 774 | putchar( ':' ); | ||
| 775 | #endif | ||
| 776 | g_peerbuffer_pos += sizeof(ot_hash); | ||
| 777 | #if 0 | ||
| 778 | printf( "%hhu.%hhu.%hhu.%hhu:%hu (%02X %02X)\n", g_peerbuffer_pos[0], g_peerbuffer_pos[1], g_peerbuffer_pos[2], g_peerbuffer_pos[3], | ||
| 779 | g_peerbuffer_pos[4] | ( g_peerbuffer_pos[5] << 8 ), g_peerbuffer_pos[6], g_peerbuffer_pos[7] ); | ||
| 780 | #endif | ||
| 781 | g_peerbuffer_pos += sizeof(ot_peer); | ||
| 782 | |||
| 783 | if (g_peerbuffer_pos >= g_peerbuffer_highwater) | ||
| 784 | livesync_issue_peersync(); | ||
| 785 | } | ||
| 786 | |||
| 787 | static void process_indata(proxy_peer *peer) { | ||
| 788 | size_t consumed, peers; | ||
| 789 | uint8_t *data = peer->indata, *hash; | ||
| 790 | uint8_t *dataend = data + peer->indata_length; | ||
| 791 | |||
| 792 | while (1) { | ||
| 793 | /* If we're not inside of a packet, make a new one */ | ||
| 794 | if (!peer->packet_tcount) { | ||
| 795 | /* Ensure the header is complete or postpone processing */ | ||
| 796 | if (data + 4 > dataend) | ||
| 797 | break; | ||
| 798 | peer->packet_type = data[0]; | ||
| 799 | peer->packet_tprefix = data[1]; | ||
| 800 | peer->packet_tcount = data[2] * 256 + data[3]; | ||
| 801 | data += 4; | ||
| 802 | printf("type: %hhu, prefix: %02X, torrentcount: %zd\n", peer->packet_type, peer->packet_tprefix, peer->packet_tcount); | ||
| 803 | } | ||
| 804 | |||
| 805 | /* Ensure size for a minimal torrent block */ | ||
| 806 | if (data + sizeof(ot_hash) + OT_IP_SIZE + 3 > dataend) | ||
| 807 | break; | ||
| 808 | |||
| 809 | /* Advance pointer to peer count or peers */ | ||
| 810 | hash = data; | ||
| 811 | data += sizeof(ot_hash) - 1; | ||
| 812 | |||
| 813 | /* Type 0 has peer count encoded before each peers */ | ||
| 814 | peers = peer->packet_type; | ||
| 815 | if (!peers) { | ||
| 816 | int shift = 0; | ||
| 817 | do | ||
| 818 | peers |= (0x7f & *data) << (7 * shift); | ||
| 819 | while (*(data++) & 0x80 && shift++ < 6); | ||
| 820 | } | ||
| 821 | #if 0 | ||
| 822 | printf( "peers: %zd\n", peers ); | ||
| 823 | #endif | ||
| 824 | /* Ensure enough data being read to hold all peers */ | ||
| 825 | if (data + (OT_IP_SIZE + 3) * peers > dataend) { | ||
| 826 | data = hash; | ||
| 827 | break; | ||
| 828 | } | ||
| 829 | while (peers--) { | ||
| 830 | livesync_proxytell(peer->packet_tprefix, hash, data); | ||
| 831 | data += OT_IP_SIZE + 3; | ||
| 832 | } | ||
| 833 | --peer->packet_tcount; | ||
| 834 | } | ||
| 835 | |||
| 836 | consumed = data - peer->indata; | ||
| 837 | memmove(peer->indata, data, peer->indata_length - consumed); | ||
| 838 | peer->indata_length -= consumed; | ||
| 839 | } | ||
| 840 | |||
| 841 | static void *livesync_worker(void *args) { | ||
| 842 | (void)args; | ||
| 843 | while (1) { | ||
| 844 | ot_ip6 in_ip; | ||
| 845 | uint16_t in_port; | ||
| 846 | uint32_t packet_type; | ||
| 847 | ssize_t datalen = socket_recv(g_socket_in, g_inbuffer, LIVESYNC_INCOMING_BUFFSIZE, in_ip, &in_port); | ||
| 848 | |||
| 849 | /* Expect at least tracker id and packet type */ | ||
| 850 | if (datalen <= (ssize_t)(sizeof(g_tracker_id) + sizeof(uint32_t))) | ||
| 851 | continue; | ||
| 852 | if (!memcmp(g_inbuffer, &g_tracker_id, sizeof(g_tracker_id))) { | ||
| 853 | /* drop packet coming from ourselves */ | ||
| 854 | continue; | ||
| 855 | } | ||
| 856 | memcpy(&packet_type, g_inbuffer + sizeof(g_tracker_id), sizeof(packet_type)); | ||
| 857 | |||
| 858 | switch (ntohl(packet_type)) { | ||
| 859 | case OT_SYNC_PEER4: | ||
| 860 | livesync_handle_peersync(datalen, OT_PEER_SIZE4); | ||
| 861 | break; | ||
| 862 | case OT_SYNC_PEER6: | ||
| 863 | livesync_handle_peersync(datalen, OT_PEER_SIZE6); | ||
| 864 | break; | ||
| 865 | default: | ||
| 866 | // fprintf( stderr, "Received an unknown live sync packet type %u.\n", ntohl( packet_type ) ); | ||
| 867 | break; | ||
| 868 | } | ||
| 869 | } | ||
| 870 | return 0; | ||
| 871 | } | ||
diff --git a/src/trackerlogic.c b/src/trackerlogic.c new file mode 100644 index 0000000..8e30a48 --- /dev/null +++ b/src/trackerlogic.c | |||
| @@ -0,0 +1,630 @@ | |||
| 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 <arpa/inet.h> | ||
| 6 | #include <stdint.h> | ||
| 7 | #include <stdio.h> | ||
| 8 | #include <stdlib.h> | ||
| 9 | #include <string.h> | ||
| 10 | |||
| 11 | |||
| 12 | /* Opentracker */ | ||
| 13 | #include "ot_accesslist.h" | ||
| 14 | #include "ot_clean.h" | ||
| 15 | #include "ot_fmt.h" | ||
| 16 | #include "ot_fullscrape.h" | ||
| 17 | #include "ot_http.h" | ||
| 18 | #include "ot_ip.h" | ||
| 19 | #include "ot_livesync.h" | ||
| 20 | #include "ot_mutex.h" | ||
| 21 | #include "ot_workqueue.h" | ||
| 22 | #include "ot_random.h" | ||
| 23 | #include "ot_stats.h" | ||
| 24 | #include "ot_udp.h" | ||
| 25 | #include "ot_vector.h" | ||
| 26 | #include "trackerlogic.h" | ||
| 27 | |||
| 28 | /* Forward declaration */ | ||
| 29 | size_t return_peers_for_torrent(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply, PROTO_FLAG proto); | ||
| 30 | |||
| 31 | void free_peerlist(ot_peerlist *peer_list) { | ||
| 32 | if (peer_list && peer_list->peers.data) { | ||
| 33 | if (OT_PEERLIST_HASBUCKETS(peer_list)) | ||
| 34 | vector_clean_list((ot_vector *)peer_list->peers.data, peer_list->peers.size); | ||
| 35 | else | ||
| 36 | free(peer_list->peers.data); | ||
| 37 | } | ||
| 38 | free(peer_list); | ||
| 39 | } | ||
| 40 | |||
| 41 | void add_torrent_from_saved_state(ot_hash const hash, ot_time base, size_t down_count) { | ||
| 42 | int exactmatch; | ||
| 43 | ot_torrent *torrent; | ||
| 44 | ot_vector *torrents_list = mutex_bucket_lock_by_hash(hash); | ||
| 45 | |||
| 46 | if (!accesslist_hashisvalid(hash)) { | ||
| 47 | mutex_bucket_unlock_by_hash(hash, 0); | ||
| 48 | return; | ||
| 49 | } | ||
| 50 | |||
| 51 | torrent = vector_find_or_insert(torrents_list, (void *)hash, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch); | ||
| 52 | if (!torrent || exactmatch) { | ||
| 53 | mutex_bucket_unlock_by_hash(hash, 0); | ||
| 54 | return; | ||
| 55 | } | ||
| 56 | |||
| 57 | /* Create a new torrent entry, then */ | ||
| 58 | memset(torrent, 0, sizeof(ot_torrent)); | ||
| 59 | memcpy(torrent->hash, hash, sizeof(ot_hash)); | ||
| 60 | |||
| 61 | if (!(torrent->peer_list6 = malloc(sizeof(ot_peerlist))) || !(torrent->peer_list4 = malloc(sizeof(ot_peerlist)))) { | ||
| 62 | vector_remove_torrent(torrents_list, torrent); | ||
| 63 | mutex_bucket_unlock_by_hash(hash, 0); | ||
| 64 | return; | ||
| 65 | } | ||
| 66 | |||
| 67 | memset(torrent->peer_list6, 0, sizeof(ot_peerlist)); | ||
| 68 | memset(torrent->peer_list4, 0, sizeof(ot_peerlist)); | ||
| 69 | torrent->peer_list6->base = base; | ||
| 70 | torrent->peer_list4->base = base; | ||
| 71 | torrent->peer_list6->down_count = down_count; | ||
| 72 | torrent->peer_list4->down_count = down_count; | ||
| 73 | |||
| 74 | mutex_bucket_unlock_by_hash(hash, 1); | ||
| 75 | } | ||
| 76 | |||
| 77 | size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstruct *ws, size_t amount) { | ||
| 78 | int exactmatch, delta_torrentcount = 0; | ||
| 79 | ot_torrent *torrent; | ||
| 80 | ot_peer *peer_dest; | ||
| 81 | ot_vector *torrents_list = mutex_bucket_lock_by_hash(*ws->hash); | ||
| 82 | ot_peerlist *peer_list; | ||
| 83 | size_t peer_size; /* initialized in next line */ | ||
| 84 | ot_peer const *peer_src = peer_from_peer6(&ws->peer, &peer_size); | ||
| 85 | |||
| 86 | if (!accesslist_hashisvalid(*ws->hash)) { | ||
| 87 | mutex_bucket_unlock_by_hash(*ws->hash, 0); | ||
| 88 | if (proto == FLAG_TCP) { | ||
| 89 | const char invalid_hash[] = "d14:failure reason63:Requested download is not authorized for use with this tracker.e"; | ||
| 90 | memcpy(ws->reply, invalid_hash, strlen(invalid_hash)); | ||
| 91 | return strlen(invalid_hash); | ||
| 92 | } | ||
| 93 | return 0; | ||
| 94 | } | ||
| 95 | |||
| 96 | torrent = vector_find_or_insert(torrents_list, (void *)ws->hash, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch); | ||
| 97 | if (!torrent) { | ||
| 98 | mutex_bucket_unlock_by_hash(*ws->hash, 0); | ||
| 99 | return 0; | ||
| 100 | } | ||
| 101 | |||
| 102 | if (!exactmatch) { | ||
| 103 | /* Create a new torrent entry, then */ | ||
| 104 | memset(torrent, 0, sizeof(ot_torrent)); | ||
| 105 | memcpy(torrent->hash, *ws->hash, sizeof(ot_hash)); | ||
| 106 | |||
| 107 | if (!(torrent->peer_list6 = malloc(sizeof(ot_peerlist))) || !(torrent->peer_list4 = malloc(sizeof(ot_peerlist)))) { | ||
| 108 | vector_remove_torrent(torrents_list, torrent); | ||
| 109 | mutex_bucket_unlock_by_hash(*ws->hash, 0); | ||
| 110 | return 0; | ||
| 111 | } | ||
| 112 | |||
| 113 | memset(torrent->peer_list6, 0, sizeof(ot_peerlist)); | ||
| 114 | memset(torrent->peer_list4, 0, sizeof(ot_peerlist)); | ||
| 115 | delta_torrentcount = 1; | ||
| 116 | } else | ||
| 117 | clean_single_torrent(torrent); | ||
| 118 | |||
| 119 | torrent->peer_list6->base = time_now_minutes(); | ||
| 120 | torrent->peer_list4->base = time_now_minutes(); | ||
| 121 | |||
| 122 | peer_list = peer_size == OT_PEER_SIZE6 ? torrent->peer_list6 : torrent->peer_list4; | ||
| 123 | |||
| 124 | /* Check for peer in torrent */ | ||
| 125 | peer_dest = vector_find_or_insert_peer(&(peer_list->peers), peer_src, peer_size, &exactmatch); | ||
| 126 | if (!peer_dest) { | ||
| 127 | mutex_bucket_unlock_by_hash(*ws->hash, delta_torrentcount); | ||
| 128 | return 0; | ||
| 129 | } | ||
| 130 | |||
| 131 | /* Tell peer that it's fresh */ | ||
| 132 | OT_PEERTIME(ws->peer, OT_PEER_SIZE6) = 0; | ||
| 133 | |||
| 134 | /* Sanitize flags: Whoever claims to have completed download, must be a seeder */ | ||
| 135 | if ((OT_PEERFLAG(ws->peer) & (PEER_FLAG_COMPLETED | PEER_FLAG_SEEDING)) == PEER_FLAG_COMPLETED) | ||
| 136 | OT_PEERFLAG(ws->peer) ^= PEER_FLAG_COMPLETED; | ||
| 137 | |||
| 138 | /* If we hadn't had a match create peer there */ | ||
| 139 | if (!exactmatch) { | ||
| 140 | |||
| 141 | #ifdef WANT_SYNC_LIVE | ||
| 142 | if (proto == FLAG_MCA) | ||
| 143 | OT_PEERFLAG(ws->peer) |= PEER_FLAG_FROM_SYNC; | ||
| 144 | else | ||
| 145 | livesync_tell(ws); | ||
| 146 | #endif | ||
| 147 | |||
| 148 | peer_list->peer_count++; | ||
| 149 | if (OT_PEERFLAG(ws->peer) & PEER_FLAG_COMPLETED) { | ||
| 150 | peer_list->down_count++; | ||
| 151 | stats_issue_event(ws, EVENT_COMPLETED, 0, 0); | ||
| 152 | } | ||
| 153 | if (OT_PEERFLAG(ws->peer) & PEER_FLAG_SEEDING) | ||
| 154 | peer_list->seed_count++; | ||
| 155 | |||
| 156 | } else { | ||
| 157 | stats_issue_event(ws, EVENT_RENEW, 0, OT_PEERTIME(peer_dest, peer_size)); | ||
| 158 | #ifdef WANT_SPOT_WOODPECKER | ||
| 159 | if ((OT_PEERTIME(peer_dest, peer_size) > 0) && (OT_PEERTIME(peer_dest, peer_size) < 20)) | ||
| 160 | stats_issue_event(ws, EVENT_WOODPECKER, 0, (uintptr_t)&ws->peer); | ||
| 161 | #endif | ||
| 162 | #ifdef WANT_SYNC_LIVE | ||
| 163 | /* Won't live sync peers that come back too fast. Only exception: | ||
| 164 | fresh "completed" reports */ | ||
| 165 | if (proto != FLAG_MCA) { | ||
| 166 | if (OT_PEERTIME(peer_dest, peer_size) > OT_CLIENT_SYNC_RENEW_BOUNDARY || | ||
| 167 | (!(OT_PEERFLAG_D(peer_dest, peer_size) & PEER_FLAG_COMPLETED) && (OT_PEERFLAG(ws->peer) & PEER_FLAG_COMPLETED))) | ||
| 168 | livesync_tell(ws); | ||
| 169 | } | ||
| 170 | #endif | ||
| 171 | |||
| 172 | if ((OT_PEERFLAG_D(peer_dest, peer_size) & PEER_FLAG_SEEDING) && !(OT_PEERFLAG(ws->peer) & PEER_FLAG_SEEDING)) | ||
| 173 | peer_list->seed_count--; | ||
| 174 | if (!(OT_PEERFLAG_D(peer_dest, peer_size) & PEER_FLAG_SEEDING) && (OT_PEERFLAG(ws->peer) & PEER_FLAG_SEEDING)) | ||
| 175 | peer_list->seed_count++; | ||
| 176 | if (!(OT_PEERFLAG_D(peer_dest, peer_size) & PEER_FLAG_COMPLETED) && (OT_PEERFLAG(ws->peer) & PEER_FLAG_COMPLETED)) { | ||
| 177 | peer_list->down_count++; | ||
| 178 | stats_issue_event(ws, EVENT_COMPLETED, 0, 0); | ||
| 179 | } | ||
| 180 | if (OT_PEERFLAG_D(peer_dest, peer_size) & PEER_FLAG_COMPLETED) | ||
| 181 | OT_PEERFLAG(ws->peer) |= PEER_FLAG_COMPLETED; | ||
| 182 | } | ||
| 183 | |||
| 184 | memcpy(peer_dest, peer_src, peer_size); | ||
| 185 | #ifdef WANT_SYNC | ||
| 186 | if (proto == FLAG_MCA) { | ||
| 187 | mutex_bucket_unlock_by_hash(*ws->hash, delta_torrentcount); | ||
| 188 | return 0; | ||
| 189 | } | ||
| 190 | #endif | ||
| 191 | |||
| 192 | ws->reply_size = return_peers_for_torrent(ws, torrent, amount, ws->reply, proto); | ||
| 193 | mutex_bucket_unlock_by_hash(*ws->hash, delta_torrentcount); | ||
| 194 | return ws->reply_size; | ||
| 195 | } | ||
| 196 | |||
| 197 | static size_t return_peers_all(ot_peerlist *peer_list, size_t peer_size, char *reply) { | ||
| 198 | size_t bucket, num_buckets = 1; | ||
| 199 | ot_vector *bucket_list = &peer_list->peers; | ||
| 200 | size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size); | ||
| 201 | size_t result = compare_size * peer_list->peer_count; | ||
| 202 | char *r_end = reply + result; | ||
| 203 | |||
| 204 | if (OT_PEERLIST_HASBUCKETS(peer_list)) { | ||
| 205 | num_buckets = bucket_list->size; | ||
| 206 | bucket_list = (ot_vector *)bucket_list->data; | ||
| 207 | } | ||
| 208 | |||
| 209 | for (bucket = 0; bucket < num_buckets; ++bucket) { | ||
| 210 | ot_peer *peers = bucket_list[bucket].data; | ||
| 211 | size_t peer_count = bucket_list[bucket].size; | ||
| 212 | while (peer_count--) { | ||
| 213 | if (OT_PEERFLAG_D(peers, peer_size) & PEER_FLAG_SEEDING) { | ||
| 214 | r_end -= compare_size; | ||
| 215 | memcpy(r_end, peers, compare_size); | ||
| 216 | } else { | ||
| 217 | memcpy(reply, peers, compare_size); | ||
| 218 | reply += compare_size; | ||
| 219 | } | ||
| 220 | peers += peer_size; | ||
| 221 | } | ||
| 222 | } | ||
| 223 | return result; | ||
| 224 | } | ||
| 225 | |||
| 226 | static size_t return_peers_selection(struct ot_workstruct *ws, ot_peerlist *peer_list, size_t peer_size, size_t amount, char *reply) { | ||
| 227 | size_t bucket_offset, bucket_index = 0, num_buckets = 1; | ||
| 228 | ot_vector *bucket_list = &peer_list->peers; | ||
| 229 | unsigned int shifted_pc = peer_list->peer_count; | ||
| 230 | unsigned int shifted_step = 0; | ||
| 231 | unsigned int shift = 0; | ||
| 232 | size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size); | ||
| 233 | size_t result = compare_size * amount; | ||
| 234 | char *r_end = reply + result; | ||
| 235 | |||
| 236 | if (OT_PEERLIST_HASBUCKETS(peer_list)) { | ||
| 237 | num_buckets = bucket_list->size; | ||
| 238 | bucket_list = (ot_vector *)bucket_list->data; | ||
| 239 | } | ||
| 240 | |||
| 241 | /* Make fixpoint arithmetic as exact as possible */ | ||
| 242 | #define MAXPRECBIT (1 << (8 * sizeof(int) - 3)) | ||
| 243 | while (!(shifted_pc & MAXPRECBIT)) { | ||
| 244 | shifted_pc <<= 1; | ||
| 245 | shift++; | ||
| 246 | } | ||
| 247 | shifted_step = shifted_pc / amount; | ||
| 248 | #undef MAXPRECBIT | ||
| 249 | |||
| 250 | /* Initialize somewhere in the middle of peers so that | ||
| 251 | fixpoint's aliasing doesn't alway miss the same peers */ | ||
| 252 | bucket_offset = nrand48(ws->rand48_state) % peer_list->peer_count; | ||
| 253 | |||
| 254 | while (amount--) { | ||
| 255 | ot_peer *peer; | ||
| 256 | |||
| 257 | /* This is the aliased, non shifted range, next value may fall into */ | ||
| 258 | unsigned int diff = (((amount + 1) * shifted_step) >> shift) - ((amount * shifted_step) >> shift); | ||
| 259 | bucket_offset += 1 + nrand48(ws->rand48_state) % diff; | ||
| 260 | |||
| 261 | while (bucket_offset >= bucket_list[bucket_index].size) { | ||
| 262 | bucket_offset -= bucket_list[bucket_index].size; | ||
| 263 | bucket_index = (bucket_index + 1) % num_buckets; | ||
| 264 | } | ||
| 265 | peer = (ot_peer*)bucket_list[bucket_index].data + peer_size * bucket_offset; | ||
| 266 | if (OT_PEERFLAG_D(peer, peer_size) & PEER_FLAG_SEEDING) { | ||
| 267 | r_end -= compare_size; | ||
| 268 | memcpy(r_end, peer, compare_size); | ||
| 269 | } else { | ||
| 270 | memcpy(reply, peer, compare_size); | ||
| 271 | reply += compare_size; | ||
| 272 | } | ||
| 273 | } | ||
| 274 | return result; | ||
| 275 | } | ||
| 276 | |||
| 277 | static size_t return_peers_for_torrent_udp(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply) { | ||
| 278 | char *r = reply; | ||
| 279 | size_t peer_size = peer_size_from_peer6(&ws->peer); | ||
| 280 | ot_peerlist *peer_list = peer_size == OT_PEER_SIZE6 ? torrent->peer_list6 : torrent->peer_list4; | ||
| 281 | size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; | ||
| 282 | size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; | ||
| 283 | |||
| 284 | if (amount > peer_list->peer_count) | ||
| 285 | amount = peer_list->peer_count; | ||
| 286 | |||
| 287 | *(uint32_t *)(r + 0) = htonl(OT_CLIENT_REQUEST_INTERVAL_RANDOM); | ||
| 288 | *(uint32_t *)(r + 4) = htonl(peer_count - seed_count); | ||
| 289 | *(uint32_t *)(r + 8) = htonl(seed_count); | ||
| 290 | r += 12; | ||
| 291 | |||
| 292 | if (amount) { | ||
| 293 | if (amount == peer_list->peer_count) | ||
| 294 | r += return_peers_all(peer_list, peer_size, r); | ||
| 295 | else | ||
| 296 | r += return_peers_selection(ws, peer_list, peer_size, amount, r); | ||
| 297 | } | ||
| 298 | return r - reply; | ||
| 299 | } | ||
| 300 | |||
| 301 | static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply) { | ||
| 302 | char *r = reply; | ||
| 303 | const char *r_end = ws->outbuf + G_OUTBUF_SIZE; | ||
| 304 | int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM; | ||
| 305 | size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; | ||
| 306 | size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count; | ||
| 307 | size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - seed_count; | ||
| 308 | |||
| 309 | /* Simple case: amount of peers in both lists is less than requested, here we return all results */ | ||
| 310 | size_t amount_v4 = torrent->peer_list4->peer_count; | ||
| 311 | size_t amount_v6 = torrent->peer_list6->peer_count; | ||
| 312 | |||
| 313 | /* Complex case: both lists have more than enough entries and we need to split between v4 and v6 clients */ | ||
| 314 | if (amount_v4 + amount_v6 > amount) { | ||
| 315 | size_t amount_left, percent_v6 = 0, percent_v4 = 0, left_v6, left_v4; | ||
| 316 | const size_t SCALE = 1024; | ||
| 317 | |||
| 318 | /* If possible, fill at least a quarter of peer from each family */ | ||
| 319 | if (amount / 4 <= amount_v4) | ||
| 320 | amount_v4 = amount / 4; | ||
| 321 | if (amount / 4 <= amount_v6) | ||
| 322 | amount_v6 = amount / 4; | ||
| 323 | |||
| 324 | /* Fill the rest according to which family's pool provides more peers */ | ||
| 325 | amount_left = amount - (amount_v4 + amount_v6); | ||
| 326 | |||
| 327 | left_v4 = torrent->peer_list4->peer_count - amount_v4; | ||
| 328 | left_v6 = torrent->peer_list6->peer_count - amount_v6; | ||
| 329 | |||
| 330 | if (left_v4 + left_v6) { | ||
| 331 | percent_v4 = (SCALE * left_v4) / (left_v4 + left_v6); | ||
| 332 | percent_v6 = (SCALE * left_v6) / (left_v4 + left_v6); | ||
| 333 | } | ||
| 334 | |||
| 335 | amount_v4 += (amount_left * percent_v4) / SCALE; | ||
| 336 | amount_v6 += (amount_left * percent_v6) / SCALE; | ||
| 337 | |||
| 338 | /* Integer division rounding can leave out a peer */ | ||
| 339 | if (amount_v4 + amount_v6 < amount && amount_v6 < torrent->peer_list6->peer_count) | ||
| 340 | ++amount_v6; | ||
| 341 | if (amount_v4 + amount_v6 < amount && amount_v4 < torrent->peer_list4->peer_count) | ||
| 342 | ++amount_v4; | ||
| 343 | } | ||
| 344 | |||
| 345 | fmt_append(&r, r_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zde8:intervali%ie12:min intervali%ie", seed_count, down_count, peer_count, erval, | ||
| 346 | erval / 2); | ||
| 347 | |||
| 348 | if (amount_v4) { | ||
| 349 | fmt_append(&r, r_end, PEERS_BENCODED4 "%zd:", OT_PEER_COMPARE_SIZE4 * amount_v4); | ||
| 350 | if (amount_v4 == torrent->peer_list4->peer_count) | ||
| 351 | r += return_peers_all(torrent->peer_list4, OT_PEER_SIZE4, r); | ||
| 352 | else | ||
| 353 | r += return_peers_selection(ws, torrent->peer_list4, OT_PEER_SIZE4, amount_v4, r); | ||
| 354 | } | ||
| 355 | |||
| 356 | if (amount_v6) { | ||
| 357 | fmt_append(&r, r_end, PEERS_BENCODED6 "%zd:", OT_PEER_COMPARE_SIZE6 * amount_v6); | ||
| 358 | if (amount_v6 == torrent->peer_list6->peer_count) | ||
| 359 | r += return_peers_all(torrent->peer_list6, OT_PEER_SIZE6, r); | ||
| 360 | else | ||
| 361 | r += return_peers_selection(ws, torrent->peer_list6, OT_PEER_SIZE6, amount_v6, r); | ||
| 362 | } | ||
| 363 | |||
| 364 | fmt_append(&r, r_end, "e"); | ||
| 365 | |||
| 366 | return r - reply; | ||
| 367 | } | ||
| 368 | |||
| 369 | /* Compiles a list of random peers for a torrent | ||
| 370 | * Reply must have enough space to hold: | ||
| 371 | * 92 + 6 * amount bytes for TCP/IPv4 | ||
| 372 | * 92 + 18 * amount bytes for TCP/IPv6 | ||
| 373 | * 12 + 6 * amount bytes for UDP/IPv4 | ||
| 374 | * 12 + 18 * amount bytes for UDP/IPv6 | ||
| 375 | * Does not yet check not to return self | ||
| 376 | */ | ||
| 377 | size_t return_peers_for_torrent(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply, PROTO_FLAG proto) { | ||
| 378 | return proto == FLAG_TCP ? return_peers_for_torrent_tcp(ws, torrent, amount, reply) : return_peers_for_torrent_udp(ws, torrent, amount, reply); | ||
| 379 | } | ||
| 380 | |||
| 381 | /* Fetches scrape info for a specific torrent */ | ||
| 382 | size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply) { | ||
| 383 | int exactmatch, delta_torrentcount = 0; | ||
| 384 | ot_vector *torrents_list = mutex_bucket_lock_by_hash(hash); | ||
| 385 | ot_torrent *torrent = vector_binary_search(hash, torrents_list->data, torrents_list->size, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch); | ||
| 386 | |||
| 387 | if (!exactmatch) { | ||
| 388 | memset(reply, 0, 12); | ||
| 389 | } else { | ||
| 390 | uint32_t *r = (uint32_t *)reply; | ||
| 391 | |||
| 392 | if (clean_single_torrent(torrent)) { | ||
| 393 | vector_remove_torrent(torrents_list, torrent); | ||
| 394 | memset(reply, 0, 12); | ||
| 395 | delta_torrentcount = -1; | ||
| 396 | } else { | ||
| 397 | r[0] = htonl(torrent->peer_list6->seed_count + torrent->peer_list4->seed_count); | ||
| 398 | r[1] = htonl(torrent->peer_list6->down_count + torrent->peer_list4->down_count); | ||
| 399 | r[2] = htonl(torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - torrent->peer_list6->seed_count - torrent->peer_list4->seed_count); | ||
| 400 | } | ||
| 401 | } | ||
| 402 | mutex_bucket_unlock_by_hash(hash, delta_torrentcount); | ||
| 403 | return 12; | ||
| 404 | } | ||
| 405 | |||
| 406 | /* Fetches scrape info for a specific torrent */ | ||
| 407 | void return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char **reply, const char *reply_end) { | ||
| 408 | int exactmatch; | ||
| 409 | size_t i; | ||
| 410 | |||
| 411 | fmt_append(reply, reply_end, "d5:filesd"); | ||
| 412 | |||
| 413 | for (i = 0; i < amount; ++i) { | ||
| 414 | int delta_torrentcount = 0; | ||
| 415 | ot_hash const *hash = hash_list + i; | ||
| 416 | ot_vector *torrents_list = mutex_bucket_lock_by_hash(*hash); | ||
| 417 | ot_torrent *torrent = vector_binary_search(hash, torrents_list->data, torrents_list->size, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch); | ||
| 418 | |||
| 419 | if (exactmatch) { | ||
| 420 | if (clean_single_torrent(torrent)) { | ||
| 421 | vector_remove_torrent(torrents_list, torrent); | ||
| 422 | delta_torrentcount = -1; | ||
| 423 | } else { | ||
| 424 | fmt_append(reply, reply_end, "20:"); | ||
| 425 | fmt_bytes(reply, reply_end, hash, sizeof(ot_hash)); | ||
| 426 | fmt_append(reply, reply_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", | ||
| 427 | torrent->peer_list6->seed_count + torrent->peer_list4->seed_count, torrent->peer_list6->down_count + torrent->peer_list4->down_count, | ||
| 428 | torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - torrent->peer_list6->seed_count - torrent->peer_list4->seed_count); | ||
| 429 | } | ||
| 430 | } | ||
| 431 | mutex_bucket_unlock_by_hash(*hash, delta_torrentcount); | ||
| 432 | } | ||
| 433 | |||
| 434 | fmt_append(reply, reply_end, "ee"); | ||
| 435 | } | ||
| 436 | |||
| 437 | static ot_peerlist dummy_list; | ||
| 438 | size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws) { | ||
| 439 | int exactmatch; | ||
| 440 | ot_vector *torrents_list = mutex_bucket_lock_by_hash(*ws->hash); | ||
| 441 | ot_torrent *torrent = vector_binary_search(ws->hash, torrents_list->data, torrents_list->size, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch); | ||
| 442 | ot_peerlist *peer_list = &dummy_list; | ||
| 443 | size_t peer_size; /* initialized in next line */ | ||
| 444 | ot_peer const *peer_src = peer_from_peer6(&ws->peer, &peer_size); | ||
| 445 | size_t peer_count = 0, seed_count = 0; | ||
| 446 | |||
| 447 | #ifdef WANT_SYNC_LIVE | ||
| 448 | if (proto != FLAG_MCA) { | ||
| 449 | OT_PEERFLAG(ws->peer) |= PEER_FLAG_STOPPED; | ||
| 450 | livesync_tell(ws); | ||
| 451 | } | ||
| 452 | #endif | ||
| 453 | |||
| 454 | if (exactmatch) { | ||
| 455 | peer_list = peer_size == OT_PEER_SIZE6 ? torrent->peer_list6 : torrent->peer_list4; | ||
| 456 | switch (vector_remove_peer(&peer_list->peers, peer_src, peer_size)) { | ||
| 457 | case 2: | ||
| 458 | peer_list->seed_count--; /* Intentional fallthrough */ | ||
| 459 | case 1: | ||
| 460 | peer_list->peer_count--; /* Intentional fallthrough */ | ||
| 461 | default: | ||
| 462 | break; | ||
| 463 | } | ||
| 464 | |||
| 465 | peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; | ||
| 466 | seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; | ||
| 467 | } | ||
| 468 | |||
| 469 | if (proto == FLAG_TCP) { | ||
| 470 | int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM; | ||
| 471 | char *r = ws->reply; | ||
| 472 | |||
| 473 | fmt_append(&r, ws->outbuf + G_OUTBUF_SIZE, "d8:completei%zde10:incompletei%zde8:intervali%ie12:min intervali%ie%s0:e", seed_count, peer_count - seed_count, | ||
| 474 | erval, erval / 2, peer_size == OT_PEER_SIZE6 ? PEERS_BENCODED6 : PEERS_BENCODED4); | ||
| 475 | ws->reply_size = r - ws->reply; | ||
| 476 | } | ||
| 477 | |||
| 478 | /* Handle UDP reply */ | ||
| 479 | if (proto == FLAG_UDP) { | ||
| 480 | ((uint32_t *)ws->reply)[2] = htonl(OT_CLIENT_REQUEST_INTERVAL_RANDOM); | ||
| 481 | ((uint32_t *)ws->reply)[3] = htonl(peer_count - seed_count); | ||
| 482 | ((uint32_t *)ws->reply)[4] = htonl(seed_count); | ||
| 483 | ws->reply_size = 20; | ||
| 484 | } | ||
| 485 | |||
| 486 | mutex_bucket_unlock_by_hash(*ws->hash, 0); | ||
| 487 | return ws->reply_size; | ||
| 488 | } | ||
| 489 | |||
| 490 | void iterate_all_torrents(int (*for_each)(ot_torrent *torrent, uintptr_t data), uintptr_t data) { | ||
| 491 | size_t bucket, j; | ||
| 492 | |||
| 493 | for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { | ||
| 494 | ot_vector *torrents_list = mutex_bucket_lock(bucket); | ||
| 495 | ot_torrent *torrents = (ot_torrent *)(torrents_list->data); | ||
| 496 | |||
| 497 | for (j = 0; j < torrents_list->size; ++j) | ||
| 498 | if (for_each(torrents + j, data)) | ||
| 499 | break; | ||
| 500 | |||
| 501 | mutex_bucket_unlock(bucket, 0); | ||
| 502 | if (!g_opentracker_running) | ||
| 503 | return; | ||
| 504 | } | ||
| 505 | } | ||
| 506 | |||
| 507 | ot_peer *peer_from_peer6(ot_peer6 *peer, size_t *peer_size) { | ||
| 508 | ot_ip6 *ip = (ot_ip6 *)peer; | ||
| 509 | if (!ip_is_v4mapped(ip)) { | ||
| 510 | *peer_size = OT_PEER_SIZE6; | ||
| 511 | return (ot_peer *)peer; | ||
| 512 | } | ||
| 513 | *peer_size = OT_PEER_SIZE4; | ||
| 514 | return (ot_peer *)(((uint8_t *)peer) + 12); | ||
| 515 | } | ||
| 516 | |||
| 517 | size_t peer_size_from_peer6(ot_peer6 *peer) { | ||
| 518 | ot_ip6 *ip = (ot_ip6 *)peer; | ||
| 519 | if (!ip_is_v4mapped(ip)) | ||
| 520 | return OT_PEER_SIZE6; | ||
| 521 | return OT_PEER_SIZE4; | ||
| 522 | } | ||
| 523 | |||
| 524 | #ifdef _DEBUG_RANDOMTORRENTS | ||
| 525 | void trackerlogic_add_random_torrents(size_t amount) { | ||
| 526 | struct ot_workstruct ws; | ||
| 527 | memset(&ws, 0, sizeof(ws)); | ||
| 528 | |||
| 529 | ws.inbuf = malloc(G_INBUF_SIZE); | ||
| 530 | ws.outbuf = malloc(G_OUTBUF_SIZE); | ||
| 531 | ws.reply = ws.outbuf; | ||
| 532 | ws.hash = (ot_hash *)ws.inbuf; | ||
| 533 | |||
| 534 | while (amount--) { | ||
| 535 | random_bytes(ws.hash, sizeof(ot_hash)); | ||
| 536 | random_bytes(&ws.peer, sizeof(ws.peer)); | ||
| 537 | |||
| 538 | OT_PEERFLAG(ws.peer) &= PEER_FLAG_SEEDING | PEER_FLAG_COMPLETED | PEER_FLAG_STOPPED; | ||
| 539 | |||
| 540 | add_peer_to_torrent_and_return_peers(FLAG_TCP, &ws, 1); | ||
| 541 | } | ||
| 542 | |||
| 543 | free(ws.inbuf); | ||
| 544 | free(ws.outbuf); | ||
| 545 | } | ||
| 546 | #endif | ||
| 547 | |||
| 548 | void exerr(const char *message) { | ||
| 549 | fprintf(stderr, "%s\n", message); | ||
| 550 | exit(111); | ||
| 551 | } | ||
| 552 | |||
| 553 | void trackerlogic_init(void) { | ||
| 554 | g_tracker_id = random_uint32(); | ||
| 555 | |||
| 556 | if (!g_stats_path) | ||
| 557 | g_stats_path = "stats"; | ||
| 558 | g_stats_path_len = strlen(g_stats_path); | ||
| 559 | |||
| 560 | /* Initialise background worker threads */ | ||
| 561 | mutex_init(); | ||
| 562 | workqueue_init(); | ||
| 563 | clean_init(); | ||
| 564 | fullscrape_init(); | ||
| 565 | accesslist_init(); | ||
| 566 | livesync_init(); | ||
| 567 | stats_init(); | ||
| 568 | } | ||
| 569 | |||
| 570 | /* Live sync reads whole packets rather than requests and wants a larger | ||
| 571 | inbuf than the request path does, hence the size. */ | ||
| 572 | int workstruct_init(struct ot_workstruct *ws, size_t inbuf_size) { | ||
| 573 | memset(ws, 0, sizeof(*ws)); | ||
| 574 | ws->inbuf = malloc(inbuf_size); | ||
| 575 | ws->outbuf = malloc(G_OUTBUF_SIZE); | ||
| 576 | #ifdef _DEBUG_HTTPERROR | ||
| 577 | ws->debugbuf = malloc(G_DEBUGBUF_SIZE); | ||
| 578 | #endif | ||
| 579 | if (!ws->inbuf || !ws->outbuf) | ||
| 580 | return -1; | ||
| 581 | |||
| 582 | /* Picking which peers to hand out starts here, so two workers seeded alike | ||
| 583 | would hand out alike */ | ||
| 584 | random_bytes(ws->rand48_state, sizeof(ws->rand48_state)); | ||
| 585 | |||
| 586 | stats_counters_register(ws); | ||
| 587 | return 0; | ||
| 588 | } | ||
| 589 | |||
| 590 | void workstruct_deinit(struct ot_workstruct *ws) { | ||
| 591 | /* The tally goes to the global before this struct goes out of scope */ | ||
| 592 | stats_counters_retire(ws); | ||
| 593 | |||
| 594 | free(ws->inbuf); | ||
| 595 | free(ws->outbuf); | ||
| 596 | #ifdef _DEBUG_HTTPERROR | ||
| 597 | free(ws->debugbuf); | ||
| 598 | #endif | ||
| 599 | } | ||
| 600 | |||
| 601 | void trackerlogic_deinit(void) { | ||
| 602 | int delta_torrentcount = 0; | ||
| 603 | size_t bucket, j; | ||
| 604 | |||
| 605 | /* Stop the background workers first */ | ||
| 606 | stats_deinit(); | ||
| 607 | livesync_deinit(); | ||
| 608 | accesslist_deinit(); | ||
| 609 | fullscrape_deinit(); | ||
| 610 | clean_deinit(); | ||
| 611 | |||
| 612 | /* Free all torrents... */ | ||
| 613 | for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { | ||
| 614 | ot_vector *torrents_list = mutex_bucket_lock(bucket); | ||
| 615 | if (torrents_list->size) { | ||
| 616 | for (j = 0; j < torrents_list->size; ++j) { | ||
| 617 | ot_torrent *torrent = ((ot_torrent *)(torrents_list->data)) + j; | ||
| 618 | free_peerlist(torrent->peer_list6); | ||
| 619 | free_peerlist(torrent->peer_list4); | ||
| 620 | delta_torrentcount -= 1; | ||
| 621 | } | ||
| 622 | free(torrents_list->data); | ||
| 623 | } | ||
| 624 | mutex_bucket_unlock(bucket, delta_torrentcount); | ||
| 625 | } | ||
| 626 | |||
| 627 | /* Release mutexes */ | ||
| 628 | workqueue_deinit(); | ||
| 629 | mutex_deinit(); | ||
| 630 | } | ||
diff --git a/src/trackerlogic.h b/src/trackerlogic.h new file mode 100644 index 0000000..692b6af --- /dev/null +++ b/src/trackerlogic.h | |||
| @@ -0,0 +1,283 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. */ | ||
| 3 | |||
| 4 | #ifndef OT_TRACKERLOGIC_H__ | ||
| 5 | #define OT_TRACKERLOGIC_H__ | ||
| 6 | |||
| 7 | #include <signal.h> | ||
| 8 | #include <stdatomic.h> | ||
| 9 | #include <stdint.h> | ||
| 10 | #include <stdlib.h> | ||
| 11 | #include <sys/time.h> | ||
| 12 | #include <sys/types.h> | ||
| 13 | #include <time.h> | ||
| 14 | |||
| 15 | typedef uint8_t ot_hash[20]; | ||
| 16 | typedef time_t ot_time; | ||
| 17 | typedef char ot_ip6[16]; | ||
| 18 | typedef struct { | ||
| 19 | ot_ip6 address; | ||
| 20 | int bits; | ||
| 21 | } ot_net; | ||
| 22 | /* List of peers should fit in a single UDP packet (around 1200 bytes) */ | ||
| 23 | #define OT_MAX_PEERS_UDP6 66 | ||
| 24 | #define OT_MAX_PEERS_UDP4 200 | ||
| 25 | |||
| 26 | #define OT_IP_SIZE6 16 | ||
| 27 | #define OT_IP_SIZE4 4 | ||
| 28 | #define OT_PORT_SIZE 2 | ||
| 29 | #define OT_FLAG_SIZE 1 | ||
| 30 | #define OT_TIME_SIZE 1 | ||
| 31 | |||
| 32 | /* Some tracker behaviour tunable */ | ||
| 33 | #ifndef OT_CLIENT_TIMEOUT | ||
| 34 | #define OT_CLIENT_TIMEOUT 30 | ||
| 35 | #endif | ||
| 36 | #ifndef OT_CLIENT_TIMEOUT_CHECKINTERVAL | ||
| 37 | #define OT_CLIENT_TIMEOUT_CHECKINTERVAL 10 | ||
| 38 | #endif | ||
| 39 | #define OT_CLIENT_TIMEOUT_SEND (60 * 15) | ||
| 40 | #define OT_CLIENT_REQUEST_INTERVAL (60 * 30) | ||
| 41 | #define OT_CLIENT_REQUEST_VARIATION (60 * 6) | ||
| 42 | |||
| 43 | #define OT_TORRENT_TIMEOUT_HOURS 24 | ||
| 44 | #define OT_TORRENT_TIMEOUT (60 * OT_TORRENT_TIMEOUT_HOURS) | ||
| 45 | |||
| 46 | #define OT_CLIENT_REQUEST_INTERVAL_RANDOM \ | ||
| 47 | (OT_CLIENT_REQUEST_INTERVAL - OT_CLIENT_REQUEST_VARIATION / 2 + (int)(nrand48(ws->rand48_state) % OT_CLIENT_REQUEST_VARIATION)) | ||
| 48 | |||
| 49 | /* If WANT_MODEST_FULLSCRAPES is on, ip addresses may not | ||
| 50 | fullscrape more frequently than this amount in seconds */ | ||
| 51 | #define OT_MODEST_PEER_TIMEOUT (60 * 5) | ||
| 52 | |||
| 53 | /* If peers come back before 10 minutes, don't live sync them */ | ||
| 54 | #define OT_CLIENT_SYNC_RENEW_BOUNDARY 10 | ||
| 55 | |||
| 56 | /* Number of tracker admin ip addresses allowed */ | ||
| 57 | #define OT_ADMINIP_MAX 64 | ||
| 58 | #define OT_MAX_THREADS 64 | ||
| 59 | |||
| 60 | /* Number of minutes after announce before peer is removed */ | ||
| 61 | #define OT_PEER_TIMEOUT 45 | ||
| 62 | |||
| 63 | /* We maintain a list of 1024 pointers to sorted list of ot_torrent structs | ||
| 64 | Sort key is, of course, its hash */ | ||
| 65 | /* Set from the Makefile, 8 to 16. More buckets means shorter vectors and a | ||
| 66 | cheaper insert, which only pays for a tracker carrying millions of | ||
| 67 | torrents; see DESIGN. */ | ||
| 68 | #ifndef OT_BUCKET_COUNT_BITS | ||
| 69 | #define OT_BUCKET_COUNT_BITS 10 | ||
| 70 | #endif | ||
| 71 | |||
| 72 | #define OT_BUCKET_COUNT (1 << OT_BUCKET_COUNT_BITS) | ||
| 73 | #define OT_BUCKET_COUNT_SHIFT (32 - OT_BUCKET_COUNT_BITS) | ||
| 74 | |||
| 75 | /* if _DEBUG_RANDOMTORRENTS is set, this is the amount of torrents to create | ||
| 76 | on startup */ | ||
| 77 | #define RANDOMTORRENTS (1024 * 1024 * 1) | ||
| 78 | |||
| 79 | #if defined(CLOCK_REALTIME_COARSE) | ||
| 80 | #define OT_CLOCK_CHEAP CLOCK_REALTIME_COARSE /* Linux */ | ||
| 81 | #elif defined(CLOCK_REALTIME_FAST) | ||
| 82 | #define OT_CLOCK_CHEAP CLOCK_REALTIME_FAST /* the BSDs */ | ||
| 83 | #else | ||
| 84 | #define OT_CLOCK_CHEAP CLOCK_REALTIME /* macOS and whatever else */ | ||
| 85 | #endif | ||
| 86 | |||
| 87 | static inline ot_time time_now_seconds(void) { | ||
| 88 | struct timespec now; | ||
| 89 | clock_gettime(OT_CLOCK_CHEAP, &now); | ||
| 90 | return (ot_time)now.tv_sec; | ||
| 91 | } | ||
| 92 | |||
| 93 | static inline ot_time time_now_minutes(void) { return time_now_seconds() / 60; } | ||
| 94 | |||
| 95 | /* For the deadlines the tracker sets on itself, which read better as the | ||
| 96 | span they allow than as an addition to the current second */ | ||
| 97 | static inline ot_time time_future_seconds(int span) { return time_now_seconds() + span; } | ||
| 98 | /* Cleared from the interrupt handler and read by every worker */ | ||
| 99 | extern _Atomic int g_opentracker_running; | ||
| 100 | |||
| 101 | /* Sent to a thread parked in a blocking read */ | ||
| 102 | #define OT_SIGNAL_WAKE SIGUSR1 | ||
| 103 | |||
| 104 | |||
| 105 | extern uint32_t g_tracker_id; | ||
| 106 | typedef enum { FLAG_TCP, FLAG_UDP, FLAG_MCA, FLAG_SELFPIPE } PROTO_FLAG; | ||
| 107 | |||
| 108 | #define OT_PEER_COMPARE_SIZE6 ((OT_IP_SIZE6) + (OT_PORT_SIZE)) | ||
| 109 | #define OT_PEER_COMPARE_SIZE4 ((OT_IP_SIZE4) + (OT_PORT_SIZE)) | ||
| 110 | #define OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(PEER_SIZE) ((PEER_SIZE) - (OT_TIME_SIZE) - (OT_FLAG_SIZE)) | ||
| 111 | |||
| 112 | #define OT_PEER_SIZE6 ((OT_TIME_SIZE) + (OT_FLAG_SIZE) + (OT_PEER_COMPARE_SIZE6)) | ||
| 113 | #define OT_PEER_SIZE4 ((OT_TIME_SIZE) + (OT_FLAG_SIZE) + (OT_PEER_COMPARE_SIZE4)) | ||
| 114 | |||
| 115 | typedef uint8_t ot_peer; /* Generic pointer to a v6 or v4 peer */ | ||
| 116 | typedef uint8_t ot_peer6[OT_PEER_SIZE6]; | ||
| 117 | typedef uint8_t ot_peer4[OT_PEER_SIZE4]; | ||
| 118 | static const uint8_t PEER_FLAG_SEEDING = 0x80; | ||
| 119 | static const uint8_t PEER_FLAG_COMPLETED = 0x40; | ||
| 120 | static const uint8_t PEER_FLAG_STOPPED = 0x20; | ||
| 121 | static const uint8_t PEER_FLAG_FROM_SYNC = 0x10; | ||
| 122 | static const uint8_t PEER_FLAG_LEECHING = 0x00; | ||
| 123 | |||
| 124 | /* Takes an ot_peer6 and returns the proper pointer to the peer and sets peer_size */ | ||
| 125 | ot_peer *peer_from_peer6(ot_peer6 *peer, size_t *peer_size); | ||
| 126 | size_t peer_size_from_peer6(ot_peer6 *peer); | ||
| 127 | |||
| 128 | /* New style */ | ||
| 129 | #define OT_SETIP(peer, ip) memcpy((uint8_t *)(peer), (ip), OT_IP_SIZE6) | ||
| 130 | #define OT_SETPORT(peer, port) memcpy(((uint8_t *)(peer)) + (OT_IP_SIZE6), (port), 2) | ||
| 131 | #define OT_PEERFLAG(peer) (((uint8_t *)(peer))[(OT_IP_SIZE6) + 2]) | ||
| 132 | #define OT_PEERFLAG_D(peer, peersize) (((uint8_t *)(peer))[(peersize) - 2]) | ||
| 133 | #define OT_PEERTIME(peer, peersize) (((uint8_t *)(peer))[(peersize) - 1]) | ||
| 134 | |||
| 135 | #define PEERS_BENCODED6 "6:peers6" | ||
| 136 | #define PEERS_BENCODED4 "5:peers" | ||
| 137 | |||
| 138 | #define OT_HASH_COMPARE_SIZE (sizeof(ot_hash)) | ||
| 139 | |||
| 140 | struct ot_peerlist; | ||
| 141 | typedef struct ot_peerlist ot_peerlist; | ||
| 142 | typedef struct { | ||
| 143 | ot_hash hash; | ||
| 144 | ot_peerlist *peer_list6; | ||
| 145 | ot_peerlist *peer_list4; | ||
| 146 | } ot_torrent; | ||
| 147 | |||
| 148 | #include "ot_vector.h" | ||
| 149 | |||
| 150 | struct ot_peerlist { | ||
| 151 | ot_time base; | ||
| 152 | size_t seed_count; | ||
| 153 | size_t peer_count; | ||
| 154 | size_t down_count; | ||
| 155 | /* normal peers vector or | ||
| 156 | pointer to ot_vector[32] buckets if data != NULL and space == 0 | ||
| 157 | */ | ||
| 158 | ot_vector peers; | ||
| 159 | }; | ||
| 160 | #define OT_PEERLIST_HASBUCKETS(peer_list) ((peer_list)->peers.size > (peer_list)->peers.space) | ||
| 161 | |||
| 162 | /* The http replies the tracker counts when it has to refuse a request. */ | ||
| 163 | enum { | ||
| 164 | CODE_HTTPERROR_302, | ||
| 165 | CODE_HTTPERROR_400, | ||
| 166 | CODE_HTTPERROR_400_PARAM, | ||
| 167 | CODE_HTTPERROR_400_COMPACT, | ||
| 168 | CODE_HTTPERROR_402_NOTMODEST, | ||
| 169 | CODE_HTTPERROR_402_PAYMENT_REQUIRED, | ||
| 170 | CODE_HTTPERROR_403_IP, | ||
| 171 | CODE_HTTPERROR_404, | ||
| 172 | CODE_HTTPERROR_500, | ||
| 173 | |||
| 174 | CODE_HTTPERROR_COUNT | ||
| 175 | }; | ||
| 176 | |||
| 177 | /* One statistic. Used when workers report stats to their workstruct */ | ||
| 178 | typedef _Atomic unsigned long long ot_counter; | ||
| 179 | |||
| 180 | /* What each thread counts for itself. The two runs at the end are indexed by | ||
| 181 | the http error code and by the age in minutes of a renewed peer */ | ||
| 182 | typedef enum { | ||
| 183 | OT_COUNTER_TCP_CONNECTIONS, | ||
| 184 | OT_COUNTER_UDP_CONNECTIONS, | ||
| 185 | OT_COUNTER_TCP_ANNOUNCES, | ||
| 186 | OT_COUNTER_UDP_ANNOUNCES, | ||
| 187 | OT_COUNTER_TCP_SCRAPES, | ||
| 188 | OT_COUNTER_UDP_SCRAPES, | ||
| 189 | OT_COUNTER_TCP_CONNECTS, | ||
| 190 | OT_COUNTER_UDP_CONNECTS, | ||
| 191 | OT_COUNTER_UDP_CONNID_MISSMATCH, | ||
| 192 | OT_COUNTER_COMPLETED, | ||
| 193 | OT_COUNTER_FULLSCRAPES, | ||
| 194 | OT_COUNTER_FULLSCRAPE_REQUESTS, | ||
| 195 | OT_COUNTER_FULLSCRAPE_BYTES, | ||
| 196 | OT_COUNTER_SYNCS, | ||
| 197 | OT_COUNTER_STALLS, | ||
| 198 | |||
| 199 | OT_COUNTER_FAILED, /* CODE_HTTPERROR_COUNT of them */ | ||
| 200 | OT_COUNTER_RENEWED = OT_COUNTER_FAILED + CODE_HTTPERROR_COUNT, /* OT_PEER_TIMEOUT of them */ | ||
| 201 | |||
| 202 | OT_COUNTER_COUNT = OT_COUNTER_RENEWED + OT_PEER_TIMEOUT | ||
| 203 | } ot_counter_index; | ||
| 204 | |||
| 205 | struct ot_workstruct { | ||
| 206 | /* Thread specific, static */ | ||
| 207 | |||
| 208 | /* This thread's own statistics, and the link ot_stats keeps them on */ | ||
| 209 | ot_counter counters[OT_COUNTER_COUNT]; | ||
| 210 | struct ot_workstruct *counters_next; | ||
| 211 | |||
| 212 | char *inbuf; | ||
| 213 | #define G_INBUF_SIZE 8192 | ||
| 214 | char *outbuf; | ||
| 215 | #define G_OUTBUF_SIZE 8192 | ||
| 216 | #ifdef _DEBUG_HTTPERROR | ||
| 217 | char *debugbuf; | ||
| 218 | #define G_DEBUGBUF_SIZE 8192 | ||
| 219 | #endif | ||
| 220 | |||
| 221 | /* The peer currently in the working */ | ||
| 222 | ot_peer6 peer; /* Can fit v6 and v4 peers */ | ||
| 223 | |||
| 224 | /* Pointers into the request buffer */ | ||
| 225 | ot_hash *hash; | ||
| 226 | char *peer_id; | ||
| 227 | |||
| 228 | /* HTTP specific, non static */ | ||
| 229 | char *request; | ||
| 230 | ssize_t request_size; | ||
| 231 | ssize_t header_size; | ||
| 232 | char *reply; | ||
| 233 | ssize_t reply_size; | ||
| 234 | |||
| 235 | /* Entropy state for rand48 function so that threads don't need to acquire mutexes for | ||
| 236 | global random() or arc4random() state, which causes heavy load on linuxes */ | ||
| 237 | uint16_t rand48_state[3]; | ||
| 238 | |||
| 239 | int keep_alive; | ||
| 240 | }; | ||
| 241 | |||
| 242 | /* Everything a thread that serves requests needs for itself. Set up and torn | ||
| 243 | down in one place so that a new kind of worker cannot miss a piece. | ||
| 244 | Returns -1 if the buffers could not be had. */ | ||
| 245 | int workstruct_init(struct ot_workstruct *ws, size_t inbuf_size); | ||
| 246 | void workstruct_deinit(struct ot_workstruct *ws); | ||
| 247 | |||
| 248 | /* | ||
| 249 | Exported functions | ||
| 250 | */ | ||
| 251 | |||
| 252 | #ifdef WANT_SYNC_LIVE | ||
| 253 | #define WANT_SYNC | ||
| 254 | #endif | ||
| 255 | |||
| 256 | #ifdef WANT_SYNC | ||
| 257 | #define WANT_SYNC_PARAM(param) , param | ||
| 258 | #else | ||
| 259 | #define WANT_SYNC_PARAM(param) | ||
| 260 | #endif | ||
| 261 | |||
| 262 | void trackerlogic_init(void); | ||
| 263 | void trackerlogic_deinit(void); | ||
| 264 | void exerr(const char *message); | ||
| 265 | |||
| 266 | /* add_peer_to_torrent does only release the torrent bucket if from_sync is set, | ||
| 267 | otherwise it is released in return_peers_for_torrent */ | ||
| 268 | size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstruct *ws, size_t amount); | ||
| 269 | size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws); | ||
| 270 | void return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char **reply, const char *reply_end); | ||
| 271 | size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply); | ||
| 272 | void add_torrent_from_saved_state(ot_hash const hash, ot_time base, size_t down_count); | ||
| 273 | #ifdef _DEBUG_RANDOMTORRENTS | ||
| 274 | void trackerlogic_add_random_torrents(size_t amount); | ||
| 275 | #endif | ||
| 276 | |||
| 277 | /* torrent iterator */ | ||
| 278 | void iterate_all_torrents(int (*for_each)(ot_torrent *torrent, uintptr_t data), uintptr_t data); | ||
| 279 | |||
| 280 | /* Helper, before it moves to its own object */ | ||
| 281 | void free_peerlist(ot_peerlist *peer_list); | ||
| 282 | |||
| 283 | #endif | ||
