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/t_socket.c43
3 files changed, 157 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/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