mirror of
https://github.com/telemt/telemt.git
synced 2026-09-15 15:04:15 +03:00
Rustfmt
This commit is contained in:
+16
-10
@@ -313,16 +313,22 @@ mod tests {
|
||||
assert!(page.body.contains("X-Lane-ID"));
|
||||
assert!(page.body.contains("const when=Date.parse(header)"));
|
||||
assert!(page.body.contains("},{once:false});"));
|
||||
assert!(page.body.contains("if(!sessionToken)throw new Error('missing session token')"));
|
||||
assert!(
|
||||
page.body
|
||||
.contains("if(!sessionToken)throw new Error('missing session token')")
|
||||
);
|
||||
assert!(!page.body.contains("welcomeBytes"));
|
||||
assert!(page
|
||||
.body
|
||||
.contains("for(const value of splitFrames(data))if(value.id!==lane.id)"));
|
||||
assert!(page
|
||||
.body
|
||||
.contains("let frames;try{frames=splitFrames(value)}catch(error){fail();return}"));
|
||||
assert!(page
|
||||
.content_security_policy
|
||||
.contains("frame-ancestors http://127.0.0.1:*"));
|
||||
assert!(
|
||||
page.body
|
||||
.contains("for(const value of splitFrames(data))if(value.id!==lane.id)")
|
||||
);
|
||||
assert!(
|
||||
page.body
|
||||
.contains("let frames;try{frames=splitFrames(value)}catch(error){fail();return}")
|
||||
);
|
||||
assert!(
|
||||
page.content_security_policy
|
||||
.contains("frame-ancestors http://127.0.0.1:*")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-9
@@ -98,15 +98,10 @@ pub(crate) fn parse_all<'a>(
|
||||
return Err(FrameError::Incomplete);
|
||||
}
|
||||
let frame_type = FrameType::parse(remaining[0]).ok_or(FrameError::UnknownType)?;
|
||||
let stream_id = u32::from(remaining[1]) << 16
|
||||
| u32::from(remaining[2]) << 8
|
||||
| u32::from(remaining[3]);
|
||||
let payload_len = u32::from_be_bytes([
|
||||
remaining[4],
|
||||
remaining[5],
|
||||
remaining[6],
|
||||
remaining[7],
|
||||
]) as usize;
|
||||
let stream_id =
|
||||
u32::from(remaining[1]) << 16 | u32::from(remaining[2]) << 8 | u32::from(remaining[3]);
|
||||
let payload_len =
|
||||
u32::from_be_bytes([remaining[4], remaining[5], remaining[6], remaining[7]]) as usize;
|
||||
if payload_len > limits.max_frame_payload_bytes {
|
||||
return Err(FrameError::PayloadLimit);
|
||||
}
|
||||
|
||||
+13
-30
@@ -34,9 +34,9 @@ mod request;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use decoy::serve_decoy;
|
||||
use activity::{ActivityBody, RequestActivity};
|
||||
use body::{CollectBodyError, CollectedBody, collect_body};
|
||||
use decoy::serve_decoy;
|
||||
use request::{
|
||||
bearer_token_hash, binary_content_type, bridge_candidate, canonical_request_host,
|
||||
canonical_u64_header, client_ip, match_profile,
|
||||
@@ -84,10 +84,7 @@ pub(crate) async fn serve_connection(
|
||||
} else {
|
||||
service_unavailable()
|
||||
};
|
||||
let response = response.map(|body| {
|
||||
ActivityBody::new(body, activity)
|
||||
.boxed_unsync()
|
||||
});
|
||||
let response = response.map(|body| ActivityBody::new(body, activity).boxed_unsync());
|
||||
Ok::<_, Infallible>(response)
|
||||
}
|
||||
});
|
||||
@@ -174,12 +171,7 @@ async fn handle_root(
|
||||
let Some(profile) = profile.filter(|_| canonical && request.method() == Method::GET) else {
|
||||
return serve_decoy(request, vhost, false, &runtime).await;
|
||||
};
|
||||
let Some(client_ip) = client_ip(
|
||||
&request,
|
||||
peer,
|
||||
client_ip_source,
|
||||
trusted_proxy_cidrs,
|
||||
) else {
|
||||
let Some(client_ip) = client_ip(&request, peer, client_ip_source, trusted_proxy_cidrs) else {
|
||||
strip_query(&mut request);
|
||||
return serve_decoy(request, vhost, true, &runtime).await;
|
||||
};
|
||||
@@ -242,21 +234,14 @@ async fn handle_api(
|
||||
if request.uri().query().is_some() || request.headers().contains_key(header::COOKIE) {
|
||||
return serve_decoy(request, vhost, true, &runtime).await;
|
||||
}
|
||||
let Some(client_ip) = client_ip(
|
||||
&request,
|
||||
peer,
|
||||
client_ip_source,
|
||||
trusted_proxy_cidrs,
|
||||
) else {
|
||||
let Some(client_ip) = client_ip(&request, peer, client_ip_source, trusted_proxy_cidrs) else {
|
||||
return serve_decoy(request, vhost, true, &runtime).await;
|
||||
};
|
||||
let Some(token_hash) = bearer_token_hash(&request) else {
|
||||
return serve_decoy(request, vhost, true, &runtime).await;
|
||||
};
|
||||
match request.uri().path() {
|
||||
"/api/v1/session" => {
|
||||
handle_session(request, runtime, vhost, token_hash, client_ip).await
|
||||
}
|
||||
"/api/v1/session" => handle_session(request, runtime, vhost, token_hash, client_ip).await,
|
||||
"/api/v1/up" => handle_up(request, runtime, vhost, token_hash).await,
|
||||
"/api/v1/down" => handle_down(request, runtime, vhost, token_hash).await,
|
||||
_ => serve_decoy(request, vhost, true, &runtime).await,
|
||||
@@ -356,7 +341,12 @@ async fn handle_up(
|
||||
let Some(lane_id) = carrier_lane(&request, session.carrier()) else {
|
||||
return serve_decoy(request, vhost, true, &runtime).await;
|
||||
};
|
||||
let limit = runtime.active_generation().config().web.limits.max_body_bytes;
|
||||
let limit = runtime
|
||||
.active_generation()
|
||||
.config()
|
||||
.web
|
||||
.limits
|
||||
.max_body_bytes;
|
||||
let CollectedBody {
|
||||
request,
|
||||
body,
|
||||
@@ -510,10 +500,7 @@ fn bad_gateway() -> HttpResponse {
|
||||
}
|
||||
|
||||
fn generic_not_found() -> HttpResponse {
|
||||
full_response(
|
||||
StatusCode::NOT_FOUND,
|
||||
Bytes::from_static(b"not found\n"),
|
||||
)
|
||||
full_response(StatusCode::NOT_FOUND, Bytes::from_static(b"not found\n"))
|
||||
}
|
||||
|
||||
fn full_response(status: StatusCode, body: Bytes) -> HttpResponse {
|
||||
@@ -523,11 +510,7 @@ fn full_response(status: StatusCode, body: Bytes) -> HttpResponse {
|
||||
.boxed_unsync();
|
||||
let mut response = Response::new(body);
|
||||
*response.status_mut() = status;
|
||||
insert_header(
|
||||
&mut response,
|
||||
header::CONTENT_LENGTH,
|
||||
&length.to_string(),
|
||||
);
|
||||
insert_header(&mut response, header::CONTENT_LENGTH, &length.to_string());
|
||||
response
|
||||
}
|
||||
|
||||
|
||||
@@ -37,10 +37,7 @@ pub(super) struct ActivityBody {
|
||||
impl ActivityBody {
|
||||
/// Binds one response body to its request activity guard.
|
||||
pub(super) fn new(inner: HttpBody, activity: RequestActivity) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
activity,
|
||||
}
|
||||
Self { inner, activity }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ use std::time::Duration;
|
||||
|
||||
use bytes::Bytes;
|
||||
use http_body_util::{BodyExt, Empty, Limited};
|
||||
use hyper::body::{Body as _, Incoming};
|
||||
use hyper::Request;
|
||||
use hyper::body::{Body as _, Incoming};
|
||||
|
||||
use crate::web::manager::WebProcessRuntime;
|
||||
|
||||
@@ -50,14 +50,8 @@ pub(super) async fn collect_body(
|
||||
let Some((reader_budget, body_budget)) = runtime.try_body_budget(limit) else {
|
||||
return Err(CollectBodyError::Limit);
|
||||
};
|
||||
let body_timeout = Duration::from_secs(
|
||||
runtime
|
||||
.active_generation()
|
||||
.config()
|
||||
.web
|
||||
.timeouts
|
||||
.body_secs,
|
||||
);
|
||||
let body_timeout =
|
||||
Duration::from_secs(runtime.active_generation().config().web.timeouts.body_secs);
|
||||
let body = match tokio::time::timeout(body_timeout, Limited::new(body, limit).collect()).await {
|
||||
Ok(Ok(body)) => body.to_bytes(),
|
||||
_ => {
|
||||
|
||||
+19
-22
@@ -11,8 +11,7 @@ use hyper_util::rt::TokioIo;
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
use super::{
|
||||
BoxError, HttpBody, HttpResponse, bad_gateway, full_response, generic_not_found,
|
||||
insert_header,
|
||||
BoxError, HttpBody, HttpResponse, bad_gateway, full_response, generic_not_found, insert_header,
|
||||
};
|
||||
use crate::config::{WebRuntimeDecoy, WebRuntimeVhost};
|
||||
use crate::web::manager::WebProcessRuntime;
|
||||
@@ -128,14 +127,13 @@ fn static_entry<B>(
|
||||
header::X_CONTENT_TYPE_OPTIONS,
|
||||
HeaderValue::from_static("nosniff"),
|
||||
);
|
||||
response.headers_mut().insert(header::X_FRAME_OPTIONS, HeaderValue::from_static("DENY"));
|
||||
response
|
||||
.headers_mut()
|
||||
.insert(header::X_FRAME_OPTIONS, HeaderValue::from_static("DENY"));
|
||||
response
|
||||
}
|
||||
|
||||
fn resolve_static_path<'a>(
|
||||
path: &str,
|
||||
site: &'a crate::config::WebStaticSite,
|
||||
) -> Option<&'a str> {
|
||||
fn resolve_static_path<'a>(path: &str, site: &'a crate::config::WebStaticSite) -> Option<&'a str> {
|
||||
if !path.starts_with('/')
|
||||
|| path.contains('\\')
|
||||
|| path.contains("//")
|
||||
@@ -151,7 +149,10 @@ fn resolve_static_path<'a>(
|
||||
path
|
||||
};
|
||||
if site.assets.contains_key(route) {
|
||||
return site.assets.get_key_value(route).map(|(key, _)| key.as_str());
|
||||
return site
|
||||
.assets
|
||||
.get_key_value(route)
|
||||
.map(|(key, _)| key.as_str());
|
||||
}
|
||||
if route == "/favicon.ico" && site.assets.contains_key("/favicon.svg") {
|
||||
return Some("/favicon.svg");
|
||||
@@ -198,23 +199,19 @@ async fn proxy_to_upstream(
|
||||
.max_header_bytes;
|
||||
let mut builder = hyper::client::conn::http1::Builder::new();
|
||||
builder.max_buf_size(max_header_bytes);
|
||||
let (mut sender, connection) = match tokio::time::timeout(
|
||||
header_timeout,
|
||||
builder.handshake(TokioIo::new(stream)),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(parts)) => parts,
|
||||
_ => return bad_gateway(),
|
||||
};
|
||||
let (mut sender, connection) =
|
||||
match tokio::time::timeout(header_timeout, builder.handshake(TokioIo::new(stream))).await {
|
||||
Ok(Ok(parts)) => parts,
|
||||
_ => return bad_gateway(),
|
||||
};
|
||||
runtime.spawn_auxiliary(async move {
|
||||
let _ = connection.await;
|
||||
});
|
||||
let mut response = match tokio::time::timeout(header_timeout, sender.send_request(request)).await
|
||||
{
|
||||
Ok(Ok(response)) => response,
|
||||
_ => return bad_gateway(),
|
||||
};
|
||||
let mut response =
|
||||
match tokio::time::timeout(header_timeout, sender.send_request(request)).await {
|
||||
Ok(Ok(response)) => response,
|
||||
_ => return bad_gateway(),
|
||||
};
|
||||
remove_hop_by_hop(response.headers_mut());
|
||||
response.map(|body| {
|
||||
body.map_err(|error| -> BoxError { Box::new(error) })
|
||||
|
||||
+11
-21
@@ -2,15 +2,13 @@ use std::net::{IpAddr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
|
||||
use base64::Engine as _;
|
||||
use hyper::header;
|
||||
use hyper::Request;
|
||||
use hyper::header;
|
||||
use ipnetwork::IpNetwork;
|
||||
use sha2::{Digest, Sha256};
|
||||
use subtle::ConstantTimeEq;
|
||||
|
||||
use crate::config::{
|
||||
WebClientIpSource, WebRuntimeProfile, WebRuntimeVhost,
|
||||
};
|
||||
use crate::config::{WebClientIpSource, WebRuntimeProfile, WebRuntimeVhost};
|
||||
use crate::web::manager::TokenHash;
|
||||
|
||||
/// Parses one lowercase canonical Host value restricted to the public HTTPS port.
|
||||
@@ -26,8 +24,7 @@ pub(super) fn canonical_request_host<B>(request: &Request<B>) -> Option<&str> {
|
||||
return None;
|
||||
}
|
||||
let host = value.strip_suffix(":443").unwrap_or(value);
|
||||
if authority.host() != host || host.bytes().any(|byte| byte.is_ascii_uppercase())
|
||||
{
|
||||
if authority.host() != host || host.bytes().any(|byte| byte.is_ascii_uppercase()) {
|
||||
return None;
|
||||
}
|
||||
Some(host)
|
||||
@@ -74,14 +71,14 @@ pub(super) fn bridge_candidate(query: Option<&str>) -> ([u8; 32], bool) {
|
||||
return (candidate, false);
|
||||
}
|
||||
let mut decoded = [0u8; 32];
|
||||
let Ok(decoded_len) = base64::engine::general_purpose::URL_SAFE_NO_PAD
|
||||
.decode_slice(value, &mut decoded)
|
||||
let Ok(decoded_len) =
|
||||
base64::engine::general_purpose::URL_SAFE_NO_PAD.decode_slice(value, &mut decoded)
|
||||
else {
|
||||
return (candidate, false);
|
||||
};
|
||||
let mut canonical = [0u8; 43];
|
||||
let Ok(encoded_len) = base64::engine::general_purpose::URL_SAFE_NO_PAD
|
||||
.encode_slice(decoded, &mut canonical)
|
||||
let Ok(encoded_len) =
|
||||
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode_slice(decoded, &mut canonical)
|
||||
else {
|
||||
return (candidate, false);
|
||||
};
|
||||
@@ -114,8 +111,7 @@ pub(super) fn bearer_token_hash<B>(request: &Request<B>) -> Option<TokenHash> {
|
||||
let values = request.headers().get_all(header::AUTHORIZATION);
|
||||
let mut values = values.iter();
|
||||
let value = values.next()?.to_str().ok()?;
|
||||
if values.next().is_some() || !value.starts_with("Bearer ") || value.matches(' ').count() != 1
|
||||
{
|
||||
if values.next().is_some() || !value.starts_with("Bearer ") || value.matches(' ').count() != 1 {
|
||||
return None;
|
||||
}
|
||||
let token = value.strip_prefix("Bearer ")?;
|
||||
@@ -133,7 +129,7 @@ pub(super) fn bearer_token_hash<B>(request: &Request<B>) -> Option<TokenHash> {
|
||||
(decoded_len == decoded.len()
|
||||
&& encoded_len == canonical.len()
|
||||
&& bool::from(canonical.ct_eq(token.as_bytes())))
|
||||
.then(|| Sha256::digest(decoded).into())
|
||||
.then(|| Sha256::digest(decoded).into())
|
||||
}
|
||||
|
||||
/// Checks the exact carrier media type without accepting duplicate headers.
|
||||
@@ -146,10 +142,7 @@ pub(super) fn binary_content_type<B>(request: &Request<B>) -> bool {
|
||||
}
|
||||
|
||||
/// Parses one canonical unsigned decimal carrier sequence header.
|
||||
pub(super) fn canonical_u64_header<B>(
|
||||
request: &Request<B>,
|
||||
name: &'static str,
|
||||
) -> Option<u64> {
|
||||
pub(super) fn canonical_u64_header<B>(request: &Request<B>, name: &'static str) -> Option<u64> {
|
||||
let values = request.headers().get_all(name);
|
||||
let mut values = values.iter();
|
||||
let value = values.next()?.to_str().ok()?;
|
||||
@@ -183,10 +176,7 @@ mod tests {
|
||||
.header("x-forwarded-for", "192.0.2.10")
|
||||
.body(())
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
canonical_request_host(&request),
|
||||
Some("proxy.example.com")
|
||||
);
|
||||
assert_eq!(canonical_request_host(&request), Some("proxy.example.com"));
|
||||
let trusted: [IpNetwork; 1] = ["127.0.0.1/32".parse().unwrap()];
|
||||
assert_eq!(
|
||||
client_ip(
|
||||
|
||||
+26
-25
@@ -164,10 +164,8 @@ async fn https_carrier_bootstraps_and_closes_one_session() {
|
||||
let session = response_header(create_headers, "x-session-token");
|
||||
assert_eq!(session.len(), 43);
|
||||
|
||||
let replacement = test_runtime_generation(
|
||||
2,
|
||||
runtime_config(capability, WebCarrier::HttpsLanes),
|
||||
);
|
||||
let replacement =
|
||||
test_runtime_generation(2, runtime_config(capability, WebCarrier::HttpsLanes));
|
||||
active_runtime.store(Arc::clone(&replacement));
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
let retry_response = request(&listener, &runtime, create_retry).await;
|
||||
@@ -183,10 +181,16 @@ async fn https_carrier_bootstraps_and_closes_one_session() {
|
||||
.into_bytes();
|
||||
let next_root_response = request(&listener, &runtime, next_root).await;
|
||||
let (_, next_root_body) = split_response(&next_root_response);
|
||||
assert!(next_root_body.windows(11).any(|value| value == b"bootstrap='"));
|
||||
assert!(next_root_body
|
||||
.windows(21)
|
||||
.any(|value| value == b"carrier='https-lanes'"));
|
||||
assert!(
|
||||
next_root_body
|
||||
.windows(11)
|
||||
.any(|value| value == b"bootstrap='")
|
||||
);
|
||||
assert!(
|
||||
next_root_body
|
||||
.windows(21)
|
||||
.any(|value| value == b"carrier='https-lanes'")
|
||||
);
|
||||
|
||||
let close = format!(
|
||||
"DELETE /api/v1/session HTTP/1.1\r\nHost: proxy.example.com\r\nX-Forwarded-For: 192.0.2.10\r\nAuthorization: Bearer {session}\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
|
||||
@@ -262,10 +266,7 @@ async fn unused_bootstrap_survives_equivalent_runtime_generation_swap() {
|
||||
.map(|(token, _)| token)
|
||||
.unwrap();
|
||||
|
||||
let replacement = test_runtime_generation(
|
||||
2,
|
||||
runtime_config(capability, WebCarrier::Https),
|
||||
);
|
||||
let replacement = test_runtime_generation(2, runtime_config(capability, WebCarrier::Https));
|
||||
active_runtime.store(Arc::clone(&replacement));
|
||||
let hello = frame::encode(FrameType::Hello, 0, &[1]);
|
||||
let mut create = format!(
|
||||
@@ -305,10 +306,8 @@ async fn unused_bootstrap_is_rejected_after_profile_identity_change() {
|
||||
.map(|(token, _)| token)
|
||||
.unwrap();
|
||||
|
||||
let replacement = test_runtime_generation(
|
||||
2,
|
||||
runtime_config(capability, WebCarrier::HttpsLanes),
|
||||
);
|
||||
let replacement =
|
||||
test_runtime_generation(2, runtime_config(capability, WebCarrier::HttpsLanes));
|
||||
active_runtime.store(Arc::clone(&replacement));
|
||||
let hello = frame::encode(FrameType::Hello, 0, &[1]);
|
||||
let mut create = format!(
|
||||
@@ -330,10 +329,7 @@ async fn unused_bootstrap_is_rejected_after_profile_identity_change() {
|
||||
#[tokio::test]
|
||||
async fn https_lanes_is_advertised_and_requires_canonical_lane_headers() {
|
||||
let capability = [9u8; 32];
|
||||
let generation = test_runtime_generation(
|
||||
1,
|
||||
runtime_config(capability, WebCarrier::HttpsLanes),
|
||||
);
|
||||
let generation = test_runtime_generation(1, runtime_config(capability, WebCarrier::HttpsLanes));
|
||||
let active_runtime = Arc::new(ArcSwap::from(Arc::clone(&generation)));
|
||||
let runtime = WebProcessRuntime::start(active_runtime);
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
@@ -361,7 +357,10 @@ async fn https_lanes_is_advertised_and_requires_canonical_lane_headers() {
|
||||
create.extend_from_slice(&hello);
|
||||
let create_response = request(&listener, &runtime, create).await;
|
||||
let (create_headers, _) = split_response(&create_response);
|
||||
assert_eq!(response_header(create_headers, "x-carrier-mode"), "https-lanes");
|
||||
assert_eq!(
|
||||
response_header(create_headers, "x-carrier-mode"),
|
||||
"https-lanes"
|
||||
);
|
||||
let session = response_header(create_headers, "x-session-token").to_string();
|
||||
|
||||
let pong = frame::encode(FrameType::Pong, 0, &[]);
|
||||
@@ -375,10 +374,12 @@ async fn https_lanes_is_advertised_and_requires_canonical_lane_headers() {
|
||||
let (uplink_headers, _) = split_response(&uplink_response);
|
||||
assert!(uplink_headers.starts_with(b"HTTP/1.1 204"));
|
||||
assert_eq!(response_header(uplink_headers, "x-up-ack"), "1");
|
||||
assert!(!std::str::from_utf8(uplink_headers)
|
||||
.unwrap()
|
||||
.lines()
|
||||
.any(|line| line.to_ascii_lowercase().starts_with("content-length:")));
|
||||
assert!(
|
||||
!std::str::from_utf8(uplink_headers)
|
||||
.unwrap()
|
||||
.lines()
|
||||
.any(|line| line.to_ascii_lowercase().starts_with("content-length:"))
|
||||
);
|
||||
|
||||
let mut missing_lane = format!(
|
||||
"POST /api/v1/up HTTP/1.1\r\nHost: proxy.example.com\r\nX-Forwarded-For: 192.0.2.10\r\nAuthorization: Bearer {session}\r\nContent-Type: application/octet-stream\r\nX-Up-Seq: 2\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
|
||||
|
||||
+16
-26
@@ -89,9 +89,7 @@ pub(crate) struct WebProcessRuntime {
|
||||
|
||||
impl WebProcessRuntime {
|
||||
/// Starts one process-scoped manager using immutable allocation ceilings.
|
||||
pub(crate) fn start(
|
||||
active_runtime: Arc<ArcSwap<RuntimeGeneration>>,
|
||||
) -> Arc<Self> {
|
||||
pub(crate) fn start(active_runtime: Arc<ArcSwap<RuntimeGeneration>>) -> Arc<Self> {
|
||||
let limits = active_runtime.load().config().web.limits.clone();
|
||||
let runtime = Arc::new(Self {
|
||||
active_runtime,
|
||||
@@ -169,9 +167,7 @@ impl WebProcessRuntime {
|
||||
|
||||
/// Reserves one logical stream in the inner MTProxy handshake phase.
|
||||
pub(crate) fn try_stream_handshake(&self) -> Option<OwnedSemaphorePermit> {
|
||||
let permit = Arc::clone(&self.stream_handshakes)
|
||||
.try_acquire_owned()
|
||||
.ok();
|
||||
let permit = Arc::clone(&self.stream_handshakes).try_acquire_owned().ok();
|
||||
if permit.is_none() {
|
||||
self.record_stream_rejected();
|
||||
}
|
||||
@@ -236,7 +232,11 @@ impl WebProcessRuntime {
|
||||
let mut state = self.state.lock();
|
||||
remove_expired_locked(&mut state, now);
|
||||
if state.closed
|
||||
|| state.bootstraps_per_ip.get(&client_ip).copied().unwrap_or(0)
|
||||
|| state
|
||||
.bootstraps_per_ip
|
||||
.get(&client_ip)
|
||||
.copied()
|
||||
.unwrap_or(0)
|
||||
>= self.limits.max_bootstraps_per_ip
|
||||
|| !allow_rate(
|
||||
&mut state.bootstrap_rate,
|
||||
@@ -313,10 +313,7 @@ impl WebProcessRuntime {
|
||||
if !digest_matches {
|
||||
return Err(ManagerError::Authentication);
|
||||
}
|
||||
let session = entry
|
||||
.session
|
||||
.as_ref()
|
||||
.ok_or(ManagerError::Authentication)?;
|
||||
let session = entry.session.as_ref().ok_or(ManagerError::Authentication)?;
|
||||
return Ok(CreateResult {
|
||||
token: entry.session_token.as_str().to_owned(),
|
||||
carrier: session.carrier(),
|
||||
@@ -448,14 +445,11 @@ impl WebProcessRuntime {
|
||||
let fits = if control {
|
||||
bytes <= self.limits.control_bytes_global
|
||||
&& items <= control_item_reserve
|
||||
&& state.pending_bytes
|
||||
<= self.limits.pending_bytes_global.saturating_sub(bytes)
|
||||
&& state.pending_items
|
||||
<= self.limits.pending_items_global.saturating_sub(items)
|
||||
&& state.pending_bytes <= self.limits.pending_bytes_global.saturating_sub(bytes)
|
||||
&& state.pending_items <= self.limits.pending_items_global.saturating_sub(items)
|
||||
&& state.pending_control_bytes
|
||||
<= self.limits.control_bytes_global.saturating_sub(bytes)
|
||||
&& state.pending_control_items
|
||||
<= control_item_reserve.saturating_sub(items)
|
||||
&& state.pending_control_items <= control_item_reserve.saturating_sub(items)
|
||||
} else {
|
||||
let data_bytes = state
|
||||
.pending_bytes
|
||||
@@ -464,14 +458,11 @@ impl WebProcessRuntime {
|
||||
.pending_items
|
||||
.saturating_sub(state.pending_control_items);
|
||||
let (byte_limit, item_limit) = if downlink {
|
||||
let uplink_bytes = self
|
||||
.limits
|
||||
.max_body_bytes
|
||||
.saturating_add(
|
||||
self.limits
|
||||
.max_frames_per_body
|
||||
.saturating_mul(crate::web::session::QUEUE_ITEM_COST),
|
||||
);
|
||||
let uplink_bytes = self.limits.max_body_bytes.saturating_add(
|
||||
self.limits
|
||||
.max_frames_per_body
|
||||
.saturating_mul(crate::web::session::QUEUE_ITEM_COST),
|
||||
);
|
||||
(
|
||||
data_byte_limit.saturating_sub(uplink_bytes),
|
||||
data_item_limit.saturating_sub(self.limits.max_frames_per_body),
|
||||
@@ -531,5 +522,4 @@ impl WebProcessRuntime {
|
||||
fn record_limit_hit(&self) {
|
||||
self.limit_hits.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@ use std::net::{IpAddr, SocketAddr};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::Instant;
|
||||
|
||||
use super::state::{
|
||||
allocate_stream_port, allow_rate, decrement_map, release_stream_port,
|
||||
};
|
||||
use super::state::{allocate_stream_port, allow_rate, decrement_map, release_stream_port};
|
||||
use super::{ProfileKey, WebProcessRuntime};
|
||||
|
||||
impl WebProcessRuntime {
|
||||
@@ -43,10 +41,7 @@ impl WebProcessRuntime {
|
||||
return None;
|
||||
};
|
||||
state.streams_live += 1;
|
||||
*state
|
||||
.streams_per_profile
|
||||
.entry(profile_key)
|
||||
.or_insert(0) += 1;
|
||||
*state.streams_per_profile.entry(profile_key).or_insert(0) += 1;
|
||||
self.streams_opened.fetch_add(1, Ordering::Relaxed);
|
||||
Some(peer_port)
|
||||
}
|
||||
@@ -105,12 +100,7 @@ mod tests {
|
||||
let downlink_bytes = data_bytes - uplink_bytes;
|
||||
let downlink_items = data_items - runtime.limits.max_frames_per_body;
|
||||
|
||||
assert!(runtime.try_reserve_pending(
|
||||
downlink_bytes,
|
||||
downlink_items,
|
||||
false,
|
||||
true,
|
||||
));
|
||||
assert!(runtime.try_reserve_pending(downlink_bytes, downlink_items, false, true,));
|
||||
assert!(runtime.try_reserve_pending(
|
||||
uplink_bytes,
|
||||
runtime.limits.max_frames_per_body,
|
||||
@@ -120,11 +110,7 @@ mod tests {
|
||||
assert!(!runtime.try_reserve_pending(1, 1, false, true));
|
||||
|
||||
runtime.release_pending(downlink_bytes, downlink_items, false);
|
||||
runtime.release_pending(
|
||||
uplink_bytes,
|
||||
runtime.limits.max_frames_per_body,
|
||||
false,
|
||||
);
|
||||
runtime.release_pending(uplink_bytes, runtime.limits.max_frames_per_body, false);
|
||||
runtime.shutdown().await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,8 @@ use std::time::{Duration, Instant};
|
||||
|
||||
use tracing::info;
|
||||
|
||||
use super::state::{ClosedToken, decrement_map, remove_bootstrap_locked, remove_expired_locked};
|
||||
use super::{ProfileKey, TokenHash, WebProcessRuntime};
|
||||
use super::state::{
|
||||
ClosedToken, decrement_map, remove_bootstrap_locked, remove_expired_locked,
|
||||
};
|
||||
|
||||
impl WebProcessRuntime {
|
||||
/// Removes one closed session and retains a bounded host-bound replay marker.
|
||||
|
||||
@@ -135,17 +135,11 @@ pub(super) fn matching_profile(
|
||||
}
|
||||
|
||||
/// Applies one token-bucket admission decision at a caller-supplied monotonic time.
|
||||
pub(super) fn allow_rate(
|
||||
state: &mut RateState,
|
||||
now: Instant,
|
||||
per_minute: u32,
|
||||
burst: u32,
|
||||
) -> bool {
|
||||
pub(super) fn allow_rate(state: &mut RateState, now: Instant, per_minute: u32, burst: u32) -> bool {
|
||||
let burst = f64::from(burst);
|
||||
if let Some(last) = state.last {
|
||||
let elapsed = now.saturating_duration_since(last).as_secs_f64();
|
||||
state.tokens =
|
||||
(state.tokens + elapsed * f64::from(per_minute) / 60.0).min(burst);
|
||||
state.tokens = (state.tokens + elapsed * f64::from(per_minute) / 60.0).min(burst);
|
||||
} else {
|
||||
state.tokens = burst;
|
||||
}
|
||||
@@ -216,9 +210,9 @@ where
|
||||
|
||||
/// Computes the process-wide item reserve required for session control progress.
|
||||
pub(super) fn control_item_reserve(limits: &WebLimitsConfig) -> usize {
|
||||
limits.max_sessions_global.saturating_mul(
|
||||
16usize.saturating_add(limits.max_streams_per_session.saturating_mul(3)),
|
||||
)
|
||||
limits
|
||||
.max_sessions_global
|
||||
.saturating_mul(16usize.saturating_add(limits.max_streams_per_session.saturating_mul(3)))
|
||||
}
|
||||
|
||||
/// Allocates a non-zero source port unique among live streams for one KDF route.
|
||||
|
||||
+7
-5
@@ -314,7 +314,9 @@ impl WebSession {
|
||||
if !self.queue_window_locked(&mut state, stream_id, count as u32) {
|
||||
drop(state);
|
||||
self.close();
|
||||
return Poll::Ready(Err(io::Error::other("WEB session control budget exhausted")));
|
||||
return Poll::Ready(Err(io::Error::other(
|
||||
"WEB session control budget exhausted",
|
||||
)));
|
||||
}
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
@@ -368,14 +370,14 @@ impl WebSession {
|
||||
|
||||
/// Returns the process queue-capacity notification source while the manager lives.
|
||||
pub(super) fn budget_notify(&self) -> Option<Arc<Notify>> {
|
||||
self.manager.upgrade().map(|manager| manager.budget_notify())
|
||||
self.manager
|
||||
.upgrade()
|
||||
.map(|manager| manager.budget_notify())
|
||||
}
|
||||
|
||||
fn release_stream_reservation(&self, peer_port: u16) {
|
||||
let removed = self.state.lock().active_peer_ports.remove(&peer_port);
|
||||
if removed
|
||||
&& let Some(manager) = self.manager.upgrade()
|
||||
{
|
||||
if removed && let Some(manager) = self.manager.upgrade() {
|
||||
manager.release_stream(
|
||||
self.profile_key,
|
||||
self.client_ip,
|
||||
|
||||
@@ -3,9 +3,9 @@ use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::proxy::shared_state::ConntrackClosePolicy;
|
||||
use crate::web::frame::FrameType;
|
||||
use crate::web::stream::WebLogicalStream;
|
||||
use crate::proxy::shared_state::ConntrackClosePolicy;
|
||||
|
||||
use super::{WebSession, inbound_queue_cost};
|
||||
|
||||
@@ -70,9 +70,7 @@ impl WebSession {
|
||||
});
|
||||
(queued, reserved)
|
||||
};
|
||||
if reserved
|
||||
&& let Some(manager) = self.manager.upgrade()
|
||||
{
|
||||
if reserved && let Some(manager) = self.manager.upgrade() {
|
||||
manager.release_stream(
|
||||
self.profile_key,
|
||||
self.client_ip,
|
||||
|
||||
+11
-29
@@ -3,8 +3,7 @@ use std::time::{Duration, Instant};
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
|
||||
use super::{
|
||||
DownBatch, PendingClass, PollResult, QUEUE_ITEM_COST, QueuedFrame, SessionState,
|
||||
WebSession,
|
||||
DownBatch, PendingClass, PollResult, QUEUE_ITEM_COST, QueuedFrame, SessionState, WebSession,
|
||||
};
|
||||
use crate::config::WebCarrier;
|
||||
use crate::web::frame::{self, FrameType};
|
||||
@@ -117,9 +116,8 @@ impl WebSession {
|
||||
.limits
|
||||
.pending_bytes_per_session
|
||||
.saturating_sub(self.limits.control_bytes_per_session);
|
||||
let item_reserve = 16usize.saturating_add(
|
||||
self.limits.max_streams_per_session.saturating_mul(3),
|
||||
);
|
||||
let item_reserve =
|
||||
16usize.saturating_add(self.limits.max_streams_per_session.saturating_mul(3));
|
||||
let data_item_limit = self
|
||||
.limits
|
||||
.pending_items_per_session
|
||||
@@ -146,14 +144,11 @@ impl WebSession {
|
||||
.pending_items
|
||||
.saturating_sub(state.pending_control_items);
|
||||
let (byte_limit, item_limit) = if class == PendingClass::Downlink {
|
||||
let uplink_bytes = self
|
||||
.limits
|
||||
.max_body_bytes
|
||||
.saturating_add(
|
||||
self.limits
|
||||
.max_frames_per_body
|
||||
.saturating_mul(QUEUE_ITEM_COST),
|
||||
);
|
||||
let uplink_bytes = self.limits.max_body_bytes.saturating_add(
|
||||
self.limits
|
||||
.max_frames_per_body
|
||||
.saturating_mul(QUEUE_ITEM_COST),
|
||||
);
|
||||
(
|
||||
data_byte_limit.saturating_sub(uplink_bytes),
|
||||
data_item_limit.saturating_sub(self.limits.max_frames_per_body),
|
||||
@@ -172,12 +167,7 @@ impl WebSession {
|
||||
let Some(manager) = self.manager.upgrade() else {
|
||||
return false;
|
||||
};
|
||||
if !manager.try_reserve_pending(
|
||||
bytes,
|
||||
items,
|
||||
control,
|
||||
class == PendingClass::Downlink,
|
||||
) {
|
||||
if !manager.try_reserve_pending(bytes, items, control, class == PendingClass::Downlink) {
|
||||
return false;
|
||||
}
|
||||
state.pending_bytes += bytes;
|
||||
@@ -301,13 +291,7 @@ impl WebSession {
|
||||
control: bool,
|
||||
) -> bool {
|
||||
if self.carrier() == WebCarrier::HttpsLanes {
|
||||
return self.queue_lane_frame_locked(
|
||||
state,
|
||||
frame_type,
|
||||
stream_id,
|
||||
payload,
|
||||
control,
|
||||
);
|
||||
return self.queue_lane_frame_locked(state, frame_type, stream_id, payload, control);
|
||||
}
|
||||
let cost = frame::HEADER_BYTES + payload.len() + QUEUE_ITEM_COST;
|
||||
let class = if control {
|
||||
@@ -425,9 +409,7 @@ mod tests {
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::config::{
|
||||
WebLimitsConfig, WebRuntimeProfile, WebSecretMode, WebTimeoutsConfig,
|
||||
};
|
||||
use crate::config::{WebLimitsConfig, WebRuntimeProfile, WebSecretMode, WebTimeoutsConfig};
|
||||
use crate::web::manager::WebProcessRuntime;
|
||||
|
||||
fn session() -> Arc<WebSession> {
|
||||
|
||||
+15
-18
@@ -7,8 +7,8 @@ use subtle::ConstantTimeEq;
|
||||
|
||||
use super::uplink::{inbound_reservation, validate_batch};
|
||||
use super::{
|
||||
CarrierLane, DownBatch, PendingClass, PollResult, QUEUE_ITEM_COST, QueuedFrame,
|
||||
SessionState, WebSession, remember_closed,
|
||||
CarrierLane, DownBatch, PendingClass, PollResult, QUEUE_ITEM_COST, QueuedFrame, SessionState,
|
||||
WebSession, remember_closed,
|
||||
};
|
||||
use crate::config::{WebCarrier, WebLimitsConfig};
|
||||
use crate::web::frame::{self, Frame, FrameType};
|
||||
@@ -32,9 +32,11 @@ impl WebSession {
|
||||
return Err(ManagerError::Protocol);
|
||||
}
|
||||
};
|
||||
if frames.iter().copied().any(|value| {
|
||||
value.stream_id != lane_id || frame::validate_client_shape(value).is_err()
|
||||
}) {
|
||||
if frames
|
||||
.iter()
|
||||
.copied()
|
||||
.any(|value| value.stream_id != lane_id || frame::validate_client_shape(value).is_err())
|
||||
{
|
||||
self.close();
|
||||
return Err(ManagerError::Protocol);
|
||||
}
|
||||
@@ -48,13 +50,17 @@ impl WebSession {
|
||||
state.last_activity = Instant::now();
|
||||
if !state.carrier_lanes.contains_key(&lane_id) {
|
||||
if lane_id != 0
|
||||
&& frames.first().is_some_and(|value| value.frame_type != FrameType::Open)
|
||||
&& frames
|
||||
.first()
|
||||
.is_some_and(|value| value.frame_type != FrameType::Open)
|
||||
&& only_late_frames(&frames)
|
||||
{
|
||||
return Ok(sequence);
|
||||
}
|
||||
if lane_id == 0
|
||||
|| frames.first().is_none_or(|value| value.frame_type != FrameType::Open)
|
||||
|| frames
|
||||
.first()
|
||||
.is_none_or(|value| value.frame_type != FrameType::Open)
|
||||
{
|
||||
drop(state);
|
||||
self.close();
|
||||
@@ -182,12 +188,7 @@ impl WebSession {
|
||||
};
|
||||
if let Some(batch) = acknowledged {
|
||||
self.release_locked(&mut state, batch.data_bytes, batch.data_items, false);
|
||||
self.release_locked(
|
||||
&mut state,
|
||||
batch.control_bytes,
|
||||
batch.control_items,
|
||||
true,
|
||||
);
|
||||
self.release_locked(&mut state, batch.control_bytes, batch.control_items, true);
|
||||
if let Some(stream) = state.streams.get_mut(&lane_id)
|
||||
&& let Some(waker) = stream.write_waker.take()
|
||||
{
|
||||
@@ -402,11 +403,7 @@ impl WebSession {
|
||||
}
|
||||
|
||||
pub(super) fn remember_closed_locked(&self, state: &mut SessionState, stream_id: u32) {
|
||||
let evicted = remember_closed(
|
||||
state,
|
||||
stream_id,
|
||||
self.limits.max_tombstones_per_session,
|
||||
);
|
||||
let evicted = remember_closed(state, stream_id, self.limits.max_tombstones_per_session);
|
||||
if self.carrier() != WebCarrier::HttpsLanes {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,7 @@ use std::sync::Arc;
|
||||
use bytes::BytesMut;
|
||||
|
||||
use super::*;
|
||||
use crate::config::{
|
||||
WebRuntimeProfile, WebSecretMode, WebTimeoutsConfig,
|
||||
};
|
||||
use crate::config::{WebRuntimeProfile, WebSecretMode, WebTimeoutsConfig};
|
||||
use crate::web::manager::WebProcessRuntime;
|
||||
|
||||
fn session_with_limits(limits: WebLimitsConfig) -> Arc<WebSession> {
|
||||
@@ -103,15 +101,18 @@ fn tombstone_eviction_releases_lane_budget_and_accepts_late_frames() {
|
||||
state.carrier_lanes.insert(7, CarrierLane::new());
|
||||
let encoded = frame::encode(FrameType::Close, 7, &[]);
|
||||
let cost = encoded.len() + QUEUE_ITEM_COST;
|
||||
state.carrier_lanes.get_mut(&7).unwrap().pending_frames.push_back(
|
||||
QueuedFrame {
|
||||
state
|
||||
.carrier_lanes
|
||||
.get_mut(&7)
|
||||
.unwrap()
|
||||
.pending_frames
|
||||
.push_back(QueuedFrame {
|
||||
encoded: BytesMut::from(encoded.as_ref()),
|
||||
frame_type: FrameType::Close,
|
||||
stream_id: 7,
|
||||
control: true,
|
||||
cost,
|
||||
},
|
||||
);
|
||||
});
|
||||
state.pending_bytes = cost;
|
||||
state.pending_items = 1;
|
||||
state.pending_control_bytes = cost;
|
||||
|
||||
@@ -138,12 +138,8 @@ impl WebSession {
|
||||
FrameType::Open => {
|
||||
let Some(peer_port) = self.reserve_stream_locked(state) else {
|
||||
self.remember_closed_locked(state, value.stream_id);
|
||||
if !self.queue_control_locked(
|
||||
state,
|
||||
FrameType::Close,
|
||||
value.stream_id,
|
||||
&[],
|
||||
) {
|
||||
if !self.queue_control_locked(state, FrameType::Close, value.stream_id, &[])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
@@ -169,8 +165,8 @@ impl WebSession {
|
||||
bytes: Bytes::copy_from_slice(value.payload),
|
||||
offset: 0,
|
||||
});
|
||||
*unused_bytes = unused_bytes
|
||||
.saturating_sub(value.payload.len() + QUEUE_ITEM_COST);
|
||||
*unused_bytes =
|
||||
unused_bytes.saturating_sub(value.payload.len() + QUEUE_ITEM_COST);
|
||||
*unused_items = unused_items.saturating_sub(1);
|
||||
if let Some(waker) = stream.read_waker.take() {
|
||||
waker.wake();
|
||||
@@ -256,8 +252,8 @@ pub(super) fn validate_batch(state: &SessionState, frames: &[Frame<'_>]) -> bool
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let was_closed = state.closed_streams.contains(&value.stream_id)
|
||||
|| closed.contains(&value.stream_id);
|
||||
let was_closed =
|
||||
state.closed_streams.contains(&value.stream_id) || closed.contains(&value.stream_id);
|
||||
match value.frame_type {
|
||||
FrameType::Open => {
|
||||
if live.contains_key(&value.stream_id) || was_closed {
|
||||
@@ -308,10 +304,7 @@ pub(super) fn validate_batch(state: &SessionState, frames: &[Frame<'_>]) -> bool
|
||||
true
|
||||
}
|
||||
|
||||
pub(super) fn inbound_reservation(
|
||||
state: &SessionState,
|
||||
frames: &[Frame<'_>],
|
||||
) -> (usize, usize) {
|
||||
pub(super) fn inbound_reservation(state: &SessionState, frames: &[Frame<'_>]) -> (usize, usize) {
|
||||
let mut live = state.streams.keys().copied().collect::<HashSet<_>>();
|
||||
let mut bytes = 0usize;
|
||||
let mut items = 0usize;
|
||||
@@ -338,9 +331,7 @@ mod tests {
|
||||
use super::*;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use crate::config::{
|
||||
WebLimitsConfig, WebRuntimeProfile, WebSecretMode, WebTimeoutsConfig,
|
||||
};
|
||||
use crate::config::{WebLimitsConfig, WebRuntimeProfile, WebSecretMode, WebTimeoutsConfig};
|
||||
use crate::web::manager::WebProcessRuntime;
|
||||
|
||||
fn session() -> Arc<WebSession> {
|
||||
@@ -383,10 +374,7 @@ mod tests {
|
||||
let session = session();
|
||||
let body = frame::encode(FrameType::Pong, 0, &[]);
|
||||
session.up_active.store(true, Ordering::Release);
|
||||
assert_eq!(
|
||||
session.process_up(1, &body),
|
||||
Err(ManagerError::Concurrent)
|
||||
);
|
||||
assert_eq!(session.process_up(1, &body), Err(ManagerError::Concurrent));
|
||||
assert_eq!(session.state.lock().last_up_sequence, 0);
|
||||
session.up_active.store(false, Ordering::Release);
|
||||
assert_eq!(session.process_up(1, &body), Ok(1));
|
||||
|
||||
Reference in New Issue
Block a user