Skip to content

Commit 0692891

Browse files
authored
Merge pull request #152 from dfinity/igor/fix-ip
fix(BOUN-1497): fix client IP addr resolution
2 parents 1332a89 + a5e8f13 commit 0692891

File tree

9 files changed

+94
-33
lines changed

9 files changed

+94
-33
lines changed

Cargo.lock

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ name = "ic-gateway"
33
version = "0.2.0"
44
description = "HTTP-to-IC gateway"
55
edition = "2024"
6+
default-run = "ic-gateway"
67

78
[features]
89
default = []

src/metrics/mod.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ use prometheus::{
3333
use tower_http::compression::CompressionLayer;
3434
use tracing::info;
3535

36-
use crate::core::{ENV, HOSTNAME};
36+
use crate::{
37+
core::{ENV, HOSTNAME},
38+
routing::RemoteAddr,
39+
};
3740

3841
use crate::routing::{
3942
CanisterId, RequestCtx,
@@ -167,10 +170,16 @@ pub async fn middleware(
167170
State(state): State<Arc<HttpMetrics>>,
168171
Extension(conn_info): Extension<Arc<ConnInfo>>,
169172
Extension(request_id): Extension<RequestId>,
170-
request: Request,
173+
mut request: Request,
171174
next: Next,
172175
) -> impl IntoResponse {
176+
let remote_addr = request.extensions_mut().remove::<RemoteAddr>();
173177
let tls_info = request.extensions().get::<Arc<TlsInfo>>().cloned();
178+
let country_code = request
179+
.extensions_mut()
180+
.remove::<CountryCode>()
181+
.map(|x| x.0)
182+
.unwrap_or_default();
174183

175184
// Prepare to execute the request and count its body size
176185
let (parts, body) = request.into_parts();
@@ -220,11 +229,6 @@ pub async fn middleware(
220229
let canister_id = response.extensions_mut().get::<CanisterId>().copied();
221230
let error_cause = response.extensions_mut().remove::<ErrorCause>();
222231
let ic_status = response.extensions_mut().remove::<IcResponseStatus>();
223-
let country_code = response
224-
.extensions_mut()
225-
.remove::<CountryCode>()
226-
.map(|x| x.0)
227-
.unwrap_or_default();
228232
let status = response.status().as_u16();
229233

230234
// IC request metadata
@@ -338,7 +342,7 @@ pub async fn middleware(
338342
let conn_rcvd = conn_info.traffic.rcvd();
339343
let conn_sent = conn_info.traffic.sent();
340344
let conn_reqs = conn_info.req_count();
341-
let remote_addr = conn_info.remote_addr.ip().to_string();
345+
let remote_addr = remote_addr.map(|x| x.to_string()).unwrap_or_default();
342346
let request_id_str = request_id.to_string();
343347

344348
let (ic_http_streaming, ic_http_upgrade) = ic_status.as_ref().map_or((false, false), |x| {

src/routing/middleware/geoip.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ use axum::{
66
middleware::Next,
77
response::Response,
88
};
9-
use ic_bn_lib::http::middleware::extract_ip_from_request;
109
use maxminddb::geoip2;
1110
use tracing::warn;
1211

12+
use crate::routing::RemoteAddr;
13+
1314
#[derive(Clone, Debug)]
1415
pub struct CountryCode(pub String);
1516

@@ -44,17 +45,19 @@ pub async fn middleware(
4445
mut request: Request,
4546
next: Next,
4647
) -> Response {
47-
let ip = extract_ip_from_request(&request);
48+
let remote_addr = request.extensions().get::<RemoteAddr>().copied();
4849

4950
// Lookup code
50-
let country_code = ip.map(|x| geoip.lookup(x));
51+
let country_code = remote_addr.and_then(|x| geoip.lookup(*x));
5152

5253
if let Some(v) = &country_code {
5354
request.extensions_mut().insert(v.clone());
5455
}
5556

57+
#[allow(unused_mut)]
5658
let mut response = next.run(request).await;
5759

60+
#[cfg(test)]
5861
if let Some(v) = country_code {
5962
response.extensions_mut().insert(v);
6063
}

src/routing/middleware/headers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const HEADERS_REMOVE: [HeaderName; 12] = [
2424
X_IC_COUNTRY_CODE,
2525
];
2626

27-
// Add various headers
27+
// Add various headers to the response
2828
pub async fn middleware(request: Request, next: Next) -> Response {
2929
let mut response = next.run(request).await;
3030

src/routing/middleware/request_id.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ use axum::{
88
use bytes::Bytes;
99
use derive_new::new;
1010
use http::header::HeaderValue;
11-
use ic_bn_lib::http::headers::X_REQUEST_ID;
11+
use ic_bn_lib::http::{headers::X_REQUEST_ID, middleware::extract_ip_from_request};
1212
use uuid::Uuid;
1313

14+
use crate::routing::RemoteAddr;
15+
1416
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1517
pub struct RequestId(pub Uuid);
1618

@@ -42,7 +44,7 @@ pub async fn middleware(
4244
mut request: Request,
4345
next: Next,
4446
) -> Response {
45-
// Try to get & parse incoming UUID if it's there
47+
// Try to get & parse incoming request UUID if it's there
4648
let request_id = if let Some(v) = extract_request_id(&request)
4749
&& state.trust_incoming
4850
{
@@ -51,16 +53,28 @@ pub async fn middleware(
5153
RequestId(Uuid::now_v7())
5254
};
5355

56+
// Extract client's IP
57+
let remote_addr = extract_ip_from_request(&request).map(RemoteAddr);
58+
if let Some(v) = remote_addr {
59+
request.extensions_mut().insert(v);
60+
}
61+
5462
let hdr = HeaderValue::from_maybe_shared(Bytes::from(request_id.to_string())).unwrap();
5563

5664
request.extensions_mut().insert(request_id);
5765
request.headers_mut().insert(X_REQUEST_ID, hdr.clone());
5866

5967
let mut response = next.run(request).await;
60-
61-
response.extensions_mut().insert(request_id);
6268
response.headers_mut().insert(X_REQUEST_ID, hdr);
6369

70+
#[cfg(test)]
71+
{
72+
response.extensions_mut().insert(request_id);
73+
if let Some(v) = remote_addr {
74+
response.extensions_mut().insert(v);
75+
}
76+
}
77+
6478
response
6579
}
6680

src/routing/mod.rs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ pub mod ic;
44
pub mod middleware;
55
pub mod proxy;
66

7-
use std::{ops::Deref, str::FromStr, sync::Arc, time::Duration};
7+
use std::{net::IpAddr, ops::Deref, str::FromStr, sync::Arc, time::Duration};
88

99
use anyhow::{Context, Error};
1010
use axum::{
@@ -181,6 +181,18 @@ impl TypeExtractor for RequestTypeExtractor {
181181
}
182182
}
183183

184+
/// Client address
185+
#[derive(Debug, Clone, Copy)]
186+
pub struct RemoteAddr(pub IpAddr);
187+
188+
impl Deref for RemoteAddr {
189+
type Target = IpAddr;
190+
191+
fn deref(&self) -> &Self::Target {
192+
&self.0
193+
}
194+
}
195+
184196
// TODO: make it less horrible by using maybe builder pattern or just a struct
185197
#[allow(clippy::too_many_arguments)]
186198
#[allow(clippy::cognitive_complexity)]
@@ -534,12 +546,12 @@ pub async fn setup_router(
534546
request_type_state,
535547
request_type::middleware,
536548
))
549+
.layer(geoip_mw)
537550
.layer(metrics_mw)
538551
.layer(option_layer(waf_layer))
539552
.layer(load_shedder_system_mw)
540553
.layer(from_fn_with_state(validate_state, validate::middleware))
541554
.layer(concurrency_limit_mw)
542-
.layer(geoip_mw)
543555
.layer(load_shedder_latency_mw);
544556

545557
let api_hostname = cli.api.api_hostname.clone().map(|x| x.to_string());
@@ -634,15 +646,20 @@ pub async fn setup_router(
634646

635647
#[cfg(test)]
636648
mod test {
637-
use crate::test::setup_test_router;
649+
use crate::{
650+
routing::middleware::{geoip::CountryCode, request_id::RequestId},
651+
test::setup_test_router,
652+
};
638653

639654
use super::*;
640655
use axum::body::{Body, to_bytes};
641-
use http::Uri;
656+
use http::{HeaderValue, Uri};
657+
use ic_bn_lib::http::headers::{X_REAL_IP, X_REQUEST_ID};
642658
use ic_bn_lib_common::types::http::ConnInfo;
643659
use rand::{seq::SliceRandom, thread_rng};
644660
use std::str::FromStr;
645661
use tower::Service;
662+
use uuid::Uuid;
646663

647664
#[test]
648665
fn test_request_type() {
@@ -704,11 +721,30 @@ mod test {
704721
let mut req = axum::extract::Request::new(Body::from(""));
705722
*req.uri_mut() = Uri::try_from(format!("http://{domain}")).unwrap();
706723
let conn_info = Arc::new(ConnInfo::default());
707-
(*req.extensions_mut()).insert(conn_info);
724+
req.extensions_mut().insert(conn_info);
725+
// Some Swiss IP
726+
let remote_addr = IpAddr::from_str("77.109.180.4").unwrap();
727+
req.headers_mut().insert(
728+
X_REAL_IP,
729+
HeaderValue::from_str(&remote_addr.to_string()).unwrap(),
730+
);
731+
732+
// Send request id
733+
let request_id = Uuid::from_str("7373F02E-9560-4E16-AC6D-4974300C827B").unwrap();
734+
req.headers_mut().insert(
735+
X_REQUEST_ID,
736+
HeaderValue::from_str(&request_id.to_string()).unwrap(),
737+
);
708738

709739
// Make sure that we get right answer
710740
let resp = router.call(req).await.unwrap();
711741
assert_eq!(resp.status(), StatusCode::OK);
742+
assert_eq!(
743+
resp.extensions().get::<RemoteAddr>().unwrap().0,
744+
remote_addr,
745+
);
746+
assert_eq!(resp.extensions().get::<CountryCode>().unwrap().0, "CH");
747+
assert_eq!(resp.extensions().get::<RequestId>().unwrap().0, request_id);
712748

713749
let body = resp.into_body();
714750
let body = to_bytes(body, 1024).await.unwrap();

src/test.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ pub async fn setup_test_router(tasks: &mut TaskManager) -> (Router, Vec<String>)
103103
let args = vec![
104104
"",
105105
"--ic-unsafe-disable-response-verification",
106+
"--network-trust-x-request-id",
106107
"--cache-size",
107108
"2gb",
108109
"--domain",
@@ -113,6 +114,8 @@ pub async fn setup_test_router(tasks: &mut TaskManager) -> (Router, Vec<String>)
113114
"test_data/denylist.json",
114115
"--policy-denylist-allowlist",
115116
"test_data/allowlist.txt",
117+
"--geoip-db",
118+
"test_data/GeoLite2-Country.mmdb",
116119
"--log-vector-url",
117120
"http://127.0.0.1/vector",
118121
];

test_data/GeoLite2-Country.mmdb

9.35 MB
Binary file not shown.

0 commit comments

Comments
 (0)