diff --git a/Cargo.lock b/Cargo.lock index c2d68639a..328e2db93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4375,6 +4375,7 @@ dependencies = [ "torrust-server-lib", "torrust-tracker-clock", "torrust-tracker-configuration", + "torrust-tracker-primitives", "torrust-tracker-test-helpers", "torrust-udp-tracker-server", "tower-http", @@ -4510,10 +4511,11 @@ name = "torrust-server-lib" version = "3.0.0-develop" dependencies = [ "derive_more", + "rstest", "tokio", + "torrust-tracker-primitives", "tower-http", "tracing", - "url", ] [[package]] @@ -4632,11 +4634,13 @@ dependencies = [ "binascii", "bittorrent-primitives", "derive_more", + "rstest", "serde", "tdyne-peer-id", "tdyne-peer-id-registry", "thiserror 2.0.12", "torrust-tracker-configuration", + "url", "zerocopy 0.7.35", ] diff --git a/packages/axum-health-check-api-server/Cargo.toml b/packages/axum-health-check-api-server/Cargo.toml index 6766ce587..e0504f7df 100644 --- a/packages/axum-health-check-api-server/Cargo.toml +++ b/packages/axum-health-check-api-server/Cargo.toml @@ -24,6 +24,7 @@ tokio = { version = "1", features = ["macros", "net", "rt-multi-thread", "signal torrust-axum-server = { version = "3.0.0-develop", path = "../axum-server" } torrust-server-lib = { version = "3.0.0-develop", path = "../server-lib" } torrust-tracker-configuration = { version = "3.0.0-develop", path = "../configuration" } +torrust-tracker-primitives = { version = "3.0.0-develop", path = "../primitives" } tower-http = { version = "0", features = ["compression-full", "cors", "propagate-header", "request-id", "trace"] } tracing = "0" url = "2.5.4" diff --git a/packages/axum-health-check-api-server/src/handlers.rs b/packages/axum-health-check-api-server/src/handlers.rs index 66390f089..a26c901d7 100644 --- a/packages/axum-health-check-api-server/src/handlers.rs +++ b/packages/axum-health-check-api-server/src/handlers.rs @@ -31,8 +31,8 @@ pub(crate) async fn health_check_handler(State(register): State let jobs = checks.drain(..).map(|c| { tokio::spawn(async move { CheckReport { - listen_url: c.listen_url.clone(), - binding: c.binding, + service_binding: c.service_binding.url(), + binding: c.service_binding.bind_address(), info: c.info.clone(), service_type: c.service_type, result: c.job.await.expect("it should be able to join into the checking function"), diff --git a/packages/axum-health-check-api-server/src/resources.rs b/packages/axum-health-check-api-server/src/resources.rs index 24079b00f..44e64b24c 100644 --- a/packages/axum-health-check-api-server/src/resources.rs +++ b/packages/axum-health-check-api-server/src/resources.rs @@ -12,7 +12,7 @@ pub enum Status { #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] pub struct CheckReport { - pub listen_url: Url, + pub service_binding: Url, pub binding: SocketAddr, pub service_type: String, pub info: String, diff --git a/packages/axum-health-check-api-server/src/server.rs b/packages/axum-health-check-api-server/src/server.rs index cc721f5eb..8b37f1828 100644 --- a/packages/axum-health-check-api-server/src/server.rs +++ b/packages/axum-health-check-api-server/src/server.rs @@ -18,6 +18,7 @@ use torrust_axum_server::signals::graceful_shutdown; use torrust_server_lib::logging::Latency; use torrust_server_lib::registar::ServiceRegistry; use torrust_server_lib::signals::{Halted, Started}; +use torrust_tracker_primitives::service_binding::{Protocol, ServiceBinding}; use tower_http::classify::ServerErrorsFailureClass; use tower_http::compression::CompressionLayer; use tower_http::propagate_header::PropagateHeaderLayer; @@ -25,7 +26,6 @@ use tower_http::request_id::{MakeRequestUuid, SetRequestIdLayer}; use tower_http::trace::{DefaultMakeSpan, TraceLayer}; use tower_http::LatencyUnit; use tracing::{instrument, Level, Span}; -use url::Url; use crate::handlers::health_check_handler; use crate::HEALTH_CHECK_API_LOG_TARGET; @@ -102,9 +102,8 @@ pub fn start( let socket = std::net::TcpListener::bind(bind_to).expect("Could not bind tcp_listener to address."); let address = socket.local_addr().expect("Could not get local_addr from tcp_listener."); - let protocol = "http"; // The health check API only supports HTTP directly now. Use a reverse proxy for HTTPS. - let listen_url = - Url::parse(&format!("{protocol}://{address}")).expect("Could not parse internal service url for health check API."); + let protocol = Protocol::HTTP; // The health check API only supports HTTP directly now. Use a reverse proxy for HTTPS. + let service_binding = ServiceBinding::new(protocol.clone(), address).expect("Service binding creation failed"); let handle = Handle::new(); @@ -120,8 +119,11 @@ pub fn start( .handle(handle) .serve(router.into_make_service_with_connect_info::()); - tx.send(Started { listen_url, address }) - .expect("the Health Check API server should not be dropped"); + tx.send(Started { + service_binding, + address, + }) + .expect("the Health Check API server should not be dropped"); running } diff --git a/packages/axum-http-tracker-server/src/server.rs b/packages/axum-http-tracker-server/src/server.rs index eefb124e8..610f70020 100644 --- a/packages/axum-http-tracker-server/src/server.rs +++ b/packages/axum-http-tracker-server/src/server.rs @@ -7,13 +7,13 @@ use axum_server::Handle; use bittorrent_http_tracker_core::container::HttpTrackerCoreContainer; use derive_more::Constructor; use futures::future::BoxFuture; -use reqwest::Url; use tokio::sync::oneshot::{Receiver, Sender}; use torrust_axum_server::custom_axum_server::{self, TimeoutAcceptor}; use torrust_axum_server::signals::graceful_shutdown; use torrust_server_lib::logging::STARTED_ON; use torrust_server_lib::registar::{ServiceHealthCheckJob, ServiceRegistration, ServiceRegistrationForm}; use torrust_server_lib::signals::{Halted, Started}; +use torrust_tracker_primitives::service_binding::{Protocol, ServiceBinding}; use tracing::instrument; use super::v1::routes::router; @@ -63,9 +63,8 @@ impl Launcher { )); let tls = self.tls.clone(); - let protocol = if tls.is_some() { "https" } else { "http" }; - let listen_url = - Url::parse(&format!("{protocol}://{address}")).expect("Could not parse internal service url for HTTP tracker."); + let protocol = if tls.is_some() { Protocol::HTTPS } else { Protocol::HTTP }; + let service_binding = ServiceBinding::new(protocol.clone(), address).expect("Service binding creation failed"); tracing::info!(target: HTTP_TRACKER_LOG_TARGET, "Starting on: {protocol}://{address}"); @@ -93,7 +92,10 @@ impl Launcher { tracing::info!(target: HTTP_TRACKER_LOG_TARGET, "{STARTED_ON}: {protocol}://{}", address); tx_start - .send(Started { listen_url, address }) + .send(Started { + service_binding, + address, + }) .expect("the HTTP(s) Tracker service should not be dropped"); running @@ -182,10 +184,10 @@ impl HttpServer { let started = rx_start.await.expect("it should be able to start the service"); - let listen_url = started.listen_url; + let listen_url = started.service_binding; let binding = started.address; - form.send(ServiceRegistration::new(listen_url, binding, check_fn)) + form.send(ServiceRegistration::new(listen_url, check_fn)) .expect("it should be able to send service registration"); Ok(HttpServer { @@ -226,8 +228,8 @@ impl HttpServer { /// This function will return an error if unable to connect. /// Or if the request returns an error. #[must_use] -pub fn check_fn(listen_url: &Url, binding: &SocketAddr) -> ServiceHealthCheckJob { - let url = format!("http://{binding}/health_check"); // DevSkim: ignore DS137138 +pub fn check_fn(service_binding: &ServiceBinding) -> ServiceHealthCheckJob { + let url = format!("http://{}/health_check", service_binding.bind_address()); // DevSkim: ignore DS137138 let info = format!("checking http tracker health check at: {url}"); @@ -238,7 +240,7 @@ pub fn check_fn(listen_url: &Url, binding: &SocketAddr) -> ServiceHealthCheckJob } }); - ServiceHealthCheckJob::new(listen_url.clone(), *binding, info, TYPE_STRING.to_string(), job) + ServiceHealthCheckJob::new(service_binding.clone(), info, TYPE_STRING.to_string(), job) } #[cfg(test)] diff --git a/packages/axum-rest-tracker-api-server/src/server.rs b/packages/axum-rest-tracker-api-server/src/server.rs index 20775dbc1..cbd4948ff 100644 --- a/packages/axum-rest-tracker-api-server/src/server.rs +++ b/packages/axum-rest-tracker-api-server/src/server.rs @@ -40,8 +40,8 @@ use torrust_server_lib::logging::STARTED_ON; use torrust_server_lib::registar::{ServiceHealthCheckJob, ServiceRegistration, ServiceRegistrationForm}; use torrust_server_lib::signals::{Halted, Started}; use torrust_tracker_configuration::AccessTokens; +use torrust_tracker_primitives::service_binding::{Protocol, ServiceBinding}; use tracing::{instrument, Level}; -use url::Url; use super::routes::router; use crate::API_LOG_TARGET; @@ -149,7 +149,7 @@ impl ApiServer { let api_server = match rx_start.await { Ok(started) => { - form.send(ServiceRegistration::new(started.listen_url, started.address, check_fn)) + form.send(ServiceRegistration::new(started.service_binding, check_fn)) .expect("it should be able to send service registration"); ApiServer { @@ -196,8 +196,8 @@ impl ApiServer { /// Or if there request returns an error code. #[must_use] #[instrument(skip())] -pub fn check_fn(listen_url: &Url, binding: &SocketAddr) -> ServiceHealthCheckJob { - let url = format!("http://{binding}/api/health_check"); // DevSkim: ignore DS137138 +pub fn check_fn(service_binding: &ServiceBinding) -> ServiceHealthCheckJob { + let url = format!("http://{}/api/health_check", service_binding.bind_address()); // DevSkim: ignore DS137138 let info = format!("checking api health check at: {url}"); @@ -207,7 +207,7 @@ pub fn check_fn(listen_url: &Url, binding: &SocketAddr) -> ServiceHealthCheckJob Err(err) => Err(err.to_string()), } }); - ServiceHealthCheckJob::new(listen_url.clone(), *binding, info, TYPE_STRING.to_string(), job) + ServiceHealthCheckJob::new(service_binding.clone(), info, TYPE_STRING.to_string(), job) } /// A struct responsible for starting the API server. @@ -260,9 +260,8 @@ impl Launcher { )); let tls = self.tls.clone(); - let protocol = if tls.is_some() { "https" } else { "http" }; - let listen_url = - Url::parse(&format!("{protocol}://{address}")).expect("Could not parse internal service url for tracker API."); + let protocol = if tls.is_some() { Protocol::HTTPS } else { Protocol::HTTP }; + let service_binding = ServiceBinding::new(protocol.clone(), address).expect("Service binding creation failed"); tracing::info!(target: API_LOG_TARGET, "Starting on: {protocol}://{address}"); @@ -288,7 +287,10 @@ impl Launcher { tracing::info!(target: API_LOG_TARGET, "{STARTED_ON}: {protocol}://{}", address); tx_start - .send(Started { listen_url, address }) + .send(Started { + service_binding, + address, + }) .expect("the HTTP(s) Tracker API service should not be dropped"); running diff --git a/packages/primitives/Cargo.toml b/packages/primitives/Cargo.toml index 1396d8bc8..21fab09bf 100644 --- a/packages/primitives/Cargo.toml +++ b/packages/primitives/Cargo.toml @@ -24,4 +24,8 @@ tdyne-peer-id = "1" tdyne-peer-id-registry = "0" thiserror = "2" torrust-tracker-configuration = { version = "3.0.0-develop", path = "../configuration" } +url = "2.5.4" zerocopy = "0.7" + +[dev-dependencies] +rstest = "0.25.0" diff --git a/packages/primitives/src/lib.rs b/packages/primitives/src/lib.rs index b50516893..c901e5276 100644 --- a/packages/primitives/src/lib.rs +++ b/packages/primitives/src/lib.rs @@ -7,6 +7,7 @@ pub mod core; pub mod pagination; pub mod peer; +pub mod service_binding; pub mod swarm_metadata; use std::collections::BTreeMap; diff --git a/packages/primitives/src/service_binding.rs b/packages/primitives/src/service_binding.rs new file mode 100644 index 000000000..dbbb32fd5 --- /dev/null +++ b/packages/primitives/src/service_binding.rs @@ -0,0 +1,188 @@ +use std::fmt; +use std::net::SocketAddr; + +use serde::{Deserialize, Serialize}; +use url::Url; + +/// Represents the supported network protocols. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +pub enum Protocol { + UDP, + HTTP, + HTTPS, +} + +impl fmt::Display for Protocol { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let proto_str = match self { + Protocol::UDP => "udp", + Protocol::HTTP => "http", + Protocol::HTTPS => "https", + }; + write!(f, "{proto_str}") + } +} + +#[derive(thiserror::Error, Debug, Clone)] +pub enum Error { + #[error("The port number cannot be zero. It must be an assigned valid port.")] + PortZeroNotAllowed, +} + +/// Represents a network service binding, encapsulating protocol and socket +/// address. +/// +/// This struct is used to define how a service binds to a network interface and +/// port. +/// +/// It's an URL without path and some restrictions: +/// +/// - Only some schemes are accepted: `udp`, `http`, `https`. +/// - The port number must be greater than zero. The service should be already +/// listening on that port. +/// - The authority part of the URL must be a valid socket address (wildcard is +/// accepted). +/// +/// Besides it accepts some non well-formed URLs, like: +/// or . Those URLs are not valid because they use non +/// standard ports (80 and 443). +/// +/// NOTICE: It does not represent a public valid URL clients can connect to. It +/// represents the service's internal URL configuration after assigning a port. +/// If the port in the configuration is not zero, it's basically the same +/// information you get from the configuration (binding address + protocol). +/// +/// # Examples +/// +/// ``` +/// use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +/// use torrust_tracker_primitives::service_binding::{ServiceBinding, Protocol}; +/// +/// let service_binding = ServiceBinding::new(Protocol::HTTP, SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7070)).unwrap(); +/// +/// assert_eq!(service_binding.url().to_string(), "http://127.0.0.1:7070/".to_string()); +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +pub struct ServiceBinding { + /// The network protocol used by the service (UDP, HTTP, HTTPS). + protocol: Protocol, + + /// The socket address (IP and port) to which the service binds. + bind_address: SocketAddr, +} + +impl ServiceBinding { + /// # Errors + /// + /// This function will return an error if the port number is zero. + pub fn new(protocol: Protocol, bind_address: SocketAddr) -> Result { + if bind_address.port() == 0 { + return Err(Error::PortZeroNotAllowed); + } + + Ok(Self { protocol, bind_address }) + } + + #[must_use] + pub fn bind_address(&self) -> SocketAddr { + self.bind_address + } + + /// # Panics + /// + /// It never panics because the URL is always valid. + #[must_use] + pub fn url(&self) -> Url { + Url::parse(&format!("{}://{}", self.protocol, self.bind_address)) + .expect("Service binding can always be parsed into a URL") + } +} + +impl From for Url { + fn from(service_binding: ServiceBinding) -> Self { + service_binding.url() + } +} + +impl fmt::Display for ServiceBinding { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.url()) + } +} + +#[cfg(test)] +mod tests { + + mod the_service_binding { + use std::net::SocketAddr; + use std::str::FromStr; + + use rstest::rstest; + use url::Url; + + use crate::service_binding::{Error, Protocol, ServiceBinding}; + + #[rstest] + #[case("wildcard_ip", Protocol::UDP, SocketAddr::from_str("0.0.0.0:6969").unwrap())] + #[case("udp_service", Protocol::UDP, SocketAddr::from_str("127.0.0.1:6969").unwrap())] + #[case("http_service", Protocol::HTTP, SocketAddr::from_str("127.0.0.1:7070").unwrap())] + #[case("https_service", Protocol::HTTPS, SocketAddr::from_str("127.0.0.1:7070").unwrap())] + fn should_allow_a_subset_of_urls(#[case] case: &str, #[case] protocol: Protocol, #[case] bind_address: SocketAddr) { + let service_binding = ServiceBinding::new(protocol.clone(), bind_address); + + assert!(service_binding.is_ok(), "{}", format!("{case} failed: {service_binding:?}")); + } + + #[test] + fn should_not_allow_undefined_port_zero() { + let service_binding = ServiceBinding::new(Protocol::UDP, SocketAddr::from_str("127.0.0.1:0").unwrap()); + + assert!(matches!(service_binding, Err(Error::PortZeroNotAllowed))); + } + + #[test] + fn should_return_the_bind_address() { + let service_binding = ServiceBinding::new(Protocol::UDP, SocketAddr::from_str("127.0.0.1:6969").unwrap()).unwrap(); + + assert_eq!( + service_binding.bind_address(), + SocketAddr::from_str("127.0.0.1:6969").unwrap() + ); + } + + #[test] + fn should_return_the_corresponding_url() { + let service_binding = ServiceBinding::new(Protocol::UDP, SocketAddr::from_str("127.0.0.1:6969").unwrap()).unwrap(); + + assert_eq!(service_binding.url(), Url::parse("udp://127.0.0.1:6969").unwrap()); + } + + #[test] + fn should_be_converted_into_an_url() { + let service_binding = ServiceBinding::new(Protocol::UDP, SocketAddr::from_str("127.0.0.1:6969").unwrap()).unwrap(); + + let url: Url = service_binding.clone().into(); + + assert_eq!(url, Url::parse("udp://127.0.0.1:6969").unwrap()); + } + + #[rstest] + #[case("udp_service", Protocol::UDP, SocketAddr::from_str("127.0.0.1:6969").unwrap(), "udp://127.0.0.1:6969")] + #[case("http_service", Protocol::HTTP, SocketAddr::from_str("127.0.0.1:7070").unwrap(), "http://127.0.0.1:7070/")] + #[case("https_service", Protocol::HTTPS, SocketAddr::from_str("127.0.0.1:7070").unwrap(), "https://127.0.0.1:7070/")] + fn should_always_have_a_corresponding_unique_url( + #[case] case: &str, + #[case] protocol: Protocol, + #[case] bind_address: SocketAddr, + #[case] expected_url: String, + ) { + let service_binding = ServiceBinding::new(protocol.clone(), bind_address).unwrap(); + + assert_eq!( + service_binding.url().to_string(), + expected_url, + "{case} failed: {service_binding:?}", + ); + } + } +} diff --git a/packages/server-lib/Cargo.toml b/packages/server-lib/Cargo.toml index 514828953..1d30e7fb5 100644 --- a/packages/server-lib/Cargo.toml +++ b/packages/server-lib/Cargo.toml @@ -14,10 +14,11 @@ rust-version.workspace = true version.workspace = true [dependencies] -derive_more = { version = "2", features = ["as_ref", "constructor", "from"] } +derive_more = { version = "2", features = ["as_ref", "constructor", "display", "from"] } tokio = { version = "1", features = ["macros", "net", "rt-multi-thread", "signal", "sync"] } +torrust-tracker-primitives = { version = "3.0.0-develop", path = "../primitives" } tower-http = { version = "0", features = ["compression-full", "cors", "propagate-header", "request-id", "trace"] } tracing = "0" -url = "2.5.4" [dev-dependencies] +rstest = "0.25.0" diff --git a/packages/server-lib/src/registar.rs b/packages/server-lib/src/registar.rs index 9d36cf1fe..efa94034b 100644 --- a/packages/server-lib/src/registar.rs +++ b/packages/server-lib/src/registar.rs @@ -1,13 +1,12 @@ //! Registar. Registers Services for Health Check. use std::collections::HashMap; -use std::net::SocketAddr; use std::sync::Arc; use derive_more::Constructor; use tokio::sync::Mutex; use tokio::task::JoinHandle; -use url::Url; +use torrust_tracker_primitives::service_binding::ServiceBinding; /// A [`ServiceHeathCheckResult`] is returned by a completed health check. pub type ServiceHeathCheckResult = Result; @@ -17,8 +16,7 @@ pub type ServiceHeathCheckResult = Result; /// The `job` awaits a [`ServiceHeathCheckResult`]. #[derive(Debug, Constructor)] pub struct ServiceHealthCheckJob { - pub listen_url: Url, - pub binding: SocketAddr, + pub service_binding: ServiceBinding, pub info: String, pub service_type: String, pub job: JoinHandle, @@ -27,22 +25,21 @@ pub struct ServiceHealthCheckJob { /// The function specification [`FnSpawnServiceHeathCheck`]. /// /// A function fulfilling this specification will spawn a new [`ServiceHealthCheckJob`]. -pub type FnSpawnServiceHeathCheck = fn(&Url, &SocketAddr) -> ServiceHealthCheckJob; +pub type FnSpawnServiceHeathCheck = fn(&ServiceBinding) -> ServiceHealthCheckJob; /// A [`ServiceRegistration`] is provided to the [`Registar`] for registration. /// /// Each registration includes a function that fulfils the [`FnSpawnServiceHeathCheck`] specification. #[derive(Clone, Debug, Constructor)] pub struct ServiceRegistration { - listen_url: Url, - binding: SocketAddr, + service_binding: ServiceBinding, check_fn: FnSpawnServiceHeathCheck, } impl ServiceRegistration { #[must_use] pub fn spawn_check(&self) -> ServiceHealthCheckJob { - (self.check_fn)(&self.listen_url, &self.binding) + (self.check_fn)(&self.service_binding) } } @@ -50,7 +47,7 @@ impl ServiceRegistration { pub type ServiceRegistrationForm = tokio::sync::oneshot::Sender; /// The [`ServiceRegistry`] contains each unique [`ServiceRegistration`] by it's [`SocketAddr`]. -pub type ServiceRegistry = Arc>>; +pub type ServiceRegistry = Arc>>; /// The [`Registar`] manages the [`ServiceRegistry`]. #[derive(Clone, Debug)] @@ -93,7 +90,7 @@ impl Registar { let mut mutex = self.registry.lock().await; - mutex.insert(service_registration.binding, service_registration); + mutex.insert(service_registration.service_binding.clone(), service_registration); } /// Returns the [`ServiceRegistry`] of services diff --git a/packages/server-lib/src/signals.rs b/packages/server-lib/src/signals.rs index 94ee474ea..581729e57 100644 --- a/packages/server-lib/src/signals.rs +++ b/packages/server-lib/src/signals.rs @@ -1,14 +1,14 @@ //! This module contains functions to handle signals. use derive_more::Display; +use torrust_tracker_primitives::service_binding::ServiceBinding; use tracing::instrument; -use url::Url; /// This is the message that the "launcher" spawned task sends to the main /// application process to notify the service was successfully started. /// #[derive(Debug)] pub struct Started { - pub listen_url: Url, + pub service_binding: ServiceBinding, pub address: std::net::SocketAddr, } diff --git a/packages/tracker-client/src/udp/client.rs b/packages/tracker-client/src/udp/client.rs index 89a33726d..1c5ffd901 100644 --- a/packages/tracker-client/src/udp/client.rs +++ b/packages/tracker-client/src/udp/client.rs @@ -8,6 +8,7 @@ use aquatic_udp_protocol::{ConnectRequest, Request, Response, TransactionId}; use tokio::net::UdpSocket; use tokio::time; use torrust_tracker_configuration::DEFAULT_TIMEOUT; +use torrust_tracker_primitives::service_binding::ServiceBinding; use zerocopy::network_endian::I32; use super::Error; @@ -230,10 +231,12 @@ impl UdpTrackerClient { /// /// # Errors /// -pub async fn check(remote_addr: &SocketAddr) -> Result { +pub async fn check(service_binding: &ServiceBinding) -> Result { + let remote_addr = service_binding.bind_address(); + tracing::debug!("Checking Service (detail): {remote_addr:?}."); - match UdpTrackerClient::new(*remote_addr, DEFAULT_TIMEOUT).await { + match UdpTrackerClient::new(remote_addr, DEFAULT_TIMEOUT).await { Ok(client) => { let connect_request = ConnectRequest { transaction_id: TransactionId(I32::new(123)), diff --git a/packages/udp-tracker-server/src/server/bound_socket.rs b/packages/udp-tracker-server/src/server/bound_socket.rs index 988bfb67f..6b81545d2 100644 --- a/packages/udp-tracker-server/src/server/bound_socket.rs +++ b/packages/udp-tracker-server/src/server/bound_socket.rs @@ -3,6 +3,7 @@ use std::net::SocketAddr; use std::ops::Deref; use bittorrent_udp_tracker_core::UDP_TRACKER_LOG_TARGET; +use torrust_tracker_primitives::service_binding::{Protocol, ServiceBinding}; use url::Url; /// Wrapper for Tokio [`UdpSocket`][`tokio::net::UdpSocket`] that is bound to a particular socket. @@ -47,6 +48,15 @@ impl BoundSocket { pub fn url(&self) -> Url { Url::parse(&format!("udp://{}", self.address())).expect("UDP socket address should be valid") } + + /// # Panics + /// + /// It should never panic because the conversion to a [`ServiceBinding`] + /// is infallible. + #[must_use] + pub fn service_binding(&self) -> ServiceBinding { + ServiceBinding::new(Protocol::UDP, self.address()).expect("Conversion to ServiceBinding should not fail") + } } impl Deref for BoundSocket { diff --git a/packages/udp-tracker-server/src/server/launcher.rs b/packages/udp-tracker-server/src/server/launcher.rs index 67e2ceed6..fd689a96f 100644 --- a/packages/udp-tracker-server/src/server/launcher.rs +++ b/packages/udp-tracker-server/src/server/launcher.rs @@ -13,8 +13,8 @@ use tokio::time::interval; use torrust_server_lib::logging::STARTED_ON; use torrust_server_lib::registar::ServiceHealthCheckJob; use torrust_server_lib::signals::{shutdown_signal_with_message, Halted, Started}; +use torrust_tracker_primitives::service_binding::ServiceBinding; use tracing::instrument; -use url::Url; use super::request_buffer::ActiveRequests; use crate::container::UdpTrackerServerContainer; @@ -66,7 +66,7 @@ impl Launcher { } }; - let listen_url = bound_socket.url().clone(); + let service_binding = bound_socket.service_binding().clone(); let address = bound_socket.address(); let local_udp_url = bound_socket.url().to_string(); @@ -91,7 +91,10 @@ impl Launcher { }; tx_start - .send(Started { listen_url, address }) + .send(Started { + service_binding, + address, + }) .expect("the UDP Tracker service should not be dropped"); tracing::debug!(target: UDP_TRACKER_LOG_TARGET, local_udp_url, "Udp::run_with_graceful_shutdown (started)"); @@ -113,14 +116,15 @@ impl Launcher { } #[must_use] - #[instrument(skip(binding))] - pub fn check(listen_url: &Url, binding: &SocketAddr) -> ServiceHealthCheckJob { - let binding = *binding; - let info = format!("checking the udp tracker health check at: {binding}"); + #[instrument(skip(service_binding))] + pub fn check(service_binding: &ServiceBinding) -> ServiceHealthCheckJob { + let info = format!("checking the udp tracker health check at: {}", service_binding.bind_address()); + + let service_binding_clone = service_binding.clone(); - let job = tokio::spawn(async move { check(&binding).await }); + let job = tokio::spawn(async move { check(&service_binding_clone).await }); - ServiceHealthCheckJob::new(listen_url.clone(), binding, info, TYPE_STRING.to_string(), job) + ServiceHealthCheckJob::new(service_binding.clone(), info, TYPE_STRING.to_string(), job) } #[instrument(skip(receiver, udp_tracker_core_container, udp_tracker_server_container))] diff --git a/packages/udp-tracker-server/src/server/states.rs b/packages/udp-tracker-server/src/server/states.rs index f10a02fb7..4ad059095 100644 --- a/packages/udp-tracker-server/src/server/states.rs +++ b/packages/udp-tracker-server/src/server/states.rs @@ -85,10 +85,10 @@ impl Server { let started = rx_start.await.expect("it should be able to start the service"); - let listen_url = started.listen_url; + let service_binding = started.service_binding; let local_addr = started.address; - form.send(ServiceRegistration::new(listen_url, local_addr, Launcher::check)) + form.send(ServiceRegistration::new(service_binding, Launcher::check)) .expect("it should be able to send service registration"); let running_udp_server: Server = Server {