Compare commits

..

31 Commits

Author SHA1 Message Date
00b73c80b6 chore: bump to v1.0.0
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2026-03-19 22:14:07 +01:00
3a35a2df41 test: add tests for ipv6 generalization
All checks were successful
continuous-integration/drone/push Build is passing
2026-03-19 22:12:23 +01:00
5240a871ad feat: add cidr notation support 2026-03-19 22:03:28 +01:00
10de58c421 chore: upgrade rust to edition 2024
All checks were successful
continuous-integration/drone/push Build is passing
2026-03-19 21:20:04 +01:00
5f1ad75126 Merge pull request 'Update Rust crate actix-web to 4.13.0' (#34) from renovate/actix-web-4.x into master
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-20 00:11:00 +00:00
f0bca5ecdf Update Rust crate actix-web to 4.13.0
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2026-02-19 00:11:34 +00:00
ed7c0b090b Merge pull request 'Update Rust crate futures-util to 0.3.32' (#33) from renovate/futures-util-0.x into master
All checks were successful
continuous-integration/drone/push Build is passing
2026-02-17 00:23:08 +00:00
67f922fa0a Update Rust crate futures-util to 0.3.32
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2026-02-16 00:24:01 +00:00
f2292ad425 Merge pull request 'Update Rust crate log to 0.4.29' (#32) from renovate/log-0.x into master
All checks were successful
continuous-integration/drone/push Build is passing
2025-12-04 00:11:32 +00:00
f29656cc63 Update Rust crate log to 0.4.29
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-12-03 00:11:42 +00:00
acb82262ed Merge pull request 'Update Rust crate actix-web to 4.12.1' (#31) from renovate/actix-web-4.x into master
All checks were successful
continuous-integration/drone/push Build is passing
2025-11-28 00:11:34 +00:00
d762d97832 Update Rust crate actix-web to 4.12.1
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-11-27 00:12:31 +00:00
b5c421f896 Merge pull request 'Update Rust crate actix-web to 4.12.0' (#30) from renovate/actix-web-4.x into master
Some checks failed
continuous-integration/drone/push Build is failing
2025-11-18 00:06:23 +00:00
632c91ece7 Update Rust crate actix-web to 4.12.0
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2025-11-17 00:06:23 +00:00
40d4db1ee7 Merge pull request 'Update Rust crate log to 0.4.28' (#29) from renovate/log-0.x into master
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-05 00:17:31 +00:00
a48b6bb854 Update Rust crate log to 0.4.28
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-09-04 00:24:29 +00:00
3354e7ca0e Update Rust crate futures-util to 0.3.31
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-05-19 00:03:56 +00:00
f97624d48d Update Rust crate actix-web to 4.11.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-05-13 00:12:47 +00:00
c1afcc21f6 Update Rust crate log to 0.4.27
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-03-29 22:50:23 +00:00
51ac9290bd Update Rust crate actix-web to 4.10.2
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-03-29 18:11:35 +00:00
ec43657c2a Update renovate.json
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-29 17:34:05 +00:00
956bfdaf73 Update Rust crate log to v0.4.27
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2025-03-29 12:30:34 +00:00
1eba754e86 Update renovate.json
All checks were successful
continuous-integration/drone/push Build is passing
2025-03-29 12:19:21 +00:00
ab7a6f5f3c Merge pull request 'Update Rust crate log to v0.4.26' (#18) from renovate/log-0.x-lockfile into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #18
2025-03-28 15:51:31 +00:00
614752132b Update Rust crate log to v0.4.26
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-03-12 00:16:43 +00:00
89865dbb28 Merge pull request 'Update Rust crate log to v0.4.25' (#16) from renovate/log-0.x-lockfile into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #16
2025-03-11 13:13:21 +00:00
4ddb88805c Merge pull request 'Update Rust crate actix-web to v4.10.2' (#17) from renovate/actix-web-4.x-lockfile into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #17
2025-03-11 13:13:13 +00:00
29c68e8153 Update Rust crate actix-web to v4.10.2
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-03-11 00:16:38 +00:00
dff56b187e Update Rust crate log to v0.4.25
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-01-20 00:21:41 +00:00
32bed1c2f4 Merge pull request 'Update Rust crate futures-util to v0.3.31' (#14) from renovate/futures-util-0.x-lockfile into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #14
2024-12-03 20:56:33 +00:00
55aec42917 Update Rust crate futures-util to v0.3.31
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-10-06 00:23:53 +00:00
6 changed files with 918 additions and 419 deletions

916
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
[package]
name = "actix-remote-ip"
version = "0.1.0"
edition = "2021"
version = "1.0.0"
edition = "2024"
authors = ["Pierre HUBERT <pierre.git@communiquons.org>"]
description = "Tiny extractor to get real client IP address, parsing X-Forwarded-For header"
readme = "README.md"
@@ -11,6 +11,7 @@ repository = "https://gitea.communiquons.org/pierre/actix-remote-ip"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
log = "0.4.21"
actix-web = "4.5.1"
futures-util = "0.3.30"
log = "0.4.29"
actix-web = "4.13.0"
futures-util = "0.3.32"
ipnet = "2.12.0"

View File

@@ -14,9 +14,7 @@ Configure it when you configure your Actix server:
```rust
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(RemoteIPConfig {
proxy: Some("IP".to_string())
}))
.app_data(web::Data::new(RemoteIPConfig::parse_opt(Some("IP"))))
// ...
})
```

View File

@@ -1,3 +1,3 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
"extends": ["local>renovate/presets"]
}

View File

@@ -1,30 +1,5 @@
use std::net::{IpAddr, Ipv6Addr};
use std::str::FromStr;
/// Parse an IP address
pub fn parse_ip(ip: &str) -> Option<IpAddr> {
let mut ip = match IpAddr::from_str(ip) {
Ok(ip) => ip,
Err(e) => {
log::warn!("Failed to parse an IP address: {}", e);
return None;
}
};
// In case of IPv6 address, we skip the 8 last octets
if let IpAddr::V6(ipv6) = &mut ip {
let mut octets = ipv6.octets();
for o in octets.iter_mut().skip(8) {
*o = 0;
}
ip = IpAddr::V6(Ipv6Addr::from(octets));
}
Some(ip)
}
/// Check if two ips matches
pub fn match_ip(pattern: &str, ip: &str) -> bool {
pub(crate) fn legacy_match_ip(pattern: &str, ip: &str) -> bool {
if pattern.eq(ip) {
return true;
}
@@ -38,8 +13,19 @@ pub fn match_ip(pattern: &str, ip: &str) -> bool {
#[cfg(test)]
mod test {
use crate::ip_utils::parse_ip;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
/// Parse an IP address
fn parse_ip(ip: &str) -> Option<IpAddr> {
match IpAddr::from_str(ip) {
Ok(ip) => Some(ip),
Err(e) => {
log::warn!("Failed to parse an IP address: {e}");
None
}
}
}
#[test]
fn parse_bad_ip() {
@@ -58,19 +44,21 @@ mod test {
let ip = parse_ip("2a00:1450:4007:813::200e").unwrap();
assert_eq!(
ip,
IpAddr::V6(Ipv6Addr::new(0x2a00, 0x1450, 0x4007, 0x813, 0, 0, 0, 0))
IpAddr::V6(Ipv6Addr::new(
0x2a00, 0x1450, 0x4007, 0x813, 0, 0, 0, 0x200e
))
);
}
#[test]
fn parse_ip_v6_address_2() {
let ip = parse_ip("::1").unwrap();
assert_eq!(ip, IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)));
assert_eq!(ip, IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)));
}
#[test]
fn parse_ip_v6_address_3() {
let ip = parse_ip("a::1").unwrap();
assert_eq!(ip, IpAddr::V6(Ipv6Addr::new(0xa, 0, 0, 0, 0, 0, 0, 0)));
assert_eq!(ip, IpAddr::V6(Ipv6Addr::new(0xa, 0, 0, 0, 0, 0, 0, 1)));
}
}

View File

@@ -3,64 +3,133 @@
//! A tiny crate to determine the Real IP address of a client, taking account
//! of an eventual reverse proxy
use crate::ip_utils::{match_ip, parse_ip};
use crate::legacy_ip_utils::legacy_match_ip;
use actix_web::dev::Payload;
use actix_web::web::Data;
use actix_web::{Error, FromRequest, HttpRequest};
use futures_util::future::{ready, Ready};
use futures_util::future::{Ready, ready};
use ipnet::AddrParseError;
use std::fmt::Display;
use std::net::IpAddr;
use std::net::{IpAddr, Ipv6Addr};
use std::str::FromStr;
mod ip_utils;
/// Legacy IP support
pub(crate) mod legacy_ip_utils;
/// Remote IP retrieval configuration
///
/// This configuration must be built and set as Actix web data
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct RemoteIPConfig {
/// The IP address of the proxy. This address can ends with a star '*'
pub proxy: Option<String>,
pub enum RemoteIPConfig {
/// Legacy IP format
Legacy(String),
/// Modern IP format
Modern(Vec<ipnet::IpNet>),
#[default]
None,
}
impl RemoteIPConfig {
/// Initiate a new RemoteIPConfig configuration instance, that
/// can be set as [actix_web::Data] structure
pub fn with_proxy_ip<D: Display>(proxy: D) -> Self {
Self {
proxy: Some(proxy.to_string()),
pub fn parse_opt<D: Display>(proxy: Option<D>) -> Self {
match proxy {
None => Self::None,
Some(p) => Self::parse(p),
}
}
/// Initiate a new RemoteIPConfig configuration instance, that
/// can be set as [actix_web::Data] structure
pub fn parse<D: Display>(proxy: D) -> Self {
Self::try_parse(proxy).expect("failed to parse given proxy ip address!")
}
/// Initiate a new RemoteIPConfig configuration instance, that
/// can be set as [actix_web::Data] structure
pub fn try_parse<D: Display>(proxy: D) -> Result<Self, AddrParseError> {
let proxy_ip = proxy.to_string();
// If nothing was specified...
if proxy_ip.trim().is_empty() {
return Ok(Self::None);
}
// Handle legacy format
if (proxy_ip.contains("*") || !proxy_ip.contains("/")) && !proxy_ip.contains(",") {
return Ok(Self::Legacy(proxy_ip));
}
let mut ips = vec![];
for ip in proxy_ip.split(",") {
let ip = ip.trim();
if ip.is_empty() {
continue;
}
ips.push(ipnet::IpNet::from_str(ip)?);
}
Ok(Self::Modern(ips))
}
/// Check out whether an IP address is a trusted IP address or not
pub fn is_trusted_proxy_ip(&self, ip: IpAddr) -> bool {
match self {
RemoteIPConfig::Legacy(proxy_ip) => legacy_match_ip(proxy_ip, &ip.to_string()),
RemoteIPConfig::Modern(networks) => networks.iter().any(|n| n.contains(&ip)),
// No proxy
RemoteIPConfig::None => false,
}
}
}
/// Get the remote IP address
pub fn get_remote_ip(req: &HttpRequest) -> IpAddr {
let proxy = req
.app_data::<Data<RemoteIPConfig>>()
.map(|c| c.proxy.as_ref())
.unwrap_or_default();
log::trace!("Proxy IP: {:?}", proxy);
let config = req.app_data::<Data<RemoteIPConfig>>().map(|c| c.as_ref());
let mut ip = req.peer_addr().unwrap().ip();
log::trace!("Proxy IP config: {:?}", config);
let mut peer_ip = req.peer_addr().unwrap().ip();
// We check if the request comes from a trusted reverse proxy
if let Some(proxy) = proxy.as_ref() {
if match_ip(proxy, &ip.to_string()) {
if let Some(header) = req.headers().get("X-Forwarded-For") {
let header = header.to_str().unwrap();
if let Some(config) = config.as_ref()
&& config.is_trusted_proxy_ip(peer_ip)
&& let Some(header) = req.headers().get("X-Forwarded-For")
{
let header = header.to_str().unwrap();
let remote_ip = if let Some((upstream_ip, _)) = header.split_once(',') {
upstream_ip
} else {
header
};
let remote_ip = if let Some((upstream_ip, _)) = header.split_once(',') {
upstream_ip
} else {
header
};
if let Some(upstream_ip) = parse_ip(remote_ip) {
ip = upstream_ip;
}
match IpAddr::from_str(remote_ip) {
Ok(ip) => peer_ip = ip,
Err(e) => {
log::warn!("Failed to parse an IP address: {e}");
}
}
}
generalize_ip(peer_ip)
}
/// Generalize IPv6 to consider IP inside a network as a single IP (for bruteforce protection)
fn generalize_ip(mut ip: IpAddr) -> IpAddr {
// In case of IPv6 address, we skip the 8 last octets
if let IpAddr::V6(ipv6) = &mut ip {
let mut octets = ipv6.octets();
for o in octets.iter_mut().skip(8) {
*o = 0;
}
ip = IpAddr::V6(Ipv6Addr::from(octets));
}
ip
}
@@ -88,9 +157,98 @@ mod test {
use std::net::{IpAddr, SocketAddr};
use std::str::FromStr;
use crate::{get_remote_ip, RemoteIPConfig};
use crate::{RemoteIPConfig, get_remote_ip};
use actix_web::test::TestRequest;
use actix_web::web::Data;
use ipnet::{IpNet, Ipv4Net, Ipv6Net};
#[test]
fn test_parse_config() {
assert_eq!(
RemoteIPConfig::parse_opt::<String>(None),
RemoteIPConfig::None
);
assert_eq!(
RemoteIPConfig::parse_opt(Some("192.168.1.5")),
RemoteIPConfig::Legacy("192.168.1.5".to_string())
);
assert_eq!(
RemoteIPConfig::parse("192.168.1.5"),
RemoteIPConfig::Legacy("192.168.1.5".to_string())
);
assert_eq!(
RemoteIPConfig::try_parse("192.168.1.5"),
Ok(RemoteIPConfig::Legacy("192.168.1.5".to_string()))
);
assert_eq!(
RemoteIPConfig::parse("10.0.0.0/8"),
RemoteIPConfig::Modern(vec![IpNet::V4(Ipv4Net::from_str("10.0.0.0/8").unwrap())])
);
assert_eq!(
RemoteIPConfig::parse("10.0.0.0/8,192.168.0.0/16"),
RemoteIPConfig::Modern(vec![
IpNet::V4(Ipv4Net::from_str("10.0.0.0/8").unwrap()),
IpNet::V4(Ipv4Net::from_str("192.168.0.0/16").unwrap())
])
);
assert_eq!(
RemoteIPConfig::parse("10.0.0.0/8,192.168.0.0/16,2001:d445:b62e:dd2a::/64"),
RemoteIPConfig::Modern(vec![
IpNet::V4(Ipv4Net::from_str("10.0.0.0/8").unwrap()),
IpNet::V4(Ipv4Net::from_str("192.168.0.0/16").unwrap()),
IpNet::V6(Ipv6Net::from_str("2001:d445:b62e:dd2a::/64").unwrap())
])
);
assert_eq!(RemoteIPConfig::parse(""), RemoteIPConfig::None);
assert!(RemoteIPConfig::try_parse("10.0.0.0/a,192.168.0.0/16").is_err());
assert!(RemoteIPConfig::try_parse("10.0.0.0/a,192:.168.0.0/16").is_err());
assert!(RemoteIPConfig::try_parse("10.0.0.0/a,192:*.168.0.0/16").is_err());
}
#[test]
fn test_trusted_ip() {
let none = RemoteIPConfig::None;
assert!(!none.is_trusted_proxy_ip(IpAddr::from_str("0.0.0.0").unwrap()));
assert!(!none.is_trusted_proxy_ip(IpAddr::from_str("10.20.30.40").unwrap()));
assert!(!none.is_trusted_proxy_ip(IpAddr::from_str("192.168.1.0").unwrap()));
assert!(!none.is_trusted_proxy_ip(IpAddr::from_str("192.169.1.0").unwrap()));
assert!(
!none.is_trusted_proxy_ip(IpAddr::from_str("2001:d445:b62e:dd2a:2:2:2:0").unwrap())
);
let legacy = RemoteIPConfig::Legacy("10.*".to_string());
assert!(legacy.is_trusted_proxy_ip(IpAddr::from_str("10.20.30.40").unwrap()));
assert!(!legacy.is_trusted_proxy_ip(IpAddr::from_str("192.168.1.0").unwrap()));
assert!(!legacy.is_trusted_proxy_ip(IpAddr::from_str("192.169.1.0").unwrap()));
assert!(
!legacy.is_trusted_proxy_ip(IpAddr::from_str("2001:d445:b62e:dd2a:2:2:2:0").unwrap())
);
let legacyv6 = RemoteIPConfig::Legacy("2001:d445:b62e:dd2a*".to_string());
assert!(!legacyv6.is_trusted_proxy_ip(IpAddr::from_str("10.20.30.40").unwrap()));
assert!(!legacyv6.is_trusted_proxy_ip(IpAddr::from_str("192.168.1.0").unwrap()));
assert!(!legacyv6.is_trusted_proxy_ip(IpAddr::from_str("192.169.1.0").unwrap()));
assert!(
legacyv6.is_trusted_proxy_ip(IpAddr::from_str("2001:d445:b62e:dd2a:2:2:2:0").unwrap())
);
assert!(
!legacyv6.is_trusted_proxy_ip(IpAddr::from_str("2001:a445:b62e:dd2a:2:2:2:0").unwrap())
);
let modern = RemoteIPConfig::Modern(vec![
IpNet::V4(Ipv4Net::from_str("10.0.0.0/8").unwrap()),
IpNet::V4(Ipv4Net::from_str("192.168.0.0/16").unwrap()),
IpNet::V6(Ipv6Net::from_str("2001:d445:b62e:dd2a::/64").unwrap()),
]);
assert!(modern.is_trusted_proxy_ip(IpAddr::from_str("10.20.30.40").unwrap()));
assert!(modern.is_trusted_proxy_ip(IpAddr::from_str("192.168.1.0").unwrap()));
assert!(!modern.is_trusted_proxy_ip(IpAddr::from_str("192.169.1.0").unwrap()));
assert!(
modern.is_trusted_proxy_ip(IpAddr::from_str("2001:d445:b62e:dd2a:2:2:2:0").unwrap())
);
}
#[test]
fn test_get_remote_ip() {
@@ -104,32 +262,114 @@ mod test {
}
#[test]
fn test_get_remote_ip_from_proxy() {
fn test_get_remote_ip_v6() {
let req = TestRequest::default()
.peer_addr(
SocketAddr::from_str("[2001:c0c3:f0d2:562d:193d:a5f6:1668:3cba]:1000").unwrap(),
)
.to_http_request();
assert_eq!(
get_remote_ip(&req),
"2001:c0c3:f0d2:562d:0:0:0:0".parse::<IpAddr>().unwrap()
);
}
#[test]
fn test_get_remote_ip_from_proxy_legacy() {
let req = TestRequest::default()
.peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap())
.insert_header(("X-Forwarded-For", "1.1.1.1"))
.app_data(Data::new(RemoteIPConfig::with_proxy_ip("192.168.1.1")))
.app_data(Data::new(RemoteIPConfig::parse("192.168.1.1")))
.to_http_request();
assert_eq!(get_remote_ip(&req), "1.1.1.1".parse::<IpAddr>().unwrap());
}
#[test]
fn test_get_remote_ip_from_proxy_2() {
fn test_get_remote_ip_from_proxy_legacy_2() {
let req = TestRequest::default()
.peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap())
.insert_header(("X-Forwarded-For", "1.1.1.1, 1.2.2.2"))
.app_data(Data::new(RemoteIPConfig::with_proxy_ip("192.168.1.1")))
.app_data(Data::new(RemoteIPConfig::parse("192.168.1.1")))
.to_http_request();
assert_eq!(get_remote_ip(&req), "1.1.1.1".parse::<IpAddr>().unwrap());
}
#[test]
fn test_get_remote_ip_from_proxy_ipv6() {
fn test_get_remote_ip_from_proxy_modern() {
let req = TestRequest::default()
.peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap())
.insert_header(("X-Forwarded-For", "1.1.1.1"))
.app_data(Data::new(RemoteIPConfig::parse("192.168.1.0/24")))
.to_http_request();
assert_eq!(get_remote_ip(&req), "1.1.1.1".parse::<IpAddr>().unwrap());
}
#[test]
fn test_get_remote_ip_from_proxy_modern_multiple() {
let req = TestRequest::default()
.peer_addr(SocketAddr::from_str("10.10.1.1:1000").unwrap())
.insert_header(("X-Forwarded-For", "1.1.1.1"))
.app_data(Data::new(RemoteIPConfig::parse(
"192.168.1.0/24,10.10.0.0/16",
)))
.to_http_request();
assert_eq!(get_remote_ip(&req), "1.1.1.1".parse::<IpAddr>().unwrap());
}
#[test]
fn test_get_remote_ip_from_proxy_modern_untrusted() {
let req = TestRequest::default()
.peer_addr(SocketAddr::from_str("10.10.1.1:1000").unwrap())
.insert_header(("X-Forwarded-For", "1.1.1.1"))
.app_data(Data::new(RemoteIPConfig::parse("192.168.1.0/24")))
.to_http_request();
assert_eq!(get_remote_ip(&req), "10.10.1.1".parse::<IpAddr>().unwrap());
}
#[test]
fn test_get_remote_ip_from_proxy_modern_untrusted_ipv6() {
let req = TestRequest::default()
.peer_addr(
SocketAddr::from_str("[2001:d445:b62e:dd2a:1ab8:3a21:c200:69ce]:1000").unwrap(),
)
.insert_header(("X-Forwarded-For", "1.1.1.1"))
.app_data(Data::new(RemoteIPConfig::parse("192.168.1.0/24")))
.to_http_request();
assert_eq!(
get_remote_ip(&req),
"2001:d445:b62e:dd2a::".parse::<IpAddr>().unwrap()
);
}
#[test]
fn test_get_remote_ip_from_proxy_modern_trusted_ipv6() {
let req = TestRequest::default()
.peer_addr(
SocketAddr::from_str("[2001:d445:b62e:dd2a:1ab8:3a21:c200:69ce]:1000").unwrap(),
)
.insert_header(("X-Forwarded-For", "2001:129c:4dee:1f22:ddcb:d2ae:cdcb:dd22"))
.app_data(Data::new(RemoteIPConfig::parse(
"192.168.1.0/24,2001:d445:b62e:dd2a:1ab8:3a21:c200:69ce/128",
)))
.to_http_request();
assert_eq!(
get_remote_ip(&req),
"2001:129c:4dee:1f22::".parse::<IpAddr>().unwrap()
);
}
#[test]
fn test_get_remote_ip_from_proxy_ipv6_legacy() {
let req = TestRequest::default()
.peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap())
.insert_header(("X-Forwarded-For", "10::1, 1.2.2.2"))
.app_data(Data::new(RemoteIPConfig::with_proxy_ip("192.168.1.1")))
.app_data(Data::new(RemoteIPConfig::parse("192.168.1.1")))
.to_http_request();
assert_eq!(get_remote_ip(&req), "10::".parse::<IpAddr>().unwrap());
@@ -149,11 +389,51 @@ mod test {
}
#[test]
fn test_get_remote_ip_from_other_proxy() {
fn test_get_remote_ip_from_other_proxy_legacy() {
let req = TestRequest::default()
.peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap())
.insert_header(("X-Forwarded-For", "1.1.1.1, 1.2.2.2"))
.app_data(Data::new(RemoteIPConfig::with_proxy_ip("192.168.1.2")))
.app_data(Data::new(RemoteIPConfig::parse("192.168.1.2")))
.to_http_request();
assert_eq!(
get_remote_ip(&req),
"192.168.1.1".parse::<IpAddr>().unwrap()
);
}
#[test]
fn test_get_remote_ip_from_other_proxy_modern() {
let req = TestRequest::default()
.peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap())
.insert_header(("X-Forwarded-For", "1.1.1.1, 1.2.2.2"))
.app_data(Data::new(RemoteIPConfig::parse("192.168.1.2/32")))
.to_http_request();
assert_eq!(
get_remote_ip(&req),
"192.168.1.1".parse::<IpAddr>().unwrap()
);
}
#[test]
fn test_get_remote_ip_config_without_proxy_legacy() {
let req = TestRequest::default()
.peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap())
.app_data(Data::new(RemoteIPConfig::parse("10.0.0.*")))
.to_http_request();
assert_eq!(
get_remote_ip(&req),
"192.168.1.1".parse::<IpAddr>().unwrap()
);
}
#[test]
fn test_get_remote_ip_config_without_proxy_modern() {
let req = TestRequest::default()
.peer_addr(SocketAddr::from_str("192.168.1.1:1000").unwrap())
.app_data(Data::new(RemoteIPConfig::parse("10.0.0.1/32")))
.to_http_request();
assert_eq!(