summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--DESIGN39
-rw-r--r--Makefile21
-rw-r--r--Mk/proxy.mk2
-rw-r--r--Mk/tests.mk23
-rw-r--r--README_v61
-rw-r--r--opentracker.conf.sample6
-rw-r--r--ot_clean.h19
-rw-r--r--src/opentracker.c (renamed from opentracker.c)2
-rw-r--r--src/ot_accesslist.c (renamed from ot_accesslist.c)0
-rw-r--r--src/ot_accesslist.h (renamed from ot_accesslist.h)0
-rw-r--r--src/ot_batch.c (renamed from ot_batch.c)0
-rw-r--r--src/ot_batch.h (renamed from ot_batch.h)0
-rw-r--r--src/ot_buf.c (renamed from ot_buf.c)0
-rw-r--r--src/ot_buf.h (renamed from ot_buf.h)0
-rw-r--r--src/ot_clean.c (renamed from ot_clean.c)38
-rw-r--r--src/ot_clean.h28
-rw-r--r--src/ot_config.c (renamed from ot_config.c)0
-rw-r--r--src/ot_config.h (renamed from ot_config.h)0
-rw-r--r--src/ot_event.c (renamed from ot_event.c)0
-rw-r--r--src/ot_event.h (renamed from ot_event.h)0
-rw-r--r--src/ot_fdstate.c (renamed from ot_fdstate.c)0
-rw-r--r--src/ot_fdstate.h (renamed from ot_fdstate.h)0
-rw-r--r--src/ot_fmt.c (renamed from ot_fmt.c)0
-rw-r--r--src/ot_fmt.h (renamed from ot_fmt.h)0
-rw-r--r--src/ot_fullscrape.c (renamed from ot_fullscrape.c)0
-rw-r--r--src/ot_fullscrape.h (renamed from ot_fullscrape.h)0
-rw-r--r--src/ot_hex.c (renamed from ot_hex.c)0
-rw-r--r--src/ot_hex.h (renamed from ot_hex.h)0
-rw-r--r--src/ot_http.c (renamed from ot_http.c)0
-rw-r--r--src/ot_http.h (renamed from ot_http.h)0
-rw-r--r--src/ot_iovec.c (renamed from ot_iovec.c)0
-rw-r--r--src/ot_iovec.h (renamed from ot_iovec.h)0
-rw-r--r--src/ot_ip.c (renamed from ot_ip.c)0
-rw-r--r--src/ot_ip.h (renamed from ot_ip.h)0
-rw-r--r--src/ot_livesync.c (renamed from ot_livesync.c)32
-rw-r--r--src/ot_livesync.h (renamed from ot_livesync.h)3
-rw-r--r--src/ot_mutex.c (renamed from ot_mutex.c)0
-rw-r--r--src/ot_mutex.h (renamed from ot_mutex.h)0
-rw-r--r--src/ot_random.c (renamed from ot_random.c)0
-rw-r--r--src/ot_random.h (renamed from ot_random.h)0
-rw-r--r--src/ot_rijndael.c (renamed from ot_rijndael.c)0
-rw-r--r--src/ot_rijndael.h (renamed from ot_rijndael.h)0
-rw-r--r--src/ot_scan.c (renamed from ot_scan.c)0
-rw-r--r--src/ot_scan.h (renamed from ot_scan.h)0
-rw-r--r--src/ot_socket.c (renamed from ot_socket.c)0
-rw-r--r--src/ot_socket.h (renamed from ot_socket.h)0
-rw-r--r--src/ot_stats.c (renamed from ot_stats.c)0
-rw-r--r--src/ot_stats.h (renamed from ot_stats.h)0
-rw-r--r--src/ot_tcp.c (renamed from ot_tcp.c)0
-rw-r--r--src/ot_tcp.h (renamed from ot_tcp.h)0
-rw-r--r--src/ot_udp.c (renamed from ot_udp.c)0
-rw-r--r--src/ot_udp.h (renamed from ot_udp.h)0
-rw-r--r--src/ot_urlencoded.c (renamed from ot_urlencoded.c)0
-rw-r--r--src/ot_urlencoded.h (renamed from ot_urlencoded.h)0
-rw-r--r--src/ot_vector.c (renamed from ot_vector.c)0
-rw-r--r--src/ot_vector.h (renamed from ot_vector.h)0
-rw-r--r--src/ot_worker.c (renamed from ot_worker.c)2
-rw-r--r--src/ot_worker.h (renamed from ot_worker.h)0
-rw-r--r--src/ot_workqueue.c (renamed from ot_workqueue.c)0
-rw-r--r--src/ot_workqueue.h (renamed from ot_workqueue.h)0
-rw-r--r--src/proxy.c (renamed from proxy.c)0
-rw-r--r--src/trackerlogic.c (renamed from trackerlogic.c)6
-rw-r--r--src/trackerlogic.h (renamed from trackerlogic.h)7
-rw-r--r--tests/integration/harness.py34
-rw-r--r--tests/integration/test_livesync.py166
-rw-r--r--tests/sync_daemon.pl (renamed from sync_daemon.pl)0
-rw-r--r--tests/t_mutex.c2
-rw-r--r--tests/t_trackerlogic.c2
68 files changed, 365 insertions, 68 deletions
diff --git a/DESIGN b/DESIGN
index ff37706..f058fe0 100644
--- a/DESIGN
+++ b/DESIGN
@@ -16,7 +16,7 @@ A connection carries one request and closes. Keep-alive works and is rarely
16asked for. 16asked for.
17 17
18Statistics are approximate on purpose. Counters are per thread and summed 18Statistics are approximate on purpose. Counters are per thread and summed
19when read, so a /stats read can under-report once. Nothing drifts. 19when read, so a /stats read can under-report once.
20 20
21 21
22Decisions 22Decisions
@@ -60,9 +60,44 @@ connections, the filter serves more requests than no filter, not fewer, so it
60is not the denial of service vector that FreeBSD-SA-02:26 once described. 60is not the denial of service vector that FreeBSD-SA-02:26 once described.
61 61
62 62
63The torrent table is split into 2^OT_BUCKET_COUNT_BITS buckets, 1024 by
64default, set from the Makefile. Inserting a torrent opens a slot in a sorted
65vector, so the bytes moved grow with the bucket's occupancy. Insert cost per
66torrent, Linux:
67
68 2^10 2^12 2^14 2^16
69 200k 466 ns 439 ns
70 1M 834 ns 579 ns 529 ns
71 4M 2348 ns 1163 ns 770 ns 674 ns
72
73Re-announcing an existing torrent moves by under ten per cent over the same
74range, so the cost is paid while the torrent population is built and not in
75steady state. Below a few hundred thousand torrents there is nothing to win.
762^14 captures most of the gain for two per cent more resident memory.
77
78The cleaner's wake rate does not follow the bucket count. A cap of ten wakes
79a second sets the sleep, and a wake cleans as many buckets as it must to
80finish a sweep in OT_CLEAN_INTERVAL_MINUTES. At the default bucket count that
81is one bucket every 117 ms.
82
83
63Paths not taken 84Paths not taken
64--------------- 85---------------
65 86
87A keyed hash for the bucket index. Info hashes are SHA-1 output and already
88uniform, so it changes no distribution. It would buy resistance to deliberate
89bucket flooding and cost the ordering a full scrape depends on: buckets are
90ordered by hash prefix and sorted within, so walking them emits the sorted
91dictionary bencode wants, with no sort and no copy.
92
93Cache line padding for the per-bucket locks and vector headers. The
94contention it was meant to remove is not there. On glibc, where a 48-byte
95mutex straddles a line, padding gains 3 to 8 per cent, but gains as much at
96one thread where no false sharing can exist, and scaling from one to eight
97threads is identical either way. On FreeBSD, where eight 8-byte mutexes share
98a line and the risk should be highest, padding is 15 per cent slower at one
99thread and neutral to 6 per cent slower at four and eight.
100
66accf_http, the HTTP-aware accept filter. It duplicates a parser the tracker 101accf_http, the HTTP-aware accept filter. It duplicates a parser the tracker
67has anyway, puts more kernel code in the path, and holds a connection that 102has anyway, puts more kernel code in the path, and holds a connection that
68sends "GET /" and stops, which accf_data hands straight over. 103sends "GET /" and stops, which accf_data hands straight over.
@@ -79,8 +114,6 @@ A directory for object files. bmake has no pattern rules and the Makefile
79builds under FreeBSD's make today. Building each binary in one invocation 114builds under FreeBSD's make today. Building each binary in one invocation
80clears the objects out of the tree without giving that up. 115clears the objects out of the tree without giving that up.
81 116
82A src/ directory. 52 files sharing an ot_ prefix are a module list rather
83than clutter, and build output was most of what made the tree look crowded.
84 117
85 118
86Platform differences that cost us code 119Platform differences that cost us code
diff --git a/Makefile b/Makefile
index ddf1e05..d2e0c2d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,6 @@
1PREFIX?=.. 1PREFIX?=/usr/local
2BINDIR?=$(PREFIX)/bin 2BINDIR?=$(PREFIX)/bin
3MANDIR?=$(PREFIX)/share/man
3STRIP?=strip 4STRIP?=strip
4 5
5#FEATURES+=-DWANT_V4_ONLY 6#FEATURES+=-DWANT_V4_ONLY
@@ -19,6 +20,12 @@ STRIP?=strip
19#FEATURES+=-DWANT_DEV_RANDOM 20#FEATURES+=-DWANT_DEV_RANDOM
20FEATURES+=-DWANT_FULLSCRAPE 21FEATURES+=-DWANT_FULLSCRAPE
21 22
23# Buckets the torrent table is split into, as a power of two, 8 to 16. More
24# buckets make inserting a new torrent cheaper and cost memory, which is worth
25# it for a tracker carrying millions of torrents and not for one carrying
26# thousands. DESIGN has the measurements.
27FEATURES+=-DOT_BUCKET_COUNT_BITS=10
28
22#FEATURES+=-D_DEBUG_HTTPERROR 29#FEATURES+=-D_DEBUG_HTTPERROR
23#FEATURES+=-D_DEBUG_RANDOMTORRENTS 30#FEATURES+=-D_DEBUG_RANDOMTORRENTS
24 31
@@ -33,7 +40,6 @@ include Mk/gzip.mk
33 40
34CFLAGS?=-O3 41CFLAGS?=-O3
35 42
36# What the build requires, whatever you do to those.
37OT_CFLAGS+=-Wall -Wextra -Wwrite-strings -pipe -pthread 43OT_CFLAGS+=-Wall -Wextra -Wwrite-strings -pipe -pthread
38OT_LDFLAGS+=-pthread 44OT_LDFLAGS+=-pthread
39 45
@@ -43,8 +49,8 @@ COMPILE=$(CPPFLAGS) $(OT_CFLAGS) $(FEATURES)
43LINK=$(LDFLAGS) $(OT_LDFLAGS) $(LDLIBS) $(OT_LDLIBS) 49LINK=$(LDFLAGS) $(OT_LDFLAGS) $(LDLIBS) $(OT_LDLIBS)
44 50
45BINARY=opentracker 51BINARY=opentracker
46HEADERS=trackerlogic.h ot_urlencoded.h ot_batch.h ot_buf.h ot_event.h ot_fdstate.h ot_fmt.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_tcp.h ot_worker.h ot_workqueue.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 52HEADERS=src/trackerlogic.h src/ot_urlencoded.h src/ot_batch.h src/ot_buf.h src/ot_event.h src/ot_fdstate.h src/ot_fmt.h src/ot_hex.h src/ot_ip.h src/ot_config.h src/ot_scan.h src/ot_socket.h src/ot_tcp.h src/ot_worker.h src/ot_workqueue.h src/ot_mutex.h src/ot_random.h src/ot_stats.h src/ot_vector.h src/ot_clean.h src/ot_udp.h src/ot_iovec.h src/ot_fullscrape.h src/ot_accesslist.h src/ot_http.h src/ot_livesync.h src/ot_rijndael.h
47SOURCES=opentracker.c trackerlogic.c ot_urlencoded.c ot_batch.c ot_buf.c ot_event.c ot_fdstate.c ot_fmt.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_tcp.c ot_worker.c ot_workqueue.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 53SOURCES=src/opentracker.c src/trackerlogic.c src/ot_urlencoded.c src/ot_batch.c src/ot_buf.c src/ot_event.c src/ot_fdstate.c src/ot_fmt.c src/ot_hex.c src/ot_ip.c src/ot_config.c src/ot_scan.c src/ot_socket.c src/ot_tcp.c src/ot_worker.c src/ot_workqueue.c src/ot_mutex.c src/ot_random.c src/ot_stats.c src/ot_vector.c src/ot_clean.c src/ot_udp.c src/ot_iovec.c src/ot_fullscrape.c src/ot_accesslist.c src/ot_http.c src/ot_livesync.c src/ot_rijndael.c
48BINARIES=$(BINARY) $(BINARY).debug 54BINARIES=$(BINARY) $(BINARY).debug
49 55
50# Everything is built in one go from sources, so there are no object files to 56# Everything is built in one go from sources, so there are no object files to
@@ -67,10 +73,13 @@ $(BUILDSTAMP): FORCE
67 @echo "$(CFLAGS) $(COMPILE) $(LINK)" | cmp -s - $@ 2>/dev/null || echo "$(CFLAGS) $(COMPILE) $(LINK)" > $@ 73 @echo "$(CFLAGS) $(COMPILE) $(LINK)" | cmp -s - $@ 2>/dev/null || echo "$(CFLAGS) $(COMPILE) $(LINK)" > $@
68 74
69clean: 75clean:
70 rm -rf $(BINARIES) $(BINARIES:=.dSYM) $(BUILDSTAMP) *.o *~ 76 rm -rf $(BINARIES) $(BINARIES:=.dSYM) $(BUILDSTAMP) *.o *~ src/*.o src/*~
71 77
72install: 78install: $(BINARY)
79 install -d $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man1 $(DESTDIR)$(MANDIR)/man5
73 install -m 755 $(BINARY) $(DESTDIR)$(BINDIR) 80 install -m 755 $(BINARY) $(DESTDIR)$(BINDIR)
81 install -m 644 man1/opentracker.1 $(DESTDIR)$(MANDIR)/man1
82 install -m 644 man5/opentracker.conf.5 $(DESTDIR)$(MANDIR)/man5
74 83
75FORCE: 84FORCE:
76 85
diff --git a/Mk/proxy.mk b/Mk/proxy.mk
index 2e876e9..64d18dc 100644
--- a/Mk/proxy.mk
+++ b/Mk/proxy.mk
@@ -7,7 +7,7 @@
7# CFLAGS_libowfat=-I$(LIBOWFAT_HEADERS) 7# CFLAGS_libowfat=-I$(LIBOWFAT_HEADERS)
8# LDFLAGS_libowfat=-L$(LIBOWFAT_LIBRARY) -lowfat 8# LDFLAGS_libowfat=-L$(LIBOWFAT_LIBRARY) -lowfat
9# 9#
10# 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 10# SOURCES_proxy=src/proxy.c src/ot_batch.c src/ot_fdstate.c src/ot_vector.c src/ot_mutex.c src/ot_random.c src/ot_scan.c src/ot_ip.c src/ot_socket.c
11# CFLAGS_proxy=$(CFLAGS_production) $(CFLAGS_libowfat) -DWANT_SYNC_LIVE 11# CFLAGS_proxy=$(CFLAGS_production) $(CFLAGS_libowfat) -DWANT_SYNC_LIVE
12# CFLAGS_proxy_debug=$(CFLAGS_debug) $(CFLAGS_libowfat) -DWANT_SYNC_LIVE 12# CFLAGS_proxy_debug=$(CFLAGS_debug) $(CFLAGS_libowfat) -DWANT_SYNC_LIVE
13# 13#
diff --git a/Mk/tests.mk b/Mk/tests.mk
index 8e63839..2efdd72 100644
--- a/Mk/tests.mk
+++ b/Mk/tests.mk
@@ -5,13 +5,13 @@ BUILDDEPS+=Mk/tests.mk
5# the few symbols those modules reference but the tests never reach. 5# the few symbols those modules reference but the tests never reach.
6TESTBIN=tests/runner 6TESTBIN=tests/runner
7TEST_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_trackerlogic.c tests/t_workqueue.c tests/t_batch.c tests/t_fdstate.c tests/t_event.c tests/t_fmt.c 7TEST_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_trackerlogic.c tests/t_workqueue.c tests/t_batch.c tests/t_fdstate.c tests/t_event.c tests/t_fmt.c
8TEST_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 ot_workqueue.c trackerlogic.c 8TEST_UNITS=src/ot_urlencoded.c src/ot_fmt.c src/ot_event.c src/ot_batch.c src/ot_fdstate.c src/ot_buf.c src/ot_ip.c src/ot_socket.c src/ot_iovec.c src/ot_vector.c src/ot_random.c src/ot_hex.c src/ot_scan.c src/ot_accesslist.c src/ot_mutex.c src/ot_workqueue.c src/trackerlogic.c
9TEST_HEADERS=tests/test.h $(HEADERS) 9TEST_HEADERS=tests/test.h $(HEADERS)
10 10
11# A test binary is not a production binary, so it is built with the features 11# A test binary is not a production binary, so it is built with the features
12# the tests need to reach. 12# the tests need to reach.
13FEATURES_test=$(FEATURES) -DWANT_ACCESSLIST_BLACK -DWANT_SYNC_LIVE 13FEATURES_test=$(FEATURES) -DWANT_ACCESSLIST_BLACK -DWANT_SYNC_LIVE
14CFLAGS_test=-I. -g -O1 $(CPPFLAGS) $(OT_CFLAGS) $(FEATURES_test) 14CFLAGS_test=-Isrc -g -O1 $(CPPFLAGS) $(OT_CFLAGS) $(FEATURES_test)
15 15
16# clang over $(CC) because the two toolchains do not check the same things. 16# clang over $(CC) because the two toolchains do not check the same things.
17CC_sanitize?=clang 17CC_sanitize?=clang
@@ -29,7 +29,13 @@ LDFLAGS_integration=$(LINK) -fsanitize=address,undefined
29CFLAGS_tsan=$(OPTS_debug) $(CPPFLAGS) $(OT_CFLAGS) $(FEATURES_integration) -fsanitize=thread -fno-omit-frame-pointer 29CFLAGS_tsan=$(OPTS_debug) $(CPPFLAGS) $(OT_CFLAGS) $(FEATURES_integration) -fsanitize=thread -fno-omit-frame-pointer
30LDFLAGS_tsan=$(LINK) -fsanitize=thread 30LDFLAGS_tsan=$(LINK) -fsanitize=thread
31 31
32BINARIES+=$(TESTBIN) $(TESTBIN).poll $(TESTBIN).sanitize opentracker.integration opentracker.tsan 32# Live sync is a compile time feature, so reaching it from a black box test
33# needs a binary of its own. tests/integration/test_livesync.py skips itself
34# against a build without it, which is what -v is for.
35FEATURES_livesync=$(FEATURES_integration) -DWANT_SYNC_LIVE
36CFLAGS_livesync=$(OPTS_debug) $(CPPFLAGS) $(OT_CFLAGS) $(FEATURES_livesync) -fsanitize=address,undefined -fno-omit-frame-pointer
37
38BINARIES+=$(TESTBIN) $(TESTBIN).poll $(TESTBIN).sanitize opentracker.integration opentracker.tsan opentracker.livesync
33 39
34$(TESTBIN): $(TEST_SOURCES) $(TEST_UNITS) $(TEST_HEADERS) $(BUILDDEPS) 40$(TESTBIN): $(TEST_SOURCES) $(TEST_UNITS) $(TEST_HEADERS) $(BUILDDEPS)
35 $(CC) -o $@ $(CFLAGS_test) $(TEST_SOURCES) $(TEST_UNITS) 41 $(CC) -o $@ $(CFLAGS_test) $(TEST_SOURCES) $(TEST_UNITS)
@@ -46,6 +52,9 @@ opentracker.integration: $(SOURCES) $(HEADERS) $(BUILDDEPS)
46opentracker.tsan: $(SOURCES) $(HEADERS) $(BUILDDEPS) 52opentracker.tsan: $(SOURCES) $(HEADERS) $(BUILDDEPS)
47 $(CC) -o $@ $(CFLAGS_tsan) $(SOURCES) $(LDFLAGS_tsan) 53 $(CC) -o $@ $(CFLAGS_tsan) $(SOURCES) $(LDFLAGS_tsan)
48 54
55opentracker.livesync: $(SOURCES) $(HEADERS) $(BUILDDEPS)
56 $(CC) -o $@ $(CFLAGS_livesync) $(SOURCES) $(LDFLAGS_integration)
57
49check: $(TESTBIN) $(BINARY) 58check: $(TESTBIN) $(BINARY)
50 ./$(TESTBIN) 59 ./$(TESTBIN)
51 60
@@ -61,12 +70,18 @@ check-integration: opentracker.integration
61check-tsan: opentracker.tsan 70check-tsan: opentracker.tsan
62 OPENTRACKER=./opentracker.tsan $(PYTHON) -m unittest discover -s tests/integration 71 OPENTRACKER=./opentracker.tsan $(PYTHON) -m unittest discover -s tests/integration
63 72
73# The whole suite against a build that has live sync, which is also the only
74# way the live sync tests stop skipping.
75check-livesync: opentracker.livesync
76 OPENTRACKER=./opentracker.livesync $(PYTHON) -m unittest discover -s tests/integration
77
64# Cheapest first, so a quick failure comes back quickly. 78# Cheapest first, so a quick failure comes back quickly.
65check-all: 79check-all:
66 $(MAKE) check 80 $(MAKE) check
67 $(MAKE) check-poll 81 $(MAKE) check-poll
68 $(MAKE) check-sanitize 82 $(MAKE) check-sanitize
69 $(MAKE) check-integration 83 $(MAKE) check-integration
84 $(MAKE) check-livesync
70 $(MAKE) check-tsan 85 $(MAKE) check-tsan
71 86
72.PHONY: check check-poll check-sanitize check-integration check-tsan check-all 87.PHONY: check check-poll check-sanitize check-integration check-livesync check-tsan check-all
diff --git a/README_v6 b/README_v6
deleted file mode 100644
index 7fac79a..0000000
--- a/README_v6
+++ /dev/null
@@ -1 +0,0 @@
1IPv6 is implemented in opentracker now. You can chose whether your tracker runs in v4 or v6 mode in Makefile. YMMV.
diff --git a/opentracker.conf.sample b/opentracker.conf.sample
index 491c88a..3cd30dc 100644
--- a/opentracker.conf.sample
+++ b/opentracker.conf.sample
@@ -138,9 +138,11 @@
138# livesync.cluster.listen 192.168.0.1:9696 138# livesync.cluster.listen 192.168.0.1:9696
139# 139#
140# Note that two udp sockets will be opened. One on ip address 0.0.0.0 140# Note that two udp sockets will be opened. One on ip address 0.0.0.0
141# port 9696, that will join the multicast group 224.0.42.23 for incoming 141# port 9696, that will join the multicast group 224.0.23.5 for incoming
142# udp packets and one on ip address 192.168.0.1 port 9696 for outgoing 142# udp packets and one on ip address 192.168.0.1 port 9696 for outgoing
143# udp packets. 143# udp packets. The group is the same for every opentracker, so a cluster
144# is kept to itself by the node_ip list below and by the ttl of 1 that
145# keeps packets on the local link.
144# 146#
145# As of now one and only one ip address must be given, if opentracker 147# As of now one and only one ip address must be given, if opentracker
146# was built with the WANT_SYNC_LIVE feature. 148# was built with the WANT_SYNC_LIVE feature.
diff --git a/ot_clean.h b/ot_clean.h
deleted file mode 100644
index e069ee0..0000000
--- a/ot_clean.h
+++ /dev/null
@@ -1,19 +0,0 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_CLEAN_H__
5#define OT_CLEAN_H__
6
7#include "trackerlogic.h"
8
9/* The amount of time a clean cycle should take */
10#define OT_CLEAN_INTERVAL_MINUTES 2
11
12/* So after each bucket wait 1 / OT_BUCKET_COUNT intervals */
13#define OT_CLEAN_SLEEP (((OT_CLEAN_INTERVAL_MINUTES) * 60 * 1000000) / (OT_BUCKET_COUNT))
14
15void clean_init(void);
16void clean_deinit(void);
17int clean_single_torrent(ot_torrent *torrent);
18
19#endif
diff --git a/opentracker.c b/src/opentracker.c
index 14b817e..60a6d8c 100644
--- a/opentracker.c
+++ b/src/opentracker.c
@@ -109,7 +109,7 @@ static void *server_mainloop(void *args) {
109 109
110 (void)args; 110 (void)args;
111 111
112 if (workstruct_init(&ws) == -1) 112 if (workstruct_init(&ws, G_INBUF_SIZE) == -1)
113 panic("Initializing worker failed"); 113 panic("Initializing worker failed");
114 114
115 while (g_opentracker_running) { 115 while (g_opentracker_running) {
diff --git a/ot_accesslist.c b/src/ot_accesslist.c
index 86f9a7d..86f9a7d 100644
--- a/ot_accesslist.c
+++ b/src/ot_accesslist.c
diff --git a/ot_accesslist.h b/src/ot_accesslist.h
index 68c88dc..68c88dc 100644
--- a/ot_accesslist.h
+++ b/src/ot_accesslist.h
diff --git a/ot_batch.c b/src/ot_batch.c
index e4dda0b..e4dda0b 100644
--- a/ot_batch.c
+++ b/src/ot_batch.c
diff --git a/ot_batch.h b/src/ot_batch.h
index 910bea7..910bea7 100644
--- a/ot_batch.h
+++ b/src/ot_batch.h
diff --git a/ot_buf.c b/src/ot_buf.c
index ecd3f8d..ecd3f8d 100644
--- a/ot_buf.c
+++ b/src/ot_buf.c
diff --git a/ot_buf.h b/src/ot_buf.h
index 7fb944e..7fb944e 100644
--- a/ot_buf.h
+++ b/src/ot_buf.h
diff --git a/ot_clean.c b/src/ot_clean.c
index e1d1971..f181e83 100644
--- a/ot_clean.c
+++ b/src/ot_clean.c
@@ -125,26 +125,34 @@ static void clean_sleep(void) {
125 pthread_mutex_unlock(&g_clean_mutex); 125 pthread_mutex_unlock(&g_clean_mutex);
126} 126}
127 127
128/* Clean up all peers in current bucket, remove timedout pools and 128/* Clean up all peers in one bucket, remove timedout pools and torrents */
129 torrents */ 129static void clean_bucket(size_t bucket) {
130 ot_vector *torrents_list = mutex_bucket_lock((int)bucket);
131 size_t toffs;
132 int delta_torrentcount = 0;
133
134 for (toffs = 0; toffs < torrents_list->size; ++toffs) {
135 ot_torrent *torrent = ((ot_torrent *)(torrents_list->data)) + toffs;
136 if (clean_single_torrent(torrent)) {
137 vector_remove_torrent(torrents_list, torrent);
138 --delta_torrentcount;
139 --toffs;
140 }
141 }
142 mutex_bucket_unlock((int)bucket, delta_torrentcount);
143}
144
130static void *clean_worker(void *args) { 145static void *clean_worker(void *args) {
131 (void)args; 146 (void)args;
132 while (1) { 147 while (1) {
133 size_t bucket = OT_BUCKET_COUNT; 148 size_t bucket = OT_BUCKET_COUNT;
134 while (bucket--) { 149 while (bucket) {
135 ot_vector *torrents_list = mutex_bucket_lock(bucket); 150 size_t todo = bucket < OT_CLEAN_BUCKETS_PER_WAKE ? bucket : OT_CLEAN_BUCKETS_PER_WAKE;
136 size_t toffs; 151
137 int delta_torrentcount = 0; 152 /* One bucket is locked at a time, however many a wake gets through */
153 while (todo--)
154 clean_bucket(--bucket);
138 155
139 for (toffs = 0; toffs < torrents_list->size; ++toffs) {
140 ot_torrent *torrent = ((ot_torrent *)(torrents_list->data)) + toffs;
141 if (clean_single_torrent(torrent)) {
142 vector_remove_torrent(torrents_list, torrent);
143 --delta_torrentcount;
144 --toffs;
145 }
146 }
147 mutex_bucket_unlock(bucket, delta_torrentcount);
148 if (!g_opentracker_running) 156 if (!g_opentracker_running)
149 return NULL; 157 return NULL;
150 clean_sleep(); 158 clean_sleep();
diff --git a/src/ot_clean.h b/src/ot_clean.h
new file mode 100644
index 0000000..0592791
--- /dev/null
+++ b/src/ot_clean.h
@@ -0,0 +1,28 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4#ifndef OT_CLEAN_H__
5#define OT_CLEAN_H__
6
7#include "trackerlogic.h"
8
9/* The amount of time a clean cycle should take */
10#define OT_CLEAN_INTERVAL_MINUTES 2
11
12/* How often the cleaner may wake. A full sweep takes OT_CLEAN_INTERVAL_MINUTES.
13 The clean operations per wake is goverend by OT_BUCKET_COUNT_BITS,
14 set by the Makefile.
15 At the default of 1024 buckets this works out at one bucket every
16 117 ms. */
17#define OT_CLEAN_MAX_WAKES_PER_SECOND 10
18
19#define OT_CLEAN_WAKE_BUDGET ((OT_CLEAN_INTERVAL_MINUTES) * 60 * (OT_CLEAN_MAX_WAKES_PER_SECOND))
20#define OT_CLEAN_BUCKETS_PER_WAKE (((OT_BUCKET_COUNT) + (OT_CLEAN_WAKE_BUDGET) - 1) / (OT_CLEAN_WAKE_BUDGET))
21#define OT_CLEAN_WAKES ((OT_BUCKET_COUNT) / (OT_CLEAN_BUCKETS_PER_WAKE))
22#define OT_CLEAN_SLEEP (((OT_CLEAN_INTERVAL_MINUTES) * 60 * 1000000) / (OT_CLEAN_WAKES))
23
24void clean_init(void);
25void clean_deinit(void);
26int clean_single_torrent(ot_torrent *torrent);
27
28#endif
diff --git a/ot_config.c b/src/ot_config.c
index 95136b5..95136b5 100644
--- a/ot_config.c
+++ b/src/ot_config.c
diff --git a/ot_config.h b/src/ot_config.h
index 79187ee..79187ee 100644
--- a/ot_config.h
+++ b/src/ot_config.h
diff --git a/ot_event.c b/src/ot_event.c
index 6062f70..6062f70 100644
--- a/ot_event.c
+++ b/src/ot_event.c
diff --git a/ot_event.h b/src/ot_event.h
index cb84ca8..cb84ca8 100644
--- a/ot_event.h
+++ b/src/ot_event.h
diff --git a/ot_fdstate.c b/src/ot_fdstate.c
index 8e4bd89..8e4bd89 100644
--- a/ot_fdstate.c
+++ b/src/ot_fdstate.c
diff --git a/ot_fdstate.h b/src/ot_fdstate.h
index 60fe070..60fe070 100644
--- a/ot_fdstate.h
+++ b/src/ot_fdstate.h
diff --git a/ot_fmt.c b/src/ot_fmt.c
index 362c9b5..362c9b5 100644
--- a/ot_fmt.c
+++ b/src/ot_fmt.c
diff --git a/ot_fmt.h b/src/ot_fmt.h
index 214cbc4..214cbc4 100644
--- a/ot_fmt.h
+++ b/src/ot_fmt.h
diff --git a/ot_fullscrape.c b/src/ot_fullscrape.c
index 9afad92..9afad92 100644
--- a/ot_fullscrape.c
+++ b/src/ot_fullscrape.c
diff --git a/ot_fullscrape.h b/src/ot_fullscrape.h
index 8d11cd3..8d11cd3 100644
--- a/ot_fullscrape.h
+++ b/src/ot_fullscrape.h
diff --git a/ot_hex.c b/src/ot_hex.c
index b537094..b537094 100644
--- a/ot_hex.c
+++ b/src/ot_hex.c
diff --git a/ot_hex.h b/src/ot_hex.h
index ddbbb44..ddbbb44 100644
--- a/ot_hex.h
+++ b/src/ot_hex.h
diff --git a/ot_http.c b/src/ot_http.c
index 96b8f4e..96b8f4e 100644
--- a/ot_http.c
+++ b/src/ot_http.c
diff --git a/ot_http.h b/src/ot_http.h
index fb56c64..fb56c64 100644
--- a/ot_http.h
+++ b/src/ot_http.h
diff --git a/ot_iovec.c b/src/ot_iovec.c
index 328f160..328f160 100644
--- a/ot_iovec.c
+++ b/src/ot_iovec.c
diff --git a/ot_iovec.h b/src/ot_iovec.h
index 4a8abbb..4a8abbb 100644
--- a/ot_iovec.h
+++ b/src/ot_iovec.h
diff --git a/ot_ip.c b/src/ot_ip.c
index 9c76290..9c76290 100644
--- a/ot_ip.c
+++ b/src/ot_ip.c
diff --git a/ot_ip.h b/src/ot_ip.h
index 533403e..533403e 100644
--- a/ot_ip.h
+++ b/src/ot_ip.h
diff --git a/ot_livesync.c b/src/ot_livesync.c
index 083fe1a..11b75d2 100644
--- a/ot_livesync.c
+++ b/src/ot_livesync.c
@@ -4,6 +4,7 @@
4/* System */ 4/* System */
5#include <arpa/inet.h> 5#include <arpa/inet.h>
6#include <pthread.h> 6#include <pthread.h>
7#include <stdio.h>
7#include <stdlib.h> 8#include <stdlib.h>
8#include <string.h> 9#include <string.h>
9#include <sys/socket.h> 10#include <sys/socket.h>
@@ -22,6 +23,7 @@
22#ifdef WANT_SYNC_LIVE 23#ifdef WANT_SYNC_LIVE
23 24
24ot_ip6 groupip_1 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 224, 0, 23, 5}; 25ot_ip6 groupip_1 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 224, 0, 23, 5};
26static uint16_t g_port = LIVESYNC_PORT;
25 27
26#define LIVESYNC_INCOMING_BUFFSIZE (256 * 256) 28#define LIVESYNC_INCOMING_BUFFSIZE (256 * 256)
27 29
@@ -59,7 +61,7 @@ void livesync_init(void) {
59 uint32_t packet_type4 = htonl(OT_SYNC_PEER4); 61 uint32_t packet_type4 = htonl(OT_SYNC_PEER4);
60 62
61 if (g_socket_in == -1) 63 if (g_socket_in == -1)
62 exerr("No socket address for live sync specified."); 64 return;
63 65
64 /* Prepare outgoing peers buffer */ 66 /* Prepare outgoing peers buffer */
65 memcpy(g_v6_buf.data, &g_tracker_id, sizeof(g_tracker_id)); 67 memcpy(g_v6_buf.data, &g_tracker_id, sizeof(g_tracker_id));
@@ -101,6 +103,8 @@ void livesync_bind_mcast(ot_ip6 ip, uint16_t port) {
101 if (g_socket_in != -1) 103 if (g_socket_in != -1)
102 exerr("Error: Livesync listen ip specified twice."); 104 exerr("Error: Livesync listen ip specified twice.");
103 105
106 g_port = port;
107
104 if ((g_socket_in = socket_mcast_join(groupip_1, ip, port)) == -1) 108 if ((g_socket_in = socket_mcast_join(groupip_1, ip, port)) == -1)
105 exerr("Error: Cant bind live sync incoming socket."); 109 exerr("Error: Cant bind live sync incoming socket.");
106 110
@@ -121,7 +125,7 @@ static void livesync_issue_peersync(sync_buffer *buf) {
121 has modified the protected element */ 125 has modified the protected element */
122 pthread_mutex_unlock(&g_outbuf_mutex); 126 pthread_mutex_unlock(&g_outbuf_mutex);
123 127
124 socket_mcast_send(g_socket_out, mycopy, fill, groupip_1, LIVESYNC_PORT); 128 socket_mcast_send(g_socket_out, mycopy, fill, groupip_1, g_port);
125} 129}
126 130
127static void livesync_handle_peersync(struct ot_workstruct *ws, size_t peer_size) { 131static void livesync_handle_peersync(struct ot_workstruct *ws, size_t peer_size) {
@@ -159,6 +163,9 @@ static void livesync_handle_peersync(struct ot_workstruct *ws, size_t peer_size)
159 stuck when there's not enough traffic to fill udp packets fast 163 stuck when there's not enough traffic to fill udp packets fast
160 enough */ 164 enough */
161void livesync_ticker(void) { 165void livesync_ticker(void) {
166 if (g_socket_out == -1)
167 return;
168
162 /* livesync_issue_peersync sets g_next_packet_time */ 169 /* livesync_issue_peersync sets g_next_packet_time */
163 pthread_mutex_lock(&g_outbuf_mutex); 170 pthread_mutex_lock(&g_outbuf_mutex);
164 if (time_now_seconds() > g_v6_buf.next_packet_time && g_v6_buf.fill > sizeof(g_tracker_id) + sizeof(uint32_t)) 171 if (time_now_seconds() > g_v6_buf.next_packet_time && g_v6_buf.fill > sizeof(g_tracker_id) + sizeof(uint32_t))
@@ -176,8 +183,14 @@ void livesync_ticker(void) {
176/* Inform live sync about whats going on. */ 183/* Inform live sync about whats going on. */
177void livesync_tell(struct ot_workstruct *ws) { 184void livesync_tell(struct ot_workstruct *ws) {
178 size_t peer_size; /* initialized in next line */ 185 size_t peer_size; /* initialized in next line */
179 ot_peer *peer_src = peer_from_peer6(&ws->peer, &peer_size); 186 ot_peer *peer_src;
180 sync_buffer *dest_buf = peer_size == OT_PEER_SIZE6 ? &g_v6_buf : &g_v4_buf; 187 sync_buffer *dest_buf;
188
189 if (g_socket_out == -1)
190 return;
191
192 peer_src = peer_from_peer6(&ws->peer, &peer_size);
193 dest_buf = peer_size == OT_PEER_SIZE6 ? &g_v6_buf : &g_v4_buf;
181 194
182 pthread_mutex_lock(&g_outbuf_mutex); 195 pthread_mutex_lock(&g_outbuf_mutex);
183 196
@@ -200,9 +213,12 @@ static void *livesync_worker(void *args) {
200 213
201 (void)args; 214 (void)args;
202 215
203 /* Initialize our "thread local storage" */ 216 if (workstruct_init(&ws, LIVESYNC_INCOMING_BUFFSIZE) == -1) {
204 ws.inbuf = ws.request = malloc(LIVESYNC_INCOMING_BUFFSIZE); 217 fprintf(stderr, "Warning: live sync could not get its buffers and will not run.\n");
205 ws.outbuf = ws.reply = 0; 218 atomic_store(&thread_left, 1);
219 return NULL;
220 }
221 ws.request = ws.inbuf;
206 222
207 while (g_opentracker_running) { 223 while (g_opentracker_running) {
208 uint32_t packet_type; 224 uint32_t packet_type;
@@ -233,7 +249,7 @@ static void *livesync_worker(void *args) {
233 } 249 }
234 } 250 }
235 251
236 free(ws.inbuf); 252 workstruct_deinit(&ws);
237 atomic_store(&thread_left, 1); 253 atomic_store(&thread_left, 1);
238 return NULL; 254 return NULL;
239} 255}
diff --git a/ot_livesync.h b/src/ot_livesync.h
index 4bd3af0..d8d20b3 100644
--- a/ot_livesync.h
+++ b/src/ot_livesync.h
@@ -7,7 +7,8 @@
7#include "trackerlogic.h" 7#include "trackerlogic.h"
8 8
9/* 9/*
10 Syncing is done as udp packets in the multicast domain 224.0.42.5 port 9696 10 Syncing is done as udp packets in the multicast group 224.0.23.5, on the
11 port livesync.cluster.listen names, 9696 by default
11 12
12 Each tracker should join the multicast group and send its live sync packets 13 Each tracker should join the multicast group and send its live sync packets
13 to that group, using a ttl of 1 14 to that group, using a ttl of 1
diff --git a/ot_mutex.c b/src/ot_mutex.c
index 884b74c..884b74c 100644
--- a/ot_mutex.c
+++ b/src/ot_mutex.c
diff --git a/ot_mutex.h b/src/ot_mutex.h
index d7ded92..d7ded92 100644
--- a/ot_mutex.h
+++ b/src/ot_mutex.h
diff --git a/ot_random.c b/src/ot_random.c
index 43f91bb..43f91bb 100644
--- a/ot_random.c
+++ b/src/ot_random.c
diff --git a/ot_random.h b/src/ot_random.h
index c2d0387..c2d0387 100644
--- a/ot_random.h
+++ b/src/ot_random.h
diff --git a/ot_rijndael.c b/src/ot_rijndael.c
index 3f36bde..3f36bde 100644
--- a/ot_rijndael.c
+++ b/src/ot_rijndael.c
diff --git a/ot_rijndael.h b/src/ot_rijndael.h
index 1235215..1235215 100644
--- a/ot_rijndael.h
+++ b/src/ot_rijndael.h
diff --git a/ot_scan.c b/src/ot_scan.c
index 3b8e87c..3b8e87c 100644
--- a/ot_scan.c
+++ b/src/ot_scan.c
diff --git a/ot_scan.h b/src/ot_scan.h
index a79ec13..a79ec13 100644
--- a/ot_scan.h
+++ b/src/ot_scan.h
diff --git a/ot_socket.c b/src/ot_socket.c
index 79bb01a..79bb01a 100644
--- a/ot_socket.c
+++ b/src/ot_socket.c
diff --git a/ot_socket.h b/src/ot_socket.h
index 5c1d840..5c1d840 100644
--- a/ot_socket.h
+++ b/src/ot_socket.h
diff --git a/ot_stats.c b/src/ot_stats.c
index 2d6335b..2d6335b 100644
--- a/ot_stats.c
+++ b/src/ot_stats.c
diff --git a/ot_stats.h b/src/ot_stats.h
index 45a547e..45a547e 100644
--- a/ot_stats.h
+++ b/src/ot_stats.h
diff --git a/ot_tcp.c b/src/ot_tcp.c
index 1351b92..1351b92 100644
--- a/ot_tcp.c
+++ b/src/ot_tcp.c
diff --git a/ot_tcp.h b/src/ot_tcp.h
index afed142..afed142 100644
--- a/ot_tcp.h
+++ b/src/ot_tcp.h
diff --git a/ot_udp.c b/src/ot_udp.c
index 644b71d..644b71d 100644
--- a/ot_udp.c
+++ b/src/ot_udp.c
diff --git a/ot_udp.h b/src/ot_udp.h
index 2710744..2710744 100644
--- a/ot_udp.h
+++ b/src/ot_udp.h
diff --git a/ot_urlencoded.c b/src/ot_urlencoded.c
index 4109bb5..4109bb5 100644
--- a/ot_urlencoded.c
+++ b/src/ot_urlencoded.c
diff --git a/ot_urlencoded.h b/src/ot_urlencoded.h
index 9882468..9882468 100644
--- a/ot_urlencoded.h
+++ b/src/ot_urlencoded.h
diff --git a/ot_vector.c b/src/ot_vector.c
index ab0566c..ab0566c 100644
--- a/ot_vector.c
+++ b/src/ot_vector.c
diff --git a/ot_vector.h b/src/ot_vector.h
index 8a5ba86..8a5ba86 100644
--- a/ot_vector.h
+++ b/src/ot_vector.h
diff --git a/ot_worker.c b/src/ot_worker.c
index 432d272..ef50945 100644
--- a/ot_worker.c
+++ b/src/ot_worker.c
@@ -20,7 +20,7 @@ static size_t g_worker_count;
20static void *worker_trampoline(void *args) { 20static void *worker_trampoline(void *args) {
21 struct ot_worker *me = args; 21 struct ot_worker *me = args;
22 22
23 if (workstruct_init(&me->ws) == -1) 23 if (workstruct_init(&me->ws, G_INBUF_SIZE) == -1)
24 fprintf(stderr, "Warning: a worker could not get its buffers and will not run.\n"); 24 fprintf(stderr, "Warning: a worker could not get its buffers and will not run.\n");
25 else { 25 else {
26 me->run(me); 26 me->run(me);
diff --git a/ot_worker.h b/src/ot_worker.h
index 4954773..4954773 100644
--- a/ot_worker.h
+++ b/src/ot_worker.h
diff --git a/ot_workqueue.c b/src/ot_workqueue.c
index 56a7cd2..56a7cd2 100644
--- a/ot_workqueue.c
+++ b/src/ot_workqueue.c
diff --git a/ot_workqueue.h b/src/ot_workqueue.h
index 82ae29b..82ae29b 100644
--- a/ot_workqueue.h
+++ b/src/ot_workqueue.h
diff --git a/proxy.c b/src/proxy.c
index 8589db5..8589db5 100644
--- a/proxy.c
+++ b/src/proxy.c
diff --git a/trackerlogic.c b/src/trackerlogic.c
index 9565090..8e30a48 100644
--- a/trackerlogic.c
+++ b/src/trackerlogic.c
@@ -567,9 +567,11 @@ void trackerlogic_init(void) {
567 stats_init(); 567 stats_init();
568} 568}
569 569
570int workstruct_init(struct ot_workstruct *ws) { 570/* Live sync reads whole packets rather than requests and wants a larger
571 inbuf than the request path does, hence the size. */
572int workstruct_init(struct ot_workstruct *ws, size_t inbuf_size) {
571 memset(ws, 0, sizeof(*ws)); 573 memset(ws, 0, sizeof(*ws));
572 ws->inbuf = malloc(G_INBUF_SIZE); 574 ws->inbuf = malloc(inbuf_size);
573 ws->outbuf = malloc(G_OUTBUF_SIZE); 575 ws->outbuf = malloc(G_OUTBUF_SIZE);
574#ifdef _DEBUG_HTTPERROR 576#ifdef _DEBUG_HTTPERROR
575 ws->debugbuf = malloc(G_DEBUGBUF_SIZE); 577 ws->debugbuf = malloc(G_DEBUGBUF_SIZE);
diff --git a/trackerlogic.h b/src/trackerlogic.h
index 338e4e6..692b6af 100644
--- a/trackerlogic.h
+++ b/src/trackerlogic.h
@@ -62,7 +62,12 @@ typedef struct {
62 62
63/* We maintain a list of 1024 pointers to sorted list of ot_torrent structs 63/* We maintain a list of 1024 pointers to sorted list of ot_torrent structs
64 Sort key is, of course, its hash */ 64 Sort key is, of course, its hash */
65/* Set from the Makefile, 8 to 16. More buckets means shorter vectors and a
66 cheaper insert, which only pays for a tracker carrying millions of
67 torrents; see DESIGN. */
68#ifndef OT_BUCKET_COUNT_BITS
65#define OT_BUCKET_COUNT_BITS 10 69#define OT_BUCKET_COUNT_BITS 10
70#endif
66 71
67#define OT_BUCKET_COUNT (1 << OT_BUCKET_COUNT_BITS) 72#define OT_BUCKET_COUNT (1 << OT_BUCKET_COUNT_BITS)
68#define OT_BUCKET_COUNT_SHIFT (32 - OT_BUCKET_COUNT_BITS) 73#define OT_BUCKET_COUNT_SHIFT (32 - OT_BUCKET_COUNT_BITS)
@@ -237,7 +242,7 @@ struct ot_workstruct {
237/* Everything a thread that serves requests needs for itself. Set up and torn 242/* Everything a thread that serves requests needs for itself. Set up and torn
238 down in one place so that a new kind of worker cannot miss a piece. 243 down in one place so that a new kind of worker cannot miss a piece.
239 Returns -1 if the buffers could not be had. */ 244 Returns -1 if the buffers could not be had. */
240int workstruct_init(struct ot_workstruct *ws); 245int workstruct_init(struct ot_workstruct *ws, size_t inbuf_size);
241void workstruct_deinit(struct ot_workstruct *ws); 246void workstruct_deinit(struct ot_workstruct *ws);
242 247
243/* 248/*
diff --git a/tests/integration/harness.py b/tests/integration/harness.py
index b98ce83..a9d38ca 100644
--- a/tests/integration/harness.py
+++ b/tests/integration/harness.py
@@ -20,6 +20,9 @@ import urllib.error
20import urllib.parse 20import urllib.parse
21import urllib.request 21import urllib.request
22 22
23# groupip_1 in src/ot_livesync.c
24LIVESYNC_GROUP = "224.0.23.5"
25
23TRACKER = os.environ.get("OPENTRACKER", os.path.join(os.path.dirname(__file__), "..", "..", "opentracker.debug")) 26TRACKER = os.environ.get("OPENTRACKER", os.path.join(os.path.dirname(__file__), "..", "..", "opentracker.debug"))
24 27
25 28
@@ -76,6 +79,9 @@ class Tracker:
76 79
77 def __init__(self, extra_config="", files=None): 80 def __init__(self, extra_config="", files=None):
78 self.port = free_port() 81 self.port = free_port()
82 # Only used by a config that asks for live sync, but allocated either
83 # way so {livesync_port} always resolves.
84 self.livesync_port = free_port()
79 self.dir = tempfile.mkdtemp(prefix="ot-integration-") 85 self.dir = tempfile.mkdtemp(prefix="ot-integration-")
80 self.log = None 86 self.log = None
81 self.process = None 87 self.process = None
@@ -87,7 +93,7 @@ class Tracker:
87 with open(self.config, "w") as handle: 93 with open(self.config, "w") as handle:
88 handle.write("listen.tcp 127.0.0.1:%d\n" % self.port) 94 handle.write("listen.tcp 127.0.0.1:%d\n" % self.port)
89 handle.write("listen.udp 127.0.0.1:%d\n" % self.port) 95 handle.write("listen.udp 127.0.0.1:%d\n" % self.port)
90 handle.write(extra_config.replace("{dir}", self.dir)) 96 handle.write(extra_config.replace("{dir}", self.dir).replace("{livesync_port}", str(self.livesync_port)))
91 self.log = open(os.path.join(self.dir, "tracker.log"), "w+b") 97 self.log = open(os.path.join(self.dir, "tracker.log"), "w+b")
92 # Leak detection is left at the sanitizer's own default, which is on 98 # Leak detection is left at the sanitizer's own default, which is on
93 # where LeakSanitizer exists and off where it does not. Asking for it 99 # where LeakSanitizer exists and off where it does not. Asking for it
@@ -186,6 +192,32 @@ class Tracker:
186 def torrent_count(self): 192 def torrent_count(self):
187 return int(self.stats("torr").split()[0]) 193 return int(self.stats("torr").split()[0])
188 194
195 def livesync_send(self, payload):
196 """Put a packet on the live sync group.
197
198 It has to go to the group rather than straight at the port: the
199 tracker binds two sockets there, the reader on the wildcard address
200 and the writer on the configured one, and the kernel gives a unicast
201 datagram to the more specific of the two, which nobody reads."""
202 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
203 try:
204 sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton("127.0.0.1"))
205 sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 1)
206 sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 1)
207 sock.sendto(payload, (LIVESYNC_GROUP, self.livesync_port))
208 finally:
209 sock.close()
210
211 def wait_until(self, predicate, timeout=5):
212 """A live sync packet is handled by another thread, so an effect of
213 one arrives some time after the send."""
214 deadline = time.time() + timeout
215 while time.time() < deadline:
216 if predicate():
217 return True
218 time.sleep(0.02)
219 return predicate()
220
189 # ------------------------------------------------------------ over udp 221 # ------------------------------------------------------------ over udp
190 222
191 def udp_connect(self): 223 def udp_connect(self):
diff --git a/tests/integration/test_livesync.py b/tests/integration/test_livesync.py
new file mode 100644
index 0000000..3a9ee58
--- /dev/null
+++ b/tests/integration/test_livesync.py
@@ -0,0 +1,166 @@
1"""Live sync, driven over the multicast group a cluster would use.
2
3Live sync sits behind WANT_SYNC_LIVE, so these only run against a binary
4built with it: make check-livesync. Against any other build they skip, which
5is what the feature list in -v is for.
6"""
7
8import socket
9import struct
10import subprocess
11import unittest
12
13from harness import TRACKER, TrackerTestCase
14
15# enum { OT_SYNC_PEER4, OT_SYNC_PEER6 } in src/ot_livesync.c
16SYNC_PEER4 = 0
17SYNC_PEER6 = 1
18
19# src/trackerlogic.h
20FLAG_SEEDING = 0x80
21FLAG_COMPLETED = 0x40
22FLAG_STOPPED = 0x20
23
24
25def has_livesync():
26 done = subprocess.run([TRACKER, "-v"], capture_output=True, timeout=30)
27 return b"WANT_SYNC_LIVE" in done.stderr
28
29
30def peer4(address, port, flags):
31 """Four bytes of address, two of port, one of flags, one the tracker
32 overwrites with its own idea of the time."""
33 return socket.inet_aton(address) + struct.pack("!H", port) + bytes([flags, 0])
34
35
36def peer6(address, port, flags):
37 return socket.inet_pton(socket.AF_INET6, address) + struct.pack("!H", port) + bytes([flags, 0])
38
39
40def packet(kind, entries, tracker_id=0xDEADBEEF):
41 body = b"".join(info_hash + peer for info_hash, peer in entries)
42 return struct.pack("!I", tracker_id) + struct.pack("!I", kind) + body
43
44
45@unittest.skipUnless(has_livesync(), "this build has no WANT_SYNC_LIVE")
46class LiveSyncIn(TrackerTestCase):
47 config = "livesync.cluster.listen 127.0.0.1:{livesync_port}\nlivesync.cluster.node_ip 127.0.0.1\n"
48
49 def counts(self, info_hash):
50 """Seeders and leechers a scrape reports for one torrent."""
51 files = self.tracker.scrape(info_hash)[b"files"]
52 if info_hash not in files:
53 return None
54 entry = files[info_hash]
55 return entry[b"complete"], entry[b"incomplete"]
56
57 def test_a_synced_seeder_arrives(self):
58 info_hash = self.hash_for("sync-seed")
59 self.tracker.livesync_send(packet(SYNC_PEER4, [(info_hash, peer4("10.1.2.3", 6881, FLAG_SEEDING))]))
60 self.assertTrue(self.tracker.wait_until(lambda: self.counts(info_hash) == (1, 0)),
61 "scrape says %r" % (self.counts(info_hash),))
62
63 def test_a_synced_leecher_arrives(self):
64 info_hash = self.hash_for("sync-leech")
65 self.tracker.livesync_send(packet(SYNC_PEER4, [(info_hash, peer4("10.1.2.4", 6881, 0))]))
66 self.assertTrue(self.tracker.wait_until(lambda: self.counts(info_hash) == (0, 1)),
67 "scrape says %r" % (self.counts(info_hash),))
68
69 def test_a_v6_peer_arrives(self):
70 info_hash = self.hash_for("sync-v6")
71 self.tracker.livesync_send(packet(SYNC_PEER6, [(info_hash, peer6("2001:db8::1", 6881, FLAG_SEEDING))]))
72 self.assertTrue(self.tracker.wait_until(lambda: self.counts(info_hash) == (1, 0)),
73 "scrape says %r" % (self.counts(info_hash),))
74
75 def test_one_packet_carries_several_torrents(self):
76 hashes = [self.hash_for("sync-many-%d" % i) for i in range(4)]
77 self.tracker.livesync_send(packet(SYNC_PEER4, [(h, peer4("10.1.2.%d" % (5 + i), 6881, FLAG_SEEDING))
78 for i, h in enumerate(hashes)]))
79 self.assertTrue(self.tracker.wait_until(lambda: all(self.counts(h) == (1, 0) for h in hashes)))
80
81 def test_a_stopped_peer_is_taken_away_again(self):
82 info_hash = self.hash_for("sync-stop")
83 peer = peer4("10.1.2.9", 6881, FLAG_SEEDING)
84 self.tracker.livesync_send(packet(SYNC_PEER4, [(info_hash, peer)]))
85 self.assertTrue(self.tracker.wait_until(lambda: self.counts(info_hash) == (1, 0)))
86
87 self.tracker.livesync_send(packet(SYNC_PEER4, [(info_hash, peer4("10.1.2.9", 6881, FLAG_STOPPED))]))
88 self.assertTrue(self.tracker.wait_until(lambda: self.counts(info_hash) in (None, (0, 0))),
89 "scrape still says %r" % (self.counts(info_hash),))
90
91 def test_a_truncated_entry_is_dropped(self):
92 """The loop takes whole hash and peer pairs and leaves a short tail
93 where it is, so the complete entry before it still lands."""
94 good, short = self.hash_for("sync-good"), self.hash_for("sync-short")
95 payload = packet(SYNC_PEER4, [(good, peer4("10.1.2.10", 6881, FLAG_SEEDING))]) + short + b"\x0a\x01"
96 self.tracker.livesync_send(payload)
97 self.assertTrue(self.tracker.wait_until(lambda: self.counts(good) == (1, 0)))
98 self.assertIsNone(self.counts(short))
99
100 def test_an_unknown_packet_type_is_ignored(self):
101 info_hash = self.hash_for("sync-unknown")
102 self.tracker.livesync_send(packet(99, [(info_hash, peer4("10.1.2.11", 6881, FLAG_SEEDING))]))
103 self.assertFalse(self.tracker.wait_until(lambda: self.counts(info_hash) is not None, timeout=1))
104
105 def test_synced_peers_are_counted(self):
106 """The live sync worker keeps its own tally like every other worker,
107 and ?mode=syncs is the sum of them."""
108 before = int(self.tracker.stats("syncs").split()[0])
109 hashes = [self.hash_for("sync-count-%d" % i) for i in range(3)]
110 self.tracker.livesync_send(packet(SYNC_PEER4, [(h, peer4("10.1.2.%d" % (20 + i), 6881, FLAG_SEEDING))
111 for i, h in enumerate(hashes)]))
112 self.assertTrue(self.tracker.wait_until(
113 lambda: int(self.tracker.stats("syncs").split()[0]) == before + 3),
114 "syncs says %s" % self.tracker.stats("syncs").split()[0])
115
116 def test_a_packet_with_nothing_but_a_header_is_ignored(self):
117 before = self.tracker.torrent_count()
118 self.tracker.livesync_send(struct.pack("!I", 0xDEADBEEF) + struct.pack("!I", SYNC_PEER4))
119 self.assertFalse(self.tracker.wait_until(lambda: self.tracker.torrent_count() != before, timeout=1))
120
121
122@unittest.skipUnless(has_livesync(), "this build has no WANT_SYNC_LIVE")
123class LiveSyncAccess(TrackerTestCase):
124 """Nothing blesses 127.0.0.1 here, so the tracker should take nothing
125 from it. Source address checking is all live sync has."""
126
127 config = "livesync.cluster.listen 127.0.0.1:{livesync_port}\nlivesync.cluster.node_ip 10.0.0.1\n"
128
129 def test_an_unblessed_sender_is_ignored(self):
130 info_hash = self.hash_for("sync-unblessed")
131 self.tracker.livesync_send(packet(SYNC_PEER4, [(info_hash, peer4("10.1.2.12", 6881, FLAG_SEEDING))]))
132 self.assertFalse(self.tracker.wait_until(lambda: self.tracker.torrent_count() != 0, timeout=1))
133
134
135@unittest.skipUnless(has_livesync(), "this build has no WANT_SYNC_LIVE")
136class LiveSyncUnconfigured(TrackerTestCase):
137 """A binary that has the feature but no cluster to join. Somebody whose
138 packager turned WANT_SYNC_LIVE on did not ask for live sync and should
139 not be able to tell that it is there."""
140
141 config = ""
142
143 def test_the_tracker_runs_anyway(self):
144 info_hash = self.hash_for("no-cluster")
145 self.tracker.announce(info_hash, port=6881, left=0)
146 self.assertEqual(self.tracker.torrent_count(), 1)
147
148 def test_nothing_is_said_about_the_missing_cluster(self):
149 self.assertNotIn("sync", self.tracker.output().lower())
150
151 def test_no_socket_is_bound_for_it(self):
152 """A configured tracker holds the wildcard address on that port, so
153 taking it here proves nothing was started."""
154 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
155 try:
156 sock.bind(("0.0.0.0", self.tracker.livesync_port))
157 finally:
158 sock.close()
159
160 def test_announcing_stays_clean(self):
161 """Enough announces to pass the watermark that would flush an
162 outgoing buffer, if there were one to flush. The sanitizers in this
163 build turn anything the attempt touched into a failure."""
164 for i in range(200):
165 self.tracker.announce(self.hash_for("no-cluster-%d" % i), port=6881 + i, left=0)
166 self.assertEqual(self.tracker.torrent_count(), 200)
diff --git a/sync_daemon.pl b/tests/sync_daemon.pl
index 9e4bdb9..9e4bdb9 100644
--- a/sync_daemon.pl
+++ b/tests/sync_daemon.pl
diff --git a/tests/t_mutex.c b/tests/t_mutex.c
index e54e2fa..c0c41cc 100644
--- a/tests/t_mutex.c
+++ b/tests/t_mutex.c
@@ -24,7 +24,7 @@ static void hash_for_bucket(ot_hash hash, int bucket) {
24} 24}
25 25
26static void case_a_hash_names_its_bucket(void) { 26static void case_a_hash_names_its_bucket(void) {
27 static const int buckets[] = {0, 1, 2, 17, 511, 512, OT_BUCKET_COUNT - 1}; 27 static const int buckets[] = {0, 1, 2, 17, OT_BUCKET_COUNT / 2 - 1, OT_BUCKET_COUNT / 2, OT_BUCKET_COUNT - 1};
28 size_t i; 28 size_t i;
29 29
30 test_case("a hash is served by the bucket its leading bits name"); 30 test_case("a hash is served by the bucket its leading bits name");
diff --git a/tests/t_trackerlogic.c b/tests/t_trackerlogic.c
index f64977f..1cf9141 100644
--- a/tests/t_trackerlogic.c
+++ b/tests/t_trackerlogic.c
@@ -224,7 +224,7 @@ void test_trackerlogic(void) {
224 pthread_sigmask(SIG_BLOCK, &signal_mask, NULL); 224 pthread_sigmask(SIG_BLOCK, &signal_mask, NULL);
225 225
226 trackerlogic_init(); 226 trackerlogic_init();
227 CHECK_INT(workstruct_init(&ws), 0); 227 CHECK_INT(workstruct_init(&ws, G_INBUF_SIZE), 0);
228 228
229 case_an_announce_makes_a_torrent(); 229 case_an_announce_makes_a_torrent();
230 case_the_same_peer_twice_is_one_peer(); 230 case_the_same_peer_twice_is_one_peer();