Skip to content

Commit

Permalink
Merge #185: Axum HTTP tracker: announce request in public mode
Browse files Browse the repository at this point in the history
da638d6 docs(http): fix extractor docs (Jose Celano)
30918da refactor(http): [#184] move extractor to extractor mod (Jose Celano)
99dbbe4 refactor(http): [#184] extract announce service in Axum tracker (Jose Celano)
02e2516 feat(http): [#184] Axum extractor for peer IP (Jose Celano)
74ed592 feat(http): [#184] added optional params to announce req in Axum implementation (Jose Celano)
b1612f6 test(http): improve tests (Jose Celano)
3eb7475 feat(http): [#184] normal (non-compact) announce response in axum tracker (Jose Celano)
42bd313 feat: [#184] calculate remote client ip depending on whether the tracker is running on reverse proxy or not (Jose Celano)
8318057 feat: [#184] add dependency: axum-client-ip (Jose Celano)
f327dcf fix(http): [#184] bencoded error responses for announce request (Jose Celano)
d0c8eb0 refactor(http): reorganize mods (Jose Celano)
03024e2 refactor(http): extract function to get client IP on reverse proxy (Jose Celano)

Pull request description:

  It implements the `announce` request handler in the Axum HTTP tracker for `public` mode.

  - [x] Normal (non-compact) response with only mandatory params
  - [x] Optional params for `announce` request
  - [x] Return bencoded error when remote client IP cannot be obtained from the `X-Forwarded-For` header on reverse proxy mode. See commented tests.
  - [x] Refactor: extract service for handler body.

  Out of this PR scope:

  - [ ] Capture unexpected handler errors and convert them into bencoded generic HTTP tracker response errors. Moved to [parent issue](#160).
  - [ ] Compact response.

  **UPDATE**: 16/02/2023

Top commit has no ACKs.

Tree-SHA512: 76f77ed87f64ff6a488090e9013d92cbc516135a770206408a38bcba1c57ceb01154cd87b7169ed01ee12791c12d5e991ab59c4e53ba7c164a146292d6a94677
  • Loading branch information
josecelano committed Feb 20, 2023
2 parents 20f5751 + da638d6 commit 46e9e68
Show file tree
Hide file tree
Showing 33 changed files with 1,504 additions and 379 deletions.
28 changes: 28 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 @@ -60,6 +60,7 @@ aquatic_udp_protocol = "0.2"
uuid = { version = "1", features = ["v4"] }
axum = "0.6.1"
axum-server = { version = "0.4.4", features = ["tls-rustls"] }
axum-client-ip = "0.4.0"


[dev-dependencies]
Expand Down
159 changes: 0 additions & 159 deletions src/http/axum_implementation/extractors.rs

This file was deleted.

45 changes: 45 additions & 0 deletions src/http/axum_implementation/extractors/announce_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use std::panic::Location;

use axum::async_trait;
use axum::extract::FromRequestParts;
use axum::http::request::Parts;
use axum::response::{IntoResponse, Response};

use crate::http::axum_implementation::query::Query;
use crate::http::axum_implementation::requests::announce::{Announce, ParseAnnounceQueryError};
use crate::http::axum_implementation::responses;

pub struct ExtractRequest(pub Announce);

#[async_trait]
impl<S> FromRequestParts<S> for ExtractRequest
where
S: Send + Sync,
{
type Rejection = Response;

async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
let raw_query = parts.uri.query();

if raw_query.is_none() {
return Err(responses::error::Error::from(ParseAnnounceQueryError::MissingParams {
location: Location::caller(),
})
.into_response());
}

let query = raw_query.unwrap().parse::<Query>();

if let Err(error) = query {
return Err(responses::error::Error::from(error).into_response());
}

let announce_request = Announce::try_from(query.unwrap());

if let Err(error) = announce_request {
return Err(responses::error::Error::from(error).into_response());
}

Ok(ExtractRequest(announce_request.unwrap()))
}
}
3 changes: 3 additions & 0 deletions src/http/axum_implementation/extractors/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod announce_request;
pub mod peer_ip;
pub mod remote_client_ip;
52 changes: 52 additions & 0 deletions src/http/axum_implementation/extractors/peer_ip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use std::net::IpAddr;
use std::panic::Location;

use axum::response::{IntoResponse, Response};
use thiserror::Error;

use super::remote_client_ip::RemoteClientIp;
use crate::http::axum_implementation::responses;

#[derive(Error, Debug)]
pub enum ResolutionError {
#[error("missing the right most X-Forwarded-For IP (mandatory on reverse proxy tracker configuration) in {location}")]
MissingRightMostXForwardedForIp { location: &'static Location<'static> },
#[error("cannot get the client IP from the connection info in {location}")]
MissingClientIp { location: &'static Location<'static> },
}

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

/// It resolves the peer IP.
///
/// # Errors
///
/// Will return an error if the peer IP cannot be obtained according to the configuration.
/// For example, if the IP is extracted from an HTTP header which is missing in the request.
pub fn assign_ip_address_to_peer(on_reverse_proxy: bool, remote_client_ip: &RemoteClientIp) -> Result<IpAddr, Response> {
if on_reverse_proxy {
if let Some(ip) = remote_client_ip.right_most_x_forwarded_for {
Ok(ip)
} else {
Err(
responses::error::Error::from(ResolutionError::MissingRightMostXForwardedForIp {
location: Location::caller(),
})
.into_response(),
)
}
} else if let Some(ip) = remote_client_ip.connection_info_ip {
Ok(ip)
} else {
Err(responses::error::Error::from(ResolutionError::MissingClientIp {
location: Location::caller(),
})
.into_response())
}
}
51 changes: 51 additions & 0 deletions src/http/axum_implementation/extractors/remote_client_ip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use std::net::{IpAddr, SocketAddr};

use axum::async_trait;
use axum::extract::{ConnectInfo, FromRequestParts};
use axum::http::request::Parts;
use axum::response::Response;
use axum_client_ip::RightmostXForwardedFor;
use serde::{Deserialize, Serialize};

/// Given this request chain:
///
/// client <-> http proxy 1 <-> http proxy 2 <-> server
/// ip: 126.0.0.1 ip: 126.0.0.2 ip: 126.0.0.3 ip: 126.0.0.4
/// X-Forwarded-For: 126.0.0.1 X-Forwarded-For: 126.0.0.1,126.0.0.2
///
/// This extractor extracts these values from the HTTP headers and connection info.
///
/// `right_most_x_forwarded_for` = 126.0.0.2
/// `connection_info_ip` = 126.0.0.3
///
/// More info about inner extractors :<https://github.com/imbolc/axum-client-ip>
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct RemoteClientIp {
pub right_most_x_forwarded_for: Option<IpAddr>,
pub connection_info_ip: Option<IpAddr>,
}

#[async_trait]
impl<S> FromRequestParts<S> for RemoteClientIp
where
S: Send + Sync,
{
type Rejection = Response;

async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let right_most_x_forwarded_for = match RightmostXForwardedFor::from_request_parts(parts, state).await {
Ok(right_most_x_forwarded_for) => Some(right_most_x_forwarded_for.0),
Err(_) => None,
};

let connection_info_ip = match ConnectInfo::<SocketAddr>::from_request_parts(parts, state).await {
Ok(connection_info_socket_addr) => Some(connection_info_socket_addr.0.ip()),
Err(_) => None,
};

Ok(RemoteClientIp {
right_most_x_forwarded_for,
connection_info_ip,
})
}
}
Loading

0 comments on commit 46e9e68

Please sign in to comment.