Skip to content

Commit 633514e

Browse files
committed
Merge #1041: Tracker checker supports more service address formats
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
2 parents befbe1b + faee02f commit 633514e

File tree

3 files changed

+209
-41
lines changed

3 files changed

+209
-41
lines changed

src/console/clients/checker/checks/http.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,24 @@ pub async fn run(http_trackers: Vec<Url>, timeout: Duration) -> Vec<Result<Check
2828
tracing::debug!("HTTP trackers ...");
2929

3030
for ref url in http_trackers {
31+
let mut base_url = url.clone();
32+
base_url.set_path("");
33+
3134
let mut checks = Checks {
3235
url: url.clone(),
3336
results: Vec::default(),
3437
};
3538

3639
// Announce
3740
{
38-
let check = check_http_announce(url, timeout).await.map(|_| ());
41+
let check = check_http_announce(&base_url, timeout).await.map(|_| ());
3942

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

4346
// Scrape
4447
{
45-
let check = check_http_scrape(url, timeout).await.map(|_| ());
48+
let check = check_http_scrape(&base_url, timeout).await.map(|_| ());
4649

4750
checks.results.push((Check::Scrape, check));
4851
}

src/console/clients/checker/checks/udp.rs

+40-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::time::Duration;
44
use aquatic_udp_protocol::TransactionId;
55
use hex_literal::hex;
66
use serde::Serialize;
7+
use url::Url;
78

89
use crate::console::clients::udp::checker::Client;
910
use crate::console::clients::udp::Error;
@@ -23,20 +24,22 @@ pub enum Check {
2324
}
2425

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

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

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

33-
for remote_addr in udp_trackers {
34+
for remote_url in udp_trackers {
35+
let remote_addr = resolve_socket_addr(&remote_url);
36+
3437
let mut checks = Checks {
3538
remote_addr,
3639
results: Vec::default(),
3740
};
3841

39-
tracing::debug!("UDP tracker: {:?}", remote_addr);
42+
tracing::debug!("UDP tracker: {:?}", remote_url);
4043

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

9699
results
97100
}
101+
102+
fn resolve_socket_addr(url: &Url) -> SocketAddr {
103+
let socket_addr = url.socket_addrs(|| None).unwrap();
104+
*socket_addr.first().unwrap()
105+
}
106+
107+
#[cfg(test)]
108+
mod tests {
109+
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
110+
111+
use url::Url;
112+
113+
use crate::console::clients::checker::checks::udp::resolve_socket_addr;
114+
115+
#[test]
116+
fn it_should_resolve_the_socket_address_for_udp_scheme_urls_containing_a_domain() {
117+
let socket_addr = resolve_socket_addr(&Url::parse("udp://localhost:8080").unwrap());
118+
119+
assert!(
120+
socket_addr == SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080)
121+
|| socket_addr == SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 8080)
122+
);
123+
}
124+
125+
#[test]
126+
fn it_should_resolve_the_socket_address_for_udp_scheme_urls_containing_an_ip() {
127+
let socket_addr = resolve_socket_addr(&Url::parse("udp://localhost:8080").unwrap());
128+
129+
assert!(
130+
socket_addr == SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080)
131+
|| socket_addr == SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 8080)
132+
);
133+
}
134+
}

src/console/clients/checker/config.rs

+164-36
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::error::Error;
22
use std::fmt;
3-
use std::net::SocketAddr;
43

54
use reqwest::Url as ServiceUrl;
65
use serde::Deserialize;
@@ -31,7 +30,7 @@ struct PlainConfiguration {
3130

3231
/// Validated configuration
3332
pub struct Configuration {
34-
pub udp_trackers: Vec<SocketAddr>,
33+
pub udp_trackers: Vec<ServiceUrl>,
3534
pub http_trackers: Vec<ServiceUrl>,
3635
pub health_checks: Vec<ServiceUrl>,
3736
}
@@ -62,7 +61,8 @@ impl TryFrom<PlainConfiguration> for Configuration {
6261
let udp_trackers = plain_config
6362
.udp_trackers
6463
.into_iter()
65-
.map(|s| s.parse::<SocketAddr>().map_err(ConfigurationError::InvalidUdpAddress))
64+
.map(|s| if s.starts_with("udp://") { s } else { format!("udp://{s}") })
65+
.map(|s| s.parse::<ServiceUrl>().map_err(ConfigurationError::InvalidUrl))
6666
.collect::<Result<Vec<_>, _>>()?;
6767

6868
let http_trackers = plain_config
@@ -87,68 +87,196 @@ impl TryFrom<PlainConfiguration> for Configuration {
8787

8888
#[cfg(test)]
8989
mod tests {
90-
use std::net::{IpAddr, Ipv4Addr};
91-
9290
use super::*;
9391

9492
#[test]
9593
fn configuration_should_be_build_from_plain_serializable_configuration() {
9694
let dto = PlainConfiguration {
97-
udp_trackers: vec!["127.0.0.1:8080".to_string()],
95+
udp_trackers: vec!["udp://127.0.0.1:8080".to_string()],
9896
http_trackers: vec!["http://127.0.0.1:8080".to_string()],
9997
health_checks: vec!["http://127.0.0.1:8080/health".to_string()],
10098
};
10199

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

104-
assert_eq!(
105-
config.udp_trackers,
106-
vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080)]
107-
);
102+
assert_eq!(config.udp_trackers, vec![ServiceUrl::parse("udp://127.0.0.1:8080").unwrap()]);
103+
108104
assert_eq!(
109105
config.http_trackers,
110106
vec![ServiceUrl::parse("http://127.0.0.1:8080").unwrap()]
111107
);
108+
112109
assert_eq!(
113110
config.health_checks,
114111
vec![ServiceUrl::parse("http://127.0.0.1:8080/health").unwrap()]
115112
);
116113
}
117114

118-
mod building_configuration_from_plan_configuration {
119-
use crate::console::clients::checker::config::{Configuration, PlainConfiguration};
115+
mod building_configuration_from_plain_configuration_for {
116+
117+
mod udp_trackers {
118+
use crate::console::clients::checker::config::{Configuration, PlainConfiguration, ServiceUrl};
119+
120+
/* The plain configuration should allow UDP URLs with:
121+
122+
- IP or domain.
123+
- With or without scheme.
124+
- With or without `announce` suffix.
125+
- With or without `/` at the end of the authority section (with empty path).
126+
127+
For example:
128+
129+
127.0.0.1:6969
130+
127.0.0.1:6969/
131+
127.0.0.1:6969/announce
132+
133+
localhost:6969
134+
localhost:6969/
135+
localhost:6969/announce
136+
137+
udp://127.0.0.1:6969
138+
udp://127.0.0.1:6969/
139+
udp://127.0.0.1:6969/announce
140+
141+
udp://localhost:6969
142+
udp://localhost:6969/
143+
udp://localhost:6969/announce
144+
145+
*/
146+
147+
#[test]
148+
fn it_should_fail_when_a_tracker_udp_url_is_invalid() {
149+
let plain_config = PlainConfiguration {
150+
udp_trackers: vec!["invalid URL".to_string()],
151+
http_trackers: vec![],
152+
health_checks: vec![],
153+
};
154+
155+
assert!(Configuration::try_from(plain_config).is_err());
156+
}
157+
158+
#[test]
159+
fn it_should_add_the_udp_scheme_to_the_udp_url_when_it_is_missing() {
160+
let plain_config = PlainConfiguration {
161+
udp_trackers: vec!["127.0.0.1:6969".to_string()],
162+
http_trackers: vec![],
163+
health_checks: vec![],
164+
};
165+
166+
let config = Configuration::try_from(plain_config).expect("Invalid plain configuration");
120167

121-
#[test]
122-
fn it_should_fail_when_a_tracker_udp_address_is_invalid() {
123-
let plain_config = PlainConfiguration {
124-
udp_trackers: vec!["invalid_address".to_string()],
125-
http_trackers: vec![],
126-
health_checks: vec![],
127-
};
168+
assert_eq!(config.udp_trackers[0], "udp://127.0.0.1:6969".parse::<ServiceUrl>().unwrap());
169+
}
128170

129-
assert!(Configuration::try_from(plain_config).is_err());
171+
#[test]
172+
fn it_should_allow_using_domains() {
173+
let plain_config = PlainConfiguration {
174+
udp_trackers: vec!["udp://localhost:6969".to_string()],
175+
http_trackers: vec![],
176+
health_checks: vec![],
177+
};
178+
179+
let config = Configuration::try_from(plain_config).expect("Invalid plain configuration");
180+
181+
assert_eq!(config.udp_trackers[0], "udp://localhost:6969".parse::<ServiceUrl>().unwrap());
182+
}
183+
184+
#[test]
185+
fn it_should_allow_the_url_to_have_an_empty_path() {
186+
let plain_config = PlainConfiguration {
187+
udp_trackers: vec!["127.0.0.1:6969/".to_string()],
188+
http_trackers: vec![],
189+
health_checks: vec![],
190+
};
191+
192+
let config = Configuration::try_from(plain_config).expect("Invalid plain configuration");
193+
194+
assert_eq!(config.udp_trackers[0], "udp://127.0.0.1:6969/".parse::<ServiceUrl>().unwrap());
195+
}
196+
197+
#[test]
198+
fn it_should_allow_the_url_to_contain_a_path() {
199+
// This is the common format for UDP tracker URLs:
200+
// udp://domain.com:6969/announce
201+
202+
let plain_config = PlainConfiguration {
203+
udp_trackers: vec!["127.0.0.1:6969/announce".to_string()],
204+
http_trackers: vec![],
205+
health_checks: vec![],
206+
};
207+
208+
let config = Configuration::try_from(plain_config).expect("Invalid plain configuration");
209+
210+
assert_eq!(
211+
config.udp_trackers[0],
212+
"udp://127.0.0.1:6969/announce".parse::<ServiceUrl>().unwrap()
213+
);
214+
}
130215
}
131216

132-
#[test]
133-
fn it_should_fail_when_a_tracker_http_address_is_invalid() {
134-
let plain_config = PlainConfiguration {
135-
udp_trackers: vec![],
136-
http_trackers: vec!["not_a_url".to_string()],
137-
health_checks: vec![],
138-
};
217+
mod http_trackers {
218+
use crate::console::clients::checker::config::{Configuration, PlainConfiguration, ServiceUrl};
139219

140-
assert!(Configuration::try_from(plain_config).is_err());
220+
#[test]
221+
fn it_should_fail_when_a_tracker_http_url_is_invalid() {
222+
let plain_config = PlainConfiguration {
223+
udp_trackers: vec![],
224+
http_trackers: vec!["invalid URL".to_string()],
225+
health_checks: vec![],
226+
};
227+
228+
assert!(Configuration::try_from(plain_config).is_err());
229+
}
230+
231+
#[test]
232+
fn it_should_allow_the_url_to_contain_a_path() {
233+
// This is the common format for HTTP tracker URLs:
234+
// http://domain.com:7070/announce
235+
236+
let plain_config = PlainConfiguration {
237+
udp_trackers: vec![],
238+
http_trackers: vec!["http://127.0.0.1:7070/announce".to_string()],
239+
health_checks: vec![],
240+
};
241+
242+
let config = Configuration::try_from(plain_config).expect("Invalid plain configuration");
243+
244+
assert_eq!(
245+
config.http_trackers[0],
246+
"http://127.0.0.1:7070/announce".parse::<ServiceUrl>().unwrap()
247+
);
248+
}
249+
250+
#[test]
251+
fn it_should_allow_the_url_to_contain_an_empty_path() {
252+
let plain_config = PlainConfiguration {
253+
udp_trackers: vec![],
254+
http_trackers: vec!["http://127.0.0.1:7070/".to_string()],
255+
health_checks: vec![],
256+
};
257+
258+
let config = Configuration::try_from(plain_config).expect("Invalid plain configuration");
259+
260+
assert_eq!(
261+
config.http_trackers[0],
262+
"http://127.0.0.1:7070/".parse::<ServiceUrl>().unwrap()
263+
);
264+
}
141265
}
142266

143-
#[test]
144-
fn it_should_fail_when_a_health_check_http_address_is_invalid() {
145-
let plain_config = PlainConfiguration {
146-
udp_trackers: vec![],
147-
http_trackers: vec![],
148-
health_checks: vec!["not_a_url".to_string()],
149-
};
267+
mod health_checks {
268+
use crate::console::clients::checker::config::{Configuration, PlainConfiguration};
269+
270+
#[test]
271+
fn it_should_fail_when_a_health_check_http_url_is_invalid() {
272+
let plain_config = PlainConfiguration {
273+
udp_trackers: vec![],
274+
http_trackers: vec![],
275+
health_checks: vec!["invalid URL".to_string()],
276+
};
150277

151-
assert!(Configuration::try_from(plain_config).is_err());
278+
assert!(Configuration::try_from(plain_config).is_err());
279+
}
152280
}
153281
}
154282
}

0 commit comments

Comments
 (0)