diff --git a/.github/workflows/deployment.yaml b/.github/workflows/deployment.yaml index 41b40fea..328bd91b 100644 --- a/.github/workflows/deployment.yaml +++ b/.github/workflows/deployment.yaml @@ -58,6 +58,7 @@ jobs: cargo publish -p bittorrent-http-protocol cargo publish -p bittorrent-tracker-client cargo publish -p bittorrent-tracker-core + cargo publish -p bittorrent-udp-protocol cargo publish -p torrust-tracker cargo publish -p torrust-tracker-api-client cargo publish -p torrust-tracker-client diff --git a/Cargo.lock b/Cargo.lock index 06cde245..07c08ab0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -634,6 +634,15 @@ dependencies = [ "url", ] +[[package]] +name = "bittorrent-udp-protocol" +version = "3.0.0-develop" +dependencies = [ + "aquatic_udp_protocol", + "torrust-tracker-clock", + "torrust-tracker-primitives", +] + [[package]] name = "bitvec" version = "1.0.1" @@ -4301,6 +4310,7 @@ dependencies = [ "bittorrent-primitives", "bittorrent-tracker-client", "bittorrent-tracker-core", + "bittorrent-udp-protocol", "bloom", "blowfish", "camino", diff --git a/Cargo.toml b/Cargo.toml index 6c9f7f22..7337b49a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ bittorrent-http-protocol = { version = "3.0.0-develop", path = "packages/http-pr bittorrent-primitives = "0.1.0" bittorrent-tracker-client = { version = "3.0.0-develop", path = "packages/tracker-client" } bittorrent-tracker-core = { version = "3.0.0-develop", path = "packages/tracker-core" } +bittorrent-udp-protocol = { version = "3.0.0-develop", path = "packages/udp-protocol" } bloom = "0.3.2" blowfish = "0" camino = { version = "1", features = ["serde", "serde1"] } diff --git a/packages/http-protocol/src/v1/requests/announce.rs b/packages/http-protocol/src/v1/requests/announce.rs index f293b9cf..66f7a122 100644 --- a/packages/http-protocol/src/v1/requests/announce.rs +++ b/packages/http-protocol/src/v1/requests/announce.rs @@ -183,6 +183,17 @@ impl fmt::Display for Event { } } +impl From for Event { + fn from(value: aquatic_udp_protocol::request::AnnounceEvent) -> Self { + match value { + AnnounceEvent::Started => Self::Started, + AnnounceEvent::Stopped => Self::Stopped, + AnnounceEvent::Completed => Self::Completed, + AnnounceEvent::None => panic!("can't convert announce event from aquatic for None variant"), + } + } +} + /// Whether the `announce` response should be in compact mode or not. /// /// Depending on the value of this param, the tracker will return a different diff --git a/packages/http-protocol/src/v1/responses/error.rs b/packages/http-protocol/src/v1/responses/error.rs index 8a6b4cf5..2bd8cd95 100644 --- a/packages/http-protocol/src/v1/responses/error.rs +++ b/packages/http-protocol/src/v1/responses/error.rs @@ -55,10 +55,26 @@ impl From for Error { } } +impl From for Error { + fn from(err: bittorrent_tracker_core::error::AnnounceError) -> Self { + Error { + failure_reason: format!("Tracker announce error: {err}"), + } + } +} + +impl From for Error { + fn from(err: bittorrent_tracker_core::error::ScrapeError) -> Self { + Error { + failure_reason: format!("Tracker scrape error: {err}"), + } + } +} + impl From for Error { fn from(err: bittorrent_tracker_core::error::WhitelistError) -> Self { Error { - failure_reason: format!("Tracker error: {err}"), + failure_reason: format!("Tracker whitelist error: {err}"), } } } diff --git a/packages/tracker-core/src/announce_handler.rs b/packages/tracker-core/src/announce_handler.rs index 6707f191..cd207385 100644 --- a/packages/tracker-core/src/announce_handler.rs +++ b/packages/tracker-core/src/announce_handler.rs @@ -101,12 +101,17 @@ use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; use super::torrent::repository::in_memory::InMemoryTorrentRepository; use super::torrent::repository::persisted::DatabasePersistentTorrentRepository; +use crate::error::AnnounceError; +use crate::whitelist::authorization::WhitelistAuthorization; /// Handles `announce` requests from `BitTorrent` clients. pub struct AnnounceHandler { /// The tracker configuration. config: Core, + /// Service for authorizing access to whitelisted torrents. + whitelist_authorization: Arc, + /// Repository for in-memory torrent data. in_memory_torrent_repository: Arc, @@ -119,10 +124,12 @@ impl AnnounceHandler { #[must_use] pub fn new( config: &Core, + whitelist_authorization: &Arc, in_memory_torrent_repository: &Arc, db_torrent_repository: &Arc, ) -> Self { Self { + whitelist_authorization: whitelist_authorization.clone(), config: config.clone(), in_memory_torrent_repository: in_memory_torrent_repository.clone(), db_torrent_repository: db_torrent_repository.clone(), @@ -143,27 +150,23 @@ impl AnnounceHandler { /// # Returns /// /// An `AnnounceData` struct containing the list of peers, swarm statistics, and tracker policy. - pub fn announce( + /// + /// # Errors + /// + /// Returns an error if the tracker is running in `listed` mode and the + /// torrent is not whitelisted. + pub async fn announce( &self, info_hash: &InfoHash, peer: &mut peer::Peer, remote_client_ip: &IpAddr, peers_wanted: &PeersWanted, - ) -> AnnounceData { + ) -> Result { // code-review: maybe instead of mutating the peer we could just return // a tuple with the new peer and the announce data: (Peer, AnnounceData). // It could even be a different struct: `StoredPeer` or `PublicPeer`. - // code-review: in the `scrape` function we perform an authorization check. - // We check if the torrent is whitelisted. Should we also check authorization here? - // I think so because the `Tracker` has the responsibility for checking authentication and authorization. - // The `Tracker` has delegated that responsibility to the handlers - // (because we want to return a friendly error response) but that does not mean we should - // double-check authorization at this domain level too. - // I would propose to return a `Result` here. - // Besides, regarding authentication the `Tracker` is also responsible for authentication but - // we are actually handling authentication at the handlers level. So I would extract that - // responsibility into another authentication service. + self.whitelist_authorization.authorize(info_hash).await?; tracing::debug!("Before: {peer:?}"); peer.change_ip(&assign_ip_address_to_peer(remote_client_ip, self.config.net.external_ip)); @@ -175,11 +178,11 @@ impl AnnounceHandler { .in_memory_torrent_repository .get_peers_for(info_hash, peer, peers_wanted.limit()); - AnnounceData { + Ok(AnnounceData { peers, stats, policy: self.config.announce_policy, - } + }) } /// Updates the torrent data in memory, persists statistics if needed, and @@ -461,8 +464,10 @@ mod tests { let mut peer = sample_peer(); - let announce_data = - announce_handler.announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::AsManyAsPossible); + let announce_data = announce_handler + .announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::AsManyAsPossible) + .await + .unwrap(); assert_eq!(announce_data.peers, vec![]); } @@ -472,16 +477,21 @@ mod tests { let (announce_handler, _scrape_handler) = public_tracker(); let mut previously_announced_peer = sample_peer_1(); - announce_handler.announce( - &sample_info_hash(), - &mut previously_announced_peer, - &peer_ip(), - &PeersWanted::AsManyAsPossible, - ); + announce_handler + .announce( + &sample_info_hash(), + &mut previously_announced_peer, + &peer_ip(), + &PeersWanted::AsManyAsPossible, + ) + .await + .unwrap(); let mut peer = sample_peer_2(); - let announce_data = - announce_handler.announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::AsManyAsPossible); + let announce_data = announce_handler + .announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::AsManyAsPossible) + .await + .unwrap(); assert_eq!(announce_data.peers, vec![Arc::new(previously_announced_peer)]); } @@ -491,24 +501,32 @@ mod tests { let (announce_handler, _scrape_handler) = public_tracker(); let mut previously_announced_peer_1 = sample_peer_1(); - announce_handler.announce( - &sample_info_hash(), - &mut previously_announced_peer_1, - &peer_ip(), - &PeersWanted::AsManyAsPossible, - ); + announce_handler + .announce( + &sample_info_hash(), + &mut previously_announced_peer_1, + &peer_ip(), + &PeersWanted::AsManyAsPossible, + ) + .await + .unwrap(); let mut previously_announced_peer_2 = sample_peer_2(); - announce_handler.announce( - &sample_info_hash(), - &mut previously_announced_peer_2, - &peer_ip(), - &PeersWanted::AsManyAsPossible, - ); + announce_handler + .announce( + &sample_info_hash(), + &mut previously_announced_peer_2, + &peer_ip(), + &PeersWanted::AsManyAsPossible, + ) + .await + .unwrap(); let mut peer = sample_peer_3(); - let announce_data = - announce_handler.announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::only(1)); + let announce_data = announce_handler + .announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::only(1)) + .await + .unwrap(); // It should return only one peer. There is no guarantee on // which peer will be returned. @@ -530,8 +548,10 @@ mod tests { let mut peer = seeder(); - let announce_data = - announce_handler.announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::AsManyAsPossible); + let announce_data = announce_handler + .announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::AsManyAsPossible) + .await + .unwrap(); assert_eq!(announce_data.stats.complete, 1); } @@ -542,8 +562,10 @@ mod tests { let mut peer = leecher(); - let announce_data = - announce_handler.announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::AsManyAsPossible); + let announce_data = announce_handler + .announce(&sample_info_hash(), &mut peer, &peer_ip(), &PeersWanted::AsManyAsPossible) + .await + .unwrap(); assert_eq!(announce_data.stats.incomplete, 1); } @@ -554,20 +576,26 @@ mod tests { // We have to announce with "started" event because peer does not count if peer was not previously known let mut started_peer = started_peer(); - announce_handler.announce( - &sample_info_hash(), - &mut started_peer, - &peer_ip(), - &PeersWanted::AsManyAsPossible, - ); + announce_handler + .announce( + &sample_info_hash(), + &mut started_peer, + &peer_ip(), + &PeersWanted::AsManyAsPossible, + ) + .await + .unwrap(); let mut completed_peer = completed_peer(); - let announce_data = announce_handler.announce( - &sample_info_hash(), - &mut completed_peer, - &peer_ip(), - &PeersWanted::AsManyAsPossible, - ); + let announce_data = announce_handler + .announce( + &sample_info_hash(), + &mut completed_peer, + &peer_ip(), + &PeersWanted::AsManyAsPossible, + ) + .await + .unwrap(); assert_eq!(announce_data.stats.downloaded, 1); } @@ -590,10 +618,12 @@ mod tests { use crate::torrent::manager::TorrentsManager; use crate::torrent::repository::in_memory::InMemoryTorrentRepository; use crate::torrent::repository::persisted::DatabasePersistentTorrentRepository; + use crate::whitelist::authorization::WhitelistAuthorization; + use crate::whitelist::repository::in_memory::InMemoryWhitelist; #[tokio::test] async fn it_should_persist_the_number_of_completed_peers_for_all_torrents_into_the_database() { - let mut config = configuration::ephemeral_listed(); + let mut config = configuration::ephemeral_public(); config.core.tracker_policy.persistent_torrent_completed_stat = true; @@ -605,8 +635,11 @@ mod tests { &in_memory_torrent_repository, &db_torrent_repository, )); + let in_memory_whitelist = Arc::new(InMemoryWhitelist::default()); + let whitelist_authorization = Arc::new(WhitelistAuthorization::new(&config.core, &in_memory_whitelist.clone())); let announce_handler = Arc::new(AnnounceHandler::new( &config.core, + &whitelist_authorization, &in_memory_torrent_repository, &db_torrent_repository, )); @@ -616,11 +649,17 @@ mod tests { let mut peer = sample_peer(); peer.event = AnnounceEvent::Started; - let announce_data = announce_handler.announce(&info_hash, &mut peer, &peer_ip(), &PeersWanted::AsManyAsPossible); + let announce_data = announce_handler + .announce(&info_hash, &mut peer, &peer_ip(), &PeersWanted::AsManyAsPossible) + .await + .unwrap(); assert_eq!(announce_data.stats.downloaded, 0); peer.event = AnnounceEvent::Completed; - let announce_data = announce_handler.announce(&info_hash, &mut peer, &peer_ip(), &PeersWanted::AsManyAsPossible); + let announce_data = announce_handler + .announce(&info_hash, &mut peer, &peer_ip(), &PeersWanted::AsManyAsPossible) + .await + .unwrap(); assert_eq!(announce_data.stats.downloaded, 1); // Remove the newly updated torrent from memory diff --git a/packages/tracker-core/src/error.rs b/packages/tracker-core/src/error.rs index 99ac48ed..fed076ff 100644 --- a/packages/tracker-core/src/error.rs +++ b/packages/tracker-core/src/error.rs @@ -15,6 +15,22 @@ use torrust_tracker_located_error::LocatedError; use super::authentication::key::ParseKeyError; use super::databases; +/// Errors related to announce requests. +#[derive(thiserror::Error, Debug, Clone)] +pub enum AnnounceError { + /// Wraps errors related to torrent whitelisting. + #[error("Whitelist error: {0}")] + Whitelist(#[from] WhitelistError), +} + +/// Errors related to scrape requests. +#[derive(thiserror::Error, Debug, Clone)] +pub enum ScrapeError { + /// Wraps errors related to torrent whitelisting. + #[error("Whitelist error: {0}")] + Whitelist(#[from] WhitelistError), +} + /// Errors related to torrent whitelisting. /// /// This error is returned when an operation involves a torrent that is not diff --git a/packages/tracker-core/src/lib.rs b/packages/tracker-core/src/lib.rs index 843817de..8e73fe02 100644 --- a/packages/tracker-core/src/lib.rs +++ b/packages/tracker-core/src/lib.rs @@ -144,8 +144,6 @@ pub(crate) type CurrentClock = clock::Stopped; #[cfg(test)] mod tests { mod the_tracker { - use std::net::{IpAddr, Ipv4Addr}; - use std::str::FromStr; use std::sync::Arc; use torrust_tracker_test_helpers::configuration; @@ -164,11 +162,6 @@ mod tests { initialize_handlers(&config) } - // The client peer IP - fn peer_ip() -> IpAddr { - IpAddr::V4(Ipv4Addr::from_str("126.0.0.1").unwrap()) - } - mod for_all_config_modes { mod handling_a_scrape_request { @@ -191,24 +184,30 @@ mod tests { // Announce a "complete" peer for the torrent let mut complete_peer = complete_peer(); - announce_handler.announce( - &info_hash, - &mut complete_peer, - &IpAddr::V4(Ipv4Addr::new(126, 0, 0, 10)), - &PeersWanted::AsManyAsPossible, - ); + announce_handler + .announce( + &info_hash, + &mut complete_peer, + &IpAddr::V4(Ipv4Addr::new(126, 0, 0, 10)), + &PeersWanted::AsManyAsPossible, + ) + .await + .unwrap(); // Announce an "incomplete" peer for the torrent let mut incomplete_peer = incomplete_peer(); - announce_handler.announce( - &info_hash, - &mut incomplete_peer, - &IpAddr::V4(Ipv4Addr::new(126, 0, 0, 11)), - &PeersWanted::AsManyAsPossible, - ); + announce_handler + .announce( + &info_hash, + &mut incomplete_peer, + &IpAddr::V4(Ipv4Addr::new(126, 0, 0, 11)), + &PeersWanted::AsManyAsPossible, + ) + .await + .unwrap(); // Scrape - let scrape_data = scrape_handler.scrape(&vec![info_hash]).await; + let scrape_data = scrape_handler.scrape(&vec![info_hash]).await.unwrap(); // The expected swarm metadata for the file let mut expected_scrape_data = ScrapeData::empty(); @@ -234,28 +233,19 @@ mod tests { use torrust_tracker_primitives::core::ScrapeData; use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; - use crate::announce_handler::PeersWanted; - use crate::test_helpers::tests::{complete_peer, incomplete_peer}; - use crate::tests::the_tracker::{initialize_handlers_for_listed_tracker, peer_ip}; + use crate::tests::the_tracker::initialize_handlers_for_listed_tracker; #[tokio::test] async fn it_should_return_the_zeroed_swarm_metadata_for_the_requested_file_if_it_is_not_whitelisted() { - let (announce_handler, scrape_handler) = initialize_handlers_for_listed_tracker(); - - let info_hash = "3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0".parse::().unwrap(); // DevSkim: ignore DS173237 - - let mut peer = incomplete_peer(); - announce_handler.announce(&info_hash, &mut peer, &peer_ip(), &PeersWanted::AsManyAsPossible); + let (_announce_handler, scrape_handler) = initialize_handlers_for_listed_tracker(); - // Announce twice to force non zeroed swarm metadata - let mut peer = complete_peer(); - announce_handler.announce(&info_hash, &mut peer, &peer_ip(), &PeersWanted::AsManyAsPossible); + let non_whitelisted_info_hash = "3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0".parse::().unwrap(); // DevSkim: ignore DS173237 - let scrape_data = scrape_handler.scrape(&vec![info_hash]).await; + let scrape_data = scrape_handler.scrape(&vec![non_whitelisted_info_hash]).await.unwrap(); // The expected zeroed swarm metadata for the file let mut expected_scrape_data = ScrapeData::empty(); - expected_scrape_data.add_file(&info_hash, SwarmMetadata::zeroed()); + expected_scrape_data.add_file(&non_whitelisted_info_hash, SwarmMetadata::zeroed()); assert_eq!(scrape_data, expected_scrape_data); } diff --git a/packages/tracker-core/src/scrape_handler.rs b/packages/tracker-core/src/scrape_handler.rs index 1e75580a..93b25dea 100644 --- a/packages/tracker-core/src/scrape_handler.rs +++ b/packages/tracker-core/src/scrape_handler.rs @@ -67,6 +67,7 @@ use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; use super::torrent::repository::in_memory::InMemoryTorrentRepository; use super::whitelist; +use crate::error::ScrapeError; /// Handles scrape requests, providing torrent swarm metadata. pub struct ScrapeHandler { @@ -95,10 +96,18 @@ impl ScrapeHandler { /// - Returns metadata for each requested torrent. /// - If a torrent isn't whitelisted or doesn't exist, returns zeroed stats. /// + /// # Errors + /// + /// It does not return any errors for the time being. The error is returned + /// to avoid breaking changes in the future if we decide to return errors. + /// For example, a new tracker configuration option could be added to return + /// an error if a torrent is not whitelisted instead of returning zeroed + /// stats. + /// /// # BEP Reference: /// /// [BEP 48: Scrape Protocol](https://www.bittorrent.org/beps/bep_0048.html) - pub async fn scrape(&self, info_hashes: &Vec) -> ScrapeData { + pub async fn scrape(&self, info_hashes: &Vec) -> Result { let mut scrape_data = ScrapeData::empty(); for info_hash in info_hashes { @@ -109,7 +118,7 @@ impl ScrapeHandler { scrape_data.add_file(info_hash, swarm_metadata); } - scrape_data + Ok(scrape_data) } } @@ -145,7 +154,7 @@ mod tests { let info_hashes = vec!["3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0".parse::().unwrap()]; // DevSkim: ignore DS173237 - let scrape_data = scrape_handler.scrape(&info_hashes).await; + let scrape_data = scrape_handler.scrape(&info_hashes).await.unwrap(); let mut expected_scrape_data = ScrapeData::empty(); @@ -163,7 +172,7 @@ mod tests { "99c82bb73505a3c0b453f9fa0e881d6e5a32a0c1".parse::().unwrap(), // DevSkim: ignore DS173237 ]; - let scrape_data = scrape_handler.scrape(&info_hashes).await; + let scrape_data = scrape_handler.scrape(&info_hashes).await.unwrap(); let mut expected_scrape_data = ScrapeData::empty(); expected_scrape_data.add_file_with_zeroed_metadata(&info_hashes[0]); diff --git a/packages/tracker-core/src/test_helpers.rs b/packages/tracker-core/src/test_helpers.rs index 06f5ce38..79904dec 100644 --- a/packages/tracker-core/src/test_helpers.rs +++ b/packages/tracker-core/src/test_helpers.rs @@ -177,6 +177,7 @@ pub(crate) mod tests { let announce_handler = Arc::new(AnnounceHandler::new( &config.core, + &whitelist_authorization, &in_memory_torrent_repository, &db_torrent_repository, )); diff --git a/packages/tracker-core/tests/integration.rs b/packages/tracker-core/tests/integration.rs index 4dbd60b9..5aaded10 100644 --- a/packages/tracker-core/tests/integration.rs +++ b/packages/tracker-core/tests/integration.rs @@ -76,6 +76,7 @@ impl Container { )); let announce_handler = Arc::new(AnnounceHandler::new( config, + &whitelist_authorization, &in_memory_torrent_repository, &db_torrent_repository, )); @@ -102,10 +103,11 @@ async fn test_announce_and_scrape_requests() { // First announce: download started peer.event = AnnounceEvent::Started; - let announce_data = - container - .announce_handler - .announce(&info_hash, &mut peer, &remote_client_ip(), &PeersWanted::AsManyAsPossible); + let announce_data = container + .announce_handler + .announce(&info_hash, &mut peer, &remote_client_ip(), &PeersWanted::AsManyAsPossible) + .await + .unwrap(); // NOTICE: you don't get back the peer making the request. assert_eq!(announce_data.peers.len(), 0); @@ -113,17 +115,18 @@ async fn test_announce_and_scrape_requests() { // Second announce: download completed peer.event = AnnounceEvent::Completed; - let announce_data = - container - .announce_handler - .announce(&info_hash, &mut peer, &remote_client_ip(), &PeersWanted::AsManyAsPossible); + let announce_data = container + .announce_handler + .announce(&info_hash, &mut peer, &remote_client_ip(), &PeersWanted::AsManyAsPossible) + .await + .unwrap(); assert_eq!(announce_data.peers.len(), 0); assert_eq!(announce_data.stats.downloaded, 1); // Scrape - let scrape_data = container.scrape_handler.scrape(&vec![info_hash]).await; + let scrape_data = container.scrape_handler.scrape(&vec![info_hash]).await.unwrap(); assert!(scrape_data.files.contains_key(&info_hash)); } diff --git a/packages/udp-protocol/Cargo.toml b/packages/udp-protocol/Cargo.toml new file mode 100644 index 00000000..8f0f9fe9 --- /dev/null +++ b/packages/udp-protocol/Cargo.toml @@ -0,0 +1,20 @@ +[package] +description = "A library with the primitive types and functions for the BitTorrent UDP tracker protocol." +keywords = ["bittorrent", "library", "primitives", "udp"] +name = "bittorrent-udp-protocol" +readme = "README.md" + +authors.workspace = true +documentation.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +publish.workspace = true +repository.workspace = true +rust-version.workspace = true +version.workspace = true + +[dependencies] +aquatic_udp_protocol = "0" +torrust-tracker-clock = { version = "3.0.0-develop", path = "../clock" } +torrust-tracker-primitives = { version = "3.0.0-develop", path = "../primitives" } diff --git a/packages/udp-protocol/LICENSE b/packages/udp-protocol/LICENSE new file mode 100644 index 00000000..0ad25db4 --- /dev/null +++ b/packages/udp-protocol/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/packages/udp-protocol/README.md b/packages/udp-protocol/README.md new file mode 100644 index 00000000..4f63fb67 --- /dev/null +++ b/packages/udp-protocol/README.md @@ -0,0 +1,11 @@ +# BitTorrent UDP Tracker Protocol + +A library with the primitive types and functions used by BitTorrent UDP trackers. + +## Documentation + +[Crate documentation](https://docs.rs/bittorrent-udp-protocol). + +## License + +The project is licensed under the terms of the [GNU AFFERO GENERAL PUBLIC LICENSE](./LICENSE). diff --git a/packages/udp-protocol/src/lib.rs b/packages/udp-protocol/src/lib.rs new file mode 100644 index 00000000..f0983a7b --- /dev/null +++ b/packages/udp-protocol/src/lib.rs @@ -0,0 +1,15 @@ +//! Primitive types and functions for `BitTorrent` UDP trackers. +pub mod peer_builder; + +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; diff --git a/src/packages/udp_tracker_core/peer_builder.rs b/packages/udp-protocol/src/peer_builder.rs similarity index 100% rename from src/packages/udp_tracker_core/peer_builder.rs rename to packages/udp-protocol/src/peer_builder.rs diff --git a/src/bootstrap/app.rs b/src/bootstrap/app.rs index e0d77ab8..41023f2f 100644 --- a/src/bootstrap/app.rs +++ b/src/bootstrap/app.rs @@ -129,6 +129,7 @@ pub fn initialize_app_container(configuration: &Configuration) -> AppContainer { let announce_handler = Arc::new(AnnounceHandler::new( &configuration.core, + &whitelist_authorization, &in_memory_torrent_repository, &db_torrent_repository, )); diff --git a/src/packages/http_tracker_core/services/announce.rs b/src/packages/http_tracker_core/services/announce.rs index 049d0d22..2bc421f1 100644 --- a/src/packages/http_tracker_core/services/announce.rs +++ b/src/packages/http_tracker_core/services/announce.rs @@ -13,13 +13,11 @@ 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; use crate::packages::http_tracker_core; @@ -68,29 +66,12 @@ pub async fn handle_announce( 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, - opt_http_stats_event_sender: Arc>>, - 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); + let announce_data = announce_handler + .announce(&announce_request.info_hash, &mut peer, &original_peer_ip, &peers_wanted) + .await?; if let Some(http_stats_event_sender) = opt_http_stats_event_sender.as_deref() { match original_peer_ip { @@ -107,7 +88,7 @@ pub async fn invoke( } } - announce_data + Ok(announce_data) } #[cfg(test)] @@ -116,17 +97,26 @@ mod tests { use std::sync::Arc; use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId}; + use bittorrent_http_protocol::v1::requests::announce::Announce; + use bittorrent_http_protocol::v1::services::peer_ip_resolver::ClientIpSources; use bittorrent_tracker_core::announce_handler::AnnounceHandler; + use bittorrent_tracker_core::authentication::key::repository::in_memory::InMemoryKeyRepository; + use bittorrent_tracker_core::authentication::service::AuthenticationService; use bittorrent_tracker_core::databases::setup::initialize_database; use bittorrent_tracker_core::torrent::repository::in_memory::InMemoryTorrentRepository; use bittorrent_tracker_core::torrent::repository::persisted::DatabasePersistentTorrentRepository; - use torrust_tracker_configuration::Core; + use bittorrent_tracker_core::whitelist::authorization::WhitelistAuthorization; + use bittorrent_tracker_core::whitelist::repository::in_memory::InMemoryWhitelist; + use torrust_tracker_configuration::{Configuration, Core}; + use torrust_tracker_primitives::peer::Peer; use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch}; use torrust_tracker_test_helpers::configuration; struct CoreTrackerServices { pub core_config: Arc, pub announce_handler: Arc, + pub authentication_service: Arc, + pub whitelist_authorization: Arc, } struct CoreHttpTrackerServices { @@ -134,15 +124,22 @@ mod tests { } fn initialize_core_tracker_services() -> (CoreTrackerServices, CoreHttpTrackerServices) { - let config = configuration::ephemeral_public(); + initialize_core_tracker_services_with_config(&configuration::ephemeral_public()) + } + fn initialize_core_tracker_services_with_config(config: &Configuration) -> (CoreTrackerServices, CoreHttpTrackerServices) { let core_config = Arc::new(config.core.clone()); let database = initialize_database(&config.core); let in_memory_torrent_repository = Arc::new(InMemoryTorrentRepository::default()); let db_torrent_repository = Arc::new(DatabasePersistentTorrentRepository::new(&database)); + let in_memory_whitelist = Arc::new(InMemoryWhitelist::default()); + let whitelist_authorization = Arc::new(WhitelistAuthorization::new(&config.core, &in_memory_whitelist.clone())); + let in_memory_key_repository = Arc::new(InMemoryKeyRepository::default()); + let authentication_service = Arc::new(AuthenticationService::new(&core_config, &in_memory_key_repository)); let announce_handler = Arc::new(AnnounceHandler::new( &config.core, + &whitelist_authorization, &in_memory_torrent_repository, &db_torrent_repository, )); @@ -157,6 +154,8 @@ mod tests { CoreTrackerServices { core_config, announce_handler, + authentication_service, + whitelist_authorization, }, CoreHttpTrackerServices { http_stats_event_sender }, ) @@ -187,11 +186,33 @@ mod tests { } } + fn sample_announce_request_for_peer(peer: Peer) -> (Announce, ClientIpSources) { + let announce_request = Announce { + info_hash: sample_info_hash(), + peer_id: peer.peer_id, + port: peer.peer_addr.port(), + uploaded: Some(peer.uploaded), + downloaded: Some(peer.downloaded), + left: Some(peer.left), + event: Some(peer.event.into()), + compact: None, + numwant: None, + }; + + let client_ip_sources = ClientIpSources { + right_most_x_forwarded_for: None, + connection_info_ip: Some(peer.peer_addr.ip()), + }; + + (announce_request, client_ip_sources) + } + use futures::future::BoxFuture; use mockall::mock; use tokio::sync::mpsc::error::SendError; use crate::packages::http_tracker_core; + use crate::servers::http::test_helpers::tests::sample_info_hash; mock! { HttpStatsEventSender {} @@ -205,11 +226,8 @@ mod tests { use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::sync::Arc; - use bittorrent_tracker_core::announce_handler::{AnnounceHandler, PeersWanted}; - use bittorrent_tracker_core::databases::setup::initialize_database; - use bittorrent_tracker_core::torrent::repository::in_memory::InMemoryTorrentRepository; - use bittorrent_tracker_core::torrent::repository::persisted::DatabasePersistentTorrentRepository; use mockall::predicate::eq; + use torrust_tracker_configuration::Configuration; use torrust_tracker_primitives::core::AnnounceData; use torrust_tracker_primitives::peer; use torrust_tracker_primitives::swarm_metadata::SwarmMetadata; @@ -217,40 +235,31 @@ mod tests { use super::{sample_peer_using_ipv4, sample_peer_using_ipv6}; use crate::packages::http_tracker_core; - use crate::packages::http_tracker_core::services::announce::invoke; + use crate::packages::http_tracker_core::services::announce::handle_announce; use crate::packages::http_tracker_core::services::announce::tests::{ - initialize_core_tracker_services, sample_peer, MockHttpStatsEventSender, + initialize_core_tracker_services, initialize_core_tracker_services_with_config, sample_announce_request_for_peer, + sample_peer, MockHttpStatsEventSender, }; - use crate::servers::http::test_helpers::tests::sample_info_hash; - - fn initialize_announce_handler() -> Arc { - let config = configuration::ephemeral(); - - let database = initialize_database(&config.core); - let in_memory_torrent_repository = Arc::new(InMemoryTorrentRepository::default()); - let db_torrent_repository = Arc::new(DatabasePersistentTorrentRepository::new(&database)); - - Arc::new(AnnounceHandler::new( - &config.core, - &in_memory_torrent_repository, - &db_torrent_repository, - )) - } #[tokio::test] async fn it_should_return_the_announce_data() { let (core_tracker_services, core_http_tracker_services) = initialize_core_tracker_services(); - let mut peer = sample_peer(); + let peer = sample_peer(); + + let (announce_request, client_ip_sources) = sample_announce_request_for_peer(peer); - let announce_data = invoke( - core_tracker_services.announce_handler.clone(), - core_http_tracker_services.http_stats_event_sender.clone(), - sample_info_hash(), - &mut peer, - &PeersWanted::AsManyAsPossible, + let announce_data = handle_announce( + &core_tracker_services.core_config, + &core_tracker_services.announce_handler, + &core_tracker_services.authentication_service, + &core_tracker_services.whitelist_authorization, + &core_http_tracker_services.http_stats_event_sender, + &announce_request, + &client_ip_sources, ) - .await; + .await + .unwrap(); let expected_announce_data = AnnounceData { peers: vec![], @@ -276,27 +285,32 @@ mod tests { let http_stats_event_sender: Arc>> = Arc::new(Some(Box::new(http_stats_event_sender_mock))); - let announce_handler = initialize_announce_handler(); + let (core_tracker_services, mut core_http_tracker_services) = initialize_core_tracker_services(); + core_http_tracker_services.http_stats_event_sender = http_stats_event_sender; - let mut peer = sample_peer_using_ipv4(); + let peer = sample_peer_using_ipv4(); - let _announce_data = invoke( - announce_handler, - http_stats_event_sender, - sample_info_hash(), - &mut peer, - &PeersWanted::AsManyAsPossible, + let (announce_request, client_ip_sources) = sample_announce_request_for_peer(peer); + + let _announce_data = handle_announce( + &core_tracker_services.core_config, + &core_tracker_services.announce_handler, + &core_tracker_services.authentication_service, + &core_tracker_services.whitelist_authorization, + &core_http_tracker_services.http_stats_event_sender, + &announce_request, + &client_ip_sources, ) - .await; + .await + .unwrap(); } - fn tracker_with_an_ipv6_external_ip() -> Arc { + fn tracker_with_an_ipv6_external_ip() -> Configuration { let mut configuration = configuration::ephemeral(); configuration.core.net.external_ip = Some(IpAddr::V6(Ipv6Addr::new( 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, ))); - - initialize_announce_handler() + configuration } fn peer_with_the_ipv4_loopback_ip() -> peer::Peer { @@ -321,18 +335,25 @@ mod tests { let http_stats_event_sender: Arc>> = Arc::new(Some(Box::new(http_stats_event_sender_mock))); - let mut peer = peer_with_the_ipv4_loopback_ip(); + let (core_tracker_services, mut core_http_tracker_services) = + initialize_core_tracker_services_with_config(&tracker_with_an_ipv6_external_ip()); + core_http_tracker_services.http_stats_event_sender = http_stats_event_sender; - let announce_handler = tracker_with_an_ipv6_external_ip(); + let peer = peer_with_the_ipv4_loopback_ip(); - let _announce_data = invoke( - announce_handler, - http_stats_event_sender, - sample_info_hash(), - &mut peer, - &PeersWanted::AsManyAsPossible, + let (announce_request, client_ip_sources) = sample_announce_request_for_peer(peer); + + let _announce_data = handle_announce( + &core_tracker_services.core_config, + &core_tracker_services.announce_handler, + &core_tracker_services.authentication_service, + &core_tracker_services.whitelist_authorization, + &core_http_tracker_services.http_stats_event_sender, + &announce_request, + &client_ip_sources, ) - .await; + .await + .unwrap(); } #[tokio::test] @@ -347,18 +368,24 @@ mod tests { let http_stats_event_sender: Arc>> = Arc::new(Some(Box::new(http_stats_event_sender_mock))); - let announce_handler = initialize_announce_handler(); + let (core_tracker_services, mut core_http_tracker_services) = initialize_core_tracker_services(); + core_http_tracker_services.http_stats_event_sender = http_stats_event_sender; - let mut peer = sample_peer_using_ipv6(); + let peer = sample_peer_using_ipv6(); - let _announce_data = invoke( - announce_handler, - http_stats_event_sender, - sample_info_hash(), - &mut peer, - &PeersWanted::AsManyAsPossible, + let (announce_request, client_ip_sources) = sample_announce_request_for_peer(peer); + + let _announce_data = handle_announce( + &core_tracker_services.core_config, + &core_tracker_services.announce_handler, + &core_tracker_services.authentication_service, + &core_tracker_services.whitelist_authorization, + &core_http_tracker_services.http_stats_event_sender, + &announce_request, + &client_ip_sources, ) - .await; + .await + .unwrap(); } } } diff --git a/src/packages/http_tracker_core/services/scrape.rs b/src/packages/http_tracker_core/services/scrape.rs index 62f5fdf6..667ce8d0 100644 --- a/src/packages/http_tracker_core/services/scrape.rs +++ b/src/packages/http_tracker_core/services/scrape.rs @@ -14,7 +14,6 @@ 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; @@ -41,13 +40,12 @@ use crate::packages::http_tracker_core; pub async fn handle_scrape( core_config: &Arc, scrape_handler: &Arc, - _authentication_service: &Arc, opt_http_stats_event_sender: &Arc>>, scrape_request: &Scrape, client_ip_sources: &ClientIpSources, - return_real_scrape_data: bool, + return_fake_scrape_data: bool, ) -> Result { - // Authorization for scrape requests is handled at the `http_tracker_core` + // Authorization for scrape requests is handled at the `bittorrent-_racker_core` // level for each torrent. let peer_ip = match peer_ip_resolver::invoke(core_config.net.on_reverse_proxy, client_ip_sources) { @@ -55,30 +53,17 @@ pub async fn handle_scrape( 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) + if return_fake_scrape_data { + return 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, - opt_http_stats_event_sender: &Arc>>, - info_hashes: &Vec, - original_peer_ip: &IpAddr, -) -> ScrapeData { - let scrape_data = scrape_handler.scrape(info_hashes).await; + let scrape_data = scrape_handler.scrape(&scrape_request.info_hashes).await?; - send_scrape_event(original_peer_ip, opt_http_stats_event_sender).await; + send_scrape_event(&peer_ip, opt_http_stats_event_sender).await; - scrape_data + Ok(scrape_data) } /// The HTTP tracker fake `scrape` service. It returns zeroed stats. @@ -135,6 +120,7 @@ mod tests { use futures::future::BoxFuture; use mockall::mock; use tokio::sync::mpsc::error::SendError; + use torrust_tracker_configuration::Configuration; use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch}; use torrust_tracker_test_helpers::configuration; @@ -142,8 +128,12 @@ mod tests { use crate::servers::http::test_helpers::tests::sample_info_hash; fn initialize_announce_and_scrape_handlers_for_public_tracker() -> (Arc, Arc) { - let config = configuration::ephemeral_public(); + initialize_announce_and_scrape_handlers_with_configuration(&configuration::ephemeral_public()) + } + fn initialize_announce_and_scrape_handlers_with_configuration( + config: &Configuration, + ) -> (Arc, Arc) { let database = initialize_database(&config.core); let in_memory_whitelist = Arc::new(InMemoryWhitelist::default()); let whitelist_authorization = Arc::new(WhitelistAuthorization::new(&config.core, &in_memory_whitelist.clone())); @@ -151,6 +141,7 @@ mod tests { let db_torrent_repository = Arc::new(DatabasePersistentTorrentRepository::new(&database)); let announce_handler = Arc::new(AnnounceHandler::new( &config.core, + &whitelist_authorization, &in_memory_torrent_repository, &db_torrent_repository, )); @@ -175,9 +166,7 @@ mod tests { } } - fn initialize_scrape_handler() -> Arc { - let config = configuration::ephemeral(); - + fn initialize_scrape_handler_with_config(config: &Configuration) -> Arc { let in_memory_whitelist = Arc::new(InMemoryWhitelist::default()); let whitelist_authorization = Arc::new(WhitelistAuthorization::new(&config.core, &in_memory_whitelist.clone())); let in_memory_torrent_repository = Arc::new(InMemoryTorrentRepository::default()); @@ -198,36 +187,63 @@ mod tests { use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::sync::Arc; + use bittorrent_http_protocol::v1::requests::scrape::Scrape; + use bittorrent_http_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_primitives::swarm_metadata::SwarmMetadata; + use torrust_tracker_test_helpers::configuration; - use crate::packages::http_tracker_core::services::scrape::invoke; + use crate::packages::http_tracker_core::services::scrape::handle_scrape; 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, + initialize_announce_and_scrape_handlers_with_configuration, initialize_scrape_handler_with_config, + 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() { + let configuration = configuration::ephemeral_public(); + let core_config = Arc::new(configuration.core.clone()); + let (http_stats_event_sender, _http_stats_repository) = packages::http_tracker_core::statistics::setup::factory(false); let http_stats_event_sender = Arc::new(http_stats_event_sender); - let (announce_handler, scrape_handler) = initialize_announce_and_scrape_handlers_for_public_tracker(); + let (announce_handler, scrape_handler) = initialize_announce_and_scrape_handlers_with_configuration(&configuration); 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(); - announce_handler.announce(&info_hash, &mut peer, &original_peer_ip, &PeersWanted::AsManyAsPossible); - - let scrape_data = invoke(&scrape_handler, &http_stats_event_sender, &info_hashes, &original_peer_ip).await; + announce_handler + .announce(&info_hash, &mut peer, &original_peer_ip, &PeersWanted::AsManyAsPossible) + .await + .unwrap(); + + let scrape_request = Scrape { + info_hashes: info_hashes.clone(), + }; + + let client_ip_sources = ClientIpSources { + right_most_x_forwarded_for: None, + connection_info_ip: Some(original_peer_ip), + }; + + let scrape_data = handle_scrape( + &core_config, + &scrape_handler, + &http_stats_event_sender, + &scrape_request, + &client_ip_sources, + false, + ) + .await + .unwrap(); let mut expected_scrape_data = ScrapeData::empty(); expected_scrape_data.add_file( @@ -244,6 +260,8 @@ mod tests { #[tokio::test] async fn it_should_send_the_tcp_4_scrape_event_when_the_peer_uses_ipv4() { + let config = configuration::ephemeral(); + let mut http_stats_event_sender_mock = MockHttpStatsEventSender::new(); http_stats_event_sender_mock .expect_send_event() @@ -253,15 +271,35 @@ mod tests { let http_stats_event_sender: Arc>> = Arc::new(Some(Box::new(http_stats_event_sender_mock))); - let scrape_handler = initialize_scrape_handler(); + let scrape_handler = initialize_scrape_handler_with_config(&config); let peer_ip = IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)); - invoke(&scrape_handler, &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), + }; + + handle_scrape( + &Arc::new(config.core), + &scrape_handler, + &http_stats_event_sender, + &scrape_request, + &client_ip_sources, + false, + ) + .await + .unwrap(); } #[tokio::test] async fn it_should_send_the_tcp_6_scrape_event_when_the_peer_uses_ipv6() { + let config = configuration::ephemeral(); + let mut http_stats_event_sender_mock = MockHttpStatsEventSender::new(); http_stats_event_sender_mock .expect_send_event() @@ -271,11 +309,29 @@ mod tests { let http_stats_event_sender: Arc>> = Arc::new(Some(Box::new(http_stats_event_sender_mock))); - let scrape_handler = initialize_scrape_handler(); + let scrape_handler = initialize_scrape_handler_with_config(&config); let peer_ip = IpAddr::V6(Ipv6Addr::new(0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969)); - invoke(&scrape_handler, &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), + }; + + handle_scrape( + &Arc::new(config.core), + &scrape_handler, + &http_stats_event_sender, + &scrape_request, + &client_ip_sources, + false, + ) + .await + .unwrap(); } } @@ -310,7 +366,10 @@ mod tests { // Announce a new peer to force scrape data to contain not zeroed data let mut peer = sample_peer(); let original_peer_ip = peer.ip(); - announce_handler.announce(&info_hash, &mut peer, &original_peer_ip, &PeersWanted::AsManyAsPossible); + announce_handler + .announce(&info_hash, &mut peer, &original_peer_ip, &PeersWanted::AsManyAsPossible) + .await + .unwrap(); let scrape_data = fake(&http_stats_event_sender, &info_hashes, &original_peer_ip).await; diff --git a/src/packages/udp_tracker_core/mod.rs b/src/packages/udp_tracker_core/mod.rs index 3ab1d83d..4f3e5485 100644 --- a/src/packages/udp_tracker_core/mod.rs +++ b/src/packages/udp_tracker_core/mod.rs @@ -1,3 +1,2 @@ -pub mod peer_builder; pub mod services; pub mod statistics; diff --git a/src/packages/udp_tracker_core/services/announce.rs b/src/packages/udp_tracker_core/services/announce.rs index dec506ae..5851cdc9 100644 --- a/src/packages/udp_tracker_core/services/announce.rs +++ b/src/packages/udp_tracker_core/services/announce.rs @@ -13,12 +13,13 @@ 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::error::AnnounceError; use bittorrent_tracker_core::whitelist; +use bittorrent_udp_protocol::peer_builder; use torrust_tracker_primitives::core::AnnounceData; use torrust_tracker_primitives::peer; -use crate::packages::udp_tracker_core::{self, peer_builder}; +use crate::packages::udp_tracker_core::{self}; /// It handles the `Announce` request. /// @@ -35,7 +36,7 @@ pub async fn handle_announce( announce_handler: &Arc, whitelist_authorization: &Arc, opt_udp_stats_event_sender: &Arc>>, -) -> Result { +) -> Result { let info_hash = request.info_hash.into(); let remote_client_ip = remote_addr.ip(); @@ -45,29 +46,47 @@ pub async fn handle_announce( 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; + let original_peer_ip = peer.peer_addr.ip(); + + // The tracker could change the original peer ip + let announce_data = announce_handler + .announce(&info_hash, &mut peer, &original_peer_ip, &peers_wanted) + .await?; + + 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; + } + } + } Ok(announce_data) } +/// # Errors +/// +/// It will return an error if the announce request fails. pub async fn invoke( announce_handler: Arc, opt_udp_stats_event_sender: Arc>>, info_hash: InfoHash, peer: &mut peer::Peer, peers_wanted: &PeersWanted, -) -> AnnounceData { +) -> Result { 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); + let announce_data = announce_handler + .announce(&info_hash, peer, &original_peer_ip, peers_wanted) + .await?; if let Some(udp_stats_event_sender) = opt_udp_stats_event_sender.as_deref() { match original_peer_ip { @@ -84,5 +103,5 @@ pub async fn invoke( } } - announce_data + Ok(announce_data) } diff --git a/src/packages/udp_tracker_core/services/scrape.rs b/src/packages/udp_tracker_core/services/scrape.rs index e47dd35b..07ae452f 100644 --- a/src/packages/udp_tracker_core/services/scrape.rs +++ b/src/packages/udp_tracker_core/services/scrape.rs @@ -12,34 +12,27 @@ use std::sync::Arc; use aquatic_udp_protocol::ScrapeRequest; use bittorrent_primitives::info_hash::InfoHash; +use bittorrent_tracker_core::error::ScrapeError; use bittorrent_tracker_core::scrape_handler::ScrapeHandler; use torrust_tracker_primitives::core::ScrapeData; use crate::packages::udp_tracker_core; /// It handles the `Scrape` request. +/// +/// # Errors +/// +/// It will return an error if the tracker core scrape handler returns an error. pub async fn handle_scrape( remote_addr: SocketAddr, request: &ScrapeRequest, scrape_handler: &Arc, opt_udp_stats_event_sender: &Arc>>, -) -> ScrapeData { +) -> Result { // Convert from aquatic infohashes - let mut info_hashes: Vec = vec![]; - for info_hash in &request.info_hashes { - info_hashes.push((*info_hash).into()); - } - - invoke(scrape_handler, opt_udp_stats_event_sender, &info_hashes, remote_addr).await -} + let info_hashes: Vec = request.info_hashes.iter().map(|&x| x.into()).collect(); -pub async fn invoke( - scrape_handler: &Arc, - opt_udp_stats_event_sender: &Arc>>, - info_hashes: &Vec, - remote_addr: SocketAddr, -) -> ScrapeData { - let scrape_data = scrape_handler.scrape(info_hashes).await; + let scrape_data = scrape_handler.scrape(&info_hashes).await?; if let Some(udp_stats_event_sender) = opt_udp_stats_event_sender.as_deref() { match remote_addr { @@ -56,5 +49,5 @@ pub async fn invoke( } } - scrape_data + Ok(scrape_data) } diff --git a/src/servers/http/v1/handlers/announce.rs b/src/servers/http/v1/handlers/announce.rs index 977e7dd6..5f25c317 100644 --- a/src/servers/http/v1/handlers/announce.rs +++ b/src/servers/http/v1/handlers/announce.rs @@ -260,6 +260,7 @@ mod tests { let db_torrent_repository = Arc::new(DatabasePersistentTorrentRepository::new(&database)); let announce_handler = Arc::new(AnnounceHandler::new( &config.core, + &whitelist_authorization, &in_memory_torrent_repository, &db_torrent_repository, )); @@ -397,7 +398,7 @@ mod tests { assert_error_response( &response, &format!( - "Tracker error: The torrent: {}, is not whitelisted", + "Tracker whitelist error: The torrent: {}, is not whitelisted", announce_request.info_hash ), ); diff --git a/src/servers/http/v1/handlers/scrape.rs b/src/servers/http/v1/handlers/scrape.rs index 39bebe18..2d41c2f7 100644 --- a/src/servers/http/v1/handlers/scrape.rs +++ b/src/servers/http/v1/handlers/scrape.rs @@ -123,26 +123,25 @@ async fn handle_scrape( // todo: move authentication inside `http_tracker_core::services::scrape::handle_scrape` // Authentication - let return_real_scrape_data = if core_config.private { + let return_fake_scrape_data = if core_config.private { match maybe_key { Some(key) => match authentication_service.authenticate(&key).await { - Ok(()) => true, - Err(_error) => false, + Ok(()) => false, + Err(_error) => true, }, - None => false, + None => true, } } else { - true + false }; http_tracker_core::services::scrape::handle_scrape( core_config, scrape_handler, - authentication_service, opt_http_stats_event_sender, scrape_request, client_ip_sources, - return_real_scrape_data, + return_fake_scrape_data, ) .await } diff --git a/src/servers/udp/handlers/announce.rs b/src/servers/udp/handlers/announce.rs index 2254ea97..a273a2ec 100644 --- a/src/servers/udp/handlers/announce.rs +++ b/src/servers/udp/handlers/announce.rs @@ -261,6 +261,7 @@ mod tests { let expected_peer = TorrentPeerBuilder::new() .with_peer_id(peer_id) .with_peer_address(SocketAddr::new(IpAddr::V4(client_ip), client_port)) + .updated_on(peers[0].updated) .into(); assert_eq!(peers[0], Arc::new(expected_peer)); @@ -495,6 +496,7 @@ mod tests { let expected_peer = TorrentPeerBuilder::new() .with_peer_id(peer_id) .with_peer_address(SocketAddr::new(external_ip_in_tracker_configuration, client_port)) + .updated_on(peers[0].updated) .into(); assert_eq!(peers[0], Arc::new(expected_peer)); @@ -567,6 +569,7 @@ mod tests { let expected_peer = TorrentPeerBuilder::new() .with_peer_id(peer_id) .with_peer_address(SocketAddr::new(IpAddr::V6(client_ip_v6), client_port)) + .updated_on(peers[0].updated) .into(); assert_eq!(peers[0], Arc::new(expected_peer)); @@ -802,6 +805,7 @@ mod tests { let announce_handler = Arc::new(AnnounceHandler::new( &config.core, + &whitelist_authorization, &in_memory_torrent_repository, &db_torrent_repository, )); diff --git a/src/servers/udp/handlers/mod.rs b/src/servers/udp/handlers/mod.rs index 252a5be0..f9f8edae 100644 --- a/src/servers/udp/handlers/mod.rs +++ b/src/servers/udp/handlers/mod.rs @@ -196,7 +196,7 @@ pub(crate) mod tests { use tokio::sync::mpsc::error::SendError; use torrust_tracker_clock::clock::Time; use torrust_tracker_configuration::{Configuration, Core}; - use torrust_tracker_primitives::peer; + use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch}; use torrust_tracker_test_helpers::configuration; use super::gen_remote_fingerprint; @@ -242,6 +242,7 @@ pub(crate) mod tests { let db_torrent_repository = Arc::new(DatabasePersistentTorrentRepository::new(&database)); let announce_handler = Arc::new(AnnounceHandler::new( &config.core, + &whitelist_authorization, &in_memory_torrent_repository, &db_torrent_repository, )); @@ -329,6 +330,12 @@ pub(crate) mod tests { self } + #[must_use] + pub fn updated_on(mut self, updated: DurationSinceUnixEpoch) -> Self { + self.peer.updated = updated; + self + } + #[must_use] pub fn into(self) -> peer::Peer { self.peer diff --git a/src/servers/udp/handlers/scrape.rs b/src/servers/udp/handlers/scrape.rs index d41563ad..2b03e0dc 100644 --- a/src/servers/udp/handlers/scrape.rs +++ b/src/servers/udp/handlers/scrape.rs @@ -45,7 +45,12 @@ pub async fn handle_scrape( .map_err(|e| (e, request.transaction_id))?; let scrape_data = - udp_tracker_core::services::scrape::handle_scrape(remote_addr, request, scrape_handler, opt_udp_stats_event_sender).await; + udp_tracker_core::services::scrape::handle_scrape(remote_addr, request, scrape_handler, opt_udp_stats_event_sender) + .await + .map_err(|e| Error::TrackerError { + source: (Arc::new(e) as Arc).into(), + }) + .map_err(|e| (e, request.transaction_id))?; // todo: extract `build_response` function.