Skip to content

Commit ec07b78

Browse files
committed
Merge #1269: Overhaul core Tracker: review whitelist functionality
eca5c59 refactor: [#1268] move scrape logic from udp server to udp_tracker_core package (Jose Celano) c0fc390 refactor: [#1268] move announce logic from udp server to udp_tracker_core package (Jose Celano) 37a142e refactor: [#1268] move scrape logic from axum to http_tracker_core package (Jose Celano) 74815ab refactor: [#1268] move announce logic from axum to http_tracker_core package (Jose Celano) e48aaf5 [#1268] move udp services to udp_tracker_core package (Jose Celano) 73753e3 [#1268] move http services to http_tracker_core package (Jose Celano) dec742e refactor: [#1268] extract servers::udp::services::scrape service (Jose Celano) 3c07b26 refactor: [#1268] extract servers::udp::services::announce service (Jose Celano) 81825c9 refactor: [#1268] separate UDP handlers into diferent modules (Jose Celano) Pull request description: Overhaul core Tracker: review whitelist functionality. ### Sub-tasks - [x] Introduce submodules for handlers in UDP: `servers::udp::handlers::{announce, scrape}`. - [x] Create the missing services (app layer) in the UDP tracker. There is no intermediary level between handlers and the core tracker. It will moved to its own package `udp-tracker-core` later. - [x] Move the service `services::announce::invoke()` in the HTTP tracker to the `http-tracker-core` package. - [x] Move the service `services::announce::invoke()` in the UDP tracker to the `udp-tracker-core` package. - [x] Move logic from the handler (in the framework level - delivery layer) to the application service in the `http-tracker-core` package. - [x] For the `announce` request - [x] For the `scrape` request - [x] Move logic from the handler (controller level - delivery layer) to the application service in the `udp-tracker-core` package. - [x] For the `announce` request - [x] For the `scrape` request - [ ] ~~Add version module also for the UDP tracker. I don't see any reason to use `v1` in the http tracker but not in the UDP tracker.~~ I will leave this until we introduce a new major version. ### Sub-tasks for a new PR I've left these tasks for a new [issue](#1270). This PR is just moving things and the new tasks imply changing function signatures. - [ ] In the tracker-core announce handler return a `Result<AnnounceData, AnnounceError>` when the torrent is not included in the whitelist. - [ ] In the tracker-core scrape handler return a `Result<ScrapeData, ScrapeError>` so we are able to return errors in the future without breaking the public API. ACKs for top commit: josecelano: ACK eca5c59 Tree-SHA512: d3ee37ffa806e8a86813fe564e3840fab7bfc44d2072f85bc2eba84ac3402c95c0f6a5beb2725071cb0498415f55915431b656e98c71b3a6bf469de961c37f03
2 parents 2f505a8 + eca5c59 commit ec07b78

File tree

23 files changed

+2248
-1974
lines changed

23 files changed

+2248
-1974
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/http-protocol/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ percent-encoding = "2"
2424
serde = { version = "1", features = ["derive"] }
2525
serde_bencode = "0"
2626
thiserror = "2"
27+
torrust-tracker-clock = { version = "3.0.0-develop", path = "../clock" }
2728
torrust-tracker-configuration = { version = "3.0.0-develop", path = "../configuration" }
2829
torrust-tracker-contrib-bencode = { version = "3.0.0-develop", path = "../../contrib/bencode" }
2930
torrust-tracker-located-error = { version = "3.0.0-develop", path = "../located-error" }

packages/http-protocol/src/lib.rs

+13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
11
//! Primitive types and function for `BitTorrent` HTTP trackers.
22
pub mod percent_encoding;
33
pub mod v1;
4+
5+
use torrust_tracker_clock::clock;
6+
7+
/// This code needs to be copied into each crate.
8+
/// Working version, for production.
9+
#[cfg(not(test))]
10+
#[allow(dead_code)]
11+
pub(crate) type CurrentClock = clock::Working;
12+
13+
/// Stopped version, for testing.
14+
#[cfg(test)]
15+
#[allow(dead_code)]
16+
pub(crate) type CurrentClock = clock::Stopped;

packages/http-protocol/src/v1/requests/announce.rs

+32-1
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@
22
//!
33
//! Data structures and logic for parsing the `announce` request.
44
use std::fmt;
5+
use std::net::{IpAddr, SocketAddr};
56
use std::panic::Location;
67
use std::str::FromStr;
78

8-
use aquatic_udp_protocol::{NumberOfBytes, PeerId};
9+
use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId};
910
use bittorrent_primitives::info_hash::{self, InfoHash};
1011
use thiserror::Error;
12+
use torrust_tracker_clock::clock::Time;
1113
use torrust_tracker_located_error::{Located, LocatedError};
1214
use torrust_tracker_primitives::peer;
1315

1416
use crate::percent_encoding::{percent_decode_info_hash, percent_decode_peer_id};
1517
use crate::v1::query::{ParseQueryError, Query};
1618
use crate::v1::responses;
19+
use crate::CurrentClock;
1720

1821
// Query param names
1922
const INFO_HASH: &str = "info_hash";
@@ -373,6 +376,34 @@ fn extract_numwant(query: &Query) -> Result<Option<u32>, ParseAnnounceQueryError
373376
}
374377
}
375378

379+
/// It builds a `Peer` from the announce request.
380+
///
381+
/// It ignores the peer address in the announce request params.
382+
#[must_use]
383+
pub fn peer_from_request(announce_request: &Announce, peer_ip: &IpAddr) -> peer::Peer {
384+
peer::Peer {
385+
peer_id: announce_request.peer_id,
386+
peer_addr: SocketAddr::new(*peer_ip, announce_request.port),
387+
updated: CurrentClock::now(),
388+
uploaded: announce_request.uploaded.unwrap_or(NumberOfBytes::new(0)),
389+
downloaded: announce_request.downloaded.unwrap_or(NumberOfBytes::new(0)),
390+
left: announce_request.left.unwrap_or(NumberOfBytes::new(0)),
391+
event: map_to_torrust_event(&announce_request.event),
392+
}
393+
}
394+
395+
#[must_use]
396+
pub fn map_to_torrust_event(event: &Option<Event>) -> AnnounceEvent {
397+
match event {
398+
Some(event) => match &event {
399+
Event::Started => AnnounceEvent::Started,
400+
Event::Stopped => AnnounceEvent::Stopped,
401+
Event::Completed => AnnounceEvent::Completed,
402+
},
403+
None => AnnounceEvent::None,
404+
}
405+
}
406+
376407
#[cfg(test)]
377408
mod tests {
378409

src/packages/http_tracker_core/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
pub mod services;
12
pub mod statistics;

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

+56-3
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,14 @@
1010
use std::net::IpAddr;
1111
use std::sync::Arc;
1212

13+
use bittorrent_http_protocol::v1::requests::announce::{peer_from_request, Announce};
14+
use bittorrent_http_protocol::v1::responses;
15+
use bittorrent_http_protocol::v1::services::peer_ip_resolver::{self, ClientIpSources};
1316
use bittorrent_primitives::info_hash::InfoHash;
1417
use bittorrent_tracker_core::announce_handler::{AnnounceHandler, PeersWanted};
18+
use bittorrent_tracker_core::authentication::service::AuthenticationService;
19+
use bittorrent_tracker_core::whitelist;
20+
use torrust_tracker_configuration::Core;
1521
use torrust_tracker_primitives::core::AnnounceData;
1622
use torrust_tracker_primitives::peer;
1723

@@ -27,6 +33,53 @@ use crate::packages::http_tracker_core;
2733
/// > **NOTICE**: as the HTTP tracker does not requires a connection request
2834
/// > like the UDP tracker, the number of TCP connections is incremented for
2935
/// > each `announce` request.
36+
///
37+
/// # Errors
38+
///
39+
/// This function will return an error if:
40+
///
41+
/// - The tracker is running in `listed` mode and the torrent is not whitelisted.
42+
/// - There is an error when resolving the client IP address.
43+
#[allow(clippy::too_many_arguments)]
44+
pub async fn handle_announce(
45+
core_config: &Arc<Core>,
46+
announce_handler: &Arc<AnnounceHandler>,
47+
_authentication_service: &Arc<AuthenticationService>,
48+
whitelist_authorization: &Arc<whitelist::authorization::WhitelistAuthorization>,
49+
opt_http_stats_event_sender: &Arc<Option<Box<dyn http_tracker_core::statistics::event::sender::Sender>>>,
50+
announce_request: &Announce,
51+
client_ip_sources: &ClientIpSources,
52+
) -> Result<AnnounceData, responses::error::Error> {
53+
// Authorization
54+
match whitelist_authorization.authorize(&announce_request.info_hash).await {
55+
Ok(()) => (),
56+
Err(error) => return Err(responses::error::Error::from(error)),
57+
}
58+
59+
let peer_ip = match peer_ip_resolver::invoke(core_config.net.on_reverse_proxy, client_ip_sources) {
60+
Ok(peer_ip) => peer_ip,
61+
Err(error) => return Err(responses::error::Error::from(error)),
62+
};
63+
64+
let mut peer = peer_from_request(announce_request, &peer_ip);
65+
66+
let peers_wanted = match announce_request.numwant {
67+
Some(numwant) => PeersWanted::only(numwant),
68+
None => PeersWanted::AsManyAsPossible,
69+
};
70+
71+
let announce_data = invoke(
72+
announce_handler.clone(),
73+
opt_http_stats_event_sender.clone(),
74+
announce_request.info_hash,
75+
&mut peer,
76+
&peers_wanted,
77+
)
78+
.await;
79+
80+
Ok(announce_data)
81+
}
82+
3083
pub async fn invoke(
3184
announce_handler: Arc<AnnounceHandler>,
3285
opt_http_stats_event_sender: Arc<Option<Box<dyn http_tracker_core::statistics::event::sender::Sender>>>,
@@ -164,11 +217,11 @@ mod tests {
164217

165218
use super::{sample_peer_using_ipv4, sample_peer_using_ipv6};
166219
use crate::packages::http_tracker_core;
167-
use crate::servers::http::test_helpers::tests::sample_info_hash;
168-
use crate::servers::http::v1::services::announce::invoke;
169-
use crate::servers::http::v1::services::announce::tests::{
220+
use crate::packages::http_tracker_core::services::announce::invoke;
221+
use crate::packages::http_tracker_core::services::announce::tests::{
170222
initialize_core_tracker_services, sample_peer, MockHttpStatsEventSender,
171223
};
224+
use crate::servers::http::test_helpers::tests::sample_info_hash;
172225

173226
fn initialize_announce_handler() -> Arc<AnnounceHandler> {
174227
let config = configuration::ephemeral();

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

+50-8
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@
1010
use std::net::IpAddr;
1111
use std::sync::Arc;
1212

13+
use bittorrent_http_protocol::v1::requests::scrape::Scrape;
14+
use bittorrent_http_protocol::v1::responses;
15+
use bittorrent_http_protocol::v1::services::peer_ip_resolver::{self, ClientIpSources};
1316
use bittorrent_primitives::info_hash::InfoHash;
17+
use bittorrent_tracker_core::authentication::service::AuthenticationService;
1418
use bittorrent_tracker_core::scrape_handler::ScrapeHandler;
19+
use torrust_tracker_configuration::Core;
1520
use torrust_tracker_primitives::core::ScrapeData;
1621

1722
use crate::packages::http_tracker_core;
@@ -26,6 +31,43 @@ use crate::packages::http_tracker_core;
2631
/// > **NOTICE**: as the HTTP tracker does not requires a connection request
2732
/// > like the UDP tracker, the number of TCP connections is incremented for
2833
/// > each `scrape` request.
34+
///
35+
/// # Errors
36+
///
37+
/// This function will return an error if:
38+
///
39+
/// - There is an error when resolving the client IP address.
40+
#[allow(clippy::too_many_arguments)]
41+
pub async fn handle_scrape(
42+
core_config: &Arc<Core>,
43+
scrape_handler: &Arc<ScrapeHandler>,
44+
_authentication_service: &Arc<AuthenticationService>,
45+
opt_http_stats_event_sender: &Arc<Option<Box<dyn http_tracker_core::statistics::event::sender::Sender>>>,
46+
scrape_request: &Scrape,
47+
client_ip_sources: &ClientIpSources,
48+
return_real_scrape_data: bool,
49+
) -> Result<ScrapeData, responses::error::Error> {
50+
// Authorization for scrape requests is handled at the `http_tracker_core`
51+
// level for each torrent.
52+
53+
let peer_ip = match peer_ip_resolver::invoke(core_config.net.on_reverse_proxy, client_ip_sources) {
54+
Ok(peer_ip) => peer_ip,
55+
Err(error) => return Err(responses::error::Error::from(error)),
56+
};
57+
58+
if return_real_scrape_data {
59+
Ok(invoke(
60+
scrape_handler,
61+
opt_http_stats_event_sender,
62+
&scrape_request.info_hashes,
63+
&peer_ip,
64+
)
65+
.await)
66+
} else {
67+
Ok(http_tracker_core::services::scrape::fake(opt_http_stats_event_sender, &scrape_request.info_hashes, &peer_ip).await)
68+
}
69+
}
70+
2971
pub async fn invoke(
3072
scrape_handler: &Arc<ScrapeHandler>,
3173
opt_http_stats_event_sender: &Arc<Option<Box<dyn http_tracker_core::statistics::event::sender::Sender>>>,
@@ -161,13 +203,13 @@ mod tests {
161203
use torrust_tracker_primitives::core::ScrapeData;
162204
use torrust_tracker_primitives::swarm_metadata::SwarmMetadata;
163205

164-
use crate::packages::{self, http_tracker_core};
165-
use crate::servers::http::test_helpers::tests::sample_info_hash;
166-
use crate::servers::http::v1::services::scrape::invoke;
167-
use crate::servers::http::v1::services::scrape::tests::{
206+
use crate::packages::http_tracker_core::services::scrape::invoke;
207+
use crate::packages::http_tracker_core::services::scrape::tests::{
168208
initialize_announce_and_scrape_handlers_for_public_tracker, initialize_scrape_handler, sample_info_hashes,
169209
sample_peer, MockHttpStatsEventSender,
170210
};
211+
use crate::packages::{self, http_tracker_core};
212+
use crate::servers::http::test_helpers::tests::sample_info_hash;
171213

172214
#[tokio::test]
173215
async fn it_should_return_the_scrape_data_for_a_torrent() {
@@ -247,12 +289,12 @@ mod tests {
247289
use mockall::predicate::eq;
248290
use torrust_tracker_primitives::core::ScrapeData;
249291

250-
use crate::packages::{self, http_tracker_core};
251-
use crate::servers::http::test_helpers::tests::sample_info_hash;
252-
use crate::servers::http::v1::services::scrape::fake;
253-
use crate::servers::http::v1::services::scrape::tests::{
292+
use crate::packages::http_tracker_core::services::scrape::fake;
293+
use crate::packages::http_tracker_core::services::scrape::tests::{
254294
initialize_announce_and_scrape_handlers_for_public_tracker, sample_info_hashes, sample_peer, MockHttpStatsEventSender,
255295
};
296+
use crate::packages::{self, http_tracker_core};
297+
use crate::servers::http::test_helpers::tests::sample_info_hash;
256298

257299
#[tokio::test]
258300
async fn it_should_always_return_the_zeroed_scrape_data_for_a_torrent() {

src/packages/udp_tracker_core/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1+
pub mod peer_builder;
2+
pub mod services;
13
pub mod statistics;
File renamed without changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//! The `announce` service.
2+
//!
3+
//! The service is responsible for handling the `announce` requests.
4+
//!
5+
//! It delegates the `announce` logic to the [`AnnounceHandler`] and it returns
6+
//! the [`AnnounceData`].
7+
//!
8+
//! It also sends an [`udp_tracker_core::statistics::event::Event`]
9+
//! because events are specific for the HTTP tracker.
10+
use std::net::{IpAddr, SocketAddr};
11+
use std::sync::Arc;
12+
13+
use aquatic_udp_protocol::AnnounceRequest;
14+
use bittorrent_primitives::info_hash::InfoHash;
15+
use bittorrent_tracker_core::announce_handler::{AnnounceHandler, PeersWanted};
16+
use bittorrent_tracker_core::error::WhitelistError;
17+
use bittorrent_tracker_core::whitelist;
18+
use torrust_tracker_primitives::core::AnnounceData;
19+
use torrust_tracker_primitives::peer;
20+
21+
use crate::packages::udp_tracker_core::{self, peer_builder};
22+
23+
/// It handles the `Announce` request.
24+
///
25+
/// # Errors
26+
///
27+
/// It will return an error if:
28+
///
29+
/// - The tracker is running in listed mode and the torrent is not in the
30+
/// whitelist.
31+
#[allow(clippy::too_many_arguments)]
32+
pub async fn handle_announce(
33+
remote_addr: SocketAddr,
34+
request: &AnnounceRequest,
35+
announce_handler: &Arc<AnnounceHandler>,
36+
whitelist_authorization: &Arc<whitelist::authorization::WhitelistAuthorization>,
37+
opt_udp_stats_event_sender: &Arc<Option<Box<dyn udp_tracker_core::statistics::event::sender::Sender>>>,
38+
) -> Result<AnnounceData, WhitelistError> {
39+
let info_hash = request.info_hash.into();
40+
let remote_client_ip = remote_addr.ip();
41+
42+
// Authorization
43+
whitelist_authorization.authorize(&info_hash).await?;
44+
45+
let mut peer = peer_builder::from_request(request, &remote_client_ip);
46+
let peers_wanted: PeersWanted = i32::from(request.peers_wanted.0).into();
47+
48+
let announce_data = invoke(
49+
announce_handler.clone(),
50+
opt_udp_stats_event_sender.clone(),
51+
info_hash,
52+
&mut peer,
53+
&peers_wanted,
54+
)
55+
.await;
56+
57+
Ok(announce_data)
58+
}
59+
60+
pub async fn invoke(
61+
announce_handler: Arc<AnnounceHandler>,
62+
opt_udp_stats_event_sender: Arc<Option<Box<dyn udp_tracker_core::statistics::event::sender::Sender>>>,
63+
info_hash: InfoHash,
64+
peer: &mut peer::Peer,
65+
peers_wanted: &PeersWanted,
66+
) -> AnnounceData {
67+
let original_peer_ip = peer.peer_addr.ip();
68+
69+
// The tracker could change the original peer ip
70+
let announce_data = announce_handler.announce(&info_hash, peer, &original_peer_ip, peers_wanted);
71+
72+
if let Some(udp_stats_event_sender) = opt_udp_stats_event_sender.as_deref() {
73+
match original_peer_ip {
74+
IpAddr::V4(_) => {
75+
udp_stats_event_sender
76+
.send_event(udp_tracker_core::statistics::event::Event::Udp4Announce)
77+
.await;
78+
}
79+
IpAddr::V6(_) => {
80+
udp_stats_event_sender
81+
.send_event(udp_tracker_core::statistics::event::Event::Udp6Announce)
82+
.await;
83+
}
84+
}
85+
}
86+
87+
announce_data
88+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod announce;
2+
pub mod scrape;

0 commit comments

Comments
 (0)