diff options
| -rw-r--r-- | man5/opentracker.conf.5 | 15 | ||||
| -rw-r--r-- | opentracker.conf.sample | 22 | ||||
| -rw-r--r-- | src/ot_config.c | 10 | ||||
| -rw-r--r-- | src/ot_livesync.c | 18 | ||||
| -rw-r--r-- | src/ot_livesync.h | 4 | ||||
| -rw-r--r-- | tests/integration/harness.py | 4 | ||||
| -rw-r--r-- | tests/integration/test_config.py | 26 | ||||
| -rw-r--r-- | tests/integration/test_livesync.py | 63 |
8 files changed, 143 insertions, 19 deletions
diff --git a/man5/opentracker.conf.5 b/man5/opentracker.conf.5 index 67e624f..2250fe5 100644 --- a/man5/opentracker.conf.5 +++ b/man5/opentracker.conf.5 | |||
| @@ -79,7 +79,20 @@ Specifies the path to the stats location. You can configure opentracker to appea | |||
| 79 | Specifies the IP address or network of the reverse proxies. Opentracker will take the X-Forwarded-For address instead of the source IP address. Can be added more than once. | 79 | Specifies the IP address or network of the reverse proxies. Opentracker will take the X-Forwarded-For address instead of the source IP address. Can be added more than once. |
| 80 | .Pp | 80 | .Pp |
| 81 | .It livesync.cluster.listen Ar ip_address:port | 81 | .It livesync.cluster.listen Ar ip_address:port |
| 82 | Specifies the IP address and port opentracker will listen on for incoming live sync packets to keep a cluster of opentrackers synchronized. | 82 | Specifies the IP address and port opentracker will listen on for incoming live |
| 83 | sync packets to keep a cluster of opentrackers synchronized. Every member of a | ||
| 84 | cluster has to agree on the port, which is also the one packets are sent to. | ||
| 85 | .Pp | ||
| 86 | .It livesync.cluster.group Ar ip_address | ||
| 87 | Specifies the multicast group a cluster talks in. For reaosns of backwards | ||
| 88 | comaptibility with legacy trackers, it defaults to 224.0.23.5, which, | ||
| 89 | technically sits in a range IANA assigns on request. | ||
| 90 | |||
| 91 | A cluster of its own is better served by an address out of 239.192.0.0/14, | ||
| 92 | which RFC 2365 sets aside for private use. Packets carry a ttl of 1 either | ||
| 93 | way and never leave the local link. Must be a valid mulitcast address from | ||
| 94 | .Pa 224.0.0.0/4 | ||
| 95 | . | ||
| 83 | .Pp | 96 | .Pp |
| 84 | .It livesync.cluster.node_ip Ar ip_address | 97 | .It livesync.cluster.node_ip Ar ip_address |
| 85 | Specifies one trusted IP address for sync between trackers running in a cluster. Can be added more than once. | 98 | Specifies one trusted IP address for sync between trackers running in a cluster. Can be added more than once. |
diff --git a/opentracker.conf.sample b/opentracker.conf.sample index 3cd30dc..7b35369 100644 --- a/opentracker.conf.sample +++ b/opentracker.conf.sample | |||
| @@ -138,14 +138,22 @@ | |||
| 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.23.5 for incoming | 141 | # port 9696, that will join the multicast group for incoming udp packets |
| 142 | # udp packets and one on ip address 192.168.0.1 port 9696 for outgoing | 142 | # and one on ip address 192.168.0.1 port 9696 for outgoing ones. Every |
| 143 | # udp packets. The group is the same for every opentracker, so a cluster | 143 | # member of a cluster has to agree on the port as well as the group. |
| 144 | # is kept to itself by the node_ip list below and by the ttl of 1 that | 144 | # |
| 145 | # keeps packets on the local link. | 145 | # The group a cluster talks in. |
| 146 | # | ||
| 147 | # livesync.cluster.group 239.192.23.5 | ||
| 148 | # | ||
| 149 | # For reaosns of backwards comaptibility with legacy trackers, it | ||
| 150 | # defaults to 224.0.23.5, which, technically sits in a range IANA | ||
| 151 | # assigns on request. | ||
| 152 | # | ||
| 153 | # A cluster of your own is better served by an address out of | ||
| 154 | # 239.192.0.0/14, which RFC 2365 sets aside for private use. Packets | ||
| 155 | # carry a ttl of 1 either way, so they never leave the local link. | ||
| 146 | # | 156 | # |
| 147 | # As of now one and only one ip address must be given, if opentracker | ||
| 148 | # was built with the WANT_SYNC_LIVE feature. | ||
| 149 | # | 157 | # |
| 150 | 158 | ||
| 151 | # IV) Sync between trackers running in a cluster is restricted to packets | 159 | # IV) Sync between trackers running in a cluster is restricted to packets |
diff --git a/src/ot_config.c b/src/ot_config.c index 95136b5..6b51c61 100644 --- a/src/ot_config.c +++ b/src/ot_config.c | |||
| @@ -208,6 +208,7 @@ typedef enum { | |||
| 208 | CONFIG_TRACKER_REDIRECT_URL, | 208 | CONFIG_TRACKER_REDIRECT_URL, |
| 209 | CONFIG_LIVESYNC_CLUSTER_NODE_IP, | 209 | CONFIG_LIVESYNC_CLUSTER_NODE_IP, |
| 210 | CONFIG_LIVESYNC_CLUSTER_LISTEN, | 210 | CONFIG_LIVESYNC_CLUSTER_LISTEN, |
| 211 | CONFIG_LIVESYNC_CLUSTER_GROUP, | ||
| 211 | } config_kind; | 212 | } config_kind; |
| 212 | 213 | ||
| 213 | typedef struct { | 214 | typedef struct { |
| @@ -247,6 +248,7 @@ static const config_key config_keys[] = { | |||
| 247 | #ifdef WANT_SYNC_LIVE | 248 | #ifdef WANT_SYNC_LIVE |
| 248 | CONFIG_KEY("livesync.cluster.node_ip", CONFIG_LIVESYNC_CLUSTER_NODE_IP), | 249 | CONFIG_KEY("livesync.cluster.node_ip", CONFIG_LIVESYNC_CLUSTER_NODE_IP), |
| 249 | CONFIG_KEY("livesync.cluster.listen", CONFIG_LIVESYNC_CLUSTER_LISTEN), | 250 | CONFIG_KEY("livesync.cluster.listen", CONFIG_LIVESYNC_CLUSTER_LISTEN), |
| 251 | CONFIG_KEY("livesync.cluster.group", CONFIG_LIVESYNC_CLUSTER_GROUP), | ||
| 250 | #endif | 252 | #endif |
| 251 | }; | 253 | }; |
| 252 | static const size_t config_entry_count = sizeof(config_keys) / sizeof(config_keys[0]); | 254 | static const size_t config_entry_count = sizeof(config_keys) / sizeof(config_keys[0]); |
| @@ -318,7 +320,7 @@ static int parse_configfile(char *config_filename) { | |||
| 318 | int off = scan_ip6_port(p, tmpip, &tmpport); | 320 | int off = scan_ip6_port(p, tmpip, &tmpport); |
| 319 | 321 | ||
| 320 | if (!off || !listen_scan_options(p + off, &shared, &workers)) | 322 | if (!off || !listen_scan_options(p + off, &shared, &workers)) |
| 321 | goto parse_error; | 323 | exerr("listen.tcp, listen.udp and listen.tcp_udp want an address, optionally a port and options."); |
| 322 | if (kind != CONFIG_LISTEN_UDP) | 324 | if (kind != CONFIG_LISTEN_UDP) |
| 323 | listen_append(tmpip, tmpport, FLAG_TCP, shared, workers), ++bound; | 325 | listen_append(tmpip, tmpport, FLAG_TCP, shared, workers), ++bound; |
| 324 | if (kind != CONFIG_LISTEN_TCP) | 326 | if (kind != CONFIG_LISTEN_TCP) |
| @@ -378,6 +380,10 @@ static int parse_configfile(char *config_filename) { | |||
| 378 | set_config_option(&g_redirecturl, p); | 380 | set_config_option(&g_redirecturl, p); |
| 379 | break; | 381 | break; |
| 380 | #ifdef WANT_SYNC_LIVE | 382 | #ifdef WANT_SYNC_LIVE |
| 383 | case CONFIG_LIVESYNC_CLUSTER_GROUP: | ||
| 384 | if (!ip_parse(p, tmpip) || livesync_set_group(tmpip) == -1) | ||
| 385 | exerr("livesync.cluster.group wants a multicast address, out of 224.0.0.0/4."); | ||
| 386 | break; | ||
| 381 | case CONFIG_LIVESYNC_CLUSTER_NODE_IP: | 387 | case CONFIG_LIVESYNC_CLUSTER_NODE_IP: |
| 382 | if (!scan_ip6_net(p, &tmpnet)) | 388 | if (!scan_ip6_net(p, &tmpnet)) |
| 383 | goto parse_error; | 389 | goto parse_error; |
| @@ -440,7 +446,7 @@ static void help(char *name) { | |||
| 440 | fprintf(stderr, "\nExample: ./opentracker -i 127.0.0.1 -p 6969 -P 6969 -f ./opentracker.conf -i 10.1.1.23 -p 2710 -p 80\n"); | 446 | fprintf(stderr, "\nExample: ./opentracker -i 127.0.0.1 -p 6969 -P 6969 -f ./opentracker.conf -i 10.1.1.23 -p 2710 -p 80\n"); |
| 441 | fprintf(stderr, " Here -i 127.0.0.1 selects the ip address for the next -p 6969 and -P 6969.\n"); | 447 | fprintf(stderr, " Here -i 127.0.0.1 selects the ip address for the next -p 6969 and -P 6969.\n"); |
| 442 | fprintf(stderr, " If no port is bound from config file or command line, the last address given\n"); | 448 | fprintf(stderr, " If no port is bound from config file or command line, the last address given\n"); |
| 443 | fprintf(stderr, " (or ::1 if none is set) will be used on port 6969.\n"); | 449 | fprintf(stderr, " is used on port 6969, and every interface if none was given.\n"); |
| 444 | } | 450 | } |
| 445 | #undef HELPLINE | 451 | #undef HELPLINE |
| 446 | 452 | ||
diff --git a/src/ot_livesync.c b/src/ot_livesync.c index 11b75d2..85577f7 100644 --- a/src/ot_livesync.c +++ b/src/ot_livesync.c | |||
| @@ -22,8 +22,10 @@ | |||
| 22 | 22 | ||
| 23 | #ifdef WANT_SYNC_LIVE | 23 | #ifdef WANT_SYNC_LIVE |
| 24 | 24 | ||
| 25 | ot_ip6 groupip_1 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 224, 0, 23, 5}; | 25 | /* The multicast group, legacy opentrackers joined by default 224.0.23.5, so it |
| 26 | static uint16_t g_port = LIVESYNC_PORT; | 26 | stays the default */ |
| 27 | static ot_ip6 g_group = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 224, 0, 23, 5}; | ||
| 28 | static uint16_t g_port = LIVESYNC_PORT; | ||
| 27 | 29 | ||
| 28 | #define LIVESYNC_INCOMING_BUFFSIZE (256 * 256) | 30 | #define LIVESYNC_INCOMING_BUFFSIZE (256 * 256) |
| 29 | 31 | ||
| @@ -96,6 +98,14 @@ void livesync_deinit(void) { | |||
| 96 | close(g_socket_out); | 98 | close(g_socket_out); |
| 97 | } | 99 | } |
| 98 | 100 | ||
| 101 | int livesync_set_group(const ot_ip6 group) { | ||
| 102 | /* v4 multicast is 224.0.0.0/4, and the socket layer has nothing else */ | ||
| 103 | if (!ip_is_v4mapped(group) || (group[12] & 0xf0) != 0xe0) | ||
| 104 | return -1; | ||
| 105 | memcpy(g_group, group, sizeof(ot_ip6)); | ||
| 106 | return 0; | ||
| 107 | } | ||
| 108 | |||
| 99 | void livesync_bind_mcast(ot_ip6 ip, uint16_t port) { | 109 | void livesync_bind_mcast(ot_ip6 ip, uint16_t port) { |
| 100 | if (!ip_is_v4mapped(ip)) | 110 | if (!ip_is_v4mapped(ip)) |
| 101 | exerr("v6 mcast support not yet available."); | 111 | exerr("v6 mcast support not yet available."); |
| @@ -105,7 +115,7 @@ void livesync_bind_mcast(ot_ip6 ip, uint16_t port) { | |||
| 105 | 115 | ||
| 106 | g_port = port; | 116 | g_port = port; |
| 107 | 117 | ||
| 108 | if ((g_socket_in = socket_mcast_join(groupip_1, ip, port)) == -1) | 118 | if ((g_socket_in = socket_mcast_join(g_group, ip, port)) == -1) |
| 109 | exerr("Error: Cant bind live sync incoming socket."); | 119 | exerr("Error: Cant bind live sync incoming socket."); |
| 110 | 120 | ||
| 111 | if ((g_socket_out = socket_mcast_bind(ip, port, 0)) == -1) | 121 | if ((g_socket_out = socket_mcast_bind(ip, port, 0)) == -1) |
| @@ -125,7 +135,7 @@ static void livesync_issue_peersync(sync_buffer *buf) { | |||
| 125 | has modified the protected element */ | 135 | has modified the protected element */ |
| 126 | pthread_mutex_unlock(&g_outbuf_mutex); | 136 | pthread_mutex_unlock(&g_outbuf_mutex); |
| 127 | 137 | ||
| 128 | socket_mcast_send(g_socket_out, mycopy, fill, groupip_1, g_port); | 138 | socket_mcast_send(g_socket_out, mycopy, fill, g_group, g_port); |
| 129 | } | 139 | } |
| 130 | 140 | ||
| 131 | static void livesync_handle_peersync(struct ot_workstruct *ws, size_t peer_size) { | 141 | static void livesync_handle_peersync(struct ot_workstruct *ws, size_t peer_size) { |
diff --git a/src/ot_livesync.h b/src/ot_livesync.h index d8d20b3..cf3c64c 100644 --- a/src/ot_livesync.h +++ b/src/ot_livesync.h | |||
| @@ -48,6 +48,10 @@ | |||
| 48 | void livesync_init(void); | 48 | void livesync_init(void); |
| 49 | void livesync_deinit(void); | 49 | void livesync_deinit(void); |
| 50 | 50 | ||
| 51 | /* The group to join and to send to, (defaults to 224.0.23.5). | ||
| 52 | Returns -1 if group is not a v4 multicast address. */ | ||
| 53 | int livesync_set_group(const ot_ip6 group); | ||
| 54 | |||
| 51 | /* Join multicast group for listening and create sending socket */ | 55 | /* Join multicast group for listening and create sending socket */ |
| 52 | void livesync_bind_mcast(ot_ip6 ip, uint16_t port); | 56 | void livesync_bind_mcast(ot_ip6 ip, uint16_t port); |
| 53 | 57 | ||
diff --git a/tests/integration/harness.py b/tests/integration/harness.py index a9d38ca..10aa6de 100644 --- a/tests/integration/harness.py +++ b/tests/integration/harness.py | |||
| @@ -192,7 +192,7 @@ class Tracker: | |||
| 192 | def torrent_count(self): | 192 | def torrent_count(self): |
| 193 | return int(self.stats("torr").split()[0]) | 193 | return int(self.stats("torr").split()[0]) |
| 194 | 194 | ||
| 195 | def livesync_send(self, payload): | 195 | def livesync_send(self, payload, group=LIVESYNC_GROUP): |
| 196 | """Put a packet on the live sync group. | 196 | """Put a packet on the live sync group. |
| 197 | 197 | ||
| 198 | It has to go to the group rather than straight at the port: the | 198 | It has to go to the group rather than straight at the port: the |
| @@ -204,7 +204,7 @@ class Tracker: | |||
| 204 | sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton("127.0.0.1")) | 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) | 205 | sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 1) |
| 206 | sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 1) | 206 | sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 1) |
| 207 | sock.sendto(payload, (LIVESYNC_GROUP, self.livesync_port)) | 207 | sock.sendto(payload, (group, self.livesync_port)) |
| 208 | finally: | 208 | finally: |
| 209 | sock.close() | 209 | sock.close() |
| 210 | 210 | ||
diff --git a/tests/integration/test_config.py b/tests/integration/test_config.py index 1a84fa7..2f6bdc4 100644 --- a/tests/integration/test_config.py +++ b/tests/integration/test_config.py | |||
| @@ -1,7 +1,9 @@ | |||
| 1 | """Settings the tracker has to accept, and still work with. | 1 | """Settings the tracker has to accept, and still work with. |
| 2 | 2 | ||
| 3 | An unrecognised key makes opentracker refuse to start, so a test that it | 3 | An unrecognised key only warns, so coming up proves nothing on its own and a |
| 4 | comes up at all is a test that the key was understood. | 4 | test that cares has to look at what the tracker said. A key that is |
| 5 | recognised but cannot be parsed is another matter, and for the listen | ||
| 6 | statements it stops the tracker. | ||
| 5 | """ | 7 | """ |
| 6 | 8 | ||
| 7 | import unittest | 9 | import unittest |
| @@ -14,6 +16,7 @@ class TcpWorkers(TrackerTestCase): | |||
| 14 | 16 | ||
| 15 | def test_the_tracker_serves_with_tcp_workers_asked_for(self): | 17 | def test_the_tracker_serves_with_tcp_workers_asked_for(self): |
| 16 | self.assertEqual(self.tracker.announce(self.hash_for("tcpworkers"))[b"complete"], 1) | 18 | self.assertEqual(self.tracker.announce(self.hash_for("tcpworkers"))[b"complete"], 1) |
| 19 | self.assertNotIn("Unhandled line", self.tracker.output()) | ||
| 17 | 20 | ||
| 18 | 21 | ||
| 19 | class WorkersOnAListenStatement(unittest.TestCase): | 22 | class WorkersOnAListenStatement(unittest.TestCase): |
| @@ -27,3 +30,22 @@ class WorkersOnAListenStatement(unittest.TestCase): | |||
| 27 | self.assertEqual(complaints, [], "the tracker reported:\n" + "\n".join(complaints)) | 30 | self.assertEqual(complaints, [], "the tracker reported:\n" + "\n".join(complaints)) |
| 28 | finally: | 31 | finally: |
| 29 | tracker._cleanup() | 32 | tracker._cleanup() |
| 33 | |||
| 34 | |||
| 35 | class AListenLineThatDoesNotParse(unittest.TestCase): | ||
| 36 | """Warning and carrying on left a tracker that had been told where to | ||
| 37 | listen coming up on its default instead, which is every interface.""" | ||
| 38 | |||
| 39 | def refuses(self, line): | ||
| 40 | with self.assertRaises(AssertionError) as caught: | ||
| 41 | Tracker(line) | ||
| 42 | return str(caught.exception) | ||
| 43 | |||
| 44 | def test_an_address_that_is_not_one_stops_the_tracker(self): | ||
| 45 | self.assertIn("want an address", self.refuses("listen.tcp 10.0.0.300:7999\n")) | ||
| 46 | |||
| 47 | def test_so_does_a_port_out_of_range(self): | ||
| 48 | self.assertIn("want an address", self.refuses("listen.udp 127.0.0.1:99999\n")) | ||
| 49 | |||
| 50 | def test_so_does_an_option_it_does_not_know(self): | ||
| 51 | self.assertIn("want an address", self.refuses("listen.tcp 127.0.0.1:7999 wokers=2\n")) | ||
diff --git a/tests/integration/test_livesync.py b/tests/integration/test_livesync.py index 3a9ee58..f4cb2db 100644 --- a/tests/integration/test_livesync.py +++ b/tests/integration/test_livesync.py | |||
| @@ -10,7 +10,7 @@ import struct | |||
| 10 | import subprocess | 10 | import subprocess |
| 11 | import unittest | 11 | import unittest |
| 12 | 12 | ||
| 13 | from harness import TRACKER, TrackerTestCase | 13 | from 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 |
| 16 | SYNC_PEER4 = 0 | 16 | SYNC_PEER4 = 0 |
| @@ -164,3 +164,64 @@ class LiveSyncUnconfigured(TrackerTestCase): | |||
| 164 | for i in range(200): | 164 | for i in range(200): |
| 165 | self.tracker.announce(self.hash_for("no-cluster-%d" % i), port=6881 + i, left=0) | 165 | self.tracker.announce(self.hash_for("no-cluster-%d" % i), port=6881 + i, left=0) |
| 166 | self.assertEqual(self.tracker.torrent_count(), 200) | 166 | self.assertEqual(self.tracker.torrent_count(), 200) |
| 167 | |||
| 168 | |||
| 169 | # RFC 2365 sets 239.192.0.0/14 aside for exactly this kind of use | ||
| 170 | OWN_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. | ||
| 177 | UNJOINED_GROUP = "239.192.0.99" | ||
| 178 | |||
| 179 | |||
| 180 | @unittest.skipUnless(has_livesync(), "this build has no WANT_SYNC_LIVE") | ||
| 181 | class LiveSyncGroup(TrackerTestCase): | ||
| 182 | config = ("livesync.cluster.group %s\n" | ||
| 183 | "livesync.cluster.listen 127.0.0.1:{livesync_port}\n" | ||
| 184 | "livesync.cluster.node_ip 127.0.0.1\n" % OWN_GROUP) | ||
| 185 | |||
| 186 | def test_the_configured_group_is_joined(self): | ||
| 187 | info_hash = self.hash_for("own-group") | ||
| 188 | self.tracker.livesync_send(packet(SYNC_PEER4, [(info_hash, peer4("10.1.3.1", 6881, FLAG_SEEDING))]), | ||
| 189 | group=OWN_GROUP) | ||
| 190 | self.assertTrue(self.tracker.wait_until(lambda: self.tracker.torrent_count() == 1)) | ||
| 191 | |||
| 192 | def test_another_group_is_left_alone(self): | ||
| 193 | """A cluster that picked its own group hears nothing from the others.""" | ||
| 194 | info_hash = self.hash_for("not-our-group") | ||
| 195 | self.tracker.livesync_send(packet(SYNC_PEER4, [(info_hash, peer4("10.1.3.2", 6881, FLAG_SEEDING))]), | ||
| 196 | group=UNJOINED_GROUP) | ||
| 197 | self.assertFalse(self.tracker.wait_until(lambda: self.tracker.torrent_count() != 0, timeout=1)) | ||
| 198 | |||
| 199 | |||
| 200 | @unittest.skipUnless(has_livesync(), "this build has no WANT_SYNC_LIVE") | ||
| 201 | class LiveSyncGroupRefused(unittest.TestCase): | ||
| 202 | """A cluster that asked for a group of its own must not quietly end up on | ||
| 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.""" | ||
| 205 | |||
| 206 | def start_with(self, group): | ||
| 207 | return Tracker("livesync.cluster.group %s\n" | ||
| 208 | "livesync.cluster.listen 127.0.0.1:{livesync_port}\n" | ||
| 209 | "livesync.cluster.node_ip 127.0.0.1\n" % group) | ||
| 210 | |||
| 211 | def test_a_unicast_address_stops_the_tracker(self): | ||
| 212 | with self.assertRaises(AssertionError) as caught: | ||
| 213 | self.start_with("10.0.0.1") | ||
| 214 | self.assertIn("multicast address", str(caught.exception)) | ||
| 215 | |||
| 216 | def test_so_does_something_that_is_not_an_address(self): | ||
| 217 | with self.assertRaises(AssertionError) as caught: | ||
| 218 | self.start_with("not-an-address") | ||
| 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() | ||
