Skip to content

Commit

Permalink
feat: [#675] tracker checker supports more service address formats
Browse files Browse the repository at this point in the history
All the following URL for UDP trackers are allow now:

```console
TORRUST_CHECKER_CONFIG='{
    "udp_trackers": [
	"127.0.0.1:6969",
	"127.0.0.1:6969/",
	"127.0.0.1:6969/announce",
	"localhost:6969",
	"localhost:6969/",
	"localhost:6969/announce",
	"udp://127.0.0.1:6969",
	"udp://127.0.0.1:6969/",
	"udp://127.0.0.1:6969/announce",
	"udp://localhost:6969",
	"udp://localhost:6969/",
	"udp://localhost:6969/announce"
    ],
    "http_trackers": [],
    "health_checks": []
}' cargo run --bin tracker_checker
```

NOTICE: the client will resolve the domain to a socket address if
needed.
  • Loading branch information
josecelano committed Sep 12, 2024
1 parent befbe1b commit a66d9ec
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 39 deletions.
39 changes: 36 additions & 3 deletions src/console/clients/checker/checks/udp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::time::Duration;
use aquatic_udp_protocol::TransactionId;
use hex_literal::hex;
use serde::Serialize;
use url::Url;

use crate::console::clients::udp::checker::Client;
use crate::console::clients::udp::Error;
Expand All @@ -23,20 +24,22 @@ pub enum Check {
}

#[allow(clippy::missing_panics_doc)]
pub async fn run(udp_trackers: Vec<SocketAddr>, timeout: Duration) -> Vec<Result<Checks, Checks>> {
pub async fn run(udp_trackers: Vec<Url>, timeout: Duration) -> Vec<Result<Checks, Checks>> {
let mut results = Vec::default();

tracing::debug!("UDP trackers ...");

let info_hash = aquatic_udp_protocol::InfoHash(hex!("9c38422213e30bff212b30c360d26f9a02136422")); // # DevSkim: ignore DS173237

for remote_addr in udp_trackers {
for remote_url in udp_trackers {
let remote_addr = resolve_socket_addr(&remote_url);

let mut checks = Checks {
remote_addr,
results: Vec::default(),
};

tracing::debug!("UDP tracker: {:?}", remote_addr);
tracing::debug!("UDP tracker: {:?}", remote_url);

// Setup
let client = match Client::new(remote_addr, timeout).await {
Expand Down Expand Up @@ -95,3 +98,33 @@ pub async fn run(udp_trackers: Vec<SocketAddr>, timeout: Duration) -> Vec<Result

results
}

fn resolve_socket_addr(url: &Url) -> SocketAddr {
let socket_addr = url.socket_addrs(|| None).unwrap();
*socket_addr.first().unwrap()
}

#[cfg(test)]
mod tests {
use std::net::{IpAddr, Ipv4Addr, SocketAddr};

use url::Url;

use crate::console::clients::checker::checks::udp::resolve_socket_addr;

#[test]
fn it_should_resolve_the_socket_address_for_udp_scheme_urls_containing_a_domain() {
assert_eq!(
resolve_socket_addr(&Url::parse("udp://localhost:8080").unwrap()),
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080)
);
}

#[test]
fn it_should_resolve_the_socket_address_for_udp_scheme_urls_containing_an_ip() {
assert_eq!(
resolve_socket_addr(&Url::parse("udp://127.0.0.1:8080").unwrap()),
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080)
);
}
}
165 changes: 129 additions & 36 deletions src/console/clients/checker/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::error::Error;
use std::fmt;
use std::net::SocketAddr;

use reqwest::Url as ServiceUrl;
use serde::Deserialize;
Expand Down Expand Up @@ -31,7 +30,7 @@ struct PlainConfiguration {

/// Validated configuration
pub struct Configuration {
pub udp_trackers: Vec<SocketAddr>,
pub udp_trackers: Vec<ServiceUrl>,
pub http_trackers: Vec<ServiceUrl>,
pub health_checks: Vec<ServiceUrl>,
}
Expand Down Expand Up @@ -62,7 +61,8 @@ impl TryFrom<PlainConfiguration> for Configuration {
let udp_trackers = plain_config
.udp_trackers
.into_iter()
.map(|s| s.parse::<SocketAddr>().map_err(ConfigurationError::InvalidUdpAddress))
.map(|s| if s.starts_with("udp://") { s } else { format!("udp://{s}") })
.map(|s| s.parse::<ServiceUrl>().map_err(ConfigurationError::InvalidUrl))
.collect::<Result<Vec<_>, _>>()?;

let http_trackers = plain_config
Expand All @@ -87,68 +87,161 @@ impl TryFrom<PlainConfiguration> for Configuration {

#[cfg(test)]
mod tests {
use std::net::{IpAddr, Ipv4Addr};

use super::*;

#[test]
fn configuration_should_be_build_from_plain_serializable_configuration() {
let dto = PlainConfiguration {
udp_trackers: vec!["127.0.0.1:8080".to_string()],
udp_trackers: vec!["udp://127.0.0.1:8080".to_string()],
http_trackers: vec!["http://127.0.0.1:8080".to_string()],
health_checks: vec!["http://127.0.0.1:8080/health".to_string()],
};

let config = Configuration::try_from(dto).expect("A valid configuration");

assert_eq!(
config.udp_trackers,
vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080)]
);
assert_eq!(config.udp_trackers, vec![ServiceUrl::parse("udp://127.0.0.1:8080").unwrap()]);

assert_eq!(
config.http_trackers,
vec![ServiceUrl::parse("http://127.0.0.1:8080").unwrap()]
);

assert_eq!(
config.health_checks,
vec![ServiceUrl::parse("http://127.0.0.1:8080/health").unwrap()]
);
}

mod building_configuration_from_plan_configuration {
use crate::console::clients::checker::config::{Configuration, PlainConfiguration};
mod building_configuration_from_plain_configuration_for {

mod udp_trackers {
use crate::console::clients::checker::config::{Configuration, PlainConfiguration, ServiceUrl};

/* The plain configuration should allow UDP URLs with:
- IP or domain.
- With or without scheme.
- With or without `announce` suffix.
- With or without `/` at the end of the authority section (with empty path).
For example:
127.0.0.1:6969
127.0.0.1:6969/
127.0.0.1:6969/announce
localhost:6969
localhost:6969/
localhost:6969/announce
udp://127.0.0.1:6969
udp://127.0.0.1:6969/
udp://127.0.0.1:6969/announce
udp://localhost:6969
udp://localhost:6969/
udp://localhost:6969/announce
*/

#[test]
fn it_should_fail_when_a_tracker_udp_address_is_invalid() {
let plain_config = PlainConfiguration {
udp_trackers: vec!["invalid_address".to_string()],
http_trackers: vec![],
health_checks: vec![],
};
#[test]
fn it_should_fail_when_a_tracker_udp_url_is_invalid() {
let plain_config = PlainConfiguration {
udp_trackers: vec!["invalid URL".to_string()],
http_trackers: vec![],
health_checks: vec![],
};

assert!(Configuration::try_from(plain_config).is_err());
assert!(Configuration::try_from(plain_config).is_err());
}

#[test]
fn it_should_add_the_udp_scheme_to_the_udp_url_when_it_is_missing() {
let plain_config = PlainConfiguration {
udp_trackers: vec!["127.0.0.1:6969".to_string()],
http_trackers: vec![],
health_checks: vec![],
};

let config = Configuration::try_from(plain_config).expect("Invalid plain configuration");

assert_eq!(config.udp_trackers[0], "udp://127.0.0.1:6969".parse::<ServiceUrl>().unwrap());
}

#[test]
fn it_should_allow_using_domains() {
let plain_config = PlainConfiguration {
udp_trackers: vec!["udp://localhost:6969".to_string()],
http_trackers: vec![],
health_checks: vec![],
};

let config = Configuration::try_from(plain_config).expect("Invalid plain configuration");

assert_eq!(config.udp_trackers[0], "udp://localhost:6969".parse::<ServiceUrl>().unwrap());
}

#[test]
fn it_should_allow_the_url_to_have_an_empty_path() {
let plain_config = PlainConfiguration {
udp_trackers: vec!["127.0.0.1:6969/".to_string()],
http_trackers: vec![],
health_checks: vec![],
};

let config = Configuration::try_from(plain_config).expect("Invalid plain configuration");

assert_eq!(config.udp_trackers[0], "udp://127.0.0.1:6969/".parse::<ServiceUrl>().unwrap());
}

#[test]
fn it_should_allow_the_url_to_contain_a_path() {
// This is the common format for UDP tracker URLs:
// udp://domain.com:6969/announce

let plain_config = PlainConfiguration {
udp_trackers: vec!["127.0.0.1:6969/announce".to_string()],
http_trackers: vec![],
health_checks: vec![],
};

let config = Configuration::try_from(plain_config).expect("Invalid plain configuration");

assert_eq!(
config.udp_trackers[0],
"udp://127.0.0.1:6969/announce".parse::<ServiceUrl>().unwrap()
);
}
}

#[test]
fn it_should_fail_when_a_tracker_http_address_is_invalid() {
let plain_config = PlainConfiguration {
udp_trackers: vec![],
http_trackers: vec!["not_a_url".to_string()],
health_checks: vec![],
};
mod http_trackers {
use crate::console::clients::checker::config::{Configuration, PlainConfiguration};

#[test]
fn it_should_fail_when_a_tracker_http_url_is_invalid() {
let plain_config = PlainConfiguration {
udp_trackers: vec![],
http_trackers: vec!["invalid URL".to_string()],
health_checks: vec![],
};

assert!(Configuration::try_from(plain_config).is_err());
assert!(Configuration::try_from(plain_config).is_err());
}
}

#[test]
fn it_should_fail_when_a_health_check_http_address_is_invalid() {
let plain_config = PlainConfiguration {
udp_trackers: vec![],
http_trackers: vec![],
health_checks: vec!["not_a_url".to_string()],
};
mod health_checks {
use crate::console::clients::checker::config::{Configuration, PlainConfiguration};

#[test]
fn it_should_fail_when_a_health_check_http_url_is_invalid() {
let plain_config = PlainConfiguration {
udp_trackers: vec![],
http_trackers: vec![],
health_checks: vec!["invalid URL".to_string()],
};

assert!(Configuration::try_from(plain_config).is_err());
assert!(Configuration::try_from(plain_config).is_err());
}
}
}
}

0 comments on commit a66d9ec

Please sign in to comment.