diff --git a/console/tracker-client/src/console/clients/checker/app.rs b/console/tracker-client/src/console/clients/checker/app.rs index 395f65df9..88ce5a8ac 100644 --- a/console/tracker-client/src/console/clients/checker/app.rs +++ b/console/tracker-client/src/console/clients/checker/app.rs @@ -114,7 +114,7 @@ fn setup_config(args: Args) -> Result<Configuration> { } fn load_config_from_file(path: &PathBuf) -> Result<Configuration> { - let file_content = std::fs::read_to_string(path).with_context(|| format!("can't read config file {path:?}"))?; + let file_content = std::fs::read_to_string(path).with_context(|| format!("can't read config file {}", path.display()))?; parse_from_json(&file_content).context("invalid config format") } diff --git a/packages/http-tracker-core/src/services/announce.rs b/packages/http-tracker-core/src/services/announce.rs index 5890d35c1..959dcc615 100644 --- a/packages/http-tracker-core/src/services/announce.rs +++ b/packages/http-tracker-core/src/services/announce.rs @@ -13,6 +13,7 @@ use std::sync::Arc; use bittorrent_http_tracker_protocol::v1::requests::announce::{peer_from_request, Announce}; use bittorrent_http_tracker_protocol::v1::services::peer_ip_resolver::{self, ClientIpSources, PeerIpResolutionError}; +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::authentication::{self, Key}; @@ -23,56 +24,6 @@ use torrust_tracker_primitives::core::AnnounceData; use crate::statistics; -/// Errors related to announce requests. -#[derive(thiserror::Error, Debug, Clone)] -pub enum HttpAnnounceError { - #[error("Error resolving peer IP: {source}")] - PeerIpResolutionError { source: PeerIpResolutionError }, - - #[error("Tracker core error: {source}")] - TrackerCoreError { source: TrackerCoreError }, -} - -impl From<PeerIpResolutionError> for HttpAnnounceError { - fn from(peer_ip_resolution_error: PeerIpResolutionError) -> Self { - Self::PeerIpResolutionError { - source: peer_ip_resolution_error, - } - } -} - -impl From<TrackerCoreError> for HttpAnnounceError { - fn from(tracker_core_error: TrackerCoreError) -> Self { - Self::TrackerCoreError { - source: tracker_core_error, - } - } -} - -impl From<AnnounceError> for HttpAnnounceError { - fn from(announce_error: AnnounceError) -> Self { - Self::TrackerCoreError { - source: announce_error.into(), - } - } -} - -impl From<WhitelistError> for HttpAnnounceError { - fn from(whitelist_error: WhitelistError) -> Self { - Self::TrackerCoreError { - source: whitelist_error.into(), - } - } -} - -impl From<authentication::key::Error> for HttpAnnounceError { - fn from(whitelist_error: authentication::key::Error) -> Self { - Self::TrackerCoreError { - source: whitelist_error.into(), - } - } -} - /// The HTTP tracker `announce` service. /// /// The service sends an statistics event that increments: @@ -123,50 +74,61 @@ impl AnnounceService { client_ip_sources: &ClientIpSources, maybe_key: Option<Key>, ) -> Result<AnnounceData, HttpAnnounceError> { - // Authentication - if self.core_config.private { - match maybe_key { - Some(key) => match self.authentication_service.authenticate(&key).await { - Ok(()) => (), - Err(error) => return Err(error.into()), - }, - None => { - return Err(authentication::key::Error::MissingAuthKey { - location: Location::caller(), - } - .into()) - } - } - } - - // Authorization - match self.whitelist_authorization.authorize(&announce_request.info_hash).await { - Ok(()) => (), - Err(error) => return Err(error.into()), - } + self.authenticate(maybe_key).await?; - let peer_ip = match peer_ip_resolver::invoke(self.core_config.net.on_reverse_proxy, client_ip_sources) { - Ok(peer_ip) => peer_ip, - Err(error) => return Err(error.into()), - }; + self.authorize(announce_request.info_hash).await?; - let mut peer = peer_from_request(announce_request, &peer_ip); + let remote_client_ip = self.resolve_remote_client_ip(client_ip_sources)?; - let peers_wanted = match announce_request.numwant { - Some(numwant) => PeersWanted::only(numwant), - None => PeersWanted::AsManyAsPossible, - }; + let mut peer = peer_from_request(announce_request, &remote_client_ip); - let original_peer_ip = peer.peer_addr.ip(); + let peers_wanted = Self::peers_wanted(announce_request); - // The tracker could change the original peer ip let announce_data = self .announce_handler - .announce(&announce_request.info_hash, &mut peer, &original_peer_ip, &peers_wanted) + .announce(&announce_request.info_hash, &mut peer, &remote_client_ip, &peers_wanted) .await?; + self.send_stats_event(remote_client_ip).await; + + Ok(announce_data) + } + + async fn authenticate(&self, maybe_key: Option<Key>) -> Result<(), authentication::key::Error> { + if self.core_config.private { + let key = maybe_key.ok_or(authentication::key::Error::MissingAuthKey { + location: Location::caller(), + })?; + + self.authentication_service.authenticate(&key).await?; + } + + Ok(()) + } + + async fn authorize(&self, info_hash: InfoHash) -> Result<(), WhitelistError> { + self.whitelist_authorization.authorize(&info_hash).await + } + + /// Resolves the client's real IP address considering proxy headers + fn resolve_remote_client_ip(&self, client_ip_sources: &ClientIpSources) -> Result<IpAddr, PeerIpResolutionError> { + match peer_ip_resolver::invoke(self.core_config.net.on_reverse_proxy, client_ip_sources) { + Ok(peer_ip) => Ok(peer_ip), + Err(error) => Err(error), + } + } + + /// Determines how many peers the client wants in the response + fn peers_wanted(announce_request: &Announce) -> PeersWanted { + match announce_request.numwant { + Some(numwant) => PeersWanted::only(numwant), + None => PeersWanted::AsManyAsPossible, + } + } + + async fn send_stats_event(&self, peer_ip: IpAddr) { if let Some(http_stats_event_sender) = self.opt_http_stats_event_sender.as_deref() { - match original_peer_ip { + match peer_ip { IpAddr::V4(_) => { http_stats_event_sender .send_event(statistics::event::Event::Tcp4Announce) @@ -179,8 +141,56 @@ impl AnnounceService { } } } + } +} - Ok(announce_data) +/// Errors related to announce requests. +#[derive(thiserror::Error, Debug, Clone)] +pub enum HttpAnnounceError { + #[error("Error resolving peer IP: {source}")] + PeerIpResolutionError { source: PeerIpResolutionError }, + + #[error("Tracker core error: {source}")] + TrackerCoreError { source: TrackerCoreError }, +} + +impl From<PeerIpResolutionError> for HttpAnnounceError { + fn from(peer_ip_resolution_error: PeerIpResolutionError) -> Self { + Self::PeerIpResolutionError { + source: peer_ip_resolution_error, + } + } +} + +impl From<TrackerCoreError> for HttpAnnounceError { + fn from(tracker_core_error: TrackerCoreError) -> Self { + Self::TrackerCoreError { + source: tracker_core_error, + } + } +} + +impl From<AnnounceError> for HttpAnnounceError { + fn from(announce_error: AnnounceError) -> Self { + Self::TrackerCoreError { + source: announce_error.into(), + } + } +} + +impl From<WhitelistError> for HttpAnnounceError { + fn from(whitelist_error: WhitelistError) -> Self { + Self::TrackerCoreError { + source: whitelist_error.into(), + } + } +} + +impl From<authentication::key::Error> for HttpAnnounceError { + fn from(whitelist_error: authentication::key::Error) -> Self { + Self::TrackerCoreError { + source: whitelist_error.into(), + } } } diff --git a/packages/http-tracker-core/src/services/scrape.rs b/packages/http-tracker-core/src/services/scrape.rs index 48cee7c8c..dcb88508c 100644 --- a/packages/http-tracker-core/src/services/scrape.rs +++ b/packages/http-tracker-core/src/services/scrape.rs @@ -12,7 +12,6 @@ use std::sync::Arc; use bittorrent_http_tracker_protocol::v1::requests::scrape::Scrape; use bittorrent_http_tracker_protocol::v1::services::peer_ip_resolver::{self, ClientIpSources, PeerIpResolutionError}; -use bittorrent_primitives::info_hash::InfoHash; use bittorrent_tracker_core::authentication::service::AuthenticationService; use bittorrent_tracker_core::authentication::{self, Key}; use bittorrent_tracker_core::error::{ScrapeError, TrackerCoreError, WhitelistError}; @@ -22,55 +21,6 @@ use torrust_tracker_primitives::core::ScrapeData; use crate::statistics; -/// Errors related to announce requests. -#[derive(thiserror::Error, Debug, Clone)] -pub enum HttpScrapeError { - #[error("Error resolving peer IP: {source}")] - PeerIpResolutionError { source: PeerIpResolutionError }, - - #[error("Tracker core error: {source}")] - TrackerCoreError { source: TrackerCoreError }, -} - -impl From<PeerIpResolutionError> for HttpScrapeError { - fn from(peer_ip_resolution_error: PeerIpResolutionError) -> Self { - Self::PeerIpResolutionError { - source: peer_ip_resolution_error, - } - } -} - -impl From<TrackerCoreError> for HttpScrapeError { - fn from(tracker_core_error: TrackerCoreError) -> Self { - Self::TrackerCoreError { - source: tracker_core_error, - } - } -} - -impl From<ScrapeError> for HttpScrapeError { - fn from(announce_error: ScrapeError) -> Self { - Self::TrackerCoreError { - source: announce_error.into(), - } - } -} - -impl From<WhitelistError> for HttpScrapeError { - fn from(whitelist_error: WhitelistError) -> Self { - Self::TrackerCoreError { - source: whitelist_error.into(), - } - } -} - -impl From<authentication::key::Error> for HttpScrapeError { - fn from(whitelist_error: authentication::key::Error) -> Self { - Self::TrackerCoreError { - source: whitelist_error.into(), - } - } -} /// The HTTP tracker `scrape` service. /// /// The service sends an statistics event that increments: @@ -110,6 +60,11 @@ impl ScrapeService { } } + /// Handles a scrape request. + /// + /// When the peer is not authenticated and the tracker is running in `private` + /// mode, the tracker returns empty stats for all the torrents. + /// /// # Errors /// /// This function will return an error if: @@ -121,67 +76,93 @@ impl ScrapeService { client_ip_sources: &ClientIpSources, maybe_key: Option<Key>, ) -> Result<ScrapeData, HttpScrapeError> { - // Authentication - let return_fake_scrape_data = if self.core_config.private { - match maybe_key { - Some(key) => match self.authentication_service.authenticate(&key).await { - Ok(()) => false, - Err(_error) => true, - }, - None => true, - } + let scrape_data = if self.authentication_is_required() && !self.is_authenticated(maybe_key).await { + ScrapeData::zeroed(&scrape_request.info_hashes) } else { - false + self.scrape_handler.scrape(&scrape_request.info_hashes).await? }; - // Authorization for scrape requests is handled at the `bittorrent_tracker_core` - // level for each torrent. + let remote_client_ip = self.resolve_remote_client_ip(client_ip_sources)?; - let peer_ip = match peer_ip_resolver::invoke(self.core_config.net.on_reverse_proxy, client_ip_sources) { - Ok(peer_ip) => peer_ip, - Err(error) => return Err(error.into()), - }; + self.send_stats_event(&remote_client_ip).await; + + Ok(scrape_data) + } + + fn authentication_is_required(&self) -> bool { + self.core_config.private + } - if return_fake_scrape_data { - return Ok(fake(&self.opt_http_stats_event_sender, &scrape_request.info_hashes, &peer_ip).await); + async fn is_authenticated(&self, maybe_key: Option<Key>) -> bool { + if let Some(key) = maybe_key { + return self.authentication_service.authenticate(&key).await.is_ok(); } - let scrape_data = self.scrape_handler.scrape(&scrape_request.info_hashes).await?; + false + } - send_scrape_event(&peer_ip, &self.opt_http_stats_event_sender).await; + /// Resolves the client's real IP address considering proxy headers. + fn resolve_remote_client_ip(&self, client_ip_sources: &ClientIpSources) -> Result<IpAddr, PeerIpResolutionError> { + peer_ip_resolver::invoke(self.core_config.net.on_reverse_proxy, client_ip_sources) + } - Ok(scrape_data) + async fn send_stats_event(&self, original_peer_ip: &IpAddr) { + if let Some(http_stats_event_sender) = self.opt_http_stats_event_sender.as_deref() { + let event = match original_peer_ip { + IpAddr::V4(_) => statistics::event::Event::Tcp4Scrape, + IpAddr::V6(_) => statistics::event::Event::Tcp6Scrape, + }; + http_stats_event_sender.send_event(event).await; + } } } -/// The HTTP tracker fake `scrape` service. It returns zeroed stats. -/// -/// When the peer is not authenticated and the tracker is running in `private` mode, -/// the tracker returns empty stats for all the torrents. -/// -/// > **NOTICE**: tracker statistics are not updated in this case. -pub async fn fake( - opt_http_stats_event_sender: &Arc<Option<Box<dyn statistics::event::sender::Sender>>>, - info_hashes: &Vec<InfoHash>, - original_peer_ip: &IpAddr, -) -> ScrapeData { - send_scrape_event(original_peer_ip, opt_http_stats_event_sender).await; - - ScrapeData::zeroed(info_hashes) +/// Errors related to announce requests. +#[derive(thiserror::Error, Debug, Clone)] +pub enum HttpScrapeError { + #[error("Error resolving peer IP: {source}")] + PeerIpResolutionError { source: PeerIpResolutionError }, + + #[error("Tracker core error: {source}")] + TrackerCoreError { source: TrackerCoreError }, } -async fn send_scrape_event( - original_peer_ip: &IpAddr, - opt_http_stats_event_sender: &Arc<Option<Box<dyn statistics::event::sender::Sender>>>, -) { - if let Some(http_stats_event_sender) = opt_http_stats_event_sender.as_deref() { - match original_peer_ip { - IpAddr::V4(_) => { - http_stats_event_sender.send_event(statistics::event::Event::Tcp4Scrape).await; - } - IpAddr::V6(_) => { - http_stats_event_sender.send_event(statistics::event::Event::Tcp6Scrape).await; - } +impl From<PeerIpResolutionError> for HttpScrapeError { + fn from(peer_ip_resolution_error: PeerIpResolutionError) -> Self { + Self::PeerIpResolutionError { + source: peer_ip_resolution_error, + } + } +} + +impl From<TrackerCoreError> for HttpScrapeError { + fn from(tracker_core_error: TrackerCoreError) -> Self { + Self::TrackerCoreError { + source: tracker_core_error, + } + } +} + +impl From<ScrapeError> for HttpScrapeError { + fn from(announce_error: ScrapeError) -> Self { + Self::TrackerCoreError { + source: announce_error.into(), + } + } +} + +impl From<WhitelistError> for HttpScrapeError { + fn from(whitelist_error: WhitelistError) -> Self { + Self::TrackerCoreError { + source: whitelist_error.into(), + } + } +} + +impl From<authentication::key::Error> for HttpScrapeError { + fn from(whitelist_error: authentication::key::Error) -> Self { + Self::TrackerCoreError { + source: whitelist_error.into(), } } } @@ -208,7 +189,6 @@ mod tests { use tokio::sync::mpsc::error::SendError; use torrust_tracker_configuration::Configuration; use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch}; - use torrust_tracker_test_helpers::configuration; use crate::statistics; use crate::tests::sample_info_hash; @@ -219,10 +199,6 @@ mod tests { authentication_service: Arc<AuthenticationService>, } - fn initialize_services_for_public_tracker() -> Container { - initialize_services_with_configuration(&configuration::ephemeral_public()) - } - fn initialize_services_with_configuration(config: &Configuration) -> Container { let database = initialize_database(&config.core); let in_memory_whitelist = Arc::new(InMemoryWhitelist::default()); @@ -433,28 +409,34 @@ mod tests { use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::sync::Arc; + use bittorrent_http_tracker_protocol::v1::requests::scrape::Scrape; + use bittorrent_http_tracker_protocol::v1::services::peer_ip_resolver::ClientIpSources; use bittorrent_tracker_core::announce_handler::PeersWanted; use mockall::predicate::eq; use torrust_tracker_primitives::core::ScrapeData; + use torrust_tracker_test_helpers::configuration; - use crate::services::scrape::fake; use crate::services::scrape::tests::{ - initialize_services_for_public_tracker, sample_info_hashes, sample_peer, MockHttpStatsEventSender, + initialize_services_with_configuration, sample_info_hashes, sample_peer, MockHttpStatsEventSender, }; + use crate::services::scrape::ScrapeService; use crate::statistics; use crate::tests::sample_info_hash; #[tokio::test] - async fn it_should_always_return_the_zeroed_scrape_data_for_a_torrent() { + async fn it_should_return_the_zeroed_scrape_data_when_the_tracker_is_running_in_private_mode_and_the_peer_is_not_authenticated( + ) { + let config = configuration::ephemeral_private(); + + let container = initialize_services_with_configuration(&config); + let (http_stats_event_sender, _http_stats_repository) = statistics::setup::factory(false); let http_stats_event_sender = Arc::new(http_stats_event_sender); - let container = initialize_services_for_public_tracker(); - let info_hash = sample_info_hash(); let info_hashes = vec![info_hash]; - // Announce a new peer to force scrape data to contain not zeroed data + // Announce a new peer to force scrape data to contain non zeroed data let mut peer = sample_peer(); let original_peer_ip = peer.ip(); container @@ -463,7 +445,26 @@ mod tests { .await .unwrap(); - let scrape_data = fake(&http_stats_event_sender, &info_hashes, &original_peer_ip).await; + let scrape_request = Scrape { + info_hashes: sample_info_hashes(), + }; + + let client_ip_sources = ClientIpSources { + right_most_x_forwarded_for: None, + connection_info_ip: Some(original_peer_ip), + }; + + let scrape_service = Arc::new(ScrapeService::new( + Arc::new(config.core), + container.scrape_handler.clone(), + container.authentication_service.clone(), + http_stats_event_sender.clone(), + )); + + let scrape_data = scrape_service + .handle_scrape(&scrape_request, &client_ip_sources, None) + .await + .unwrap(); let expected_scrape_data = ScrapeData::zeroed(&info_hashes); @@ -472,6 +473,10 @@ mod tests { #[tokio::test] async fn it_should_send_the_tcp_4_scrape_event_when_the_peer_uses_ipv4() { + let config = configuration::ephemeral(); + + let container = initialize_services_with_configuration(&config); + let mut http_stats_event_sender_mock = MockHttpStatsEventSender::new(); http_stats_event_sender_mock .expect_send_event() @@ -483,11 +488,34 @@ mod tests { let peer_ip = IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)); - fake(&http_stats_event_sender, &sample_info_hashes(), &peer_ip).await; + let scrape_request = Scrape { + info_hashes: sample_info_hashes(), + }; + + let client_ip_sources = ClientIpSources { + right_most_x_forwarded_for: None, + connection_info_ip: Some(peer_ip), + }; + + let scrape_service = Arc::new(ScrapeService::new( + Arc::new(config.core), + container.scrape_handler.clone(), + container.authentication_service.clone(), + http_stats_event_sender.clone(), + )); + + scrape_service + .handle_scrape(&scrape_request, &client_ip_sources, None) + .await + .unwrap(); } #[tokio::test] async fn it_should_send_the_tcp_6_scrape_event_when_the_peer_uses_ipv6() { + let config = configuration::ephemeral(); + + let container = initialize_services_with_configuration(&config); + let mut http_stats_event_sender_mock = MockHttpStatsEventSender::new(); http_stats_event_sender_mock .expect_send_event() @@ -499,7 +527,26 @@ mod tests { let peer_ip = IpAddr::V6(Ipv6Addr::new(0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969)); - fake(&http_stats_event_sender, &sample_info_hashes(), &peer_ip).await; + let scrape_request = Scrape { + info_hashes: sample_info_hashes(), + }; + + let client_ip_sources = ClientIpSources { + right_most_x_forwarded_for: None, + connection_info_ip: Some(peer_ip), + }; + + let scrape_service = Arc::new(ScrapeService::new( + Arc::new(config.core), + container.scrape_handler.clone(), + container.authentication_service.clone(), + http_stats_event_sender.clone(), + )); + + scrape_service + .handle_scrape(&scrape_request, &client_ip_sources, None) + .await + .unwrap(); } } } diff --git a/packages/udp-tracker-core/src/services/announce.rs b/packages/udp-tracker-core/src/services/announce.rs index 051944d7e..698f5fba6 100644 --- a/packages/udp-tracker-core/src/services/announce.rs +++ b/packages/udp-tracker-core/src/services/announce.rs @@ -12,6 +12,7 @@ use std::ops::Range; 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::{AnnounceError, WhitelistError}; use bittorrent_tracker_core::whitelist; @@ -21,47 +22,15 @@ use torrust_tracker_primitives::core::AnnounceData; use crate::connection_cookie::{check, gen_remote_fingerprint, ConnectionCookieError}; use crate::statistics; -/// Errors related to announce requests. -#[derive(thiserror::Error, Debug, Clone)] -pub enum UdpAnnounceError { - /// Error returned when there was an error with the connection cookie. - #[error("Connection cookie error: {source}")] - ConnectionCookieError { source: ConnectionCookieError }, - - /// Error returned when there was an error with the tracker core announce handler. - #[error("Tracker core announce error: {source}")] - TrackerCoreAnnounceError { source: AnnounceError }, - - /// Error returned when there was an error with the tracker core whitelist. - #[error("Tracker core whitelist error: {source}")] - TrackerCoreWhitelistError { source: WhitelistError }, -} - -impl From<ConnectionCookieError> for UdpAnnounceError { - fn from(connection_cookie_error: ConnectionCookieError) -> Self { - Self::ConnectionCookieError { - source: connection_cookie_error, - } - } -} - -impl From<AnnounceError> for UdpAnnounceError { - fn from(announce_error: AnnounceError) -> Self { - Self::TrackerCoreAnnounceError { source: announce_error } - } -} - -impl From<WhitelistError> for UdpAnnounceError { - fn from(whitelist_error: WhitelistError) -> Self { - Self::TrackerCoreWhitelistError { source: whitelist_error } - } -} - /// The `AnnounceService` is responsible for handling the `announce` requests. +/// +/// The service sends an statistics event that increments: +/// +/// - The number of UDP `announce` requests handled by the UDP tracker. pub struct AnnounceService { - pub announce_handler: Arc<AnnounceHandler>, - pub whitelist_authorization: Arc<whitelist::authorization::WhitelistAuthorization>, - pub opt_udp_core_stats_event_sender: Arc<Option<Box<dyn statistics::event::sender::Sender>>>, + announce_handler: Arc<AnnounceHandler>, + whitelist_authorization: Arc<whitelist::authorization::WhitelistAuthorization>, + opt_udp_core_stats_event_sender: Arc<Option<Box<dyn statistics::event::sender::Sender>>>, } impl AnnounceService { @@ -86,52 +55,94 @@ impl AnnounceService { /// /// - 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( &self, remote_addr: SocketAddr, request: &AnnounceRequest, cookie_valid_range: Range<f64>, ) -> Result<AnnounceData, UdpAnnounceError> { - // Authentication - check( - &request.connection_id, - gen_remote_fingerprint(&remote_addr), - cookie_valid_range, - )?; + Self::authenticate(remote_addr, request, cookie_valid_range)?; let info_hash = request.info_hash.into(); - let remote_client_ip = remote_addr.ip(); - // Authorization - self.whitelist_authorization.authorize(&info_hash).await?; + self.authorize(&info_hash).await?; + + let remote_client_ip = remote_addr.ip(); let mut peer = peer_builder::from_request(request, &remote_client_ip); - let peers_wanted: PeersWanted = i32::from(request.peers_wanted.0).into(); - let original_peer_ip = peer.peer_addr.ip(); + let peers_wanted: PeersWanted = i32::from(request.peers_wanted.0).into(); - // The tracker could change the original peer ip let announce_data = self .announce_handler - .announce(&info_hash, &mut peer, &original_peer_ip, &peers_wanted) + .announce(&info_hash, &mut peer, &remote_client_ip, &peers_wanted) .await?; + self.send_stats_event(remote_client_ip).await; + + Ok(announce_data) + } + + fn authenticate( + remote_addr: SocketAddr, + request: &AnnounceRequest, + cookie_valid_range: Range<f64>, + ) -> Result<f64, ConnectionCookieError> { + check( + &request.connection_id, + gen_remote_fingerprint(&remote_addr), + cookie_valid_range, + ) + } + + async fn authorize(&self, info_hash: &InfoHash) -> Result<(), WhitelistError> { + self.whitelist_authorization.authorize(info_hash).await + } + + async fn send_stats_event(&self, peer_ip: IpAddr) { if let Some(udp_stats_event_sender) = self.opt_udp_core_stats_event_sender.as_deref() { - match original_peer_ip { - IpAddr::V4(_) => { - udp_stats_event_sender - .send_event(statistics::event::Event::Udp4Announce) - .await; - } - IpAddr::V6(_) => { - udp_stats_event_sender - .send_event(statistics::event::Event::Udp6Announce) - .await; - } - } + let event = match peer_ip { + IpAddr::V4(_) => statistics::event::Event::Udp4Announce, + IpAddr::V6(_) => statistics::event::Event::Udp6Announce, + }; + + udp_stats_event_sender.send_event(event).await; } + } +} - Ok(announce_data) +/// Errors related to announce requests. +#[derive(thiserror::Error, Debug, Clone)] +pub enum UdpAnnounceError { + /// Error returned when there was an error with the connection cookie. + #[error("Connection cookie error: {source}")] + ConnectionCookieError { source: ConnectionCookieError }, + + /// Error returned when there was an error with the tracker core announce handler. + #[error("Tracker core announce error: {source}")] + TrackerCoreAnnounceError { source: AnnounceError }, + + /// Error returned when there was an error with the tracker core whitelist. + #[error("Tracker core whitelist error: {source}")] + TrackerCoreWhitelistError { source: WhitelistError }, +} + +impl From<ConnectionCookieError> for UdpAnnounceError { + fn from(connection_cookie_error: ConnectionCookieError) -> Self { + Self::ConnectionCookieError { + source: connection_cookie_error, + } + } +} + +impl From<AnnounceError> for UdpAnnounceError { + fn from(announce_error: AnnounceError) -> Self { + Self::TrackerCoreAnnounceError { source: announce_error } + } +} + +impl From<WhitelistError> for UdpAnnounceError { + fn from(whitelist_error: WhitelistError) -> Self { + Self::TrackerCoreWhitelistError { source: whitelist_error } } } diff --git a/packages/udp-tracker-core/src/services/scrape.rs b/packages/udp-tracker-core/src/services/scrape.rs index fddc2ec2d..61301cd43 100644 --- a/packages/udp-tracker-core/src/services/scrape.rs +++ b/packages/udp-tracker-core/src/services/scrape.rs @@ -20,50 +20,17 @@ use torrust_tracker_primitives::core::ScrapeData; use crate::connection_cookie::{check, gen_remote_fingerprint, ConnectionCookieError}; use crate::statistics; -/// Errors related to scrape requests. -#[derive(thiserror::Error, Debug, Clone)] -pub enum UdpScrapeError { - /// Error returned when there was an error with the connection cookie. - #[error("Connection cookie error: {source}")] - ConnectionCookieError { source: ConnectionCookieError }, - - /// Error returned when there was an error with the tracker core scrape handler. - #[error("Tracker core scrape error: {source}")] - TrackerCoreScrapeError { source: ScrapeError }, - - /// Error returned when there was an error with the tracker core whitelist. - #[error("Tracker core whitelist error: {source}")] - TrackerCoreWhitelistError { source: WhitelistError }, -} - -impl From<ConnectionCookieError> for UdpScrapeError { - fn from(connection_cookie_error: ConnectionCookieError) -> Self { - Self::ConnectionCookieError { - source: connection_cookie_error, - } - } -} - -impl From<ScrapeError> for UdpScrapeError { - fn from(scrape_error: ScrapeError) -> Self { - Self::TrackerCoreScrapeError { source: scrape_error } - } -} - -impl From<WhitelistError> for UdpScrapeError { - fn from(whitelist_error: WhitelistError) -> Self { - Self::TrackerCoreWhitelistError { source: whitelist_error } - } -} - /// The `ScrapeService` is responsible for handling the `scrape` requests. +/// +/// The service sends an statistics event that increments: +/// +/// - The number of UDP `scrape` requests handled by the UDP tracker. pub struct ScrapeService { scrape_handler: Arc<ScrapeHandler>, opt_udp_stats_event_sender: Arc<Option<Box<dyn statistics::event::sender::Sender>>>, } impl ScrapeService { - /// Creates a new `ScrapeService`. #[must_use] pub fn new( scrape_handler: Arc<ScrapeHandler>, @@ -82,32 +49,81 @@ impl ScrapeService { /// It will return an error if the tracker core scrape handler returns an error. pub async fn handle_scrape( &self, - remote_addr: SocketAddr, + remote_client_addr: SocketAddr, request: &ScrapeRequest, cookie_valid_range: Range<f64>, ) -> Result<ScrapeData, UdpScrapeError> { + Self::authenticate(remote_client_addr, request, cookie_valid_range)?; + + let scrape_data = self + .scrape_handler + .scrape(&Self::convert_from_aquatic(&request.info_hashes)) + .await?; + + self.send_stats_event(remote_client_addr).await; + + Ok(scrape_data) + } + + fn authenticate( + remote_addr: SocketAddr, + request: &ScrapeRequest, + cookie_valid_range: Range<f64>, + ) -> Result<f64, ConnectionCookieError> { check( &request.connection_id, gen_remote_fingerprint(&remote_addr), cookie_valid_range, - )?; - - // Convert from aquatic infohashes - let info_hashes: Vec<InfoHash> = request.info_hashes.iter().map(|&x| x.into()).collect(); + ) + } - let scrape_data = self.scrape_handler.scrape(&info_hashes).await?; + fn convert_from_aquatic(aquatic_infohashes: &[aquatic_udp_protocol::common::InfoHash]) -> Vec<InfoHash> { + aquatic_infohashes.iter().map(|&x| x.into()).collect() + } + async fn send_stats_event(&self, remote_addr: SocketAddr) { if let Some(udp_stats_event_sender) = self.opt_udp_stats_event_sender.as_deref() { - match remote_addr { - SocketAddr::V4(_) => { - udp_stats_event_sender.send_event(statistics::event::Event::Udp4Scrape).await; - } - SocketAddr::V6(_) => { - udp_stats_event_sender.send_event(statistics::event::Event::Udp6Scrape).await; - } - } + let event = match remote_addr { + SocketAddr::V4(_) => statistics::event::Event::Udp4Scrape, + SocketAddr::V6(_) => statistics::event::Event::Udp6Scrape, + }; + udp_stats_event_sender.send_event(event).await; } + } +} - Ok(scrape_data) +/// Errors related to scrape requests. +#[derive(thiserror::Error, Debug, Clone)] +pub enum UdpScrapeError { + /// Error returned when there was an error with the connection cookie. + #[error("Connection cookie error: {source}")] + ConnectionCookieError { source: ConnectionCookieError }, + + /// Error returned when there was an error with the tracker core scrape handler. + #[error("Tracker core scrape error: {source}")] + TrackerCoreScrapeError { source: ScrapeError }, + + /// Error returned when there was an error with the tracker core whitelist. + #[error("Tracker core whitelist error: {source}")] + TrackerCoreWhitelistError { source: WhitelistError }, +} + +impl From<ConnectionCookieError> for UdpScrapeError { + fn from(connection_cookie_error: ConnectionCookieError) -> Self { + Self::ConnectionCookieError { + source: connection_cookie_error, + } + } +} + +impl From<ScrapeError> for UdpScrapeError { + fn from(scrape_error: ScrapeError) -> Self { + Self::TrackerCoreScrapeError { source: scrape_error } + } +} + +impl From<WhitelistError> for UdpScrapeError { + fn from(whitelist_error: WhitelistError) -> Self { + Self::TrackerCoreWhitelistError { source: whitelist_error } } }