summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-11-08 20:08:44 +0100
committerDirk Engling <erdgeist@erdgeist.org>2026-11-08 20:08:44 +0100
commit1c2af535eb3052405503f136578e30b24a8e18ae (patch)
tree4842c19092623f0c8d666fe97065be329ad538c1
parent0d4be1abb93d37b10e0c7835bd79b62639945651 (diff)
Retire the busy network tracker. /24 networks don't say anything anymore in 2026 and in v6 it was broken from the beginning
-rw-r--r--src/ot_http.c1
-rw-r--r--src/ot_stats.c56
-rw-r--r--src/ot_workqueue.h1
-rw-r--r--tests/integration/test_concurrency.py2
-rw-r--r--tests/integration/test_stats.py47
5 files changed, 5 insertions, 102 deletions
diff --git a/src/ot_http.c b/src/ot_http.c
index 262936e..70176c3 100644
--- a/src/ot_http.c
+++ b/src/ot_http.c
@@ -279,7 +279,6 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
279 {"tcp4", TASK_STATS_TCP}, 279 {"tcp4", TASK_STATS_TCP},
280 {"torr", TASK_STATS_TORRENTS}, 280 {"torr", TASK_STATS_TORRENTS},
281 {"fscr", TASK_STATS_FULLSCRAPE}, 281 {"fscr", TASK_STATS_FULLSCRAPE},
282 {"s24s", TASK_STATS_SLASH24S},
283 {"tpbs", TASK_STATS_TPB}, 282 {"tpbs", TASK_STATS_TPB},
284 {"herr", TASK_STATS_HTTPERRORS}, 283 {"herr", TASK_STATS_HTTPERRORS},
285 {"completed", TASK_STATS_COMPLETED}, 284 {"completed", TASK_STATS_COMPLETED},
diff --git a/src/ot_stats.c b/src/ot_stats.c
index 15c06b2..ba780d3 100644
--- a/src/ot_stats.c
+++ b/src/ot_stats.c
@@ -91,12 +91,13 @@ static const char *ot_failed_request_names[] = {
91 91
92static time_t ot_start_time; 92static time_t ot_start_time;
93 93
94#ifdef WANT_SPOT_WOODPECKER
94#define STATS_NETWORK_NODE_BITWIDTH 4 95#define STATS_NETWORK_NODE_BITWIDTH 4
95#define STATS_NETWORK_NODE_COUNT (1 << STATS_NETWORK_NODE_BITWIDTH) 96#define STATS_NETWORK_NODE_COUNT (1 << STATS_NETWORK_NODE_BITWIDTH)
96 97
97#define __BYTE(P, D) (((uint8_t *)P)[D / 8]) 98#define __BYTE(P, D) (((uint8_t *)(P))[(D) / 8])
98#define __MSK (STATS_NETWORK_NODE_COUNT - 1) 99#define __MSK (STATS_NETWORK_NODE_COUNT - 1)
99#define __SHFT(D) ((D ^ STATS_NETWORK_NODE_BITWIDTH) & STATS_NETWORK_NODE_BITWIDTH) 100#define __SHFT(D) (((D) ^ STATS_NETWORK_NODE_BITWIDTH) & STATS_NETWORK_NODE_BITWIDTH)
100 101
101#define __LDR(P, D) ((__BYTE((P), (D)) >> __SHFT((D))) & __MSK) 102#define __LDR(P, D) ((__BYTE((P), (D)) >> __SHFT((D))) & __MSK)
102#define __STR(P, D, V) __BYTE((P), (D)) = (__BYTE((P), (D)) & ~(__MSK << __SHFT((D)))) | ((V) << __SHFT((D))) 103#define __STR(P, D, V) __BYTE((P), (D)) = (__BYTE((P), (D)) & ~(__MSK << __SHFT((D)))) | ((V) << __SHFT((D)))
@@ -236,54 +237,6 @@ static void stats_return_busy_networks(char **reply, const char *reply_end, stat
236 fmt_append(reply, reply_end, "\n"); 237 fmt_append(reply, reply_end, "\n");
237} 238}
238 239
239static void stats_slash24s_txt(char **reply, const char *reply_end, size_t amount) {
240 stats_network_node *slash24s_network_counters_root = NULL;
241 size_t i, bucket, peer_size = OT_PEER_SIZE4;
242
243 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
244 ot_vector *torrents_list = mutex_bucket_lock(bucket);
245 for (i = 0; i < torrents_list->size; ++i) {
246 ot_peerlist *peer_list = (((ot_torrent *)(torrents_list->data))[i]).peer_list;
247 ot_vector *bucket_list;
248 int num_buckets = 1;
249
250 if (!peer_list)
251 continue;
252 bucket_list = &peer_list->peers4; /* /24 networks are an IPv4 notion */
253
254 if (OT_VECTOR_HASBUCKETS(bucket_list)) {
255 num_buckets = bucket_list->size;
256 bucket_list = (ot_vector *)bucket_list->data;
257 }
258
259 while (num_buckets--) {
260 ot_peer *peers = (ot_peer *)bucket_list->data;
261 size_t numpeers = bucket_list->size;
262 while (numpeers--) {
263 if (stat_increase_network_count(&slash24s_network_counters_root, 0, (uintptr_t)(peers)))
264 goto bailout_unlock;
265 peers += peer_size;
266 }
267 ++bucket_list;
268 }
269 }
270 mutex_bucket_unlock(bucket, 0);
271 if (!g_opentracker_running)
272 goto done;
273 }
274
275 /* The tree is built. Now analyze */
276 stats_return_busy_networks(reply, reply_end, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_MAXDEPTH);
277 stats_return_busy_networks(reply, reply_end, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_LIMIT);
278 goto done;
279
280bailout_unlock:
281 mutex_bucket_unlock(bucket, 0);
282done:
283 stats_shift_down_network_count(&slash24s_network_counters_root, 0, sizeof(int) * 8 - 1);
284}
285
286#ifdef WANT_SPOT_WOODPECKER
287static stats_network_node *stats_woodpeckers_tree; 240static stats_network_node *stats_woodpeckers_tree;
288static pthread_mutex_t g_woodpeckers_mutex = PTHREAD_MUTEX_INITIALIZER; 241static pthread_mutex_t g_woodpeckers_mutex = PTHREAD_MUTEX_INITIALIZER;
289 242
@@ -699,9 +652,6 @@ static void stats_make(size_t *iovec_entries, struct iovec **iovector, ot_taskty
699 case TASK_STATS_PEERS: 652 case TASK_STATS_PEERS:
700 stats_peers_mrtg(&r, r_end); 653 stats_peers_mrtg(&r, r_end);
701 break; 654 break;
702 case TASK_STATS_SLASH24S:
703 stats_slash24s_txt(&r, r_end, 128);
704 break;
705 case TASK_STATS_TOP10: 655 case TASK_STATS_TOP10:
706 stats_top_txt(&r, r_end, 10); 656 stats_top_txt(&r, r_end, 10);
707 break; 657 break;
diff --git a/src/ot_workqueue.h b/src/ot_workqueue.h
index 82ae29b..d8f30d6 100644
--- a/src/ot_workqueue.h
+++ b/src/ot_workqueue.h
@@ -29,7 +29,6 @@ typedef enum {
29 TASK_STATS = 0x0100, /* Mask */ 29 TASK_STATS = 0x0100, /* Mask */
30 TASK_STATS_TORRENTS = 0x0101, 30 TASK_STATS_TORRENTS = 0x0101,
31 TASK_STATS_PEERS = 0x0102, 31 TASK_STATS_PEERS = 0x0102,
32 TASK_STATS_SLASH24S = 0x0103,
33 TASK_STATS_TOP10 = 0x0104, 32 TASK_STATS_TOP10 = 0x0104,
34 TASK_STATS_TOP100 = 0x0105, 33 TASK_STATS_TOP100 = 0x0105,
35 TASK_STATS_EVERYTHING = 0x0106, 34 TASK_STATS_EVERYTHING = 0x0106,
diff --git a/tests/integration/test_concurrency.py b/tests/integration/test_concurrency.py
index 1374852..528992d 100644
--- a/tests/integration/test_concurrency.py
+++ b/tests/integration/test_concurrency.py
@@ -148,7 +148,7 @@ class ConcurrentWorkerTasks(TrackerTestCase):
148 148
149 def poll(index): 149 def poll(index):
150 for _ in range(10): 150 for _ in range(10):
151 for mode in ("everything", "top100", "peer", "torr", "s24s"): 151 for mode in ("everything", "top100", "peer", "torr"):
152 self.tracker.stats(mode) 152 self.tracker.stats(mode)
153 153
154 threads = [threading.Thread(target=announce, args=(i,)) for i in range(THREADS // 2)] 154 threads = [threading.Thread(target=announce, args=(i,)) for i in range(THREADS // 2)]
diff --git a/tests/integration/test_stats.py b/tests/integration/test_stats.py
index c4ac95a..46ca39b 100644
--- a/tests/integration/test_stats.py
+++ b/tests/integration/test_stats.py
@@ -5,9 +5,7 @@ every one of them and, where the number means something, that it moves when
5the thing it counts happens. 5the thing it counts happens.
6""" 6"""
7 7
8import ipaddress
9import re 8import re
10import socket
11import unittest 9import unittest
12import urllib.error 10import urllib.error
13import xml.etree.ElementTree 11import xml.etree.ElementTree
@@ -18,7 +16,7 @@ from harness import TrackerTestCase
18# and belong to test_fullscrape; "woodpeckers" sits behind a 16# and belong to test_fullscrape; "woodpeckers" sits behind a
19# feature flag this build does not set. 17# feature flag this build does not set.
20MRTG_MODES = ["peer", "conn", "scrp", "udp4", "tcp4", "torr", "fscr", "completed", "syncs"] 18MRTG_MODES = ["peer", "conn", "scrp", "udp4", "tcp4", "torr", "fscr", "completed", "syncs"]
21TEXT_MODES = ["herr", "s24s", "top10", "top100", "renew"] 19TEXT_MODES = ["herr", "top10", "top100", "renew"]
22 20
23 21
24class StatsShape(TrackerTestCase): 22class StatsShape(TrackerTestCase):
@@ -144,48 +142,5 @@ class StatsTop(TrackerTestCase):
144 self.assertIn(heading, body) 142 self.assertIn(heading, body)
145 143
146 144
147class StatsNetworks(TrackerTestCase):
148 def source_address(self):
149 """Which address the tracker sees us from is not ours to choose: a
150 FreeBSD jail relocates a loopback connection to the jail's own
151 address, so ask the socket rather than assume 127.0.0.1."""
152 probe = socket.create_connection(("127.0.0.1", self.tracker.port), timeout=5)
153 try:
154 return probe.getsockname()[0]
155 finally:
156 probe.close()
157
158 def test_the_slash24_report_names_the_announcing_network(self):
159 source = self.source_address()
160 self.tracker.announce(self.hash_for("s24"), port=6001, left=0)
161 body = self.tracker.stats("s24s")
162 self.assertIn(b"Networks, limit /28:", body)
163 self.assertIn(b"Networks, limit /24:", body)
164
165 # The prefix is the first three address bytes of a v4 peer, left
166 # aligned in a sixteen byte buffer and then printed by the v6
167 # formatter, so 127.0.0.0/24 reads as "7f00::" rather than as the
168 # network an operator asked about. Pinned as it is, not blessed.
169 prefix = socket.inet_aton(source)[:3] + b"\0" * 13
170 self.assertIn(str(ipaddress.IPv6Address(prefix)).encode(), body)
171
172 def test_the_report_counts_one_network_per_announcing_address(self):
173 for port in (6001, 6002, 6003):
174 self.tracker.announce(self.hash_for("s24count"), port=port, left=0)
175 lines = [line for line in self.tracker.stats("s24s").splitlines() if b":" in line and b"limit" not in line]
176 self.assertEqual(len(lines), 2, "one line under each of the two limits")
177 for line in lines:
178 self.assertEqual(int(line.split(b":")[0]), 3, "three peers from the one network")
179
180 def test_the_report_survives_a_torrent_with_no_v4_peers(self):
181 # A torrent whose only peer is v6 has no v4 peer list once the lists
182 # are allocated on demand. The s24 report walks the v4 list of every
183 # torrent, so it has to skip the absent one rather than follow a null
184 # pointer. Needs WANT_IP_FROM_QUERY_STRING to force the family; without
185 # it the peer is loopback v4 and the walk is never asked to skip.
186 self.tracker.announce(self.hash_for("s24v6only"), ip="2001:db8::1", port=6001, left=0)
187 self.assertIn(b"Networks, limit /24:", self.tracker.stats("s24s"))
188
189
190if __name__ == "__main__": 145if __name__ == "__main__":
191 unittest.main() 146 unittest.main()