This commit is contained in:
Alexey
2026-08-27 00:06:37 +03:00
parent 1b3477a333
commit 14e8d10ad3
34 changed files with 221 additions and 299 deletions
+1 -5
View File
@@ -89,11 +89,7 @@ pub(super) async fn handle_down(
}
Ok(result) => {
if let Some(trace) = request_trace(&request) {
trace.record_frames(
TraceDirection::Response,
&result.body,
session.limits(),
);
trace.record_frames(TraceDirection::Response, &result.body, session.limits());
}
let mut response = full_response(StatusCode::OK, result.body);
carrier_headers(&mut response);
+34 -35
View File
@@ -130,13 +130,7 @@ async fn metadata_free_native_client_can_use_each_fixed_carrier() {
let response = request(
&listener,
&runtime,
create_request_with_headers(
&bootstrap,
&hello,
None,
None,
NATIVE_USER_AGENT_HEADER,
),
create_request_with_headers(&bootstrap, &hello, None, None, NATIVE_USER_AGENT_HEADER),
)
.await;
let (headers, _) = split_response(&response);
@@ -170,13 +164,7 @@ async fn metadata_free_native_client_uses_fallback_when_candidates_are_enabled()
let response = request(
&listener,
&runtime,
create_request_with_headers(
&bootstrap,
&hello,
None,
None,
NATIVE_USER_AGENT_HEADER,
),
create_request_with_headers(&bootstrap, &hello, None, None, NATIVE_USER_AGENT_HEADER),
)
.await;
let (headers, _) = split_response(&response);
@@ -209,13 +197,7 @@ async fn explicit_native_capabilities_participate_in_automatic_selection() {
let response = request(
&listener,
&runtime,
create_request_with_headers(
&bootstrap,
&hello,
Some(1),
None,
NATIVE_USER_AGENT_HEADER,
),
create_request_with_headers(&bootstrap, &hello, Some(1), None, NATIVE_USER_AGENT_HEADER),
)
.await;
let (headers, _) = split_response(&response);
@@ -261,9 +243,18 @@ async fn negotiation_replays_replaces_and_freezes_after_carrier_commit() {
let replay = request(&listener, &runtime, first_request).await;
let (replay_headers, _) = split_response(&replay);
assert_eq!(response_header(replay_headers, "x-session-token"), first_token);
assert_eq!(response_header(replay_headers, "x-carrier-candidate-count"), "3");
assert_eq!(response_header(replay_headers, "x-carrier-state"), "provisional");
assert_eq!(
response_header(replay_headers, "x-session-token"),
first_token
);
assert_eq!(
response_header(replay_headers, "x-carrier-candidate-count"),
"3"
);
assert_eq!(
response_header(replay_headers, "x-carrier-state"),
"provisional"
);
let second_request = create_request(&bootstrap, &hello, Some(2), Some("timeout"));
let second = request(&listener, &runtime, second_request.clone()).await;
@@ -346,11 +337,20 @@ async fn negotiation_replays_replaces_and_freezes_after_carrier_commit() {
let (third_headers, _) = split_response(&third);
assert!(third_headers.starts_with(b"HTTP/1.1 409"));
assert!(optional_response_header(third_headers, "x-session-token").is_none());
assert_eq!(response_header(third_headers, "x-carrier-mode"), "https-lanes");
assert_eq!(
response_header(third_headers, "x-carrier-mode"),
"https-lanes"
);
assert_eq!(response_header(third_headers, "x-carrier-attempt"), "2");
assert_eq!(response_header(third_headers, "x-carrier-candidate-count"), "3");
assert_eq!(
response_header(third_headers, "x-carrier-candidate-count"),
"3"
);
assert_eq!(response_header(third_headers, "x-carrier-deadline"), "12");
assert_eq!(response_header(third_headers, "x-carrier-state"), "committed");
assert_eq!(
response_header(third_headers, "x-carrier-state"),
"committed"
);
assert!(
runtime
.get_session(token_hash(&second_token), "proxy.example.com")
@@ -397,7 +397,10 @@ async fn timed_out_attempt_replays_before_successor_own_deadline() {
response_header(replay_headers, "x-session-token"),
first_token
);
assert_eq!(response_header(replay_headers, "x-carrier-state"), "provisional");
assert_eq!(
response_header(replay_headers, "x-carrier-state"),
"provisional"
);
let second = request(
&listener,
@@ -453,9 +456,8 @@ async fn https_lane_downlink_can_arrive_before_its_uplink_open() {
.into_bytes();
let down_listener = Arc::clone(&listener);
let down_runtime = Arc::clone(&runtime);
let down = tokio::spawn(async move {
request(&down_listener, &down_runtime, down_request).await
});
let down =
tokio::spawn(async move { request(&down_listener, &down_runtime, down_request).await });
tokio::task::yield_now().await;
let open = frame::encode(FrameType::Open, 7, &[]);
@@ -477,10 +479,7 @@ async fn https_lane_downlink_can_arrive_before_its_uplink_open() {
.unwrap()
.unwrap();
let (down_headers, _) = split_response(&down);
assert!(
down_headers.starts_with(b"HTTP/1.1 200")
|| down_headers.starts_with(b"HTTP/1.1 204")
);
assert!(down_headers.starts_with(b"HTTP/1.1 200") || down_headers.starts_with(b"HTTP/1.1 204"));
assert!(optional_response_header(down_headers, "x-down-cursor").is_some());
runtime.shutdown().await;
+9 -16
View File
@@ -15,9 +15,7 @@ const USER_AGENT_CONTEXT: &[u8] = b"telemt-web-carrier-user-agent-v1\0";
// Canonical host and forwarded-address provenance remain isolated from credentials.
mod identity;
pub(super) use identity::{
canonical_request_host, carrier_ip_learning_eligible, client_ip,
};
pub(super) use identity::{canonical_request_host, carrier_ip_learning_eligible, client_ip};
/// Decodes an exact canonical bridge query without allocating credential strings.
pub(super) fn bridge_candidate(query: Option<&str>) -> ([u8; 32], bool) {
@@ -205,17 +203,13 @@ fn parse_capabilities(value: &str) -> Option<CarrierCapabilities> {
}
fn strict_browser_hint<B>(request: &Request<B>, host: &str) -> bool {
single_header(request, header::ORIGIN)
.is_some_and(|value| value == format!("https://{host}"))
single_header(request, header::ORIGIN).is_some_and(|value| value == format!("https://{host}"))
&& single_header(request, "sec-fetch-site") == Some("same-origin")
&& single_header(request, "sec-fetch-mode") == Some("cors")
&& single_header(request, "sec-fetch-dest") == Some("empty")
}
fn optional_canonical_u8_header<B>(
request: &Request<B>,
name: &'static str,
) -> Option<Option<u8>> {
fn optional_canonical_u8_header<B>(request: &Request<B>, name: &'static str) -> Option<Option<u8>> {
if !request.headers().contains_key(name) {
return Some(None);
}
@@ -452,9 +446,11 @@ mod tests {
.header(header::USER_AGENT, "Native")
.body(())
.unwrap();
assert!(!carrier_request(&legacy, "proxy.example.com")
.unwrap()
.is_automatic());
assert!(
!carrier_request(&legacy, "proxy.example.com")
.unwrap()
.is_automatic()
);
let reordered = Request::builder()
.header("x-carrier-capabilities", "websocket,https")
@@ -479,10 +475,7 @@ mod tests {
assert!(!parsed.uses_capabilities());
let automatic = Request::builder()
.header(
"x-carrier-capabilities",
"https,https-lanes",
)
.header("x-carrier-capabilities", "https,https-lanes")
.header("x-carrier-attempt", "1")
.header(
header::USER_AGENT,
+3 -6
View File
@@ -152,12 +152,9 @@ pub(super) async fn handle_session(
}
Err(ManagerError::Committed) => {
let mut response = carrier_empty(StatusCode::CONFLICT);
if let Some(echo) = runtime.carrier_echo(
token_hash,
&vhost.host,
client_ip,
carrier_request,
) {
if let Some(echo) =
runtime.carrier_echo(token_hash, &vhost.host, client_ip, carrier_request)
{
response.headers_mut().insert(
HeaderName::from_static("x-carrier-mode"),
HeaderValue::from_static(echo.carrier.as_str()),
+6 -5
View File
@@ -134,8 +134,7 @@ fn runtime_config_with_carriers_and_deadlines(
WebCarriers::Disabled
};
config.web.carrier_learning = carrier_learning;
config.web.timeouts.carrier_negotiation_deadlines_secs =
carrier_negotiation_deadlines_secs;
config.web.timeouts.carrier_negotiation_deadlines_secs = carrier_negotiation_deadlines_secs;
config.web.limits.max_bootstraps_per_ip = 1;
config.web.timeouts.shutdown_secs = 1;
config.web.runtime = Some(Arc::new(WebRuntimeConfig {
@@ -260,9 +259,11 @@ async fn https_carrier_bootstraps_and_closes_one_session() {
.windows(11)
.any(|value| value == b"bootstrap=\"")
);
assert!(next_root_body
.windows(b"const negotiationEnabled=false".len())
.any(|value| value == b"const negotiationEnabled=false"));
assert!(
next_root_body
.windows(b"const negotiationEnabled=false".len())
.any(|value| value == b"const negotiationEnabled=false")
);
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"
+15 -19
View File
@@ -389,21 +389,20 @@ fn parse_upgrade<B>(request: &Request<B>) -> Option<ParsedUpgrade> {
{
return None;
}
let (token, carrier, acknowledge_commit) = if let Some(token) =
protocol.strip_prefix("tproxy-auto-v1.")
{
(token, ParsedCarrier::Multiplex, true)
} else if let Some(lane) = protocol.strip_prefix("tproxy-auto-lane-v1.") {
let (token, lane_id) = parse_lane_protocol(lane)?;
(token, ParsedCarrier::Lane(lane_id), true)
} else if let Some(token) = protocol.strip_prefix("tproxy-v1.") {
(token, ParsedCarrier::Multiplex, false)
} else if let Some(lane) = protocol.strip_prefix("tproxy-lane-v1.") {
let (token, lane_id) = parse_lane_protocol(lane)?;
(token, ParsedCarrier::Lane(lane_id), false)
} else {
return None;
};
let (token, carrier, acknowledge_commit) =
if let Some(token) = protocol.strip_prefix("tproxy-auto-v1.") {
(token, ParsedCarrier::Multiplex, true)
} else if let Some(lane) = protocol.strip_prefix("tproxy-auto-lane-v1.") {
let (token, lane_id) = parse_lane_protocol(lane)?;
(token, ParsedCarrier::Lane(lane_id), true)
} else if let Some(token) = protocol.strip_prefix("tproxy-v1.") {
(token, ParsedCarrier::Multiplex, false)
} else if let Some(lane) = protocol.strip_prefix("tproxy-lane-v1.") {
let (token, lane_id) = parse_lane_protocol(lane)?;
(token, ParsedCarrier::Lane(lane_id), false)
} else {
return None;
};
let raw_token = base64::engine::general_purpose::URL_SAFE_NO_PAD
.decode(token)
.ok()?;
@@ -441,10 +440,7 @@ fn parse_lane_protocol(value: &str) -> Option<(&str, u32)> {
Some((token, lane_id))
}
fn single_header<B>(
request: &Request<B>,
name: impl hyper::header::AsHeaderName,
) -> Option<&str> {
fn single_header<B>(request: &Request<B>, name: impl hyper::header::AsHeaderName) -> Option<&str> {
let mut values = request.headers().get_all(name).iter();
let value = values.next()?.to_str().ok()?;
values.next().is_none().then_some(value)
+4 -15
View File
@@ -8,12 +8,8 @@ use tokio_tungstenite::tungstenite::protocol::{Message, Role, WebSocketConfig};
use tokio_util::sync::CancellationToken;
use super::ConnectionIo;
use crate::web::manager::{
WebProcessRuntime, WebSocketBudgetLease, WebSocketConnection,
};
use crate::web::session::{
WebSession, WebSocketLaneReservation, WebSocketProbeReservation,
};
use crate::web::manager::{WebProcessRuntime, WebSocketBudgetLease, WebSocketConnection};
use crate::web::session::{WebSession, WebSocketLaneReservation, WebSocketProbeReservation};
use crate::web::trace::{TraceDirection, TraceWebSocketContext};
const READ_BUFFER_BYTES: usize = 64 * 1024;
@@ -126,8 +122,7 @@ async fn run_multiplex(
let mut next_ping = Instant::now() + liveness_interval;
let open_deadline =
Instant::now() + Duration::from_secs(session.timeouts().websocket_open_secs);
let backpressure_timeout =
Duration::from_secs(session.timeouts().websocket_backpressure_secs);
let backpressure_timeout = Duration::from_secs(session.timeouts().websocket_backpressure_secs);
let write_timeout = Duration::from_secs(session.timeouts().websocket_write_secs);
let maximum_message = session.limits().carrier_batch_bytes;
let mut active = false;
@@ -317,13 +312,7 @@ async fn run_multiplex(
started,
);
} else {
send(
socket,
Message::Binary(body),
&cancellation,
write_timeout,
)
.await?;
send(socket, Message::Binary(body), &cancellation, write_timeout).await?;
}
connection.mark_progress();
}
+2 -10
View File
@@ -24,16 +24,8 @@ pub(super) async fn read_message(
ready = socket.get_ref().readable() => ready.map_err(|_| ())?,
}
if retained_budget.is_none() {
*retained_budget = Some(
reserve_data(
runtime,
owner,
maximum,
cancellation,
backpressure_timeout,
)
.await?,
);
*retained_budget =
Some(reserve_data(runtime, owner, maximum, cancellation, backpressure_timeout).await?);
}
let message = tokio::select! {
_ = cancellation.cancelled() => return Err(()),
+3 -12
View File
@@ -7,9 +7,7 @@ use tokio_util::sync::CancellationToken;
use super::CarrierSocket;
use super::io::{flush, process_lane, read_message, record_message, reserve_data, send};
use crate::web::manager::{
WebProcessRuntime, WebSocketBudgetLease, WebSocketConnection,
};
use crate::web::manager::{WebProcessRuntime, WebSocketBudgetLease, WebSocketConnection};
use crate::web::session::{WebSession, WebSocketLaneReservation};
use crate::web::trace::{TraceDirection, TraceWebSocketContext};
@@ -32,8 +30,7 @@ pub(super) async fn run_lane(
let mut next_ping = Instant::now() + liveness_interval;
let open_deadline =
Instant::now() + Duration::from_secs(session.timeouts().websocket_open_secs);
let backpressure_timeout =
Duration::from_secs(session.timeouts().websocket_backpressure_secs);
let backpressure_timeout = Duration::from_secs(session.timeouts().websocket_backpressure_secs);
let write_timeout = Duration::from_secs(session.timeouts().websocket_write_secs);
let maximum_message = session.limits().carrier_batch_bytes;
let mut active = false;
@@ -232,13 +229,7 @@ pub(super) async fn run_lane(
started,
);
} else {
send(
socket,
Message::Binary(body),
&cancellation,
write_timeout,
)
.await?;
send(socket, Message::Binary(body), &cancellation, write_timeout).await?;
}
connection.mark_progress();
}
+1 -2
View File
@@ -452,8 +452,7 @@ async fn failed_automatic_multiplex_socket_remains_supersedable() {
Arc::from([WebCarrier::Websocket, WebCarrier::Https]),
);
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let (bootstrap_hash, hello, session, session_hash) =
create_automatic_session(&live.runtime);
let (bootstrap_hash, hello, session, session_hash) = create_automatic_session(&live.runtime);
let protocol = format!("tproxy-auto-v1.{session}");
let mut socket = upgrade(&listener, &live.runtime, &protocol).await;
socket.close(None).await.unwrap();