diff --git a/packages/configuration/src/lib.rs b/packages/configuration/src/lib.rs index a8f605289..4b81aed8b 100644 --- a/packages/configuration/src/lib.rs +++ b/packages/configuration/src/lib.rs @@ -229,7 +229,7 @@ //! [health_check_api] //! bind_address = "127.0.0.1:1313" //!``` -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::net::IpAddr; use std::str::FromStr; use std::sync::Arc; @@ -337,6 +337,8 @@ pub struct HttpTracker { pub ssl_key_path: Option, } +pub type AccessTokens = HashMap; + /// Configuration for the HTTP API. #[serde_as] #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] @@ -360,21 +362,13 @@ pub struct HttpApi { /// token and the value is the token itself. The token is used to /// authenticate the user. All tokens are valid for all endpoints and have /// the all permissions. - pub access_tokens: HashMap, + pub access_tokens: AccessTokens, } impl HttpApi { fn override_admin_token(&mut self, api_admin_token: &str) { self.access_tokens.insert("admin".to_string(), api_admin_token.to_string()); } - - /// Checks if the given token is one of the token in the configuration. - #[must_use] - pub fn contains_token(&self, token: &str) -> bool { - let tokens: HashMap = self.access_tokens.clone(); - let tokens: HashSet = tokens.into_values().collect(); - tokens.contains(token) - } } /// Configuration for the Health Check API. @@ -804,7 +798,7 @@ mod tests { fn http_api_configuration_should_check_if_it_contains_a_token() { let configuration = Configuration::default(); - assert!(configuration.http_api.contains_token("MyAccessToken")); - assert!(!configuration.http_api.contains_token("NonExistingToken")); + assert!(configuration.http_api.access_tokens.values().any(|t| t == "MyAccessToken")); + assert!(!configuration.http_api.access_tokens.values().any(|t| t == "NonExistingToken")); } } diff --git a/src/bootstrap/app.rs b/src/bootstrap/app.rs index 4a6f79a96..09b624566 100644 --- a/src/bootstrap/app.rs +++ b/src/bootstrap/app.rs @@ -24,8 +24,8 @@ use crate::shared::crypto::ephemeral_instance_keys; /// It loads the configuration from the environment and builds the main domain [`Tracker`] struct. #[must_use] -pub fn setup() -> (Arc, Arc) { - let configuration = Arc::new(initialize_configuration()); +pub fn setup() -> (Configuration, Arc) { + let configuration = initialize_configuration(); let tracker = initialize_with_configuration(&configuration); (configuration, tracker) @@ -35,7 +35,7 @@ pub fn setup() -> (Arc, Arc) { /// /// The configuration may be obtained from the environment (via config file or env vars). #[must_use] -pub fn initialize_with_configuration(configuration: &Arc) -> Arc { +pub fn initialize_with_configuration(configuration: &Configuration) -> Arc { initialize_static(); initialize_logging(configuration); Arc::new(initialize_tracker(configuration)) @@ -60,13 +60,13 @@ pub fn initialize_static() { /// The tracker is the domain layer service. It's the entrypoint to make requests to the domain layer. /// It's used by other higher-level components like the UDP and HTTP trackers or the tracker API. #[must_use] -pub fn initialize_tracker(config: &Arc) -> Tracker { - tracker_factory(config.clone()) +pub fn initialize_tracker(config: &Configuration) -> Tracker { + tracker_factory(config) } /// It initializes the log level, format and channel. /// /// See [the logging setup](crate::bootstrap::logging::setup) for more info about logging. -pub fn initialize_logging(config: &Arc) { +pub fn initialize_logging(config: &Configuration) { bootstrap::logging::setup(config); } diff --git a/src/bootstrap/jobs/torrent_cleanup.rs b/src/bootstrap/jobs/torrent_cleanup.rs index d3b084d31..6647e0249 100644 --- a/src/bootstrap/jobs/torrent_cleanup.rs +++ b/src/bootstrap/jobs/torrent_cleanup.rs @@ -25,7 +25,7 @@ use crate::core; /// /// Refer to [`torrust-tracker-configuration documentation`](https://docs.rs/torrust-tracker-configuration) for more info about that option. #[must_use] -pub fn start_job(config: &Arc, tracker: &Arc) -> JoinHandle<()> { +pub fn start_job(config: &Configuration, tracker: &Arc) -> JoinHandle<()> { let weak_tracker = std::sync::Arc::downgrade(tracker); let interval = config.inactive_peer_cleanup_interval; diff --git a/src/bootstrap/jobs/tracker_apis.rs b/src/bootstrap/jobs/tracker_apis.rs index e50a83651..43cb5de8e 100644 --- a/src/bootstrap/jobs/tracker_apis.rs +++ b/src/bootstrap/jobs/tracker_apis.rs @@ -26,7 +26,7 @@ use std::sync::Arc; use axum_server::tls_rustls::RustlsConfig; use log::info; use tokio::task::JoinHandle; -use torrust_tracker_configuration::HttpApi; +use torrust_tracker_configuration::{AccessTokens, HttpApi}; use super::make_rust_tls; use crate::core; @@ -64,8 +64,10 @@ pub async fn start_job(config: &HttpApi, tracker: Arc, version: V .await .map(|tls| tls.expect("it should have a valid tracker api tls configuration")); + let access_tokens = Arc::new(config.access_tokens.clone()); + match version { - Version::V1 => Some(start_v1(bind_to, tls, tracker.clone()).await), + Version::V1 => Some(start_v1(bind_to, tls, tracker.clone(), access_tokens).await), } } else { info!("Note: Not loading Http Tracker Service, Not Enabled in Configuration."); @@ -73,9 +75,14 @@ pub async fn start_job(config: &HttpApi, tracker: Arc, version: V } } -async fn start_v1(socket: SocketAddr, tls: Option, tracker: Arc) -> JoinHandle<()> { +async fn start_v1( + socket: SocketAddr, + tls: Option, + tracker: Arc, + access_tokens: Arc, +) -> JoinHandle<()> { let server = ApiServer::new(Launcher::new(socket, tls)) - .start(tracker) + .start(tracker, access_tokens) .await .expect("it should be able to start to the tracker api"); diff --git a/src/core/mod.rs b/src/core/mod.rs index fc44877c8..dac298462 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -447,6 +447,7 @@ use std::time::Duration; use derive_more::Constructor; use futures::future::join_all; +use log::debug; use tokio::sync::mpsc::error::SendError; use torrust_tracker_configuration::{AnnouncePolicy, Configuration}; use torrust_tracker_primitives::TrackerMode; @@ -472,17 +473,19 @@ pub const TORRENT_PEERS_LIMIT: usize = 74; /// Typically, the `Tracker` is used by a higher application service that handles /// the network layer. pub struct Tracker { - /// `Tracker` configuration. See [`torrust-tracker-configuration`](torrust_tracker_configuration) - pub config: Arc, + announce_policy: AnnouncePolicy, /// A database driver implementation: [`Sqlite3`](crate::core::databases::sqlite) /// or [`MySQL`](crate::core::databases::mysql) pub database: Arc>, mode: TrackerMode, + policy: TrackerPolicy, keys: tokio::sync::RwLock>, whitelist: tokio::sync::RwLock>, pub torrents: Arc, stats_event_sender: Option>, stats_repository: statistics::Repo, + external_ip: Option, + on_reverse_proxy: bool, } /// Structure that holds general `Tracker` torrents metrics. @@ -500,6 +503,12 @@ pub struct TorrentsMetrics { pub torrents: u64, } +#[derive(Copy, Clone, Debug, PartialEq, Default, Constructor)] +pub struct TrackerPolicy { + pub remove_peerless_torrents: bool, + pub max_peer_timeout: u32, + pub persistent_torrent_completed_stat: bool, +} /// Structure that holds the data returned by the `announce` request. #[derive(Clone, Debug, PartialEq, Constructor, Default)] pub struct AnnounceData { @@ -556,7 +565,7 @@ impl Tracker { /// /// Will return a `databases::error::Error` if unable to connect to database. The `Tracker` is responsible for the persistence. pub fn new( - config: Arc, + config: &Configuration, stats_event_sender: Option>, stats_repository: statistics::Repo, ) -> Result { @@ -565,7 +574,8 @@ impl Tracker { let mode = config.mode; Ok(Tracker { - config, + //config, + announce_policy: AnnouncePolicy::new(config.announce_interval, config.min_announce_interval), mode, keys: tokio::sync::RwLock::new(std::collections::HashMap::new()), whitelist: tokio::sync::RwLock::new(std::collections::HashSet::new()), @@ -573,6 +583,13 @@ impl Tracker { stats_event_sender, stats_repository, database, + external_ip: config.get_ext_ip(), + policy: TrackerPolicy::new( + config.remove_peerless_torrents, + config.max_peer_timeout, + config.persistent_torrent_completed_stat, + ), + on_reverse_proxy: config.on_reverse_proxy, }) } @@ -596,6 +613,19 @@ impl Tracker { self.is_private() } + /// Returns `true` is the tracker is in whitelisted mode. + pub fn is_behind_reverse_proxy(&self) -> bool { + self.on_reverse_proxy + } + + pub fn get_announce_policy(&self) -> AnnouncePolicy { + self.announce_policy + } + + pub fn get_maybe_external_ip(&self) -> Option { + self.external_ip + } + /// It handles an announce request. /// /// # Context: Tracker @@ -617,18 +647,19 @@ impl Tracker { // we are actually handling authentication at the handlers level. So I would extract that // responsibility into another authentication service. - peer.change_ip(&assign_ip_address_to_peer(remote_client_ip, self.config.get_ext_ip())); + debug!("Before: {peer:?}"); + peer.change_ip(&assign_ip_address_to_peer(remote_client_ip, self.external_ip)); + debug!("After: {peer:?}"); - let swarm_stats = self.update_torrent_with_peer_and_get_stats(info_hash, peer).await; + // we should update the torrent and get the stats before we get the peer list. + let stats = self.update_torrent_with_peer_and_get_stats(info_hash, peer).await; let peers = self.get_torrent_peers_for_peer(info_hash, peer).await; - let policy = AnnouncePolicy::new(self.config.announce_interval, self.config.min_announce_interval); - AnnounceData { peers, - stats: swarm_stats, - policy, + stats, + policy: self.get_announce_policy(), } } @@ -727,7 +758,7 @@ impl Tracker { let (stats, stats_updated) = self.torrents.update_torrent_with_peer_and_get_stats(info_hash, peer).await; - if self.config.persistent_torrent_completed_stat && stats_updated { + if self.policy.persistent_torrent_completed_stat && stats_updated { let completed = stats.downloaded; let info_hash = *info_hash; @@ -788,17 +819,17 @@ impl Tracker { let mut torrents_lock = self.torrents.get_torrents_mut().await; // If we don't need to remove torrents we will use the faster iter - if self.config.remove_peerless_torrents { + if self.policy.remove_peerless_torrents { let mut cleaned_torrents_map: BTreeMap = BTreeMap::new(); for (info_hash, torrent_entry) in &mut *torrents_lock { - torrent_entry.remove_inactive_peers(self.config.max_peer_timeout); + torrent_entry.remove_inactive_peers(self.policy.max_peer_timeout); if torrent_entry.peers.is_empty() { continue; } - if self.config.persistent_torrent_completed_stat && torrent_entry.completed == 0 { + if self.policy.persistent_torrent_completed_stat && torrent_entry.completed == 0 { continue; } @@ -808,7 +839,7 @@ impl Tracker { *torrents_lock = cleaned_torrents_map; } else { for torrent_entry in (*torrents_lock).values_mut() { - torrent_entry.remove_inactive_peers(self.config.max_peer_timeout); + torrent_entry.remove_inactive_peers(self.policy.max_peer_timeout); } } } @@ -1061,7 +1092,6 @@ mod tests { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::str::FromStr; - use std::sync::Arc; use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes}; use torrust_tracker_test_helpers::configuration; @@ -1073,21 +1103,21 @@ mod tests { use crate::shared::clock::DurationSinceUnixEpoch; fn public_tracker() -> Tracker { - tracker_factory(configuration::ephemeral_mode_public().into()) + tracker_factory(&configuration::ephemeral_mode_public()) } fn private_tracker() -> Tracker { - tracker_factory(configuration::ephemeral_mode_private().into()) + tracker_factory(&configuration::ephemeral_mode_private()) } fn whitelisted_tracker() -> Tracker { - tracker_factory(configuration::ephemeral_mode_whitelisted().into()) + tracker_factory(&configuration::ephemeral_mode_whitelisted()) } pub fn tracker_persisting_torrents_in_database() -> Tracker { let mut configuration = configuration::ephemeral(); configuration.persistent_torrent_completed_stat = true; - tracker_factory(Arc::new(configuration)) + tracker_factory(&configuration) } fn sample_info_hash() -> InfoHash { diff --git a/src/core/services/mod.rs b/src/core/services/mod.rs index f5868fc26..76c6a36f6 100644 --- a/src/core/services/mod.rs +++ b/src/core/services/mod.rs @@ -19,12 +19,12 @@ use crate::core::Tracker; /// /// Will panic if tracker cannot be instantiated. #[must_use] -pub fn tracker_factory(config: Arc) -> Tracker { +pub fn tracker_factory(config: &Configuration) -> Tracker { // Initialize statistics let (stats_event_sender, stats_repository) = statistics::setup::factory(config.tracker_usage_statistics); // Initialize Torrust tracker - match Tracker::new(config, stats_event_sender, stats_repository) { + match Tracker::new(&Arc::new(config), stats_event_sender, stats_repository) { Ok(tracker) => tracker, Err(error) => { panic!("{}", error) diff --git a/src/core/services/statistics/mod.rs b/src/core/services/statistics/mod.rs index f74df62e5..3578c53aa 100644 --- a/src/core/services/statistics/mod.rs +++ b/src/core/services/statistics/mod.rs @@ -92,13 +92,13 @@ mod tests { use crate::core::services::statistics::{get_metrics, TrackerMetrics}; use crate::core::services::tracker_factory; - pub fn tracker_configuration() -> Arc { - Arc::new(configuration::ephemeral()) + pub fn tracker_configuration() -> Configuration { + configuration::ephemeral() } #[tokio::test] async fn the_statistics_service_should_return_the_tracker_metrics() { - let tracker = Arc::new(tracker_factory(tracker_configuration())); + let tracker = Arc::new(tracker_factory(&tracker_configuration())); let tracker_metrics = get_metrics(tracker.clone()).await; diff --git a/src/core/services/torrent.rs b/src/core/services/torrent.rs index f88cf5b50..d1ab29a7f 100644 --- a/src/core/services/torrent.rs +++ b/src/core/services/torrent.rs @@ -168,13 +168,13 @@ mod tests { use crate::core::services::tracker_factory; use crate::shared::bit_torrent::info_hash::InfoHash; - pub fn tracker_configuration() -> Arc { - Arc::new(configuration::ephemeral()) + pub fn tracker_configuration() -> Configuration { + configuration::ephemeral() } #[tokio::test] async fn should_return_none_if_the_tracker_does_not_have_the_torrent() { - let tracker = Arc::new(tracker_factory(tracker_configuration())); + let tracker = Arc::new(tracker_factory(&tracker_configuration())); let torrent_info = get_torrent_info( tracker.clone(), @@ -187,7 +187,7 @@ mod tests { #[tokio::test] async fn should_return_the_torrent_info_if_the_tracker_has_the_torrent() { - let tracker = Arc::new(tracker_factory(tracker_configuration())); + let tracker = Arc::new(tracker_factory(&tracker_configuration())); let hash = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned(); let info_hash = InfoHash::from_str(&hash).unwrap(); @@ -223,13 +223,13 @@ mod tests { use crate::core::services::tracker_factory; use crate::shared::bit_torrent::info_hash::InfoHash; - pub fn tracker_configuration() -> Arc { - Arc::new(configuration::ephemeral()) + pub fn tracker_configuration() -> Configuration { + configuration::ephemeral() } #[tokio::test] async fn should_return_an_empty_result_if_the_tracker_does_not_have_any_torrent() { - let tracker = Arc::new(tracker_factory(tracker_configuration())); + let tracker = Arc::new(tracker_factory(&tracker_configuration())); let torrents = get_torrents(tracker.clone(), &Pagination::default()).await; @@ -238,7 +238,7 @@ mod tests { #[tokio::test] async fn should_return_a_summarized_info_for_all_torrents() { - let tracker = Arc::new(tracker_factory(tracker_configuration())); + let tracker = Arc::new(tracker_factory(&tracker_configuration())); let hash = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned(); let info_hash = InfoHash::from_str(&hash).unwrap(); @@ -262,7 +262,7 @@ mod tests { #[tokio::test] async fn should_allow_limiting_the_number_of_torrents_in_the_result() { - let tracker = Arc::new(tracker_factory(tracker_configuration())); + let tracker = Arc::new(tracker_factory(&tracker_configuration())); let hash1 = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned(); let info_hash1 = InfoHash::from_str(&hash1).unwrap(); @@ -286,7 +286,7 @@ mod tests { #[tokio::test] async fn should_allow_using_pagination_in_the_result() { - let tracker = Arc::new(tracker_factory(tracker_configuration())); + let tracker = Arc::new(tracker_factory(&tracker_configuration())); let hash1 = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned(); let info_hash1 = InfoHash::from_str(&hash1).unwrap(); @@ -319,7 +319,7 @@ mod tests { #[tokio::test] async fn should_return_torrents_ordered_by_info_hash() { - let tracker = Arc::new(tracker_factory(tracker_configuration())); + let tracker = Arc::new(tracker_factory(&tracker_configuration())); let hash1 = "9e0217d0fa71c87332cd8bf9dbeabcb2c2cf3c4d".to_owned(); let info_hash1 = InfoHash::from_str(&hash1).unwrap(); diff --git a/src/main.rs b/src/main.rs index 87c0fc367..5c65f8e07 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use torrust_tracker::{app, bootstrap}; async fn main() { let (config, tracker) = bootstrap::app::setup(); - let jobs = app::start(config.clone(), tracker.clone()).await; + let jobs = app::start(config.into(), tracker.clone()).await; // handle the signals tokio::select! { diff --git a/src/servers/apis/routes.rs b/src/servers/apis/routes.rs index fef412f91..227916335 100644 --- a/src/servers/apis/routes.rs +++ b/src/servers/apis/routes.rs @@ -9,26 +9,27 @@ use std::sync::Arc; use axum::routing::get; use axum::{middleware, Router}; +use torrust_tracker_configuration::AccessTokens; use tower_http::compression::CompressionLayer; use super::v1; use super::v1::context::health_check::handlers::health_check_handler; +use super::v1::middlewares::auth::State; use crate::core::Tracker; /// Add all API routes to the router. #[allow(clippy::needless_pass_by_value)] -pub fn router(tracker: Arc) -> Router { +pub fn router(tracker: Arc, access_tokens: Arc) -> Router { let router = Router::new(); let api_url_prefix = "/api"; let router = v1::routes::add(api_url_prefix, router, tracker.clone()); + let state = State { access_tokens }; + router - .layer(middleware::from_fn_with_state( - tracker.config.clone(), - v1::middlewares::auth::auth, - )) + .layer(middleware::from_fn_with_state(state, v1::middlewares::auth::auth)) .route(&format!("{api_url_prefix}/health_check"), get(health_check_handler)) .layer(CompressionLayer::new()) } diff --git a/src/servers/apis/server.rs b/src/servers/apis/server.rs index f4fdf8994..d26362f66 100644 --- a/src/servers/apis/server.rs +++ b/src/servers/apis/server.rs @@ -32,6 +32,7 @@ use derive_more::Constructor; use futures::future::BoxFuture; use log::{error, info}; use tokio::sync::oneshot::{Receiver, Sender}; +use torrust_tracker_configuration::AccessTokens; use super::routes::router; use crate::bootstrap::jobs::Started; @@ -91,14 +92,14 @@ impl ApiServer { /// # Panics /// /// It would panic if the bound socket address cannot be sent back to this starter. - pub async fn start(self, tracker: Arc) -> Result, Error> { + pub async fn start(self, tracker: Arc, access_tokens: Arc) -> Result, Error> { let (tx_start, rx_start) = tokio::sync::oneshot::channel::(); let (tx_halt, rx_halt) = tokio::sync::oneshot::channel::(); let launcher = self.state.launcher; let task = tokio::spawn(async move { - launcher.start(tracker, tx_start, rx_halt).await; + launcher.start(tracker, access_tokens, tx_start, rx_halt).await; launcher }); @@ -159,8 +160,14 @@ impl Launcher { /// /// Will panic if unable to bind to the socket, or unable to get the address of the bound socket. /// Will also panic if unable to send message regarding the bound socket address. - pub fn start(&self, tracker: Arc, tx_start: Sender, rx_halt: Receiver) -> BoxFuture<'static, ()> { - let router = router(tracker); + pub fn start( + &self, + tracker: Arc, + access_tokens: Arc, + tx_start: Sender, + rx_halt: Receiver, + ) -> BoxFuture<'static, ()> { + let router = router(tracker, access_tokens); let socket = std::net::TcpListener::bind(self.bind_to).expect("Could not bind tcp_listener to address."); let address = socket.local_addr().expect("Could not get local_addr from tcp_listener."); @@ -227,8 +234,13 @@ mod tests { .await .map(|tls| tls.expect("tls config failed")); + let access_tokens = Arc::new(config.access_tokens.clone()); + let stopped = ApiServer::new(Launcher::new(bind_to, tls)); - let started = stopped.start(tracker).await.expect("it should start the server"); + let started = stopped + .start(tracker, access_tokens) + .await + .expect("it should start the server"); let stopped = started.stop().await.expect("it should stop the server"); assert_eq!(stopped.state.launcher.bind_to, bind_to); diff --git a/src/servers/apis/v1/middlewares/auth.rs b/src/servers/apis/v1/middlewares/auth.rs index 7749b3b34..58219c7ca 100644 --- a/src/servers/apis/v1/middlewares/auth.rs +++ b/src/servers/apis/v1/middlewares/auth.rs @@ -23,12 +23,12 @@ //! identify the token. use std::sync::Arc; -use axum::extract::{Query, State}; +use axum::extract::{self}; use axum::http::Request; use axum::middleware::Next; use axum::response::{IntoResponse, Response}; use serde::Deserialize; -use torrust_tracker_configuration::{Configuration, HttpApi}; +use torrust_tracker_configuration::AccessTokens; use crate::servers::apis::v1::responses::unhandled_rejection_response; @@ -38,11 +38,16 @@ pub struct QueryParams { pub token: Option, } +#[derive(Clone, Debug)] +pub struct State { + pub access_tokens: Arc, +} + /// Middleware for authentication using a "token" GET param. /// The token must be one of the tokens in the tracker [HTTP API configuration](torrust_tracker_configuration::HttpApi). pub async fn auth( - State(config): State>, - Query(params): Query, + extract::State(state): extract::State, + extract::Query(params): extract::Query, request: Request, next: Next, ) -> Response { @@ -50,7 +55,7 @@ pub async fn auth( return AuthError::Unauthorized.into_response(); }; - if !authenticate(&token, &config.http_api) { + if !authenticate(&token, &state.access_tokens) { return AuthError::TokenNotValid.into_response(); } @@ -73,8 +78,8 @@ impl IntoResponse for AuthError { } } -fn authenticate(token: &str, http_api_config: &HttpApi) -> bool { - http_api_config.contains_token(token) +fn authenticate(token: &str, tokens: &AccessTokens) -> bool { + tokens.values().any(|t| t == token) } /// `500` error response returned when the token is missing. diff --git a/src/servers/http/v1/handlers/announce.rs b/src/servers/http/v1/handlers/announce.rs index cfe422e7f..be2085613 100644 --- a/src/servers/http/v1/handlers/announce.rs +++ b/src/servers/http/v1/handlers/announce.rs @@ -104,7 +104,7 @@ async fn handle_announce( Err(error) => return Err(responses::error::Error::from(error)), } - let peer_ip = match peer_ip_resolver::invoke(tracker.config.on_reverse_proxy, client_ip_sources) { + let peer_ip = match peer_ip_resolver::invoke(tracker.is_behind_reverse_proxy(), client_ip_sources) { Ok(peer_ip) => peer_ip, Err(error) => return Err(responses::error::Error::from(error)), }; @@ -166,19 +166,19 @@ mod tests { use crate::shared::bit_torrent::info_hash::InfoHash; fn private_tracker() -> Tracker { - tracker_factory(configuration::ephemeral_mode_private().into()) + tracker_factory(&configuration::ephemeral_mode_private()) } fn whitelisted_tracker() -> Tracker { - tracker_factory(configuration::ephemeral_mode_whitelisted().into()) + tracker_factory(&configuration::ephemeral_mode_whitelisted()) } fn tracker_on_reverse_proxy() -> Tracker { - tracker_factory(configuration::ephemeral_with_reverse_proxy().into()) + tracker_factory(&configuration::ephemeral_with_reverse_proxy()) } fn tracker_not_on_reverse_proxy() -> Tracker { - tracker_factory(configuration::ephemeral_without_reverse_proxy().into()) + tracker_factory(&configuration::ephemeral_without_reverse_proxy()) } fn sample_announce_request() -> Announce { diff --git a/src/servers/http/v1/handlers/scrape.rs b/src/servers/http/v1/handlers/scrape.rs index 298d47383..49b1aebc7 100644 --- a/src/servers/http/v1/handlers/scrape.rs +++ b/src/servers/http/v1/handlers/scrape.rs @@ -90,7 +90,7 @@ async fn handle_scrape( // Authorization for scrape requests is handled at the `Tracker` level // for each torrent. - let peer_ip = match peer_ip_resolver::invoke(tracker.config.on_reverse_proxy, client_ip_sources) { + let peer_ip = match peer_ip_resolver::invoke(tracker.is_behind_reverse_proxy(), client_ip_sources) { Ok(peer_ip) => peer_ip, Err(error) => return Err(responses::error::Error::from(error)), }; @@ -121,19 +121,19 @@ mod tests { use crate::shared::bit_torrent::info_hash::InfoHash; fn private_tracker() -> Tracker { - tracker_factory(configuration::ephemeral_mode_private().into()) + tracker_factory(&configuration::ephemeral_mode_private()) } fn whitelisted_tracker() -> Tracker { - tracker_factory(configuration::ephemeral_mode_whitelisted().into()) + tracker_factory(&configuration::ephemeral_mode_whitelisted()) } fn tracker_on_reverse_proxy() -> Tracker { - tracker_factory(configuration::ephemeral_with_reverse_proxy().into()) + tracker_factory(&configuration::ephemeral_with_reverse_proxy()) } fn tracker_not_on_reverse_proxy() -> Tracker { - tracker_factory(configuration::ephemeral_without_reverse_proxy().into()) + tracker_factory(&configuration::ephemeral_without_reverse_proxy()) } fn sample_scrape_request() -> Scrape { diff --git a/src/servers/http/v1/services/announce.rs b/src/servers/http/v1/services/announce.rs index 80dc1ca5b..b791defd7 100644 --- a/src/servers/http/v1/services/announce.rs +++ b/src/servers/http/v1/services/announce.rs @@ -56,7 +56,7 @@ mod tests { use crate::shared::clock::DurationSinceUnixEpoch; fn public_tracker() -> Tracker { - tracker_factory(configuration::ephemeral_mode_public().into()) + tracker_factory(&configuration::ephemeral_mode_public()) } fn sample_info_hash() -> InfoHash { @@ -94,7 +94,6 @@ mod tests { use std::sync::Arc; use mockall::predicate::eq; - use torrust_tracker_configuration::AnnouncePolicy; use torrust_tracker_test_helpers::configuration; use super::{sample_peer_using_ipv4, sample_peer_using_ipv6}; @@ -119,7 +118,7 @@ mod tests { complete: 1, incomplete: 0, }, - policy: AnnouncePolicy::default(), + policy: tracker.get_announce_policy(), }; assert_eq!(announce_data, expected_announce_data); @@ -135,14 +134,8 @@ mod tests { .returning(|_| Box::pin(future::ready(Some(Ok(()))))); let stats_event_sender = Box::new(stats_event_sender_mock); - let tracker = Arc::new( - Tracker::new( - Arc::new(configuration::ephemeral()), - Some(stats_event_sender), - statistics::Repo::new(), - ) - .unwrap(), - ); + let tracker = + Arc::new(Tracker::new(&configuration::ephemeral(), Some(stats_event_sender), statistics::Repo::new()).unwrap()); let mut peer = sample_peer_using_ipv4(); @@ -154,7 +147,7 @@ mod tests { configuration.external_ip = Some(IpAddr::V6(Ipv6Addr::new(0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969)).to_string()); - Tracker::new(Arc::new(configuration), Some(stats_event_sender), statistics::Repo::new()).unwrap() + Tracker::new(&configuration, Some(stats_event_sender), statistics::Repo::new()).unwrap() } fn peer_with_the_ipv4_loopback_ip() -> Peer { @@ -199,14 +192,8 @@ mod tests { .returning(|_| Box::pin(future::ready(Some(Ok(()))))); let stats_event_sender = Box::new(stats_event_sender_mock); - let tracker = Arc::new( - Tracker::new( - Arc::new(configuration::ephemeral()), - Some(stats_event_sender), - statistics::Repo::new(), - ) - .unwrap(), - ); + let tracker = + Arc::new(Tracker::new(&configuration::ephemeral(), Some(stats_event_sender), statistics::Repo::new()).unwrap()); let mut peer = sample_peer_using_ipv6(); diff --git a/src/servers/http/v1/services/scrape.rs b/src/servers/http/v1/services/scrape.rs index c2fa104de..82ca15dc8 100644 --- a/src/servers/http/v1/services/scrape.rs +++ b/src/servers/http/v1/services/scrape.rs @@ -69,7 +69,7 @@ mod tests { use crate::shared::clock::DurationSinceUnixEpoch; fn public_tracker() -> Tracker { - tracker_factory(configuration::ephemeral_mode_public().into()) + tracker_factory(&configuration::ephemeral_mode_public()) } fn sample_info_hashes() -> Vec { @@ -145,14 +145,8 @@ mod tests { .returning(|_| Box::pin(future::ready(Some(Ok(()))))); let stats_event_sender = Box::new(stats_event_sender_mock); - let tracker = Arc::new( - Tracker::new( - Arc::new(configuration::ephemeral()), - Some(stats_event_sender), - statistics::Repo::new(), - ) - .unwrap(), - ); + let tracker = + Arc::new(Tracker::new(&configuration::ephemeral(), Some(stats_event_sender), statistics::Repo::new()).unwrap()); let peer_ip = IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)); @@ -169,14 +163,8 @@ mod tests { .returning(|_| Box::pin(future::ready(Some(Ok(()))))); let stats_event_sender = Box::new(stats_event_sender_mock); - let tracker = Arc::new( - Tracker::new( - Arc::new(configuration::ephemeral()), - Some(stats_event_sender), - statistics::Repo::new(), - ) - .unwrap(), - ); + let tracker = + Arc::new(Tracker::new(&configuration::ephemeral(), Some(stats_event_sender), statistics::Repo::new()).unwrap()); let peer_ip = IpAddr::V6(Ipv6Addr::new(0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969)); @@ -228,14 +216,8 @@ mod tests { .returning(|_| Box::pin(future::ready(Some(Ok(()))))); let stats_event_sender = Box::new(stats_event_sender_mock); - let tracker = Arc::new( - Tracker::new( - Arc::new(configuration::ephemeral()), - Some(stats_event_sender), - statistics::Repo::new(), - ) - .unwrap(), - ); + let tracker = + Arc::new(Tracker::new(&configuration::ephemeral(), Some(stats_event_sender), statistics::Repo::new()).unwrap()); let peer_ip = IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)); @@ -252,14 +234,8 @@ mod tests { .returning(|_| Box::pin(future::ready(Some(Ok(()))))); let stats_event_sender = Box::new(stats_event_sender_mock); - let tracker = Arc::new( - Tracker::new( - Arc::new(configuration::ephemeral()), - Some(stats_event_sender), - statistics::Repo::new(), - ) - .unwrap(), - ); + let tracker = + Arc::new(Tracker::new(&configuration::ephemeral(), Some(stats_event_sender), statistics::Repo::new()).unwrap()); let peer_ip = IpAddr::V6(Ipv6Addr::new(0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969)); diff --git a/src/servers/udp/handlers.rs b/src/servers/udp/handlers.rs index 34ebaec89..b77cd3a42 100644 --- a/src/servers/udp/handlers.rs +++ b/src/servers/udp/handlers.rs @@ -151,7 +151,7 @@ pub async fn handle_announce( if remote_addr.is_ipv4() { let announce_response = AnnounceResponse { transaction_id: wrapped_announce_request.announce_request.transaction_id, - announce_interval: AnnounceInterval(i64::from(tracker.config.announce_interval) as i32), + announce_interval: AnnounceInterval(i64::from(tracker.get_announce_policy().interval) as i32), leechers: NumberOfPeers(i64::from(response.stats.incomplete) as i32), seeders: NumberOfPeers(i64::from(response.stats.complete) as i32), peers: response @@ -176,7 +176,7 @@ pub async fn handle_announce( } else { let announce_response = AnnounceResponse { transaction_id: wrapped_announce_request.announce_request.transaction_id, - announce_interval: AnnounceInterval(i64::from(tracker.config.announce_interval) as i32), + announce_interval: AnnounceInterval(i64::from(tracker.get_announce_policy().interval) as i32), leechers: NumberOfPeers(i64::from(response.stats.incomplete) as i32), seeders: NumberOfPeers(i64::from(response.stats.complete) as i32), peers: response @@ -282,8 +282,8 @@ mod tests { use crate::core::{peer, Tracker}; use crate::shared::clock::{Current, Time}; - fn tracker_configuration() -> Arc { - Arc::new(default_testing_tracker_configuration()) + fn tracker_configuration() -> Configuration { + default_testing_tracker_configuration() } fn default_testing_tracker_configuration() -> Configuration { @@ -291,18 +291,18 @@ mod tests { } fn public_tracker() -> Arc { - initialized_tracker(configuration::ephemeral_mode_public().into()) + initialized_tracker(&configuration::ephemeral_mode_public()) } fn private_tracker() -> Arc { - initialized_tracker(configuration::ephemeral_mode_private().into()) + initialized_tracker(&configuration::ephemeral_mode_private()) } fn whitelisted_tracker() -> Arc { - initialized_tracker(configuration::ephemeral_mode_whitelisted().into()) + initialized_tracker(&configuration::ephemeral_mode_whitelisted()) } - fn initialized_tracker(configuration: Arc) -> Arc { + fn initialized_tracker(configuration: &Configuration) -> Arc { tracker_factory(configuration).into() } @@ -452,8 +452,9 @@ mod tests { let client_socket_address = sample_ipv4_socket_address(); - let torrent_tracker = - Arc::new(core::Tracker::new(tracker_configuration(), Some(stats_event_sender), statistics::Repo::new()).unwrap()); + let torrent_tracker = Arc::new( + core::Tracker::new(&tracker_configuration(), Some(stats_event_sender), statistics::Repo::new()).unwrap(), + ); handle_connect(client_socket_address, &sample_connect_request(), &torrent_tracker) .await .unwrap(); @@ -469,8 +470,9 @@ mod tests { .returning(|_| Box::pin(future::ready(Some(Ok(()))))); let stats_event_sender = Box::new(stats_event_sender_mock); - let torrent_tracker = - Arc::new(core::Tracker::new(tracker_configuration(), Some(stats_event_sender), statistics::Repo::new()).unwrap()); + let torrent_tracker = Arc::new( + core::Tracker::new(&tracker_configuration(), Some(stats_event_sender), statistics::Repo::new()).unwrap(), + ); handle_connect(sample_ipv6_remote_addr(), &sample_connect_request(), &torrent_tracker) .await .unwrap(); @@ -710,7 +712,7 @@ mod tests { let stats_event_sender = Box::new(stats_event_sender_mock); let tracker = Arc::new( - core::Tracker::new(tracker_configuration(), Some(stats_event_sender), statistics::Repo::new()).unwrap(), + core::Tracker::new(&tracker_configuration(), Some(stats_event_sender), statistics::Repo::new()).unwrap(), ); handle_announce( @@ -756,12 +758,11 @@ mod tests { let peers = tracker.get_torrent_peers(&info_hash.0.into()).await; - let external_ip_in_tracker_configuration = - tracker.config.external_ip.clone().unwrap().parse::().unwrap(); + let external_ip_in_tracker_configuration = tracker.get_maybe_external_ip().unwrap(); let expected_peer = TorrentPeerBuilder::default() .with_peer_id(peer::Id(peer_id.0)) - .with_peer_addr(SocketAddr::new(IpAddr::V4(external_ip_in_tracker_configuration), client_port)) + .with_peer_addr(SocketAddr::new(external_ip_in_tracker_configuration, client_port)) .into(); assert_eq!(peers[0], expected_peer); @@ -938,7 +939,7 @@ mod tests { let stats_event_sender = Box::new(stats_event_sender_mock); let tracker = Arc::new( - core::Tracker::new(tracker_configuration(), Some(stats_event_sender), statistics::Repo::new()).unwrap(), + core::Tracker::new(&tracker_configuration(), Some(stats_event_sender), statistics::Repo::new()).unwrap(), ); let remote_addr = sample_ipv6_remote_addr(); @@ -968,7 +969,7 @@ mod tests { let configuration = Arc::new(TrackerConfigurationBuilder::default().with_external_ip("::126.0.0.1").into()); let (stats_event_sender, stats_repository) = Keeper::new_active_instance(); let tracker = - Arc::new(core::Tracker::new(configuration, Some(stats_event_sender), stats_repository).unwrap()); + Arc::new(core::Tracker::new(&configuration, Some(stats_event_sender), stats_repository).unwrap()); let loopback_ipv4 = Ipv4Addr::new(127, 0, 0, 1); let loopback_ipv6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); @@ -994,8 +995,9 @@ mod tests { let peers = tracker.get_torrent_peers(&info_hash.0.into()).await; - let _external_ip_in_tracker_configuration = - tracker.config.external_ip.clone().unwrap().parse::().unwrap(); + let external_ip_in_tracker_configuration = tracker.get_maybe_external_ip().unwrap(); + + assert!(external_ip_in_tracker_configuration.is_ipv6()); // There's a special type of IPv6 addresses that provide compatibility with IPv4. // The last 32 bits of these addresses represent an IPv4, and are represented like this: @@ -1246,7 +1248,7 @@ mod tests { let remote_addr = sample_ipv4_remote_addr(); let tracker = Arc::new( - core::Tracker::new(tracker_configuration(), Some(stats_event_sender), statistics::Repo::new()).unwrap(), + core::Tracker::new(&tracker_configuration(), Some(stats_event_sender), statistics::Repo::new()).unwrap(), ); handle_scrape(remote_addr, &sample_scrape_request(&remote_addr), &tracker) @@ -1278,7 +1280,7 @@ mod tests { let remote_addr = sample_ipv6_remote_addr(); let tracker = Arc::new( - core::Tracker::new(tracker_configuration(), Some(stats_event_sender), statistics::Repo::new()).unwrap(), + core::Tracker::new(&tracker_configuration(), Some(stats_event_sender), statistics::Repo::new()).unwrap(), ); handle_scrape(remote_addr, &sample_scrape_request(&remote_addr), &tracker) diff --git a/tests/servers/api/test_environment.rs b/tests/servers/api/test_environment.rs index 166bfd7d1..c6878c674 100644 --- a/tests/servers/api/test_environment.rs +++ b/tests/servers/api/test_environment.rs @@ -1,13 +1,12 @@ -use std::net::SocketAddr; use std::sync::Arc; -use axum_server::tls_rustls::RustlsConfig; use futures::executor::block_on; use torrust_tracker::bootstrap::jobs::make_rust_tls; use torrust_tracker::core::peer::Peer; use torrust_tracker::core::Tracker; use torrust_tracker::servers::apis::server::{ApiServer, Launcher, RunningApiServer, StoppedApiServer}; use torrust_tracker::shared::bit_torrent::info_hash::InfoHash; +use torrust_tracker_configuration::HttpApi; use super::connection_info::ConnectionInfo; use crate::common::app::setup_with_configuration; @@ -18,7 +17,7 @@ pub type StoppedTestEnvironment = TestEnvironment; pub type RunningTestEnvironment = TestEnvironment; pub struct TestEnvironment { - pub cfg: Arc, + pub config: Arc, pub tracker: Arc, pub state: S, } @@ -41,9 +40,10 @@ impl TestEnvironment { impl TestEnvironment { pub fn new(cfg: torrust_tracker_configuration::Configuration) -> Self { - let tracker = setup_with_configuration(&Arc::new(cfg)); + let cfg = Arc::new(cfg); + let tracker = setup_with_configuration(&cfg); - let config = tracker.config.http_api.clone(); + let config = Arc::new(cfg.http_api.clone()); let bind_to = config .bind_address @@ -53,25 +53,23 @@ impl TestEnvironment { let tls = block_on(make_rust_tls(config.ssl_enabled, &config.ssl_cert_path, &config.ssl_key_path)) .map(|tls| tls.expect("tls config failed")); - Self::new_stopped(tracker, bind_to, tls) - } - - pub fn new_stopped(tracker: Arc, bind_to: SocketAddr, tls: Option) -> Self { let api_server = api_server(Launcher::new(bind_to, tls)); Self { - cfg: tracker.config.clone(), + config, tracker, state: Stopped { api_server }, } } pub async fn start(self) -> TestEnvironment { + let access_tokens = Arc::new(self.config.access_tokens.clone()); + TestEnvironment { - cfg: self.cfg, + config: self.config, tracker: self.tracker.clone(), state: Running { - api_server: self.state.api_server.start(self.tracker).await.unwrap(), + api_server: self.state.api_server.start(self.tracker, access_tokens).await.unwrap(), }, } } @@ -90,7 +88,7 @@ impl TestEnvironment { pub async fn stop(self) -> TestEnvironment { TestEnvironment { - cfg: self.cfg, + config: self.config, tracker: self.tracker, state: Stopped { api_server: self.state.api_server.stop().await.unwrap(), @@ -101,7 +99,7 @@ impl TestEnvironment { pub fn get_connection_info(&self) -> ConnectionInfo { ConnectionInfo { bind_address: self.state.api_server.state.binding.to_string(), - api_token: self.cfg.http_api.access_tokens.get("admin").cloned(), + api_token: self.config.access_tokens.get("admin").cloned(), } } } diff --git a/tests/servers/http/v1/contract.rs b/tests/servers/http/v1/contract.rs index f3d1fcef0..e394779ad 100644 --- a/tests/servers/http/v1/contract.rs +++ b/tests/servers/http/v1/contract.rs @@ -387,13 +387,15 @@ mod for_all_config_modes { ) .await; + let announce_policy = test_env.tracker.get_announce_policy(); + assert_announce_response( response, &Announce { complete: 1, // the peer for this test incomplete: 0, - interval: test_env.tracker.config.announce_interval, - min_interval: test_env.tracker.config.min_announce_interval, + interval: announce_policy.interval, + min_interval: announce_policy.interval_min, peers: vec![], }, ) @@ -426,14 +428,16 @@ mod for_all_config_modes { ) .await; + let announce_policy = test_env.tracker.get_announce_policy(); + // It should only contain the previously announced peer assert_announce_response( response, &Announce { complete: 2, incomplete: 0, - interval: test_env.tracker.config.announce_interval, - min_interval: test_env.tracker.config.min_announce_interval, + interval: announce_policy.interval, + min_interval: announce_policy.interval_min, peers: vec![DictionaryPeer::from(previously_announced_peer)], }, ) @@ -475,6 +479,8 @@ mod for_all_config_modes { ) .await; + let announce_policy = test_env.tracker.get_announce_policy(); + // The newly announced peer is not included on the response peer list, // but all the previously announced peers should be included regardless the IP version they are using. assert_announce_response( @@ -482,8 +488,8 @@ mod for_all_config_modes { &Announce { complete: 3, incomplete: 0, - interval: test_env.tracker.config.announce_interval, - min_interval: test_env.tracker.config.min_announce_interval, + interval: announce_policy.interval, + min_interval: announce_policy.interval_min, peers: vec![DictionaryPeer::from(peer_using_ipv4), DictionaryPeer::from(peer_using_ipv6)], }, ) @@ -787,7 +793,7 @@ mod for_all_config_modes { let peers = test_env.tracker.get_torrent_peers(&info_hash).await; let peer_addr = peers[0].peer_addr; - assert_eq!(peer_addr.ip(), test_env.tracker.config.get_ext_ip().unwrap()); + assert_eq!(peer_addr.ip(), test_env.tracker.get_maybe_external_ip().unwrap()); assert_ne!(peer_addr.ip(), IpAddr::from_str("2.2.2.2").unwrap()); test_env.stop().await; @@ -826,7 +832,7 @@ mod for_all_config_modes { let peers = test_env.tracker.get_torrent_peers(&info_hash).await; let peer_addr = peers[0].peer_addr; - assert_eq!(peer_addr.ip(), test_env.tracker.config.get_ext_ip().unwrap()); + assert_eq!(peer_addr.ip(), test_env.tracker.get_maybe_external_ip().unwrap()); assert_ne!(peer_addr.ip(), IpAddr::from_str("2.2.2.2").unwrap()); test_env.stop().await;