webserver_base/ip.rs
1use std::{collections::HashMap, net::SocketAddr};
2
3use axum::http::HeaderMap;
4use tracing::{error, info, instrument};
5
6/// Determine the client's actual IP address (not the IP address of any Proxies).
7///
8/// # Panics
9///
10/// Panics if the client's actual IP address cannot be determined.
11#[instrument(skip_all)]
12pub fn resolve_true_client_ip_address(socket_addr: SocketAddr, header_map: &HeaderMap) -> String {
13 // prioritized list of HTTP headers that may contain the client's true IP address
14 // (top-most entry is the most trusted)
15 let prioritized_headers: Vec<&str> = vec![
16 // Cloudflare
17 "True-Client-IP",
18 "CF-Connecting-IP",
19 // standard
20 "X-Forwarded-For",
21 "X-Real-IP",
22 "Forwarded",
23 // backup (Axum's 'SocketAddr' IP; definitely a proxy)
24 "socket_addr",
25 ];
26
27 // get the value of each prioritized header (if it exists)
28 let prioritized_headers_values: HashMap<&str, Option<String>> = prioritized_headers.iter().map(|prioritized_header| {
29 if *prioritized_header == "socket_addr" {
30 // this is a backup in the case we fail to get 'true' IP address
31 // (usually a Proxy IP)
32 return (*prioritized_header, Some(socket_addr.ip().to_string()));
33 }
34
35 let header_value: Option<String> = match header_map.get(*prioritized_header) {
36 // prioritized header exists in the HeaderMap
37 Some(header_value) => {
38 match header_value.to_str() {
39 // successfully parsed HTTP header value
40 Ok(header_value) => {
41 if *prioritized_header == "X-Forwarded-For" {
42 info!("full HTTP 'X-Forwarded-For' header IP list: {header_value}");
43
44 // 'X-Forwarded-For' header may contain multiple IP addresses
45 let parts: Vec<&str> = header_value.split(',').collect();
46
47 // if there are multiple entries in this list, the left-most entry is the
48 // client's actual IP address (all other entries are Network Proxies)
49 let x_forwarded_for_client_ip: Option<&&str> = parts.first();
50 if let Some(x_forwarded_for_client_ip) = x_forwarded_for_client_ip {
51 let x_forwarded_for: String = (*x_forwarded_for_client_ip).to_string();
52 Some(x_forwarded_for)
53 } else {
54 // zero IP addresses found in 'X-Forwarded-For' header
55 None
56 }
57 } else {
58 // all other headers are single IP addresses
59 Some(header_value.to_string())
60 }
61 }
62 // failed to parse HTTP header value
63 Err(to_str_error) => {
64 error!("failed to parse HTTP '{prioritized_header}' header value: {to_str_error}");
65 None
66 }
67 }
68 }
69 // prioritized header does not exist in the HeaderMap
70 None => {
71 None
72 },
73 };
74 (*prioritized_header, header_value)
75 }).collect();
76 info!(
77 "Client IP headers: {:?}",
78 prioritized_headers_values.clone()
79 );
80
81 // choose the first prioritized header that exists
82 for prioritized_header in prioritized_headers {
83 if let Some(Some(header_value)) = prioritized_headers_values.get(prioritized_header) {
84 info!("chose HTTP '{prioritized_header}' header for true client IP: '{header_value}'");
85 return header_value.to_string();
86 }
87 }
88
89 // THIS SHOULD NEVER BE HIT because 'socket_addr' always exists
90 error!("failed to find any prioritized HTTP headers for true client IP address");
91 prioritized_headers_values
92 .get("socket_addr")
93 .unwrap()
94 .as_ref()
95 .unwrap()
96 .to_string()
97}