summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-11-03 11:47:30 +0100
committerDirk Engling <erdgeist@erdgeist.org>2026-11-03 11:47:30 +0100
commit10d18d4a87da4289bea198a1714288f8208dd56d (patch)
tree4c7bb5a740a5c39d99dfa0a288dd3caa4bf41ea8
parentf16ea39889c0fd65f3e533cf40cc3a0a33ac1c33 (diff)
Refuse to start on a livesync.cluster.group that is not one
-rw-r--r--man5/opentracker.conf.54
-rw-r--r--src/ot_config.c2
-rw-r--r--tests/integration/test_livesync.py53
3 files changed, 40 insertions, 19 deletions
diff --git a/man5/opentracker.conf.5 b/man5/opentracker.conf.5
index 9a836ae..2250fe5 100644
--- a/man5/opentracker.conf.5
+++ b/man5/opentracker.conf.5
@@ -90,7 +90,9 @@ technically sits in a range IANA assigns on request.
90 90
91A cluster of its own is better served by an address out of 239.192.0.0/14, 91A cluster of its own is better served by an address out of 239.192.0.0/14,
92which RFC 2365 sets aside for private use. Packets carry a ttl of 1 either 92which RFC 2365 sets aside for private use. Packets carry a ttl of 1 either
93way and never leave the local link. 93way and never leave the local link. Must be a valid mulitcast address from
94.Pa 224.0.0.0/4
95.
94.Pp 96.Pp
95.It livesync.cluster.node_ip Ar ip_address 97.It livesync.cluster.node_ip Ar ip_address
96Specifies one trusted IP address for sync between trackers running in a cluster. Can be added more than once. 98Specifies one trusted IP address for sync between trackers running in a cluster. Can be added more than once.
diff --git a/src/ot_config.c b/src/ot_config.c
index 4064d1b..36f50a6 100644
--- a/src/ot_config.c
+++ b/src/ot_config.c
@@ -382,7 +382,7 @@ static int parse_configfile(char *config_filename) {
382#ifdef WANT_SYNC_LIVE 382#ifdef WANT_SYNC_LIVE
383 case CONFIG_LIVESYNC_CLUSTER_GROUP: 383 case CONFIG_LIVESYNC_CLUSTER_GROUP:
384 if (!ip_parse(p, tmpip) || livesync_set_group(tmpip) == -1) 384 if (!ip_parse(p, tmpip) || livesync_set_group(tmpip) == -1)
385 goto parse_error; 385 exerr("livesync.cluster.group wants a multicast address, out of 224.0.0.0/4.");
386 break; 386 break;
387 case CONFIG_LIVESYNC_CLUSTER_NODE_IP: 387 case CONFIG_LIVESYNC_CLUSTER_NODE_IP:
388 if (!scan_ip6_net(p, &tmpnet)) 388 if (!scan_ip6_net(p, &tmpnet))
diff --git a/tests/integration/test_livesync.py b/tests/integration/test_livesync.py
index 52e871f..f4cb2db 100644
--- a/tests/integration/test_livesync.py
+++ b/tests/integration/test_livesync.py
@@ -10,7 +10,7 @@ import struct
10import subprocess 10import subprocess
11import unittest 11import unittest
12 12
13from harness import TRACKER, TrackerTestCase 13from harness import TRACKER, Tracker, TrackerTestCase
14 14
15# enum { OT_SYNC_PEER4, OT_SYNC_PEER6 } in src/ot_livesync.c 15# enum { OT_SYNC_PEER4, OT_SYNC_PEER6 } in src/ot_livesync.c
16SYNC_PEER4 = 0 16SYNC_PEER4 = 0
@@ -168,6 +168,13 @@ class LiveSyncUnconfigured(TrackerTestCase):
168 168
169# RFC 2365 sets 239.192.0.0/14 aside for exactly this kind of use 169# RFC 2365 sets 239.192.0.0/14 aside for exactly this kind of use
170OWN_GROUP = "239.192.0.7" 170OWN_GROUP = "239.192.0.7"
171# A group nothing in this suite ever joins. Deliberately not the default one:
172# other trackers here do join that, on ports the harness picks at random, and
173# a socket bound to the wildcard address shares a port's multicast traffic
174# with anything else on the host that has joined the group it is addressed
175# to. Two clusters sharing a host and a port is pathological in the field but
176# routine in a test suite.
177UNJOINED_GROUP = "239.192.0.99"
171 178
172 179
173@unittest.skipUnless(has_livesync(), "this build has no WANT_SYNC_LIVE") 180@unittest.skipUnless(has_livesync(), "this build has no WANT_SYNC_LIVE")
@@ -182,27 +189,39 @@ class LiveSyncGroup(TrackerTestCase):
182 group=OWN_GROUP) 189 group=OWN_GROUP)
183 self.assertTrue(self.tracker.wait_until(lambda: self.tracker.torrent_count() == 1)) 190 self.assertTrue(self.tracker.wait_until(lambda: self.tracker.torrent_count() == 1))
184 191
185 def test_the_default_group_is_left_alone(self): 192 def test_another_group_is_left_alone(self):
186 """A cluster that picked its own group should not hear the one every 193 """A cluster that picked its own group hears nothing from the others."""
187 other opentracker is on."""
188 info_hash = self.hash_for("not-our-group") 194 info_hash = self.hash_for("not-our-group")
189 self.tracker.livesync_send(packet(SYNC_PEER4, [(info_hash, peer4("10.1.3.2", 6881, FLAG_SEEDING))])) 195 self.tracker.livesync_send(packet(SYNC_PEER4, [(info_hash, peer4("10.1.3.2", 6881, FLAG_SEEDING))]),
196 group=UNJOINED_GROUP)
190 self.assertFalse(self.tracker.wait_until(lambda: self.tracker.torrent_count() != 0, timeout=1)) 197 self.assertFalse(self.tracker.wait_until(lambda: self.tracker.torrent_count() != 0, timeout=1))
191 198
192 199
193@unittest.skipUnless(has_livesync(), "this build has no WANT_SYNC_LIVE") 200@unittest.skipUnless(has_livesync(), "this build has no WANT_SYNC_LIVE")
194class LiveSyncGroupRefused(TrackerTestCase): 201class LiveSyncGroupRefused(unittest.TestCase):
195 """A unicast address is not a group. The line is refused like any other 202 """A cluster that asked for a group of its own must not quietly end up on
196 bad config line, which leaves the default standing.""" 203 the one every other opentracker is on, so a line that does not name a
204 group stops the tracker rather than falling back."""
197 205
198 config = ("livesync.cluster.group 10.0.0.1\n" 206 def start_with(self, group):
199 "livesync.cluster.listen 127.0.0.1:{livesync_port}\n" 207 return Tracker("livesync.cluster.group %s\n"
200 "livesync.cluster.node_ip 127.0.0.1\n") 208 "livesync.cluster.listen 127.0.0.1:{livesync_port}\n"
209 "livesync.cluster.node_ip 127.0.0.1\n" % group)
201 210
202 def test_the_line_is_refused(self): 211 def test_a_unicast_address_stops_the_tracker(self):
203 self.assertIn("Parse error", self.tracker.output()) 212 with self.assertRaises(AssertionError) as caught:
213 self.start_with("10.0.0.1")
214 self.assertIn("multicast address", str(caught.exception))
204 215
205 def test_and_the_default_group_still_works(self): 216 def test_so_does_something_that_is_not_an_address(self):
206 info_hash = self.hash_for("fallback-group") 217 with self.assertRaises(AssertionError) as caught:
207 self.tracker.livesync_send(packet(SYNC_PEER4, [(info_hash, peer4("10.1.3.3", 6881, FLAG_SEEDING))])) 218 self.start_with("not-an-address")
208 self.assertTrue(self.tracker.wait_until(lambda: self.tracker.torrent_count() == 1)) 219 self.assertIn("multicast address", str(caught.exception))
220
221 def test_a_group_at_either_end_of_the_range_is_taken(self):
222 for group in ("224.0.0.1", "239.255.255.255"):
223 tracker = self.start_with(group)
224 try:
225 self.assertEqual(tracker.torrent_count(), 0)
226 finally:
227 tracker.stop()