Skip to content

Commit

Permalink
Merge #1269: Overhaul core Tracker: review whitelist functionality
Browse files Browse the repository at this point in the history
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
  • Loading branch information
josecelano committed Feb 14, 2025
2 parents 2f505a8 + eca5c59 commit ec07b78
Show file tree
Hide file tree
Showing 23 changed files with 2,248 additions and 1,974 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/http-protocol/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ percent-encoding = "2"
serde = { version = "1", features = ["derive"] }
serde_bencode = "0"
thiserror = "2"
torrust-tracker-clock = { version = "3.0.0-develop", path = "../clock" }
torrust-tracker-configuration = { version = "3.0.0-develop", path = "../configuration" }
torrust-tracker-contrib-bencode = { version = "3.0.0-develop", path = "../../contrib/bencode" }
torrust-tracker-located-error = { version = "3.0.0-develop", path = "../located-error" }
Expand Down
13 changes: 13 additions & 0 deletions packages/http-protocol/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
//! Primitive types and function for `BitTorrent` HTTP trackers.
pub mod percent_encoding;
pub mod v1;

use torrust_tracker_clock::clock;

/// This code needs to be copied into each crate.
/// Working version, for production.
#[cfg(not(test))]
#[allow(dead_code)]
pub(crate) type CurrentClock = clock::Working;

/// Stopped version, for testing.
#[cfg(test)]
#[allow(dead_code)]
pub(crate) type CurrentClock = clock::Stopped;
33 changes: 32 additions & 1 deletion packages/http-protocol/src/v1/requests/announce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@
//!
//! Data structures and logic for parsing the `announce` request.
use std::fmt;
use std::net::{IpAddr, SocketAddr};
use std::panic::Location;
use std::str::FromStr;

use aquatic_udp_protocol::{NumberOfBytes, PeerId};
use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId};
use bittorrent_primitives::info_hash::{self, InfoHash};
use thiserror::Error;
use torrust_tracker_clock::clock::Time;
use torrust_tracker_located_error::{Located, LocatedError};
use torrust_tracker_primitives::peer;

use crate::percent_encoding::{percent_decode_info_hash, percent_decode_peer_id};
use crate::v1::query::{ParseQueryError, Query};
use crate::v1::responses;
use crate::CurrentClock;

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

/// It builds a `Peer` from the announce request.
///
/// It ignores the peer address in the announce request params.
#[must_use]
pub fn peer_from_request(announce_request: &Announce, peer_ip: &IpAddr) -> peer::Peer {
peer::Peer {
peer_id: announce_request.peer_id,
peer_addr: SocketAddr::new(*peer_ip, announce_request.port),
updated: CurrentClock::now(),
uploaded: announce_request.uploaded.unwrap_or(NumberOfBytes::new(0)),
downloaded: announce_request.downloaded.unwrap_or(NumberOfBytes::new(0)),
left: announce_request.left.unwrap_or(NumberOfBytes::new(0)),
event: map_to_torrust_event(&announce_request.event),
}
}

#[must_use]
pub fn map_to_torrust_event(event: &Option<Event>) -> AnnounceEvent {
match event {
Some(event) => match &event {
Event::Started => AnnounceEvent::Started,
Event::Stopped => AnnounceEvent::Stopped,
Event::Completed => AnnounceEvent::Completed,
},
None => AnnounceEvent::None,
}
}

#[cfg(test)]
mod tests {

Expand Down
1 change: 1 addition & 0 deletions src/packages/http_tracker_core/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod services;
pub mod statistics;
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@
use std::net::IpAddr;
use std::sync::Arc;

use bittorrent_http_protocol::v1::requests::announce::{peer_from_request, Announce};
use bittorrent_http_protocol::v1::responses;
use bittorrent_http_protocol::v1::services::peer_ip_resolver::{self, ClientIpSources};
use bittorrent_primitives::info_hash::InfoHash;
use bittorrent_tracker_core::announce_handler::{AnnounceHandler, PeersWanted};
use bittorrent_tracker_core::authentication::service::AuthenticationService;
use bittorrent_tracker_core::whitelist;
use torrust_tracker_configuration::Core;
use torrust_tracker_primitives::core::AnnounceData;
use torrust_tracker_primitives::peer;

Expand All @@ -27,6 +33,53 @@ use crate::packages::http_tracker_core;
/// > **NOTICE**: as the HTTP tracker does not requires a connection request
/// > like the UDP tracker, the number of TCP connections is incremented for
/// > each `announce` request.
///
/// # Errors
///
/// This function will return an error if:
///
/// - The tracker is running in `listed` mode and the torrent is not whitelisted.
/// - There is an error when resolving the client IP address.
#[allow(clippy::too_many_arguments)]
pub async fn handle_announce(
core_config: &Arc<Core>,
announce_handler: &Arc<AnnounceHandler>,
_authentication_service: &Arc<AuthenticationService>,
whitelist_authorization: &Arc<whitelist::authorization::WhitelistAuthorization>,
opt_http_stats_event_sender: &Arc<Option<Box<dyn http_tracker_core::statistics::event::sender::Sender>>>,
announce_request: &Announce,
client_ip_sources: &ClientIpSources,
) -> Result<AnnounceData, responses::error::Error> {
// Authorization
match whitelist_authorization.authorize(&announce_request.info_hash).await {
Ok(()) => (),
Err(error) => return Err(responses::error::Error::from(error)),
}

let peer_ip = match peer_ip_resolver::invoke(core_config.net.on_reverse_proxy, client_ip_sources) {
Ok(peer_ip) => peer_ip,
Err(error) => return Err(responses::error::Error::from(error)),
};

let mut peer = peer_from_request(announce_request, &peer_ip);

let peers_wanted = match announce_request.numwant {
Some(numwant) => PeersWanted::only(numwant),
None => PeersWanted::AsManyAsPossible,
};

let announce_data = invoke(
announce_handler.clone(),
opt_http_stats_event_sender.clone(),
announce_request.info_hash,
&mut peer,
&peers_wanted,
)
.await;

Ok(announce_data)
}

pub async fn invoke(
announce_handler: Arc<AnnounceHandler>,
opt_http_stats_event_sender: Arc<Option<Box<dyn http_tracker_core::statistics::event::sender::Sender>>>,
Expand Down Expand Up @@ -164,11 +217,11 @@ mod tests {

use super::{sample_peer_using_ipv4, sample_peer_using_ipv6};
use crate::packages::http_tracker_core;
use crate::servers::http::test_helpers::tests::sample_info_hash;
use crate::servers::http::v1::services::announce::invoke;
use crate::servers::http::v1::services::announce::tests::{
use crate::packages::http_tracker_core::services::announce::invoke;
use crate::packages::http_tracker_core::services::announce::tests::{
initialize_core_tracker_services, sample_peer, MockHttpStatsEventSender,
};
use crate::servers::http::test_helpers::tests::sample_info_hash;

fn initialize_announce_handler() -> Arc<AnnounceHandler> {
let config = configuration::ephemeral();
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@
use std::net::IpAddr;
use std::sync::Arc;

use bittorrent_http_protocol::v1::requests::scrape::Scrape;
use bittorrent_http_protocol::v1::responses;
use bittorrent_http_protocol::v1::services::peer_ip_resolver::{self, ClientIpSources};
use bittorrent_primitives::info_hash::InfoHash;
use bittorrent_tracker_core::authentication::service::AuthenticationService;
use bittorrent_tracker_core::scrape_handler::ScrapeHandler;
use torrust_tracker_configuration::Core;
use torrust_tracker_primitives::core::ScrapeData;

use crate::packages::http_tracker_core;
Expand All @@ -26,6 +31,43 @@ use crate::packages::http_tracker_core;
/// > **NOTICE**: as the HTTP tracker does not requires a connection request
/// > like the UDP tracker, the number of TCP connections is incremented for
/// > each `scrape` request.
///
/// # Errors
///
/// This function will return an error if:
///
/// - There is an error when resolving the client IP address.
#[allow(clippy::too_many_arguments)]
pub async fn handle_scrape(
core_config: &Arc<Core>,
scrape_handler: &Arc<ScrapeHandler>,
_authentication_service: &Arc<AuthenticationService>,
opt_http_stats_event_sender: &Arc<Option<Box<dyn http_tracker_core::statistics::event::sender::Sender>>>,
scrape_request: &Scrape,
client_ip_sources: &ClientIpSources,
return_real_scrape_data: bool,
) -> Result<ScrapeData, responses::error::Error> {
// Authorization for scrape requests is handled at the `http_tracker_core`
// level for each torrent.

let peer_ip = match peer_ip_resolver::invoke(core_config.net.on_reverse_proxy, client_ip_sources) {
Ok(peer_ip) => peer_ip,
Err(error) => return Err(responses::error::Error::from(error)),
};

if return_real_scrape_data {
Ok(invoke(
scrape_handler,
opt_http_stats_event_sender,
&scrape_request.info_hashes,
&peer_ip,
)
.await)
} else {
Ok(http_tracker_core::services::scrape::fake(opt_http_stats_event_sender, &scrape_request.info_hashes, &peer_ip).await)
}
}

pub async fn invoke(
scrape_handler: &Arc<ScrapeHandler>,
opt_http_stats_event_sender: &Arc<Option<Box<dyn http_tracker_core::statistics::event::sender::Sender>>>,
Expand Down Expand Up @@ -161,13 +203,13 @@ mod tests {
use torrust_tracker_primitives::core::ScrapeData;
use torrust_tracker_primitives::swarm_metadata::SwarmMetadata;

use crate::packages::{self, http_tracker_core};
use crate::servers::http::test_helpers::tests::sample_info_hash;
use crate::servers::http::v1::services::scrape::invoke;
use crate::servers::http::v1::services::scrape::tests::{
use crate::packages::http_tracker_core::services::scrape::invoke;
use crate::packages::http_tracker_core::services::scrape::tests::{
initialize_announce_and_scrape_handlers_for_public_tracker, initialize_scrape_handler, sample_info_hashes,
sample_peer, MockHttpStatsEventSender,
};
use crate::packages::{self, http_tracker_core};
use crate::servers::http::test_helpers::tests::sample_info_hash;

#[tokio::test]
async fn it_should_return_the_scrape_data_for_a_torrent() {
Expand Down Expand Up @@ -247,12 +289,12 @@ mod tests {
use mockall::predicate::eq;
use torrust_tracker_primitives::core::ScrapeData;

use crate::packages::{self, http_tracker_core};
use crate::servers::http::test_helpers::tests::sample_info_hash;
use crate::servers::http::v1::services::scrape::fake;
use crate::servers::http::v1::services::scrape::tests::{
use crate::packages::http_tracker_core::services::scrape::fake;
use crate::packages::http_tracker_core::services::scrape::tests::{
initialize_announce_and_scrape_handlers_for_public_tracker, sample_info_hashes, sample_peer, MockHttpStatsEventSender,
};
use crate::packages::{self, http_tracker_core};
use crate::servers::http::test_helpers::tests::sample_info_hash;

#[tokio::test]
async fn it_should_always_return_the_zeroed_scrape_data_for_a_torrent() {
Expand Down
2 changes: 2 additions & 0 deletions src/packages/udp_tracker_core/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
pub mod peer_builder;
pub mod services;
pub mod statistics;
File renamed without changes.
88 changes: 88 additions & 0 deletions src/packages/udp_tracker_core/services/announce.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//! The `announce` service.
//!
//! The service is responsible for handling the `announce` requests.
//!
//! It delegates the `announce` logic to the [`AnnounceHandler`] and it returns
//! the [`AnnounceData`].
//!
//! It also sends an [`udp_tracker_core::statistics::event::Event`]
//! because events are specific for the HTTP tracker.
use std::net::{IpAddr, SocketAddr};
use std::sync::Arc;

use aquatic_udp_protocol::AnnounceRequest;
use bittorrent_primitives::info_hash::InfoHash;
use bittorrent_tracker_core::announce_handler::{AnnounceHandler, PeersWanted};
use bittorrent_tracker_core::error::WhitelistError;
use bittorrent_tracker_core::whitelist;
use torrust_tracker_primitives::core::AnnounceData;
use torrust_tracker_primitives::peer;

use crate::packages::udp_tracker_core::{self, peer_builder};

/// It handles the `Announce` request.
///
/// # Errors
///
/// It will return an error if:
///
/// - The tracker is running in listed mode and the torrent is not in the
/// whitelist.
#[allow(clippy::too_many_arguments)]
pub async fn handle_announce(
remote_addr: SocketAddr,
request: &AnnounceRequest,
announce_handler: &Arc<AnnounceHandler>,
whitelist_authorization: &Arc<whitelist::authorization::WhitelistAuthorization>,
opt_udp_stats_event_sender: &Arc<Option<Box<dyn udp_tracker_core::statistics::event::sender::Sender>>>,
) -> Result<AnnounceData, WhitelistError> {
let info_hash = request.info_hash.into();
let remote_client_ip = remote_addr.ip();

// Authorization
whitelist_authorization.authorize(&info_hash).await?;

let mut peer = peer_builder::from_request(request, &remote_client_ip);
let peers_wanted: PeersWanted = i32::from(request.peers_wanted.0).into();

let announce_data = invoke(
announce_handler.clone(),
opt_udp_stats_event_sender.clone(),
info_hash,
&mut peer,
&peers_wanted,
)
.await;

Ok(announce_data)
}

pub async fn invoke(
announce_handler: Arc<AnnounceHandler>,
opt_udp_stats_event_sender: Arc<Option<Box<dyn udp_tracker_core::statistics::event::sender::Sender>>>,
info_hash: InfoHash,
peer: &mut peer::Peer,
peers_wanted: &PeersWanted,
) -> AnnounceData {
let original_peer_ip = peer.peer_addr.ip();

// The tracker could change the original peer ip
let announce_data = announce_handler.announce(&info_hash, peer, &original_peer_ip, peers_wanted);

if let Some(udp_stats_event_sender) = opt_udp_stats_event_sender.as_deref() {
match original_peer_ip {
IpAddr::V4(_) => {
udp_stats_event_sender
.send_event(udp_tracker_core::statistics::event::Event::Udp4Announce)
.await;
}
IpAddr::V6(_) => {
udp_stats_event_sender
.send_event(udp_tracker_core::statistics::event::Event::Udp6Announce)
.await;
}
}
}

announce_data
}
2 changes: 2 additions & 0 deletions src/packages/udp_tracker_core/services/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod announce;
pub mod scrape;
Loading

0 comments on commit ec07b78

Please sign in to comment.