diff options
| -rw-r--r-- | Makefile | 40 | ||||
| -rw-r--r-- | ot_config.c | 7 | ||||
| -rw-r--r-- | ot_fmt.c | 42 | ||||
| -rw-r--r-- | ot_fmt.h | 21 | ||||
| -rw-r--r-- | ot_fullscrape.c | 38 | ||||
| -rw-r--r-- | ot_http.c | 42 | ||||
| -rw-r--r-- | ot_stats.c | 306 | ||||
| -rw-r--r-- | ot_stats.h | 4 | ||||
| -rw-r--r-- | ot_sync.c | 13 | ||||
| -rw-r--r-- | ot_vector.c | 47 | ||||
| -rw-r--r-- | tests/integration/.gitignore | 2 | ||||
| -rw-r--r-- | tests/integration/harness.py | 198 | ||||
| -rw-r--r-- | tests/integration/test_announce.py | 129 | ||||
| -rw-r--r-- | tests/integration/test_dualstack.py | 89 | ||||
| -rw-r--r-- | tests/runner.c | 73 | ||||
| -rw-r--r-- | tests/t_fmt.c | 108 | ||||
| -rw-r--r-- | tests/t_vector.c | 46 | ||||
| -rw-r--r-- | tests/test.h | 1 | ||||
| -rw-r--r-- | trackerlogic.c | 55 | ||||
| -rw-r--r-- | trackerlogic.h | 2 |
20 files changed, 1012 insertions, 251 deletions
| @@ -69,8 +69,8 @@ CFLAGS_libowfat=-I$(LIBOWFAT_HEADERS) | |||
| 69 | LDFLAGS_libowfat=-L$(LIBOWFAT_LIBRARY) -lowfat | 69 | LDFLAGS_libowfat=-L$(LIBOWFAT_LIBRARY) -lowfat |
| 70 | 70 | ||
| 71 | BINARY =opentracker | 71 | BINARY =opentracker |
| 72 | HEADERS=trackerlogic.h ot_urlencoded.h ot_batch.h ot_buf.h ot_event.h ot_fdstate.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h | 72 | HEADERS=trackerlogic.h ot_urlencoded.h ot_batch.h ot_buf.h ot_event.h ot_fdstate.h ot_fmt.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h |
| 73 | SOURCES=opentracker.c trackerlogic.c ot_urlencoded.c ot_batch.c ot_buf.c ot_event.c ot_fdstate.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c | 73 | SOURCES=opentracker.c trackerlogic.c ot_urlencoded.c ot_batch.c ot_buf.c ot_event.c ot_fdstate.c ot_fmt.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c |
| 74 | SOURCES_proxy=proxy.c ot_batch.c ot_fdstate.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c | 74 | SOURCES_proxy=proxy.c ot_batch.c ot_fdstate.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c |
| 75 | 75 | ||
| 76 | OBJECTS = $(SOURCES:%.c=%.o) | 76 | OBJECTS = $(SOURCES:%.c=%.o) |
| @@ -120,8 +120,8 @@ $(OBJECTS_proxy_debug): $(HEADERS) | |||
| 120 | # has to. tests/stubs.c supplies the few symbols those modules reference but | 120 | # has to. tests/stubs.c supplies the few symbols those modules reference but |
| 121 | # the tests never reach. | 121 | # the tests never reach. |
| 122 | TESTBIN=tests/runner | 122 | TESTBIN=tests/runner |
| 123 | TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_urlencoded.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c tests/t_mutex.c tests/t_batch.c tests/t_fdstate.c tests/t_event.c | 123 | TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_urlencoded.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c tests/t_mutex.c tests/t_batch.c tests/t_fdstate.c tests/t_event.c tests/t_fmt.c |
| 124 | TEST_UNITS=ot_urlencoded.c ot_event.c ot_batch.c ot_fdstate.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c ot_mutex.c | 124 | TEST_UNITS=ot_urlencoded.c ot_fmt.c ot_event.c ot_batch.c ot_fdstate.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c ot_mutex.c |
| 125 | TEST_HEADERS=tests/test.h $(HEADERS) | 125 | TEST_HEADERS=tests/test.h $(HEADERS) |
| 126 | # Tests reach code that sits behind feature flags, so the units under test are | 126 | # Tests reach code that sits behind feature flags, so the units under test are |
| 127 | # built with those features enabled. A test binary is not a production binary. | 127 | # built with those features enabled. A test binary is not a production binary. |
| @@ -134,10 +134,38 @@ $(TESTBIN): $(TEST_SOURCES) $(TEST_UNITS) $(TEST_HEADERS) | |||
| 134 | check: $(TESTBIN) $(BINARY) | 134 | check: $(TESTBIN) $(BINARY) |
| 135 | ./$(TESTBIN) | 135 | ./$(TESTBIN) |
| 136 | 136 | ||
| 137 | .PHONY: all check clean install | 137 | # The same unit tests under the sanitizers. Use clang over $(CC) because the |
| 138 | # two toolchains do not check the same things | ||
| 139 | # Override CC_sanitize to use another compiler. | ||
| 140 | CC_sanitize?=clang | ||
| 141 | CFLAGS_sanitize=$(CFLAGS_test) -fsanitize=address,undefined -fno-omit-frame-pointer | ||
| 142 | LDFLAGS_sanitize=$(LDFLAGS) -fsanitize=address,undefined | ||
| 143 | |||
| 144 | $(TESTBIN).sanitize: $(TEST_SOURCES) $(TEST_UNITS) $(TEST_HEADERS) | ||
| 145 | $(CC_sanitize) -o $@ $(CFLAGS_sanitize) $(TEST_SOURCES) $(TEST_UNITS) $(LDFLAGS_sanitize) | ||
| 146 | |||
| 147 | check-sanitize: $(TESTBIN).sanitize | ||
| 148 | ./$(TESTBIN).sanitize | ||
| 149 | |||
| 150 | # Black box tests: these drive the real binary over the wire, so they cover | ||
| 151 | # the event loop and the parsers working together, which the unit tests | ||
| 152 | # cannot reach. Built with the sanitizers on, so the tracker reports its own | ||
| 153 | # memory errors and the harness turns those into failures. | ||
| 154 | PYTHON?=python3 | ||
| 155 | FEATURES_integration=$(FEATURES) -DWANT_IP_FROM_QUERY_STRING | ||
| 156 | CFLAGS_integration=$(CFLAGS) $(OPTS_debug) $(FEATURES_integration) -fsanitize=address,undefined -fno-omit-frame-pointer | ||
| 157 | LDFLAGS_integration=$(LDFLAGS) -fsanitize=address,undefined | ||
| 158 | |||
| 159 | opentracker.integration: $(SOURCES) $(HEADERS) | ||
| 160 | $(CC) -o $@ $(CFLAGS_integration) $(SOURCES) $(LDFLAGS_integration) | ||
| 161 | |||
| 162 | check-integration: opentracker.integration | ||
| 163 | OPENTRACKER=./opentracker.integration $(PYTHON) -m unittest discover -s tests/integration | ||
| 164 | |||
| 165 | .PHONY: all check check-sanitize check-integration clean install | ||
| 138 | 166 | ||
| 139 | clean: | 167 | clean: |
| 140 | rm -rf opentracker opentracker.debug proxy proxy.debug $(TESTBIN) *.o *~ | 168 | rm -rf opentracker opentracker.debug opentracker.integration proxy proxy.debug $(TESTBIN) $(TESTBIN).sanitize *.o *~ |
| 141 | 169 | ||
| 142 | install: | 170 | install: |
| 143 | install -m 755 opentracker $(DESTDIR)$(BINDIR) | 171 | install -m 755 opentracker $(DESTDIR)$(BINDIR) |
diff --git a/ot_config.c b/ot_config.c index 32625bd..ee6bc12 100644 --- a/ot_config.c +++ b/ot_config.c | |||
| @@ -544,8 +544,11 @@ void config_parse(int argc, char **argv) { | |||
| 544 | help(argv[0]); | 544 | help(argv[0]); |
| 545 | exit(0); | 545 | exit(0); |
| 546 | case 'v': { | 546 | case 'v': { |
| 547 | char buffer[8192]; | 547 | char buffer[8192]; |
| 548 | stats_return_tracker_version(buffer); | 548 | char *cursor = buffer; |
| 549 | |||
| 550 | stats_return_tracker_version(&cursor, buffer + sizeof(buffer) - 1); | ||
| 551 | *cursor = 0; | ||
| 549 | fputs(buffer, stderr); | 552 | fputs(buffer, stderr); |
| 550 | exit(0); | 553 | exit(0); |
| 551 | } | 554 | } |
diff --git a/ot_fmt.c b/ot_fmt.c new file mode 100644 index 0000000..9cab25f --- /dev/null +++ b/ot_fmt.c | |||
| @@ -0,0 +1,42 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. | ||
| 3 | |||
| 4 | $id$ */ | ||
| 5 | |||
| 6 | /* System */ | ||
| 7 | #include <stdarg.h> | ||
| 8 | #include <stdio.h> | ||
| 9 | #include <string.h> | ||
| 10 | |||
| 11 | /* Opentracker */ | ||
| 12 | #include "ot_fmt.h" | ||
| 13 | |||
| 14 | void fmt_append(char **cursor, const char *end, const char *format, ...) { | ||
| 15 | va_list arguments; | ||
| 16 | size_t room; | ||
| 17 | int written; | ||
| 18 | |||
| 19 | if (*cursor >= end) { | ||
| 20 | *cursor = (char *)end; | ||
| 21 | return; | ||
| 22 | } | ||
| 23 | |||
| 24 | room = (size_t)(end - *cursor); | ||
| 25 | va_start(arguments, format); | ||
| 26 | written = vsnprintf(*cursor, room, format, arguments); | ||
| 27 | va_end(arguments); | ||
| 28 | |||
| 29 | if (written < 0) | ||
| 30 | return; | ||
| 31 | *cursor = (size_t)written >= room ? (char *)end : *cursor + written; | ||
| 32 | } | ||
| 33 | |||
| 34 | void fmt_bytes(char **cursor, const char *end, const void *bytes, size_t length) { | ||
| 35 | if (*cursor >= end || (size_t)(end - *cursor) < length) { | ||
| 36 | *cursor = (char *)end; | ||
| 37 | return; | ||
| 38 | } | ||
| 39 | |||
| 40 | memcpy(*cursor, bytes, length); | ||
| 41 | *cursor += length; | ||
| 42 | } | ||
diff --git a/ot_fmt.h b/ot_fmt.h new file mode 100644 index 0000000..851b2d8 --- /dev/null +++ b/ot_fmt.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 | $id$ */ | ||
| 5 | |||
| 6 | #ifndef OT_FMT_H__ | ||
| 7 | #define OT_FMT_H__ | ||
| 8 | |||
| 9 | #include <stddef.h> | ||
| 10 | |||
| 11 | /* Appending to a cursor that walks a fixed buffer. Both take the cursor by | ||
| 12 | reference and advance it, so a reply is written as a run of statements and | ||
| 13 | no caller does pointer arithmetic of its own. */ | ||
| 14 | |||
| 15 | void fmt_append(char **cursor, const char *end, const char *format, ...) __attribute__((format(printf, 3, 4))); | ||
| 16 | |||
| 17 | /* Append raw bytes for the binary stretches of a bencoded reply. | ||
| 18 | Either everything is appended or nothing at all */ | ||
| 19 | void fmt_bytes(char **cursor, const char *end, const void *bytes, size_t length); | ||
| 20 | |||
| 21 | #endif | ||
diff --git a/ot_fullscrape.c b/ot_fullscrape.c index 2ba3b24..8518bd2 100644 --- a/ot_fullscrape.c +++ b/ot_fullscrape.c | |||
| @@ -18,6 +18,7 @@ | |||
| 18 | #endif | 18 | #endif |
| 19 | 19 | ||
| 20 | /* Opentracker */ | 20 | /* Opentracker */ |
| 21 | #include "ot_fmt.h" | ||
| 21 | #include "ot_hex.h" | 22 | #include "ot_hex.h" |
| 22 | #include "ot_fullscrape.h" | 23 | #include "ot_fullscrape.h" |
| 23 | #include "ot_iovec.h" | 24 | #include "ot_iovec.h" |
| @@ -83,10 +84,15 @@ void fullscrape_deliver( int64_t sock, ot_tasktype tasktype ) { | |||
| 83 | mutex_workqueue_pushtask( sock, TASK_FULLSCRAPE | tasktype ); | 84 | mutex_workqueue_pushtask( sock, TASK_FULLSCRAPE | tasktype ); |
| 84 | } | 85 | } |
| 85 | 86 | ||
| 86 | static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torrent, ot_hash *hash ) { | 87 | static void fullscrape_write_one( ot_tasktype mode, char **cursor, ot_torrent *torrent, ot_hash *hash ) { |
| 87 | size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; | 88 | /* Callers guarantee this much room: the chunk writer keeps its low |
| 88 | size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; | 89 | watermark that far from the end, the compressors hand over a buffer of |
| 89 | size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count; | 90 | exactly that size */ |
| 91 | char *r = *cursor; | ||
| 92 | const char *r_end = r + OT_SCRAPE_MAXENTRYLEN; | ||
| 93 | size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; | ||
| 94 | size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; | ||
| 95 | size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count; | ||
| 90 | 96 | ||
| 91 | switch (mode & TASK_TASK_MASK) { | 97 | switch (mode & TASK_TASK_MASK) { |
| 92 | case TASK_FULLSCRAPE: | 98 | case TASK_FULLSCRAPE: |
| @@ -98,18 +104,18 @@ static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torre | |||
| 98 | memcpy(r, hash, sizeof(ot_hash)); | 104 | memcpy(r, hash, sizeof(ot_hash)); |
| 99 | r += sizeof(ot_hash); | 105 | r += sizeof(ot_hash); |
| 100 | /* push rest of the scrape string */ | 106 | /* push rest of the scrape string */ |
| 101 | r += sprintf(r, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", seed_count, down_count, peer_count - seed_count); | 107 | fmt_append(&r, r_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", seed_count, down_count, peer_count - seed_count); |
| 102 | 108 | ||
| 103 | break; | 109 | break; |
| 104 | case TASK_FULLSCRAPE_TPB_ASCII: | 110 | case TASK_FULLSCRAPE_TPB_ASCII: |
| 105 | hex_from_hash(r, *hash); | 111 | hex_from_hash(r, *hash); |
| 106 | r += 2 * sizeof(ot_hash); | 112 | r += 2 * sizeof(ot_hash); |
| 107 | r += sprintf(r, ":%zd:%zd\n", seed_count, peer_count - seed_count); | 113 | fmt_append(&r, r_end, ":%zd:%zd\n", seed_count, peer_count - seed_count); |
| 108 | break; | 114 | break; |
| 109 | case TASK_FULLSCRAPE_TPB_ASCII_PLUS: | 115 | case TASK_FULLSCRAPE_TPB_ASCII_PLUS: |
| 110 | hex_from_hash(r, *hash); | 116 | hex_from_hash(r, *hash); |
| 111 | r += 2 * sizeof(ot_hash); | 117 | r += 2 * sizeof(ot_hash); |
| 112 | r += sprintf(r, ":%zd:%zd:%zd\n", seed_count, peer_count - seed_count, down_count); | 118 | fmt_append(&r, r_end, ":%zd:%zd:%zd\n", seed_count, peer_count - seed_count, down_count); |
| 113 | break; | 119 | break; |
| 114 | case TASK_FULLSCRAPE_TPB_BINARY: | 120 | case TASK_FULLSCRAPE_TPB_BINARY: |
| 115 | memcpy(r, *hash, sizeof(ot_hash)); | 121 | memcpy(r, *hash, sizeof(ot_hash)); |
| @@ -120,15 +126,15 @@ static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torre | |||
| 120 | break; | 126 | break; |
| 121 | case TASK_FULLSCRAPE_TPB_URLENCODED: | 127 | case TASK_FULLSCRAPE_TPB_URLENCODED: |
| 122 | r += urlencoded_format(r, *hash, sizeof(ot_hash)); | 128 | r += urlencoded_format(r, *hash, sizeof(ot_hash)); |
| 123 | r += sprintf(r, ":%zd:%zd\n", seed_count, peer_count - seed_count); | 129 | fmt_append(&r, r_end, ":%zd:%zd\n", seed_count, peer_count - seed_count); |
| 124 | break; | 130 | break; |
| 125 | case TASK_FULLSCRAPE_TRACKERSTATE: | 131 | case TASK_FULLSCRAPE_TRACKERSTATE: |
| 126 | hex_from_hash(r, *hash); | 132 | hex_from_hash(r, *hash); |
| 127 | r += 2 * sizeof(ot_hash); | 133 | r += 2 * sizeof(ot_hash); |
| 128 | r += sprintf(r, ":%zd:%zd\n", torrent->peer_list6->base, down_count); | 134 | fmt_append(&r, r_end, ":%zd:%zd\n", torrent->peer_list6->base, down_count); |
| 129 | break; | 135 | break; |
| 130 | } | 136 | } |
| 131 | return r; | 137 | *cursor = r; |
| 132 | } | 138 | } |
| 133 | 139 | ||
| 134 | static void fullscrape_make(int taskid, ot_tasktype mode) { | 140 | static void fullscrape_make(int taskid, ot_tasktype mode) { |
| @@ -145,7 +151,7 @@ static void fullscrape_make(int taskid, ot_tasktype mode) { | |||
| 145 | re = r + OT_SCRAPE_CHUNK_SIZE - OT_SCRAPE_MAXENTRYLEN; | 151 | re = r + OT_SCRAPE_CHUNK_SIZE - OT_SCRAPE_MAXENTRYLEN; |
| 146 | 152 | ||
| 147 | if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) | 153 | if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) |
| 148 | r += sprintf(r, "d5:filesd"); | 154 | fmt_append(&r, (char *)iovector.iov_base + OT_SCRAPE_CHUNK_SIZE, "d5:filesd"); |
| 149 | 155 | ||
| 150 | /* For each bucket... */ | 156 | /* For each bucket... */ |
| 151 | for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { | 157 | for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { |
| @@ -156,7 +162,7 @@ static void fullscrape_make(int taskid, ot_tasktype mode) { | |||
| 156 | 162 | ||
| 157 | /* For each torrent in this bucket.. */ | 163 | /* For each torrent in this bucket.. */ |
| 158 | for (i = 0; i < torrents_list->size; ++i) { | 164 | for (i = 0; i < torrents_list->size; ++i) { |
| 159 | r = fullscrape_write_one(mode, r, torrents + i, &torrents[i].hash); | 165 | fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash); |
| 160 | 166 | ||
| 161 | if (r > re) { | 167 | if (r > re) { |
| 162 | iovector.iov_len = r - (char *)iovector.iov_base; | 168 | iovector.iov_len = r - (char *)iovector.iov_base; |
| @@ -187,7 +193,7 @@ static void fullscrape_make(int taskid, ot_tasktype mode) { | |||
| 187 | } | 193 | } |
| 188 | 194 | ||
| 189 | if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) | 195 | if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) |
| 190 | r += sprintf(r, "ee"); | 196 | fmt_append(&r, (char *)iovector.iov_base + OT_SCRAPE_CHUNK_SIZE, "ee"); |
| 191 | 197 | ||
| 192 | /* Send rest of data */ | 198 | /* Send rest of data */ |
| 193 | iovector.iov_len = r - (char *)iovector.iov_base; | 199 | iovector.iov_len = r - (char *)iovector.iov_base; |
| @@ -230,7 +236,8 @@ static void fullscrape_make_gzip(int taskid, ot_tasktype mode) { | |||
| 230 | /* For each torrent in this bucket.. */ | 236 | /* For each torrent in this bucket.. */ |
| 231 | for (i = 0; i < torrents_list->size; ++i) { | 237 | for (i = 0; i < torrents_list->size; ++i) { |
| 232 | char compress_buffer[OT_SCRAPE_MAXENTRYLEN]; | 238 | char compress_buffer[OT_SCRAPE_MAXENTRYLEN]; |
| 233 | r = fullscrape_write_one(mode, compress_buffer, torrents + i, &torrents[i].hash); | 239 | r = compress_buffer; |
| 240 | fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash); | ||
| 234 | strm.next_in = (uint8_t *)compress_buffer; | 241 | strm.next_in = (uint8_t *)compress_buffer; |
| 235 | strm.avail_in = r - compress_buffer; | 242 | strm.avail_in = r - compress_buffer; |
| 236 | zres = deflate(&strm, Z_NO_FLUSH); | 243 | zres = deflate(&strm, Z_NO_FLUSH); |
| @@ -359,7 +366,8 @@ static void fullscrape_make_zstd(int taskid, ot_tasktype mode) { | |||
| 359 | /* For each torrent in this bucket.. */ | 366 | /* For each torrent in this bucket.. */ |
| 360 | for (i = 0; i < torrents_list->size; ++i) { | 367 | for (i = 0; i < torrents_list->size; ++i) { |
| 361 | char compress_buffer[OT_SCRAPE_MAXENTRYLEN]; | 368 | char compress_buffer[OT_SCRAPE_MAXENTRYLEN]; |
| 362 | r = fullscrape_write_one(mode, compress_buffer, torrents + i, &torrents[i].hash); | 369 | r = compress_buffer; |
| 370 | fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash); | ||
| 363 | inbuf.src = compress_buffer; | 371 | inbuf.src = compress_buffer; |
| 364 | inbuf.size = r - compress_buffer; | 372 | inbuf.size = r - compress_buffer; |
| 365 | inbuf.pos = 0; | 373 | inbuf.pos = 0; |
| @@ -24,6 +24,7 @@ | |||
| 24 | #include "ot_scan.h" | 24 | #include "ot_scan.h" |
| 25 | #include "ot_event.h" | 25 | #include "ot_event.h" |
| 26 | #include "ot_fdstate.h" | 26 | #include "ot_fdstate.h" |
| 27 | #include "ot_fmt.h" | ||
| 27 | #include "ot_stats.h" | 28 | #include "ot_stats.h" |
| 28 | #include "ot_urlencoded.h" | 29 | #include "ot_urlencoded.h" |
| 29 | #include "trackerlogic.h" | 30 | #include "trackerlogic.h" |
| @@ -257,6 +258,7 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c | |||
| 257 | {"txtp", TASK_FULLSCRAPE_TPB_ASCII_PLUS}, {NULL, -3}}; | 258 | {"txtp", TASK_FULLSCRAPE_TPB_ASCII_PLUS}, {NULL, -3}}; |
| 258 | 259 | ||
| 259 | struct http_data *cookie = fdstate_cookie(sock); | 260 | struct http_data *cookie = fdstate_cookie(sock); |
| 261 | char *reply; | ||
| 260 | int mode = TASK_STATS_PEERS, scanon = 1, format = 0; | 262 | int mode = TASK_STATS_PEERS, scanon = 1, format = 0; |
| 261 | 263 | ||
| 262 | /* A socket without one is a dead socket, which is what http_sendiovecdata | 264 | /* A socket without one is a dead socket, which is what http_sendiovecdata |
| @@ -333,7 +335,9 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c | |||
| 333 | } | 335 | } |
| 334 | 336 | ||
| 335 | /* Simple stats can be answerred immediately */ | 337 | /* Simple stats can be answerred immediately */ |
| 336 | return ws->reply_size = return_stats_for_tracker(ws->reply, mode, 0); | 338 | reply = ws->reply; |
| 339 | return_stats_for_tracker(&reply, ws->outbuf + G_OUTBUF_SIZE, mode, 0); | ||
| 340 | return ws->reply_size = reply - ws->reply; | ||
| 337 | } | 341 | } |
| 338 | 342 | ||
| 339 | #ifdef WANT_MODEST_FULLSCRAPES | 343 | #ifdef WANT_MODEST_FULLSCRAPES |
| @@ -417,9 +421,10 @@ static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct * | |||
| 417 | static ssize_t http_handle_scrape(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) { | 421 | static ssize_t http_handle_scrape(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) { |
| 418 | static const ot_keywords keywords_scrape[] = {{"info_hash", 1}, {NULL, -3}}; | 422 | static const ot_keywords keywords_scrape[] = {{"info_hash", 1}, {NULL, -3}}; |
| 419 | 423 | ||
| 420 | ot_hash *multiscrape_buf = (ot_hash *)ws->request; | 424 | ot_hash *multiscrape_buf = (ot_hash *)ws->request; |
| 421 | int scanon = 1; | 425 | char *reply = ws->reply; |
| 422 | size_t numwant = 0; | 426 | int scanon = 1; |
| 427 | size_t numwant = 0; | ||
| 423 | 428 | ||
| 424 | /* This is to hack around stupid clients that send "scrape ?info_hash" */ | 429 | /* This is to hack around stupid clients that send "scrape ?info_hash" */ |
| 425 | if (read_ptr[-1] != '?') { | 430 | if (read_ptr[-1] != '?') { |
| @@ -457,7 +462,8 @@ static ssize_t http_handle_scrape(const int64_t sock, struct ot_workstruct *ws, | |||
| 457 | numwant = OT_MAXMULTISCRAPE_COUNT; | 462 | numwant = OT_MAXMULTISCRAPE_COUNT; |
| 458 | 463 | ||
| 459 | /* Enough for http header + whole scrape string */ | 464 | /* Enough for http header + whole scrape string */ |
| 460 | ws->reply_size = return_tcp_scrape_for_torrent((const ot_hash *)multiscrape_buf, numwant, ws->reply); | 465 | return_tcp_scrape_for_torrent((const ot_hash *)multiscrape_buf, numwant, &reply, ws->outbuf + G_OUTBUF_SIZE); |
| 466 | ws->reply_size = reply - ws->reply; | ||
| 461 | stats_issue_event(EVENT_SCRAPE, FLAG_TCP, ws->reply_size); | 467 | stats_issue_event(EVENT_SCRAPE, FLAG_TCP, ws->reply_size); |
| 462 | return ws->reply_size; | 468 | return ws->reply_size; |
| 463 | } | 469 | } |
| @@ -493,6 +499,16 @@ static ot_keywords keywords_announce[] = {{"port", 1}, {"left", 2}, {"event", | |||
| 493 | #endif | 499 | #endif |
| 494 | {"peer_id", 9}, {NULL, -3}}; | 500 | {"peer_id", 9}, {NULL, -3}}; |
| 495 | static ot_keywords keywords_announce_event[] = {{"completed", 1}, {"stopped", 2}, {NULL, -3}}; | 501 | static ot_keywords keywords_announce_event[] = {{"completed", 1}, {"stopped", 2}, {NULL, -3}}; |
| 502 | |||
| 503 | /* For the replies that are one fixed string and nothing else, where the only | ||
| 504 | thing the caller wants back is how long it turned out */ | ||
| 505 | static ssize_t reply_literal(struct ot_workstruct *ws, const char *text) { | ||
| 506 | char *reply = ws->reply; | ||
| 507 | |||
| 508 | fmt_bytes(&reply, ws->outbuf + G_OUTBUF_SIZE, text, strlen(text)); | ||
| 509 | return reply - ws->reply; | ||
| 510 | } | ||
| 511 | |||
| 496 | static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) { | 512 | static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) { |
| 497 | size_t numwant; | 513 | size_t numwant; |
| 498 | int64_t tmp; | 514 | int64_t tmp; |
| @@ -601,7 +617,7 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct | |||
| 601 | char *tmp_buf1 = ws->reply, *tmp_buf2 = ws->reply + 16; | 617 | char *tmp_buf1 = ws->reply, *tmp_buf2 = ws->reply + 16; |
| 602 | len = urlencoded_parse(&read_ptr, tmp_buf2, SCAN_SEARCHPATH_VALUE); | 618 | len = urlencoded_parse(&read_ptr, tmp_buf2, SCAN_SEARCHPATH_VALUE); |
| 603 | tmp_buf2[len] = 0; | 619 | tmp_buf2[len] = 0; |
| 604 | if ((len <= 0) || !ip_parse(tmp_buf2, tmp_buf1)) | 620 | if ((len <= 0) || ip_parse(tmp_buf2, tmp_buf1) != (size_t)len) |
| 605 | HTTPERROR_400_PARAM; | 621 | HTTPERROR_400_PARAM; |
| 606 | OT_SETIP(&ws->peer, tmp_buf1); | 622 | OT_SETIP(&ws->peer, tmp_buf1); |
| 607 | } break; | 623 | } break; |
| @@ -621,7 +637,7 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct | |||
| 621 | HTTPERROR_400_PARAM; | 637 | HTTPERROR_400_PARAM; |
| 622 | if (*tmp_buf == '-') { | 638 | if (*tmp_buf == '-') { |
| 623 | loglist_reset(); | 639 | loglist_reset(); |
| 624 | return ws->reply_size = sprintf(ws->reply, "Successfully removed.\n"); | 640 | return ws->reply_size = reply_literal(ws, "Successfully removed.\n"); |
| 625 | } | 641 | } |
| 626 | parsed = ip_parse(tmp_buf, net.address); | 642 | parsed = ip_parse(tmp_buf, net.address); |
| 627 | if (!parsed) | 643 | if (!parsed) |
| @@ -638,7 +654,7 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct | |||
| 638 | } | 654 | } |
| 639 | net.bits = bits; | 655 | net.bits = bits; |
| 640 | loglist_add_network(&net); | 656 | loglist_add_network(&net); |
| 641 | return ws->reply_size = sprintf(ws->reply, "Successfully added.\n"); | 657 | return ws->reply_size = reply_literal(ws, "Successfully added.\n"); |
| 642 | //} | 658 | //} |
| 643 | } break; | 659 | } break; |
| 644 | #endif | 660 | #endif |
| @@ -661,7 +677,7 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct | |||
| 661 | 677 | ||
| 662 | /* Scanned whole query string */ | 678 | /* Scanned whole query string */ |
| 663 | if (!ws->hash) | 679 | if (!ws->hash) |
| 664 | return ws->reply_size = sprintf(ws->reply, "d14:failure reason80:Your client forgot to send your torrent's info_hash. Please upgrade your client.e"); | 680 | 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"); |
| 665 | 681 | ||
| 666 | if (OT_PEERFLAG(&ws->peer) & PEER_FLAG_STOPPED) | 682 | if (OT_PEERFLAG(&ws->peer) & PEER_FLAG_STOPPED) |
| 667 | ws->reply_size = remove_peer_from_torrent(FLAG_TCP, ws); | 683 | ws->reply_size = remove_peer_from_torrent(FLAG_TCP, ws); |
| @@ -772,9 +788,11 @@ ssize_t http_handle_request(const int64_t sock, struct ot_workstruct *ws) { | |||
| 772 | reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf(ws->outbuf, 0, "%zd", ws->reply_size); | 788 | reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf(ws->outbuf, 0, "%zd", ws->reply_size); |
| 773 | ws->reply = ws->outbuf + reply_off; | 789 | ws->reply = ws->outbuf + reply_off; |
| 774 | 790 | ||
| 775 | /* 2. Now we sprintf our header so that sprintf writes its terminating '\0' exactly one byte before content starts. Complete | 791 | /* 2. Now we write the header so that its terminating '\0' lands exactly one byte before the content starts. The bound is the |
| 776 | packet size is increased by size of header plus one byte '\n', we will copy over '\0' in next step */ | 792 | space step 1 reserved, so the '\0' falls where step 3 expects it. Complete packet size is increased by size of header plus |
| 777 | ws->reply_size += 1 + sprintf(ws->reply, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r", ws->reply_size); | 793 | one byte '\n', we will copy over '\0' in next step */ |
| 794 | ws->reply_size += 1 + snprintf(ws->reply, (size_t)(SUCCESS_HTTP_HEADER_LENGTH - reply_off), | ||
| 795 | "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r", ws->reply_size); | ||
| 778 | 796 | ||
| 779 | /* 3. Finally we join both blocks neatly */ | 797 | /* 3. Finally we join both blocks neatly */ |
| 780 | ws->outbuf[SUCCESS_HTTP_HEADER_LENGTH - 1] = '\n'; | 798 | ws->outbuf[SUCCESS_HTTP_HEADER_LENGTH - 1] = '\n'; |
| @@ -22,6 +22,7 @@ | |||
| 22 | #include "ot_iovec.h" | 22 | #include "ot_iovec.h" |
| 23 | #include "ot_ip.h" | 23 | #include "ot_ip.h" |
| 24 | #include "ot_mutex.h" | 24 | #include "ot_mutex.h" |
| 25 | #include "ot_fmt.h" | ||
| 25 | #include "ot_stats.h" | 26 | #include "ot_stats.h" |
| 26 | #include "trackerlogic.h" | 27 | #include "trackerlogic.h" |
| 27 | 28 | ||
| @@ -175,8 +176,8 @@ static size_t stats_get_highscore_networks(stats_network_node *node, int depth, | |||
| 175 | ++j; | 176 | ++j; |
| 176 | --j; | 177 | --j; |
| 177 | 178 | ||
| 178 | memcpy(scores, scores + 1, j * sizeof(*scores)); | 179 | memmove(scores, scores + 1, j * sizeof(*scores)); |
| 179 | memcpy(networks, networks + 1, j * sizeof(*networks)); | 180 | memmove(networks, networks + 1, j * sizeof(*networks)); |
| 180 | scores[j] = node_score; | 181 | scores[j] = node_score; |
| 181 | memcpy(networks + j, node_value, sizeof(*networks)); | 182 | memcpy(networks + j, node_value, sizeof(*networks)); |
| 182 | } | 183 | } |
| @@ -184,12 +185,11 @@ static size_t stats_get_highscore_networks(stats_network_node *node, int depth, | |||
| 184 | return score; | 185 | return score; |
| 185 | } | 186 | } |
| 186 | 187 | ||
| 187 | static size_t stats_return_busy_networks(char *reply, stats_network_node *tree, size_t amount, int limit) { | 188 | static void stats_return_busy_networks(char **reply, const char *reply_end, stats_network_node *tree, size_t amount, int limit) { |
| 188 | ot_ip6 networks[amount]; | 189 | ot_ip6 networks[amount]; |
| 189 | ot_ip6 node_value; | 190 | ot_ip6 node_value; |
| 190 | size_t scores[amount]; | 191 | size_t scores[amount]; |
| 191 | size_t i; | 192 | size_t i; |
| 192 | char *r = reply; | ||
| 193 | 193 | ||
| 194 | memset(scores, 0, sizeof(scores)); | 194 | memset(scores, 0, sizeof(scores)); |
| 195 | memset(networks, 0, sizeof(networks)); | 195 | memset(networks, 0, sizeof(networks)); |
| @@ -197,24 +197,21 @@ static size_t stats_return_busy_networks(char *reply, stats_network_node *tree, | |||
| 197 | 197 | ||
| 198 | stats_get_highscore_networks(tree, 0, node_value, scores, networks, amount, limit); | 198 | stats_get_highscore_networks(tree, 0, node_value, scores, networks, amount, limit); |
| 199 | 199 | ||
| 200 | r += sprintf(r, "Networks, limit /%d:\n", limit + STATS_NETWORK_NODE_BITWIDTH); | 200 | fmt_append(reply, reply_end, "Networks, limit /%d:\n", limit + STATS_NETWORK_NODE_BITWIDTH); |
| 201 | for (i = amount; i > 0; --i) { | 201 | for (i = amount; i > 0; --i) { |
| 202 | if (scores[i - 1]) { | 202 | if (scores[i - 1]) { |
| 203 | r += sprintf(r, "%08zd: ", scores[i - 1]); | 203 | char ip_readable[64]; |
| 204 | // #ifdef WANT_V6 | 204 | |
| 205 | r += ip_format(r, networks[i - 1]); | 205 | ip_readable[ip_format(ip_readable, networks[i - 1])] = 0; |
| 206 | *r++ = '\n'; | 206 | fmt_append(reply, reply_end, "%08zd: %s\n", scores[i - 1], ip_readable); |
| 207 | } | 207 | } |
| 208 | } | 208 | } |
| 209 | *r++ = '\n'; | 209 | fmt_append(reply, reply_end, "\n"); |
| 210 | |||
| 211 | return r - reply; | ||
| 212 | } | 210 | } |
| 213 | 211 | ||
| 214 | static size_t stats_slash24s_txt(char *reply, size_t amount) { | 212 | static void stats_slash24s_txt(char **reply, const char *reply_end, size_t amount) { |
| 215 | stats_network_node *slash24s_network_counters_root = NULL; | 213 | stats_network_node *slash24s_network_counters_root = NULL; |
| 216 | char *r = reply; | 214 | size_t i, bucket, peer_size = OT_PEER_SIZE4; |
| 217 | size_t i, bucket, peer_size = OT_PEER_SIZE4; | ||
| 218 | 215 | ||
| 219 | for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { | 216 | for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { |
| 220 | ot_vector *torrents_list = mutex_bucket_lock(bucket); | 217 | ot_vector *torrents_list = mutex_bucket_lock(bucket); |
| @@ -241,35 +238,28 @@ static size_t stats_slash24s_txt(char *reply, size_t amount) { | |||
| 241 | } | 238 | } |
| 242 | mutex_bucket_unlock(bucket, 0); | 239 | mutex_bucket_unlock(bucket, 0); |
| 243 | if (!g_opentracker_running) | 240 | if (!g_opentracker_running) |
| 244 | goto bailout_error; | 241 | goto done; |
| 245 | } | 242 | } |
| 246 | 243 | ||
| 247 | /* The tree is built. Now analyze */ | 244 | /* The tree is built. Now analyze */ |
| 248 | r += stats_return_busy_networks(r, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_MAXDEPTH); | 245 | stats_return_busy_networks(reply, reply_end, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_MAXDEPTH); |
| 249 | r += stats_return_busy_networks(r, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_LIMIT); | 246 | stats_return_busy_networks(reply, reply_end, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_LIMIT); |
| 250 | goto success; | 247 | goto done; |
| 251 | 248 | ||
| 252 | bailout_unlock: | 249 | bailout_unlock: |
| 253 | mutex_bucket_unlock(bucket, 0); | 250 | mutex_bucket_unlock(bucket, 0); |
| 254 | bailout_error: | 251 | done: |
| 255 | r = reply; | ||
| 256 | success: | ||
| 257 | stats_shift_down_network_count(&slash24s_network_counters_root, 0, sizeof(int) * 8 - 1); | 252 | stats_shift_down_network_count(&slash24s_network_counters_root, 0, sizeof(int) * 8 - 1); |
| 258 | |||
| 259 | return r - reply; | ||
| 260 | } | 253 | } |
| 261 | 254 | ||
| 262 | #ifdef WANT_SPOT_WOODPECKER | 255 | #ifdef WANT_SPOT_WOODPECKER |
| 263 | static stats_network_node *stats_woodpeckers_tree; | 256 | static stats_network_node *stats_woodpeckers_tree; |
| 264 | static pthread_mutex_t g_woodpeckers_mutex = PTHREAD_MUTEX_INITIALIZER; | 257 | static pthread_mutex_t g_woodpeckers_mutex = PTHREAD_MUTEX_INITIALIZER; |
| 265 | 258 | ||
| 266 | static size_t stats_return_woodpeckers(char *reply, int amount) { | 259 | static void stats_return_woodpeckers(char **reply, const char *reply_end, int amount) { |
| 267 | char *r = reply; | ||
| 268 | |||
| 269 | pthread_mutex_lock(&g_woodpeckers_mutex); | 260 | pthread_mutex_lock(&g_woodpeckers_mutex); |
| 270 | r += stats_return_busy_networks(r, stats_woodpeckers_tree, amount, STATS_NETWORK_NODE_MAXDEPTH); | 261 | stats_return_busy_networks(reply, reply_end, stats_woodpeckers_tree, amount, STATS_NETWORK_NODE_MAXDEPTH); |
| 271 | pthread_mutex_unlock(&g_woodpeckers_mutex); | 262 | pthread_mutex_unlock(&g_woodpeckers_mutex); |
| 272 | return r - reply; | ||
| 273 | } | 263 | } |
| 274 | #endif | 264 | #endif |
| 275 | 265 | ||
| @@ -309,10 +299,10 @@ static inline void stats_insert_into_highscore(size_t val, ot_record* list, size | |||
| 309 | 299 | ||
| 310 | 300 | ||
| 311 | /* Fetches stats from tracker */ | 301 | /* Fetches stats from tracker */ |
| 312 | size_t stats_top_txt(char *reply, size_t amount) { | 302 | void stats_top_txt(char **reply, const char *reply_end, size_t amount) { |
| 313 | size_t j, idx, bucket; | 303 | size_t j, idx, bucket; |
| 314 | ot_record top100s[100], top100c[100], top100l[100]; | 304 | ot_record top100s[100], top100c[100], top100l[100]; |
| 315 | char *r = reply, hex_out[42]; | 305 | char hex_out[42]; |
| 316 | 306 | ||
| 317 | if (amount > 100) | 307 | if (amount > 100) |
| 318 | amount = 100; | 308 | amount = 100; |
| @@ -323,7 +313,7 @@ size_t stats_top_txt(char *reply, size_t amount) { | |||
| 323 | 313 | ||
| 324 | /* Iterate over complete torrent list */ | 314 | /* Iterate over complete torrent list */ |
| 325 | for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { | 315 | for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { |
| 326 | ot_vector *torrents_list = mutex_bucket_lock(bucket); | 316 | ot_vector *torrents_list = mutex_bucket_lock(bucket); |
| 327 | for (j = 0; j < torrents_list->size; ++j) { | 317 | for (j = 0; j < torrents_list->size; ++j) { |
| 328 | ot_torrent *torrent = (ot_torrent *)(torrents_list->data) + j; | 318 | ot_torrent *torrent = (ot_torrent *)(torrents_list->data) + j; |
| 329 | size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; | 319 | size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; |
| @@ -336,114 +326,110 @@ size_t stats_top_txt(char *reply, size_t amount) { | |||
| 336 | } | 326 | } |
| 337 | mutex_bucket_unlock(bucket, 0); | 327 | mutex_bucket_unlock(bucket, 0); |
| 338 | if (!g_opentracker_running) | 328 | if (!g_opentracker_running) |
| 339 | return 0; | 329 | return; |
| 340 | } | 330 | } |
| 341 | 331 | ||
| 342 | r += sprintf(r, "Top %zd torrents by peers:\n", amount); | 332 | fmt_append(reply, reply_end, "Top %zd torrents by peers:\n", amount); |
| 343 | for (idx = 0; idx < amount; ++idx) | 333 | for (idx = 0; idx < amount; ++idx) |
| 344 | if (top100c[idx].val) | 334 | if (top100c[idx].val) |
| 345 | r += sprintf(r, "\t%zd\t%s\n", top100c[idx].val, hex_from_hash(hex_out, top100c[idx].hash)); | 335 | fmt_append(reply, reply_end, "\t%zd\t%s\n", top100c[idx].val, hex_from_hash(hex_out, top100c[idx].hash)); |
| 346 | r += sprintf(r, "Top %zd torrents by seeds:\n", amount); | 336 | fmt_append(reply, reply_end, "Top %zd torrents by seeds:\n", amount); |
| 347 | for (idx = 0; idx < amount; ++idx) | 337 | for (idx = 0; idx < amount; ++idx) |
| 348 | if (top100s[idx].val) | 338 | if (top100s[idx].val) |
| 349 | r += sprintf(r, "\t%zd\t%s\n", top100s[idx].val, hex_from_hash(hex_out, top100s[idx].hash)); | 339 | fmt_append(reply, reply_end, "\t%zd\t%s\n", top100s[idx].val, hex_from_hash(hex_out, top100s[idx].hash)); |
| 350 | r += sprintf(r, "Top %zd torrents by leechers:\n", amount); | 340 | fmt_append(reply, reply_end, "Top %zd torrents by leechers:\n", amount); |
| 351 | for (idx = 0; idx < amount; ++idx) | 341 | for (idx = 0; idx < amount; ++idx) |
| 352 | if (top100l[idx].val) | 342 | if (top100l[idx].val) |
| 353 | r += sprintf(r, "\t%zd\t%s\n", top100l[idx].val, hex_from_hash(hex_out, top100l[idx].hash)); | 343 | fmt_append(reply, reply_end, "\t%zd\t%s\n", top100l[idx].val, hex_from_hash(hex_out, top100l[idx].hash)); |
| 354 | |||
| 355 | return r - reply; | ||
| 356 | } | 344 | } |
| 357 | 345 | ||
| 358 | static unsigned long events_per_time(unsigned long long events, time_t t) { return events / ((unsigned int)t ? (unsigned int)t : 1); } | 346 | static unsigned long events_per_time(unsigned long long events, time_t t) { return events / ((unsigned int)t ? (unsigned int)t : 1); } |
| 359 | 347 | ||
| 360 | static size_t stats_connections_mrtg(char *reply) { | 348 | static void stats_connections_mrtg(char **reply, const char *reply_end) { |
| 361 | ot_time t = time(NULL) - ot_start_time; | 349 | ot_time t = time(NULL) - ot_start_time; |
| 362 | return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.", | 350 | fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.", |
| 363 | ot_overall_tcp_connections + ot_overall_udp_connections, | 351 | ot_overall_tcp_connections + ot_overall_udp_connections, |
| 364 | ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, (int)t, (int)(t / 3600), | 352 | ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, (int)t, (int)(t / 3600), |
| 365 | events_per_time(ot_overall_tcp_connections + ot_overall_udp_connections, t), | 353 | events_per_time(ot_overall_tcp_connections + ot_overall_udp_connections, t), |
| 366 | events_per_time(ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, t)); | 354 | events_per_time(ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, t)); |
| 367 | } | 355 | } |
| 368 | 356 | ||
| 369 | static size_t stats_udpconnections_mrtg(char *reply) { | 357 | static void stats_udpconnections_mrtg(char **reply, const char *reply_end) { |
| 370 | ot_time t = time(NULL) - ot_start_time; | 358 | ot_time t = time(NULL) - ot_start_time; |
| 371 | return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker udp4 stats, %lu conns/s :: %lu success/s.", ot_overall_udp_connections, | 359 | fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker udp4 stats, %lu conns/s :: %lu success/s.", ot_overall_udp_connections, |
| 372 | ot_overall_udp_successfulannounces + ot_overall_udp_connects, (int)t, (int)(t / 3600), events_per_time(ot_overall_udp_connections, t), | 360 | ot_overall_udp_successfulannounces + ot_overall_udp_connects, (int)t, (int)(t / 3600), events_per_time(ot_overall_udp_connections, t), |
| 373 | events_per_time(ot_overall_udp_successfulannounces + ot_overall_udp_connects, t)); | 361 | events_per_time(ot_overall_udp_successfulannounces + ot_overall_udp_connects, t)); |
| 374 | } | 362 | } |
| 375 | 363 | ||
| 376 | static size_t stats_tcpconnections_mrtg(char *reply) { | 364 | static void stats_tcpconnections_mrtg(char **reply, const char *reply_end) { |
| 377 | time_t t = time(NULL) - ot_start_time; | 365 | time_t t = time(NULL) - ot_start_time; |
| 378 | return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker tcp4 stats, %lu conns/s :: %lu success/s.", ot_overall_tcp_connections, | 366 | fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker tcp4 stats, %lu conns/s :: %lu success/s.", ot_overall_tcp_connections, |
| 379 | ot_overall_tcp_successfulannounces, (int)t, (int)(t / 3600), events_per_time(ot_overall_tcp_connections, t), | 367 | ot_overall_tcp_successfulannounces, (int)t, (int)(t / 3600), events_per_time(ot_overall_tcp_connections, t), |
| 380 | events_per_time(ot_overall_tcp_successfulannounces, t)); | 368 | events_per_time(ot_overall_tcp_successfulannounces, t)); |
| 381 | } | 369 | } |
| 382 | 370 | ||
| 383 | static size_t stats_scrape_mrtg(char *reply) { | 371 | static void stats_scrape_mrtg(char **reply, const char *reply_end) { |
| 384 | time_t t = time(NULL) - ot_start_time; | 372 | time_t t = time(NULL) - ot_start_time; |
| 385 | return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker scrape stats, %lu scrape/s (tcp and udp)", ot_overall_tcp_successfulscrapes, | 373 | fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker scrape stats, %lu scrape/s (tcp and udp)", |
| 386 | ot_overall_udp_successfulscrapes, (int)t, (int)(t / 3600), | 374 | ot_overall_tcp_successfulscrapes, ot_overall_udp_successfulscrapes, (int)t, (int)(t / 3600), |
| 387 | events_per_time((ot_overall_tcp_successfulscrapes + ot_overall_udp_successfulscrapes), t)); | 375 | events_per_time((ot_overall_tcp_successfulscrapes + ot_overall_udp_successfulscrapes), t)); |
| 388 | } | 376 | } |
| 389 | 377 | ||
| 390 | static size_t stats_fullscrapes_mrtg(char *reply) { | 378 | static void stats_fullscrapes_mrtg(char **reply, const char *reply_end) { |
| 391 | ot_time t = time(NULL) - ot_start_time; | 379 | ot_time t = time(NULL) - ot_start_time; |
| 392 | return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker full scrape stats, %lu conns/s :: %lu bytes/s.", ot_full_scrape_count * 1000, | 380 | fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker full scrape stats, %lu conns/s :: %lu bytes/s.", |
| 393 | ot_full_scrape_size, (int)t, (int)(t / 3600), events_per_time(ot_full_scrape_count, t), events_per_time(ot_full_scrape_size, t)); | 381 | ot_full_scrape_count * 1000, ot_full_scrape_size, (int)t, (int)(t / 3600), events_per_time(ot_full_scrape_count, t), |
| 382 | events_per_time(ot_full_scrape_size, t)); | ||
| 394 | } | 383 | } |
| 395 | 384 | ||
| 396 | static size_t stats_peers_mrtg(char *reply) { | 385 | static void stats_peers_mrtg(char **reply, const char *reply_end) { |
| 397 | torrent_stats stats = {0, 0, 0}; | 386 | torrent_stats stats = {0, 0, 0}; |
| 398 | 387 | ||
| 399 | iterate_all_torrents(torrent_statter, (uintptr_t)&stats); | 388 | iterate_all_torrents(torrent_statter, (uintptr_t)&stats); |
| 400 | 389 | ||
| 401 | return sprintf(reply, "%llu\n%llu\nopentracker serving %llu torrents\nopentracker", stats.peer_count, stats.seed_count, stats.torrent_count); | 390 | fmt_append(reply, reply_end, "%llu\n%llu\nopentracker serving %llu torrents\nopentracker", stats.peer_count, stats.seed_count, |
| 391 | stats.torrent_count); | ||
| 402 | } | 392 | } |
| 403 | 393 | ||
| 404 | static size_t stats_torrents_mrtg(char *reply) { | 394 | static void stats_torrents_mrtg(char **reply, const char *reply_end) { |
| 405 | size_t torrent_count = mutex_get_torrent_count(); | 395 | size_t torrent_count = mutex_get_torrent_count(); |
| 406 | 396 | ||
| 407 | return sprintf(reply, "%zd\n%zd\nopentracker serving %zd torrents\nopentracker", torrent_count, (size_t)0, torrent_count); | 397 | fmt_append(reply, reply_end, "%zd\n%zd\nopentracker serving %zd torrents\nopentracker", torrent_count, (size_t)0, torrent_count); |
| 408 | } | 398 | } |
| 409 | 399 | ||
| 410 | static size_t stats_httperrors_txt(char *reply) { | 400 | static void stats_httperrors_txt(char **reply, const char *reply_end) { |
| 411 | return sprintf(reply, "302 RED %llu\n400 ... %llu\n400 PAR %llu\n400 COM %llu\n403 IP %llu\n404 INV %llu\n500 SRV %llu\n", ot_failed_request_counts[0], | 401 | 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", |
| 412 | ot_failed_request_counts[1], ot_failed_request_counts[2], ot_failed_request_counts[3], ot_failed_request_counts[4], | 402 | ot_failed_request_counts[0], ot_failed_request_counts[1], ot_failed_request_counts[2], ot_failed_request_counts[3], |
| 413 | ot_failed_request_counts[5], ot_failed_request_counts[6]); | 403 | ot_failed_request_counts[4], ot_failed_request_counts[5], ot_failed_request_counts[6]); |
| 414 | } | 404 | } |
| 415 | 405 | ||
| 416 | static size_t stats_return_renew_bucket(char *reply) { | 406 | static void stats_return_renew_bucket(char **reply, const char *reply_end) { |
| 417 | char *r = reply; | 407 | int i; |
| 418 | int i; | ||
| 419 | 408 | ||
| 420 | for (i = 0; i < OT_PEER_TIMEOUT; ++i) | 409 | for (i = 0; i < OT_PEER_TIMEOUT; ++i) |
| 421 | r += sprintf(r, "%02i %llu\n", i, ot_renewed[i]); | 410 | fmt_append(reply, reply_end, "%02i %llu\n", i, ot_renewed[i]); |
| 422 | return r - reply; | ||
| 423 | } | 411 | } |
| 424 | 412 | ||
| 425 | static size_t stats_return_sync_mrtg(char *reply) { | 413 | static void stats_return_sync_mrtg(char **reply, const char *reply_end) { |
| 426 | ot_time t = time(NULL) - ot_start_time; | 414 | ot_time t = time(NULL) - ot_start_time; |
| 427 | return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.", ot_overall_sync_count, 0LL, (int)t, | 415 | fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.", ot_overall_sync_count, 0LL, |
| 428 | (int)(t / 3600), events_per_time(ot_overall_tcp_connections + ot_overall_udp_connections, t), | 416 | (int)t, (int)(t / 3600), events_per_time(ot_overall_tcp_connections + ot_overall_udp_connections, t), |
| 429 | events_per_time(ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, t)); | 417 | events_per_time(ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, t)); |
| 430 | } | 418 | } |
| 431 | 419 | ||
| 432 | static size_t stats_return_completed_mrtg(char *reply) { | 420 | static void stats_return_completed_mrtg(char **reply, const char *reply_end) { |
| 433 | ot_time t = time(NULL) - ot_start_time; | 421 | ot_time t = time(NULL) - ot_start_time; |
| 434 | 422 | ||
| 435 | return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker, %lu completed/h.", ot_overall_completed, 0LL, (int)t, (int)(t / 3600), | 423 | fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker, %lu completed/h.", ot_overall_completed, 0LL, (int)t, (int)(t / 3600), |
| 436 | events_per_time(ot_overall_completed, t / 3600)); | 424 | events_per_time(ot_overall_completed, t / 3600)); |
| 437 | } | 425 | } |
| 438 | 426 | ||
| 439 | #ifdef WANT_LOG_NUMWANT | 427 | #ifdef WANT_LOG_NUMWANT |
| 440 | extern unsigned long long numwants[201]; | 428 | extern unsigned long long numwants[201]; |
| 441 | static size_t stats_return_numwants(char *reply) { | 429 | static void stats_return_numwants(char **reply, const char *reply_end) { |
| 442 | char *r = reply; | 430 | int i; |
| 443 | int i; | ||
| 444 | for (i = 0; i <= 200; ++i) | 431 | for (i = 0; i <= 200; ++i) |
| 445 | r += sprintf(r, "%03d => %lld\n", i, numwants[i]); | 432 | fmt_append(reply, reply_end, "%03d => %lld\n", i, numwants[i]); |
| 446 | return r - reply; | ||
| 447 | } | 433 | } |
| 448 | #endif | 434 | #endif |
| 449 | 435 | ||
| @@ -461,17 +447,14 @@ static void stats_return_fulllog(size_t *iovec_entries, struct iovec **iovector, | |||
| 461 | return; | 447 | return; |
| 462 | re = r + 32 * OT_STATS_TMPSIZE; | 448 | re = r + 32 * OT_STATS_TMPSIZE; |
| 463 | } | 449 | } |
| 464 | r += sprintf(r, "%08ld: ", loglist->time); | 450 | char ip_readable[64]; |
| 465 | r += ip_format(r, loglist->ip); | 451 | |
| 466 | *r++ = '\n'; | 452 | ip_readable[ip_format(ip_readable, loglist->ip)] = 0; |
| 467 | memcpy(r, loglist->data, loglist->size); | 453 | fmt_append(&r, re, "%08ld: %s\n", loglist->time, ip_readable); |
| 468 | r += loglist->size; | 454 | fmt_bytes(&r, re, loglist->data, loglist->size); |
| 469 | *r++ = '\n'; | 455 | fmt_append(&r, re, "\n*\n\n"); |
| 470 | *r++ = '*'; | ||
| 471 | *r++ = '\n'; | ||
| 472 | *r++ = '\n'; | ||
| 473 | 456 | ||
| 474 | llnext = loglist->next; | 457 | llnext = loglist->next; |
| 475 | free(loglist->data); | 458 | free(loglist->data); |
| 476 | free(loglist); | 459 | free(loglist); |
| 477 | loglist = llnext; | 460 | loglist = llnext; |
| @@ -480,124 +463,137 @@ static void stats_return_fulllog(size_t *iovec_entries, struct iovec **iovector, | |||
| 480 | } | 463 | } |
| 481 | #endif | 464 | #endif |
| 482 | 465 | ||
| 483 | static size_t stats_return_everything(char *reply) { | 466 | static void stats_return_everything(char **reply, const char *reply_end) { |
| 484 | torrent_stats stats = {0, 0, 0}; | 467 | torrent_stats stats = {0, 0, 0}; |
| 485 | int i; | 468 | int i; |
| 486 | char *r = reply; | ||
| 487 | 469 | ||
| 488 | iterate_all_torrents(torrent_statter, (uintptr_t)&stats); | 470 | iterate_all_torrents(torrent_statter, (uintptr_t)&stats); |
| 489 | 471 | ||
| 490 | r += sprintf(r, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); | 472 | fmt_append(reply, reply_end, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); |
| 491 | r += sprintf(r, "<stats>\n"); | 473 | fmt_append(reply, reply_end, "<stats>\n"); |
| 492 | r += sprintf(r, " <tracker_id>%" PRIu32 "</tracker_id>\n", g_tracker_id); | 474 | fmt_append(reply, reply_end, " <tracker_id>%" PRIu32 "</tracker_id>\n", g_tracker_id); |
| 493 | r += sprintf(r, " <version>\n"); | 475 | fmt_append(reply, reply_end, " <version>\n"); |
| 494 | r += stats_return_tracker_version(r); | 476 | stats_return_tracker_version(reply, reply_end); |
| 495 | r += sprintf(r, " </version>\n"); | 477 | fmt_append(reply, reply_end, " </version>\n"); |
| 496 | r += sprintf(r, " <uptime>%llu</uptime>\n", (unsigned long long)(time(NULL) - ot_start_time)); | 478 | fmt_append(reply, reply_end, " <uptime>%llu</uptime>\n", (unsigned long long)(time(NULL) - ot_start_time)); |
| 497 | r += sprintf(r, " <torrents>\n"); | 479 | fmt_append(reply, reply_end, " <torrents>\n"); |
| 498 | r += sprintf(r, " <count_mutex>%zd</count_mutex>\n", mutex_get_torrent_count()); | 480 | fmt_append(reply, reply_end, " <count_mutex>%zd</count_mutex>\n", mutex_get_torrent_count()); |
| 499 | r += sprintf(r, " <count_iterator>%llu</count_iterator>\n", stats.torrent_count); | 481 | fmt_append(reply, reply_end, " <count_iterator>%llu</count_iterator>\n", stats.torrent_count); |
| 500 | r += sprintf(r, " </torrents>\n"); | 482 | fmt_append(reply, reply_end, " </torrents>\n"); |
| 501 | r += sprintf(r, " <peers>\n <count>%llu</count>\n </peers>\n", stats.peer_count); | 483 | fmt_append(reply, reply_end, " <peers>\n <count>%llu</count>\n </peers>\n", stats.peer_count); |
| 502 | r += sprintf(r, " <seeds>\n <count>%llu</count>\n </seeds>\n", stats.seed_count); | 484 | fmt_append(reply, reply_end, " <seeds>\n <count>%llu</count>\n </seeds>\n", stats.seed_count); |
| 503 | r += sprintf(r, " <completed>\n <count>%llu</count>\n </completed>\n", ot_overall_completed); | 485 | fmt_append(reply, reply_end, " <completed>\n <count>%llu</count>\n </completed>\n", ot_overall_completed); |
| 504 | r += sprintf(r, " <connections>\n"); | 486 | fmt_append(reply, reply_end, " <connections>\n"); |
| 505 | r += sprintf(r, " <tcp>\n <accept>%llu</accept>\n <announce>%llu</announce>\n <scrape>%llu</scrape>\n </tcp>\n", | 487 | fmt_append(reply, reply_end, " <tcp>\n <accept>%llu</accept>\n <announce>%llu</announce>\n <scrape>%llu</scrape>\n </tcp>\n", |
| 506 | ot_overall_tcp_connections, ot_overall_tcp_successfulannounces, ot_overall_tcp_successfulscrapes); | 488 | ot_overall_tcp_connections, ot_overall_tcp_successfulannounces, ot_overall_tcp_successfulscrapes); |
| 507 | r += sprintf(r, " <udp>\n <overall>%llu</overall>\n <connect>%llu</connect>\n <announce>%llu</announce>\n <scrape>%llu</scrape>\n <missmatch>%llu</missmatch>\n </udp>\n", | 489 | 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", |
| 508 | ot_overall_udp_connections, ot_overall_udp_connects, ot_overall_udp_successfulannounces, ot_overall_udp_successfulscrapes, | 490 | ot_overall_udp_connections, ot_overall_udp_connects, ot_overall_udp_successfulannounces, ot_overall_udp_successfulscrapes, |
| 509 | ot_overall_udp_connectionidmissmatches); | 491 | ot_overall_udp_connectionidmissmatches); |
| 510 | r += sprintf(r, " <livesync>\n <count>%llu</count>\n </livesync>\n", ot_overall_sync_count); | 492 | fmt_append(reply, reply_end, " <livesync>\n <count>%llu</count>\n </livesync>\n", ot_overall_sync_count); |
| 511 | r += sprintf(r, " </connections>\n"); | 493 | fmt_append(reply, reply_end, " </connections>\n"); |
| 512 | r += sprintf(r, " <debug>\n"); | 494 | fmt_append(reply, reply_end, " <debug>\n"); |
| 513 | r += sprintf(r, " <renew>\n"); | 495 | fmt_append(reply, reply_end, " <renew>\n"); |
| 514 | for (i = 0; i < OT_PEER_TIMEOUT; ++i) | 496 | for (i = 0; i < OT_PEER_TIMEOUT; ++i) |
| 515 | r += sprintf(r, " <count interval=\"%02i\">%llu</count>\n", i, ot_renewed[i]); | 497 | fmt_append(reply, reply_end, " <count interval=\"%02i\">%llu</count>\n", i, ot_renewed[i]); |
| 516 | r += sprintf(r, " </renew>\n"); | 498 | fmt_append(reply, reply_end, " </renew>\n"); |
| 517 | r += sprintf(r, " <http_error>\n"); | 499 | fmt_append(reply, reply_end, " <http_error>\n"); |
| 518 | for (i = 0; i < CODE_HTTPERROR_COUNT; ++i) | 500 | for (i = 0; i < CODE_HTTPERROR_COUNT; ++i) |
| 519 | r += sprintf(r, " <count code=\"%s\">%llu</count>\n", ot_failed_request_names[i], ot_failed_request_counts[i]); | 501 | fmt_append(reply, reply_end, " <count code=\"%s\">%llu</count>\n", ot_failed_request_names[i], ot_failed_request_counts[i]); |
| 520 | r += sprintf(r, " </http_error>\n"); | 502 | fmt_append(reply, reply_end, " </http_error>\n"); |
| 521 | r += sprintf(r, " <mutex_stall>\n <count>%llu</count>\n </mutex_stall>\n", ot_overall_stall_count); | 503 | fmt_append(reply, reply_end, " <mutex_stall>\n <count>%llu</count>\n </mutex_stall>\n", ot_overall_stall_count); |
| 522 | r += sprintf(r, " </debug>\n"); | 504 | fmt_append(reply, reply_end, " </debug>\n"); |
| 523 | r += sprintf(r, "</stats>"); | 505 | fmt_append(reply, reply_end, "</stats>"); |
| 524 | return r - reply; | ||
| 525 | } | 506 | } |
| 526 | 507 | ||
| 527 | size_t stats_return_tracker_version(char *reply) { | 508 | void stats_return_tracker_version(char **reply, const char *reply_end) { |
| 528 | #define QUOTE(name) #name | 509 | #define QUOTE(name) #name |
| 529 | #define SQUOTE(name) QUOTE(name) | 510 | #define SQUOTE(name) QUOTE(name) |
| 530 | return sprintf(reply, "https://erdgeist.org/gitweb/opentracker/commit/?id=" SQUOTE(GIT_VERSION) "\n"); | 511 | fmt_append(reply, reply_end, "https://erdgeist.org/gitweb/opentracker/commit/?id=" SQUOTE(GIT_VERSION) "\n"); |
| 531 | } | 512 | } |
| 532 | 513 | ||
| 533 | size_t return_stats_for_tracker(char *reply, int mode, int format) { | 514 | void return_stats_for_tracker(char **reply, const char *reply_end, int mode, int format) { |
| 534 | (void)format; | 515 | (void)format; |
| 535 | switch (mode & TASK_TASK_MASK) { | 516 | switch (mode & TASK_TASK_MASK) { |
| 536 | case TASK_STATS_CONNS: | 517 | case TASK_STATS_CONNS: |
| 537 | return stats_connections_mrtg(reply); | 518 | stats_connections_mrtg(reply, reply_end); |
| 519 | break; | ||
| 538 | case TASK_STATS_SCRAPE: | 520 | case TASK_STATS_SCRAPE: |
| 539 | return stats_scrape_mrtg(reply); | 521 | stats_scrape_mrtg(reply, reply_end); |
| 522 | break; | ||
| 540 | case TASK_STATS_UDP: | 523 | case TASK_STATS_UDP: |
| 541 | return stats_udpconnections_mrtg(reply); | 524 | stats_udpconnections_mrtg(reply, reply_end); |
| 525 | break; | ||
| 542 | case TASK_STATS_TCP: | 526 | case TASK_STATS_TCP: |
| 543 | return stats_tcpconnections_mrtg(reply); | 527 | stats_tcpconnections_mrtg(reply, reply_end); |
| 528 | break; | ||
| 544 | case TASK_STATS_FULLSCRAPE: | 529 | case TASK_STATS_FULLSCRAPE: |
| 545 | return stats_fullscrapes_mrtg(reply); | 530 | stats_fullscrapes_mrtg(reply, reply_end); |
| 531 | break; | ||
| 546 | case TASK_STATS_COMPLETED: | 532 | case TASK_STATS_COMPLETED: |
| 547 | return stats_return_completed_mrtg(reply); | 533 | stats_return_completed_mrtg(reply, reply_end); |
| 534 | break; | ||
| 548 | case TASK_STATS_HTTPERRORS: | 535 | case TASK_STATS_HTTPERRORS: |
| 549 | return stats_httperrors_txt(reply); | 536 | stats_httperrors_txt(reply, reply_end); |
| 537 | break; | ||
| 550 | case TASK_STATS_VERSION: | 538 | case TASK_STATS_VERSION: |
| 551 | return stats_return_tracker_version(reply); | 539 | stats_return_tracker_version(reply, reply_end); |
| 540 | break; | ||
| 552 | case TASK_STATS_RENEW: | 541 | case TASK_STATS_RENEW: |
| 553 | return stats_return_renew_bucket(reply); | 542 | stats_return_renew_bucket(reply, reply_end); |
| 543 | break; | ||
| 554 | case TASK_STATS_SYNCS: | 544 | case TASK_STATS_SYNCS: |
| 555 | return stats_return_sync_mrtg(reply); | 545 | stats_return_sync_mrtg(reply, reply_end); |
| 546 | break; | ||
| 556 | #ifdef WANT_LOG_NUMWANT | 547 | #ifdef WANT_LOG_NUMWANT |
| 557 | case TASK_STATS_NUMWANTS: | 548 | case TASK_STATS_NUMWANTS: |
| 558 | return stats_return_numwants(reply); | 549 | stats_return_numwants(reply, reply_end); |
| 550 | break; | ||
| 559 | #endif | 551 | #endif |
| 560 | default: | 552 | default: |
| 561 | return 0; | 553 | break; |
| 562 | } | 554 | } |
| 563 | } | 555 | } |
| 564 | 556 | ||
| 565 | static void stats_make(size_t *iovec_entries, struct iovec **iovector, ot_tasktype mode) { | 557 | static void stats_make(size_t *iovec_entries, struct iovec **iovector, ot_tasktype mode) { |
| 566 | char *r; | 558 | char *r; |
| 559 | const char *r_end; | ||
| 567 | 560 | ||
| 568 | *iovec_entries = 0; | 561 | *iovec_entries = 0; |
| 569 | *iovector = NULL; | 562 | *iovector = NULL; |
| 570 | if (!(r = iovec_new_chunk(iovec_entries, iovector, OT_STATS_TMPSIZE))) | 563 | if (!(r = iovec_new_chunk(iovec_entries, iovector, OT_STATS_TMPSIZE))) |
| 571 | return; | 564 | return; |
| 565 | r_end = r + OT_STATS_TMPSIZE; | ||
| 572 | 566 | ||
| 573 | switch (mode & TASK_TASK_MASK) { | 567 | switch (mode & TASK_TASK_MASK) { |
| 574 | case TASK_STATS_TORRENTS: | 568 | case TASK_STATS_TORRENTS: |
| 575 | r += stats_torrents_mrtg(r); | 569 | stats_torrents_mrtg(&r, r_end); |
| 576 | break; | 570 | break; |
| 577 | case TASK_STATS_PEERS: | 571 | case TASK_STATS_PEERS: |
| 578 | r += stats_peers_mrtg(r); | 572 | stats_peers_mrtg(&r, r_end); |
| 579 | break; | 573 | break; |
| 580 | case TASK_STATS_SLASH24S: | 574 | case TASK_STATS_SLASH24S: |
| 581 | r += stats_slash24s_txt(r, 128); | 575 | stats_slash24s_txt(&r, r_end, 128); |
| 582 | break; | 576 | break; |
| 583 | case TASK_STATS_TOP10: | 577 | case TASK_STATS_TOP10: |
| 584 | r += stats_top_txt(r, 10); | 578 | stats_top_txt(&r, r_end, 10); |
| 585 | break; | 579 | break; |
| 586 | case TASK_STATS_TOP100: | 580 | case TASK_STATS_TOP100: |
| 587 | r = iovec_next_chunk(iovec_entries, iovector, r, 4 * OT_STATS_TMPSIZE); | 581 | r = iovec_next_chunk(iovec_entries, iovector, r, 4 * OT_STATS_TMPSIZE); |
| 588 | if (!r) | 582 | if (!r) |
| 589 | return; | 583 | return; |
| 590 | r += stats_top_txt(r, 100); | 584 | r_end = r + 4 * OT_STATS_TMPSIZE; |
| 585 | stats_top_txt(&r, r_end, 100); | ||
| 591 | break; | 586 | break; |
| 592 | case TASK_STATS_EVERYTHING: | 587 | case TASK_STATS_EVERYTHING: |
| 593 | r = iovec_next_chunk(iovec_entries, iovector, r, OT_STATS_TMPSIZE + 64 * OT_PEER_TIMEOUT); | 588 | r = iovec_next_chunk(iovec_entries, iovector, r, OT_STATS_TMPSIZE + 64 * OT_PEER_TIMEOUT); |
| 594 | if (!r) | 589 | if (!r) |
| 595 | return; | 590 | return; |
| 596 | r += stats_return_everything(r); | 591 | r_end = r + OT_STATS_TMPSIZE + 64 * OT_PEER_TIMEOUT; |
| 592 | stats_return_everything(&r, r_end); | ||
| 597 | break; | 593 | break; |
| 598 | #ifdef WANT_SPOT_WOODPECKER | 594 | #ifdef WANT_SPOT_WOODPECKER |
| 599 | case TASK_STATS_WOODPECKERS: | 595 | case TASK_STATS_WOODPECKERS: |
| 600 | r += stats_return_woodpeckers(r, 128); | 596 | stats_return_woodpeckers(&r, r_end, 128); |
| 601 | break; | 597 | break; |
| 602 | #endif | 598 | #endif |
| 603 | #ifdef WANT_FULLLOG_NETWORKS | 599 | #ifdef WANT_FULLLOG_NETWORKS |
| @@ -44,8 +44,8 @@ enum { | |||
| 44 | void stats_issue_event(ot_status_event event, PROTO_FLAG proto, uintptr_t event_data); | 44 | void stats_issue_event(ot_status_event event, PROTO_FLAG proto, uintptr_t event_data); |
| 45 | void stats_deliver(int64_t sock, int tasktype); | 45 | void stats_deliver(int64_t sock, int tasktype); |
| 46 | void stats_cleanup(void); | 46 | void stats_cleanup(void); |
| 47 | size_t return_stats_for_tracker(char *reply, int mode, int format); | 47 | void return_stats_for_tracker(char **reply, const char *reply_end, int mode, int format); |
| 48 | size_t stats_return_tracker_version(char *reply); | 48 | void stats_return_tracker_version(char **reply, const char *reply_end); |
| 49 | void stats_init(void); | 49 | void stats_init(void); |
| 50 | void stats_deinit(void); | 50 | void stats_deinit(void); |
| 51 | 51 | ||
| @@ -11,6 +11,7 @@ | |||
| 11 | #include <sys/uio.h> | 11 | #include <sys/uio.h> |
| 12 | 12 | ||
| 13 | /* Opentracker */ | 13 | /* Opentracker */ |
| 14 | #include "ot_fmt.h" | ||
| 14 | #include "ot_iovec.h" | 15 | #include "ot_iovec.h" |
| 15 | #include "ot_mutex.h" | 16 | #include "ot_mutex.h" |
| 16 | #include "ot_scan.h" | 17 | #include "ot_scan.h" |
| @@ -109,14 +110,10 @@ static void sync_make(size_t *iovec_entries, struct iovec **iovector) { | |||
| 109 | re = r + OT_SYNC_CHUNK_SIZE; | 110 | re = r + OT_SYNC_CHUNK_SIZE; |
| 110 | } | 111 | } |
| 111 | 112 | ||
| 112 | *r++ = '2'; | 113 | fmt_append(&r, re, "20:"); |
| 113 | *r++ = '0'; | 114 | fmt_bytes(&r, re, hash, sizeof(ot_hash)); |
| 114 | *r++ = ':'; | 115 | fmt_append(&r, re, "%zd:", byte_count); |
| 115 | memmove(r, hash, sizeof(ot_hash)); | 116 | fmt_bytes(&r, re, peer_list->changeset.data, byte_count); |
| 116 | r += sizeof(ot_hash); | ||
| 117 | r += sprintf(r, "%zd:", byte_count); | ||
| 118 | memmove(r, peer_list->changeset.data, byte_count); | ||
| 119 | r += byte_count; | ||
| 120 | } | 117 | } |
| 121 | 118 | ||
| 122 | /* All torrents done: release lock on currenct bucket */ | 119 | /* All torrents done: release lock on currenct bucket */ |
diff --git a/ot_vector.c b/ot_vector.c index 924d468..a56d55b 100644 --- a/ot_vector.c +++ b/ot_vector.c | |||
| @@ -57,34 +57,39 @@ static uint8_t vector_hash_peer(ot_peer const *peer, size_t compare_size, size_t | |||
| 57 | */ | 57 | */ |
| 58 | void *vector_find_or_insert(ot_vector *vector, void *key, size_t member_size, size_t compare_size, int *exactmatch) { | 58 | void *vector_find_or_insert(ot_vector *vector, void *key, size_t member_size, size_t compare_size, int *exactmatch) { |
| 59 | uint8_t *match = vector_binary_search(key, vector->data, vector->size, member_size, compare_size, exactmatch); | 59 | uint8_t *match = vector_binary_search(key, vector->data, vector->size, member_size, compare_size, exactmatch); |
| 60 | size_t match_offset, bytes_after; | ||
| 60 | 61 | ||
| 61 | if (*exactmatch) | 62 | if (*exactmatch) |
| 62 | return match; | 63 | return match; |
| 63 | 64 | ||
| 65 | /* Hold the insertion point as an offset rather than a pointer: it survives | ||
| 66 | the realloc below, and an empty vector has no data pointer to offset from | ||
| 67 | in the first place */ | ||
| 68 | match_offset = vector->size ? (size_t)(match - (uint8_t *)vector->data) : 0; | ||
| 69 | bytes_after = member_size * vector->size - match_offset; | ||
| 70 | |||
| 64 | if (vector->size + 1 > vector->space) { | 71 | if (vector->size + 1 > vector->space) { |
| 65 | size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS; | 72 | size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS; |
| 66 | ptrdiff_t match_off = match - (uint8_t *)vector->data; | 73 | uint8_t *new_data = realloc(vector->data, new_space * member_size); |
| 67 | uint8_t *new_data = realloc(vector->data, new_space * member_size); | ||
| 68 | 74 | ||
| 69 | if (!new_data) | 75 | if (!new_data) |
| 70 | return NULL; | 76 | return NULL; |
| 71 | 77 | ||
| 72 | /* Adjust pointer if it moved by realloc */ | ||
| 73 | match = new_data + match_off; | ||
| 74 | |||
| 75 | vector->data = new_data; | 78 | vector->data = new_data; |
| 76 | vector->space = new_space; | 79 | vector->space = new_space; |
| 77 | } | 80 | } |
| 78 | memmove(match + member_size, match, ((uint8_t *)vector->data) + member_size * vector->size - match); | 81 | |
| 82 | match = (uint8_t *)vector->data + match_offset; | ||
| 83 | memmove(match + member_size, match, bytes_after); | ||
| 79 | 84 | ||
| 80 | vector->size++; | 85 | vector->size++; |
| 81 | return match; | 86 | return match; |
| 82 | } | 87 | } |
| 83 | 88 | ||
| 84 | ot_peer *vector_find_or_insert_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size, int *exactmatch) { | 89 | ot_peer *vector_find_or_insert_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size, int *exactmatch) { |
| 85 | ot_peer *match, *end; | 90 | ot_peer *match; |
| 86 | const size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size); | 91 | const size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size); |
| 87 | size_t match_to_end; | 92 | size_t match_offset, bytes_after; |
| 88 | 93 | ||
| 89 | /* If space is zero but size is set, we're dealing with a list of vector->size buckets */ | 94 | /* If space is zero but size is set, we're dealing with a list of vector->size buckets */ |
| 90 | if (vector->space < vector->size) | 95 | if (vector->space < vector->size) |
| @@ -94,26 +99,26 @@ ot_peer *vector_find_or_insert_peer(ot_vector *vector, ot_peer const *peer, size | |||
| 94 | if (*exactmatch) | 99 | if (*exactmatch) |
| 95 | return match; | 100 | return match; |
| 96 | 101 | ||
| 97 | /* This is the amount of bytes that needs to be pushed backwards by peer_size bytes to make room for new peer */ | 102 | /* Hold the insertion point as an offset rather than a pointer: it survives |
| 98 | end = (ot_peer *)vector->data + vector->size * peer_size; | 103 | the realloc below, and an empty vector has no data pointer to offset from |
| 99 | match_to_end = end - match; | 104 | in the first place. bytes_after is what has to move up by peer_size to |
| 105 | make room for the new peer */ | ||
| 106 | match_offset = vector->size ? (size_t)(match - (ot_peer *)vector->data) : 0; | ||
| 107 | bytes_after = vector->size * peer_size - match_offset; | ||
| 100 | 108 | ||
| 101 | if (vector->size + 1 > vector->space) { | 109 | if (vector->size + 1 > vector->space) { |
| 102 | ptrdiff_t offset = match - (ot_peer *)vector->data; | 110 | size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS; |
| 103 | size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS; | 111 | ot_peer *new_data = realloc(vector->data, new_space * peer_size); |
| 104 | ot_peer *new_data = realloc(vector->data, new_space * peer_size); | ||
| 105 | 112 | ||
| 106 | if (!new_data) | 113 | if (!new_data) |
| 107 | return NULL; | 114 | return NULL; |
| 108 | /* Adjust pointer if it moved by realloc */ | ||
| 109 | match = new_data + offset; | ||
| 110 | 115 | ||
| 111 | vector->data = new_data; | 116 | vector->data = new_data; |
| 112 | vector->space = new_space; | 117 | vector->space = new_space; |
| 113 | } | 118 | } |
| 114 | 119 | ||
| 115 | /* Here we're guaranteed to have enough space in vector to move the block of peers after insertion point */ | 120 | match = (ot_peer *)vector->data + match_offset; |
| 116 | memmove(match + peer_size, match, match_to_end); | 121 | memmove(match + peer_size, match, bytes_after); |
| 117 | 122 | ||
| 118 | vector->size++; | 123 | vector->size++; |
| 119 | return match; | 124 | return match; |
| @@ -150,11 +155,13 @@ int vector_remove_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size) | |||
| 150 | } | 155 | } |
| 151 | 156 | ||
| 152 | void vector_remove_torrent(ot_vector *vector, ot_torrent *match) { | 157 | void vector_remove_torrent(ot_vector *vector, ot_torrent *match) { |
| 153 | ot_torrent *end = ((ot_torrent *)vector->data) + vector->size; | 158 | ot_torrent *end; |
| 154 | 159 | ||
| 155 | if (!vector->size) | 160 | if (!vector->size) |
| 156 | return; | 161 | return; |
| 157 | 162 | ||
| 163 | end = ((ot_torrent *)vector->data) + vector->size; | ||
| 164 | |||
| 158 | free_peerlist(match->peer_list6); | 165 | free_peerlist(match->peer_list6); |
| 159 | free_peerlist(match->peer_list4); | 166 | free_peerlist(match->peer_list4); |
| 160 | 167 | ||
diff --git a/tests/integration/.gitignore b/tests/integration/.gitignore new file mode 100644 index 0000000..7a60b85 --- /dev/null +++ b/tests/integration/.gitignore | |||
| @@ -0,0 +1,2 @@ | |||
| 1 | __pycache__/ | ||
| 2 | *.pyc | ||
diff --git a/tests/integration/harness.py b/tests/integration/harness.py new file mode 100644 index 0000000..868075d --- /dev/null +++ b/tests/integration/harness.py | |||
| @@ -0,0 +1,198 @@ | |||
| 1 | """Starts a real opentracker and talks to it over the wire. | ||
| 2 | |||
| 3 | Everything here drives the binary rather than the code, so these tests cover | ||
| 4 | what the unit suite cannot reach: the event loop, the HTTP parser and the | ||
| 5 | torrent store working together. Run against a sanitizer build and the tracker | ||
| 6 | reports its own memory errors, which the teardown turns into test failures. | ||
| 7 | """ | ||
| 8 | |||
| 9 | import os | ||
| 10 | import shutil | ||
| 11 | import signal | ||
| 12 | import socket | ||
| 13 | import struct | ||
| 14 | import subprocess | ||
| 15 | import sys | ||
| 16 | import tempfile | ||
| 17 | import time | ||
| 18 | import unittest | ||
| 19 | import urllib.error | ||
| 20 | import urllib.parse | ||
| 21 | import urllib.request | ||
| 22 | |||
| 23 | TRACKER = os.environ.get("OPENTRACKER", os.path.join(os.path.dirname(__file__), "..", "..", "opentracker.debug")) | ||
| 24 | |||
| 25 | |||
| 26 | def bdecode(data): | ||
| 27 | """Enough bencode for what a tracker answers.""" | ||
| 28 | |||
| 29 | def value(i): | ||
| 30 | if data[i : i + 1] == b"d": | ||
| 31 | i, out = i + 1, {} | ||
| 32 | while data[i : i + 1] != b"e": | ||
| 33 | i, key = value(i) | ||
| 34 | i, out[key] = value(i) | ||
| 35 | return i + 1, out | ||
| 36 | if data[i : i + 1] == b"l": | ||
| 37 | i, out = i + 1, [] | ||
| 38 | while data[i : i + 1] != b"e": | ||
| 39 | i, item = value(i) | ||
| 40 | out.append(item) | ||
| 41 | return i + 1, out | ||
| 42 | if data[i : i + 1] == b"i": | ||
| 43 | end = data.index(b"e", i) | ||
| 44 | return end + 1, int(data[i + 1 : end]) | ||
| 45 | colon = data.index(b":", i) | ||
| 46 | length = int(data[i:colon]) | ||
| 47 | return colon + 1 + length, data[colon + 1 : colon + 1 + length] | ||
| 48 | |||
| 49 | return value(0)[1] | ||
| 50 | |||
| 51 | |||
| 52 | def free_port(): | ||
| 53 | probe = socket.socket() | ||
| 54 | probe.bind(("127.0.0.1", 0)) | ||
| 55 | port = probe.getsockname()[1] | ||
| 56 | probe.close() | ||
| 57 | return port | ||
| 58 | |||
| 59 | |||
| 60 | class Tracker: | ||
| 61 | """One tracker process, listening on loopback.""" | ||
| 62 | |||
| 63 | def __init__(self, extra_config=""): | ||
| 64 | self.port = free_port() | ||
| 65 | self.dir = tempfile.mkdtemp(prefix="ot-integration-") | ||
| 66 | self.log = None | ||
| 67 | self.process = None | ||
| 68 | try: | ||
| 69 | self.config = os.path.join(self.dir, "opentracker.conf") | ||
| 70 | with open(self.config, "w") as handle: | ||
| 71 | handle.write("listen.tcp 127.0.0.1:%d\n" % self.port) | ||
| 72 | handle.write("listen.udp 127.0.0.1:%d\n" % self.port) | ||
| 73 | handle.write(extra_config) | ||
| 74 | self.log = open(os.path.join(self.dir, "tracker.log"), "w+b") | ||
| 75 | # Leak detection is left at the sanitizer's own default, which is on | ||
| 76 | # where LeakSanitizer exists and off where it does not. Asking for it | ||
| 77 | # by name makes the tracker refuse to start on macOS and FreeBSD. | ||
| 78 | environment = dict(os.environ, ASAN_OPTIONS="abort_on_error=0") | ||
| 79 | self.process = subprocess.Popen([TRACKER, "-f", self.config], stdout=self.log, stderr=self.log, env=environment) | ||
| 80 | self._wait_for_port() | ||
| 81 | except BaseException: | ||
| 82 | self._cleanup() | ||
| 83 | raise | ||
| 84 | |||
| 85 | def _cleanup(self): | ||
| 86 | """Close what we opened and take the scratch directory with it.""" | ||
| 87 | if self.process is not None and self.process.poll() is None: | ||
| 88 | self.process.kill() | ||
| 89 | self.process.wait() | ||
| 90 | if self.log is not None: | ||
| 91 | self.log.close() | ||
| 92 | self.log = None | ||
| 93 | shutil.rmtree(self.dir, ignore_errors=True) | ||
| 94 | |||
| 95 | def _wait_for_port(self): | ||
| 96 | for _ in range(200): | ||
| 97 | if self.process.poll() is not None: | ||
| 98 | raise AssertionError("tracker exited at once:\n" + self.output()) | ||
| 99 | try: | ||
| 100 | probe = socket.create_connection(("127.0.0.1", self.port), timeout=0.2) | ||
| 101 | probe.close() | ||
| 102 | return | ||
| 103 | except OSError: | ||
| 104 | time.sleep(0.05) | ||
| 105 | raise AssertionError("tracker never listened on %d:\n%s" % (self.port, self.output())) | ||
| 106 | |||
| 107 | def output(self): | ||
| 108 | if self.log is None: | ||
| 109 | return "" | ||
| 110 | self.log.flush() | ||
| 111 | self.log.seek(0) | ||
| 112 | return self.log.read().decode("utf-8", "replace") | ||
| 113 | |||
| 114 | def stop(self): | ||
| 115 | """Returns anything the tracker complained about on the way out. | ||
| 116 | |||
| 117 | The interrupt rather than a kill is what makes the leak check work at | ||
| 118 | all: LeakSanitizer reports from an exit handler, so a killed tracker | ||
| 119 | reports nothing and the suite passes over leaks it never looked for. | ||
| 120 | """ | ||
| 121 | if self.process is not None and self.process.poll() is None: | ||
| 122 | self.process.send_signal(signal.SIGINT) | ||
| 123 | try: | ||
| 124 | self.process.wait(timeout=5) | ||
| 125 | except subprocess.TimeoutExpired: | ||
| 126 | self.process.kill() | ||
| 127 | self.process.wait() | ||
| 128 | complaints = [ | ||
| 129 | line | ||
| 130 | for line in self.output().splitlines() | ||
| 131 | if "Sanitizer" in line or "runtime error" in line or line.startswith("ERROR") | ||
| 132 | ] | ||
| 133 | self._cleanup() | ||
| 134 | return complaints | ||
| 135 | |||
| 136 | # ----------------------------------------------------------- over http | ||
| 137 | |||
| 138 | def get(self, path, timeout=10): | ||
| 139 | return urllib.request.urlopen("http://127.0.0.1:%d%s" % (self.port, path), timeout=timeout).read() | ||
| 140 | |||
| 141 | def announce(self, info_hash, peer_id=b"-OT0001-aaaaaaaaaaaa", port=6881, left=0, **extra): | ||
| 142 | query = { | ||
| 143 | "info_hash": urllib.parse.quote_from_bytes(info_hash, safe=""), | ||
| 144 | "peer_id": urllib.parse.quote_from_bytes(peer_id, safe=""), | ||
| 145 | "port": port, | ||
| 146 | "left": left, | ||
| 147 | } | ||
| 148 | query.update(extra) | ||
| 149 | return bdecode(self.get("/announce?" + "&".join("%s=%s" % item for item in query.items()))) | ||
| 150 | |||
| 151 | def scrape(self, info_hash): | ||
| 152 | return bdecode(self.get("/scrape?info_hash=" + urllib.parse.quote_from_bytes(info_hash, safe=""))) | ||
| 153 | |||
| 154 | def stats(self, mode): | ||
| 155 | return self.get("/stats?mode=" + mode) | ||
| 156 | |||
| 157 | def torrent_count(self): | ||
| 158 | return int(self.stats("torr").split()[0]) | ||
| 159 | |||
| 160 | # ------------------------------------------------------------ over udp | ||
| 161 | |||
| 162 | def udp_connect(self): | ||
| 163 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
| 164 | sock.settimeout(5) | ||
| 165 | sock.sendto(struct.pack(">QII", 0x41727101980, 0, 1), ("127.0.0.1", self.port)) | ||
| 166 | answer = sock.recv(2048) | ||
| 167 | action, _, connection_id = struct.unpack(">IIQ", answer[:16]) | ||
| 168 | assert action == 0, "udp connect answered action %d" % action | ||
| 169 | return sock, connection_id | ||
| 170 | |||
| 171 | def udp_announce(self, sock, connection_id, info_hash, left=0, event=0, port=6881, numwant=-1): | ||
| 172 | sock.sendto( | ||
| 173 | struct.pack( | ||
| 174 | ">QII20s20sQQQIIIiH", connection_id, 1, 2, info_hash, b"p" * 20, 0, left, 0, event, 0, 0, numwant, port | ||
| 175 | ), | ||
| 176 | ("127.0.0.1", self.port), | ||
| 177 | ) | ||
| 178 | answer = sock.recv(2048) | ||
| 179 | action, _, interval, leechers, seeders = struct.unpack(">IIIII", answer[:20]) | ||
| 180 | assert action == 1, "udp announce answered action %d" % action | ||
| 181 | return {"interval": interval, "leechers": leechers, "seeders": seeders, "peers": answer[20:]} | ||
| 182 | |||
| 183 | |||
| 184 | class TrackerTestCase(unittest.TestCase): | ||
| 185 | """Gives every test its own tracker and fails it on any sanitizer output.""" | ||
| 186 | |||
| 187 | config = "" | ||
| 188 | |||
| 189 | def setUp(self): | ||
| 190 | self.tracker = Tracker(self.config) | ||
| 191 | |||
| 192 | def tearDown(self): | ||
| 193 | complaints = self.tracker.stop() | ||
| 194 | self.assertEqual(complaints, [], "the tracker reported:\n" + "\n".join(complaints)) | ||
| 195 | |||
| 196 | def hash_for(self, name): | ||
| 197 | """A distinct info hash per test, so tests cannot leak into each other.""" | ||
| 198 | return (name.encode() + b"\0" * 20)[:20] | ||
diff --git a/tests/integration/test_announce.py b/tests/integration/test_announce.py new file mode 100644 index 0000000..ffbc07b --- /dev/null +++ b/tests/integration/test_announce.py | |||
| @@ -0,0 +1,129 @@ | |||
| 1 | """What a peer's announce does to the swarm it joins.""" | ||
| 2 | |||
| 3 | import os | ||
| 4 | import socket | ||
| 5 | import sys | ||
| 6 | import unittest | ||
| 7 | |||
| 8 | sys.path.insert(0, os.path.dirname(__file__)) | ||
| 9 | from harness import TrackerTestCase | ||
| 10 | |||
| 11 | |||
| 12 | def peers_of(answer, key=b"peers"): | ||
| 13 | """Compact peer list as a list of (address, port).""" | ||
| 14 | blob, out = answer.get(key, b""), [] | ||
| 15 | size = 6 if key == b"peers" else 18 | ||
| 16 | for offset in range(0, len(blob), size): | ||
| 17 | entry = blob[offset : offset + size] | ||
| 18 | address = socket.inet_ntop(socket.AF_INET if size == 6 else socket.AF_INET6, entry[:-2]) | ||
| 19 | out.append((address, entry[-2] << 8 | entry[-1])) | ||
| 20 | return out | ||
| 21 | |||
| 22 | |||
| 23 | class AnnounceLifetime(TrackerTestCase): | ||
| 24 | config = "" | ||
| 25 | |||
| 26 | def test_a_leecher_is_counted_as_incomplete(self): | ||
| 27 | info_hash = self.hash_for("leecher") | ||
| 28 | answer = self.tracker.announce(info_hash, left=100) | ||
| 29 | self.assertEqual(answer[b"complete"], 0) | ||
| 30 | self.assertEqual(answer[b"incomplete"], 1) | ||
| 31 | |||
| 32 | def test_no_bytes_left_means_seeding(self): | ||
| 33 | info_hash = self.hash_for("seeder") | ||
| 34 | answer = self.tracker.announce(info_hash, left=0) | ||
| 35 | self.assertEqual(answer[b"complete"], 1) | ||
| 36 | self.assertEqual(answer[b"incomplete"], 0) | ||
| 37 | |||
| 38 | def test_a_peer_that_finishes_moves_from_one_column_to_the_other(self): | ||
| 39 | info_hash = self.hash_for("finishes") | ||
| 40 | peer_id = b"-OT0001-finisher0001" | ||
| 41 | self.tracker.announce(info_hash, peer_id=peer_id, left=100) | ||
| 42 | self.assertEqual(self.tracker.announce(info_hash, peer_id=peer_id, left=100)[b"incomplete"], 1) | ||
| 43 | |||
| 44 | answer = self.tracker.announce(info_hash, peer_id=peer_id, left=0, event="completed") | ||
| 45 | self.assertEqual(answer[b"complete"], 1, "the peer should now count as a seeder") | ||
| 46 | self.assertEqual(answer[b"incomplete"], 0, "and no longer as a leecher") | ||
| 47 | |||
| 48 | def test_stopped_removes_the_peer(self): | ||
| 49 | info_hash = self.hash_for("stopped") | ||
| 50 | peer_id = b"-OT0001-leaves000001" | ||
| 51 | self.tracker.announce(info_hash, peer_id=peer_id, left=100) | ||
| 52 | self.assertEqual(self.tracker.scrape(info_hash)[b"files"][info_hash][b"incomplete"], 1) | ||
| 53 | |||
| 54 | self.tracker.announce(info_hash, peer_id=peer_id, left=100, event="stopped") | ||
| 55 | self.assertEqual( | ||
| 56 | self.tracker.scrape(info_hash)[b"files"][info_hash][b"incomplete"], 0, "the peer should be gone" | ||
| 57 | ) | ||
| 58 | |||
| 59 | def test_the_same_peer_announcing_twice_is_still_one_peer(self): | ||
| 60 | info_hash = self.hash_for("twice") | ||
| 61 | peer_id = b"-OT0001-repeats00001" | ||
| 62 | for _ in range(5): | ||
| 63 | answer = self.tracker.announce(info_hash, peer_id=peer_id, left=100) | ||
| 64 | self.assertEqual(answer[b"incomplete"], 1, "re-announcing must not duplicate the peer") | ||
| 65 | |||
| 66 | def test_a_peer_is_its_address_and_port_not_its_peer_id(self): | ||
| 67 | """Two announces from one address and port are one peer, whatever | ||
| 68 | peer_id they carry, because that pair is the whole identity.""" | ||
| 69 | info_hash = self.hash_for("identity") | ||
| 70 | self.tracker.announce(info_hash, peer_id=b"-OT0001-a00000000001", port=6881, left=100) | ||
| 71 | answer = self.tracker.announce(info_hash, peer_id=b"-OT0001-b00000000002", port=6881, left=100) | ||
| 72 | self.assertEqual(answer[b"incomplete"], 1, "same address and port, so still one peer") | ||
| 73 | |||
| 74 | answer = self.tracker.announce(info_hash, peer_id=b"-OT0001-c00000000003", port=6882, left=100) | ||
| 75 | self.assertEqual(answer[b"incomplete"], 2, "a different port makes a different peer") | ||
| 76 | |||
| 77 | def test_a_downloaded_count_is_kept(self): | ||
| 78 | info_hash = self.hash_for("downloaded") | ||
| 79 | self.tracker.announce(info_hash, port=7001, left=0, event="completed") | ||
| 80 | self.tracker.announce(info_hash, port=7002, left=0, event="completed") | ||
| 81 | self.assertEqual(self.tracker.scrape(info_hash)[b"files"][info_hash][b"downloaded"], 2) | ||
| 82 | |||
| 83 | def test_a_peer_is_given_the_whole_swarm_including_itself(self): | ||
| 84 | """opentracker does not filter the announcing peer out of its own | ||
| 85 | answer. Clients are expected to ignore themselves.""" | ||
| 86 | info_hash = self.hash_for("sees") | ||
| 87 | self.tracker.announce(info_hash, port=1111, left=100) | ||
| 88 | answer = self.tracker.announce(info_hash, port=2222, left=100) | ||
| 89 | |||
| 90 | ports = sorted(port for _, port in peers_of(answer)) | ||
| 91 | self.assertEqual(ports, [1111, 2222]) | ||
| 92 | |||
| 93 | def test_an_unknown_torrent_scrapes_as_empty(self): | ||
| 94 | answer = self.tracker.scrape(self.hash_for("never-seen")) | ||
| 95 | self.assertEqual(answer[b"files"], {}, "a torrent nobody announced has no entry") | ||
| 96 | |||
| 97 | |||
| 98 | class AnnounceRejects(TrackerTestCase): | ||
| 99 | def assert_rejected(self, query): | ||
| 100 | import urllib.error | ||
| 101 | |||
| 102 | with self.assertRaises(urllib.error.HTTPError) as caught: | ||
| 103 | self.tracker.get("/announce?" + query) | ||
| 104 | self.assertEqual(caught.exception.code, 400) | ||
| 105 | |||
| 106 | def test_a_short_info_hash_is_rejected(self): | ||
| 107 | self.assert_rejected("info_hash=tooshort&peer_id=" + "b" * 20 + "&port=6881&left=0") | ||
| 108 | |||
| 109 | def test_a_missing_info_hash_answers_a_failure_reason(self): | ||
| 110 | """Not a 400: the tracker answers 200 with a bencoded complaint, so | ||
| 111 | that a client shows the message to its user.""" | ||
| 112 | from harness import bdecode | ||
| 113 | |||
| 114 | answer = bdecode(self.tracker.get("/announce?peer_id=" + "b" * 20 + "&port=6881&left=0")) | ||
| 115 | self.assertIn(b"failure reason", answer) | ||
| 116 | self.assertIn(b"info_hash", answer[b"failure reason"]) | ||
| 117 | |||
| 118 | def test_a_port_out_of_range_is_rejected(self): | ||
| 119 | self.assert_rejected("info_hash=" + "a" * 20 + "&peer_id=" + "b" * 20 + "&port=65536&left=0") | ||
| 120 | |||
| 121 | def test_a_port_that_is_not_a_number_is_rejected(self): | ||
| 122 | self.assert_rejected("info_hash=" + "a" * 20 + "&peer_id=" + "b" * 20 + "&port=abc&left=0") | ||
| 123 | |||
| 124 | def test_two_info_hashes_are_rejected(self): | ||
| 125 | self.assert_rejected("info_hash=" + "a" * 20 + "&info_hash=" + "c" * 20 + "&peer_id=" + "b" * 20 + "&port=6881&left=0") | ||
| 126 | |||
| 127 | |||
| 128 | if __name__ == "__main__": | ||
| 129 | unittest.main() | ||
diff --git a/tests/integration/test_dualstack.py b/tests/integration/test_dualstack.py new file mode 100644 index 0000000..cd5ba7d --- /dev/null +++ b/tests/integration/test_dualstack.py | |||
| @@ -0,0 +1,89 @@ | |||
| 1 | """A tracker serving both families from one socket. | ||
| 2 | |||
| 3 | These need WANT_IP_FROM_QUERY_STRING so a peer's address can be dictated | ||
| 4 | rather than being whatever loopback the test happened to come from. | ||
| 5 | """ | ||
| 6 | |||
| 7 | import os | ||
| 8 | import socket | ||
| 9 | import sys | ||
| 10 | import unittest | ||
| 11 | |||
| 12 | sys.path.insert(0, os.path.dirname(__file__)) | ||
| 13 | from harness import TrackerTestCase | ||
| 14 | |||
| 15 | |||
| 16 | def addresses(answer, key): | ||
| 17 | blob, out = answer.get(key, b""), [] | ||
| 18 | size = 6 if key == b"peers" else 18 | ||
| 19 | for offset in range(0, len(blob), size): | ||
| 20 | entry = blob[offset : offset + size] | ||
| 21 | out.append(socket.inet_ntop(socket.AF_INET if size == 6 else socket.AF_INET6, entry[:-2])) | ||
| 22 | return out | ||
| 23 | |||
| 24 | |||
| 25 | class DualStack(TrackerTestCase): | ||
| 26 | def test_an_ipv4_peer_lands_in_the_v4_list(self): | ||
| 27 | info_hash = self.hash_for("v4only") | ||
| 28 | self.tracker.announce(info_hash, ip="10.1.2.3", port=5001, left=100) | ||
| 29 | answer = self.tracker.announce(info_hash, ip="10.1.2.4", port=5002, left=100) | ||
| 30 | |||
| 31 | self.assertEqual(sorted(addresses(answer, b"peers")), ["10.1.2.3", "10.1.2.4"]) | ||
| 32 | self.assertEqual(addresses(answer, b"peers6"), [], "no v6 peer was announced") | ||
| 33 | |||
| 34 | def test_an_ipv6_peer_lands_in_the_v6_list(self): | ||
| 35 | info_hash = self.hash_for("v6only") | ||
| 36 | self.tracker.announce(info_hash, ip="2001:db8::1", port=5001, left=100) | ||
| 37 | answer = self.tracker.announce(info_hash, ip="2001:db8::2", port=5002, left=100) | ||
| 38 | |||
| 39 | self.assertEqual(sorted(addresses(answer, b"peers6")), ["2001:db8::1", "2001:db8::2"]) | ||
| 40 | self.assertEqual(addresses(answer, b"peers"), [], "no v4 peer was announced") | ||
| 41 | |||
| 42 | def test_the_two_families_are_kept_apart_but_both_returned(self): | ||
| 43 | info_hash = self.hash_for("both") | ||
| 44 | self.tracker.announce(info_hash, ip="10.9.9.9", port=6001, left=100) | ||
| 45 | answer = self.tracker.announce(info_hash, ip="2001:db8::9", port=6002, left=100) | ||
| 46 | |||
| 47 | self.assertEqual(addresses(answer, b"peers"), ["10.9.9.9"]) | ||
| 48 | self.assertEqual(addresses(answer, b"peers6"), ["2001:db8::9"]) | ||
| 49 | |||
| 50 | def test_a_v4_mapped_address_is_the_same_peer_as_the_plain_one(self): | ||
| 51 | info_hash = self.hash_for("v4mapped") | ||
| 52 | self.tracker.announce(info_hash, ip="10.5.5.5", port=7001, left=100) | ||
| 53 | answer = self.tracker.announce(info_hash, ip="::ffff:10.5.5.5", port=7001, left=100) | ||
| 54 | |||
| 55 | self.assertEqual(answer[b"incomplete"], 1, "the same address written two ways is one peer") | ||
| 56 | self.assertEqual(addresses(answer, b"peers"), ["10.5.5.5"], "and it is held as v4") | ||
| 57 | |||
| 58 | def test_the_two_families_count_into_one_swarm(self): | ||
| 59 | info_hash = self.hash_for("counts") | ||
| 60 | self.tracker.announce(info_hash, ip="10.4.4.4", port=8001, left=0) | ||
| 61 | answer = self.tracker.announce(info_hash, ip="2001:db8::4", port=8002, left=100) | ||
| 62 | |||
| 63 | self.assertEqual(answer[b"complete"], 1) | ||
| 64 | self.assertEqual(answer[b"incomplete"], 1) | ||
| 65 | self.assertEqual(self.tracker.scrape(info_hash)[b"files"][info_hash][b"complete"], 1) | ||
| 66 | |||
| 67 | def test_stopped_removes_only_the_family_it_came_from(self): | ||
| 68 | info_hash = self.hash_for("stopfamily") | ||
| 69 | self.tracker.announce(info_hash, ip="10.3.3.3", port=9001, left=100) | ||
| 70 | self.tracker.announce(info_hash, ip="2001:db8::3", port=9002, left=100) | ||
| 71 | self.tracker.announce(info_hash, ip="10.3.3.3", port=9001, left=100, event="stopped") | ||
| 72 | |||
| 73 | answer = self.tracker.announce(info_hash, ip="2001:db8::33", port=9003, left=100) | ||
| 74 | self.assertEqual(addresses(answer, b"peers"), [], "the v4 peer left") | ||
| 75 | self.assertIn("2001:db8::3", addresses(answer, b"peers6"), "the v6 one stayed") | ||
| 76 | |||
| 77 | def test_a_malformed_address_is_rejected(self): | ||
| 78 | import urllib.error | ||
| 79 | |||
| 80 | # The whole field has to be the address: a prefix that happens to | ||
| 81 | # parse would quietly record the peer somewhere else | ||
| 82 | for bad in ("999.1.2.3", "nonsense", "2001:db8:::1", "10.1.2.3.4", "10.1.2.3junk", "2001:db8::1%eth0"): | ||
| 83 | with self.assertRaises(urllib.error.HTTPError, msg="ip=%s should be refused" % bad) as caught: | ||
| 84 | self.tracker.announce(self.hash_for("badip"), ip=bad, port=6881, left=0) | ||
| 85 | self.assertEqual(caught.exception.code, 400) | ||
| 86 | |||
| 87 | |||
| 88 | if __name__ == "__main__": | ||
| 89 | unittest.main() | ||
diff --git a/tests/runner.c b/tests/runner.c index c2350c3..71a5e2a 100644 --- a/tests/runner.c +++ b/tests/runner.c | |||
| @@ -4,7 +4,11 @@ | |||
| 4 | $id$ */ | 4 | $id$ */ |
| 5 | 5 | ||
| 6 | /* System */ | 6 | /* System */ |
| 7 | #include <stdio.h> | ||
| 8 | #include <stdlib.h> | ||
| 7 | #include <string.h> | 9 | #include <string.h> |
| 10 | #include <sys/wait.h> | ||
| 11 | #include <unistd.h> | ||
| 8 | 12 | ||
| 9 | /* Opentracker */ | 13 | /* Opentracker */ |
| 10 | #include "test.h" | 14 | #include "test.h" |
| @@ -27,18 +31,83 @@ static const struct module modules[] = {{"ot_urlencoded", test_urlencoded}, | |||
| 27 | {"ot_batch", test_batch}, | 31 | {"ot_batch", test_batch}, |
| 28 | {"ot_fdstate", test_fdstate}, | 32 | {"ot_fdstate", test_fdstate}, |
| 29 | {"ot_event", test_event}, | 33 | {"ot_event", test_event}, |
| 34 | {"ot_fmt", test_fmt}, | ||
| 30 | {"ot_accesslist", test_accesslist}, | 35 | {"ot_accesslist", test_accesslist}, |
| 31 | {NULL, NULL}}; | 36 | {NULL, NULL}}; |
| 32 | 37 | ||
| 33 | /* Run every module, or only those whose name contains argv[1] */ | 38 | struct tally { |
| 39 | unsigned long checks; | ||
| 40 | unsigned long failures; | ||
| 41 | }; | ||
| 42 | |||
| 43 | /* Each module runs in a child of its own, so that a crash or a sanitizer | ||
| 44 | abort costs that module's results rather than every module after it. The | ||
| 45 | child reports its tally through a pipe; one that dies without reporting is | ||
| 46 | counted as a failure under its own name. */ | ||
| 47 | static void run_forked(const struct module *m) { | ||
| 48 | struct tally tally = {0, 0}; | ||
| 49 | int pipefd[2], status = 0; | ||
| 50 | pid_t child; | ||
| 51 | ssize_t got; | ||
| 52 | |||
| 53 | if (pipe(pipefd)) { | ||
| 54 | test_failf(__FILE__, __LINE__, "pipe failed, cannot isolate %s", m->name); | ||
| 55 | return; | ||
| 56 | } | ||
| 57 | |||
| 58 | fflush(stdout); /* or the child inherits a copy and prints it again */ | ||
| 59 | child = fork(); | ||
| 60 | if (child < 0) { | ||
| 61 | close(pipefd[0]); | ||
| 62 | close(pipefd[1]); | ||
| 63 | test_failf(__FILE__, __LINE__, "fork failed, cannot isolate %s", m->name); | ||
| 64 | return; | ||
| 65 | } | ||
| 66 | |||
| 67 | if (!child) { | ||
| 68 | close(pipefd[0]); | ||
| 69 | test_checks = test_failures = 0; | ||
| 70 | m->run(); | ||
| 71 | tally.checks = test_checks; | ||
| 72 | tally.failures = test_failures; | ||
| 73 | if (write(pipefd[1], &tally, sizeof(tally)) != (ssize_t)sizeof(tally)) | ||
| 74 | _exit(1); | ||
| 75 | fflush(stdout); | ||
| 76 | _exit(0); | ||
| 77 | } | ||
| 78 | |||
| 79 | close(pipefd[1]); | ||
| 80 | got = read(pipefd[0], &tally, sizeof(tally)); | ||
| 81 | close(pipefd[0]); | ||
| 82 | while (waitpid(child, &status, 0) < 0) | ||
| 83 | ; | ||
| 84 | |||
| 85 | if (got == (ssize_t)sizeof(tally)) { | ||
| 86 | test_checks += tally.checks; | ||
| 87 | test_failures += tally.failures; | ||
| 88 | } | ||
| 89 | if (got != (ssize_t)sizeof(tally) || !WIFEXITED(status) || WEXITSTATUS(status)) | ||
| 90 | test_failf(__FILE__, __LINE__, "%s did not run to the end", m->name); | ||
| 91 | } | ||
| 92 | |||
| 93 | /* Run every module, or only those whose name contains argv[1]. | ||
| 94 | OT_TEST_FORK=0 keeps everything in one process, which is what a debugger | ||
| 95 | wants. */ | ||
| 34 | int main(int argc, char **argv) { | 96 | int main(int argc, char **argv) { |
| 97 | const char *fork_setting = getenv("OT_TEST_FORK"); | ||
| 98 | int isolate = !fork_setting || strcmp(fork_setting, "0"); | ||
| 35 | const struct module *m; | 99 | const struct module *m; |
| 36 | 100 | ||
| 37 | for (m = modules; m->name; ++m) { | 101 | for (m = modules; m->name; ++m) { |
| 38 | if (argc > 1 && !strstr(m->name, argv[1])) | 102 | if (argc > 1 && !strstr(m->name, argv[1])) |
| 39 | continue; | 103 | continue; |
| 40 | test_module(m->name); | 104 | test_module(m->name); |
| 41 | m->run(); | 105 | if (!isolate) { |
| 106 | m->run(); | ||
| 107 | continue; | ||
| 108 | } | ||
| 109 | test_case("the module runs to the end"); | ||
| 110 | run_forked(m); | ||
| 42 | } | 111 | } |
| 43 | 112 | ||
| 44 | return test_summary(); | 113 | return test_summary(); |
diff --git a/tests/t_fmt.c b/tests/t_fmt.c new file mode 100644 index 0000000..bc23ed5 --- /dev/null +++ b/tests/t_fmt.c | |||
| @@ -0,0 +1,108 @@ | |||
| 1 | /* This software was written by Dirk Engling <erdgeist@erdgeist.org> | ||
| 2 | It is considered beerware. Prost. Skol. Cheers or whatever. | ||
| 3 | |||
| 4 | $id$ */ | ||
| 5 | |||
| 6 | /* System */ | ||
| 7 | #include <string.h> | ||
| 8 | |||
| 9 | /* Opentracker */ | ||
| 10 | #include "ot_fmt.h" | ||
| 11 | #include "test.h" | ||
| 12 | |||
| 13 | static void case_appends_in_sequence(void) { | ||
| 14 | char buffer[64]; | ||
| 15 | char *r = buffer, *re = buffer + sizeof(buffer); | ||
| 16 | |||
| 17 | test_case("appends follow each other and advance the cursor"); | ||
| 18 | fmt_append(&r, re, "d8:completei%zde", (size_t)7); | ||
| 19 | fmt_append(&r, re, "ee"); | ||
| 20 | *r = 0; | ||
| 21 | CHECK_STR(buffer, "d8:completei7eee"); | ||
| 22 | CHECK_INT(r - buffer, 16); | ||
| 23 | } | ||
| 24 | |||
| 25 | static void case_truncation_stops_at_the_end(void) { | ||
| 26 | char buffer[16]; | ||
| 27 | char *r = buffer, *re = buffer + 8; /* pretend the buffer ends early */ | ||
| 28 | |||
| 29 | memset(buffer, '#', sizeof(buffer)); | ||
| 30 | test_case("a value too long for the room left stops at the end"); | ||
| 31 | fmt_append(&r, re, "%s", "0123456789"); | ||
| 32 | CHECK_PTR(r, re); | ||
| 33 | |||
| 34 | test_case("and writes nothing past it"); | ||
| 35 | CHECK_INT(buffer[8], '#'); | ||
| 36 | CHECK_INT(buffer[15], '#'); | ||
| 37 | } | ||
| 38 | |||
| 39 | static void case_a_full_buffer_takes_nothing_more(void) { | ||
| 40 | char buffer[16]; | ||
| 41 | char *r = buffer + 8, *re = buffer + 8; | ||
| 42 | |||
| 43 | memset(buffer, '#', sizeof(buffer)); | ||
| 44 | test_case("appending to a cursor already at the end changes nothing"); | ||
| 45 | fmt_append(&r, re, "more"); | ||
| 46 | CHECK_PTR(r, re); | ||
| 47 | CHECK_INT(buffer[8], '#'); | ||
| 48 | } | ||
| 49 | |||
| 50 | static void case_an_append_that_just_fits(void) { | ||
| 51 | char buffer[8]; | ||
| 52 | char *r = buffer, *re = buffer + 3; | ||
| 53 | |||
| 54 | memset(buffer, '#', sizeof(buffer)); | ||
| 55 | test_case("three characters into three bytes of room still truncate"); | ||
| 56 | fmt_append(&r, re, "abc"); | ||
| 57 | CHECK_PTR(r, re); | ||
| 58 | CHECK_INT(buffer[0], 'a'); | ||
| 59 | CHECK_INT(buffer[1], 'b'); | ||
| 60 | |||
| 61 | test_case("because snprintf spends the last byte on its terminator"); | ||
| 62 | CHECK_INT(buffer[2], 0); | ||
| 63 | CHECK_INT(buffer[3], '#'); | ||
| 64 | } | ||
| 65 | |||
| 66 | static void case_bytes_are_all_or_nothing(void) { | ||
| 67 | char buffer[16]; | ||
| 68 | char *r = buffer, *re = buffer + 8; | ||
| 69 | |||
| 70 | memset(buffer, '#', sizeof(buffer)); | ||
| 71 | test_case("a run of bytes is written when it fits"); | ||
| 72 | fmt_bytes(&r, re, "\0\1\2\3", 4); | ||
| 73 | CHECK_PTR(r, buffer + 4); | ||
| 74 | CHECK_MEM(buffer, "\0\1\2\3", 4); | ||
| 75 | |||
| 76 | test_case("a run that would not fit is refused whole"); | ||
| 77 | fmt_bytes(&r, re, "abcde", 5); | ||
| 78 | CHECK_PTR(r, re); | ||
| 79 | CHECK_INT(buffer[4], '#'); | ||
| 80 | |||
| 81 | test_case("a run of exactly the room left is written"); | ||
| 82 | r = buffer + 4; | ||
| 83 | fmt_bytes(&r, re, "wxyz", 4); | ||
| 84 | CHECK_PTR(r, re); | ||
| 85 | CHECK_MEM(buffer + 4, "wxyz", 4); | ||
| 86 | CHECK_INT(buffer[8], '#'); | ||
| 87 | } | ||
| 88 | |||
| 89 | static void case_bytes_do_not_terminate(void) { | ||
| 90 | char buffer[8]; | ||
| 91 | char *r = buffer, *re = buffer + 4; | ||
| 92 | |||
| 93 | memset(buffer, '#', sizeof(buffer)); | ||
| 94 | test_case("unlike an append, a run of bytes may fill the room exactly"); | ||
| 95 | fmt_bytes(&r, re, "abcd", 4); | ||
| 96 | CHECK_PTR(r, re); | ||
| 97 | CHECK_MEM(buffer, "abcd", 4); | ||
| 98 | CHECK_INT(buffer[4], '#'); | ||
| 99 | } | ||
| 100 | |||
| 101 | void test_fmt(void) { | ||
| 102 | case_appends_in_sequence(); | ||
| 103 | case_truncation_stops_at_the_end(); | ||
| 104 | case_a_full_buffer_takes_nothing_more(); | ||
| 105 | case_an_append_that_just_fits(); | ||
| 106 | case_bytes_are_all_or_nothing(); | ||
| 107 | case_bytes_do_not_terminate(); | ||
| 108 | } | ||
diff --git a/tests/t_vector.c b/tests/t_vector.c index 541a6da..6e048e1 100644 --- a/tests/t_vector.c +++ b/tests/t_vector.c | |||
| @@ -112,6 +112,51 @@ static void case_insert_keeps_order(void) { | |||
| 112 | free(vector.data); | 112 | free(vector.data); |
| 113 | } | 113 | } |
| 114 | 114 | ||
| 115 | /* The peer variant is reached with an empty vector on every first announce | ||
| 116 | for a torrent, and again whenever a swarm empties and refills, because | ||
| 117 | vector_fixup_peers drops the allocation and nulls the data pointer. */ | ||
| 118 | |||
| 119 | static void case_insert_peer_into_empty_vector(void) { | ||
| 120 | ot_vector vector = {NULL, 0, 0}; | ||
| 121 | ot_peer6 low, high; | ||
| 122 | int exactmatch; | ||
| 123 | ot_peer *slot; | ||
| 124 | |||
| 125 | memset(low, 0x11, sizeof(low)); | ||
| 126 | memset(high, 0x99, sizeof(high)); | ||
| 127 | |||
| 128 | test_case("the first peer goes into a vector that has no allocation yet"); | ||
| 129 | slot = vector_find_or_insert_peer(&vector, low, OT_PEER_SIZE6, &exactmatch); | ||
| 130 | CHECK(slot != NULL); | ||
| 131 | CHECK_INT(exactmatch, 0); | ||
| 132 | memcpy(slot, low, OT_PEER_SIZE6); | ||
| 133 | CHECK_INT(vector.size, 1); | ||
| 134 | CHECK_INT(vector.space, OT_VECTOR_MIN_MEMBERS); | ||
| 135 | CHECK_PTR(slot, vector.data); | ||
| 136 | |||
| 137 | test_case("a second peer keeps the order"); | ||
| 138 | slot = vector_find_or_insert_peer(&vector, high, OT_PEER_SIZE6, &exactmatch); | ||
| 139 | CHECK(slot != NULL); | ||
| 140 | CHECK_INT(exactmatch, 0); | ||
| 141 | memcpy(slot, high, OT_PEER_SIZE6); | ||
| 142 | CHECK_INT(vector.size, 2); | ||
| 143 | CHECK_MEM((char *)vector.data, low, OT_PEER_COMPARE_SIZE6); | ||
| 144 | CHECK_MEM((char *)vector.data + OT_PEER_SIZE6, high, OT_PEER_COMPARE_SIZE6); | ||
| 145 | |||
| 146 | test_case("and emptying the vector hands the allocation back"); | ||
| 147 | CHECK(vector_remove_peer(&vector, low, OT_PEER_SIZE6) != 0); | ||
| 148 | CHECK(vector_remove_peer(&vector, high, OT_PEER_SIZE6) != 0); | ||
| 149 | CHECK_INT(vector.size, 0); | ||
| 150 | CHECK_PTR(vector.data, NULL); | ||
| 151 | |||
| 152 | test_case("so the next peer starts from no allocation again"); | ||
| 153 | slot = vector_find_or_insert_peer(&vector, low, OT_PEER_SIZE6, &exactmatch); | ||
| 154 | CHECK(slot != NULL); | ||
| 155 | CHECK_INT(vector.size, 1); | ||
| 156 | |||
| 157 | free(vector.data); | ||
| 158 | } | ||
| 159 | |||
| 115 | static void case_insert_finds_existing(void) { | 160 | static void case_insert_finds_existing(void) { |
| 116 | ot_vector vector = {NULL, 0, 0}; | 161 | ot_vector vector = {NULL, 0, 0}; |
| 117 | const unsigned char five[4] = {0, 0, 0, 5}; | 162 | const unsigned char five[4] = {0, 0, 0, 5}; |
| @@ -257,6 +302,7 @@ void test_vector(void) { | |||
| 257 | case_search_past_the_end(); | 302 | case_search_past_the_end(); |
| 258 | case_search_empty(); | 303 | case_search_empty(); |
| 259 | case_insert_keeps_order(); | 304 | case_insert_keeps_order(); |
| 305 | case_insert_peer_into_empty_vector(); | ||
| 260 | case_insert_finds_existing(); | 306 | case_insert_finds_existing(); |
| 261 | case_insert_compare_prefix(); | 307 | case_insert_compare_prefix(); |
| 262 | case_redistribute_holds_every_peer(); | 308 | case_redistribute_holds_every_peer(); |
diff --git a/tests/test.h b/tests/test.h index aa4eb00..0eddc82 100644 --- a/tests/test.h +++ b/tests/test.h | |||
| @@ -90,5 +90,6 @@ void test_mutex(void); | |||
| 90 | void test_batch(void); | 90 | void test_batch(void); |
| 91 | void test_fdstate(void); | 91 | void test_fdstate(void); |
| 92 | void test_event(void); | 92 | void test_event(void); |
| 93 | void test_fmt(void); | ||
| 93 | 94 | ||
| 94 | #endif | 95 | #endif |
diff --git a/trackerlogic.c b/trackerlogic.c index 9705bd6..1f38f07 100644 --- a/trackerlogic.c +++ b/trackerlogic.c | |||
| @@ -14,6 +14,7 @@ | |||
| 14 | /* Opentracker */ | 14 | /* Opentracker */ |
| 15 | #include "ot_accesslist.h" | 15 | #include "ot_accesslist.h" |
| 16 | #include "ot_clean.h" | 16 | #include "ot_clean.h" |
| 17 | #include "ot_fmt.h" | ||
| 17 | #include "ot_fullscrape.h" | 18 | #include "ot_fullscrape.h" |
| 18 | #include "ot_http.h" | 19 | #include "ot_http.h" |
| 19 | #include "ot_ip.h" | 20 | #include "ot_ip.h" |
| @@ -299,11 +300,12 @@ static size_t return_peers_for_torrent_udp(struct ot_workstruct *ws, ot_torrent | |||
| 299 | } | 300 | } |
| 300 | 301 | ||
| 301 | static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply) { | 302 | 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 | char *r = reply; |
| 303 | int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM; | 304 | const char *r_end = ws->outbuf + G_OUTBUF_SIZE; |
| 304 | size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; | 305 | int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM; |
| 305 | size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count; | 306 | size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; |
| 306 | size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - seed_count; | 307 | size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count; |
| 308 | size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - seed_count; | ||
| 307 | 309 | ||
| 308 | /* Simple case: amount of peers in both lists is less than requested, here we return all results */ | 310 | /* Simple case: amount of peers in both lists is less than requested, here we return all results */ |
| 309 | size_t amount_v4 = torrent->peer_list4->peer_count; | 311 | size_t amount_v4 = torrent->peer_list4->peer_count; |
| @@ -341,11 +343,11 @@ static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent | |||
| 341 | ++amount_v4; | 343 | ++amount_v4; |
| 342 | } | 344 | } |
| 343 | 345 | ||
| 344 | r += | 346 | fmt_append(&r, r_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zde8:intervali%ie12:min intervali%ie", seed_count, down_count, peer_count, erval, |
| 345 | sprintf(r, "d8:completei%zde10:downloadedi%zde10:incompletei%zde8:intervali%ie12:min intervali%ie", seed_count, down_count, peer_count, erval, erval / 2); | 347 | erval / 2); |
| 346 | 348 | ||
| 347 | if (amount_v4) { | 349 | if (amount_v4) { |
| 348 | r += sprintf(r, PEERS_BENCODED4 "%zd:", OT_PEER_COMPARE_SIZE4 * amount_v4); | 350 | fmt_append(&r, r_end, PEERS_BENCODED4 "%zd:", OT_PEER_COMPARE_SIZE4 * amount_v4); |
| 349 | if (amount_v4 == torrent->peer_list4->peer_count) | 351 | if (amount_v4 == torrent->peer_list4->peer_count) |
| 350 | r += return_peers_all(torrent->peer_list4, OT_PEER_SIZE4, r); | 352 | r += return_peers_all(torrent->peer_list4, OT_PEER_SIZE4, r); |
| 351 | else | 353 | else |
| @@ -353,14 +355,14 @@ static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent | |||
| 353 | } | 355 | } |
| 354 | 356 | ||
| 355 | if (amount_v6) { | 357 | if (amount_v6) { |
| 356 | r += sprintf(r, PEERS_BENCODED6 "%zd:", OT_PEER_COMPARE_SIZE6 * amount_v6); | 358 | fmt_append(&r, r_end, PEERS_BENCODED6 "%zd:", OT_PEER_COMPARE_SIZE6 * amount_v6); |
| 357 | if (amount_v6 == torrent->peer_list6->peer_count) | 359 | if (amount_v6 == torrent->peer_list6->peer_count) |
| 358 | r += return_peers_all(torrent->peer_list6, OT_PEER_SIZE6, r); | 360 | r += return_peers_all(torrent->peer_list6, OT_PEER_SIZE6, r); |
| 359 | else | 361 | else |
| 360 | r += return_peers_selection(ws, torrent->peer_list6, OT_PEER_SIZE6, amount_v6, r); | 362 | r += return_peers_selection(ws, torrent->peer_list6, OT_PEER_SIZE6, amount_v6, r); |
| 361 | } | 363 | } |
| 362 | 364 | ||
| 363 | *r++ = 'e'; | 365 | fmt_append(&r, r_end, "e"); |
| 364 | 366 | ||
| 365 | return r - reply; | 367 | return r - reply; |
| 366 | } | 368 | } |
| @@ -403,12 +405,11 @@ size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply) { | |||
| 403 | } | 405 | } |
| 404 | 406 | ||
| 405 | /* Fetches scrape info for a specific torrent */ | 407 | /* Fetches scrape info for a specific torrent */ |
| 406 | size_t return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char *reply) { | 408 | void return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char **reply, const char *reply_end) { |
| 407 | char *r = reply; | 409 | int exactmatch; |
| 408 | int exactmatch; | ||
| 409 | size_t i; | 410 | size_t i; |
| 410 | 411 | ||
| 411 | r += sprintf(r, "d5:filesd"); | 412 | fmt_append(reply, reply_end, "d5:filesd"); |
| 412 | 413 | ||
| 413 | for (i = 0; i < amount; ++i) { | 414 | for (i = 0; i < amount; ++i) { |
| 414 | int delta_torrentcount = 0; | 415 | int delta_torrentcount = 0; |
| @@ -421,22 +422,17 @@ size_t return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, ch | |||
| 421 | vector_remove_torrent(torrents_list, torrent); | 422 | vector_remove_torrent(torrents_list, torrent); |
| 422 | delta_torrentcount = -1; | 423 | delta_torrentcount = -1; |
| 423 | } else { | 424 | } else { |
| 424 | *r++ = '2'; | 425 | fmt_append(reply, reply_end, "20:"); |
| 425 | *r++ = '0'; | 426 | fmt_bytes(reply, reply_end, hash, sizeof(ot_hash)); |
| 426 | *r++ = ':'; | 427 | fmt_append(reply, reply_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", |
| 427 | memcpy(r, hash, sizeof(ot_hash)); | 428 | torrent->peer_list6->seed_count + torrent->peer_list4->seed_count, torrent->peer_list6->down_count + torrent->peer_list4->down_count, |
| 428 | r += sizeof(ot_hash); | 429 | torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - torrent->peer_list6->seed_count - torrent->peer_list4->seed_count); |
| 429 | r += sprintf(r, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", torrent->peer_list6->seed_count + torrent->peer_list4->seed_count, | ||
| 430 | torrent->peer_list6->down_count + torrent->peer_list4->down_count, | ||
| 431 | torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - torrent->peer_list6->seed_count - torrent->peer_list4->seed_count); | ||
| 432 | } | 430 | } |
| 433 | } | 431 | } |
| 434 | mutex_bucket_unlock_by_hash(*hash, delta_torrentcount); | 432 | mutex_bucket_unlock_by_hash(*hash, delta_torrentcount); |
| 435 | } | 433 | } |
| 436 | 434 | ||
| 437 | *r++ = 'e'; | 435 | fmt_append(reply, reply_end, "ee"); |
| 438 | *r++ = 'e'; | ||
| 439 | return r - reply; | ||
| 440 | } | 436 | } |
| 441 | 437 | ||
| 442 | static ot_peerlist dummy_list; | 438 | static ot_peerlist dummy_list; |
| @@ -472,9 +468,12 @@ size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws) { | |||
| 472 | } | 468 | } |
| 473 | 469 | ||
| 474 | if (proto == FLAG_TCP) { | 470 | if (proto == FLAG_TCP) { |
| 475 | int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM; | 471 | int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM; |
| 476 | ws->reply_size = sprintf(ws->reply, "d8:completei%zde10:incompletei%zde8:intervali%ie12:min intervali%ie%s0:e", seed_count, peer_count - seed_count, erval, | 472 | char *r = ws->reply; |
| 477 | erval / 2, peer_size == OT_PEER_SIZE6 ? PEERS_BENCODED6 : PEERS_BENCODED4); | 473 | |
| 474 | 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, | ||
| 475 | erval, erval / 2, peer_size == OT_PEER_SIZE6 ? PEERS_BENCODED6 : PEERS_BENCODED4); | ||
| 476 | ws->reply_size = r - ws->reply; | ||
| 478 | } | 477 | } |
| 479 | 478 | ||
| 480 | /* Handle UDP reply */ | 479 | /* Handle UDP reply */ |
diff --git a/trackerlogic.h b/trackerlogic.h index 42018c3..63abf78 100644 --- a/trackerlogic.h +++ b/trackerlogic.h | |||
| @@ -187,7 +187,7 @@ void exerr(const char *message); | |||
| 187 | otherwise it is released in return_peers_for_torrent */ | 187 | otherwise it is released in return_peers_for_torrent */ |
| 188 | size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstruct *ws, size_t amount); | 188 | size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstruct *ws, size_t amount); |
| 189 | size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws); | 189 | size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws); |
| 190 | size_t return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char *reply); | 190 | void return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char **reply, const char *reply_end); |
| 191 | size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply); | 191 | size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply); |
| 192 | void add_torrent_from_saved_state(ot_hash const hash, ot_time base, size_t down_count); | 192 | void add_torrent_from_saved_state(ot_hash const hash, ot_time base, size_t down_count); |
| 193 | #ifdef _DEBUG_RANDOMTORRENTS | 193 | #ifdef _DEBUG_RANDOMTORRENTS |
