Skip to content

Commit

Permalink
Merge #198: Axum HTTP tracker: announce request in private mode
Browse files Browse the repository at this point in the history
f3afab1 feat(http): [#195] announce request in private mode (Jose Celano)

Pull request description:

  For the new Auxm HTTP tracker implementation.

Top commit has no ACKs.

Tree-SHA512: 9b2c7fc06511e8293c4acbbfa6c62d029c87ab64b18cb4c09630767a3869f44aa3e1e3f952cd228f684f731141574a91dcdee8942af1e5cee120a2d6ec9bdd95
  • Loading branch information
josecelano committed Feb 28, 2023
2 parents 2c4c32f + f3afab1 commit c8aba86
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 40 deletions.
43 changes: 37 additions & 6 deletions src/http/axum_implementation/handlers/announce.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,70 @@
use std::net::{IpAddr, SocketAddr};
use std::panic::Location;
use std::sync::Arc;

use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes};
use axum::extract::State;
use axum::extract::{Path, State};
use axum::response::{IntoResponse, Response};
use log::debug;

use crate::http::axum_implementation::extractors::announce_request::ExtractRequest;
use crate::http::axum_implementation::extractors::peer_ip;
use crate::http::axum_implementation::extractors::remote_client_ip::RemoteClientIp;
use crate::http::axum_implementation::handlers::auth;
use crate::http::axum_implementation::requests::announce::{Announce, Compact, Event};
use crate::http::axum_implementation::responses::announce;
use crate::http::axum_implementation::responses::{self, announce};
use crate::http::axum_implementation::services;
use crate::protocol::clock::{Current, Time};
use crate::tracker::auth::KeyId;
use crate::tracker::peer::Peer;
use crate::tracker::Tracker;

#[allow(clippy::unused_async)]
pub async fn handle(
pub async fn handle_without_key(
State(tracker): State<Arc<Tracker>>,
ExtractRequest(announce_request): ExtractRequest,
remote_client_ip: RemoteClientIp,
) -> Response {
debug!("http announce request: {:#?}", announce_request);

let peer_ip = match peer_ip::resolve(tracker.config.on_reverse_proxy, &remote_client_ip) {
if tracker.is_private() {
return responses::error::Error::from(auth::Error::MissingAuthKey {
location: Location::caller(),
})
.into_response();
}

handle(&tracker, &announce_request, &remote_client_ip).await
}

#[allow(clippy::unused_async)]
pub async fn handle_with_key(
State(tracker): State<Arc<Tracker>>,
ExtractRequest(announce_request): ExtractRequest,
Path(key_id): Path<KeyId>,
remote_client_ip: RemoteClientIp,
) -> Response {
debug!("http announce request: {:#?}", announce_request);

match auth::authenticate(&key_id, &tracker).await {
Ok(_) => (),
Err(error) => return responses::error::Error::from(error).into_response(),
}

handle(&tracker, &announce_request, &remote_client_ip).await
}

async fn handle(tracker: &Arc<Tracker>, announce_request: &Announce, remote_client_ip: &RemoteClientIp) -> Response {
let peer_ip = match peer_ip::resolve(tracker.config.on_reverse_proxy, remote_client_ip) {
Ok(peer_ip) => peer_ip,
Err(err) => return err,
};

let mut peer = peer_from_request(&announce_request, &peer_ip);
let mut peer = peer_from_request(announce_request, &peer_ip);

let announce_data = services::announce::invoke(tracker.clone(), announce_request.info_hash, &mut peer).await;

match announce_request.compact {
match &announce_request.compact {
Some(compact) => match compact {
Compact::Accepted => announce::Compact::from(announce_data).into_response(),
Compact::NotAccepted => announce::NonCompact::from(announce_data).into_response(),
Expand Down
41 changes: 41 additions & 0 deletions src/http/axum_implementation/handlers/auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use std::panic::Location;
use std::sync::Arc;

use thiserror::Error;

use crate::http::axum_implementation::responses;
use crate::tracker::auth::{self, KeyId};
use crate::tracker::Tracker;

#[derive(Debug, Error)]
pub enum Error {
#[error("Missing authentication key for private tracker. Error in {location}")]
MissingAuthKey { location: &'static Location<'static> },
}

/// # Errors
///
/// Will return an error if the the authentication key cannot be verified.
pub async fn authenticate(key_id: &KeyId, tracker: &Arc<Tracker>) -> Result<(), auth::Error> {
if tracker.is_private() {
tracker.verify_auth_key(key_id).await
} else {
Ok(())
}
}

impl From<Error> for responses::error::Error {
fn from(err: Error) -> Self {
responses::error::Error {
failure_reason: format!("Authentication error: {err}"),
}
}
}

impl From<auth::Error> for responses::error::Error {
fn from(err: auth::Error) -> Self {
responses::error::Error {
failure_reason: format!("Authentication error: {err}"),
}
}
}
1 change: 1 addition & 0 deletions src/http/axum_implementation/handlers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod announce;
pub mod auth;
pub mod scrape;
pub mod status;
3 changes: 2 additions & 1 deletion src/http/axum_implementation/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ pub fn router(tracker: &Arc<Tracker>) -> Router {
// Status
.route("/status", get(status::handle))
// Announce request
.route("/announce", get(announce::handle).with_state(tracker.clone()))
.route("/announce", get(announce::handle_without_key).with_state(tracker.clone()))
.route("/announce/:key", get(announce::handle_with_key).with_state(tracker.clone()))
// Scrape request
.route("/scrape", get(scrape::handle).with_state(tracker.clone()))
// Add extension to get the client IP from the connection info
Expand Down
22 changes: 6 additions & 16 deletions tests/http/asserts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,22 +140,6 @@ pub async fn assert_torrent_not_in_whitelist_error_response(response: Response)
assert_bencoded_error(&response.text().await.unwrap(), "is not whitelisted", Location::caller());
}

pub async fn assert_peer_not_authenticated_error_response(response: Response) {
assert_eq!(response.status(), 200);

assert_bencoded_error(
&response.text().await.unwrap(),
"The peer is not authenticated",
Location::caller(),
);
}

pub async fn assert_invalid_authentication_key_error_response(response: Response) {
assert_eq!(response.status(), 200);

assert_bencoded_error(&response.text().await.unwrap(), "is not valid", Location::caller());
}

pub async fn assert_could_not_find_remote_address_on_xff_header_error_response(response: Response) {
assert_eq!(response.status(), 200);

Expand Down Expand Up @@ -199,3 +183,9 @@ pub async fn assert_cannot_parse_query_params_error_response(response: Response,
Location::caller(),
);
}

pub async fn assert_authentication_error_response(response: Response) {
assert_eq!(response.status(), 200);

assert_bencoded_error(&response.text().await.unwrap(), "Authentication error", Location::caller());
}
19 changes: 19 additions & 0 deletions tests/http/asserts_warp.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use std::panic::Location;

/// todo: this mod should be removed when we remove the Warp implementation for the HTTP tracker.
use reqwest::Response;

use super::responses::announce_warp::WarpAnnounce;
use crate::http::asserts::assert_bencoded_error;

pub async fn assert_warp_announce_response(response: Response, expected_announce_response: &WarpAnnounce) {
assert_eq!(response.status(), 200);
Expand All @@ -13,3 +16,19 @@ pub async fn assert_warp_announce_response(response: Response, expected_announce

assert_eq!(announce_response, *expected_announce_response);
}

pub async fn assert_warp_peer_not_authenticated_error_response(response: Response) {
assert_eq!(response.status(), 200);

assert_bencoded_error(
&response.text().await.unwrap(),
"The peer is not authenticated",
Location::caller(),
);
}

pub async fn assert_warp_invalid_authentication_key_error_response(response: Response) {
assert_eq!(response.status(), 200);

assert_bencoded_error(&response.text().await.unwrap(), "is not valid", Location::caller());
}
28 changes: 11 additions & 17 deletions tests/http_tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1085,9 +1085,9 @@ mod warp_http_tracker_server {
use torrust_tracker::protocol::info_hash::InfoHash;
use torrust_tracker::tracker::auth::KeyId;

use crate::http::asserts::{
assert_invalid_authentication_key_error_response, assert_is_announce_response,
assert_peer_not_authenticated_error_response,
use crate::http::asserts::assert_is_announce_response;
use crate::http::asserts_warp::{
assert_warp_invalid_authentication_key_error_response, assert_warp_peer_not_authenticated_error_response,
};
use crate::http::client::Client;
use crate::http::requests::announce::QueryBuilder;
Expand Down Expand Up @@ -1120,7 +1120,7 @@ mod warp_http_tracker_server {
.announce(&QueryBuilder::default().with_info_hash(&info_hash).query())
.await;

assert_peer_not_authenticated_error_response(response).await;
assert_warp_peer_not_authenticated_error_response(response).await;
}

#[tokio::test]
Expand All @@ -1134,7 +1134,7 @@ mod warp_http_tracker_server {
.announce(&QueryBuilder::default().query())
.await;

assert_invalid_authentication_key_error_response(response).await;
assert_warp_invalid_authentication_key_error_response(response).await;
}
}

Expand Down Expand Up @@ -2539,16 +2539,12 @@ mod axum_http_tracker_server {
use torrust_tracker::protocol::info_hash::InfoHash;
use torrust_tracker::tracker::auth::KeyId;

use crate::http::asserts::{
assert_invalid_authentication_key_error_response, assert_is_announce_response,
assert_peer_not_authenticated_error_response,
};
use crate::http::asserts::{assert_authentication_error_response, assert_is_announce_response};
use crate::http::client::Client;
use crate::http::requests::announce::QueryBuilder;
use crate::http::server::start_private_http_tracker;

//#[tokio::test]
#[allow(dead_code)]
#[tokio::test]
async fn should_respond_to_authenticated_peers() {
let http_tracker_server = start_private_http_tracker(Version::Axum).await;

Expand All @@ -2565,8 +2561,7 @@ mod axum_http_tracker_server {
assert_is_announce_response(response).await;
}

//#[tokio::test]
#[allow(dead_code)]
#[tokio::test]
async fn should_fail_if_the_peer_has_not_provided_the_authentication_key() {
let http_tracker_server = start_private_http_tracker(Version::Axum).await;

Expand All @@ -2576,11 +2571,10 @@ mod axum_http_tracker_server {
.announce(&QueryBuilder::default().with_info_hash(&info_hash).query())
.await;

assert_peer_not_authenticated_error_response(response).await;
assert_authentication_error_response(response).await;
}

//#[tokio::test]
#[allow(dead_code)]
#[tokio::test]
async fn should_fail_if_the_peer_authentication_key_is_not_valid() {
let http_tracker_server = start_private_http_tracker(Version::Axum).await;

Expand All @@ -2591,7 +2585,7 @@ mod axum_http_tracker_server {
.announce(&QueryBuilder::default().query())
.await;

assert_invalid_authentication_key_error_response(response).await;
assert_authentication_error_response(response).await;
}
}

Expand Down

0 comments on commit c8aba86

Please sign in to comment.