summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/accept_filter_probe.py110
-rw-r--r--tests/integration/test_expiry.py7
-rw-r--r--tests/syscount.c144
-rw-r--r--tests/t_event.c37
-rw-r--r--tests/t_socket.c43
5 files changed, 338 insertions, 3 deletions
diff --git a/tests/accept_filter_probe.py b/tests/accept_filter_probe.py
new file mode 100644
index 0000000..22f585a
--- /dev/null
+++ b/tests/accept_filter_probe.py
@@ -0,0 +1,110 @@
1"""What a deferred accept does to connections that never send anything.
2
3Those connections never reach opentracker, so its own client timeout cannot
4clean them up: whatever bound exists is the kernel's. This measures it, and
5then asks the question that follows from it -- whether enough of them keep
6the tracker from answering anybody else.
7
8 make opentracker
9 python3 tests/accept_filter_probe.py
10
11On FreeBSD the filter does nothing unless accf_data is loaded, so run it both
12ways to see whether the numbers differ:
13
14 kldload accf_data
15"""
16
17import os
18import socket
19import signal
20import subprocess
21import sys
22import tempfile
23import time
24import urllib.error
25import urllib.request
26
27TRACKER = os.environ.get("OPENTRACKER", "./opentracker")
28IDLE = int(os.environ.get("IDLE", "300"))
29PATIENCE = float(os.environ.get("PATIENCE", "90"))
30HASH = "".join("%%%02x" % b for b in range(1, 21))
31
32
33def start(port):
34 directory = tempfile.mkdtemp(prefix="ot-accf-")
35 with open(os.path.join(directory, "conf"), "w") as handle:
36 handle.write("listen.tcp 127.0.0.1:%d\nlisten.udp 127.0.0.1:%d\n" % (port, port))
37 log = open(os.path.join(directory, "log"), "w+b")
38 tracker = subprocess.Popen([TRACKER, "-f", os.path.join(directory, "conf")], stdout=log, stderr=log)
39 for _ in range(300):
40 try:
41 socket.create_connection(("127.0.0.1", port), timeout=0.2).close()
42 return tracker, log
43 except OSError:
44 time.sleep(0.02)
45 tracker.kill()
46 raise SystemExit("tracker never listened on %d" % port)
47
48
49def announce(port, timeout=5):
50 """One ordinary request, so we can see whether the tracker still serves."""
51 started = time.time()
52 try:
53 urllib.request.urlopen(
54 "http://127.0.0.1:%d/announce?info_hash=%s&peer_id=-OT0001-probe0000001&port=6881&left=0" % (port, HASH),
55 timeout=timeout,
56 ).read()
57 return time.time() - started, None
58 except Exception as problem: # noqa: BLE001 - reported, not swallowed
59 return time.time() - started, problem
60
61
62def main():
63 port = int(os.environ.get("PORT", "28000"))
64 tracker, log = start(port)
65 try:
66 print(" how long a connection that says nothing is held")
67 quiet = socket.create_connection(("127.0.0.1", port), timeout=5)
68 quiet.settimeout(PATIENCE)
69 started = time.time()
70 try:
71 held = "dropped after %.1f s" % (time.time() - started) if quiet.recv(1) == b"" else "sent us something"
72 except socket.timeout:
73 held = "still open after %.0f s" % PATIENCE
74 except OSError as problem:
75 held = "ended after %.1f s: %s" % (time.time() - started, problem)
76 quiet.close()
77 print(" %s" % held)
78
79 print(" and whether %d of them keep anybody else out" % IDLE)
80 before, problem = announce(port)
81 print(" a request before: %.0f ms%s" % (before * 1000, "" if problem is None else " (%s)" % problem))
82
83 idlers = []
84 for _ in range(IDLE):
85 try:
86 idlers.append(socket.create_connection(("127.0.0.1", port), timeout=2))
87 except OSError as problem:
88 print(" could only open %d of them: %s" % (len(idlers), problem))
89 break
90 print(" holding %d connections that have sent nothing" % len(idlers))
91
92 during, problem = announce(port, timeout=10)
93 print(" a request during: %.0f ms%s" % (during * 1000, "" if problem is None else " REFUSED: %s" % problem))
94
95 for idler in idlers:
96 idler.close()
97 after, problem = announce(port)
98 print(" a request after: %.0f ms%s" % (after * 1000, "" if problem is None else " (%s)" % problem))
99 finally:
100 tracker.send_signal(signal.SIGINT)
101 try:
102 tracker.wait(timeout=10)
103 except subprocess.TimeoutExpired:
104 tracker.kill()
105 tracker.wait()
106 log.close()
107
108
109if __name__ == "__main__":
110 main()
diff --git a/tests/integration/test_expiry.py b/tests/integration/test_expiry.py
index 5a21e57..5e6dc0a 100644
--- a/tests/integration/test_expiry.py
+++ b/tests/integration/test_expiry.py
@@ -27,6 +27,11 @@ class ExpirySweep(TrackerTestCase):
27 def test_the_sweep_takes_the_idle_and_leaves_the_answered(self): 27 def test_the_sweep_takes_the_idle_and_leaves_the_answered(self):
28 idle = socket.create_connection(("127.0.0.1", self.tracker.port), timeout=5) 28 idle = socket.create_connection(("127.0.0.1", self.tracker.port), timeout=5)
29 try: 29 try:
30 # Enough for a kernel that holds a connection back until data
31 # arrives to hand it over, and far too little to be answered, so
32 # the tracker has accepted it and is waiting for the rest
33 idle.sendall(b"GET /ann")
34
30 self.assertEqual(self.tracker.announce(self.hash_for("expiry-sweep"))[b"complete"], 1) 35 self.assertEqual(self.tracker.announce(self.hash_for("expiry-sweep"))[b"complete"], 1)
31 36
32 time.sleep(CLIENT_TIMEOUT + 3 * CHECK_INTERVAL) 37 time.sleep(CLIENT_TIMEOUT + 3 * CHECK_INTERVAL)
@@ -34,6 +39,6 @@ class ExpirySweep(TrackerTestCase):
34 self.assertEqual(self.tracker.torrent_count(), 1, "the tracker stopped serving while sweeping") 39 self.assertEqual(self.tracker.torrent_count(), 1, "the tracker stopped serving while sweeping")
35 40
36 idle.settimeout(5) 41 idle.settimeout(5)
37 self.assertEqual(idle.recv(64), b"", "a connection that never sent a request was not swept") 42 self.assertEqual(idle.recv(64), b"", "a connection that went quiet mid-request was not swept")
38 finally: 43 finally:
39 idle.close() 44 idle.close()
diff --git a/tests/syscount.c b/tests/syscount.c
new file mode 100644
index 0000000..ec45c15
--- /dev/null
+++ b/tests/syscount.c
@@ -0,0 +1,144 @@
1/* Counts the system calls a run makes, by standing in front of libc.
2
3 cc -O2 -shared -fPIC -o syscount.so tests/syscount.c -ldl
4 LD_PRELOAD=./syscount.so ./opentracker -f conf (DYLD_INSERT_LIBRARIES on macOS)
5
6 The tally goes to stderr when the process exits. Reading the source tells
7 you which calls a path should make; this tells you which it does make,
8 which is not always the same thing. */
9
10#define _GNU_SOURCE
11#include <dlfcn.h>
12#include <stdarg.h>
13#include <stdatomic.h>
14#include <stdio.h>
15#include <sys/socket.h>
16#include <sys/types.h>
17#include <unistd.h>
18
19#ifdef __linux__
20#include <sys/epoll.h>
21#endif
22
23#define COUNTER(name) static _Atomic long g_##name
24#define BUMP(name) atomic_fetch_add_explicit(&g_##name, 1, memory_order_relaxed)
25#define REPORT(name) fprintf(stderr, " %-14s %10ld\n", #name, atomic_load(&g_##name))
26
27COUNTER(accept);
28COUNTER(accept4);
29COUNTER(read);
30COUNTER(write);
31COUNTER(close);
32COUNTER(fcntl);
33COUNTER(recvfrom);
34COUNTER(sendto);
35COUNTER(epoll_ctl);
36COUNTER(epoll_wait);
37
38typedef int (*fn_accept)(int, struct sockaddr *, socklen_t *);
39typedef int (*fn_accept4)(int, struct sockaddr *, socklen_t *, int);
40typedef ssize_t (*fn_read)(int, void *, size_t);
41typedef ssize_t (*fn_write)(int, const void *, size_t);
42typedef int (*fn_close)(int);
43typedef int (*fn_fcntl)(int, int, ...);
44typedef ssize_t (*fn_recvfrom)(int, void *, size_t, int, struct sockaddr *, socklen_t *);
45typedef ssize_t (*fn_sendto)(int, const void *, size_t, int, const struct sockaddr *, socklen_t);
46
47static fn_accept real_accept;
48static fn_accept4 real_accept4;
49static fn_read real_read;
50static fn_write real_write;
51static fn_close real_close;
52static fn_fcntl real_fcntl;
53static fn_recvfrom real_recvfrom;
54static fn_sendto real_sendto;
55
56/* Resolving lazily keeps the library out of the way until something is
57 actually called, which matters because dlsym itself allocates */
58#define WRAP(name) \
59 if (!real_##name) \
60 real_##name = (fn_##name)dlsym(RTLD_NEXT, #name); \
61 BUMP(name)
62
63int accept(int s, struct sockaddr *addr, socklen_t *len) {
64 WRAP(accept);
65 return real_accept(s, addr, len);
66}
67
68#ifdef SOCK_NONBLOCK
69int accept4(int s, struct sockaddr *addr, socklen_t *len, int flags) {
70 WRAP(accept4);
71 return real_accept4(s, addr, len, flags);
72}
73#endif
74
75ssize_t read(int fd, void *buf, size_t count) {
76 WRAP(read);
77 return real_read(fd, buf, count);
78}
79
80ssize_t write(int fd, const void *buf, size_t count) {
81 WRAP(write);
82 return real_write(fd, buf, count);
83}
84
85int close(int fd) {
86 WRAP(close);
87 return real_close(fd);
88}
89
90int fcntl(int fd, int cmd, ...) {
91 va_list args;
92 long arg;
93
94 va_start(args, cmd);
95 arg = va_arg(args, long);
96 va_end(args);
97 WRAP(fcntl);
98 return real_fcntl(fd, cmd, arg);
99}
100
101ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen) {
102 WRAP(recvfrom);
103 return real_recvfrom(s, buf, len, flags, from, fromlen);
104}
105
106ssize_t sendto(int s, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) {
107 WRAP(sendto);
108 return real_sendto(s, buf, len, flags, to, tolen);
109}
110
111#ifdef __linux__
112typedef int (*fn_epoll_ctl)(int, int, int, struct epoll_event *);
113typedef int (*fn_epoll_wait)(int, struct epoll_event *, int, int);
114static fn_epoll_ctl real_epoll_ctl;
115static fn_epoll_wait real_epoll_wait;
116
117int epoll_ctl(int ep, int op, int fd, struct epoll_event *ev) {
118 WRAP(epoll_ctl);
119 return real_epoll_ctl(ep, op, fd, ev);
120}
121
122int epoll_wait(int ep, struct epoll_event *events, int max, int timeout) {
123 WRAP(epoll_wait);
124 return real_epoll_wait(ep, events, max, timeout);
125}
126#endif
127
128__attribute__((destructor)) static void report(void) {
129 fprintf(stderr, "\n system calls this run\n");
130 REPORT(accept);
131#ifdef SOCK_NONBLOCK
132 REPORT(accept4);
133#endif
134 REPORT(read);
135 REPORT(write);
136 REPORT(close);
137 REPORT(fcntl);
138 REPORT(recvfrom);
139 REPORT(sendto);
140#ifdef __linux__
141 REPORT(epoll_ctl);
142 REPORT(epoll_wait);
143#endif
144}
diff --git a/tests/t_event.c b/tests/t_event.c
index 5509f19..ad3948f 100644
--- a/tests/t_event.c
+++ b/tests/t_event.c
@@ -4,6 +4,7 @@
4 $id$ */ 4 $id$ */
5 5
6/* System */ 6/* System */
7#include <fcntl.h>
7#include <string.h> 8#include <string.h>
8#include <sys/socket.h> 9#include <sys/socket.h>
9#include <sys/time.h> 10#include <sys/time.h>
@@ -205,6 +206,41 @@ static void case_unknown_descriptor(void) {
205 CHECK_INT(event_del(4096), -1); 206 CHECK_INT(event_del(4096), -1);
206} 207}
207 208
209/* event_close exists so that the mechanisms which drop a closed descriptor
210 by themselves are not asked to do it twice. The one that keeps its own
211 list has to be told, and forgetting would leave the entry there to be
212 reported for ever, so what is checked here is the silence afterwards. */
213static void case_close_takes_it_out(void) {
214 int other[2], closed, count, i, strays = 0;
215
216 test_case("a closed descriptor is gone from the set and really closed");
217 pair_open();
218 CHECK(sv[0] != -1);
219 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, other), 0);
220 CHECK_INT(event_add(sv[0], EVENT_IN, 1), 0);
221 CHECK_INT(event_add(other[0], EVENT_IN, 2), 0);
222
223 closed = sv[0];
224 CHECK_INT(event_close(closed), 0);
225 sv[0] = -1;
226 CHECK_INT(fcntl(closed, F_GETFD), -1);
227
228 /* The survivor is made ready so the wait has something to come back with,
229 and anything left over from the closed one shows up beside it */
230 CHECK_INT((int)write(other[1], "x", 1), 1);
231 count = event_wait(ready, EVENTS, 200);
232 CHECK(count >= 1);
233 for (i = 0; i < count; ++i)
234 if (ready[i].fd != other[0])
235 ++strays;
236 CHECK_INT(strays, 0);
237
238 event_del(other[0]);
239 close(other[0]);
240 close(other[1]);
241 pair_close();
242}
243
208void test_event(void) { 244void test_event(void) {
209 CHECK_INT(event_init(), 0); 245 CHECK_INT(event_init(), 0);
210 CHECK(event_mechanism() != NULL); 246 CHECK(event_mechanism() != NULL);
@@ -217,6 +253,7 @@ void test_event(void) {
217 case_delete_from_the_middle(); 253 case_delete_from_the_middle();
218 case_timeout(); 254 case_timeout();
219 case_unknown_descriptor(); 255 case_unknown_descriptor();
256 case_close_takes_it_out();
220 257
221 event_deinit(); 258 event_deinit();
222} 259}
diff --git a/tests/t_socket.c b/tests/t_socket.c
index ab4b785..20a0c0a 100644
--- a/tests/t_socket.c
+++ b/tests/t_socket.c
@@ -78,15 +78,17 @@ static int sock_flag(int s, int level, int option) {
78} 78}
79 79
80/* Wait for one event, so a test never blocks forever on a lost packet */ 80/* Wait for one event, so a test never blocks forever on a lost packet */
81static int wait_for(int s, short events) { 81static int wait_for_ms(int s, short events, int timeout_ms) {
82 struct pollfd pfd; 82 struct pollfd pfd;
83 83
84 pfd.fd = s; 84 pfd.fd = s;
85 pfd.events = events; 85 pfd.events = events;
86 pfd.revents = 0; 86 pfd.revents = 0;
87 return poll(&pfd, 1, 2000) == 1; 87 return poll(&pfd, 1, timeout_ms) == 1;
88} 88}
89 89
90static int wait_for(int s, short events) { return wait_for_ms(s, events, 2000); }
91
90/* ------------------------------------------------------------ invariants */ 92/* ------------------------------------------------------------ invariants */
91 93
92/* Asked for the wildcard address, because a socket bound to one specific 94/* Asked for the wildcard address, because a socket bound to one specific
@@ -200,6 +202,42 @@ static void case_a_signal_ends_a_blocking_read(void) {
200 close(reader_socket); 202 close(reader_socket);
201} 203}
202 204
205/* What a deferred accept is for is a descriptor that already has the request
206 in it, so what is checked is the silence before: a client that has
207 connected but said nothing must not make the listener readable. Kernels
208 that cannot do this say so, and then there is nothing to check. */
209static void case_deferred_accept(void) {
210 struct sockaddr_in6 address;
211 char ip[16];
212 uint16_t port = 0;
213 int listener, client;
214
215 test_case("a connection that has sent nothing is held back from accept");
216 listener = socket_tcp_listen(ip_loopback, 0, SOCKET_EXCLUSIVE);
217 CHECK(listener != -1);
218 if (socket_defer_accept(listener) == -1) {
219 close(listener);
220 return;
221 }
222 bound_address(listener, ip, &port);
223
224 client = socket(AF_INET6, SOCK_STREAM, 0);
225 CHECK(client != -1);
226 memset(&address, 0, sizeof(address));
227 address.sin6_family = AF_INET6;
228 address.sin6_port = htons(port);
229 memcpy(&address.sin6_addr, ip_loopback, 16);
230 CHECK_INT(connect(client, (struct sockaddr *)&address, sizeof(address)), 0);
231
232 CHECK_INT(wait_for_ms(listener, POLLIN, 300), 0);
233
234 CHECK_INT((int)write(client, "GET / HTTP/1.0\r\n\r\n", 18), 18);
235 CHECK_INT(wait_for(listener, POLLIN), 1);
236
237 close(client);
238 close(listener);
239}
240
203/* An IPv4 peer must arrive in the v4 mapped form opentracker stores, however 241/* An IPv4 peer must arrive in the v4 mapped form opentracker stores, however
204 the kernel chose to report it. Linux fills a v4 mapped sockaddr_in6 here 242 the kernel chose to report it. Linux fills a v4 mapped sockaddr_in6 here
205 and the BSDs fill a plain sockaddr_in, so this is the case that tells the 243 and the BSDs fill a plain sockaddr_in, so this is the case that tells the
@@ -352,6 +390,7 @@ void test_socket(void) {
352 case_a_signal_ends_a_blocking_read(); 390 case_a_signal_ends_a_blocking_read();
353 case_udp_v4mapped(); 391 case_udp_v4mapped();
354 case_tcp_roundtrip(); 392 case_tcp_roundtrip();
393 case_deferred_accept();
355 case_connect_refused(); 394 case_connect_refused();
356 case_bind_failure(); 395 case_bind_failure();
357#ifdef WANT_SYNC_LIVE 396#ifdef WANT_SYNC_LIVE