1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
It is considered beerware. Prost. Skol. Cheers or whatever. */
#include <arpa/inet.h>
#include <signal.h>
#include <pthread.h>
#include <string.h>
#include "ot_clean.h"
#include "ot_mutex.h"
#include "trackerlogic.h"
#include "test.h"
/* A clock stepped backwards used to leave peer_list->base in the future, so
the span the cleaner works from came out negative. It was then added to a
peer's age and stored in the single byte that age lives in, where -1 reads
as 255 and the next pass threw the whole swarm away. */
static void announce(struct ot_workstruct *ws, ot_hash *hash, uint8_t last_octet, int seeding) {
static const uint8_t v4mapped[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};
/* The peer list is written into the reply, which workstruct_init leaves
for its caller to point somewhere */
ws->reply = ws->outbuf;
memset(&ws->peer, 0, sizeof(ws->peer));
memcpy(&ws->peer, v4mapped, sizeof(v4mapped));
((uint8_t *)&ws->peer)[15] = last_octet;
OT_SETPORT(&ws->peer, &(uint16_t){6881});
if (seeding)
OT_PEERFLAG(ws->peer) |= PEER_FLAG_SEEDING;
ws->hash = hash;
add_peer_to_torrent_and_return_peers(FLAG_TCP, ws, 0);
}
static size_t peers_in(ot_hash *hash) {
char reply[1024];
size_t len = return_udp_scrape_for_torrent(*hash, reply);
uint32_t seeders, leechers;
if (len < 12)
return 0;
memcpy(&seeders, reply, 4);
memcpy(&leechers, reply + 8, 4);
return (size_t)ntohl(seeders) + (size_t)ntohl(leechers);
}
static ot_torrent *torrent_of(ot_hash *hash) {
ot_vector *bucket = mutex_bucket_lock_by_hash(*hash);
ot_torrent *t = (ot_torrent *)bucket->data;
ot_torrent *found = NULL;
size_t i;
for (i = 0; i < bucket->size; ++i)
if (!memcmp(t[i].hash, hash, sizeof(ot_hash)))
found = t + i;
mutex_bucket_unlock_by_hash(*hash, 0);
return found;
}
/* What a clock stepped back by `minutes` leaves behind */
static void step_clock_back(ot_torrent *torrent, int minutes) {
torrent->peer_list6->base += minutes;
torrent->peer_list4->base += minutes;
}
/* What the next minute of a well behaved clock looks like */
static void age_by_a_minute(ot_torrent *torrent) {
torrent->peer_list6->base -= 1;
torrent->peer_list4->base -= 1;
}
static void case_a_clock_that_went_back_keeps_the_swarm(void) {
struct ot_workstruct ws;
ot_hash hash;
ot_torrent *torrent;
test_case("a swarm survives the clock being stepped backwards");
memset(hash, 0x31, sizeof(hash));
CHECK_INT(workstruct_init(&ws, G_INBUF_SIZE), 0);
announce(&ws, &hash, 1, 1);
announce(&ws, &hash, 2, 0);
CHECK_INT(peers_in(&hash), 2);
torrent = torrent_of(&hash);
CHECK(torrent != NULL);
step_clock_back(torrent, 5);
/* The pass that sees the step keeps everybody either way: unclamped it
writes a negative span into each peer's age byte, where it reads as 251
rather than as a removal. */
CHECK_INT(clean_single_torrent(torrent), 0);
CHECK_INT(peers_in(&hash), 2);
/* A minute later, and this is where a poisoned age byte took the swarm:
251 plus one minute is past any timeout. */
age_by_a_minute(torrent);
CHECK_INT(clean_single_torrent(torrent), 0);
CHECK_INT(peers_in(&hash), 2);
workstruct_deinit(&ws);
}
static void case_an_idle_swarm_still_ages_out(void) {
struct ot_workstruct ws;
ot_hash hash;
ot_torrent *torrent;
test_case("a swarm nobody touched is still cleaned away");
memset(hash, 0x32, sizeof(hash));
CHECK_INT(workstruct_init(&ws, G_INBUF_SIZE), 0);
announce(&ws, &hash, 3, 1);
CHECK_INT(peers_in(&hash), 1);
torrent = torrent_of(&hash);
CHECK(torrent != NULL);
step_clock_back(torrent, -OT_PEER_TIMEOUT);
CHECK_INT(clean_single_torrent(torrent), 0);
CHECK_INT(peers_in(&hash), 0);
workstruct_deinit(&ws);
}
void test_clean(void) {
sigset_t hup;
/* accesslist_deinit sends SIGHUP to its reader, trackerlogic_init starts
the accesslist, and the test process must not take it */
sigemptyset(&hup);
sigaddset(&hup, SIGHUP);
pthread_sigmask(SIG_BLOCK, &hup, NULL);
trackerlogic_init();
case_a_clock_that_went_back_keeps_the_swarm();
case_an_idle_swarm_still_ages_out();
trackerlogic_deinit();
}
|