Skip to content

Commit

Permalink
Merge torrust#664: Tracker Checker: check UDP trackers
Browse files Browse the repository at this point in the history
873f98d refactor: [torrust#639] Tracker Checker: extract mod for Health checks (Jose Celano)
70924ed refactor: [torrust#639] Tracker Checker: extract mod for HTTP checks (Jose Celano)
77c32a1 refactor: [torrust#639] Tracker Checker: extract mod for UDP checks (Jose Celano)
592c0dd fix: add timeouts to UdpClient operations (Jose Celano)
6b74c66 feat: [torrust#639] Tracker Checker. Setup logging (Jose Celano)
bbfca2c feat: [torrust#639] Tracker Checker. Check UDP trackers (Jose Celano)
661b521 chore: [torrust#639] add cargo dependency: hex_literal (Jose Celano)
1b92b77 refactor: [torrust#639] UDP client. Extract aquatic reponses wrappers (Jose Celano)
a2e123c refactor: [torrust#639] UDP client. Extract command handlers (Jose Celano)
011fdb7 refactor: [torrust#639] Tracker Checker: extract checker:Client to check UDP servers (Jose Celano)
3b735a7 refactor: [torrust#639] Tracker Checker: prepare outout for UDP checks (Jose Celano)

Pull request description:

  Add checks for UDP trackers to the Tracker Checker.

  The output will look like the following:

  ```output
  Running checks for trackers ...
  UDP trackers ...
  ✓ - Announce at 127.0.0.1:6969 is OK
  ✓ - Scrape at 127.0.0.1:6969 is OK
  HTTP trackers ...
  ✓ - Announce at http://127.0.0.1:7070/ is OK
  ✓ - Scrape at http://127.0.0.1:7070/ is OK
  Health checks ...
  ✓ - Health API at http://127.0.0.1:1313/health_check is OK
  ```

  See subtasks in torrust#639

ACKs for top commit:
  josecelano:
    ACK 873f98d

Tree-SHA512: 04c5472c72d6c8b6659c07d18b9240e1320249c3cc7455c727e7030a8e18aebd7bee0962ab1f3153323fafbc722a9ec8efad2c67d36ff826e00d5b9ef3c234f4
  • Loading branch information
josecelano committed Feb 1, 2024
2 parents c291ef1 + 873f98d commit 2bc2bff
Show file tree
Hide file tree
Showing 14 changed files with 675 additions and 325 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ url = "2.5.0"
tempfile = "3.9.0"
clap = { version = "4.4.18", features = ["derive", "env"]}
anyhow = "1.0.79"
hex-literal = "0.4.1"

[dev-dependencies]
criterion = { version = "0.5.1", features = ["async_tokio"] }
Expand Down
24 changes: 24 additions & 0 deletions src/console/clients/checker/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use std::sync::Arc;

use anyhow::{Context, Result};
use clap::Parser;
use log::{debug, LevelFilter};

use super::config::Configuration;
use super::console::Console;
Expand All @@ -39,6 +40,8 @@ struct Args {
///
/// Will return an error if the configuration was not provided.
pub async fn run() -> Result<Vec<CheckResult>> {
setup_logging(LevelFilter::Info);

let args = Args::parse();

let config = setup_config(args)?;
Expand All @@ -53,6 +56,27 @@ pub async fn run() -> Result<Vec<CheckResult>> {
Ok(service.run_checks().await)
}

fn setup_logging(level: LevelFilter) {
if let Err(_err) = fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"{} [{}][{}] {}",
chrono::Local::now().format("%+"),
record.target(),
record.level(),
message
));
})
.level(level)
.chain(std::io::stdout())
.apply()
{
panic!("Failed to initialize logging.")
}

debug!("logging initialized.");
}

fn setup_config(args: Args) -> Result<Configuration> {
match (args.config_path, args.config_content) {
(Some(config_path), _) => load_config_from_file(&config_path),
Expand Down
51 changes: 51 additions & 0 deletions src/console/clients/checker/checks/health.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use std::time::Duration;

use colored::Colorize;
use reqwest::{Client as HttpClient, Url, Url as ServiceUrl};

use crate::console::clients::checker::console::Console;
use crate::console::clients::checker::printer::Printer;
use crate::console::clients::checker::service::{CheckError, CheckResult};

pub async fn run(health_checks: &Vec<ServiceUrl>, console: &Console, check_results: &mut Vec<CheckResult>) {
console.println("Health checks ...");

for health_check_url in health_checks {
match run_health_check(health_check_url.clone(), console).await {
Ok(()) => check_results.push(Ok(())),
Err(err) => check_results.push(Err(err)),
}
}
}

async fn run_health_check(url: Url, console: &Console) -> Result<(), CheckError> {
let client = HttpClient::builder().timeout(Duration::from_secs(5)).build().unwrap();

let colored_url = url.to_string().yellow();

match client.get(url.clone()).send().await {
Ok(response) => {
if response.status().is_success() {
console.println(&format!("{} - Health API at {} is OK", "✓".green(), colored_url));
Ok(())
} else {
console.eprintln(&format!(
"{} - Health API at {} is failing: {:?}",
"✗".red(),
colored_url,
response
));
Err(CheckError::HealthCheckError { url })
}
}
Err(err) => {
console.eprintln(&format!(
"{} - Health API at {} is failing: {:?}",
"✗".red(),
colored_url,
err
));
Err(CheckError::HealthCheckError { url })
}
}
}
95 changes: 95 additions & 0 deletions src/console/clients/checker/checks/http.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use std::str::FromStr;

use colored::Colorize;
use log::debug;
use reqwest::Url as ServiceUrl;
use url::Url;

use crate::console::clients::checker::console::Console;
use crate::console::clients::checker::printer::Printer;
use crate::console::clients::checker::service::{CheckError, CheckResult};
use crate::shared::bit_torrent::info_hash::InfoHash;
use crate::shared::bit_torrent::tracker::http::client::requests::announce::QueryBuilder;
use crate::shared::bit_torrent::tracker::http::client::responses::announce::Announce;
use crate::shared::bit_torrent::tracker::http::client::responses::scrape;
use crate::shared::bit_torrent::tracker::http::client::{requests, Client};

pub async fn run(http_trackers: &Vec<ServiceUrl>, console: &Console, check_results: &mut Vec<CheckResult>) {
console.println("HTTP trackers ...");

for http_tracker in http_trackers {
let colored_tracker_url = http_tracker.to_string().yellow();

match check_http_announce(http_tracker).await {
Ok(()) => {
check_results.push(Ok(()));
console.println(&format!("{} - Announce at {} is OK", "✓".green(), colored_tracker_url));
}
Err(err) => {
check_results.push(Err(err));
console.println(&format!("{} - Announce at {} is failing", "✗".red(), colored_tracker_url));
}
}

match check_http_scrape(http_tracker).await {
Ok(()) => {
check_results.push(Ok(()));
console.println(&format!("{} - Scrape at {} is OK", "✓".green(), colored_tracker_url));
}
Err(err) => {
check_results.push(Err(err));
console.println(&format!("{} - Scrape at {} is failing", "✗".red(), colored_tracker_url));
}
}
}
}

async fn check_http_announce(tracker_url: &Url) -> Result<(), CheckError> {
let info_hash_str = "9c38422213e30bff212b30c360d26f9a02136422".to_string(); // # DevSkim: ignore DS173237
let info_hash = InfoHash::from_str(&info_hash_str).expect("a valid info-hash is required");

// todo: HTTP request could panic.For example, if the server is not accessible.
// We should change the client to catch that error and return a `CheckError`.
// Otherwise the checking process will stop. The idea is to process all checks
// and return a final report.
let response = Client::new(tracker_url.clone())
.announce(&QueryBuilder::with_default_values().with_info_hash(&info_hash).query())
.await;

if let Ok(body) = response.bytes().await {
if let Ok(_announce_response) = serde_bencode::from_bytes::<Announce>(&body) {
Ok(())
} else {
debug!("announce body {:#?}", body);
Err(CheckError::HttpError {
url: tracker_url.clone(),
})
}
} else {
Err(CheckError::HttpError {
url: tracker_url.clone(),
})
}
}

async fn check_http_scrape(url: &Url) -> Result<(), CheckError> {
let info_hashes: Vec<String> = vec!["9c38422213e30bff212b30c360d26f9a02136422".to_string()]; // # DevSkim: ignore DS173237
let query = requests::scrape::Query::try_from(info_hashes).expect("a valid array of info-hashes is required");

// todo: HTTP request could panic.For example, if the server is not accessible.
// We should change the client to catch that error and return a `CheckError`.
// Otherwise the checking process will stop. The idea is to process all checks
// and return a final report.
let response = Client::new(url.clone()).scrape(&query).await;

if let Ok(body) = response.bytes().await {
if let Ok(_scrape_response) = scrape::Response::try_from_bencoded(&body) {
Ok(())
} else {
debug!("scrape body {:#?}", body);
Err(CheckError::HttpError { url: url.clone() })
}
} else {
Err(CheckError::HttpError { url: url.clone() })
}
}
3 changes: 3 additions & 0 deletions src/console/clients/checker/checks/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod health;
pub mod http;
pub mod udp;
87 changes: 87 additions & 0 deletions src/console/clients/checker/checks/udp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use std::net::SocketAddr;

use aquatic_udp_protocol::{Port, TransactionId};
use colored::Colorize;
use hex_literal::hex;
use log::debug;

use crate::console::clients::checker::console::Console;
use crate::console::clients::checker::printer::Printer;
use crate::console::clients::checker::service::{CheckError, CheckResult};
use crate::console::clients::udp::checker;
use crate::shared::bit_torrent::info_hash::InfoHash;

const ASSIGNED_BY_OS: u16 = 0;
const RANDOM_TRANSACTION_ID: i32 = -888_840_697;

pub async fn run(udp_trackers: &Vec<SocketAddr>, console: &Console, check_results: &mut Vec<CheckResult>) {
console.println("UDP trackers ...");

for udp_tracker in udp_trackers {
debug!("UDP tracker: {:?}", udp_tracker);

let colored_tracker_url = udp_tracker.to_string().yellow();

let transaction_id = TransactionId(RANDOM_TRANSACTION_ID);

let mut client = checker::Client::default();

debug!("Bind and connect");

let Ok(bound_to) = client.bind_and_connect(ASSIGNED_BY_OS, udp_tracker).await else {
check_results.push(Err(CheckError::UdpError {
socket_addr: *udp_tracker,
}));
console.println(&format!("{} - Can't connect to socket {}", "✗".red(), colored_tracker_url));
break;
};

debug!("Send connection request");

let Ok(connection_id) = client.send_connection_request(transaction_id).await else {
check_results.push(Err(CheckError::UdpError {
socket_addr: *udp_tracker,
}));
console.println(&format!(
"{} - Can't make tracker connection request to {}",
"✗".red(),
colored_tracker_url
));
break;
};

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

debug!("Send announce request");

if (client
.send_announce_request(connection_id, transaction_id, info_hash, Port(bound_to.port()))
.await)
.is_ok()
{
check_results.push(Ok(()));
console.println(&format!("{} - Announce at {} is OK", "✓".green(), colored_tracker_url));
} else {
let err = CheckError::UdpError {
socket_addr: *udp_tracker,
};
check_results.push(Err(err));
console.println(&format!("{} - Announce at {} is failing", "✗".red(), colored_tracker_url));
}

debug!("Send scrape request");

let info_hashes = vec![InfoHash(hex!("9c38422213e30bff212b30c360d26f9a02136422"))]; // # DevSkim: ignore DS173237

if (client.send_scrape_request(connection_id, transaction_id, info_hashes).await).is_ok() {
check_results.push(Ok(()));
console.println(&format!("{} - Announce at {} is OK", "✓".green(), colored_tracker_url));
} else {
let err = CheckError::UdpError {
socket_addr: *udp_tracker,
};
check_results.push(Err(err));
console.println(&format!("{} - Announce at {} is failing", "✗".red(), colored_tracker_url));
}
}
}
1 change: 1 addition & 0 deletions src/console/clients/checker/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod app;
pub mod checks;
pub mod config;
pub mod console;
pub mod logger;
Expand Down
Loading

0 comments on commit 2bc2bff

Please sign in to comment.