From 672864e2a5606b747caff2c0ec0adb2b3e323775 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Fri, 11 Mar 2022 22:27:16 +0100 Subject: [PATCH 01/32] feat: print announce request in debug log --- src/torrust_http_tracker/handlers.rs | 2 ++ src/torrust_http_tracker/request.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/src/torrust_http_tracker/handlers.rs b/src/torrust_http_tracker/handlers.rs index 6a25016ad..9dc7a47c9 100644 --- a/src/torrust_http_tracker/handlers.rs +++ b/src/torrust_http_tracker/handlers.rs @@ -34,6 +34,8 @@ pub async fn handle_announce(announce_request: AnnounceRequest, auth_key: Option return Err(reject::custom(e)) } + debug!("{:?}", announce_request); + if tracker.config.http_tracker.on_reverse_proxy && announce_request.forwarded_ip.is_none() { return Err(reject::custom(ServerError::AddressNotFound)) } diff --git a/src/torrust_http_tracker/request.rs b/src/torrust_http_tracker/request.rs index 2039de72e..9a34ce433 100644 --- a/src/torrust_http_tracker/request.rs +++ b/src/torrust_http_tracker/request.rs @@ -15,6 +15,7 @@ pub struct AnnounceRequestQuery { pub compact: Option, } +#[derive(Debug)] pub struct AnnounceRequest { pub info_hash: InfoHash, pub peer_addr: SocketAddr, From 9637661f10c52507884c0dc9b809d22ebcea9d50 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Mon, 14 Mar 2022 21:53:15 +0100 Subject: [PATCH 02/32] refactor: made all fields optional in http announce request except peer_id, info_hash and port --- src/torrust_http_tracker/filters.rs | 8 ++++---- src/torrust_http_tracker/request.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/torrust_http_tracker/filters.rs b/src/torrust_http_tracker/filters.rs index 85a4d3a99..3e05d58ef 100644 --- a/src/torrust_http_tracker/filters.rs +++ b/src/torrust_http_tracker/filters.rs @@ -76,16 +76,16 @@ async fn announce_request(announce_request_query: AnnounceRequestQuery, info_has .and_then(|ip_str| IpAddr::from_str(ip_str).ok()) } }; - + Ok(AnnounceRequest { info_hash: info_hashes[0], peer_addr: remote_addr.unwrap(), forwarded_ip, - downloaded: announce_request_query.downloaded, - uploaded: announce_request_query.uploaded, + downloaded: announce_request_query.downloaded.unwrap_or(0), + uploaded: announce_request_query.uploaded.unwrap_or(0), peer_id: announce_request_query.peer_id, port: announce_request_query.port, - left: announce_request_query.left, + left: announce_request_query.left.unwrap_or(0), event: announce_request_query.event, compact: announce_request_query.compact }) diff --git a/src/torrust_http_tracker/request.rs b/src/torrust_http_tracker/request.rs index 9a34ce433..9eed15d74 100644 --- a/src/torrust_http_tracker/request.rs +++ b/src/torrust_http_tracker/request.rs @@ -5,12 +5,12 @@ use crate::torrust_http_tracker::Bytes; #[derive(Deserialize)] pub struct AnnounceRequestQuery { - pub downloaded: Bytes, - pub uploaded: Bytes, - pub key: String, + pub downloaded: Option, + pub uploaded: Option, + pub key: Option, pub peer_id: String, pub port: u16, - pub left: Bytes, + pub left: Option, pub event: Option, pub compact: Option, } From f2125f279087a9e4beb90c38b599ac02ac5497bb Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Mon, 14 Mar 2022 21:53:44 +0100 Subject: [PATCH 03/32] refactor: return http announce errors in bencoded format instead of json --- src/torrust_http_tracker/handlers.rs | 10 +++++----- src/torrust_http_tracker/response.rs | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/torrust_http_tracker/handlers.rs b/src/torrust_http_tracker/handlers.rs index 9dc7a47c9..84394a7b2 100644 --- a/src/torrust_http_tracker/handlers.rs +++ b/src/torrust_http_tracker/handlers.rs @@ -3,7 +3,7 @@ use std::convert::Infallible; use std::sync::Arc; use log::debug; use warp::{reject, Rejection, Reply}; -use warp::http::{Response, StatusCode}; +use warp::http::{Response}; use crate::{InfoHash, TorrentError, TorrentPeer, TorrentStats, TorrentTracker}; use crate::key_manager::AuthKey; use crate::torrust_http_tracker::{AnnounceRequest, AnnounceResponse, ErrorResponse, Peer, ScrapeRequest, ScrapeResponse, ScrapeResponseEntry, ServerError, WebResult}; @@ -87,11 +87,11 @@ pub async fn handle_scrape(scrape_request: ScrapeRequest, auth_key: Option std::result::Result { if let Some(e) = r.find::() { debug!("{:?}", e); - let reply = warp::reply::json(&ErrorResponse { failure_reason: e.to_string() }); - Ok(warp::reply::with_status(reply, StatusCode::BAD_REQUEST)) + let body: String = ErrorResponse { failure_reason: e.to_string() }.write(); + Ok(Response::new(body)) } else { - let reply = warp::reply::json(&ErrorResponse { failure_reason: "internal server error".to_string() }); - Ok(warp::reply::with_status(reply, StatusCode::INTERNAL_SERVER_ERROR)) + let body: String = ErrorResponse { failure_reason: "internal server error".to_string() }.write(); + Ok(Response::new(body)) } } diff --git a/src/torrust_http_tracker/response.rs b/src/torrust_http_tracker/response.rs index df039a1c2..a80ee27c5 100644 --- a/src/torrust_http_tracker/response.rs +++ b/src/torrust_http_tracker/response.rs @@ -85,3 +85,9 @@ impl ScrapeResponse { pub struct ErrorResponse { pub failure_reason: String } + +impl ErrorResponse { + pub fn write(&self) -> String { + serde_bencode::to_string(&self).unwrap() + } +} From f596f226e9e1dd5662ba5f0079f0469d89e4f76d Mon Sep 17 00:00:00 2001 From: Power2All Date: Tue, 15 Mar 2022 13:23:46 +0100 Subject: [PATCH 04/32] Added: Separate HTTP/HTTPS/IPv4/IPv6 binding of sockets, persistant saving and loading of torrent hashes with completed count --- src/config.rs | 25 ++++++++ src/database.rs | 56 +++++++++++++++++- src/main.rs | 96 +++++++++++++++++++++++++------ src/torrust_udp_tracker/server.rs | 9 +++ src/tracker.rs | 28 ++++++++- 5 files changed, 193 insertions(+), 21 deletions(-) diff --git a/src/config.rs b/src/config.rs index 429997fcc..4efcb2825 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,6 +17,7 @@ pub enum TrackerServer { #[derive(Serialize, Deserialize)] pub struct UdpTrackerConfig { + pub enabled: bool, pub bind_address: String, pub announce_interval: u32, } @@ -28,6 +29,7 @@ pub struct HttpTrackerConfig { pub on_reverse_proxy: bool, pub announce_interval: u32, pub ssl_enabled: bool, + pub ssl_bind_address: String, #[serde(serialize_with = "none_as_empty_string")] pub ssl_cert_path: Option, #[serde(serialize_with = "none_as_empty_string")] @@ -52,10 +54,14 @@ pub struct Configuration { pub log_level: Option, pub mode: TrackerMode, pub db_path: String, + pub persistence: bool, pub cleanup_interval: Option, + pub cleanup_peerless: bool, pub external_ip: Option, pub udp_tracker: UdpTrackerConfig, + pub udp_tracker_ipv6: UdpTrackerConfig, pub http_tracker: HttpTrackerConfig, + pub http_tracker_ipv6: HttpTrackerConfig, pub http_api: HttpApiConfig, } @@ -128,18 +134,37 @@ impl Configuration { log_level: Option::from(String::from("info")), mode: TrackerMode::PublicMode, db_path: String::from("data.db"), + persistence: false, cleanup_interval: Some(600), + cleanup_peerless: true, external_ip: Some(String::from("0.0.0.0")), udp_tracker: UdpTrackerConfig { + enabled: true, bind_address: String::from("0.0.0.0:6969"), announce_interval: 120, }, + udp_tracker_ipv6: UdpTrackerConfig { + enabled: false, + bind_address: String::from("[::]:6969"), + announce_interval: 120, + }, http_tracker: HttpTrackerConfig { enabled: false, bind_address: String::from("0.0.0.0:6969"), on_reverse_proxy: false, announce_interval: 120, ssl_enabled: false, + ssl_bind_address: String::from("0.0.0.0:6868"), + ssl_cert_path: None, + ssl_key_path: None + }, + http_tracker_ipv6: HttpTrackerConfig { + enabled: false, + bind_address: String::from("[::]:6969"), + on_reverse_proxy: false, + announce_interval: 120, + ssl_enabled: false, + ssl_bind_address: String::from("[::]:6868"), ssl_cert_path: None, ssl_key_path: None }, diff --git a/src/database.rs b/src/database.rs index fbec824a0..827c7dbed 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,10 +1,11 @@ -use crate::{InfoHash, AUTH_KEY_LENGTH}; +use crate::{InfoHash, AUTH_KEY_LENGTH, TorrentTracker}; use log::debug; use r2d2_sqlite::{SqliteConnectionManager, rusqlite}; use r2d2::{Pool}; use r2d2_sqlite::rusqlite::NO_PARAMS; use crate::key_manager::AuthKey; use std::str::FromStr; +use std::sync::Arc; pub struct SqliteDatabase { pool: Pool @@ -32,6 +33,13 @@ impl SqliteDatabase { info_hash VARCHAR(20) NOT NULL UNIQUE );".to_string(); + let create_torrents_table = " + CREATE TABLE IF NOT EXISTS torrents ( + id integer PRIMARY KEY AUTOINCREMENT, + info_hash VARCHAR(20) NOT NULL UNIQUE, + completed INTEGER DEFAULT 0 NOT NULL + );".to_string(); + let create_keys_table = format!(" CREATE TABLE IF NOT EXISTS keys ( id integer PRIMARY KEY AUTOINCREMENT, @@ -43,7 +51,15 @@ impl SqliteDatabase { match conn.execute(&create_whitelist_table, NO_PARAMS) { Ok(updated) => { match conn.execute(&create_keys_table, NO_PARAMS) { - Ok(updated2) => Ok(updated + updated2), + Ok(updated2) => { + match conn.execute(&create_torrents_table, NO_PARAMS) { + Ok(updated3) => Ok(updated + updated2 + updated3), + Err(e) => { + debug!("{:?}", e); + Err(e) + } + } + } Err(e) => { debug!("{:?}", e); Err(e) @@ -57,6 +73,42 @@ impl SqliteDatabase { } } + pub async fn load_persistent_torrent_data(&self, tracker: Arc) -> Result { + let tracker_copy = tracker.clone(); + let conn = self.pool.get().unwrap(); + let mut stmt = conn.prepare("SELECT info_hash, completed FROM torrents")?; + + let info_hash_iter = stmt.query_map(NO_PARAMS, |row| { + let info_hash: String = row.get(0)?; + let info_hash_converted = InfoHash::from_str(&info_hash).unwrap(); + let completed: u32 = row.get(1)?; + Ok((info_hash_converted, completed)) + })?; + + for info_hash_item in info_hash_iter { + let (info_hash, completed): (InfoHash, u32) = info_hash_item.unwrap(); + tracker_copy.add_torrent(&info_hash, 0u32, completed, 0u32).await; + } + + Ok(true) + } + + pub async fn save_persistent_torrent_data(&self, tracker: Arc) -> Result { + let tracker_copy = tracker.clone(); + let mut conn = self.pool.get().unwrap(); + let db = tracker_copy.get_torrents().await; + let db_transaction = conn.transaction()?; + let _: Vec<_> = db + .iter() + .map(|(info_hash, torrent_entry)| { + let (_seeders, completed, _leechers) = torrent_entry.get_stats(); + let _ = db_transaction.execute("INSERT OR REPLACE INTO torrents (info_hash, completed) VALUES (?, ?)", &[info_hash.to_string(), completed.to_string()]); + }) + .collect(); + let _ = db_transaction.commit(); + Ok(true) + } + pub async fn get_info_hash_from_whitelist(&self, info_hash: &str) -> Result { let conn = self.pool.get().unwrap(); let mut stmt = conn.prepare("SELECT info_hash FROM whitelist WHERE info_hash = ?")?; diff --git a/src/main.rs b/src/main.rs index d8a73854e..741f05061 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use std::net::SocketAddr; use log::{info}; -use torrust_tracker::{http_api_server, Configuration, TorrentTracker, UdpServer, HttpTrackerConfig, UdpTrackerConfig, HttpApiConfig, logging, TrackerServer}; +use torrust_tracker::{http_api_server, Configuration, TorrentTracker, UdpServer, HttpTrackerConfig, UdpTrackerConfig, HttpApiConfig, logging}; use std::sync::Arc; use tokio::task::JoinHandle; use torrust_tracker::torrust_http_tracker::server::HttpServer; @@ -19,6 +19,11 @@ async fn main() { // the singleton torrent tracker that gets passed to the HTTP and UDP server let tracker = Arc::new(TorrentTracker::new(config.clone())); + // Load torrents if enabled + if config.persistence { + load_torrents_into_memory(tracker.clone()).await; + } + // start torrent cleanup job (periodically removes old peers) let _torrent_cleanup_job = start_torrent_cleanup_job(config.clone(), tracker.clone()).unwrap(); @@ -27,21 +32,58 @@ async fn main() { let _api_server = start_api_server(&config.http_api, tracker.clone()); } - // check which tracker to run, UDP (Default) or HTTP - let _tracker_server = match config.get_tracker_server() { - TrackerServer::UDP => { - start_udp_tracker_server(&config.udp_tracker, tracker.clone()).await - } - TrackerServer::HTTP => { - start_http_tracker_server(&config.http_tracker, tracker.clone()) - } - }; + // start UDP tracker if enabled + if config.udp_tracker.enabled { + let _udp_server = start_udp_tracker_server(&config.udp_tracker, tracker.clone()).await; + } + + // start UDP tracker for IPv6 if enabled + if config.udp_tracker_ipv6.enabled { + let _udp_server_ipv6 = start_udp_ipv6_tracker_server(&config.udp_tracker_ipv6, tracker.clone()).await; + } + + // start HTTP tracker if enabled + if config.http_tracker.enabled { + let _http_server = start_http_tracker_server(&config.http_tracker, tracker.clone()); + } + // start HTTPS tracker if enabled + if config.http_tracker.ssl_enabled { + let _http_ssl_server = start_http_ssl_tracker_server(&config.http_tracker, tracker.clone()); + } + + //start HTTP tracker for IPv6 if enabled + if config.http_tracker_ipv6.enabled { + let _http_server_ipv6 = start_http_tracker_server(&config.http_tracker_ipv6, tracker.clone()); + } + + // start HTTPS tracker for IPv6 if enabled + if config.http_tracker_ipv6.ssl_enabled { + let _http_ssl_server_ipv6 = start_http_ssl_tracker_server(&config.http_tracker_ipv6, tracker.clone()); + } + + // handle the signals here let ctrl_c = tokio::signal::ctrl_c(); tokio::select! { - _ = _tracker_server => { panic!("Tracker server exited.") }, _ = ctrl_c => { info!("Torrust shutting down..") } } + + // Save torrents if enabled + if config.persistence { + save_torrents_into_memory(tracker.clone()).await; + } +} + +async fn load_torrents_into_memory(tracker: Arc) { + info!("Loading torrents from SQL into memory..."); + let _ = tracker.load_torrents(tracker.clone()).await; + info!("Torrents loaded"); +} + +async fn save_torrents_into_memory(tracker: Arc) { + info!("Saving torrents into SQL from memory..."); + let _ = tracker.save_torrents(tracker.clone()).await; + info!("Torrents saved"); } fn start_torrent_cleanup_job(config: Arc, tracker: Arc) -> Option> { @@ -77,19 +119,26 @@ fn start_api_server(config: &HttpApiConfig, tracker: Arc) -> Joi fn start_http_tracker_server(config: &HttpTrackerConfig, tracker: Arc) -> JoinHandle<()> { let http_tracker = HttpServer::new(tracker); let bind_addr = config.bind_address.parse::().unwrap(); - let ssl_enabled = config.ssl_enabled; + + tokio::spawn(async move { + // run with tls if ssl_enabled and cert and key path are set + info!("Starting HTTP server on: {}", bind_addr); + http_tracker.start(bind_addr).await; + }) +} + +fn start_http_ssl_tracker_server(config: &HttpTrackerConfig, tracker: Arc) -> JoinHandle<()> { + let http_tracker = HttpServer::new(tracker); + let ssl_bind_addr = config.ssl_bind_address.parse::().unwrap(); let ssl_cert_path = config.ssl_cert_path.clone(); let ssl_key_path = config.ssl_key_path.clone(); tokio::spawn(async move { // run with tls if ssl_enabled and cert and key path are set - if ssl_enabled && ssl_cert_path.is_some() && ssl_key_path.is_some() { - info!("Starting HTTPS server on: {} (TLS)", bind_addr); - http_tracker.start_tls(bind_addr, ssl_cert_path.as_ref().unwrap(), ssl_key_path.as_ref().unwrap()).await; - } else { - info!("Starting HTTP server on: {}", bind_addr); - http_tracker.start(bind_addr).await; + if ssl_cert_path.is_some() && ssl_key_path.is_some() { + info!("Starting HTTPS server on: {} (TLS)", ssl_bind_addr); + http_tracker.start_tls(ssl_bind_addr, ssl_cert_path.as_ref().unwrap(), ssl_key_path.as_ref().unwrap()).await; } }) } @@ -104,3 +153,14 @@ async fn start_udp_tracker_server(config: &UdpTrackerConfig, tracker: Arc) -> JoinHandle<()> { + let udp_server = UdpServer::new_ipv6(tracker).await.unwrap_or_else(|e| { + panic!("Could not start UDP server (IPv6): {}", e); + }); + + info!("Starting UDP server on: {}", config.bind_address); + tokio::spawn(async move { + udp_server.start().await; + }) +} diff --git a/src/torrust_udp_tracker/server.rs b/src/torrust_udp_tracker/server.rs index 0da4ce140..86cc9727f 100644 --- a/src/torrust_udp_tracker/server.rs +++ b/src/torrust_udp_tracker/server.rs @@ -22,6 +22,15 @@ impl UdpServer { }) } + pub async fn new_ipv6(tracker: Arc) -> Result { + let srv = UdpSocket::bind(&tracker.config.udp_tracker_ipv6.bind_address).await?; + + Ok(UdpServer { + socket: srv, + tracker, + }) + } + pub async fn start(&self) { loop { let mut data = [0; MAX_PACKET_SIZE]; diff --git a/src/tracker.rs b/src/tracker.rs index 67acd7583..c53367f95 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -321,6 +321,16 @@ impl TorrentTracker { Ok(()) } + // Loading the torrents into memory + pub async fn load_torrents(&self, tracker: Arc) -> Result { + self.database.load_persistent_torrent_data(tracker).await + } + + // Saving the torrents from memory + pub async fn save_torrents(&self, tracker: Arc) -> Result { + self.database.save_persistent_torrent_data(tracker).await + } + // Adding torrents is not relevant to public trackers. pub async fn add_torrent_to_whitelist(&self, info_hash: &InfoHash) -> Result { self.database.add_info_hash_to_whitelist(info_hash.clone()).await @@ -378,6 +388,22 @@ impl TorrentTracker { } } + pub async fn add_torrent(&self, info_hash: &InfoHash, seeders: u32, completed: u32, leechers: u32) -> TorrentStats { + let mut torrents = self.torrents.write().await; + + if !torrents.contains_key(&info_hash) { + let mut torrent_entry = TorrentEntry::new(); + torrent_entry.completed = completed; + torrents.insert(info_hash.clone(), torrent_entry); + } + + TorrentStats { + seeders, + completed, + leechers, + } + } + pub async fn get_torrents(&self) -> tokio::sync::RwLockReadGuard<'_, BTreeMap> { self.torrents.read().await } @@ -413,7 +439,7 @@ impl TorrentTracker { } } - if self.config.mode.clone() == TrackerMode::PublicMode { + if self.config.mode.clone() == TrackerMode::PublicMode && self.config.cleanup_peerless && !self.config.persistence { // peer-less torrents.. if torrent_entry.peers.len() == 0 { torrents_to_remove.push(k.clone()); From afe75a7d03c9ff81ba65997ceb5fd3ad62c0c264 Mon Sep 17 00:00:00 2001 From: Power2All Date: Tue, 15 Mar 2022 16:07:01 +0100 Subject: [PATCH 05/32] Changing the static binding options to a dynamical one, you can now bind to as many sockets as you like, and for HTTP it can be either HTTP or HTTPS, or One of the both at the same time with each a custom binding address --- src/config.rs | 68 ++++++++++------------------ src/main.rs | 67 ++++++++++----------------- src/torrust_http_tracker/handlers.rs | 6 +-- src/torrust_udp_tracker/handlers.rs | 2 +- src/torrust_udp_tracker/server.rs | 15 ++---- 5 files changed, 55 insertions(+), 103 deletions(-) diff --git a/src/config.rs b/src/config.rs index 4efcb2825..f23a13c16 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,15 +19,12 @@ pub enum TrackerServer { pub struct UdpTrackerConfig { pub enabled: bool, pub bind_address: String, - pub announce_interval: u32, } #[derive(Serialize, Deserialize)] pub struct HttpTrackerConfig { pub enabled: bool, pub bind_address: String, - pub on_reverse_proxy: bool, - pub announce_interval: u32, pub ssl_enabled: bool, pub ssl_bind_address: String, #[serde(serialize_with = "none_as_empty_string")] @@ -58,10 +55,10 @@ pub struct Configuration { pub cleanup_interval: Option, pub cleanup_peerless: bool, pub external_ip: Option, - pub udp_tracker: UdpTrackerConfig, - pub udp_tracker_ipv6: UdpTrackerConfig, - pub http_tracker: HttpTrackerConfig, - pub http_tracker_ipv6: HttpTrackerConfig, + pub announce_interval: u32, + pub on_reverse_proxy: bool, + pub udp_trackers: Vec, + pub http_trackers: Vec, pub http_api: HttpApiConfig, } @@ -130,7 +127,7 @@ impl Configuration { impl Configuration { pub fn default() -> Configuration { - Configuration { + let mut configuration = Configuration { log_level: Option::from(String::from("info")), mode: TrackerMode::PublicMode, db_path: String::from("data.db"), @@ -138,47 +135,38 @@ impl Configuration { cleanup_interval: Some(600), cleanup_peerless: true, external_ip: Some(String::from("0.0.0.0")), - udp_tracker: UdpTrackerConfig { + announce_interval: 0, + on_reverse_proxy: false, + udp_trackers: Vec::new(), + http_trackers: Vec::new(), + http_api: HttpApiConfig { enabled: true, - bind_address: String::from("0.0.0.0:6969"), - announce_interval: 120, + bind_address: String::from("127.0.0.1:1212"), + access_tokens: [(String::from("admin"), String::from("MyAccessToken"))].iter().cloned().collect(), }, - udp_tracker_ipv6: UdpTrackerConfig { + }; + configuration.udp_trackers.push( + UdpTrackerConfig{ enabled: false, - bind_address: String::from("[::]:6969"), - announce_interval: 120, - }, - http_tracker: HttpTrackerConfig { + bind_address: String::from("0.0.0.0:6969") + } + ); + configuration.http_trackers.push( + HttpTrackerConfig{ enabled: false, bind_address: String::from("0.0.0.0:6969"), - on_reverse_proxy: false, - announce_interval: 120, ssl_enabled: false, ssl_bind_address: String::from("0.0.0.0:6868"), ssl_cert_path: None, ssl_key_path: None - }, - http_tracker_ipv6: HttpTrackerConfig { - enabled: false, - bind_address: String::from("[::]:6969"), - on_reverse_proxy: false, - announce_interval: 120, - ssl_enabled: false, - ssl_bind_address: String::from("[::]:6868"), - ssl_cert_path: None, - ssl_key_path: None - }, - http_api: HttpApiConfig { - enabled: true, - bind_address: String::from("127.0.0.1:1212"), - access_tokens: [(String::from("admin"), String::from("MyAccessToken"))].iter().cloned().collect(), - }, - } + } + ); + configuration } pub fn verify(&self) -> Result<(), ConfigurationError> { // UDP is not secure for sending private keys - if (self.mode == TrackerMode::PrivateMode || self.mode == TrackerMode::PrivateListedMode) && self.get_tracker_server() == TrackerServer::UDP { + if self.mode == TrackerMode::PrivateMode || self.mode == TrackerMode::PrivateListedMode { return Err(ConfigurationError::TrackerModeIncompatible) } @@ -213,12 +201,4 @@ impl Configuration { fs::write("config.toml", toml_string).expect("Could not write to file!"); Ok(()) } - - pub fn get_tracker_server(&self) -> TrackerServer { - if self.http_tracker.enabled { - TrackerServer::HTTP - } else { - TrackerServer::UDP - } - } } diff --git a/src/main.rs b/src/main.rs index 741f05061..f6f9fefb8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,34 +32,17 @@ async fn main() { let _api_server = start_api_server(&config.http_api, tracker.clone()); } - // start UDP tracker if enabled - if config.udp_tracker.enabled { - let _udp_server = start_udp_tracker_server(&config.udp_tracker, tracker.clone()).await; - } - - // start UDP tracker for IPv6 if enabled - if config.udp_tracker_ipv6.enabled { - let _udp_server_ipv6 = start_udp_ipv6_tracker_server(&config.udp_tracker_ipv6, tracker.clone()).await; - } - - // start HTTP tracker if enabled - if config.http_tracker.enabled { - let _http_server = start_http_tracker_server(&config.http_tracker, tracker.clone()); - } - - // start HTTPS tracker if enabled - if config.http_tracker.ssl_enabled { - let _http_ssl_server = start_http_ssl_tracker_server(&config.http_tracker, tracker.clone()); - } - - //start HTTP tracker for IPv6 if enabled - if config.http_tracker_ipv6.enabled { - let _http_server_ipv6 = start_http_tracker_server(&config.http_tracker_ipv6, tracker.clone()); + // start the udp blocks + for udp_tracker in &config.udp_trackers { + if udp_tracker.enabled { + let _ = start_udp_tracker_server(&udp_tracker, tracker.clone()).await; + } } - // start HTTPS tracker for IPv6 if enabled - if config.http_tracker_ipv6.ssl_enabled { - let _http_ssl_server_ipv6 = start_http_ssl_tracker_server(&config.http_tracker_ipv6, tracker.clone()); + // start the http blocks + for http_tracker in &config.http_trackers { + let _ = start_http_tracker_server(&http_tracker, tracker.clone(), true); + let _ = start_http_tracker_server(&http_tracker, tracker.clone(), false); } // handle the signals here @@ -116,14 +99,25 @@ fn start_api_server(config: &HttpApiConfig, tracker: Arc) -> Joi }) } -fn start_http_tracker_server(config: &HttpTrackerConfig, tracker: Arc) -> JoinHandle<()> { +fn start_http_tracker_server(config: &HttpTrackerConfig, tracker: Arc, ssl: bool) -> JoinHandle<()> { let http_tracker = HttpServer::new(tracker); + let enabled = config.enabled; let bind_addr = config.bind_address.parse::().unwrap(); + let ssl_enabled = config.ssl_enabled; + let ssl_bind_addr = config.ssl_bind_address.parse::().unwrap(); + let ssl_cert_path = config.ssl_cert_path.clone(); + let ssl_key_path = config.ssl_key_path.clone(); tokio::spawn(async move { // run with tls if ssl_enabled and cert and key path are set - info!("Starting HTTP server on: {}", bind_addr); - http_tracker.start(bind_addr).await; + if ssl && ssl_enabled && ssl_cert_path.is_some() && ssl_key_path.is_some() { + info!("Starting HTTPS server on: {} (TLS)", ssl_bind_addr); + http_tracker.start_tls(ssl_bind_addr, ssl_cert_path.as_ref().unwrap(), ssl_key_path.as_ref().unwrap()).await; + } + if !ssl && enabled { + info!("Starting HTTP server on: {}", bind_addr); + http_tracker.start(bind_addr).await; + } }) } @@ -137,14 +131,12 @@ fn start_http_ssl_tracker_server(config: &HttpTrackerConfig, tracker: Arc) -> JoinHandle<()> { - let udp_server = UdpServer::new(tracker).await.unwrap_or_else(|e| { + let udp_server = UdpServer::new(tracker, config).await.unwrap_or_else(|e| { panic!("Could not start UDP server: {}", e); }); @@ -153,14 +145,3 @@ async fn start_udp_tracker_server(config: &UdpTrackerConfig, tracker: Arc) -> JoinHandle<()> { - let udp_server = UdpServer::new_ipv6(tracker).await.unwrap_or_else(|e| { - panic!("Could not start UDP server (IPv6): {}", e); - }); - - info!("Starting UDP server on: {}", config.bind_address); - tokio::spawn(async move { - udp_server.start().await; - }) -} diff --git a/src/torrust_http_tracker/handlers.rs b/src/torrust_http_tracker/handlers.rs index 84394a7b2..81b17bd24 100644 --- a/src/torrust_http_tracker/handlers.rs +++ b/src/torrust_http_tracker/handlers.rs @@ -36,11 +36,11 @@ pub async fn handle_announce(announce_request: AnnounceRequest, auth_key: Option debug!("{:?}", announce_request); - if tracker.config.http_tracker.on_reverse_proxy && announce_request.forwarded_ip.is_none() { + if tracker.config.on_reverse_proxy && announce_request.forwarded_ip.is_none() { return Err(reject::custom(ServerError::AddressNotFound)) } - let peer_ip = match tracker.config.http_tracker.on_reverse_proxy { + let peer_ip = match tracker.config.on_reverse_proxy { true => announce_request.forwarded_ip.unwrap(), false => announce_request.peer_addr.ip() }; @@ -52,7 +52,7 @@ pub async fn handle_announce(announce_request: AnnounceRequest, auth_key: Option if peers.is_none() { return Err(reject::custom(ServerError::NoPeersFound)) } // success response - let announce_interval = tracker.config.http_tracker.announce_interval; + let announce_interval = tracker.config.announce_interval; send_announce_response(&announce_request, torrent_stats, peers.unwrap(), announce_interval) } diff --git a/src/torrust_udp_tracker/handlers.rs b/src/torrust_udp_tracker/handlers.rs index e10001527..84cba3f45 100644 --- a/src/torrust_udp_tracker/handlers.rs +++ b/src/torrust_udp_tracker/handlers.rs @@ -90,7 +90,7 @@ pub async fn handle_announce(remote_addr: SocketAddr, announce_request: &Announc Ok(Response::from(AnnounceResponse { transaction_id: wrapped_announce_request.announce_request.transaction_id, - announce_interval: AnnounceInterval(tracker.config.udp_tracker.announce_interval as i32), + announce_interval: AnnounceInterval(tracker.config.announce_interval as i32), leechers: NumberOfPeers(torrent_stats.leechers as i32), seeders: NumberOfPeers(torrent_stats.seeders as i32), peers: peers.iter().map(|peer| diff --git a/src/torrust_udp_tracker/server.rs b/src/torrust_udp_tracker/server.rs index 86cc9727f..0e24fd81c 100644 --- a/src/torrust_udp_tracker/server.rs +++ b/src/torrust_udp_tracker/server.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use aquatic_udp_protocol::{IpVersion, Response}; use log::debug; use tokio::net::UdpSocket; -use crate::TorrentTracker; +use crate::{TorrentTracker, UdpTrackerConfig}; use crate::torrust_udp_tracker::{handle_packet, MAX_PACKET_SIZE}; pub struct UdpServer { @@ -13,17 +13,8 @@ pub struct UdpServer { } impl UdpServer { - pub async fn new(tracker: Arc) -> Result { - let srv = UdpSocket::bind(&tracker.config.udp_tracker.bind_address).await?; - - Ok(UdpServer { - socket: srv, - tracker, - }) - } - - pub async fn new_ipv6(tracker: Arc) -> Result { - let srv = UdpSocket::bind(&tracker.config.udp_tracker_ipv6.bind_address).await?; + pub async fn new(tracker: Arc, config: &UdpTrackerConfig) -> Result { + let srv = UdpSocket::bind(&config.bind_address).await?; Ok(UdpServer { socket: srv, From 16f9d8af15a160a2cddb75f2104799cb5bf58682 Mon Sep 17 00:00:00 2001 From: Power2All Date: Tue, 15 Mar 2022 16:20:51 +0100 Subject: [PATCH 06/32] Readme updated with latest changes so far --- README.md | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index e3f2ed5be..9a85dddcd 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,15 @@ Torrust Tracker is a lightweight but incredibly powerful and feature-rich BitTor ### Features * [X] UDP server -* [X] HTTP (optional SSL) server +* [X] HTTP and/or HTTPS (SSL) server +* [X] Multiple UDP and HTTP(S) blocks for socket binding possible +* [X] Full IPv4 and IPv6 support for both UDP and HTTP(S) * [X] Private & Whitelisted mode * [X] Built-in API * [X] Torrent whitelisting * [X] Peer authentication using time-bound keys +* [ ] NewTrackOn check supported +* [X] SQLite3 Persistent loading and saving of the torrent hashes and completed count ### Implemented BEPs * [BEP 15](http://www.bittorrent.org/beps/bep_0015.html): UDP Tracker Protocol for BitTorrent @@ -44,24 +48,27 @@ cargo build --release * Edit the newly created config.toml file according to your liking, see [configuration documentation](https://torrust.github.io/torrust-documentation/torrust-tracker/config/). Eg: ```toml -log_level = "trace" +log_level = "info" mode = "public" db_path = "data.db" +persistence = false cleanup_interval = 600 -external_ip = "YOUR_EXTERNAL_IP" +cleanup_peerless = true +external_ip = "0.0.0.0" +announce_interval = 0 +on_reverse_proxy = false -[udp_tracker] +[[udp_trackers]] +enabled = true bind_address = "0.0.0.0:6969" -announce_interval = 120 -[http_tracker] +[[http_trackers]] enabled = true bind_address = "0.0.0.0:6969" -on_reverse_proxy = false -announce_interval = 120 -ssl_enabled = false -ssl_cert_path = "" -ssl_key_path = "" +ssl_enabled = true +ssl_bind_address = "0.0.0.0:6868" +ssl_cert_path = "cert.pem" +ssl_key_path = "key.pem" [http_api] enabled = true @@ -78,7 +85,7 @@ admin = "MyAccessToken" ``` ### Tracker URL -Your tracker announce URL will be **udp://{tracker-ip:port}** or **https://{tracker-ip:port}/announce** depending on your tracker mode. +Your tracker announce URL will be **udp://{tracker-ip:port}** and/or **http://{tracker-ip:port}/announce** and/or **https://{tracker-ip:port}/announce** depending on your bindings. In private & private_listed mode, tracker keys are added after the tracker URL like: **https://{tracker-ip:port}/announce/{key}**. ### Built-in API @@ -87,3 +94,4 @@ Read the API documentation [here](https://torrust.github.io/torrust-documentatio ### Credits This project was a joint effort by [Nautilus Cyberneering GmbH](https://nautilus-cyberneering.de/) and [Dutch Bits](https://dutchbits.nl). Also thanks to [Naim A.](https://github.com/naim94a/udpt) and [greatest-ape](https://github.com/greatest-ape/aquatic) for some parts of the code. +Further added features and functions thanks to [Power2All](https://github.com/power2all). \ No newline at end of file From b91c5e4ead25ff41244d367293cb45d4795968f3 Mon Sep 17 00:00:00 2001 From: Power2All Date: Tue, 15 Mar 2022 16:55:46 +0100 Subject: [PATCH 07/32] Fixing a nasty peer_id bug --- src/torrust_http_tracker/errors.rs | 3 +++ src/torrust_http_tracker/handlers.rs | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/torrust_http_tracker/errors.rs b/src/torrust_http_tracker/errors.rs index f0bedfe1b..d8d6c7623 100644 --- a/src/torrust_http_tracker/errors.rs +++ b/src/torrust_http_tracker/errors.rs @@ -9,6 +9,9 @@ pub enum ServerError { #[error("info_hash is either missing or invalid")] InvalidInfoHash, + #[error("peer_id is either missing or invalid")] + InvalidPeerId, + #[error("could not find remote address")] AddressNotFound, diff --git a/src/torrust_http_tracker/handlers.rs b/src/torrust_http_tracker/handlers.rs index 81b17bd24..21c92f1b1 100644 --- a/src/torrust_http_tracker/handlers.rs +++ b/src/torrust_http_tracker/handlers.rs @@ -45,6 +45,10 @@ pub async fn handle_announce(announce_request: AnnounceRequest, auth_key: Option false => announce_request.peer_addr.ip() }; + if announce_request.peer_id.len() != 40 { + return Err(reject::custom(ServerError::InvalidPeerId)) + } + let peer = TorrentPeer::from_http_announce_request(&announce_request, peer_ip, tracker.config.get_ext_ip()); let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&announce_request.info_hash, &peer).await; // get all peers excluding the client_addr From ca2d118a573b6156ea1812340db4c6604dae4073 Mon Sep 17 00:00:00 2001 From: Power2All Date: Tue, 15 Mar 2022 17:15:07 +0100 Subject: [PATCH 08/32] Fixing the peer timeout for both seed and peers, and put the config into the configuration as expected --- src/config.rs | 6 ++++-- src/tracker.rs | 7 ++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/config.rs b/src/config.rs index f23a13c16..3ec55f857 100644 --- a/src/config.rs +++ b/src/config.rs @@ -56,6 +56,7 @@ pub struct Configuration { pub cleanup_peerless: bool, pub external_ip: Option, pub announce_interval: u32, + pub peer_timeout: u32, pub on_reverse_proxy: bool, pub udp_trackers: Vec, pub http_trackers: Vec, @@ -135,7 +136,8 @@ impl Configuration { cleanup_interval: Some(600), cleanup_peerless: true, external_ip: Some(String::from("0.0.0.0")), - announce_interval: 0, + announce_interval: 120, + peer_timeout: 900, on_reverse_proxy: false, udp_trackers: Vec::new(), http_trackers: Vec::new(), @@ -143,7 +145,7 @@ impl Configuration { enabled: true, bind_address: String::from("127.0.0.1:1212"), access_tokens: [(String::from("admin"), String::from("MyAccessToken"))].iter().cloned().collect(), - }, + } }; configuration.udp_trackers.push( UdpTrackerConfig{ diff --git a/src/tracker.rs b/src/tracker.rs index c53367f95..80190dac4 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -15,9 +15,6 @@ use crate::key_manager::AuthKey; use r2d2_sqlite::rusqlite; use crate::torrust_http_tracker::AnnounceRequest; -const TWO_HOURS: std::time::Duration = std::time::Duration::from_secs(3600 * 2); -const FIVE_MINUTES: std::time::Duration = std::time::Duration::from_secs(300); - #[derive(Serialize, Deserialize, Clone, PartialEq)] pub enum TrackerMode { // Will track every new info hash and serve every peer. @@ -423,12 +420,12 @@ impl TorrentTracker { for (peer_id, peer) in torrent_peers.iter() { if peer.is_seeder() { - if peer.updated.elapsed() > FIVE_MINUTES { + if peer.updated.elapsed() > std::time::Duration::from_secs(self.config.peer_timeout as u64) { // remove seeders after 5 minutes since last update... peers_to_remove.push(peer_id.clone()); torrent_entry.seeders -= 1; } - } else if peer.updated.elapsed() > TWO_HOURS { + } else if peer.updated.elapsed() > std::time::Duration::from_secs(self.config.peer_timeout as u64) { // remove peers after 2 hours since last update... peers_to_remove.push(peer_id.clone()); } From e1ccb1b55ae337facb313f8045896d84968845f6 Mon Sep 17 00:00:00 2001 From: Power2All Date: Tue, 15 Mar 2022 22:51:24 +0100 Subject: [PATCH 09/32] Revert "Fixing a nasty peer_id bug" This reverts commit b91c5e4ead25ff41244d367293cb45d4795968f3. --- src/torrust_http_tracker/errors.rs | 3 --- src/torrust_http_tracker/handlers.rs | 4 ---- 2 files changed, 7 deletions(-) diff --git a/src/torrust_http_tracker/errors.rs b/src/torrust_http_tracker/errors.rs index d8d6c7623..f0bedfe1b 100644 --- a/src/torrust_http_tracker/errors.rs +++ b/src/torrust_http_tracker/errors.rs @@ -9,9 +9,6 @@ pub enum ServerError { #[error("info_hash is either missing or invalid")] InvalidInfoHash, - #[error("peer_id is either missing or invalid")] - InvalidPeerId, - #[error("could not find remote address")] AddressNotFound, diff --git a/src/torrust_http_tracker/handlers.rs b/src/torrust_http_tracker/handlers.rs index 21c92f1b1..81b17bd24 100644 --- a/src/torrust_http_tracker/handlers.rs +++ b/src/torrust_http_tracker/handlers.rs @@ -45,10 +45,6 @@ pub async fn handle_announce(announce_request: AnnounceRequest, auth_key: Option false => announce_request.peer_addr.ip() }; - if announce_request.peer_id.len() != 40 { - return Err(reject::custom(ServerError::InvalidPeerId)) - } - let peer = TorrentPeer::from_http_announce_request(&announce_request, peer_ip, tracker.config.get_ext_ip()); let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&announce_request.info_hash, &peer).await; // get all peers excluding the client_addr From 5a075178a6bf7f3845c8879b1a056c66f41d7a73 Mon Sep 17 00:00:00 2001 From: Power2All Date: Tue, 15 Mar 2022 23:03:53 +0100 Subject: [PATCH 10/32] Applied the Peer_ID fix from master of torrust tracker to development --- src/common.rs | 20 +++++++---- src/torrust_http_tracker/errors.rs | 3 ++ src/torrust_http_tracker/filters.rs | 52 +++++++++++++++++++++++++--- src/torrust_http_tracker/handlers.rs | 2 +- src/torrust_http_tracker/request.rs | 5 ++- src/tracker.rs | 9 ++--- 6 files changed, 69 insertions(+), 22 deletions(-) diff --git a/src/common.rs b/src/common.rs index b73a0511b..0037f058f 100644 --- a/src/common.rs +++ b/src/common.rs @@ -126,15 +126,23 @@ impl<'v> serde::de::Visitor<'v> for InfoHashVisitor { } #[derive(PartialEq, Eq, Hash, Clone, Debug, PartialOrd, Ord)] -pub struct PeerId(pub String); +pub struct PeerId(pub [u8; 20]); + +impl PeerId { + pub fn to_string(&self) -> String { + let mut buffer = [0u8; 20]; + let bytes_out = binascii::bin2hex(&self.0, &mut buffer).ok().unwrap(); + String::from(std::str::from_utf8(bytes_out).unwrap()) + } +} impl PeerId { pub fn get_client_name(&self) -> Option<&'static str> { - if self.0.as_bytes()[0] == b'M' { + if self.0[0] == b'M' { return Some("BitTorrent"); } - if self.0.as_bytes()[0] == b'-' { - let name = match &self.0.as_bytes()[1..3] { + if self.0[0] == b'-' { + let name = match &self.0[1..3] { b"AG" => "Ares", b"A~" => "Ares", b"AR" => "Arctic", @@ -211,9 +219,9 @@ impl Serialize for PeerId { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { - let buff_size = self.0.as_bytes().len() * 2; + let buff_size = self.0.len() * 2; let mut tmp: Vec = vec![0; buff_size]; - binascii::bin2hex(&self.0.as_bytes(), &mut tmp).unwrap(); + binascii::bin2hex(&self.0, &mut tmp).unwrap(); let id = std::str::from_utf8(&tmp).ok(); #[derive(Serialize)] diff --git a/src/torrust_http_tracker/errors.rs b/src/torrust_http_tracker/errors.rs index f0bedfe1b..d8d6c7623 100644 --- a/src/torrust_http_tracker/errors.rs +++ b/src/torrust_http_tracker/errors.rs @@ -9,6 +9,9 @@ pub enum ServerError { #[error("info_hash is either missing or invalid")] InvalidInfoHash, + #[error("peer_id is either missing or invalid")] + InvalidPeerId, + #[error("could not find remote address")] AddressNotFound, diff --git a/src/torrust_http_tracker/filters.rs b/src/torrust_http_tracker/filters.rs index 3e05d58ef..0e2b364b5 100644 --- a/src/torrust_http_tracker/filters.rs +++ b/src/torrust_http_tracker/filters.rs @@ -3,7 +3,7 @@ use std::net::{IpAddr, SocketAddr}; use std::str::FromStr; use std::sync::Arc; use warp::{Filter, reject, Rejection}; -use crate::{InfoHash, MAX_SCRAPE_TORRENTS, TorrentTracker}; +use crate::{InfoHash, MAX_SCRAPE_TORRENTS, PeerId, TorrentTracker}; use crate::key_manager::AuthKey; use crate::torrust_http_tracker::{AnnounceRequest, AnnounceRequestQuery, ScrapeRequest, ServerError, WebResult}; @@ -44,6 +44,49 @@ async fn info_hashes(raw_query: String) -> WebResult> { } } +/// Check for PeerId +pub fn with_peer_id() -> impl Filter + Clone { + warp::filters::query::raw() + .and_then(peer_id) +} + +/// Parse PeerId from raw query string +async fn peer_id(raw_query: String) -> WebResult { + // put all query params in a vec + let split_raw_query: Vec<&str> = raw_query.split("&").collect(); + + let mut peer_id: Option = None; + + for v in split_raw_query { + // look for the peer_id param + if v.contains("peer_id") { + // get raw percent_encoded peer_id + let raw_peer_id = v.split("=").collect::>()[1]; + + // decode peer_id + let peer_id_bytes = percent_encoding::percent_decode_str(raw_peer_id).collect::>(); + + // peer_id must be 20 bytes + if peer_id_bytes.len() > 20 { + return Err(reject::custom(ServerError::InvalidPeerId)); + } + + // clone peer_id_bytes into fixed length array + let mut byte_arr: [u8; 20] = Default::default(); + byte_arr.clone_from_slice(peer_id_bytes.as_slice()); + + peer_id = Some(PeerId(byte_arr)); + break; + } + } + + if peer_id.is_none() { + Err(reject::custom(ServerError::InvalidPeerId)) + } else { + Ok(peer_id.unwrap()) + } +} + /// Pass Arc along pub fn with_auth_key() -> impl Filter,), Error = Infallible> + Clone { warp::path::param::() @@ -59,15 +102,14 @@ pub fn with_auth_key() -> impl Filter,), Error = Infa pub fn with_announce_request() -> impl Filter + Clone { warp::filters::query::query::() .and(with_info_hash()) + .and(with_peer_id()) .and(warp::addr::remote()) .and(warp::header::optional::("X-Forwarded-For")) .and_then(announce_request) } /// Parse AnnounceRequest from raw AnnounceRequestQuery, InfoHash and Option -async fn announce_request(announce_request_query: AnnounceRequestQuery, info_hashes: Vec, remote_addr: Option, forwarded_for: Option) -> WebResult { - if remote_addr.is_none() { return Err(reject::custom(ServerError::AddressNotFound)) } - +async fn announce_request(announce_request_query: AnnounceRequestQuery, info_hashes: Vec, peer_id: PeerId, remote_addr: Option, forwarded_for: Option) -> WebResult { // get first forwarded ip let forwarded_ip = match forwarded_for { None => None, @@ -83,7 +125,7 @@ async fn announce_request(announce_request_query: AnnounceRequestQuery, info_has forwarded_ip, downloaded: announce_request_query.downloaded.unwrap_or(0), uploaded: announce_request_query.uploaded.unwrap_or(0), - peer_id: announce_request_query.peer_id, + peer_id, port: announce_request_query.port, left: announce_request_query.left.unwrap_or(0), event: announce_request_query.event, diff --git a/src/torrust_http_tracker/handlers.rs b/src/torrust_http_tracker/handlers.rs index 81b17bd24..586c520f8 100644 --- a/src/torrust_http_tracker/handlers.rs +++ b/src/torrust_http_tracker/handlers.rs @@ -98,7 +98,7 @@ pub async fn handle_error(r: Rejection) -> std::result::Result, interval: u32) -> WebResult { let http_peers: Vec = peers.iter().map(|peer| Peer { - peer_id: peer.peer_id.0.clone(), + peer_id: peer.peer_id.to_string(), ip: peer.peer_addr.ip(), port: peer.peer_addr.port() }).collect(); diff --git a/src/torrust_http_tracker/request.rs b/src/torrust_http_tracker/request.rs index 9eed15d74..6630e5660 100644 --- a/src/torrust_http_tracker/request.rs +++ b/src/torrust_http_tracker/request.rs @@ -1,6 +1,6 @@ use std::net::{IpAddr, SocketAddr}; use serde::{Deserialize}; -use crate::InfoHash; +use crate::{InfoHash, PeerId}; use crate::torrust_http_tracker::Bytes; #[derive(Deserialize)] @@ -8,7 +8,6 @@ pub struct AnnounceRequestQuery { pub downloaded: Option, pub uploaded: Option, pub key: Option, - pub peer_id: String, pub port: u16, pub left: Option, pub event: Option, @@ -22,7 +21,7 @@ pub struct AnnounceRequest { pub forwarded_ip: Option, pub downloaded: Bytes, pub uploaded: Bytes, - pub peer_id: String, + pub peer_id: PeerId, pub port: u16, pub left: Bytes, pub event: Option, diff --git a/src/tracker.rs b/src/tracker.rs index 80190dac4..3a717f239 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -54,10 +54,8 @@ impl TorrentPeer { pub fn from_udp_announce_request(announce_request: &aquatic_udp_protocol::AnnounceRequest, remote_ip: IpAddr, host_opt_ip: Option) -> Self { let peer_addr = TorrentPeer::peer_addr_from_ip_and_port_and_opt_host_ip(remote_ip, host_opt_ip, announce_request.port.0); - let peer_id = String::from_utf8_lossy(&announce_request.peer_id.0).parse().unwrap_or("unknown".to_string()); - TorrentPeer { - peer_id: PeerId(peer_id), + peer_id: PeerId(announce_request.peer_id.0), peer_addr, updated: std::time::Instant::now(), uploaded: announce_request.bytes_uploaded, @@ -70,9 +68,6 @@ impl TorrentPeer { pub fn from_http_announce_request(announce_request: &AnnounceRequest, remote_ip: IpAddr, host_opt_ip: Option) -> Self { let peer_addr = TorrentPeer::peer_addr_from_ip_and_port_and_opt_host_ip(remote_ip, host_opt_ip, announce_request.port); - let max_string_size = announce_request.peer_id.len().clamp(0, 40); - let peer_id = announce_request.peer_id[..max_string_size].to_string(); - let event: AnnounceEvent = if let Some(event) = &announce_request.event { match event.as_ref() { "started" => AnnounceEvent::Started, @@ -85,7 +80,7 @@ impl TorrentPeer { }; TorrentPeer { - peer_id: PeerId(peer_id), + peer_id: announce_request.peer_id.clone(), peer_addr, updated: std::time::Instant::now(), uploaded: NumberOfBytes(announce_request.uploaded as i64), From 18e7057fc02565a34711cc2c3f745c32e6b3ebad Mon Sep 17 00:00:00 2001 From: Power2All Date: Tue, 15 Mar 2022 23:06:13 +0100 Subject: [PATCH 11/32] Removed a unneeded function --- src/main.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/main.rs b/src/main.rs index f6f9fefb8..bd5ff3b40 100644 --- a/src/main.rs +++ b/src/main.rs @@ -121,20 +121,6 @@ fn start_http_tracker_server(config: &HttpTrackerConfig, tracker: Arc) -> JoinHandle<()> { - let http_tracker = HttpServer::new(tracker); - let ssl_bind_addr = config.ssl_bind_address.parse::().unwrap(); - let ssl_cert_path = config.ssl_cert_path.clone(); - let ssl_key_path = config.ssl_key_path.clone(); - - - tokio::spawn(async move { - // run with tls if ssl_enabled and cert and key path are set - if ssl_cert_path.is_some() && ssl_key_path.is_some() { - } - }) -} - async fn start_udp_tracker_server(config: &UdpTrackerConfig, tracker: Arc) -> JoinHandle<()> { let udp_server = UdpServer::new(tracker, config).await.unwrap_or_else(|e| { panic!("Could not start UDP server: {}", e); From 74649d4acb8a29c1641bd9fe5ec8947acaa9a69f Mon Sep 17 00:00:00 2001 From: Power2All Date: Tue, 15 Mar 2022 23:12:54 +0100 Subject: [PATCH 12/32] Trying to debug --- src/common.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common.rs b/src/common.rs index 0037f058f..e36013db0 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes}; +use log::info; pub const MAX_SCRAPE_TORRENTS: u8 = 74; pub const AUTH_KEY_LENGTH: usize = 32; @@ -132,6 +133,7 @@ impl PeerId { pub fn to_string(&self) -> String { let mut buffer = [0u8; 20]; let bytes_out = binascii::bin2hex(&self.0, &mut buffer).ok().unwrap(); + info!("{:#?}", bytes_out); String::from(std::str::from_utf8(bytes_out).unwrap()) } } From d3e96881ff9f8e8f4ac2305b0286d6dc5af98466 Mon Sep 17 00:00:00 2001 From: Power2All Date: Tue, 15 Mar 2022 23:30:20 +0100 Subject: [PATCH 13/32] Solving a bug --- src/common.rs | 3 --- src/torrust_http_tracker/filters.rs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/common.rs b/src/common.rs index e36013db0..92bd1d1e9 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,6 +1,5 @@ use serde::{Deserialize, Serialize}; use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes}; -use log::info; pub const MAX_SCRAPE_TORRENTS: u8 = 74; pub const AUTH_KEY_LENGTH: usize = 32; @@ -85,7 +84,6 @@ impl serde::ser::Serialize for InfoHash { let mut buffer = [0u8; 40]; let bytes_out = binascii::bin2hex(&self.0, &mut buffer).ok().unwrap(); let str_out = std::str::from_utf8(bytes_out).unwrap(); - serializer.serialize_str(str_out) } } @@ -133,7 +131,6 @@ impl PeerId { pub fn to_string(&self) -> String { let mut buffer = [0u8; 20]; let bytes_out = binascii::bin2hex(&self.0, &mut buffer).ok().unwrap(); - info!("{:#?}", bytes_out); String::from(std::str::from_utf8(bytes_out).unwrap()) } } diff --git a/src/torrust_http_tracker/filters.rs b/src/torrust_http_tracker/filters.rs index 0e2b364b5..1015b2b38 100644 --- a/src/torrust_http_tracker/filters.rs +++ b/src/torrust_http_tracker/filters.rs @@ -67,7 +67,7 @@ async fn peer_id(raw_query: String) -> WebResult { let peer_id_bytes = percent_encoding::percent_decode_str(raw_peer_id).collect::>(); // peer_id must be 20 bytes - if peer_id_bytes.len() > 20 { + if peer_id_bytes.len() != 20 { return Err(reject::custom(ServerError::InvalidPeerId)); } From f478f3b02d5e8e51fe6326b238fcb7e8d20e1c82 Mon Sep 17 00:00:00 2001 From: Power2All Date: Tue, 15 Mar 2022 23:45:15 +0100 Subject: [PATCH 14/32] Trying to fix again a bug --- src/common.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/common.rs b/src/common.rs index 92bd1d1e9..4d2f5ec71 100644 --- a/src/common.rs +++ b/src/common.rs @@ -130,8 +130,12 @@ pub struct PeerId(pub [u8; 20]); impl PeerId { pub fn to_string(&self) -> String { let mut buffer = [0u8; 20]; - let bytes_out = binascii::bin2hex(&self.0, &mut buffer).ok().unwrap(); - String::from(std::str::from_utf8(bytes_out).unwrap()) + let bytes_out = binascii::bin2hex(&self.0, &mut buffer).ok(); + return if let Some(bytes_out) = bytes_out { + String::from(std::str::from_utf8(bytes_out).unwrap()) + } else { + "".to_string() + } } } From b0417a3d5cf7074874dd13a9e06dc6fa7c8383ca Mon Sep 17 00:00:00 2001 From: Power2All Date: Wed, 16 Mar 2022 07:37:32 +0100 Subject: [PATCH 15/32] Prevent error when no peers with udp --- src/torrust_udp_tracker/handlers.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/torrust_udp_tracker/handlers.rs b/src/torrust_udp_tracker/handlers.rs index 84cba3f45..ea9043697 100644 --- a/src/torrust_udp_tracker/handlers.rs +++ b/src/torrust_udp_tracker/handlers.rs @@ -85,7 +85,11 @@ pub async fn handle_announce(remote_addr: SocketAddr, announce_request: &Announc // get all peers excluding the client_addr let peers = match tracker.get_torrent_peers(&wrapped_announce_request.info_hash, &peer.peer_addr).await { Some(v) => v, - None => { return Err(ServerError::NoPeersFound); } + None => { + // return Err(ServerError::NoPeersFound); + let return_data: Vec = Vec::new(); + return_data + } }; Ok(Response::from(AnnounceResponse { From f6eb8533909678c6f5547a9d1a95b26b81978c68 Mon Sep 17 00:00:00 2001 From: Power2All Date: Wed, 16 Mar 2022 09:20:17 +0100 Subject: [PATCH 16/32] First version of stats tracking, but needs improvements --- src/http_api_server.rs | 84 +++++++++++++++++++++++++++- src/torrust_http_tracker/filters.rs | 17 +++++- src/torrust_http_tracker/handlers.rs | 31 +++++++++- src/torrust_http_tracker/request.rs | 2 + src/torrust_udp_tracker/handlers.rs | 38 +++++++++++-- src/tracker.rs | 41 +++++++++++++- 6 files changed, 203 insertions(+), 10 deletions(-) diff --git a/src/http_api_server.rs b/src/http_api_server.rs index f87f460a4..a6bee4a14 100644 --- a/src/http_api_server.rs +++ b/src/http_api_server.rs @@ -23,6 +23,26 @@ struct Torrent<'a> { peers: Option>, } +#[derive(Serialize)] +struct Stats { + torrents: u32, + seeders: u32, + completed: u32, + leechers: u32, + tcp4_connections_handled: u32, + tcp4_announces_handled: u32, + tcp4_scrapes_handled: u32, + tcp6_connections_handled: u32, + tcp6_announces_handled: u32, + tcp6_scrapes_handled: u32, + udp4_connections_handled: u32, + udp4_announces_handled: u32, + udp4_scrapes_handled: u32, + udp6_connections_handled: u32, + udp6_announces_handled: u32, + udp6_scrapes_handled: u32, +} + #[derive(Serialize, Debug)] #[serde(tag = "status", rename_all = "snake_case")] enum ActionStatus<'a> { @@ -64,13 +84,13 @@ fn authenticate(tokens: HashMap) -> impl Filter) -> Server + Clone + Send + Sync + 'static> { // GET /api/torrents?offset=:u32&limit=:u32 // View torrent list - let t1 = tracker.clone(); + let api_torrents = tracker.clone(); let view_torrent_list = filters::method::get() .and(filters::path::path("torrents")) .and(filters::path::end()) .and(filters::query::query()) .map(move |limits| { - let tracker = t1.clone(); + let tracker = api_torrents.clone(); (limits, tracker) }) .and_then(|(limits, tracker): (TorrentInfoQuery, Arc)| { @@ -99,6 +119,65 @@ pub fn build_server(tracker: Arc) -> Server| { + async move { + let mut results = Stats{ + torrents: 0, + seeders: 0, + completed: 0, + leechers: 0, + tcp4_connections_handled: 0, + tcp4_announces_handled: 0, + tcp4_scrapes_handled: 0, + tcp6_connections_handled: 0, + tcp6_announces_handled: 0, + tcp6_scrapes_handled: 0, + udp4_connections_handled: 0, + udp4_announces_handled: 0, + udp4_scrapes_handled: 0, + udp6_connections_handled: 0, + udp6_announces_handled: 0, + udp6_scrapes_handled: 0 + }; + let db = tracker.get_torrents().await; + let _: Vec<_> = db + .iter() + .map(|(_info_hash, torrent_entry)| { + let (seeders, completed, leechers) = torrent_entry.get_stats(); + results.seeders += seeders; + results.completed += completed; + results.leechers += leechers; + results.torrents += 1; + }) + .collect(); + let stats = tracker.get_stats().await; + results.tcp4_connections_handled = stats.tcp4_connections_handled as u32; + results.tcp4_announces_handled = stats.tcp4_announces_handled as u32; + results.tcp4_scrapes_handled = stats.tcp4_scrapes_handled as u32; + results.tcp6_connections_handled = stats.tcp6_connections_handled as u32; + results.tcp6_announces_handled = stats.tcp6_announces_handled as u32; + results.tcp6_scrapes_handled = stats.tcp6_scrapes_handled as u32; + results.udp4_connections_handled = stats.udp4_connections_handled as u32; + results.udp4_announces_handled = stats.udp4_announces_handled as u32; + results.udp4_scrapes_handled = stats.udp4_scrapes_handled as u32; + results.udp6_connections_handled = stats.udp6_connections_handled as u32; + results.udp6_announces_handled = stats.udp6_announces_handled as u32; + results.udp6_scrapes_handled = stats.udp6_scrapes_handled as u32; + + Result::<_, warp::reject::Rejection>::Ok(reply::json(&results)) + } + }); + // GET /api/torrent/:info_hash // View torrent info let t2 = tracker.clone(); @@ -219,6 +298,7 @@ pub fn build_server(tracker: Arc) -> Server impl Filter + Clone { warp::any() .and(with_info_hash()) + .and(warp::addr::remote()) + .and(warp::header::optional::("X-Forwarded-For")) .and_then(scrape_request) } /// Parse ScrapeRequest from InfoHash -async fn scrape_request(info_hashes: Vec) -> WebResult { +async fn scrape_request(info_hashes: Vec, remote_addr: Option, forwarded_for: Option) -> WebResult { + if remote_addr.is_none() { return Err(reject::custom(ServerError::AddressNotFound)) } + + // get first forwarded ip + let forwarded_ip = match forwarded_for { + None => None, + Some(forwarded_for_str) => { + forwarded_for_str.split(",").next() + .and_then(|ip_str| IpAddr::from_str(ip_str).ok()) + } + }; + Ok(ScrapeRequest { info_hashes, + remote_addr: remote_addr.unwrap(), + forwarded_ip, }) } diff --git a/src/torrust_http_tracker/handlers.rs b/src/torrust_http_tracker/handlers.rs index 586c520f8..f410a7403 100644 --- a/src/torrust_http_tracker/handlers.rs +++ b/src/torrust_http_tracker/handlers.rs @@ -29,7 +29,7 @@ pub async fn authenticate(info_hash: &InfoHash, auth_key: &Option, trac } /// Handle announce request -pub async fn handle_announce(announce_request: AnnounceRequest, auth_key: Option, tracker: Arc,) -> WebResult { +pub async fn handle_announce(announce_request: AnnounceRequest, auth_key: Option, tracker: Arc) -> WebResult { if let Err(e) = authenticate(&announce_request.info_hash, &auth_key, tracker.clone()).await { return Err(reject::custom(e)) } @@ -52,12 +52,23 @@ pub async fn handle_announce(announce_request: AnnounceRequest, auth_key: Option if peers.is_none() { return Err(reject::custom(ServerError::NoPeersFound)) } // success response + let tracker_copy = tracker.clone(); + tokio::spawn(async move { + let mut status_writer = tracker_copy.set_stats().await; + if peer_ip.is_ipv4() { + status_writer.tcp4_connections_handled += 1; + status_writer.tcp4_announces_handled += 1; + } else { + status_writer.tcp6_connections_handled += 1; + status_writer.tcp6_announces_handled += 1; + } + }); let announce_interval = tracker.config.announce_interval; send_announce_response(&announce_request, torrent_stats, peers.unwrap(), announce_interval) } /// Handle scrape request -pub async fn handle_scrape(scrape_request: ScrapeRequest, auth_key: Option, tracker: Arc,) -> WebResult { +pub async fn handle_scrape(scrape_request: ScrapeRequest, auth_key: Option, tracker: Arc) -> WebResult { let mut files: HashMap = HashMap::new(); let db = tracker.get_torrents().await; @@ -80,6 +91,22 @@ pub async fn handle_scrape(scrape_request: ScrapeRequest, auth_key: Option scrape_request.forwarded_ip.unwrap(), + false => scrape_request.remote_addr.ip() + }; + + let tracker_copy = tracker.clone(); + tokio::spawn(async move { + let mut status_writer = tracker_copy.set_stats().await; + if ip.is_ipv4() { + status_writer.tcp4_connections_handled += 1; + status_writer.tcp4_scrapes_handled += 1; + } else { + status_writer.tcp6_connections_handled += 1; + status_writer.tcp6_scrapes_handled += 1; + } + }); send_scrape_response(files) } diff --git a/src/torrust_http_tracker/request.rs b/src/torrust_http_tracker/request.rs index 6630e5660..ac0e9602f 100644 --- a/src/torrust_http_tracker/request.rs +++ b/src/torrust_http_tracker/request.rs @@ -30,4 +30,6 @@ pub struct AnnounceRequest { pub struct ScrapeRequest { pub info_hashes: Vec, + pub forwarded_ip: Option, + pub remote_addr: SocketAddr, } diff --git a/src/torrust_udp_tracker/handlers.rs b/src/torrust_udp_tracker/handlers.rs index ea9043697..d51dd7995 100644 --- a/src/torrust_udp_tracker/handlers.rs +++ b/src/torrust_udp_tracker/handlers.rs @@ -52,18 +52,18 @@ pub async fn handle_packet(remote_addr: SocketAddr, payload: &[u8], tracker: Arc pub async fn handle_request(request: Request, remote_addr: SocketAddr, tracker: Arc) -> Result { match request { Request::Connect(connect_request) => { - handle_connect(remote_addr, &connect_request).await + handle_connect(remote_addr, &connect_request, tracker).await } Request::Announce(announce_request) => { handle_announce(remote_addr, &announce_request, tracker).await } Request::Scrape(scrape_request) => { - handle_scrape(&scrape_request, tracker).await + handle_scrape(remote_addr, &scrape_request, tracker).await } } } -pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest) -> Result { +pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, tracker: Arc) -> Result { let connection_id = get_connection_id(&remote_addr); let response = Response::from(ConnectResponse { @@ -71,6 +71,16 @@ pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest) - connection_id, }); + let tracker_copy = tracker.clone(); + tokio::spawn(async move { + let mut status_writer = tracker_copy.set_stats().await; + if remote_addr.is_ipv4() { + status_writer.udp4_connections_handled += 1; + } else { + status_writer.udp6_connections_handled += 1; + } + }); + Ok(response) } @@ -92,6 +102,16 @@ pub async fn handle_announce(remote_addr: SocketAddr, announce_request: &Announc } }; + let tracker_copy = tracker.clone(); + tokio::spawn(async move { + let mut status_writer = tracker_copy.set_stats().await; + if remote_addr.is_ipv4() { + status_writer.udp4_announces_handled += 1; + } else { + status_writer.udp6_announces_handled += 1; + } + }); + Ok(Response::from(AnnounceResponse { transaction_id: wrapped_announce_request.announce_request.transaction_id, announce_interval: AnnounceInterval(tracker.config.announce_interval as i32), @@ -105,7 +125,7 @@ pub async fn handle_announce(remote_addr: SocketAddr, announce_request: &Announc })) } -pub async fn handle_scrape(request: &ScrapeRequest, tracker: Arc) -> Result { +pub async fn handle_scrape(remote_addr: SocketAddr, request: &ScrapeRequest, tracker: Arc) -> Result { let db = tracker.get_torrents().await; let mut torrent_stats: Vec = Vec::new(); @@ -137,6 +157,16 @@ pub async fn handle_scrape(request: &ScrapeRequest, tracker: Arc torrent_stats.push(scrape_entry); } + let tracker_copy = tracker.clone(); + tokio::spawn(async move { + let mut status_writer = tracker_copy.set_stats().await; + if remote_addr.is_ipv4() { + status_writer.udp4_scrapes_handled += 1; + } else { + status_writer.udp6_scrapes_handled += 1; + } + }); + Ok(Response::from(ScrapeResponse { transaction_id: request.transaction_id, torrent_stats diff --git a/src/tracker.rs b/src/tracker.rs index 3a717f239..e25e2d803 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use serde; use std::borrow::Cow; use std::collections::BTreeMap; -use tokio::sync::RwLock; +use tokio::sync::{RwLock, RwLockWriteGuard}; use crate::common::{AnnounceEventDef, InfoHash, NumberOfBytesDef, PeerId}; use std::net::{IpAddr, SocketAddr}; use crate::{Configuration, key_manager, MAX_SCRAPE_TORRENTS}; @@ -236,10 +236,27 @@ pub enum TorrentError { InvalidInfoHash, } +#[derive(Debug)] +pub struct TrackerStats { + pub tcp4_connections_handled: u64, + pub tcp4_announces_handled: u64, + pub tcp4_scrapes_handled: u64, + pub tcp6_connections_handled: u64, + pub tcp6_announces_handled: u64, + pub tcp6_scrapes_handled: u64, + pub udp4_connections_handled: u64, + pub udp4_announces_handled: u64, + pub udp4_scrapes_handled: u64, + pub udp6_connections_handled: u64, + pub udp6_announces_handled: u64, + pub udp6_scrapes_handled: u64, +} + pub struct TorrentTracker { pub config: Arc, torrents: tokio::sync::RwLock>, database: SqliteDatabase, + stats: tokio::sync::RwLock, } impl TorrentTracker { @@ -252,6 +269,20 @@ impl TorrentTracker { config, torrents: RwLock::new(std::collections::BTreeMap::new()), database, + stats: RwLock::new(TrackerStats { + tcp4_connections_handled: 0, + tcp4_announces_handled: 0, + tcp4_scrapes_handled: 0, + tcp6_connections_handled: 0, + tcp6_announces_handled: 0, + tcp6_scrapes_handled: 0, + udp4_connections_handled: 0, + udp4_announces_handled: 0, + udp4_scrapes_handled: 0, + udp6_connections_handled: 0, + udp6_announces_handled: 0, + udp6_scrapes_handled: 0, + }), } } @@ -400,6 +431,14 @@ impl TorrentTracker { self.torrents.read().await } + pub async fn set_stats(&self) -> RwLockWriteGuard<'_, TrackerStats> { + self.stats.write().await + } + + pub async fn get_stats(&self) -> tokio::sync::RwLockReadGuard<'_, TrackerStats> { + self.stats.read().await + } + // remove torrents without peers pub async fn cleanup_torrents(&self) { debug!("Cleaning torrents.."); From 25e9476263b196377cc8a526577655056bdbbff4 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Wed, 16 Mar 2022 16:15:11 +0100 Subject: [PATCH 17/32] refactor: replace error message on empty peers with empty vec --- src/torrust_http_tracker/handlers.rs | 4 ++-- src/torrust_udp_tracker/handlers.rs | 12 ++++-------- src/torrust_udp_tracker/server.rs | 1 + src/tracker.rs | 8 +++----- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/torrust_http_tracker/handlers.rs b/src/torrust_http_tracker/handlers.rs index f410a7403..e56fe1bd0 100644 --- a/src/torrust_http_tracker/handlers.rs +++ b/src/torrust_http_tracker/handlers.rs @@ -49,7 +49,7 @@ pub async fn handle_announce(announce_request: AnnounceRequest, auth_key: Option let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&announce_request.info_hash, &peer).await; // get all peers excluding the client_addr let peers = tracker.get_torrent_peers(&announce_request.info_hash, &peer.peer_addr).await; - if peers.is_none() { return Err(reject::custom(ServerError::NoPeersFound)) } + //if peers.is_none() { return Err(reject::custom(ServerError::NoPeersFound)) } // success response let tracker_copy = tracker.clone(); @@ -64,7 +64,7 @@ pub async fn handle_announce(announce_request: AnnounceRequest, auth_key: Option } }); let announce_interval = tracker.config.announce_interval; - send_announce_response(&announce_request, torrent_stats, peers.unwrap(), announce_interval) + send_announce_response(&announce_request, torrent_stats, peers, announce_interval) } /// Handle scrape request diff --git a/src/torrust_udp_tracker/handlers.rs b/src/torrust_udp_tracker/handlers.rs index d51dd7995..6f1a6f685 100644 --- a/src/torrust_udp_tracker/handlers.rs +++ b/src/torrust_udp_tracker/handlers.rs @@ -91,16 +91,12 @@ pub async fn handle_announce(remote_addr: SocketAddr, announce_request: &Announc let peer = TorrentPeer::from_udp_announce_request(&wrapped_announce_request.announce_request, remote_addr.ip(), tracker.config.get_ext_ip()); + //let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&wrapped_announce_request.info_hash, &peer).await; + let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&wrapped_announce_request.info_hash, &peer).await; + // get all peers excluding the client_addr - let peers = match tracker.get_torrent_peers(&wrapped_announce_request.info_hash, &peer.peer_addr).await { - Some(v) => v, - None => { - // return Err(ServerError::NoPeersFound); - let return_data: Vec = Vec::new(); - return_data - } - }; + let peers = tracker.get_torrent_peers(&wrapped_announce_request.info_hash, &peer.peer_addr).await; let tracker_copy = tracker.clone(); tokio::spawn(async move { diff --git a/src/torrust_udp_tracker/server.rs b/src/torrust_udp_tracker/server.rs index 0e24fd81c..9e001347b 100644 --- a/src/torrust_udp_tracker/server.rs +++ b/src/torrust_udp_tracker/server.rs @@ -28,6 +28,7 @@ impl UdpServer { if let Ok((valid_bytes, remote_addr)) = self.socket.recv_from(&mut data).await { let data = &data[..valid_bytes]; debug!("Received {} bytes from {}", data.len(), remote_addr); + debug!("{:?}", data); let response = handle_packet(remote_addr, data, self.tracker.clone()).await; self.send_response(remote_addr, response).await; } diff --git a/src/tracker.rs b/src/tracker.rs index e25e2d803..f6a162782 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -376,14 +376,12 @@ impl TorrentTracker { &self, info_hash: &InfoHash, peer_addr: &SocketAddr - ) -> Option> { + ) -> Vec { let read_lock = self.torrents.read().await; match read_lock.get(info_hash) { - None => { - None - } + None => vec![], Some(entry) => { - Some(entry.get_peers(Some(peer_addr))) + entry.get_peers(Some(peer_addr)) } } } From 5a07d89a09ad49220316f3976f102a2eacd27f43 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Wed, 16 Mar 2022 16:51:09 +0100 Subject: [PATCH 18/32] refactor: updated to newest aquatic_udp_protocol --- Cargo.lock | 10 ++++-- Cargo.toml | 3 +- src/torrust_udp_tracker/handlers.rs | 52 +++++++++++++++++++++-------- src/torrust_udp_tracker/server.rs | 9 ++--- 4 files changed, 50 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ba818230..f56a7f003 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,10 +14,10 @@ dependencies = [ [[package]] name = "aquatic_udp_protocol" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c3026324bc5073042edfbc47699cc60c1a9cf24849f5b104c18f98d2ad4175" +source = "git+https://github.com/greatest-ape/aquatic#065e007ede84de20f20983b4b504471bbda2fdf2" dependencies = [ "byteorder", + "either", ] [[package]] @@ -263,6 +263,12 @@ dependencies = [ "generic-array 0.14.4", ] +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "enum-as-inner" version = "0.3.3" diff --git a/Cargo.toml b/Cargo.toml index 01e3dbb76..77e478988 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,5 +32,4 @@ env_logger = "0.9.0" config = "0.11" derive_more = "0.99" thiserror = "1.0" - -aquatic_udp_protocol = "0.1.0" +aquatic_udp_protocol = { git = "https://github.com/greatest-ape/aquatic" } diff --git a/src/torrust_udp_tracker/handlers.rs b/src/torrust_udp_tracker/handlers.rs index 6f1a6f685..0aeb804d7 100644 --- a/src/torrust_udp_tracker/handlers.rs +++ b/src/torrust_udp_tracker/handlers.rs @@ -1,4 +1,4 @@ -use std::net::SocketAddr; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::sync::Arc; use aquatic_udp_protocol::{AnnounceInterval, AnnounceRequest, AnnounceResponse, ConnectRequest, ConnectResponse, ErrorResponse, NumberOfDownloads, NumberOfPeers, Port, Request, Response, ResponsePeer, ScrapeRequest, ScrapeResponse, TorrentScrapeStatistics, TransactionId}; use crate::{InfoHash, MAX_SCRAPE_TORRENTS, TorrentError, TorrentPeer, TorrentTracker}; @@ -108,17 +108,43 @@ pub async fn handle_announce(remote_addr: SocketAddr, announce_request: &Announc } }); - Ok(Response::from(AnnounceResponse { - transaction_id: wrapped_announce_request.announce_request.transaction_id, - announce_interval: AnnounceInterval(tracker.config.announce_interval as i32), - leechers: NumberOfPeers(torrent_stats.leechers as i32), - seeders: NumberOfPeers(torrent_stats.seeders as i32), - peers: peers.iter().map(|peer| - ResponsePeer { - ip_address: peer.peer_addr.ip(), - port: Port(peer.peer_addr.port()) - }).collect() - })) + let announce_response = if remote_addr.is_ipv4() { + Response::from(AnnounceResponse { + transaction_id: wrapped_announce_request.announce_request.transaction_id, + announce_interval: AnnounceInterval(tracker.config.announce_interval as i32), + leechers: NumberOfPeers(torrent_stats.leechers as i32), + seeders: NumberOfPeers(torrent_stats.seeders as i32), + peers: peers.iter() + .filter_map(|peer| if let IpAddr::V4(ip) = peer.peer_addr.ip() { + Some(ResponsePeer:: { + ip_address: ip, + port: Port(peer.peer_addr.port()) + }) + } else { + None + } + ).collect() + }) + } else { + Response::from(AnnounceResponse { + transaction_id: wrapped_announce_request.announce_request.transaction_id, + announce_interval: AnnounceInterval(tracker.config.announce_interval as i32), + leechers: NumberOfPeers(torrent_stats.leechers as i32), + seeders: NumberOfPeers(torrent_stats.seeders as i32), + peers: peers.iter() + .filter_map(|peer| if let IpAddr::V6(ip) = peer.peer_addr.ip() { + Some(ResponsePeer:: { + ip_address: ip, + port: Port(peer.peer_addr.port()) + }) + } else { + None + } + ).collect() + }) + }; + + Ok(announce_response) } pub async fn handle_scrape(remote_addr: SocketAddr, request: &ScrapeRequest, tracker: Arc) -> Result { @@ -171,5 +197,5 @@ pub async fn handle_scrape(remote_addr: SocketAddr, request: &ScrapeRequest, tra fn handle_error(e: ServerError, transaction_id: TransactionId) -> Response { let message = e.to_string(); - Response::from(ErrorResponse { transaction_id, message }) + Response::from(ErrorResponse { transaction_id, message: message.into() }) } diff --git a/src/torrust_udp_tracker/server.rs b/src/torrust_udp_tracker/server.rs index 9e001347b..2f472ef54 100644 --- a/src/torrust_udp_tracker/server.rs +++ b/src/torrust_udp_tracker/server.rs @@ -1,7 +1,7 @@ use std::io::Cursor; use std::net::{SocketAddr}; use std::sync::Arc; -use aquatic_udp_protocol::{IpVersion, Response}; +use aquatic_udp_protocol::{Response}; use log::debug; use tokio::net::UdpSocket; use crate::{TorrentTracker, UdpTrackerConfig}; @@ -41,12 +41,7 @@ impl UdpServer { let buffer = vec![0u8; MAX_PACKET_SIZE]; let mut cursor = Cursor::new(buffer); - let ip_version = match remote_addr { - SocketAddr::V4(_) => IpVersion::IPv4, - SocketAddr::V6(_) => IpVersion::IPv6 - }; - - match response.write(&mut cursor, ip_version) { + match response.write(&mut cursor) { Ok(_) => { let position = cursor.position() as usize; let inner = cursor.get_ref(); From 9d6db191de7a7646d7e974dd2c6047081c559119 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Wed, 16 Mar 2022 17:10:54 +0100 Subject: [PATCH 19/32] fix: no more panic on http scrape on_reverse_proxy but no forwarded_ip --- src/torrust_http_tracker/handlers.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/torrust_http_tracker/handlers.rs b/src/torrust_http_tracker/handlers.rs index e56fe1bd0..0d7277226 100644 --- a/src/torrust_http_tracker/handlers.rs +++ b/src/torrust_http_tracker/handlers.rs @@ -91,6 +91,10 @@ pub async fn handle_scrape(scrape_request: ScrapeRequest, auth_key: Option scrape_request.forwarded_ip.unwrap(), false => scrape_request.remote_addr.ip() From b34f56405824ee85bfbef9c942214934adf4979e Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Wed, 16 Mar 2022 17:36:27 +0100 Subject: [PATCH 20/32] fix: now able to get ipv6 peers from ipv6 remote_addr --- src/tracker.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/tracker.rs b/src/tracker.rs index f6a162782..af7aa3455 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -146,7 +146,17 @@ impl TorrentEntry { for (_, peer) in self .peers .iter() - .filter(|e| e.1.peer_addr.is_ipv4()) + .filter(|e| match remote_addr { + // don't filter on ip_version + None => true, + // filter out different ip_version from remote_addr + Some(remote_address) => { + match e.1.peer_addr.ip() { + IpAddr::V4(_) => { remote_address.is_ipv4() } + IpAddr::V6(_) => { remote_address.is_ipv6() } + } + } + }) .take(MAX_SCRAPE_TORRENTS as usize) { From ab605455ddb0861d4059dc60e4f37c384e5b3527 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Wed, 16 Mar 2022 21:36:51 +0100 Subject: [PATCH 21/32] refactor: refactored persistent torrent loading/saving --- src/database.rs | 38 ++++++++++++++++---------------------- src/main.rs | 39 ++++++++++++++++++--------------------- src/tracker.rs | 36 ++++++++++++++++++++++-------------- 3 files changed, 56 insertions(+), 57 deletions(-) diff --git a/src/database.rs b/src/database.rs index 827c7dbed..82d26d6d8 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,11 +1,11 @@ -use crate::{InfoHash, AUTH_KEY_LENGTH, TorrentTracker}; +use std::collections::BTreeMap; +use crate::{InfoHash, AUTH_KEY_LENGTH, TorrentEntry}; use log::debug; use r2d2_sqlite::{SqliteConnectionManager, rusqlite}; use r2d2::{Pool}; use r2d2_sqlite::rusqlite::NO_PARAMS; use crate::key_manager::AuthKey; use std::str::FromStr; -use std::sync::Arc; pub struct SqliteDatabase { pool: Pool @@ -73,40 +73,34 @@ impl SqliteDatabase { } } - pub async fn load_persistent_torrent_data(&self, tracker: Arc) -> Result { - let tracker_copy = tracker.clone(); + pub async fn load_persistent_torrent_data(&self) -> Result, rusqlite::Error> { let conn = self.pool.get().unwrap(); let mut stmt = conn.prepare("SELECT info_hash, completed FROM torrents")?; - let info_hash_iter = stmt.query_map(NO_PARAMS, |row| { - let info_hash: String = row.get(0)?; - let info_hash_converted = InfoHash::from_str(&info_hash).unwrap(); + let torrent_iter = stmt.query_map(NO_PARAMS, |row| { + let info_hash_string: String = row.get(0)?; + let info_hash = InfoHash::from_str(&info_hash_string).unwrap(); let completed: u32 = row.get(1)?; - Ok((info_hash_converted, completed)) + Ok((info_hash, completed)) })?; - for info_hash_item in info_hash_iter { - let (info_hash, completed): (InfoHash, u32) = info_hash_item.unwrap(); - tracker_copy.add_torrent(&info_hash, 0u32, completed, 0u32).await; - } + let torrents: Vec<(InfoHash, u32)> = torrent_iter.filter_map(|x| x.ok() ).collect(); - Ok(true) + Ok(torrents) } - pub async fn save_persistent_torrent_data(&self, tracker: Arc) -> Result { - let tracker_copy = tracker.clone(); + pub async fn save_persistent_torrent_data(&self, torrents: &BTreeMap) -> Result<(), rusqlite::Error> { let mut conn = self.pool.get().unwrap(); - let db = tracker_copy.get_torrents().await; let db_transaction = conn.transaction()?; - let _: Vec<_> = db - .iter() - .map(|(info_hash, torrent_entry)| { + + for (info_hash, torrent_entry) in torrents { let (_seeders, completed, _leechers) = torrent_entry.get_stats(); let _ = db_transaction.execute("INSERT OR REPLACE INTO torrents (info_hash, completed) VALUES (?, ?)", &[info_hash.to_string(), completed.to_string()]); - }) - .collect(); + } + let _ = db_transaction.commit(); - Ok(true) + + Ok(()) } pub async fn get_info_hash_from_whitelist(&self, info_hash: &str) -> Result { diff --git a/src/main.rs b/src/main.rs index bd5ff3b40..5829d3edc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,11 +17,17 @@ async fn main() { logging::setup_logging(&config); // the singleton torrent tracker that gets passed to the HTTP and UDP server - let tracker = Arc::new(TorrentTracker::new(config.clone())); + let tracker = Arc::new(TorrentTracker::new(config.clone()).unwrap_or_else(|e| { + panic!("{}", e) + })); - // Load torrents if enabled + // load persistent torrents if enabled if config.persistence { - load_torrents_into_memory(tracker.clone()).await; + info!("Loading persistent torrents into memory..."); + if tracker.load_torrents().await.is_err() { + panic!("Could not load persistent torrents.") + }; + info!("Persistent torrents loaded."); } // start torrent cleanup job (periodically removes old peers) @@ -48,27 +54,18 @@ async fn main() { // handle the signals here let ctrl_c = tokio::signal::ctrl_c(); tokio::select! { - _ = ctrl_c => { info!("Torrust shutting down..") } - } - - // Save torrents if enabled - if config.persistence { - save_torrents_into_memory(tracker.clone()).await; + _ = ctrl_c => { + info!("Torrust shutting down.."); + // Save torrents if enabled + if config.persistence { + info!("Saving torrents into SQL from memory..."); + let _ = tracker.save_torrents().await; + info!("Torrents saved"); + } + } } } -async fn load_torrents_into_memory(tracker: Arc) { - info!("Loading torrents from SQL into memory..."); - let _ = tracker.load_torrents(tracker.clone()).await; - info!("Torrents loaded"); -} - -async fn save_torrents_into_memory(tracker: Arc) { - info!("Saving torrents into SQL from memory..."); - let _ = tracker.save_torrents(tracker.clone()).await; - info!("Torrents saved"); -} - fn start_torrent_cleanup_job(config: Arc, tracker: Arc) -> Option> { let weak_tracker = std::sync::Arc::downgrade(&tracker); let interval = config.cleanup_interval.unwrap_or(600); diff --git a/src/tracker.rs b/src/tracker.rs index af7aa3455..58ddd70ab 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -10,7 +10,7 @@ use std::collections::btree_map::Entry; use crate::database::SqliteDatabase; use std::sync::Arc; use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes}; -use log::debug; +use log::{debug}; use crate::key_manager::AuthKey; use r2d2_sqlite::rusqlite; use crate::torrust_http_tracker::AnnounceRequest; @@ -270,12 +270,10 @@ pub struct TorrentTracker { } impl TorrentTracker { - pub fn new(config: Arc) -> TorrentTracker { - let database = SqliteDatabase::new(&config.db_path).unwrap_or_else(|error| { - panic!("Could not create SQLite database. Reason: {}", error) - }); + pub fn new(config: Arc) -> Result { + let database = SqliteDatabase::new(&config.db_path)?; - TorrentTracker { + Ok(TorrentTracker { config, torrents: RwLock::new(std::collections::BTreeMap::new()), database, @@ -293,7 +291,7 @@ impl TorrentTracker { udp6_announces_handled: 0, udp6_scrapes_handled: 0, }), - } + }) } fn is_public(&self) -> bool { @@ -355,13 +353,20 @@ impl TorrentTracker { } // Loading the torrents into memory - pub async fn load_torrents(&self, tracker: Arc) -> Result { - self.database.load_persistent_torrent_data(tracker).await + pub async fn load_torrents(&self) -> Result<(), rusqlite::Error> { + let torrents = self.database.load_persistent_torrent_data().await?; + + for torrent in torrents { + self.add_torrent(torrent.0, 0, torrent.1, 0).await; + } + + Ok(()) } // Saving the torrents from memory - pub async fn save_torrents(&self, tracker: Arc) -> Result { - self.database.save_persistent_torrent_data(tracker).await + pub async fn save_torrents(&self) -> Result<(), rusqlite::Error> { + let torrents = self.torrents.read().await; + self.database.save_persistent_torrent_data(&*torrents).await } // Adding torrents is not relevant to public trackers. @@ -419,12 +424,15 @@ impl TorrentTracker { } } - pub async fn add_torrent(&self, info_hash: &InfoHash, seeders: u32, completed: u32, leechers: u32) -> TorrentStats { + pub async fn add_torrent(&self, info_hash: InfoHash, seeders: u32, completed: u32, leechers: u32) -> TorrentStats { let mut torrents = self.torrents.write().await; if !torrents.contains_key(&info_hash) { - let mut torrent_entry = TorrentEntry::new(); - torrent_entry.completed = completed; + let torrent_entry = TorrentEntry { + peers: Default::default(), + completed, + seeders + }; torrents.insert(info_hash.clone(), torrent_entry); } From 0e0df0e45d3a80a2e5d5a684cbfca527b9bb150f Mon Sep 17 00:00:00 2001 From: Power2All Date: Thu, 17 Mar 2022 11:10:30 +0100 Subject: [PATCH 22/32] Conflict fixed, added min interval to response, and a proper exiting of tokio spawn --- Cargo.lock | 472 +-------------------------- Cargo.toml | 2 - src/config.rs | 2 + src/main.rs | 12 +- src/torrust_http_tracker/handlers.rs | 5 +- src/torrust_http_tracker/response.rs | 3 + src/torrust_http_tracker/server.rs | 18 +- src/utils.rs | 27 ++ 8 files changed, 71 insertions(+), 470 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f56a7f003..7017da294 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,40 +26,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" -[[package]] -name = "async-trait" -version = "0.1.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "attohttpc" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb8867f378f33f78a811a8eb9bf108ad99430d7aad43315dd9319c827ef6247" -dependencies = [ - "http", - "log", - "url", - "wildmatch", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.0.1" @@ -201,22 +167,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" -[[package]] -name = "core-foundation" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" - [[package]] name = "cpufeatures" version = "0.2.1" @@ -226,12 +176,6 @@ dependencies = [ "libc", ] -[[package]] -name = "data-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" - [[package]] name = "derive_more" version = "0.99.17" @@ -269,47 +213,6 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" -[[package]] -name = "enum-as-inner" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "env_logger" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "external-ip" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2406194c5c4be3678bd7c1c128237ec589a6a3b7a3b05786971998bda7693c27" -dependencies = [ - "futures", - "http", - "hyper", - "hyper-tls", - "igd", - "log", - "rand 0.8.4", - "trust-dns-resolver", -] - [[package]] name = "fake-simd" version = "0.1.2" @@ -343,21 +246,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.0.0" @@ -370,13 +258,12 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.12" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9052a1a50244d8d5aa9bf55cbc2fb6f357c86cc52e46c62ed390a7180cf150" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ "futures-channel", "futures-core", - "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -385,9 +272,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.12" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d31b7ec7efab6eefc7c57233bb10b847986139d88cc2f5a02a1ae6871a1846" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", "futures-sink", @@ -395,71 +282,39 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65" - -[[package]] -name = "futures-executor" -version = "0.3.12" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e59fdc009a4b3096bf94f740a0f2424c082521f20a9b08c5c07c48d90fd9b9" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-io" -version = "0.3.12" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28be053525281ad8259d47e4de5de657b25e7bac113458555bb4b70bc6870500" - -[[package]] -name = "futures-macro" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c287d25add322d9f9abdcdc5927ca398917996600182178774032e9f8258fedd" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "syn", -] +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" [[package]] name = "futures-sink" -version = "0.3.12" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf5c69029bda2e743fddd0582d1083951d65cc9539aebf8812f36c3491342d6" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" [[package]] name = "futures-task" -version = "0.3.12" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13de07eb8ea81ae445aca7b69f5f7bf15d7bf4912d8ca37d6645c77ae8a58d86" -dependencies = [ - "once_cell", -] +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" [[package]] name = "futures-util" -version = "0.3.12" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632a8cd0f2a4b3fdea1657f08bde063848c3bd00f9bbf6e256b8be78802e624b" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ - "futures-channel", "futures-core", - "futures-io", - "futures-macro", "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", - "proc-macro-hack", - "proc-macro-nested", "slab", ] @@ -554,15 +409,6 @@ dependencies = [ "http", ] -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "hermit-abi" version = "0.1.18" @@ -578,17 +424,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - [[package]] name = "http" version = "0.2.3" @@ -622,12 +457,6 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hyper" version = "0.14.2" @@ -652,19 +481,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "idna" version = "0.2.3" @@ -676,19 +492,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "igd" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4e7ee8b51e541486d7040883fe1f00e2a9954bcc24fd155b7e4f03ed4b93dd" -dependencies = [ - "attohttpc", - "log", - "rand 0.8.4", - "url", - "xmltree", -] - [[package]] name = "indexmap" version = "1.6.1" @@ -717,24 +520,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "ipconfig" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" -dependencies = [ - "socket2", - "widestring", - "winapi", - "winreg", -] - -[[package]] -name = "ipnet" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" - [[package]] name = "itoa" version = "0.4.7" @@ -818,12 +603,6 @@ dependencies = [ "linked-hash-map", ] -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "matches" version = "0.1.8" @@ -893,24 +672,6 @@ dependencies = [ "twoway", ] -[[package]] -name = "native-tls" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nom" version = "5.1.2" @@ -987,39 +748,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "openssl" -version = "0.10.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9facdb76fec0b73c406f125d44d86fdad818d66fef0531eec9233ca425ff4a" -dependencies = [ - "bitflags", - "cfg-if 1.0.0", - "foreign-types", - "libc", - "once_cell", - "openssl-sys", -] - -[[package]] -name = "openssl-probe" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" - -[[package]] -name = "openssl-sys" -version = "0.9.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1996d2d305e561b70d1ee0c53f1542833f4e1ac6ce9a6708b6ff2738ca67dc82" -dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "parking_lot" version = "0.11.1" @@ -1095,18 +823,6 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "proc-macro-nested" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" - [[package]] name = "proc-macro2" version = "1.0.24" @@ -1268,16 +984,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "resolv-conf" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" -dependencies = [ - "hostname", - "quick-error", -] - [[package]] name = "ring" version = "0.16.20" @@ -1349,16 +1055,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" -[[package]] -name = "schannel" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" -dependencies = [ - "lazy_static", - "winapi", -] - [[package]] name = "scheduled-thread-pool" version = "0.2.5" @@ -1390,29 +1086,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "security-framework" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19133a286e494cc3311c165c4676ccb1fd47bed45b55f9d71fbd784ad4cea6f8" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "semver" version = "1.0.4" @@ -1593,15 +1266,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "termcolor" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" version = "1.0.26" @@ -1678,16 +1342,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.22.0" @@ -1756,8 +1410,6 @@ dependencies = [ "chrono", "config", "derive_more", - "env_logger", - "external-ip", "fern", "hex", "log", @@ -1802,51 +1454,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "trust-dns-proto" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0d7f5db438199a6e2609debe3f69f808d074e0a2888ee0bccb45fe234d03f4" -dependencies = [ - "async-trait", - "cfg-if 1.0.0", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna", - "ipnet", - "lazy_static", - "log", - "rand 0.8.4", - "smallvec", - "thiserror", - "tinyvec", - "tokio", - "url", -] - -[[package]] -name = "trust-dns-resolver" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ad17b608a64bd0735e67bde16b0636f8aa8591f831a25d18443ed00a699770" -dependencies = [ - "cfg-if 1.0.0", - "futures-util", - "ipconfig", - "lazy_static", - "log", - "lru-cache", - "parking_lot", - "resolv-conf", - "smallvec", - "thiserror", - "tokio", - "trust-dns-proto", -] - [[package]] name = "try-lock" version = "0.2.3" @@ -1911,12 +1518,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" - [[package]] name = "unicode-xid" version = "0.2.1" @@ -2085,18 +1686,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "widestring" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" - -[[package]] -name = "wildmatch" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f44b95f62d34113cf558c93511ac93027e03e9c29a60dd0fd70e6e025c7270a" - [[package]] name = "winapi" version = "0.3.9" @@ -2113,45 +1702,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "winreg" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" -dependencies = [ - "winapi", -] - -[[package]] -name = "xml-rs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" - -[[package]] -name = "xmltree" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" -dependencies = [ - "xml-rs", -] - [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index 77e478988..f91dba1cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,11 +24,9 @@ log = {version = "0.4", features = ["release_max_level_info"]} fern = "0.6" chrono = "0.4" byteorder = "1" -external-ip = "4.1.0" r2d2_sqlite = "0.16.0" r2d2 = "0.8.8" rand = "0.8.4" -env_logger = "0.9.0" config = "0.11" derive_more = "0.99" thiserror = "1.0" diff --git a/src/config.rs b/src/config.rs index 3ec55f857..94b37464d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -56,6 +56,7 @@ pub struct Configuration { pub cleanup_peerless: bool, pub external_ip: Option, pub announce_interval: u32, + pub announce_interval_min: u32, pub peer_timeout: u32, pub on_reverse_proxy: bool, pub udp_trackers: Vec, @@ -137,6 +138,7 @@ impl Configuration { cleanup_peerless: true, external_ip: Some(String::from("0.0.0.0")), announce_interval: 120, + announce_interval_min: 120, peer_timeout: 900, on_reverse_proxy: false, udp_trackers: Vec::new(), diff --git a/src/main.rs b/src/main.rs index 5829d3edc..fbd64abff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ use std::net::SocketAddr; -use log::{info}; -use torrust_tracker::{http_api_server, Configuration, TorrentTracker, UdpServer, HttpTrackerConfig, UdpTrackerConfig, HttpApiConfig, logging}; use std::sync::Arc; +use log::info; use tokio::task::JoinHandle; +use torrust_tracker::{Configuration, http_api_server, HttpApiConfig, HttpTrackerConfig, logging, TorrentTracker, UdpServer, UdpTrackerConfig}; use torrust_tracker::torrust_http_tracker::server::HttpServer; #[tokio::main] @@ -83,7 +83,7 @@ fn start_torrent_cleanup_job(config: Arc, tracker: Arc) -> JoinHandle<()> { @@ -92,7 +92,11 @@ fn start_api_server(config: &HttpApiConfig, tracker: Arc) -> Joi tokio::spawn(async move { let server = http_api_server::build_server(tracker); - server.bind(bind_addr).await; + let _ = server.bind_with_graceful_shutdown(bind_addr, async move { + tokio::signal::ctrl_c() + .await + .expect("failed to listen to shutdown signal"); + }); }) } diff --git a/src/torrust_http_tracker/handlers.rs b/src/torrust_http_tracker/handlers.rs index 0d7277226..2fe216196 100644 --- a/src/torrust_http_tracker/handlers.rs +++ b/src/torrust_http_tracker/handlers.rs @@ -64,7 +64,7 @@ pub async fn handle_announce(announce_request: AnnounceRequest, auth_key: Option } }); let announce_interval = tracker.config.announce_interval; - send_announce_response(&announce_request, torrent_stats, peers, announce_interval) + send_announce_response(&announce_request, torrent_stats, peers, announce_interval, tracker.config.announce_interval_min) } /// Handle scrape request @@ -127,7 +127,7 @@ pub async fn handle_error(r: Rejection) -> std::result::Result, interval: u32) -> WebResult { +fn send_announce_response(announce_request: &AnnounceRequest, torrent_stats: TorrentStats, peers: Vec, interval: u32, interval_min: u32) -> WebResult { let http_peers: Vec = peers.iter().map(|peer| Peer { peer_id: peer.peer_id.to_string(), ip: peer.peer_addr.ip(), @@ -136,6 +136,7 @@ fn send_announce_response(announce_request: &AnnounceRequest, torrent_stats: Tor let res = AnnounceResponse { interval, + interval_min, complete: torrent_stats.seeders, incomplete: torrent_stats.leechers, peers: http_peers diff --git a/src/torrust_http_tracker/response.rs b/src/torrust_http_tracker/response.rs index a80ee27c5..dc6614dd4 100644 --- a/src/torrust_http_tracker/response.rs +++ b/src/torrust_http_tracker/response.rs @@ -14,6 +14,7 @@ pub struct Peer { #[derive(Serialize)] pub struct AnnounceResponse { pub interval: u32, + pub interval_min: u32, //pub tracker_id: String, pub complete: u32, pub incomplete: u32, @@ -45,6 +46,8 @@ impl AnnounceResponse { let mut bytes: Vec = Vec::new(); bytes.write(b"d8:intervali")?; bytes.write(&self.interval.to_string().as_bytes())?; + bytes.write(b"d12:min intervali")?; + bytes.write(&self.interval.to_string().as_bytes())?; bytes.write(b"e8:completei")?; bytes.write(&self.complete.to_string().as_bytes())?; bytes.write(b"e10:incompletei")?; diff --git a/src/torrust_http_tracker/server.rs b/src/torrust_http_tracker/server.rs index 90f8a84d0..69811b3d9 100644 --- a/src/torrust_http_tracker/server.rs +++ b/src/torrust_http_tracker/server.rs @@ -18,16 +18,26 @@ impl HttpServer { /// Start the HttpServer pub async fn start(&self, socket_addr: SocketAddr) { - warp::serve(routes(self.tracker.clone())) - .run(socket_addr).await; + let (_addr, server) = warp::serve(routes(self.tracker.clone())) + .bind_with_graceful_shutdown(socket_addr, async move { + tokio::signal::ctrl_c() + .await + .expect("failed to listen to shutdown signal"); + }); + tokio::task::spawn(server); } /// Start the HttpServer in TLS mode pub async fn start_tls(&self, socket_addr: SocketAddr, ssl_cert_path: &str, ssl_key_path: &str) { - warp::serve(routes(self.tracker.clone())) + let (_addr, server) = warp::serve(routes(self.tracker.clone())) .tls() .cert_path(ssl_cert_path) .key_path(ssl_key_path) - .run(socket_addr).await; + .bind_with_graceful_shutdown(socket_addr, async move { + tokio::signal::ctrl_c() + .await + .expect("failed to listen to shutdown signal"); + }); + tokio::task::spawn(server); } } diff --git a/src/utils.rs b/src/utils.rs index 5790e6067..e3a8302df 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,7 +2,9 @@ use std::net::SocketAddr; use std::time::SystemTime; use std::error::Error; use std::fmt::Write; +use std::io::Cursor; use aquatic_udp_protocol::ConnectionId; +use byteorder::{BigEndian, ReadBytesExt}; pub fn get_connection_id(remote_address: &SocketAddr) -> ConnectionId { match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) { @@ -29,3 +31,28 @@ pub fn url_encode_bytes(content: &[u8]) -> Result> { Ok(out) } + +// Function that will convert a small or big number into the smallest form of a byte array. +pub async fn convert_int_to_bytes(number: &u64) -> Vec { + let mut return_data: Vec = Vec::new(); + // return_data.extend(number.to_be_bytes().reverse()); + for i in 1..8 { + if number < &256u64.pow(i) { + let start: usize = 16usize - i as usize; + return_data.extend(number.to_be_bytes()[start..8].iter()); + return return_data; + } + } + return return_data; +} + +pub async fn convert_bytes_to_int(array: &Vec) -> u64 { + let mut array_fixed: Vec = Vec::new(); + let size = 8 - array.len(); + for _ in 0..size { + array_fixed.push(0); + } + array_fixed.extend(array); + let mut rdr = Cursor::new(array_fixed); + return rdr.read_u64::().unwrap(); +} From 37fe6fdc6138f55125e5677990ac9a0f4f376013 Mon Sep 17 00:00:00 2001 From: Power2All Date: Thu, 17 Mar 2022 11:34:13 +0100 Subject: [PATCH 23/32] Updated README to reflect changes --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9a85dddcd..77055a335 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,13 @@ Torrust Tracker is a lightweight but incredibly powerful and feature-rich BitTor ### Features -* [X] UDP server -* [X] HTTP and/or HTTPS (SSL) server -* [X] Multiple UDP and HTTP(S) blocks for socket binding possible +* [X] Multiple UDP server and HTTP(S) server blocks for socket binding possible * [X] Full IPv4 and IPv6 support for both UDP and HTTP(S) * [X] Private & Whitelisted mode * [X] Built-in API * [X] Torrent whitelisting * [X] Peer authentication using time-bound keys -* [ ] NewTrackOn check supported +* [X] newTrackon check supported for both HTTP, UDP, where IPv4 and IPv6 is properly handled * [X] SQLite3 Persistent loading and saving of the torrent hashes and completed count ### Implemented BEPs From 65834bd98171ee4f5fdf234523a76e92f8e306c3 Mon Sep 17 00:00:00 2001 From: Power2All Date: Thu, 17 Mar 2022 11:46:53 +0100 Subject: [PATCH 24/32] Version bump to 2.2.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7017da294..5994554bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1402,7 +1402,7 @@ dependencies = [ [[package]] name = "torrust-tracker" -version = "2.1.2" +version = "2.2.0" dependencies = [ "aquatic_udp_protocol", "binascii", diff --git a/Cargo.toml b/Cargo.toml index f91dba1cb..4570bd8f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "torrust-tracker" -version = "2.1.2" +version = "2.2.0" license = "AGPL-3.0" authors = ["Mick van Dijke "] description = "A feature rich BitTorrent tracker." From cd0473c31050b0686b2a3511efac929cbc921544 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Thu, 17 Mar 2022 02:24:54 +0100 Subject: [PATCH 25/32] refactor: optimized http (reverse proxy) ip determination --- src/torrust_http_tracker/filters.rs | 72 +++++++++++++++------------- src/torrust_http_tracker/handlers.rs | 58 +++++++++------------- src/torrust_http_tracker/request.rs | 8 ++-- src/torrust_http_tracker/routes.rs | 10 ++-- src/torrust_udp_tracker/handlers.rs | 1 + src/tracker.rs | 6 +-- 6 files changed, 75 insertions(+), 80 deletions(-) diff --git a/src/torrust_http_tracker/filters.rs b/src/torrust_http_tracker/filters.rs index 1e646ac35..9a79f228e 100644 --- a/src/torrust_http_tracker/filters.rs +++ b/src/torrust_http_tracker/filters.rs @@ -2,6 +2,7 @@ use std::convert::Infallible; use std::net::{IpAddr, SocketAddr}; use std::str::FromStr; use std::sync::Arc; +use log::debug; use warp::{Filter, reject, Rejection}; use crate::{InfoHash, MAX_SCRAPE_TORRENTS, PeerId, TorrentTracker}; use crate::key_manager::AuthKey; @@ -98,31 +99,51 @@ pub fn with_auth_key() -> impl Filter,), Error = Infa }) } +/// Check for PeerAddress +pub fn with_peer_addr(on_reverse_proxy: bool) -> impl Filter + Clone { + warp::addr::remote() + .and(warp::header::optional::("X-Forwarded-For")) + .map(move |remote_addr: Option, x_forwarded_for: Option| { + (on_reverse_proxy, remote_addr, x_forwarded_for) + }) + .and_then(peer_addr) +} + +/// Get PeerAddress from RemoteAddress or Forwarded +async fn peer_addr((on_reverse_proxy, remote_addr, x_forwarded_for): (bool, Option, Option)) -> WebResult { + if !on_reverse_proxy && remote_addr.is_none() { + return Err(reject::custom(ServerError::AddressNotFound)) + } + + if on_reverse_proxy && x_forwarded_for.is_none() { + return Err(reject::custom(ServerError::AddressNotFound)) + } + + match on_reverse_proxy { + true => { + IpAddr::from_str(x_forwarded_for.as_ref().unwrap()).or_else(|e| { + debug!("{}", e); + Err(reject::custom(ServerError::AddressNotFound)) + }) + }, + false => Ok(remote_addr.unwrap().ip()) + } +} + /// Check for AnnounceRequest -pub fn with_announce_request() -> impl Filter + Clone { +pub fn with_announce_request(on_reverse_proxy: bool) -> impl Filter + Clone { warp::filters::query::query::() .and(with_info_hash()) .and(with_peer_id()) - .and(warp::addr::remote()) - .and(warp::header::optional::("X-Forwarded-For")) + .and(with_peer_addr(on_reverse_proxy)) .and_then(announce_request) } /// Parse AnnounceRequest from raw AnnounceRequestQuery, InfoHash and Option -async fn announce_request(announce_request_query: AnnounceRequestQuery, info_hashes: Vec, peer_id: PeerId, remote_addr: Option, forwarded_for: Option) -> WebResult { - // get first forwarded ip - let forwarded_ip = match forwarded_for { - None => None, - Some(forwarded_for_str) => { - forwarded_for_str.split(",").next() - .and_then(|ip_str| IpAddr::from_str(ip_str).ok()) - } - }; - +async fn announce_request(announce_request_query: AnnounceRequestQuery, info_hashes: Vec, peer_id: PeerId, peer_addr: IpAddr) -> WebResult { Ok(AnnounceRequest { info_hash: info_hashes[0], - peer_addr: remote_addr.unwrap(), - forwarded_ip, + peer_addr, downloaded: announce_request_query.downloaded.unwrap_or(0), uploaded: announce_request_query.uploaded.unwrap_or(0), peer_id, @@ -134,30 +155,17 @@ async fn announce_request(announce_request_query: AnnounceRequestQuery, info_has } /// Check for ScrapeRequest -pub fn with_scrape_request() -> impl Filter + Clone { +pub fn with_scrape_request(on_reverse_proxy: bool) -> impl Filter + Clone { warp::any() .and(with_info_hash()) - .and(warp::addr::remote()) - .and(warp::header::optional::("X-Forwarded-For")) + .and(with_peer_addr(on_reverse_proxy)) .and_then(scrape_request) } /// Parse ScrapeRequest from InfoHash -async fn scrape_request(info_hashes: Vec, remote_addr: Option, forwarded_for: Option) -> WebResult { - if remote_addr.is_none() { return Err(reject::custom(ServerError::AddressNotFound)) } - - // get first forwarded ip - let forwarded_ip = match forwarded_for { - None => None, - Some(forwarded_for_str) => { - forwarded_for_str.split(",").next() - .and_then(|ip_str| IpAddr::from_str(ip_str).ok()) - } - }; - +async fn scrape_request(info_hashes: Vec, peer_addr: IpAddr) -> WebResult { Ok(ScrapeRequest { info_hashes, - remote_addr: remote_addr.unwrap(), - forwarded_ip, + peer_addr, }) } diff --git a/src/torrust_http_tracker/handlers.rs b/src/torrust_http_tracker/handlers.rs index 2fe216196..f15b7143a 100644 --- a/src/torrust_http_tracker/handlers.rs +++ b/src/torrust_http_tracker/handlers.rs @@ -36,26 +36,19 @@ pub async fn handle_announce(announce_request: AnnounceRequest, auth_key: Option debug!("{:?}", announce_request); - if tracker.config.on_reverse_proxy && announce_request.forwarded_ip.is_none() { - return Err(reject::custom(ServerError::AddressNotFound)) - } - - let peer_ip = match tracker.config.on_reverse_proxy { - true => announce_request.forwarded_ip.unwrap(), - false => announce_request.peer_addr.ip() - }; - - let peer = TorrentPeer::from_http_announce_request(&announce_request, peer_ip, tracker.config.get_ext_ip()); + let peer = TorrentPeer::from_http_announce_request(&announce_request, announce_request.peer_addr, tracker.config.get_ext_ip()); let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&announce_request.info_hash, &peer).await; - // get all peers excluding the client_addr + + // get all torrent peers excluding the peer_addr let peers = tracker.get_torrent_peers(&announce_request.info_hash, &peer.peer_addr).await; - //if peers.is_none() { return Err(reject::custom(ServerError::NoPeersFound)) } // success response let tracker_copy = tracker.clone(); + let is_ipv4 = announce_request.peer_addr.is_ipv4(); + tokio::spawn(async move { let mut status_writer = tracker_copy.set_stats().await; - if peer_ip.is_ipv4() { + if is_ipv4 { status_writer.tcp4_connections_handled += 1; status_writer.tcp4_announces_handled += 1; } else { @@ -63,7 +56,9 @@ pub async fn handle_announce(announce_request: AnnounceRequest, auth_key: Option status_writer.tcp6_announces_handled += 1; } }); + let announce_interval = tracker.config.announce_interval; + send_announce_response(&announce_request, torrent_stats, peers, announce_interval, tracker.config.announce_interval_min) } @@ -91,19 +86,11 @@ pub async fn handle_scrape(scrape_request: ScrapeRequest, auth_key: Option scrape_request.forwarded_ip.unwrap(), - false => scrape_request.remote_addr.ip() - }; - let tracker_copy = tracker.clone(); + tokio::spawn(async move { let mut status_writer = tracker_copy.set_stats().await; - if ip.is_ipv4() { + if scrape_request.peer_addr.is_ipv4() { status_writer.tcp4_connections_handled += 1; status_writer.tcp4_scrapes_handled += 1; } else { @@ -111,19 +98,8 @@ pub async fn handle_scrape(scrape_request: ScrapeRequest, auth_key: Option std::result::Result { - if let Some(e) = r.find::() { - debug!("{:?}", e); - let body: String = ErrorResponse { failure_reason: e.to_string() }.write(); - Ok(Response::new(body)) - } else { - let body: String = ErrorResponse { failure_reason: "internal server error".to_string() }.write(); - Ok(Response::new(body)) - } + send_scrape_response(files) } /// Send announce response @@ -157,3 +133,15 @@ fn send_announce_response(announce_request: &AnnounceRequest, torrent_stats: Tor fn send_scrape_response(files: HashMap) -> WebResult { Ok(Response::new(ScrapeResponse { files }.write())) } + +/// Handle all server errors and send error reply +pub async fn send_error(r: Rejection) -> std::result::Result { + let body = if let Some(server_error) = r.find::() { + debug!("{:?}", server_error); + ErrorResponse { failure_reason: server_error.to_string() }.write() + } else { + ErrorResponse { failure_reason: ServerError::InternalServerError.to_string() }.write() + }; + + Ok(Response::new(body)) +} diff --git a/src/torrust_http_tracker/request.rs b/src/torrust_http_tracker/request.rs index ac0e9602f..0fb316671 100644 --- a/src/torrust_http_tracker/request.rs +++ b/src/torrust_http_tracker/request.rs @@ -1,4 +1,4 @@ -use std::net::{IpAddr, SocketAddr}; +use std::net::{IpAddr}; use serde::{Deserialize}; use crate::{InfoHash, PeerId}; use crate::torrust_http_tracker::Bytes; @@ -17,8 +17,7 @@ pub struct AnnounceRequestQuery { #[derive(Debug)] pub struct AnnounceRequest { pub info_hash: InfoHash, - pub peer_addr: SocketAddr, - pub forwarded_ip: Option, + pub peer_addr: IpAddr, pub downloaded: Bytes, pub uploaded: Bytes, pub peer_id: PeerId, @@ -30,6 +29,5 @@ pub struct AnnounceRequest { pub struct ScrapeRequest { pub info_hashes: Vec, - pub forwarded_ip: Option, - pub remote_addr: SocketAddr, + pub peer_addr: IpAddr, } diff --git a/src/torrust_http_tracker/routes.rs b/src/torrust_http_tracker/routes.rs index ad873e83e..4b4de722f 100644 --- a/src/torrust_http_tracker/routes.rs +++ b/src/torrust_http_tracker/routes.rs @@ -2,21 +2,21 @@ use std::convert::Infallible; use std::sync::Arc; use warp::{Filter, Rejection}; use crate::TorrentTracker; -use crate::torrust_http_tracker::{handle_announce, handle_error, handle_scrape, with_announce_request, with_auth_key, with_scrape_request, with_tracker}; +use crate::torrust_http_tracker::{handle_announce, send_error, handle_scrape, with_announce_request, with_auth_key, with_scrape_request, with_tracker}; /// All routes pub fn routes(tracker: Arc,) -> impl Filter + Clone { root(tracker.clone()) .or(announce(tracker.clone())) .or(scrape(tracker.clone())) - .recover(handle_error) + .recover(send_error) } /// GET / or / fn root(tracker: Arc,) -> impl Filter + Clone { warp::any() .and(warp::filters::method::get()) - .and(with_announce_request()) + .and(with_announce_request(tracker.config.on_reverse_proxy)) .and(with_auth_key()) .and(with_tracker(tracker)) .and_then(handle_announce) @@ -26,7 +26,7 @@ fn root(tracker: Arc,) -> impl Filter,) -> impl Filter + Clone { warp::path::path("announce") .and(warp::filters::method::get()) - .and(with_announce_request()) + .and(with_announce_request(tracker.config.on_reverse_proxy)) .and(with_auth_key()) .and(with_tracker(tracker)) .and_then(handle_announce) @@ -36,7 +36,7 @@ fn announce(tracker: Arc,) -> impl Filter,) -> impl Filter + Clone { warp::path::path("scrape") .and(warp::filters::method::get()) - .and(with_scrape_request()) + .and(with_scrape_request(tracker.config.on_reverse_proxy)) .and(with_auth_key()) .and(with_tracker(tracker)) .and_then(handle_scrape) diff --git a/src/torrust_udp_tracker/handlers.rs b/src/torrust_udp_tracker/handlers.rs index 0aeb804d7..cbd7b7a83 100644 --- a/src/torrust_udp_tracker/handlers.rs +++ b/src/torrust_udp_tracker/handlers.rs @@ -147,6 +147,7 @@ pub async fn handle_announce(remote_addr: SocketAddr, announce_request: &Announc Ok(announce_response) } +// todo: refactor this, db lock can be a lot shorter pub async fn handle_scrape(remote_addr: SocketAddr, request: &ScrapeRequest, tracker: Arc) -> Result { let db = tracker.get_torrents().await; diff --git a/src/tracker.rs b/src/tracker.rs index 58ddd70ab..98c5be730 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use serde; use std::borrow::Cow; use std::collections::BTreeMap; -use tokio::sync::{RwLock, RwLockWriteGuard}; +use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use crate::common::{AnnounceEventDef, InfoHash, NumberOfBytesDef, PeerId}; use std::net::{IpAddr, SocketAddr}; use crate::{Configuration, key_manager, MAX_SCRAPE_TORRENTS}; @@ -443,7 +443,7 @@ impl TorrentTracker { } } - pub async fn get_torrents(&self) -> tokio::sync::RwLockReadGuard<'_, BTreeMap> { + pub async fn get_torrents(&self) -> RwLockReadGuard<'_, BTreeMap> { self.torrents.read().await } @@ -451,7 +451,7 @@ impl TorrentTracker { self.stats.write().await } - pub async fn get_stats(&self) -> tokio::sync::RwLockReadGuard<'_, TrackerStats> { + pub async fn get_stats(&self) -> RwLockReadGuard<'_, TrackerStats> { self.stats.read().await } From 660a5550939de38a394436e8efbbeb586832a735 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Thu, 17 Mar 2022 14:15:19 +0100 Subject: [PATCH 26/32] chore: updated readme with the latest example config --- README.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 77055a335..1804f1277 100644 --- a/README.md +++ b/README.md @@ -53,20 +53,26 @@ persistence = false cleanup_interval = 600 cleanup_peerless = true external_ip = "0.0.0.0" -announce_interval = 0 +announce_interval = 120 +announce_interval_min = 900 +peer_timeout = 900 on_reverse_proxy = false [[udp_trackers]] -enabled = true +enabled = false bind_address = "0.0.0.0:6969" +[[udp_trackers]] +enabled = true +bind_address = "[::]:6969" + [[http_trackers]] enabled = true bind_address = "0.0.0.0:6969" -ssl_enabled = true +ssl_enabled = false ssl_bind_address = "0.0.0.0:6868" -ssl_cert_path = "cert.pem" -ssl_key_path = "key.pem" +ssl_cert_path = "" +ssl_key_path = "" [http_api] enabled = true @@ -92,4 +98,4 @@ Read the API documentation [here](https://torrust.github.io/torrust-documentatio ### Credits This project was a joint effort by [Nautilus Cyberneering GmbH](https://nautilus-cyberneering.de/) and [Dutch Bits](https://dutchbits.nl). Also thanks to [Naim A.](https://github.com/naim94a/udpt) and [greatest-ape](https://github.com/greatest-ape/aquatic) for some parts of the code. -Further added features and functions thanks to [Power2All](https://github.com/power2all). \ No newline at end of file +Further added features and functions thanks to [Power2All](https://github.com/power2all). From 0bfc7b7603b82997d1f1c4cf5c83f71ad9d8fc77 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Thu, 17 Mar 2022 14:22:38 +0100 Subject: [PATCH 27/32] chore: updated readme with BEP's --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1804f1277..929585c11 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ Torrust Tracker is a lightweight but incredibly powerful and feature-rich BitTor * [X] SQLite3 Persistent loading and saving of the torrent hashes and completed count ### Implemented BEPs +* [BEP 3](https://www.bittorrent.org/beps/bep_0003.html): The BitTorrent Protocol +* [BEP 7](https://www.bittorrent.org/beps/bep_0007.html): IPv6 Support * [BEP 15](http://www.bittorrent.org/beps/bep_0015.html): UDP Tracker Protocol for BitTorrent * [BEP 23](http://bittorrent.org/beps/bep_0023.html): Tracker Returns Compact Peer Lists * [BEP 27](http://bittorrent.org/beps/bep_0027.html): Private Torrents From 9f601a59816f2edd7288580d603489bf1df1adfa Mon Sep 17 00:00:00 2001 From: Power2All Date: Thu, 17 Mar 2022 14:34:05 +0100 Subject: [PATCH 28/32] Debugging a issue where API wouldn't bind... --- src/main.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index fbd64abff..a529d4710 100644 --- a/src/main.rs +++ b/src/main.rs @@ -92,11 +92,7 @@ fn start_api_server(config: &HttpApiConfig, tracker: Arc) -> Joi tokio::spawn(async move { let server = http_api_server::build_server(tracker); - let _ = server.bind_with_graceful_shutdown(bind_addr, async move { - tokio::signal::ctrl_c() - .await - .expect("failed to listen to shutdown signal"); - }); + let _ = server.bind(bind_addr).await; }) } From 2e25d537b197efb82249984f26fdd36a7d28e6a1 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Thu, 17 Mar 2022 15:53:03 +0100 Subject: [PATCH 29/32] fix: (HTTP) getting the peer address from x-forwarded-for when it is an array --- src/torrust_http_tracker/filters.rs | 98 ++++++++++++++++------------- 1 file changed, 53 insertions(+), 45 deletions(-) diff --git a/src/torrust_http_tracker/filters.rs b/src/torrust_http_tracker/filters.rs index 9a79f228e..61fa20a45 100644 --- a/src/torrust_http_tracker/filters.rs +++ b/src/torrust_http_tracker/filters.rs @@ -20,6 +20,50 @@ pub fn with_info_hash() -> impl Filter,), Error = Rejec .and_then(info_hashes) } +/// Check for PeerId +pub fn with_peer_id() -> impl Filter + Clone { + warp::filters::query::raw() + .and_then(peer_id) +} + +/// Pass Arc along +pub fn with_auth_key() -> impl Filter,), Error = Infallible> + Clone { + warp::path::param::() + .map(|key: String| { + AuthKey::from_string(&key) + }) + .or_else(|_| async { + Ok::<(Option,), Infallible>((None,)) + }) +} + +/// Check for PeerAddress +pub fn with_peer_addr(on_reverse_proxy: bool) -> impl Filter + Clone { + warp::addr::remote() + .and(warp::header::optional::("X-Forwarded-For")) + .map(move |remote_addr: Option, x_forwarded_for: Option| { + (on_reverse_proxy, remote_addr, x_forwarded_for) + }) + .and_then(peer_addr) +} + +/// Check for AnnounceRequest +pub fn with_announce_request(on_reverse_proxy: bool) -> impl Filter + Clone { + warp::filters::query::query::() + .and(with_info_hash()) + .and(with_peer_id()) + .and(with_peer_addr(on_reverse_proxy)) + .and_then(announce_request) +} + +/// Check for ScrapeRequest +pub fn with_scrape_request(on_reverse_proxy: bool) -> impl Filter + Clone { + warp::any() + .and(with_info_hash()) + .and(with_peer_addr(on_reverse_proxy)) + .and_then(scrape_request) +} + /// Parse InfoHash from raw query string async fn info_hashes(raw_query: String) -> WebResult> { let split_raw_query: Vec<&str> = raw_query.split("&").collect(); @@ -45,12 +89,6 @@ async fn info_hashes(raw_query: String) -> WebResult> { } } -/// Check for PeerId -pub fn with_peer_id() -> impl Filter + Clone { - warp::filters::query::raw() - .and_then(peer_id) -} - /// Parse PeerId from raw query string async fn peer_id(raw_query: String) -> WebResult { // put all query params in a vec @@ -88,27 +126,6 @@ async fn peer_id(raw_query: String) -> WebResult { } } -/// Pass Arc along -pub fn with_auth_key() -> impl Filter,), Error = Infallible> + Clone { - warp::path::param::() - .map(|key: String| { - AuthKey::from_string(&key) - }) - .or_else(|_| async { - Ok::<(Option,), Infallible>((None,)) - }) -} - -/// Check for PeerAddress -pub fn with_peer_addr(on_reverse_proxy: bool) -> impl Filter + Clone { - warp::addr::remote() - .and(warp::header::optional::("X-Forwarded-For")) - .map(move |remote_addr: Option, x_forwarded_for: Option| { - (on_reverse_proxy, remote_addr, x_forwarded_for) - }) - .and_then(peer_addr) -} - /// Get PeerAddress from RemoteAddress or Forwarded async fn peer_addr((on_reverse_proxy, remote_addr, x_forwarded_for): (bool, Option, Option)) -> WebResult { if !on_reverse_proxy && remote_addr.is_none() { @@ -121,7 +138,15 @@ async fn peer_addr((on_reverse_proxy, remote_addr, x_forwarded_for): (bool, Opti match on_reverse_proxy { true => { - IpAddr::from_str(x_forwarded_for.as_ref().unwrap()).or_else(|e| { + let mut x_forwarded_for_raw = x_forwarded_for.unwrap(); + // remove whitespace chars + x_forwarded_for_raw.retain(|c| !c.is_whitespace()); + // get all forwarded ip's in a vec + let x_forwarded_ips: Vec<&str> = x_forwarded_for_raw.split(',').collect(); + // set client ip to last forwarded ip + let x_forwarded_ip = *x_forwarded_ips.last().unwrap(); + + IpAddr::from_str(x_forwarded_ip).or_else(|e| { debug!("{}", e); Err(reject::custom(ServerError::AddressNotFound)) }) @@ -130,15 +155,6 @@ async fn peer_addr((on_reverse_proxy, remote_addr, x_forwarded_for): (bool, Opti } } -/// Check for AnnounceRequest -pub fn with_announce_request(on_reverse_proxy: bool) -> impl Filter + Clone { - warp::filters::query::query::() - .and(with_info_hash()) - .and(with_peer_id()) - .and(with_peer_addr(on_reverse_proxy)) - .and_then(announce_request) -} - /// Parse AnnounceRequest from raw AnnounceRequestQuery, InfoHash and Option async fn announce_request(announce_request_query: AnnounceRequestQuery, info_hashes: Vec, peer_id: PeerId, peer_addr: IpAddr) -> WebResult { Ok(AnnounceRequest { @@ -154,14 +170,6 @@ async fn announce_request(announce_request_query: AnnounceRequestQuery, info_has }) } -/// Check for ScrapeRequest -pub fn with_scrape_request(on_reverse_proxy: bool) -> impl Filter + Clone { - warp::any() - .and(with_info_hash()) - .and(with_peer_addr(on_reverse_proxy)) - .and_then(scrape_request) -} - /// Parse ScrapeRequest from InfoHash async fn scrape_request(info_hashes: Vec, peer_addr: IpAddr) -> WebResult { Ok(ScrapeRequest { From a22047863f54e7368b80c73a05c8316c522165ae Mon Sep 17 00:00:00 2001 From: Power2All Date: Thu, 17 Mar 2022 15:59:48 +0100 Subject: [PATCH 30/32] Fixing a bug in return data --- src/torrust_http_tracker/response.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/torrust_http_tracker/response.rs b/src/torrust_http_tracker/response.rs index dc6614dd4..d459a6fdd 100644 --- a/src/torrust_http_tracker/response.rs +++ b/src/torrust_http_tracker/response.rs @@ -46,8 +46,8 @@ impl AnnounceResponse { let mut bytes: Vec = Vec::new(); bytes.write(b"d8:intervali")?; bytes.write(&self.interval.to_string().as_bytes())?; - bytes.write(b"d12:min intervali")?; - bytes.write(&self.interval.to_string().as_bytes())?; + bytes.write(b"e12:min intervali")?; + bytes.write(&self.interval_min.to_string().as_bytes())?; bytes.write(b"e8:completei")?; bytes.write(&self.complete.to_string().as_bytes())?; bytes.write(b"e10:incompletei")?; From f46df3845e8ac7828db93bad17a9bae5343ee351 Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Thu, 17 Mar 2022 23:11:10 +0100 Subject: [PATCH 31/32] feat: gracefully shutdown udp servers --- Cargo.lock | 28 +++++++++++++++++ Cargo.toml | 1 + src/main.rs | 30 +++++++++++++----- src/torrust_udp_tracker/handlers.rs | 2 +- src/torrust_udp_tracker/server.rs | 47 ++++++++++++++++++----------- 5 files changed, 82 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5994554bc..4279ad5b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -264,6 +264,7 @@ checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -286,12 +287,34 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.21" @@ -310,9 +333,13 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -1411,6 +1438,7 @@ dependencies = [ "config", "derive_more", "fern", + "futures", "hex", "log", "percent-encoding", diff --git a/Cargo.toml b/Cargo.toml index 4570bd8f3..084a7cfb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,3 +31,4 @@ config = "0.11" derive_more = "0.99" thiserror = "1.0" aquatic_udp_protocol = { git = "https://github.com/greatest-ape/aquatic" } +futures = "0.3.21" diff --git a/src/main.rs b/src/main.rs index a529d4710..08610d24a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use torrust_tracker::torrust_http_tracker::server::HttpServer; #[tokio::main] async fn main() { + // torrust config let config = match Configuration::load_from_file() { Ok(config) => Arc::new(config), Err(error) => { @@ -14,13 +15,13 @@ async fn main() { } }; - logging::setup_logging(&config); - // the singleton torrent tracker that gets passed to the HTTP and UDP server let tracker = Arc::new(TorrentTracker::new(config.clone()).unwrap_or_else(|e| { panic!("{}", e) })); + logging::setup_logging(&config); + // load persistent torrents if enabled if config.persistence { info!("Loading persistent torrents into memory..."); @@ -38,10 +39,17 @@ async fn main() { let _api_server = start_api_server(&config.http_api, tracker.clone()); } + let (tx, rx) = tokio::sync::watch::channel(false); + let mut udp_server_handles = Vec::new(); + // start the udp blocks for udp_tracker in &config.udp_trackers { + // used to send kill signal to thread + if udp_tracker.enabled { - let _ = start_udp_tracker_server(&udp_tracker, tracker.clone()).await; + udp_server_handles.push( + start_udp_tracker_server(&udp_tracker, tracker.clone(), rx.clone()).await + ) } } @@ -52,10 +60,16 @@ async fn main() { } // handle the signals here - let ctrl_c = tokio::signal::ctrl_c(); tokio::select! { - _ = ctrl_c => { + _ = tokio::signal::ctrl_c() => { info!("Torrust shutting down.."); + + // send kill signal + let _ = tx.send(true); + + // await for all udp servers to shutdown + futures::future::join_all(udp_server_handles).await; + // Save torrents if enabled if config.persistence { info!("Saving torrents into SQL from memory..."); @@ -118,13 +132,13 @@ fn start_http_tracker_server(config: &HttpTrackerConfig, tracker: Arc) -> JoinHandle<()> { - let udp_server = UdpServer::new(tracker, config).await.unwrap_or_else(|e| { +async fn start_udp_tracker_server(config: &UdpTrackerConfig, tracker: Arc, rx: tokio::sync::watch::Receiver) -> JoinHandle<()> { + let udp_server = UdpServer::new(tracker, &config.bind_address).await.unwrap_or_else(|e| { panic!("Could not start UDP server: {}", e); }); info!("Starting UDP server on: {}", config.bind_address); tokio::spawn(async move { - udp_server.start().await; + udp_server.start(rx).await; }) } diff --git a/src/torrust_udp_tracker/handlers.rs b/src/torrust_udp_tracker/handlers.rs index cbd7b7a83..bf25a8861 100644 --- a/src/torrust_udp_tracker/handlers.rs +++ b/src/torrust_udp_tracker/handlers.rs @@ -24,7 +24,7 @@ pub async fn authenticate(info_hash: &InfoHash, tracker: Arc) -> } } -pub async fn handle_packet(remote_addr: SocketAddr, payload: &[u8], tracker: Arc) -> Response { +pub async fn handle_packet(remote_addr: SocketAddr, payload: Vec, tracker: Arc) -> Response { match Request::from_bytes(&payload[..payload.len()], MAX_SCRAPE_TORRENTS).map_err(|_| ServerError::InternalServerError) { Ok(request) => { let transaction_id = match &request { diff --git a/src/torrust_udp_tracker/server.rs b/src/torrust_udp_tracker/server.rs index 2f472ef54..44a60738b 100644 --- a/src/torrust_udp_tracker/server.rs +++ b/src/torrust_udp_tracker/server.rs @@ -1,41 +1,54 @@ +use std::future::Future; use std::io::Cursor; use std::net::{SocketAddr}; use std::sync::Arc; use aquatic_udp_protocol::{Response}; -use log::debug; +use log::{debug, info}; use tokio::net::UdpSocket; -use crate::{TorrentTracker, UdpTrackerConfig}; +use crate::{TorrentTracker}; use crate::torrust_udp_tracker::{handle_packet, MAX_PACKET_SIZE}; pub struct UdpServer { - socket: UdpSocket, + socket: Arc, tracker: Arc, } impl UdpServer { - pub async fn new(tracker: Arc, config: &UdpTrackerConfig) -> Result { - let srv = UdpSocket::bind(&config.bind_address).await?; + pub async fn new(tracker: Arc, bind_address: &str) -> tokio::io::Result { + let socket = UdpSocket::bind(bind_address).await?; Ok(UdpServer { - socket: srv, + socket: Arc::new(socket), tracker, }) } - pub async fn start(&self) { + pub async fn start(&self, rx: tokio::sync::watch::Receiver) { loop { + let mut rx = rx.clone(); let mut data = [0; MAX_PACKET_SIZE]; - if let Ok((valid_bytes, remote_addr)) = self.socket.recv_from(&mut data).await { - let data = &data[..valid_bytes]; - debug!("Received {} bytes from {}", data.len(), remote_addr); - debug!("{:?}", data); - let response = handle_packet(remote_addr, data, self.tracker.clone()).await; - self.send_response(remote_addr, response).await; + let socket = self.socket.clone(); + let tracker = self.tracker.clone(); + + tokio::select! { + _ = rx.changed() => { + info!("Stopping UDP server: {}...", socket.local_addr().unwrap()); + break; + } + Ok((valid_bytes, remote_addr)) = socket.recv_from(&mut data) => { + let payload = data[..valid_bytes].to_vec(); + + debug!("Received {} bytes from {}", payload.len(), remote_addr); + debug!("{:?}", payload); + + let response = handle_packet(remote_addr, payload, tracker).await; + UdpServer::send_response(socket, remote_addr, response).await; + } } } } - async fn send_response(&self, remote_addr: SocketAddr, response: Response) { + async fn send_response(socket: Arc, remote_addr: SocketAddr, response: Response) { debug!("sending response to: {:?}", &remote_addr); let buffer = vec![0u8; MAX_PACKET_SIZE]; @@ -47,14 +60,14 @@ impl UdpServer { let inner = cursor.get_ref(); debug!("{:?}", &inner[..position]); - self.send_packet(&remote_addr, &inner[..position]).await; + UdpServer::send_packet(socket, &remote_addr, &inner[..position]).await; } Err(_) => { debug!("could not write response to bytes."); } } } - async fn send_packet(&self, remote_addr: &SocketAddr, payload: &[u8]) { + async fn send_packet(socket: Arc, remote_addr: &SocketAddr, payload: &[u8]) { // doesn't matter if it reaches or not - let _ = self.socket.send_to(payload, remote_addr).await; + let _ = socket.send_to(payload, remote_addr).await; } } From f9eaa10ba86334dbf918a798e4d48a8d711b0bfe Mon Sep 17 00:00:00 2001 From: Warm Beer Date: Thu, 17 Mar 2022 23:50:32 +0100 Subject: [PATCH 32/32] refactor: changed udp max packet size from 65535 to 1496 --- src/torrust_udp_tracker/mod.rs | 2 +- src/torrust_udp_tracker/server.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/torrust_udp_tracker/mod.rs b/src/torrust_udp_tracker/mod.rs index cd4b99f5b..6aa5fbce0 100644 --- a/src/torrust_udp_tracker/mod.rs +++ b/src/torrust_udp_tracker/mod.rs @@ -12,5 +12,5 @@ pub type Bytes = u64; pub type Port = u16; pub type TransactionId = i64; -pub const MAX_PACKET_SIZE: usize = 0xffff; +pub const MAX_PACKET_SIZE: usize = 1496; pub const PROTOCOL_ID: i64 = 0x41727101980; diff --git a/src/torrust_udp_tracker/server.rs b/src/torrust_udp_tracker/server.rs index 44a60738b..cae1e5b94 100644 --- a/src/torrust_udp_tracker/server.rs +++ b/src/torrust_udp_tracker/server.rs @@ -1,4 +1,3 @@ -use std::future::Future; use std::io::Cursor; use std::net::{SocketAddr}; use std::sync::Arc;