relay_server/extractors/
forwarded_for.rs1use std::convert::Infallible;
2use std::net::SocketAddr;
3
4use axum::extract::{ConnectInfo, FromRequestParts};
5use axum::http::HeaderMap;
6use axum::http::request::Parts;
7
8#[derive(Debug)]
9pub struct ForwardedFor(String);
10
11impl ForwardedFor {
12 const FORWARDED_HEADER: &'static str = "X-Forwarded-For";
16 const SENTRY_FORWARDED_HEADER: &'static str = "X-Sentry-Forwarded-For";
23 const VERCEL_FORWARDED_HEADER: &'static str = "X-Vercel-Forwarded-For";
27 const CLOUDFLARE_FORWARDED_HEADER: &'static str = "CF-Connecting-IP";
31
32 fn get_forwarded_for_ip(header_map: &HeaderMap) -> Option<&str> {
45 let headers = [
47 Self::CLOUDFLARE_FORWARDED_HEADER,
48 Self::VERCEL_FORWARDED_HEADER,
49 Self::SENTRY_FORWARDED_HEADER,
50 Self::FORWARDED_HEADER,
51 ];
52
53 headers
54 .into_iter()
55 .flat_map(|header| header_map.get(header))
56 .flat_map(|value| value.to_str().ok())
57 .find(|value| !value.is_empty())
58 }
59
60 pub fn into_inner(self) -> String {
61 self.0
62 }
63}
64
65impl AsRef<str> for ForwardedFor {
66 fn as_ref(&self) -> &str {
67 &self.0
68 }
69}
70
71impl From<ForwardedFor> for String {
72 fn from(forwarded: ForwardedFor) -> Self {
73 forwarded.into_inner()
74 }
75}
76
77impl<S> FromRequestParts<S> for ForwardedFor
78where
79 S: Send + Sync,
80{
81 type Rejection = Infallible;
82
83 async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
84 let peer_addr = ConnectInfo::<SocketAddr>::from_request_parts(parts, state)
85 .await
86 .map(|ConnectInfo(peer)| peer.ip().to_string())
87 .ok();
88
89 let forwarded = Self::get_forwarded_for_ip(&parts.headers);
90
91 Ok(ForwardedFor(match (forwarded, peer_addr) {
92 (None, None) => String::new(),
93 (None, Some(peer_addr)) => peer_addr,
94 (Some(forwarded), None) => forwarded.to_owned(),
95 (Some(forwarded), Some(peer_addr)) => format!("{forwarded}, {peer_addr}"),
96 }))
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use axum::http::HeaderValue;
104
105 #[test]
106 fn test_prefer_vercel_forwarded() {
107 let vercel_ip = "192.158.1.38";
108 let other_ip = "111.222.3.44";
109
110 let mut headermap = HeaderMap::default();
111 headermap.insert(
112 ForwardedFor::VERCEL_FORWARDED_HEADER,
113 HeaderValue::from_str(vercel_ip).unwrap(),
114 );
115 headermap.insert(
116 ForwardedFor::FORWARDED_HEADER,
117 HeaderValue::from_str(other_ip).unwrap(),
118 );
119
120 let forwarded = ForwardedFor::get_forwarded_for_ip(&headermap);
121
122 assert_eq!(forwarded, Some(vercel_ip));
123 }
124
125 #[test]
126 fn test_prefer_cf_forwarded() {
127 let cf_ip = "192.158.1.38";
128 let other_ip = "111.222.3.44";
129
130 let mut headermap = HeaderMap::default();
131 headermap.insert(
132 ForwardedFor::CLOUDFLARE_FORWARDED_HEADER,
133 HeaderValue::from_str(cf_ip).unwrap(),
134 );
135 headermap.insert(
136 ForwardedFor::FORWARDED_HEADER,
137 HeaderValue::from_str(other_ip).unwrap(),
138 );
139
140 let forwarded = ForwardedFor::get_forwarded_for_ip(&headermap);
141
142 assert_eq!(forwarded, Some(cf_ip));
143 }
144
145 #[test]
146 fn test_prefer_sentry_forwarded() {
147 let sentry_ip = "192.158.1.38";
148 let other_ip = "111.222.3.44";
149
150 let mut headermap = HeaderMap::default();
151 headermap.insert(
152 ForwardedFor::SENTRY_FORWARDED_HEADER,
153 HeaderValue::from_str(sentry_ip).unwrap(),
154 );
155 headermap.insert(
156 ForwardedFor::FORWARDED_HEADER,
157 HeaderValue::from_str(other_ip).unwrap(),
158 );
159
160 let forwarded = ForwardedFor::get_forwarded_for_ip(&headermap);
161
162 assert_eq!(forwarded, Some(sentry_ip));
163 }
164
165 #[test]
167 fn test_fall_back_on_forwarded_for_header() {
168 let other_ip = "111.222.3.44";
169
170 let mut headermap = HeaderMap::default();
171 headermap.insert(
172 ForwardedFor::FORWARDED_HEADER,
173 HeaderValue::from_str(other_ip).unwrap(),
174 );
175
176 let forwarded = ForwardedFor::get_forwarded_for_ip(&headermap);
177
178 assert_eq!(forwarded, Some(other_ip));
179 }
180
181 #[test]
182 fn test_get_none_if_empty_header() {
183 let mut headermap = HeaderMap::default();
184 headermap.insert(
185 ForwardedFor::FORWARDED_HEADER,
186 HeaderValue::from_str("").unwrap(),
187 );
188
189 let forwarded = ForwardedFor::get_forwarded_for_ip(&headermap);
190 assert!(forwarded.is_none());
191 }
192
193 #[test]
194 fn test_get_none_if_invalid_header() {
195 let other_ip = "111.222.3.44";
196
197 let mut headermap = HeaderMap::default();
198 headermap.insert("X-Invalid-Header", HeaderValue::from_str(other_ip).unwrap());
199
200 let forwarded = ForwardedFor::get_forwarded_for_ip(&headermap);
201 assert!(forwarded.is_none());
202 }
203}