diff --git a/Cargo.lock b/Cargo.lock index e8150fb63..eea7dd623 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5282,6 +5282,7 @@ dependencies = [ name = "torrust-tracker-udp-core" version = "3.0.0-develop" dependencies = [ + "async-trait", "bloom", "blowfish", "cipher", @@ -5324,6 +5325,7 @@ dependencies = [ name = "torrust-tracker-udp-server" version = "3.0.0-develop" dependencies = [ + "async-trait", "derive_more 2.1.1", "futures", "futures-util", diff --git a/packages/configuration/src/v2_0_0/udp_tracker.rs b/packages/configuration/src/v2_0_0/udp_tracker.rs index 2021c7508..2a71aa539 100644 --- a/packages/configuration/src/v2_0_0/udp_tracker.rs +++ b/packages/configuration/src/v2_0_0/udp_tracker.rs @@ -32,6 +32,11 @@ pub struct UdpTracker { /// > be disabled; setting this to `false` is a no-op. #[serde(default = "UdpTracker::default_ipv6_v6only")] pub ipv6_v6only: bool, + + /// The maximum number of connection ID errors per IP before the client is + /// banned. Default is `10`. + #[serde(default = "UdpTracker::default_max_connection_id_errors_per_ip")] + pub max_connection_id_errors_per_ip: u32, } impl Default for UdpTracker { fn default() -> Self { @@ -40,6 +45,7 @@ impl Default for UdpTracker { cookie_lifetime: Self::default_cookie_lifetime(), tracker_usage_statistics: Self::default_tracker_usage_statistics(), ipv6_v6only: Self::default_ipv6_v6only(), + max_connection_id_errors_per_ip: Self::default_max_connection_id_errors_per_ip(), } } } @@ -60,4 +66,8 @@ impl UdpTracker { fn default_ipv6_v6only() -> bool { false } + + fn default_max_connection_id_errors_per_ip() -> u32 { + 10 + } } diff --git a/packages/rest-api-core/src/statistics/services.rs b/packages/rest-api-core/src/statistics/services.rs index c91a972f1..be648bb2d 100644 --- a/packages/rest-api-core/src/statistics/services.rs +++ b/packages/rest-api-core/src/statistics/services.rs @@ -201,7 +201,6 @@ mod tests { use torrust_tracker_http_core::statistics::repository::Repository; use torrust_tracker_swarm_coordination_registry::container::SwarmCoordinationRegistryContainer; use torrust_tracker_test_helpers::configuration; - use torrust_tracker_udp_core::MAX_CONNECTION_ID_ERRORS_PER_IP; use torrust_tracker_udp_core::services::banning::BanService; use crate::statistics::metrics::{ProtocolMetrics, TorrentsMetrics}; @@ -224,7 +223,7 @@ mod tests { let tracker_core_container = TrackerCoreContainer::initialize_from(&core_config, &swarm_coordination_registry_container.clone()).await; - let _ban_service = Arc::new(RwLock::new(BanService::new(MAX_CONNECTION_ID_ERRORS_PER_IP))); + let _ban_service = Arc::new(RwLock::new(BanService::new(10))); // HTTP core stats let http_core_broadcaster = Broadcaster::default(); diff --git a/packages/test-helpers/src/configuration.rs b/packages/test-helpers/src/configuration.rs index 960ef4a0b..5b24cbda5 100644 --- a/packages/test-helpers/src/configuration.rs +++ b/packages/test-helpers/src/configuration.rs @@ -57,6 +57,7 @@ pub fn ephemeral() -> Configuration { cookie_lifetime: Duration::from_secs(120), tracker_usage_statistics: true, ipv6_v6only: false, + max_connection_id_errors_per_ip: 10, }]); // Ephemeral socket address for HTTP tracker diff --git a/packages/udp-core/Cargo.toml b/packages/udp-core/Cargo.toml index a8758ddcf..724857bf9 100644 --- a/packages/udp-core/Cargo.toml +++ b/packages/udp-core/Cargo.toml @@ -36,6 +36,7 @@ torrust-tracker-primitives = { version = "3.0.0-develop", path = "../primitives" torrust-tracker-swarm-coordination-registry = { version = "3.0.0-develop", path = "../swarm-coordination-registry" } tracing = "0" zerocopy = "0.8" +async-trait = "0" [dev-dependencies] mockall = "0" diff --git a/packages/udp-core/src/container.rs b/packages/udp-core/src/container.rs index f1b4bda1c..3354dae22 100644 --- a/packages/udp-core/src/container.rs +++ b/packages/udp-core/src/container.rs @@ -12,7 +12,7 @@ use crate::services::banning::BanService; use crate::services::connect::ConnectService; use crate::services::scrape::ScrapeService; use crate::statistics::repository::Repository; -use crate::{MAX_CONNECTION_ID_ERRORS_PER_IP, event, services, statistics}; +use crate::{event, services, statistics}; pub struct UdpTrackerCoreContainer { pub udp_tracker_config: Arc, @@ -47,7 +47,9 @@ impl UdpTrackerCoreContainer { tracker_core_container: &Arc, udp_tracker_config: &Arc, ) -> Arc { - let udp_tracker_core_services = UdpTrackerCoreServices::initialize_from(tracker_core_container); + let max_connection_id_errors_per_ip = udp_tracker_config.max_connection_id_errors_per_ip; + let udp_tracker_core_services = + UdpTrackerCoreServices::initialize_from(tracker_core_container, max_connection_id_errors_per_ip); Self::initialize_from_services(tracker_core_container, &udp_tracker_core_services, udp_tracker_config) } @@ -87,7 +89,10 @@ pub struct UdpTrackerCoreServices { impl UdpTrackerCoreServices { #[must_use] - pub fn initialize_from(tracker_core_container: &Arc) -> Arc { + pub fn initialize_from( + tracker_core_container: &Arc, + max_connection_id_errors_per_ip: u32, + ) -> Arc { let udp_core_broadcaster = Broadcaster::default(); let udp_core_stats_repository = Arc::new(Repository::new()); let event_bus = Arc::new(EventBus::new( @@ -96,7 +101,7 @@ impl UdpTrackerCoreServices { )); let udp_core_stats_event_sender = event_bus.sender(); - let ban_service = Arc::new(RwLock::new(BanService::new(MAX_CONNECTION_ID_ERRORS_PER_IP))); + let ban_service = Arc::new(RwLock::new(BanService::new(max_connection_id_errors_per_ip))); let connect_service = Arc::new(ConnectService::new(udp_core_stats_event_sender.clone())); let announce_service = Arc::new(AnnounceService::new( tracker_core_container.announce_handler.clone(), diff --git a/packages/udp-core/src/lib.rs b/packages/udp-core/src/lib.rs index 01451edaa..cc32822ea 100644 --- a/packages/udp-core/src/lib.rs +++ b/packages/udp-core/src/lib.rs @@ -22,10 +22,6 @@ pub(crate) type CurrentClock = clock::Stopped; use crypto::ephemeral_instance_keys; use tracing::instrument; -/// The maximum number of connection id errors per ip. Clients will be banned if -/// they exceed this limit. -pub const MAX_CONNECTION_ID_ERRORS_PER_IP: u32 = 10; - pub const UDP_TRACKER_LOG_TARGET: &str = "UDP TRACKER"; /// It initializes the static values. diff --git a/packages/udp-core/src/services/banning.rs b/packages/udp-core/src/services/banning.rs index b83ee91fb..93b4dad4a 100644 --- a/packages/udp-core/src/services/banning.rs +++ b/packages/udp-core/src/services/banning.rs @@ -23,6 +23,12 @@ use tokio::time::Instant; use crate::UDP_TRACKER_LOG_TARGET; +/// Trait exposing only the banning statistics that external consumers need. +pub trait BanningStats: Send + Sync { + /// Returns the total number of banned IPs. + fn get_banned_ips_total(&self) -> usize; +} + pub struct BanService { max_connection_id_errors_per_ip: u32, fuzzy_error_counter: CountingBloomFilter, @@ -88,6 +94,12 @@ impl BanService { } } +impl BanningStats for BanService { + fn get_banned_ips_total(&self) -> usize { + self.accurate_error_counter.len() + } +} + #[cfg(test)] mod tests { use std::net::IpAddr; diff --git a/packages/udp-core/src/statistics/repository.rs b/packages/udp-core/src/statistics/repository.rs index 94af1371d..68bb3f0c5 100644 --- a/packages/udp-core/src/statistics/repository.rs +++ b/packages/udp-core/src/statistics/repository.rs @@ -4,11 +4,17 @@ use tokio::sync::{RwLock, RwLockReadGuard}; use torrust_clock::DurationSinceUnixEpoch; use torrust_metrics::label::LabelSet; use torrust_metrics::metric::MetricName; -use torrust_metrics::metric_collection::Error; +use torrust_metrics::metric_collection::{Error, MetricCollection}; use super::describe_metrics; use super::metrics::Metrics; +/// Trait exposing only the UDP core statistics that external consumers need. +#[async_trait::async_trait] +pub trait UdpCoreStatsRepository: Send + Sync { + async fn get_metrics_collection(&self) -> MetricCollection; +} + /// A repository for the tracker metrics. #[derive(Clone)] pub struct Repository { @@ -52,3 +58,10 @@ impl Repository { result } } + +#[async_trait::async_trait] +impl UdpCoreStatsRepository for Repository { + async fn get_metrics_collection(&self) -> MetricCollection { + self.stats.read().await.metric_collection.clone() + } +} diff --git a/packages/udp-server/Cargo.toml b/packages/udp-server/Cargo.toml index e239af5a2..74a16bd4a 100644 --- a/packages/udp-server/Cargo.toml +++ b/packages/udp-server/Cargo.toml @@ -38,6 +38,7 @@ torrust-tracker-primitives = { version = "3.0.0-develop", path = "../primitives" torrust-tracker-swarm-coordination-registry = { version = "3.0.0-develop", path = "../swarm-coordination-registry" } tracing = "0" url = { version = "2", features = [ "serde" ] } +async-trait = "0" uuid = { version = "1", features = [ "v4" ] } zerocopy = "0.8" socket2 = "0.6.4" diff --git a/packages/udp-server/examples/udp_only_public_tracker.rs b/packages/udp-server/examples/udp_only_public_tracker.rs index a2868cb84..6cd2f63ac 100644 --- a/packages/udp-server/examples/udp_only_public_tracker.rs +++ b/packages/udp-server/examples/udp_only_public_tracker.rs @@ -61,6 +61,7 @@ async fn main() { cookie_lifetime: Duration::from_secs(120), tracker_usage_statistics: false, ipv6_v6only: false, + max_connection_id_errors_per_ip: 10, }; println!("Types from torrust-tracker-configuration used by this binary:"); diff --git a/packages/udp-server/src/statistics/repository.rs b/packages/udp-server/src/statistics/repository.rs index 6bfacad20..cdc942e64 100644 --- a/packages/udp-server/src/statistics/repository.rs +++ b/packages/udp-server/src/statistics/repository.rs @@ -5,11 +5,17 @@ use tokio::sync::{RwLock, RwLockReadGuard}; use torrust_clock::DurationSinceUnixEpoch; use torrust_metrics::label::LabelSet; use torrust_metrics::metric::MetricName; -use torrust_metrics::metric_collection::Error; +use torrust_metrics::metric_collection::{Error, MetricCollection}; use super::describe_metrics; use super::metrics::Metrics; +/// Trait exposing only the UDP server statistics that external consumers need. +#[async_trait::async_trait] +pub trait UdpServerStatsRepository: Send + Sync { + async fn get_metrics_collection(&self) -> MetricCollection; +} + /// A repository for the tracker metrics. #[derive(Clone)] pub struct Repository { @@ -89,6 +95,13 @@ impl Repository { } } +#[async_trait::async_trait] +impl UdpServerStatsRepository for Repository { + async fn get_metrics_collection(&self) -> MetricCollection { + self.stats.read().await.metric_collection.clone() + } +} + #[cfg(test)] mod tests { use core::f64; diff --git a/src/container.rs b/src/container.rs index 5935e890e..fcfaafce3 100644 --- a/src/container.rs +++ b/src/container.rs @@ -81,7 +81,20 @@ impl AppContainer { // UDP - let udp_tracker_core_services = UdpTrackerCoreServices::initialize_from(&tracker_core_container); + use torrust_tracker_configuration::UdpTracker as UdpTrackerConfig; + + let default_max_connection_id_errors = UdpTrackerConfig::default().max_connection_id_errors_per_ip; + + let max_connection_id_errors = configuration + .udp_trackers + .as_ref() + .and_then(|trackers| trackers.first()) + .map_or(default_max_connection_id_errors, |config| { + config.max_connection_id_errors_per_ip + }); + + let udp_tracker_core_services = + UdpTrackerCoreServices::initialize_from(&tracker_core_container, max_connection_id_errors); let udp_tracker_server_container = UdpTrackerServerContainer::initialize(&core_config);