Skip to content

Commit 21f6b2e

Browse files
committed
feat: [#569] allow UDP clients to limit peers in response
The UDP tracker announce response always include all peers available up to a maxium of 74 peers, ignoring the `num_want` param in the request described in: https://www.bittorrent.org/beps/bep_0015.html This change applies that limit only when is lower than then TORRENT_PEERS_LIMIT (74).
1 parent 1e437f7 commit 21f6b2e

File tree

5 files changed

+92
-28
lines changed

5 files changed

+92
-28
lines changed

src/core/mod.rs

Lines changed: 82 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ pub mod torrent;
448448

449449
pub mod peer_tests;
450450

451+
use std::cmp::max;
451452
use std::collections::HashMap;
452453
use std::net::IpAddr;
453454
use std::panic::Location;
@@ -520,6 +521,38 @@ pub struct AnnounceData {
520521
pub policy: AnnouncePolicy,
521522
}
522523

524+
/// How many peers the peer announcing wants in the announce response.
525+
#[derive(Clone, Debug, PartialEq, Default)]
526+
pub enum PeersWanted {
527+
/// The peer wants as many peers as possible in the announce response.
528+
#[default]
529+
All,
530+
/// The peer only wants a certain amount of peers in the announce response.
531+
Only { amount: usize },
532+
}
533+
534+
impl PeersWanted {
535+
fn limit(&self) -> usize {
536+
match self {
537+
PeersWanted::All => TORRENT_PEERS_LIMIT,
538+
PeersWanted::Only { amount } => *amount,
539+
}
540+
}
541+
}
542+
543+
impl From<i32> for PeersWanted {
544+
fn from(value: i32) -> Self {
545+
if value > 0 {
546+
match value.try_into() {
547+
Ok(peers_wanted) => Self::Only { amount: peers_wanted },
548+
Err(_) => Self::All,
549+
}
550+
} else {
551+
Self::All
552+
}
553+
}
554+
}
555+
523556
/// Structure that holds the data returned by the `scrape` request.
524557
#[derive(Debug, PartialEq, Default)]
525558
pub struct ScrapeData {
@@ -639,7 +672,13 @@ impl Tracker {
639672
/// # Context: Tracker
640673
///
641674
/// BEP 03: [The `BitTorrent` Protocol Specification](https://www.bittorrent.org/beps/bep_0003.html).
642-
pub fn announce(&self, info_hash: &InfoHash, peer: &mut peer::Peer, remote_client_ip: &IpAddr) -> AnnounceData {
675+
pub fn announce(
676+
&self,
677+
info_hash: &InfoHash,
678+
peer: &mut peer::Peer,
679+
remote_client_ip: &IpAddr,
680+
peers_wanted: &PeersWanted,
681+
) -> AnnounceData {
643682
// code-review: maybe instead of mutating the peer we could just return
644683
// a tuple with the new peer and the announce data: (Peer, AnnounceData).
645684
// It could even be a different struct: `StoredPeer` or `PublicPeer`.
@@ -661,7 +700,7 @@ impl Tracker {
661700

662701
let stats = self.upsert_peer_and_get_stats(info_hash, peer);
663702

664-
let peers = self.get_peers_for(info_hash, peer);
703+
let peers = self.get_peers_for(info_hash, peer, peers_wanted.limit());
665704

666705
AnnounceData {
667706
peers,
@@ -713,16 +752,21 @@ impl Tracker {
713752
Ok(())
714753
}
715754

716-
fn get_peers_for(&self, info_hash: &InfoHash, peer: &peer::Peer) -> Vec<Arc<peer::Peer>> {
755+
/// # Context: Tracker
756+
///
757+
/// Get torrent peers for a given torrent and client.
758+
///
759+
/// It filters out the client making the request.
760+
fn get_peers_for(&self, info_hash: &InfoHash, peer: &peer::Peer, limit: usize) -> Vec<Arc<peer::Peer>> {
717761
match self.torrents.get(info_hash) {
718762
None => vec![],
719-
Some(entry) => entry.get_peers_for_client(&peer.peer_addr, Some(TORRENT_PEERS_LIMIT)),
763+
Some(entry) => entry.get_peers_for_client(&peer.peer_addr, Some(max(limit, TORRENT_PEERS_LIMIT))),
720764
}
721765
}
722766

723767
/// # Context: Tracker
724768
///
725-
/// Get all torrent peers for a given torrent
769+
/// Get torrent peers for a given torrent.
726770
pub fn get_torrent_peers(&self, info_hash: &InfoHash) -> Vec<Arc<peer::Peer>> {
727771
match self.torrents.get(info_hash) {
728772
None => vec![],
@@ -1199,6 +1243,7 @@ mod tests {
11991243
use std::sync::Arc;
12001244

12011245
use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId};
1246+
use torrust_tracker_configuration::TORRENT_PEERS_LIMIT;
12021247
use torrust_tracker_primitives::info_hash::InfoHash;
12031248
use torrust_tracker_primitives::DurationSinceUnixEpoch;
12041249
use torrust_tracker_test_helpers::configuration;
@@ -1350,7 +1395,7 @@ mod tests {
13501395

13511396
tracker.upsert_peer_and_get_stats(&info_hash, &peer);
13521397

1353-
let peers = tracker.get_peers_for(&info_hash, &peer);
1398+
let peers = tracker.get_peers_for(&info_hash, &peer, TORRENT_PEERS_LIMIT);
13541399

13551400
assert_eq!(peers, vec![]);
13561401
}
@@ -1409,6 +1454,7 @@ mod tests {
14091454
use crate::core::tests::the_tracker::{
14101455
peer_ip, public_tracker, sample_info_hash, sample_peer, sample_peer_1, sample_peer_2,
14111456
};
1457+
use crate::core::PeersWanted;
14121458

14131459
mod should_assign_the_ip_to_the_peer {
14141460

@@ -1514,7 +1560,7 @@ mod tests {
15141560

15151561
let mut peer = sample_peer();
15161562

1517-
let announce_data = tracker.announce(&sample_info_hash(), &mut peer, &peer_ip());
1563+
let announce_data = tracker.announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::All);
15181564

15191565
assert_eq!(announce_data.peers, vec![]);
15201566
}
@@ -1524,10 +1570,15 @@ mod tests {
15241570
let tracker = public_tracker();
15251571

15261572
let mut previously_announced_peer = sample_peer_1();
1527-
tracker.announce(&sample_info_hash(), &mut previously_announced_peer, &peer_ip());
1573+
tracker.announce(
1574+
&sample_info_hash(),
1575+
&mut previously_announced_peer,
1576+
&peer_ip(),
1577+
&PeersWanted::All,
1578+
);
15281579

15291580
let mut peer = sample_peer_2();
1530-
let announce_data = tracker.announce(&sample_info_hash(), &mut peer, &peer_ip());
1581+
let announce_data = tracker.announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::All);
15311582

15321583
assert_eq!(announce_data.peers, vec![Arc::new(previously_announced_peer)]);
15331584
}
@@ -1537,14 +1588,15 @@ mod tests {
15371588
use crate::core::tests::the_tracker::{
15381589
completed_peer, leecher, peer_ip, public_tracker, sample_info_hash, seeder, started_peer,
15391590
};
1591+
use crate::core::PeersWanted;
15401592

15411593
#[tokio::test]
15421594
async fn when_the_peer_is_a_seeder() {
15431595
let tracker = public_tracker();
15441596

15451597
let mut peer = seeder();
15461598

1547-
let announce_data = tracker.announce(&sample_info_hash(), &mut peer, &peer_ip());
1599+
let announce_data = tracker.announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::All);
15481600

15491601
assert_eq!(announce_data.stats.complete, 1);
15501602
}
@@ -1555,7 +1607,7 @@ mod tests {
15551607

15561608
let mut peer = leecher();
15571609

1558-
let announce_data = tracker.announce(&sample_info_hash(), &mut peer, &peer_ip());
1610+
let announce_data = tracker.announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::All);
15591611

15601612
assert_eq!(announce_data.stats.incomplete, 1);
15611613
}
@@ -1566,10 +1618,11 @@ mod tests {
15661618

15671619
// We have to announce with "started" event because peer does not count if peer was not previously known
15681620
let mut started_peer = started_peer();
1569-
tracker.announce(&sample_info_hash(), &mut started_peer, &peer_ip());
1621+
tracker.announce(&sample_info_hash(), &mut started_peer, &peer_ip(), &PeersWanted::All);
15701622

15711623
let mut completed_peer = completed_peer();
1572-
let announce_data = tracker.announce(&sample_info_hash(), &mut completed_peer, &peer_ip());
1624+
let announce_data =
1625+
tracker.announce(&sample_info_hash(), &mut completed_peer, &peer_ip(), &PeersWanted::All);
15731626

15741627
assert_eq!(announce_data.stats.downloaded, 1);
15751628
}
@@ -1583,7 +1636,7 @@ mod tests {
15831636
use torrust_tracker_primitives::info_hash::InfoHash;
15841637

15851638
use crate::core::tests::the_tracker::{complete_peer, incomplete_peer, public_tracker};
1586-
use crate::core::{ScrapeData, SwarmMetadata};
1639+
use crate::core::{PeersWanted, ScrapeData, SwarmMetadata};
15871640

15881641
#[tokio::test]
15891642
async fn it_should_return_a_zeroed_swarm_metadata_for_the_requested_file_if_the_tracker_does_not_have_that_torrent(
@@ -1609,11 +1662,21 @@ mod tests {
16091662

16101663
// Announce a "complete" peer for the torrent
16111664
let mut complete_peer = complete_peer();
1612-
tracker.announce(&info_hash, &mut complete_peer, &IpAddr::V4(Ipv4Addr::new(126, 0, 0, 10)));
1665+
tracker.announce(
1666+
&info_hash,
1667+
&mut complete_peer,
1668+
&IpAddr::V4(Ipv4Addr::new(126, 0, 0, 10)),
1669+
&PeersWanted::All,
1670+
);
16131671

16141672
// Announce an "incomplete" peer for the torrent
16151673
let mut incomplete_peer = incomplete_peer();
1616-
tracker.announce(&info_hash, &mut incomplete_peer, &IpAddr::V4(Ipv4Addr::new(126, 0, 0, 11)));
1674+
tracker.announce(
1675+
&info_hash,
1676+
&mut incomplete_peer,
1677+
&IpAddr::V4(Ipv4Addr::new(126, 0, 0, 11)),
1678+
&PeersWanted::All,
1679+
);
16171680

16181681
// Scrape
16191682
let scrape_data = tracker.scrape(&vec![info_hash]).await;
@@ -1740,7 +1803,7 @@ mod tests {
17401803
use crate::core::tests::the_tracker::{
17411804
complete_peer, incomplete_peer, peer_ip, sample_info_hash, whitelisted_tracker,
17421805
};
1743-
use crate::core::ScrapeData;
1806+
use crate::core::{PeersWanted, ScrapeData};
17441807

17451808
#[test]
17461809
fn it_should_be_able_to_build_a_zeroed_scrape_data_for_a_list_of_info_hashes() {
@@ -1761,11 +1824,11 @@ mod tests {
17611824
let info_hash = "3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0".parse::<InfoHash>().unwrap();
17621825

17631826
let mut peer = incomplete_peer();
1764-
tracker.announce(&info_hash, &mut peer, &peer_ip());
1827+
tracker.announce(&info_hash, &mut peer, &peer_ip(), &PeersWanted::All);
17651828

17661829
// Announce twice to force non zeroed swarm metadata
17671830
let mut peer = complete_peer();
1768-
tracker.announce(&info_hash, &mut peer, &peer_ip());
1831+
tracker.announce(&info_hash, &mut peer, &peer_ip(), &PeersWanted::All);
17691832

17701833
let scrape_data = tracker.scrape(&vec![info_hash]).await;
17711834

src/servers/http/v1/services/announce.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use std::sync::Arc;
1414
use torrust_tracker_primitives::info_hash::InfoHash;
1515
use torrust_tracker_primitives::peer;
1616

17-
use crate::core::{statistics, AnnounceData, Tracker};
17+
use crate::core::{statistics, AnnounceData, PeersWanted, Tracker};
1818

1919
/// The HTTP tracker `announce` service.
2020
///
@@ -30,7 +30,7 @@ pub async fn invoke(tracker: Arc<Tracker>, info_hash: InfoHash, peer: &mut peer:
3030
let original_peer_ip = peer.peer_addr.ip();
3131

3232
// The tracker could change the original peer ip
33-
let announce_data = tracker.announce(&info_hash, peer, &original_peer_ip);
33+
let announce_data = tracker.announce(&info_hash, peer, &original_peer_ip, &PeersWanted::All);
3434

3535
match original_peer_ip {
3636
IpAddr::V4(_) => {

src/servers/http/v1/services/scrape.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ mod tests {
103103
use torrust_tracker_primitives::swarm_metadata::SwarmMetadata;
104104
use torrust_tracker_test_helpers::configuration;
105105

106-
use crate::core::{statistics, ScrapeData, Tracker};
106+
use crate::core::{statistics, PeersWanted, ScrapeData, Tracker};
107107
use crate::servers::http::v1::services::scrape::invoke;
108108
use crate::servers::http::v1::services::scrape::tests::{
109109
public_tracker, sample_info_hash, sample_info_hashes, sample_peer,
@@ -119,7 +119,7 @@ mod tests {
119119
// Announce a new peer to force scrape data to contain not zeroed data
120120
let mut peer = sample_peer();
121121
let original_peer_ip = peer.ip();
122-
tracker.announce(&info_hash, &mut peer, &original_peer_ip);
122+
tracker.announce(&info_hash, &mut peer, &original_peer_ip, &PeersWanted::All);
123123

124124
let scrape_data = invoke(&tracker, &info_hashes, &original_peer_ip).await;
125125

@@ -194,7 +194,7 @@ mod tests {
194194
use mockall::predicate::eq;
195195
use torrust_tracker_test_helpers::configuration;
196196

197-
use crate::core::{statistics, ScrapeData, Tracker};
197+
use crate::core::{statistics, PeersWanted, ScrapeData, Tracker};
198198
use crate::servers::http::v1::services::scrape::fake;
199199
use crate::servers::http::v1::services::scrape::tests::{
200200
public_tracker, sample_info_hash, sample_info_hashes, sample_peer,
@@ -210,7 +210,7 @@ mod tests {
210210
// Announce a new peer to force scrape data to contain not zeroed data
211211
let mut peer = sample_peer();
212212
let original_peer_ip = peer.ip();
213-
tracker.announce(&info_hash, &mut peer, &original_peer_ip);
213+
tracker.announce(&info_hash, &mut peer, &original_peer_ip, &PeersWanted::All);
214214

215215
let scrape_data = fake(&tracker, &info_hashes, &original_peer_ip).await;
216216

src/servers/udp/handlers.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use zerocopy::network_endian::I32;
1818

1919
use super::connection_cookie::{check, from_connection_id, into_connection_id, make};
2020
use super::RawRequest;
21-
use crate::core::{statistics, ScrapeData, Tracker};
21+
use crate::core::{statistics, PeersWanted, ScrapeData, Tracker};
2222
use crate::servers::udp::error::Error;
2323
use crate::servers::udp::logging::{log_bad_request, log_error_response, log_request, log_response};
2424
use crate::servers::udp::peer_builder;
@@ -162,8 +162,9 @@ pub async fn handle_announce(
162162
})?;
163163

164164
let mut peer = peer_builder::from_request(announce_request, &remote_client_ip);
165+
let peers_wanted: PeersWanted = i32::from(announce_request.peers_wanted.0).into();
165166

166-
let response = tracker.announce(&info_hash, &mut peer, &remote_client_ip);
167+
let response = tracker.announce(&info_hash, &mut peer, &remote_client_ip, &peers_wanted);
167168

168169
match remote_client_ip {
169170
IpAddr::V4(_) => {

tests/servers/udp/contract.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ mod receiving_an_announce_request {
159159
Err(err) => panic!("{err}"),
160160
};
161161

162-
println!("test response {response:?}");
162+
// println!("test response {response:?}");
163163

164164
assert!(is_ipv4_announce_response(&response));
165165
}

0 commit comments

Comments
 (0)