Skip to content

Commit

Permalink
Merge #1041: Tracker checker supports more service address formats
Browse files Browse the repository at this point in the history
faee02f feat: [#675] tracker checker (HTTP tracker) supports more service address formats (Jose Celano)
520026d feat: [#675] tracker checker supports more service address formats (Jose Celano)

Pull request description:

  ### UDP Trackers

  All the following URLs for UDP trackers are now allowed:

  ```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 an IP address if it's needed.

  ### HTTP Trackers

    Now, it supports a path suffix (`/` or `/announce`). It will be removed by the client to build
    the "scrape" URLs.

    This type of URL is widespread in tracker lists like https://newtrackon.com/.

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

ACKs for top commit:
  josecelano:
    ACK faee02f

Tree-SHA512: 16f7ada07fc863b03fa40073c020b377188958e53f92a9ef49a81a1d565ab7e55ca6654fa71cf54dde770f85201e72ef08858e14491531ab39381c0c707f1b6f
  • Loading branch information
josecelano committed Sep 12, 2024
2 parents befbe1b + faee02f commit 633514e
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 41 deletions.
7 changes: 5 additions & 2 deletions src/console/clients/checker/checks/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,24 @@ pub async fn run(http_trackers: Vec<Url>, timeout: Duration) -> Vec<Result<Check
tracing::debug!("HTTP trackers ...");

for ref url in http_trackers {
let mut base_url = url.clone();
base_url.set_path("");

let mut checks = Checks {
url: url.clone(),
results: Vec::default(),
};

// Announce
{
let check = check_http_announce(url, timeout).await.map(|_| ());
let check = check_http_announce(&base_url, timeout).await.map(|_| ());

checks.results.push((Check::Announce, check));
}

// Scrape
{
let check = check_http_scrape(url, timeout).await.map(|_| ());
let check = check_http_scrape(&base_url, timeout).await.map(|_| ());

checks.results.push((Check::Scrape, check));
}
Expand Down
43 changes: 40 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,37 @@ 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, Ipv6Addr, 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() {
let socket_addr = resolve_socket_addr(&Url::parse("udp://localhost:8080").unwrap());

assert!(
socket_addr == SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080)
|| socket_addr == SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 8080)
);
}

#[test]
fn it_should_resolve_the_socket_address_for_udp_scheme_urls_containing_an_ip() {
let socket_addr = resolve_socket_addr(&Url::parse("udp://localhost:8080").unwrap());

assert!(
socket_addr == SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080)
|| socket_addr == SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 8080)
);
}
}
200 changes: 164 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,196 @@ 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_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());
}

#[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");

#[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![],
};
assert_eq!(config.udp_trackers[0], "udp://127.0.0.1:6969".parse::<ServiceUrl>().unwrap());
}

assert!(Configuration::try_from(plain_config).is_err());
#[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, ServiceUrl};

assert!(Configuration::try_from(plain_config).is_err());
#[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());
}

#[test]
fn it_should_allow_the_url_to_contain_a_path() {
// This is the common format for HTTP tracker URLs:
// http://domain.com:7070/announce

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

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

assert_eq!(
config.http_trackers[0],
"http://127.0.0.1:7070/announce".parse::<ServiceUrl>().unwrap()
);
}

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

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

assert_eq!(
config.http_trackers[0],
"http://127.0.0.1:7070/".parse::<ServiceUrl>().unwrap()
);
}
}

#[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 633514e

Please sign in to comment.