/* This software was written by Dirk Engling It is considered beerware. Prost. Skol. Cheers or whatever. */ #include #include #include #include #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(); }