Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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;
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