v0.34.0_10
This commit is contained in:
1 parent
8c4cbc583f
commit
dd2502dd11
13 files changed
+389
-113
No files matched your search
+12
-10
@@ -326,8 +326,9 @@ b45d6040810c93ae2acb04ef6b0a59c189751edf9008cd81a4521c9e04e7f07d src/docker.rs
|
|||||||
4c2c988191a97f81568a8df64af772463d010844f1abf327871901cdb32d4336 src/docker/model.rs
|
4c2c988191a97f81568a8df64af772463d010844f1abf327871901cdb32d4336 src/docker/model.rs
|
||||||
5f4610c59b2d4ff6a205a2d9ae11bc84be689b2a68302459f546b3f43e53593c src/docker/reconcile.rs
|
5f4610c59b2d4ff6a205a2d9ae11bc84be689b2a68302459f546b3f43e53593c src/docker/reconcile.rs
|
||||||
a12c47df4b90b6fc36c978a42477e6ddffbbbf69aed7252101c2fd829709bd6e src/docker/tests.rs
|
a12c47df4b90b6fc36c978a42477e6ddffbbbf69aed7252101c2fd829709bd6e src/docker/tests.rs
|
||||||
ae46f4a971119d300d79fa506ed116054313334484546ba2f411e893e4e7586c src/forwarding.rs
|
526ae9c7f777b0442ced235237ded659e6e9457aaccc68a1fd5af80b58d3c567 src/forwarding.rs
|
||||||
4622bf34a316fca5dd524d229652a1fef8b1828f34e2c3e9bc0e35fe03b6a393 src/forwarding/tests.rs
|
4be1a43a911e61ffccad01f0ca71913e5e1ee682d83591d02d478baeb900b54e src/forwarding/peer_formatting.rs
|
||||||
|
f3c1de0f428b008ed830ca410bfa5b4ca489a0861275b8541dbfa6217d16cb95 src/forwarding/tests.rs
|
||||||
af7a4e91c32ffbe4701c8c06e48ba3f8bbbe55e8b9e877aecb30ccdf858e9e8f src/geoip.rs
|
af7a4e91c32ffbe4701c8c06e48ba3f8bbbe55e8b9e877aecb30ccdf858e9e8f src/geoip.rs
|
||||||
80850f7acd1ed4c0048b065e9e81740869a8efbb0d9b5d2e83d8ec7fbc494703 src/geoip/mmdb.rs
|
80850f7acd1ed4c0048b065e9e81740869a8efbb0d9b5d2e83d8ec7fbc494703 src/geoip/mmdb.rs
|
||||||
06aa056d789742cc11c8826c338d985e6a6fb22da6c3530937eec38e35478034 src/geoip/mmdb_tests.rs
|
06aa056d789742cc11c8826c338d985e6a6fb22da6c3530937eec38e35478034 src/geoip/mmdb_tests.rs
|
||||||
@@ -375,7 +376,7 @@ e03f0d6b3f35dae6b98c394837aa0d17e4d73a8306767b4c607868bc00c30286 src/prometheus
|
|||||||
fb2d8d45cd2e947f783eb981b37fa418c39fcb327a3af17a648fadea7f37f888 src/prometheus/web_php.rs
|
fb2d8d45cd2e947f783eb981b37fa418c39fcb327a3af17a648fadea7f37f888 src/prometheus/web_php.rs
|
||||||
2c97367686b54989573ce7fb81b2c6dde72adb254df9522bc05ded74f1fbffb6 src/proxy/body.rs
|
2c97367686b54989573ce7fb81b2c6dde72adb254df9522bc05ded74f1fbffb6 src/proxy/body.rs
|
||||||
66b7f2cb47f2e70dc26b7b19eb8650872e34d3e44cf250d2effdb758295f8b8a src/proxy/body/tests.rs
|
66b7f2cb47f2e70dc26b7b19eb8650872e34d3e44cf250d2effdb758295f8b8a src/proxy/body/tests.rs
|
||||||
818dcfefa1c40d6a722dacf53c7f45c53141240f1c1913cfa651e19dd2718526 src/proxy/cache_helpers.rs
|
7586fc88e10381eb97f3be6a66f355d16088cc9481f50c193eee8fe169c4fc8e src/proxy/cache_helpers.rs
|
||||||
aa49c9928bb91e3d5bae0d340d88396a0417b9edec6192d41e1403533319a897 src/proxy/cache_http_invalidation.rs
|
aa49c9928bb91e3d5bae0d340d88396a0417b9edec6192d41e1403533319a897 src/proxy/cache_http_invalidation.rs
|
||||||
676c1b325fd8e55bcdd86451c7d1d8b8d347ca00e8da6b3e5b148a58c376a9b4 src/proxy/cache_plan.rs
|
676c1b325fd8e55bcdd86451c7d1d8b8d347ca00e8da6b3e5b148a58c376a9b4 src/proxy/cache_plan.rs
|
||||||
3404b03096862de1050088f62cdd811ea1ab61d191f902501173107f81f23b9b src/proxy/cache_plan/tests.rs
|
3404b03096862de1050088f62cdd811ea1ab61d191f902501173107f81f23b9b src/proxy/cache_plan/tests.rs
|
||||||
@@ -387,20 +388,21 @@ cfa941b60b5d167ec9d8e2566ea3342b588d1a9078a6ca711ad6062ddf00fec9 src/proxy/forw
|
|||||||
0a921474ef2b93eabdaf3888651cf29130eace5ccb1e0f5d6bc25e6aa69280be src/proxy/forward/success_prepare.rs
|
0a921474ef2b93eabdaf3888651cf29130eace5ccb1e0f5d6bc25e6aa69280be src/proxy/forward/success_prepare.rs
|
||||||
3f21439bbe4966fff43b3ebce8b59fa76ecce376197b4c56a2dce3a6d98f5556 src/proxy/forward/upstream.rs
|
3f21439bbe4966fff43b3ebce8b59fa76ecce376197b4c56a2dce3a6d98f5556 src/proxy/forward/upstream.rs
|
||||||
bb8c9117e05b0cb4bbfe029ef4d1a031fc755cdce255b15dad072afd1c845737 src/proxy/forward/web_source.rs
|
bb8c9117e05b0cb4bbfe029ef4d1a031fc755cdce255b15dad072afd1c845737 src/proxy/forward/web_source.rs
|
||||||
2c681e48d6be7db784b65cc186bced40f1b6c777d97a4d222c653008b8406a31 src/proxy/forward_entry.rs
|
3b9f27445c637dbf093b07a5b4e77cbff4df2bab2ad622bb87b6772dab245ec6 src/proxy/forward_entry.rs
|
||||||
9b25f5e3ea4443a63ee6f45d12e1957dd316fe8e85325fc00ea416faaab1025a src/proxy/h2_graceful.rs
|
9b25f5e3ea4443a63ee6f45d12e1957dd316fe8e85325fc00ea416faaab1025a src/proxy/h2_graceful.rs
|
||||||
cc7fe83f42acb19d79abe83107427cdc6b0ed14083702f4fe66af22f7cdfc4cc src/proxy/headers.rs
|
cc7fe83f42acb19d79abe83107427cdc6b0ed14083702f4fe66af22f7cdfc4cc src/proxy/headers.rs
|
||||||
0f9314d1380febe436def36b5f381731e97cbac90625637575f38f3d72eaaace src/proxy/headers/tests.rs
|
0f9314d1380febe436def36b5f381731e97cbac90625637575f38f3d72eaaace src/proxy/headers/tests.rs
|
||||||
3f25429418941eb72c3847125340db74684ad316eb278ecf658953026fd41f4f src/proxy/mod.rs
|
30a01e70328e8e5d544d86e3df540c0078a17de33376d389b5ad06e351477634 src/proxy/identity.rs
|
||||||
|
050c66311f71cac75c1d4c8547351a1d6f1111ed0db128691366fdd73647e90a src/proxy/mod.rs
|
||||||
55133ceace2e5262d9c54fa41bea099a27b817d1e09c1cd0e2493fd9cee4da38 src/proxy/redirect_helpers.rs
|
55133ceace2e5262d9c54fa41bea099a27b817d1e09c1cd0e2493fd9cee4da38 src/proxy/redirect_helpers.rs
|
||||||
2280841494c3e14ccc5e3f02b026fe3c4328a3dab04ab449b9286bb69e39c028 src/proxy/request_id.rs
|
2280841494c3e14ccc5e3f02b026fe3c4328a3dab04ab449b9286bb69e39c028 src/proxy/request_id.rs
|
||||||
b9a2307c6aa7f16613d935508a61574dfee964020b1b5be86293df2d684615d8 src/proxy/request_id/tests.rs
|
b9a2307c6aa7f16613d935508a61574dfee964020b1b5be86293df2d684615d8 src/proxy/request_id/tests.rs
|
||||||
3db4188e2cc6d69fdff1b817cc15d0e6b18bda18d4e4729d37c05736c7d8cad6 src/proxy/tests/authority_tests.rs
|
94a50c0bf147565098e06672df3625e44d5af34ef1f12fd355d82cc7a72cf033 src/proxy/tests/authority_tests.rs
|
||||||
1aa9bbbfb2659facdaeaf185e11221b1c8cd5fe6e23c7c1dacd3aa55e3db4fcb src/proxy/tests/cache_fast_path_tests.rs
|
1aa9bbbfb2659facdaeaf185e11221b1c8cd5fe6e23c7c1dacd3aa55e3db4fcb src/proxy/tests/cache_fast_path_tests.rs
|
||||||
0f2eacadf1447db8aa1c1a78005aa262e0f747096bfe7e0a0e41a2cd31a0d17f src/proxy/tests/h3_navigation_tests.rs
|
0f2eacadf1447db8aa1c1a78005aa262e0f747096bfe7e0a0e41a2cd31a0d17f src/proxy/tests/h3_navigation_tests.rs
|
||||||
d134b0eb617f82f9971b3f3461379ea37c57bd0079bf54748d0c434b2ef1b475 src/proxy/tests/http_invalidation_tests.rs
|
d134b0eb617f82f9971b3f3461379ea37c57bd0079bf54748d0c434b2ef1b475 src/proxy/tests/http_invalidation_tests.rs
|
||||||
a74815a0b46d1c94ffbf8cc3a8bbbc967daa4aec0981139d2d92624c10553026 src/proxy/types.rs
|
fecc1c7ffff740caa7168370e59234a17b9836a6dce6d08b770ee9648c22709b src/proxy/types.rs
|
||||||
1db11efc7c35b8654f88f3176d9bca341c3e625d055e6c7e888f953c04b5f573 src/proxy/upstream_helpers.rs
|
0f9655d4ca1de35dcd7d5807d146cb3127a78b993beaeec7deec30e2f4040bb8 src/proxy/upstream_helpers.rs
|
||||||
2d9142e3ae95895b5271b4009ab48236a3a2074ce51179c021193b35777560e4 src/proxy/web_cache_revalidation.rs
|
2d9142e3ae95895b5271b4009ab48236a3a2074ce51179c021193b35777560e4 src/proxy/web_cache_revalidation.rs
|
||||||
d7b4b809b12ee89293df45e9a64b1c9981fb3f89deaab3762f4a3ad3992dd7b1 src/proxy/web_cache_revalidation/tests.rs
|
d7b4b809b12ee89293df45e9a64b1c9981fb3f89deaab3762f4a3ad3992dd7b1 src/proxy/web_cache_revalidation/tests.rs
|
||||||
69f775f6746b4c6d729dc10961dbef54168846f4ce9cf7fe453ab5a2b879c9b8 src/proxy/websocket.rs
|
69f775f6746b4c6d729dc10961dbef54168846f4ce9cf7fe453ab5a2b879c9b8 src/proxy/websocket.rs
|
||||||
@@ -425,8 +427,8 @@ ee2050b9f8efb45a209c91d76043a1ba29c4289978896aaf7e7d344d48fdc369 src/runtime/te
|
|||||||
0e5fc2aff856bf6fd959cba12b5ddd1e3b3cbeb0fbaadde906c811cdfafdaae7 src/security/tests.rs
|
0e5fc2aff856bf6fd959cba12b5ddd1e3b3cbeb0fbaadde906c811cdfafdaae7 src/security/tests.rs
|
||||||
98c52aa3b50d2e211ab752ccaf353c9a8c1500a87f9c1684b5dd0ba9d10e4619 src/server.rs
|
98c52aa3b50d2e211ab752ccaf353c9a8c1500a87f9c1684b5dd0ba9d10e4619 src/server.rs
|
||||||
f2f6196632b2fdb2444189624f961edaf5bdfff57f700b0207b57e2491c1daa5 src/server/http.rs
|
f2f6196632b2fdb2444189624f961edaf5bdfff57f700b0207b57e2491c1daa5 src/server/http.rs
|
||||||
050c48ea1127aa87dba92c074ab84e8346e65dbe253621ea53f8ca2f68d791ab src/server/http1_connection.rs
|
164384468c933320abf1c656f1e07a687aa869f5794d3e5a9b429885b5272266 src/server/http1_connection.rs
|
||||||
8c6ca5eaffb9acab57199d0349722703b903cfff3752baad2cf2596bd51188de src/server/listeners.rs
|
cd33fa634a5f1ee1d425e9e78269f9575b2e324967115383c80c6883c878ccfe src/server/listeners.rs
|
||||||
4424a99b0b688d1119ee25626778b4804aedfaa0799a4a9bc0ee99bc5c7ba96b src/server/sendfile.rs
|
4424a99b0b688d1119ee25626778b4804aedfaa0799a4a9bc0ee99bc5c7ba96b src/server/sendfile.rs
|
||||||
71899a9b1897fa22ab9e8eb6a670054a6d382bad78714006b9b218d07ba99902 src/server/serve.rs
|
71899a9b1897fa22ab9e8eb6a670054a6d382bad78714006b9b218d07ba99902 src/server/serve.rs
|
||||||
e9e478cf45b88994eaffd115f51764b6b07852ee3c941cafdabb20584d3dabc0 src/server/serve/tests.rs
|
e9e478cf45b88994eaffd115f51764b6b07852ee3c941cafdabb20584d3dabc0 src/server/serve/tests.rs
|
||||||
|
|||||||
+108
-45
@@ -28,6 +28,8 @@ pub struct Context {
|
|||||||
pub original_url: String,
|
pub original_url: String,
|
||||||
values: HeaderMap,
|
values: HeaderMap,
|
||||||
}
|
}
|
||||||
|
include!("forwarding/peer_formatting.rs");
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
pub fn compile(&self) -> Result<Compiled> {
|
pub fn compile(&self) -> Result<Compiled> {
|
||||||
ensure!(
|
ensure!(
|
||||||
@@ -231,24 +233,58 @@ impl Compiled {
|
|||||||
uri: &Uri,
|
uri: &Uri,
|
||||||
include_original_url: bool,
|
include_original_url: bool,
|
||||||
) -> Result<Context> {
|
) -> Result<Context> {
|
||||||
let peer_ip = canonical(peer.ip());
|
self.context_with_peer(
|
||||||
|
incoming, peer, local, tls, authority, uri, include_original_url,
|
||||||
|
None, false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Only the host/path validation and formatting data from the same request
|
||||||
|
/// and accepted connection are reused. Dynamic trust and XFF are evaluated
|
||||||
|
/// on every request, including after an in-place configuration reload.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub(crate) fn context_with_peer(
|
||||||
|
&self,
|
||||||
|
incoming: &HeaderMap,
|
||||||
|
peer: SocketAddr,
|
||||||
|
local: SocketAddr,
|
||||||
|
tls: bool,
|
||||||
|
authority: &HeaderValue,
|
||||||
|
uri: &Uri,
|
||||||
|
include_original_url: bool,
|
||||||
|
cached: Option<&PeerFormatting>,
|
||||||
|
validated_host: bool,
|
||||||
|
) -> Result<Context> {
|
||||||
|
let cached = cached.filter(|cached| cached.peer == peer && cached.local == local);
|
||||||
|
let peer_ip = cached.map_or_else(|| canonical(peer.ip()), |cached| cached.peer_ip);
|
||||||
let trusted_peer = self.trusted(peer_ip);
|
let trusted_peer = self.trusted(peer_ip);
|
||||||
if !trusted_peer {
|
if !trusted_peer {
|
||||||
let scheme = if tls { "https" } else { "http" };
|
let scheme = if tls { "https" } else { "http" };
|
||||||
let authority_text = authority.to_str()?;
|
let authority_text = authority.to_str()?;
|
||||||
crate::config::request_host(authority_text)?;
|
if !validated_host { crate::config::request_host(authority_text)?; }
|
||||||
let target = uri.path_and_query().map(|v| v.as_str()).unwrap_or("/");
|
let target = uri.path_and_query().map(|v| v.as_str()).unwrap_or("/");
|
||||||
let original_url = if include_original_url {
|
let original_url = if include_original_url {
|
||||||
format!("{scheme}://{authority_text}{target}")
|
format!("{scheme}://{authority_text}{target}")
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
let peer_text = peer_ip.to_string();
|
let peer_text = cached.is_none().then(|| peer_ip.to_string());
|
||||||
let peer_header = HeaderValue::from_str(&peer_text)?;
|
let ipv6_node = (cached.is_none() && peer_ip.is_ipv6()).then(|| node(peer_ip));
|
||||||
let peer_node = if peer_ip.is_ipv4() { None } else { Some(node(peer_ip)) };
|
let peer_header = if let Some(cached) = cached {
|
||||||
|
cached.peer_header.clone()
|
||||||
|
} else {
|
||||||
|
HeaderValue::from_str(peer_text.as_deref().unwrap_or(""))?
|
||||||
|
};
|
||||||
|
let by_text = cached.is_none().then(|| node(canonical(local.ip())));
|
||||||
|
let peer_node = cached.map_or_else(
|
||||||
|
|| if peer_ip.is_ipv4() { peer_text.as_deref().unwrap_or("") }
|
||||||
|
else { ipv6_node.as_deref().unwrap_or("") },
|
||||||
|
|cached| cached.peer_node.as_str(),
|
||||||
|
);
|
||||||
|
let by_node = cached.map_or_else(|| by_text.as_deref().unwrap_or(""),
|
||||||
|
|cached| cached.by_node.as_str());
|
||||||
let forwarded = format!(
|
let forwarded = format!(
|
||||||
"for={};by={};proto={scheme};host=\"{authority_text}\"",
|
"for={peer_node};by={by_node};proto={scheme};host=\"{authority_text}\""
|
||||||
peer_node.as_deref().unwrap_or(&peer_text), node(canonical(local.ip()))
|
|
||||||
);
|
);
|
||||||
let mut values = HeaderMap::with_capacity(7);
|
let mut values = HeaderMap::with_capacity(7);
|
||||||
values.insert("forwarded", forwarded.parse()?);
|
values.insert("forwarded", forwarded.parse()?);
|
||||||
@@ -256,7 +292,11 @@ impl Compiled {
|
|||||||
values.insert("x-real-ip", peer_header);
|
values.insert("x-real-ip", peer_header);
|
||||||
values.insert("x-forwarded-host", authority.clone());
|
values.insert("x-forwarded-host", authority.clone());
|
||||||
values.insert("x-forwarded-proto", HeaderValue::from_static(scheme));
|
values.insert("x-forwarded-proto", HeaderValue::from_static(scheme));
|
||||||
values.insert("x-forwarded-port", local.port().to_string().parse()?);
|
values.insert("x-forwarded-port", if let Some(cached) = cached {
|
||||||
|
cached.port_header.clone()
|
||||||
|
} else {
|
||||||
|
local.port().to_string().parse()?
|
||||||
|
});
|
||||||
values.insert("x-forwarded-uri", target.parse()?);
|
values.insert("x-forwarded-uri", target.parse()?);
|
||||||
return Ok(Context {
|
return Ok(Context {
|
||||||
client_ip: peer_ip,
|
client_ip: peer_ip,
|
||||||
@@ -287,29 +327,39 @@ impl Compiled {
|
|||||||
}
|
}
|
||||||
let authority_text = authority.to_str()?;
|
let authority_text = authority.to_str()?;
|
||||||
// Validate before inserting authority into Forwarded's quoted host field.
|
// Validate before inserting authority into Forwarded's quoted host field.
|
||||||
crate::config::request_host(authority_text)?;
|
if !validated_host { crate::config::request_host(authority_text)?; }
|
||||||
let original_target = uri.path_and_query().map(|v| v.as_str()).unwrap_or("/");
|
let original_target = uri.path_and_query().map(|v| v.as_str()).unwrap_or("/");
|
||||||
let original_url = if include_original_url {
|
let original_url = if include_original_url {
|
||||||
format!("{scheme}://{authority_text}{original_target}")
|
format!("{scheme}://{authority_text}{original_target}")
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
let mut forwarded: Vec<_> = ips.iter().map(|ip| format!("for={}", node(*ip))).collect();
|
use std::fmt::Write as _;
|
||||||
let last = forwarded.last_mut().expect("actual peer is always present");
|
let mut forwarded = String::with_capacity(ips.len() * 52 + 80 + authority_text.len());
|
||||||
last.push_str(&format!(
|
let mut xff = String::with_capacity(ips.len() * 44);
|
||||||
";by={};proto={scheme};host=\"{authority_text}\"",
|
for (index, ip) in ips.iter().enumerate() {
|
||||||
node(canonical(local.ip()))
|
if index > 0 { forwarded.push_str(", "); xff.push_str(", "); }
|
||||||
));
|
forwarded.push_str("for=");
|
||||||
let mut values = HeaderMap::new();
|
if index + 1 == ips.len() {
|
||||||
values.insert("forwarded", forwarded.join(", ").parse()?);
|
if let Some(cached) = cached { forwarded.push_str(&cached.peer_node); }
|
||||||
values.insert(
|
else { forwarded.push_str(&node(*ip)); }
|
||||||
"x-forwarded-for",
|
} else {
|
||||||
ips.iter()
|
forwarded.push_str(&node(*ip));
|
||||||
.map(ToString::to_string)
|
}
|
||||||
.collect::<Vec<_>>()
|
if index + 1 == ips.len() {
|
||||||
.join(", ")
|
if let Some(cached) = cached { xff.push_str(cached.peer_header.to_str()?); }
|
||||||
.parse()?,
|
else { write!(&mut xff, "{ip}")?; }
|
||||||
);
|
} else {
|
||||||
|
write!(&mut xff, "{ip}")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
forwarded.push_str(";by=");
|
||||||
|
if let Some(cached) = cached { forwarded.push_str(&cached.by_node); }
|
||||||
|
else { forwarded.push_str(&node(canonical(local.ip()))); }
|
||||||
|
write!(&mut forwarded, ";proto={scheme};host=\"{authority_text}\"")?;
|
||||||
|
let mut values = HeaderMap::with_capacity(7);
|
||||||
|
values.insert("forwarded", forwarded.parse()?);
|
||||||
|
values.insert("x-forwarded-for", xff.parse()?);
|
||||||
values.insert("x-real-ip", client_ip.to_string().parse()?);
|
values.insert("x-real-ip", client_ip.to_string().parse()?);
|
||||||
values.insert("x-forwarded-host", authority.clone());
|
values.insert("x-forwarded-host", authority.clone());
|
||||||
values.insert("x-forwarded-proto", HeaderValue::from_static(scheme));
|
values.insert("x-forwarded-proto", HeaderValue::from_static(scheme));
|
||||||
@@ -321,7 +371,11 @@ impl Compiled {
|
|||||||
} else {
|
} else {
|
||||||
local.port()
|
local.port()
|
||||||
};
|
};
|
||||||
values.insert("x-forwarded-port", port.to_string().parse()?);
|
values.insert("x-forwarded-port", if cached.is_some() && !(trusted_peer && self.trust_proto) {
|
||||||
|
cached.expect("tested above").port_header.clone()
|
||||||
|
} else {
|
||||||
|
port.to_string().parse()?
|
||||||
|
});
|
||||||
values.insert("x-forwarded-uri", original_target.parse()?);
|
values.insert("x-forwarded-uri", original_target.parse()?);
|
||||||
Ok(Context {
|
Ok(Context {
|
||||||
client_ip,
|
client_ip,
|
||||||
@@ -371,21 +425,35 @@ impl Context {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn apply(&self, headers: &mut HeaderMap) {
|
pub fn apply(&self, headers: &mut HeaderMap) {
|
||||||
let remove: Vec<_> = headers
|
self.apply_clean(headers, true);
|
||||||
.keys()
|
}
|
||||||
.filter(|name| {
|
fn apply_clean(&self, headers: &mut HeaderMap, include_client_ip: bool) {
|
||||||
name.as_str().starts_with("x-forwarded-")
|
// HeaderMap::insert replaces all values for a known canonical name.
|
||||||
|| matches!(
|
// Remove unknown/spoofed forwarding names while retaining the valid ones.
|
||||||
name.as_str(),
|
// The normal request has no extra spoofed header names. Allocate
|
||||||
"forwarded" | "x-real-ip" | "x-original-url" | "x-original-method"
|
// a removal list only for requests which actually need sanitization.
|
||||||
)
|
let mut remove = None;
|
||||||
})
|
for name in headers.keys() {
|
||||||
.cloned()
|
let text = name.as_str();
|
||||||
.collect();
|
if matches!(text, "x-original-url" | "x-original-method")
|
||||||
for name in remove {
|
|| (text.starts_with("x-forwarded-")
|
||||||
headers.remove(name);
|
&& !self.values.contains_key(text))
|
||||||
|
{
|
||||||
|
remove.get_or_insert_with(Vec::new).push(name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(remove) = remove {
|
||||||
|
for name in remove { headers.remove(name); }
|
||||||
|
}
|
||||||
|
if !include_client_ip {
|
||||||
|
for name in ["forwarded", "x-forwarded-for", "x-real-ip"] {
|
||||||
|
headers.remove(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (name, value) in &self.values {
|
for (name, value) in &self.values {
|
||||||
|
if !include_client_ip && matches!(name.as_str(), "forwarded" | "x-forwarded-for" | "x-real-ip") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
headers.insert(name.clone(), value.clone());
|
headers.insert(name.clone(), value.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -395,12 +463,7 @@ impl Context {
|
|||||||
/// the visitor address so frameworks such as ProxyFix see the RustProxy socket peer.
|
/// the visitor address so frameworks such as ProxyFix see the RustProxy socket peer.
|
||||||
/// Authentication verifiers use `apply` directly and still receive the real client IP.
|
/// Authentication verifiers use `apply` directly and still receive the real client IP.
|
||||||
pub fn apply_upstream(&self, headers: &mut HeaderMap, forward_client_ip: bool) {
|
pub fn apply_upstream(&self, headers: &mut HeaderMap, forward_client_ip: bool) {
|
||||||
self.apply(headers);
|
self.apply_clean(headers, forward_client_ip);
|
||||||
if !forward_client_ip {
|
|
||||||
headers.remove("forwarded");
|
|
||||||
headers.remove("x-forwarded-for");
|
|
||||||
headers.remove("x-real-ip");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/// Immutable formatting shared by all HTTP/1 requests on one accepted socket.
|
||||||
|
/// No routing or trust decision is cached: those follow the current snapshot.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct PeerFormatting {
|
||||||
|
peer: SocketAddr,
|
||||||
|
local: SocketAddr,
|
||||||
|
peer_ip: IpAddr,
|
||||||
|
peer_header: HeaderValue,
|
||||||
|
peer_node: String,
|
||||||
|
by_node: String,
|
||||||
|
port_header: HeaderValue,
|
||||||
|
}
|
||||||
|
impl PeerFormatting {
|
||||||
|
pub(crate) fn new(peer: SocketAddr, local: SocketAddr) -> Self {
|
||||||
|
let peer_ip = canonical(peer.ip());
|
||||||
|
let peer_text = peer_ip.to_string();
|
||||||
|
Self {
|
||||||
|
peer,
|
||||||
|
local,
|
||||||
|
peer_ip,
|
||||||
|
peer_header: HeaderValue::from_str(&peer_text).expect("IP text is a valid header"),
|
||||||
|
peer_node: if peer_ip.is_ipv4() { peer_text } else { node(peer_ip) },
|
||||||
|
by_node: node(canonical(local.ip())),
|
||||||
|
port_header: HeaderValue::from_str(&local.port().to_string())
|
||||||
|
.expect("TCP port is a valid header"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn peer_header(&self) -> &HeaderValue { &self.peer_header }
|
||||||
|
}
|
||||||
@@ -14,6 +14,105 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn socket_cached_forwarding_matches_uncached_for_ipv4_ipv6_and_trusted_hops() {
|
||||||
|
for (peer, local, networks) in [
|
||||||
|
("192.0.2.51:32000", "127.0.0.1:8080", Vec::<String>::new()),
|
||||||
|
("[2001:db8::8]:32100", "[2001:db8::1]:8443", Vec::new()),
|
||||||
|
("10.2.3.4:4123", "10.0.0.1:8443", vec!["10.0.0.0/8".to_owned()]),
|
||||||
|
] {
|
||||||
|
let peer: SocketAddr = peer.parse().unwrap();
|
||||||
|
let local: SocketAddr = local.parse().unwrap();
|
||||||
|
let formatter = PeerFormatting::new(peer, local);
|
||||||
|
let compiled = Settings {
|
||||||
|
trusted_proxies: networks,
|
||||||
|
trust_forwarded_proto: true,
|
||||||
|
}.compile().unwrap();
|
||||||
|
let mut incoming = HeaderMap::new();
|
||||||
|
incoming.insert("x-forwarded-for", "198.51.100.8, 10.3.2.1".parse().unwrap());
|
||||||
|
incoming.insert("x-forwarded-proto", "https".parse().unwrap());
|
||||||
|
let authority = HeaderValue::from_static("app.example:8443");
|
||||||
|
let uri: Uri = "/some%20file?q=4".parse().unwrap();
|
||||||
|
let plain = compiled.context_with_original_url(
|
||||||
|
&incoming, peer, local, true, &authority, &uri, false
|
||||||
|
).unwrap();
|
||||||
|
let cached = compiled.context_with_peer(
|
||||||
|
&incoming, peer, local, true, &authority, &uri, false,
|
||||||
|
Some(&formatter), true,
|
||||||
|
).unwrap();
|
||||||
|
assert_eq!(cached.client_ip, plain.client_ip);
|
||||||
|
assert_eq!(cached.scheme, plain.scheme);
|
||||||
|
assert_eq!(cached.original_url, plain.original_url);
|
||||||
|
assert_eq!(cached.values, plain.values);
|
||||||
|
assert_eq!(formatter.peer_header().to_str().unwrap(),
|
||||||
|
crate::vhost::canonical_ip(peer.ip()).to_string());
|
||||||
|
for include_ip in [false, true] {
|
||||||
|
let mut a = incoming.clone();
|
||||||
|
let mut b = incoming.clone();
|
||||||
|
a.insert("x-forwarded-evil", "spoofed".parse().unwrap());
|
||||||
|
b.insert("x-forwarded-evil", "spoofed".parse().unwrap());
|
||||||
|
a.append("x-forwarded-for", "1.2.3.4".parse().unwrap());
|
||||||
|
b.append("x-forwarded-for", "1.2.3.4".parse().unwrap());
|
||||||
|
plain.apply_upstream(&mut a, include_ip);
|
||||||
|
cached.apply_upstream(&mut b, include_ip);
|
||||||
|
assert_eq!(a, b);
|
||||||
|
assert!(!b.contains_key("x-forwarded-evil"));
|
||||||
|
assert_eq!(b.contains_key("x-real-ip"), include_ip);
|
||||||
|
assert_eq!(b.contains_key("forwarded"), include_ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn canonical_forwarding_headers_replace_duplicate_untrusted_values() {
|
||||||
|
let compiled = Settings::default().compile().unwrap();
|
||||||
|
let peer: SocketAddr = "192.0.2.9:4000".parse().unwrap();
|
||||||
|
let local: SocketAddr = "127.0.0.1:8080".parse().unwrap();
|
||||||
|
let value = compiled.context_with_peer(
|
||||||
|
&HeaderMap::new(), peer, local, false,
|
||||||
|
&HeaderValue::from_static("app.example"),
|
||||||
|
&"/".parse().unwrap(), false,
|
||||||
|
Some(&PeerFormatting::new(peer, local)), false,
|
||||||
|
).unwrap();
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
for (name, val) in [
|
||||||
|
("forwarded", "for=203.0.113.1"),
|
||||||
|
("forwarded", "for=203.0.113.2"),
|
||||||
|
("x-forwarded-for", "203.0.113.3"),
|
||||||
|
("x-forwarded-for", "203.0.113.4"),
|
||||||
|
("x-real-ip", "203.0.113.5"),
|
||||||
|
("x-forwarded-privilege", "admin"),
|
||||||
|
("x-original-method", "DELETE"),
|
||||||
|
] {
|
||||||
|
headers.append(name, val.parse().unwrap());
|
||||||
|
}
|
||||||
|
value.apply(&mut headers);
|
||||||
|
assert_eq!(headers.get_all("forwarded").iter().count(), 1);
|
||||||
|
assert_eq!(headers.get_all("x-forwarded-for").iter().count(), 1);
|
||||||
|
assert_eq!(headers["x-forwarded-for"], "192.0.2.9");
|
||||||
|
assert!(!headers.contains_key("x-forwarded-privilege"));
|
||||||
|
assert!(!headers.contains_key("x-original-method"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn peer_cache_can_never_override_the_actual_socket() {
|
||||||
|
let compiled = Settings::default().compile().unwrap();
|
||||||
|
let different = PeerFormatting::new(
|
||||||
|
"192.0.2.1:9001".parse().unwrap(),
|
||||||
|
"192.0.2.2:8080".parse().unwrap(),
|
||||||
|
);
|
||||||
|
let actual: SocketAddr = "198.51.100.6:4000".parse().unwrap();
|
||||||
|
let local: SocketAddr = "127.0.0.1:8080".parse().unwrap();
|
||||||
|
let authority = HeaderValue::from_static("app.example");
|
||||||
|
let uri: Uri = "/safe".parse().unwrap();
|
||||||
|
let value = compiled.context_with_peer(
|
||||||
|
&HeaderMap::new(), actual, local, false,
|
||||||
|
&authority, &uri, false, Some(&different), false,
|
||||||
|
).unwrap();
|
||||||
|
assert_eq!(value.client_ip, actual.ip());
|
||||||
|
assert_eq!(value.values["x-forwarded-for"], "198.51.100.6");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn untrusted_headers_are_rebuilt_not_appended() {
|
fn untrusted_headers_are_rebuilt_not_appended() {
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
|
|||||||
+28
-12
@@ -157,27 +157,43 @@ fn encoded_origin_path(path: &str) -> Option<String> {
|
|||||||
fn response_context(
|
fn response_context(
|
||||||
snapshot: &crate::config::Snapshot,
|
snapshot: &crate::config::Snapshot,
|
||||||
request: &Request,
|
request: &Request,
|
||||||
) -> (
|
) -> SelectedRouteContext {
|
||||||
Option<Arc<crate::config::CompiledFrontend>>,
|
|
||||||
Option<Arc<crate::config::CompiledRoute>>,
|
|
||||||
) {
|
|
||||||
let tls = request
|
let tls = request
|
||||||
.extensions()
|
.extensions()
|
||||||
.get::<Transport>()
|
.get::<Transport>()
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.tls;
|
.tls;
|
||||||
let context = (|| {
|
let selected = (|| {
|
||||||
let local = request.extensions().get::<LocalAddr>()?.0;
|
let local = request.extensions().get::<LocalAddr>()?.0;
|
||||||
let authority = effective_host(request, tls).ok()?;
|
let original_host = effective_host(request, tls).ok()?;
|
||||||
let host = request_host(authority.to_str().ok()?).ok()?;
|
let host = request_host(original_host.to_str().ok()?).ok()?;
|
||||||
let frontend = snapshot.frontend(&host, local, tls).cloned();
|
let frontend = snapshot.frontend(&host, local, tls).cloned();
|
||||||
let route = crate::acl::routing_path_view(request.uri().path())
|
let path = crate::acl::routing_path_view(request.uri().path()).ok();
|
||||||
.ok()
|
let route = path.as_ref().and_then(|path| {
|
||||||
.and_then(|path| snapshot.select_on_arc(&host, path.as_ref(), local, tls).cloned());
|
snapshot.select_on_arc(&host, path.as_ref(), local, tls).cloned()
|
||||||
Some((frontend, route))
|
});
|
||||||
|
// An ordinary path borrows the original URI in forward_request. Only
|
||||||
|
// a decoded path needs to be moved between middleware and forwarding.
|
||||||
|
let decoded_path = path.and_then(|path| match path {
|
||||||
|
std::borrow::Cow::Borrowed(_) => None,
|
||||||
|
std::borrow::Cow::Owned(path) => Some(path),
|
||||||
|
});
|
||||||
|
Some(SelectedRouteContext {
|
||||||
|
frontend,
|
||||||
|
route,
|
||||||
|
validated: Some(ValidatedRoutingIdentity {
|
||||||
|
original_host,
|
||||||
|
host,
|
||||||
|
decoded_path,
|
||||||
|
}),
|
||||||
|
})
|
||||||
})();
|
})();
|
||||||
context.unwrap_or((None, None))
|
selected.unwrap_or(SelectedRouteContext {
|
||||||
|
frontend: None,
|
||||||
|
route: None,
|
||||||
|
validated: None,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reserved administration-session cookies may cross the proxy boundary only
|
/// Reserved administration-session cookies may cross the proxy boundary only
|
||||||
|
|||||||
+19
-21
@@ -27,20 +27,11 @@ async fn forward_request(
|
|||||||
.copied()
|
.copied()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.tls;
|
.tls;
|
||||||
let Ok(original_host) = effective_host(&request, tls) else {
|
let (selected_context, original_host, host, decoded_path, host_already_validated) =
|
||||||
return error(
|
match take_routing_identity(&mut request, tls) {
|
||||||
StatusCode::BAD_REQUEST,
|
Ok(identity) => identity,
|
||||||
"RustProxy: invalid or conflicting request authority\n",
|
Err(response) => return response,
|
||||||
);
|
};
|
||||||
};
|
|
||||||
let Ok(host) = original_host
|
|
||||||
.to_str()
|
|
||||||
.ok()
|
|
||||||
.and_then(|v| request_host(v).ok())
|
|
||||||
.ok_or(())
|
|
||||||
else {
|
|
||||||
return error(StatusCode::BAD_REQUEST, "RustProxy: invalid hostname\n");
|
|
||||||
};
|
|
||||||
let handshake = if request.method() == Method::CONNECT {
|
let handshake = if request.method() == Method::CONNECT {
|
||||||
let protocol = match request.version() {
|
let protocol = match request.version() {
|
||||||
Version::HTTP_2 => websocket::h2_protocol(&request),
|
Version::HTTP_2 => websocket::h2_protocol(&request),
|
||||||
@@ -106,13 +97,17 @@ async fn forward_request(
|
|||||||
// forwarding operation, even after the request URI/headers are rewritten.
|
// forwarding operation, even after the request URI/headers are rewritten.
|
||||||
// Encoded/slash-folded paths still allocate via the strict decoder.
|
// Encoded/slash-folded paths still allocate via the strict decoder.
|
||||||
let routing_uri = request.uri().clone();
|
let routing_uri = request.uri().clone();
|
||||||
let Ok(path) = crate::acl::routing_path_view(routing_uri.path()) else {
|
let path = if let Some(path) = decoded_path.as_deref() {
|
||||||
return error(
|
std::borrow::Cow::Borrowed(path)
|
||||||
StatusCode::BAD_REQUEST,
|
} else {
|
||||||
"RustProxy: ambiguous request path\n",
|
let Ok(path) = crate::acl::routing_path_view(routing_uri.path()) else {
|
||||||
);
|
return error(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
"RustProxy: ambiguous request path\n",
|
||||||
|
);
|
||||||
|
};
|
||||||
|
path
|
||||||
};
|
};
|
||||||
let selected_context = request.extensions().get::<SelectedRouteContext>().cloned();
|
|
||||||
let frontend = if let Some(selection) = &selected_context {
|
let frontend = if let Some(selection) = &selected_context {
|
||||||
selection.frontend.as_ref()
|
selection.frontend.as_ref()
|
||||||
} else {
|
} else {
|
||||||
@@ -193,7 +188,7 @@ async fn forward_request(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut context = match snapshot.forwarding.context_with_original_url(
|
let mut context = match snapshot.forwarding.context_with_peer(
|
||||||
request.headers(),
|
request.headers(),
|
||||||
peer.0,
|
peer.0,
|
||||||
local.0,
|
local.0,
|
||||||
@@ -201,6 +196,9 @@ async fn forward_request(
|
|||||||
&original_host,
|
&original_host,
|
||||||
request.uri(),
|
request.uri(),
|
||||||
frontend.is_some_and(|frontend| frontend.tinyauth.is_some()),
|
frontend.is_some_and(|frontend| frontend.tinyauth.is_some()),
|
||||||
|
request.extensions().get::<Arc<crate::forwarding::PeerFormatting>>()
|
||||||
|
.map(Arc::as_ref),
|
||||||
|
host_already_validated,
|
||||||
) {
|
) {
|
||||||
Ok(context) => context,
|
Ok(context) => context,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Observation has already validated routing identity. Transfer it to the
|
||||||
|
// forward handler instead of repeating hostname parsing and percent decoding.
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct ValidatedRoutingIdentity {
|
||||||
|
original_host: HeaderValue,
|
||||||
|
host: String,
|
||||||
|
decoded_path: Option<String>,
|
||||||
|
}
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct SelectedRouteContext {
|
||||||
|
frontend: Option<Arc<crate::config::CompiledFrontend>>,
|
||||||
|
route: Option<Arc<crate::config::CompiledRoute>>,
|
||||||
|
validated: Option<ValidatedRoutingIdentity>,
|
||||||
|
}
|
||||||
|
/// Transfer middleware-validated identity without cloning strings. Internal
|
||||||
|
/// fallback dispatches still use the complete authority validation path.
|
||||||
|
fn take_routing_identity(
|
||||||
|
request: &mut Request,
|
||||||
|
tls: bool,
|
||||||
|
) -> Result<(Option<SelectedRouteContext>, HeaderValue, String, Option<String>, bool), Response> {
|
||||||
|
let mut selected_context = request.extensions_mut().remove::<SelectedRouteContext>();
|
||||||
|
let validated = selected_context.as_mut().and_then(|context| context.validated.take());
|
||||||
|
let host_already_validated = validated.is_some();
|
||||||
|
let (original_host, host, decoded_path) = match validated {
|
||||||
|
Some(validated) => (
|
||||||
|
validated.original_host,
|
||||||
|
validated.host,
|
||||||
|
validated.decoded_path,
|
||||||
|
),
|
||||||
|
None => {
|
||||||
|
let original_host = effective_host(request, tls).map_err(|_| error(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
"RustProxy: invalid or conflicting request authority\n",
|
||||||
|
))?;
|
||||||
|
let host = original_host.to_str().ok()
|
||||||
|
.and_then(|v| request_host(v).ok())
|
||||||
|
.ok_or_else(|| error(StatusCode::BAD_REQUEST,
|
||||||
|
"RustProxy: invalid hostname\n"))?;
|
||||||
|
(original_host, host, None)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok((selected_context, original_host, host, decoded_path, host_already_validated))
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
include!("types.rs");
|
include!("types.rs");
|
||||||
|
include!("identity.rs");
|
||||||
include!("error_fallback.rs");
|
include!("error_fallback.rs");
|
||||||
include!("cache_helpers.rs");
|
include!("cache_helpers.rs");
|
||||||
mod cache_plan;
|
mod cache_plan;
|
||||||
|
|||||||
@@ -11,6 +11,32 @@ mod authority_tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn observation_transfers_validated_host_and_decoded_path() {
|
||||||
|
let config = crate::config::demo_config("127.0.0.1:9000".parse().unwrap());
|
||||||
|
let snapshot = crate::config::compile(config, 1, &validation_policy()).unwrap();
|
||||||
|
for (raw, normalized) in [
|
||||||
|
("/assets/app.js?rev=5", None),
|
||||||
|
("/assets/%61pp.js?rev=5", Some("/assets/app.js")),
|
||||||
|
("/assets//app.js", Some("/assets/app.js")),
|
||||||
|
] {
|
||||||
|
let mut request = Request::builder().method(Method::GET).uri(raw)
|
||||||
|
.header(HOST, "localhost").body(Body::empty()).unwrap();
|
||||||
|
request.extensions_mut().insert(LocalAddr("127.0.0.1:8080".parse().unwrap()));
|
||||||
|
let context = response_context(&snapshot, &request);
|
||||||
|
let validated = context.validated.unwrap();
|
||||||
|
assert_eq!(validated.host, "localhost");
|
||||||
|
assert_eq!(validated.original_host, "localhost");
|
||||||
|
assert_eq!(validated.decoded_path.as_deref(), normalized);
|
||||||
|
}
|
||||||
|
let mut invalid = Request::builder().uri("/ok")
|
||||||
|
.header(HOST, "localhost").header(HOST, "another.example")
|
||||||
|
.body(Body::empty()).unwrap();
|
||||||
|
invalid.extensions_mut().insert(LocalAddr("127.0.0.1:8080".parse().unwrap()));
|
||||||
|
assert!(response_context(&snapshot, &invalid).validated.is_none());
|
||||||
|
assert!(crate::acl::routing_path_view("/encoded%2fslash").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn retry_is_limited_to_safe_methods() {
|
fn retry_is_limited_to_safe_methods() {
|
||||||
for method in [Method::GET, Method::HEAD, Method::OPTIONS] {
|
for method in [Method::GET, Method::HEAD, Method::OPTIONS] {
|
||||||
|
|||||||
+1
-7
@@ -21,13 +21,7 @@ use http::{
|
|||||||
};
|
};
|
||||||
use http_body::Body as HttpBody;
|
use http_body::Body as HttpBody;
|
||||||
use http_body_util::BodyExt;
|
use http_body_util::BodyExt;
|
||||||
/// A route resolved against the immutable snapshot in the observation layer.
|
/// Mark responses whose configured outbound policies were already applied.
|
||||||
/// The internal error-fallback dispatcher must resolve its target independently.
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct SelectedRouteContext {
|
|
||||||
frontend: Option<Arc<crate::config::CompiledFrontend>>,
|
|
||||||
route: Option<Arc<crate::config::CompiledRoute>>,
|
|
||||||
}
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct ResponseRulesApplied;
|
struct ResponseRulesApplied;
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|||||||
@@ -37,13 +37,12 @@ fn should_replace_error_page(response: &Response, minimal_welcome: bool) -> bool
|
|||||||
|
|
||||||
async fn observe(State(state): State<Arc<App>>, mut request: Request, next: Next) -> Response {
|
async fn observe(State(state): State<Arc<App>>, mut request: Request, next: Next) -> Response {
|
||||||
let snapshot = state.config.load_full();
|
let snapshot = state.config.load_full();
|
||||||
let (frontend_context, route_context) = response_context(&snapshot, &request);
|
let selected = response_context(&snapshot, &request);
|
||||||
// The same snapshot is inserted into the request. No second host/regex
|
let frontend_context = selected.frontend.clone();
|
||||||
// selection or expensive full CompiledRoute clone is necessary in forward().
|
let route_context = selected.route.clone();
|
||||||
request.extensions_mut().insert(SelectedRouteContext {
|
// The same validated request identity and snapshot are transferred to
|
||||||
frontend: frontend_context.clone(),
|
// forward(); no second hostname parsing or encoded-path decoding.
|
||||||
route: route_context.clone(),
|
request.extensions_mut().insert(selected);
|
||||||
});
|
|
||||||
let local = request.extensions().get::<LocalAddr>().map(|v| v.0);
|
let local = request.extensions().get::<LocalAddr>().map(|v| v.0);
|
||||||
let transport_tls = request
|
let transport_tls = request
|
||||||
.extensions()
|
.extensions()
|
||||||
|
|||||||
@@ -39,25 +39,29 @@ async fn connection<I>(
|
|||||||
}
|
}
|
||||||
let request_stop = stop.clone();
|
let request_stop = stop.clone();
|
||||||
let service_frontend_state = frontend_state.clone();
|
let service_frontend_state = frontend_state.clone();
|
||||||
|
let formatted_peer = Arc::new(crate::forwarding::PeerFormatting::new(peer, local));
|
||||||
let service = service_fn(move |mut request: Request<Incoming>| {
|
let service = service_fn(move |mut request: Request<Incoming>| {
|
||||||
let router = router.clone();
|
let mut router = router.clone();
|
||||||
let request_stop = request_stop.clone();
|
let request_stop = request_stop.clone();
|
||||||
let request_lease = lease.clone();
|
let request_lease = lease.clone();
|
||||||
let upgrade_state = service_frontend_state.clone();
|
let upgrade_state = service_frontend_state.clone();
|
||||||
|
let formatted_peer = formatted_peer.clone();
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
let sendfile_slots = sendfile_slots.clone();
|
let sendfile_slots = sendfile_slots.clone();
|
||||||
async move {
|
async move {
|
||||||
request.extensions_mut().insert(PeerAddr(peer));
|
// Reuse immutable per-socket formatting and insert extensions in
|
||||||
request.extensions_mut().insert(LocalAddr(local));
|
// one pass. Configuration and trust policies are still per-request.
|
||||||
request.extensions_mut().insert(transport);
|
let extensions = request.extensions_mut();
|
||||||
request.extensions_mut().insert(request_lease.clone());
|
extensions.insert(PeerAddr(peer));
|
||||||
|
extensions.insert(LocalAddr(local));
|
||||||
|
extensions.insert(transport);
|
||||||
|
extensions.insert(formatted_peer);
|
||||||
|
extensions.insert(request_lease.clone());
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
if let Some(slots) = sendfile_slots {
|
if let Some(slots) = sendfile_slots {
|
||||||
request.extensions_mut().insert(slots);
|
extensions.insert(slots);
|
||||||
}
|
}
|
||||||
request
|
extensions.insert(ServerShutdown(request_stop.clone()));
|
||||||
.extensions_mut()
|
|
||||||
.insert(ServerShutdown(request_stop.clone()));
|
|
||||||
|
|
||||||
if h2c_attempt(&request) {
|
if h2c_attempt(&request) {
|
||||||
if !h2c_upgrade {
|
if !h2c_upgrade {
|
||||||
@@ -175,7 +179,9 @@ async fn connection<I>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
router.oneshot(request.map(Body::new)).await
|
// Axum Router is always ready; avoid the per-request Oneshot
|
||||||
|
// readiness adapter while retaining the same middleware stack.
|
||||||
|
router.call(request.map(Body::new)).await
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let mut builder = http1::Builder::new();
|
let mut builder = http1::Builder::new();
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ use tokio::{
|
|||||||
task::JoinSet,
|
task::JoinSet,
|
||||||
};
|
};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
use tower::ServiceExt;
|
use tower::{Service, ServiceExt};
|
||||||
|
|
||||||
const HTTP2_PRIOR_KNOWLEDGE_PREFACE: &[u8; 24] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
|
const HTTP2_PRIOR_KNOWLEDGE_PREFACE: &[u8; 24] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
|
||||||
const H2_FRAME_HEADER_LEN: usize = 9;
|
const H2_FRAME_HEADER_LEN: usize = 9;
|
||||||
|
|||||||
Reference in new issue
Block a user