Skip to content

Commit f8486a9

Browse files
committed
Merge #1474: Refactor remote client IP resolution code
e0162d1 refactor: the remote client IP resolution code (Jose Celano) Pull request description: Refactor the remote client IP resolution code. - [x] Extract `ReverseProxyMode` flag. - [x] Extract `RemoteClientAddr` type. - [x] Add IP wrapper `ResolvedIp` to track IP source. - [x] Move all code to the `http-protocol` package. - [x] Other improvements. ACKs for top commit: josecelano: ACK e0162d1 Tree-SHA512: 21721ac0fa334c747663dd239bae427bc7a1abf2bd9d8d04000ddb32265df3cb1ea9357b7e17e0d60a10094a49295e47a485b1f290e384e89a7f79a285603934
2 parents 0485a86 + e0162d1 commit f8486a9

File tree

6 files changed

+195
-173
lines changed

6 files changed

+195
-173
lines changed

packages/http-protocol/src/v1/services/peer_ip_resolver.rs

Lines changed: 141 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! This service resolves the peer IP from the request.
1+
//! This service resolves the remote client address.
22
//!
33
//! The peer IP is used to identify the peer in the tracker. It's the peer IP
44
//! that is used in the `announce` responses (peer list). And it's also used to
@@ -12,20 +12,65 @@
1212
//! X-Forwarded-For: 126.0.0.1 X-Forwarded-For: 126.0.0.1,126.0.0.2
1313
//! ```
1414
//!
15-
//! This service returns two options for the peer IP:
15+
//! This `ClientIpSources` contains two options for the peer IP:
1616
//!
1717
//! ```text
1818
//! right_most_x_forwarded_for = 126.0.0.2
1919
//! connection_info_ip = 126.0.0.3
2020
//! ```
2121
//!
22-
//! Depending on the tracker configuration.
22+
//! Which one to use depends on the `ReverseProxyMode`.
2323
use std::net::{IpAddr, SocketAddr};
2424
use std::panic::Location;
2525

2626
use serde::{Deserialize, Serialize};
2727
use thiserror::Error;
2828

29+
/// Resolves the client's real address considering proxy headers. Port is also
30+
/// included when available.
31+
///
32+
/// # Errors
33+
///
34+
/// This function returns an error if the IP address cannot be resolved.
35+
pub fn resolve_remote_client_addr(
36+
reverse_proxy_mode: &ReverseProxyMode,
37+
client_ip_sources: &ClientIpSources,
38+
) -> Result<RemoteClientAddr, PeerIpResolutionError> {
39+
let ip = match reverse_proxy_mode {
40+
ReverseProxyMode::Enabled => ResolvedIp::FromXForwardedFor(client_ip_sources.try_client_ip_from_proxy_header()?),
41+
ReverseProxyMode::Disabled => ResolvedIp::FromSocketAddr(client_ip_sources.try_client_ip_from_connection_info()?),
42+
};
43+
44+
let port = client_ip_sources.client_port_from_connection_info();
45+
46+
Ok(RemoteClientAddr::new(ip, port))
47+
}
48+
49+
/// This struct indicates whether the tracker is running on reverse proxy mode.
50+
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
51+
pub enum ReverseProxyMode {
52+
Enabled,
53+
Disabled,
54+
}
55+
56+
impl From<ReverseProxyMode> for bool {
57+
fn from(reverse_proxy_mode: ReverseProxyMode) -> Self {
58+
match reverse_proxy_mode {
59+
ReverseProxyMode::Enabled => true,
60+
ReverseProxyMode::Disabled => false,
61+
}
62+
}
63+
}
64+
65+
impl From<bool> for ReverseProxyMode {
66+
fn from(reverse_proxy_mode: bool) -> Self {
67+
if reverse_proxy_mode {
68+
ReverseProxyMode::Enabled
69+
} else {
70+
ReverseProxyMode::Disabled
71+
}
72+
}
73+
}
2974
/// This struct contains the sources from which the peer IP can be obtained.
3075
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
3176
pub struct ClientIpSources {
@@ -36,6 +81,36 @@ pub struct ClientIpSources {
3681
pub connection_info_socket_address: Option<SocketAddr>,
3782
}
3883

84+
impl ClientIpSources {
85+
fn try_client_ip_from_connection_info(&self) -> Result<IpAddr, PeerIpResolutionError> {
86+
if let Some(socket_addr) = self.connection_info_socket_address {
87+
Ok(socket_addr.ip())
88+
} else {
89+
Err(PeerIpResolutionError::MissingClientIp {
90+
location: Location::caller(),
91+
})
92+
}
93+
}
94+
95+
fn try_client_ip_from_proxy_header(&self) -> Result<IpAddr, PeerIpResolutionError> {
96+
if let Some(ip) = self.right_most_x_forwarded_for {
97+
Ok(ip)
98+
} else {
99+
Err(PeerIpResolutionError::MissingRightMostXForwardedForIp {
100+
location: Location::caller(),
101+
})
102+
}
103+
}
104+
105+
fn client_port_from_connection_info(&self) -> Option<u16> {
106+
if self.connection_info_socket_address.is_some() {
107+
self.connection_info_socket_address.map(|socket_addr| socket_addr.port())
108+
} else {
109+
None
110+
}
111+
}
112+
}
113+
39114
/// The error that can occur when resolving the peer IP.
40115
#[derive(Error, Debug, Clone)]
41116
pub enum PeerIpResolutionError {
@@ -54,120 +129,79 @@ pub enum PeerIpResolutionError {
54129
MissingClientIp { location: &'static Location<'static> },
55130
}
56131

57-
/// Resolves the peer IP from the request.
58-
///
59-
/// Given the sources from which the peer IP can be obtained, this function
60-
/// resolves the peer IP according to the tracker configuration.
61-
///
62-
/// With the tracker running on reverse proxy mode:
63-
///
64-
/// ```rust
65-
/// use std::net::IpAddr;
66-
/// use std::str::FromStr;
67-
///
68-
/// use bittorrent_http_tracker_protocol::v1::services::peer_ip_resolver::{invoke, ClientIpSources, PeerIpResolutionError};
69-
///
70-
/// let on_reverse_proxy = true;
71-
///
72-
/// let ip = invoke(
73-
/// on_reverse_proxy,
74-
/// &ClientIpSources {
75-
/// right_most_x_forwarded_for: Some(IpAddr::from_str("203.0.113.195").unwrap()),
76-
/// connection_info_socket_address: None,
77-
/// },
78-
/// )
79-
/// .unwrap();
80-
///
81-
/// assert_eq!(ip, IpAddr::from_str("203.0.113.195").unwrap());
82-
/// ```
83-
///
84-
/// With the tracker non running on reverse proxy mode:
85-
///
86-
/// ```rust
87-
/// use std::net::{IpAddr,Ipv4Addr,SocketAddr};
88-
/// use std::str::FromStr;
89-
///
90-
/// use bittorrent_http_tracker_protocol::v1::services::peer_ip_resolver::{invoke, ClientIpSources, PeerIpResolutionError};
91-
///
92-
/// let on_reverse_proxy = false;
93-
///
94-
/// let ip = invoke(
95-
/// on_reverse_proxy,
96-
/// &ClientIpSources {
97-
/// right_most_x_forwarded_for: None,
98-
/// connection_info_socket_address: Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 195)), 8080))
99-
/// },
100-
/// )
101-
/// .unwrap();
102-
///
103-
/// assert_eq!(ip, IpAddr::from_str("203.0.113.195").unwrap());
104-
/// ```
105-
///
106-
/// # Errors
107-
///
108-
/// Will return an error if the peer IP cannot be obtained according to the configuration.
109-
/// For example, if the IP is extracted from an HTTP header which is missing in the request.
110-
pub fn invoke(on_reverse_proxy: bool, client_ip_sources: &ClientIpSources) -> Result<IpAddr, PeerIpResolutionError> {
111-
if on_reverse_proxy {
112-
resolve_peer_ip_on_reverse_proxy(client_ip_sources)
113-
} else {
114-
resolve_peer_ip_without_reverse_proxy(client_ip_sources)
115-
}
132+
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
133+
pub struct RemoteClientAddr {
134+
ip: ResolvedIp,
135+
port: Option<u16>,
116136
}
117137

118-
fn resolve_peer_ip_without_reverse_proxy(remote_client_ip: &ClientIpSources) -> Result<IpAddr, PeerIpResolutionError> {
119-
if let Some(socket_addr) = remote_client_ip.connection_info_socket_address {
120-
Ok(socket_addr.ip())
121-
} else {
122-
Err(PeerIpResolutionError::MissingClientIp {
123-
location: Location::caller(),
124-
})
138+
impl RemoteClientAddr {
139+
#[must_use]
140+
pub fn new(ip: ResolvedIp, port: Option<u16>) -> Self {
141+
Self { ip, port }
142+
}
143+
144+
#[must_use]
145+
pub fn ip(&self) -> IpAddr {
146+
match self.ip {
147+
ResolvedIp::FromSocketAddr(ip) | ResolvedIp::FromXForwardedFor(ip) => ip,
148+
}
125149
}
126-
}
127150

128-
fn resolve_peer_ip_on_reverse_proxy(remote_client_ip: &ClientIpSources) -> Result<IpAddr, PeerIpResolutionError> {
129-
if let Some(ip) = remote_client_ip.right_most_x_forwarded_for {
130-
Ok(ip)
131-
} else {
132-
Err(PeerIpResolutionError::MissingRightMostXForwardedForIp {
133-
location: Location::caller(),
134-
})
151+
#[must_use]
152+
pub fn port(&self) -> Option<u16> {
153+
self.port
135154
}
136155
}
137156

157+
/// This enum indicates the source of the resolved IP address.
158+
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
159+
pub enum ResolvedIp {
160+
FromXForwardedFor(IpAddr),
161+
FromSocketAddr(IpAddr),
162+
}
163+
138164
#[cfg(test)]
139165
mod tests {
140-
use super::invoke;
166+
use super::resolve_remote_client_addr;
141167

142168
mod working_without_reverse_proxy {
143169
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
144170
use std::str::FromStr;
145171

146-
use super::invoke;
147-
use crate::v1::services::peer_ip_resolver::{ClientIpSources, PeerIpResolutionError};
172+
use super::resolve_remote_client_addr;
173+
use crate::v1::services::peer_ip_resolver::{
174+
ClientIpSources, PeerIpResolutionError, RemoteClientAddr, ResolvedIp, ReverseProxyMode,
175+
};
148176

149177
#[test]
150-
fn it_should_get_the_peer_ip_from_the_connection_info() {
151-
let on_reverse_proxy = false;
178+
fn it_should_get_the_remote_client_address_from_the_connection_info() {
179+
let reverse_proxy_mode = ReverseProxyMode::Disabled;
152180

153-
let ip = invoke(
154-
on_reverse_proxy,
181+
let ip = resolve_remote_client_addr(
182+
&reverse_proxy_mode,
155183
&ClientIpSources {
156184
right_most_x_forwarded_for: None,
157185
connection_info_socket_address: Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 195)), 8080)),
158186
},
159187
)
160188
.unwrap();
161189

162-
assert_eq!(ip, IpAddr::from_str("203.0.113.195").unwrap());
190+
assert_eq!(
191+
ip,
192+
RemoteClientAddr::new(
193+
ResolvedIp::FromSocketAddr(IpAddr::from_str("203.0.113.195").unwrap()),
194+
Some(8080)
195+
)
196+
);
163197
}
164198

165199
#[test]
166-
fn it_should_return_an_error_if_it_cannot_get_the_peer_ip_from_the_connection_info() {
167-
let on_reverse_proxy = false;
200+
fn it_should_return_an_error_if_it_cannot_get_the_remote_client_ip_from_the_connection_info() {
201+
let reverse_proxy_mode = ReverseProxyMode::Disabled;
168202

169-
let error = invoke(
170-
on_reverse_proxy,
203+
let error = resolve_remote_client_addr(
204+
&reverse_proxy_mode,
171205
&ClientIpSources {
172206
right_most_x_forwarded_for: None,
173207
connection_info_socket_address: None,
@@ -179,34 +213,42 @@ mod tests {
179213
}
180214
}
181215

182-
mod working_on_reverse_proxy {
216+
mod working_on_reverse_proxy_mode {
183217
use std::net::IpAddr;
184218
use std::str::FromStr;
185219

186-
use crate::v1::services::peer_ip_resolver::{invoke, ClientIpSources, PeerIpResolutionError};
220+
use crate::v1::services::peer_ip_resolver::{
221+
resolve_remote_client_addr, ClientIpSources, PeerIpResolutionError, RemoteClientAddr, ResolvedIp, ReverseProxyMode,
222+
};
187223

188224
#[test]
189-
fn it_should_get_the_peer_ip_from_the_right_most_ip_in_the_x_forwarded_for_header() {
190-
let on_reverse_proxy = true;
225+
fn it_should_get_the_remote_client_ip_from_the_right_most_ip_in_the_x_forwarded_for_header() {
226+
let reverse_proxy_mode = ReverseProxyMode::Enabled;
191227

192-
let ip = invoke(
193-
on_reverse_proxy,
228+
let ip = resolve_remote_client_addr(
229+
&reverse_proxy_mode,
194230
&ClientIpSources {
195231
right_most_x_forwarded_for: Some(IpAddr::from_str("203.0.113.195").unwrap()),
196232
connection_info_socket_address: None,
197233
},
198234
)
199235
.unwrap();
200236

201-
assert_eq!(ip, IpAddr::from_str("203.0.113.195").unwrap());
237+
assert_eq!(
238+
ip,
239+
RemoteClientAddr::new(
240+
ResolvedIp::FromXForwardedFor(IpAddr::from_str("203.0.113.195").unwrap()),
241+
None
242+
)
243+
);
202244
}
203245

204246
#[test]
205247
fn it_should_return_an_error_if_it_cannot_get_the_right_most_ip_from_the_x_forwarded_for_header() {
206-
let on_reverse_proxy = true;
248+
let reverse_proxy_mode = ReverseProxyMode::Enabled;
207249

208-
let error = invoke(
209-
on_reverse_proxy,
250+
let error = resolve_remote_client_addr(
251+
&reverse_proxy_mode,
210252
&ClientIpSources {
211253
right_most_x_forwarded_for: None,
212254
connection_info_socket_address: None,

packages/http-tracker-core/src/event/mod.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
use std::net::{IpAddr, SocketAddr};
22

3+
use bittorrent_http_tracker_protocol::v1::services::peer_ip_resolver::RemoteClientAddr;
34
use bittorrent_primitives::info_hash::InfoHash;
45
use torrust_tracker_metrics::label::{LabelSet, LabelValue};
56
use torrust_tracker_metrics::label_name;
67
use torrust_tracker_primitives::peer::PeerAnnouncement;
78
use torrust_tracker_primitives::service_binding::ServiceBinding;
89

9-
use crate::services::RemoteClientAddr;
10-
1110
pub mod sender;
1211

1312
/// A HTTP core event.
@@ -64,12 +63,12 @@ pub struct ClientConnectionContext {
6463
impl ClientConnectionContext {
6564
#[must_use]
6665
pub fn ip_addr(&self) -> IpAddr {
67-
self.remote_client_addr.ip
66+
self.remote_client_addr.ip()
6867
}
6968

7069
#[must_use]
7170
pub fn port(&self) -> Option<u16> {
72-
self.remote_client_addr.port
71+
self.remote_client_addr.port()
7372
}
7473
}
7574

@@ -100,11 +99,11 @@ impl From<ConnectionContext> for LabelSet {
10099
#[cfg(test)]
101100
pub mod test {
102101

102+
use bittorrent_http_tracker_protocol::v1::services::peer_ip_resolver::{RemoteClientAddr, ResolvedIp};
103103
use torrust_tracker_primitives::peer::Peer;
104104
use torrust_tracker_primitives::service_binding::Protocol;
105105

106106
use super::Event;
107-
use crate::services::RemoteClientAddr;
108107
use crate::tests::sample_info_hash;
109108

110109
#[must_use]
@@ -153,7 +152,7 @@ pub mod test {
153152

154153
let event1 = Event::TcpAnnounce {
155154
connection: ConnectionContext::new(
156-
RemoteClientAddr::new(remote_client_ip, Some(8080)),
155+
RemoteClientAddr::new(ResolvedIp::FromSocketAddr(remote_client_ip), Some(8080)),
157156
ServiceBinding::new(Protocol::HTTP, SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7070)).unwrap(),
158157
),
159158
info_hash,
@@ -162,7 +161,10 @@ pub mod test {
162161

163162
let event2 = Event::TcpAnnounce {
164163
connection: ConnectionContext::new(
165-
RemoteClientAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), Some(8080)),
164+
RemoteClientAddr::new(
165+
ResolvedIp::FromSocketAddr(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2))),
166+
Some(8080),
167+
),
166168
ServiceBinding::new(Protocol::HTTP, SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7070)).unwrap(),
167169
),
168170
info_hash,

0 commit comments

Comments
 (0)