Bounded Debugging + Websocket Carriers + Carriers Negotiation

Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
Alexey
2026-08-26 17:00:20 +03:00
parent 43cd84aaa5
commit 923c79796a
52 changed files with 3450 additions and 980 deletions
+40 -104
View File
@@ -10,20 +10,48 @@ use tokio_util::sync::CancellationToken;
use super::serve_connection;
use crate::config::{
ProxyConfig, WebCarrier, WebClientIpSource, WebRuntimeConfig, WebRuntimeDecoy,
ProxyConfig, WebCarrier, WebCarriers, WebClientIpSource, WebRuntimeConfig, WebRuntimeDecoy,
WebRuntimeProfile, WebRuntimeVhost, WebSecretMode, WebStaticAsset, WebStaticSite,
};
use crate::maestro::generation::test_runtime_generation;
use crate::web::frame::{self, FrameType};
use crate::web::manager::WebProcessRuntime;
#[path = "legacy_tests.rs"]
mod legacy_tests;
#[path = "negotiation_tests.rs"]
mod negotiation_tests;
pub(super) fn runtime_config(capability: [u8; 32], carrier: WebCarrier) -> ProxyConfig {
runtime_config_with_carriers(capability, carrier, false, true, Arc::from([carrier]))
}
pub(super) fn negotiation_runtime_config(
capability: [u8; 32],
carrier: WebCarrier,
carrier_learning: bool,
carriers: Arc<[WebCarrier]>,
) -> ProxyConfig {
runtime_config_with_carriers(capability, carrier, true, carrier_learning, carriers)
}
fn runtime_config_with_carriers(
capability: [u8; 32],
carrier: WebCarrier,
carrier_negotiation_enabled: bool,
carrier_learning: bool,
carriers: Arc<[WebCarrier]>,
) -> ProxyConfig {
let profile = Arc::new(WebRuntimeProfile {
host: "proxy.example.com".to_string(),
public_addr: "203.0.113.10:443".parse().unwrap(),
user: "alice".to_string(),
secret_mode: WebSecretMode::Plain,
carrier,
carrier_negotiation_enabled,
carrier_learning,
carriers: Arc::clone(&carriers),
carrier_negotiation_deadlines_secs: [3, 5, 8, 12],
capability,
key_fingerprint: "0000000000000000".to_string(),
max_sessions: 4,
@@ -63,6 +91,12 @@ pub(super) fn runtime_config(capability: [u8; 32], carrier: WebCarrier) -> Proxy
let mut config = ProxyConfig::default();
config.web.enabled = true;
config.web.carrier = carrier;
config.web.carriers = if carrier_negotiation_enabled {
WebCarriers::Enabled(carriers.to_vec())
} else {
WebCarriers::Disabled
};
config.web.carrier_learning = carrier_learning;
config.web.limits.max_bootstraps_per_ip = 1;
config.web.timeouts.shutdown_secs = 1;
config.web.runtime = Some(Arc::new(WebRuntimeConfig {
@@ -106,7 +140,7 @@ pub(super) fn split_response(response: &[u8]) -> (&[u8], &[u8]) {
(&response[..separator], &response[separator + 4..])
}
fn response_header<'a>(headers: &'a [u8], name: &str) -> &'a str {
pub(super) fn response_header<'a>(headers: &'a [u8], name: &str) -> &'a str {
std::str::from_utf8(headers)
.unwrap()
.lines()
@@ -187,11 +221,9 @@ async fn https_carrier_bootstraps_and_closes_one_session() {
.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(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"
@@ -375,7 +407,7 @@ async fn https_lanes_is_advertised_and_requires_canonical_lane_headers() {
let root_response = request(&listener, &runtime, root).await;
let (_, root_body) = split_response(&root_response);
let root_body = std::str::from_utf8(root_body).unwrap();
assert!(root_body.contains("carrier='https-lanes'"));
assert!(root_body.contains("const negotiationEnabled=false"));
let bootstrap = root_body
.split_once("bootstrap=\"")
.and_then(|(_, suffix)| suffix.split_once('"'))
@@ -437,99 +469,3 @@ async fn https_lanes_is_advertised_and_requires_canonical_lane_headers() {
generation.stop_sessions().await;
generation.stop_background_tasks().await;
}
#[tokio::test]
async fn windows_restricted_webview_empty_cookie_preserves_the_carrier_flow() {
for (index, carrier) in [WebCarrier::Https, WebCarrier::HttpsLanes]
.into_iter()
.enumerate()
{
let capability = [12 + index as u8; 32];
let mut config = runtime_config(capability, carrier);
config.web.timeouts.long_poll_secs = 0;
let generation = test_runtime_generation(1, config);
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();
let encoded = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(capability);
let root = format!(
"GET /?bridge={encoded} HTTP/1.1\r\nHost: proxy.example.com\r\nX-Forwarded-For: 192.0.2.10\r\nCookie:\r\nConnection: close\r\n\r\n"
)
.into_bytes();
let root_response = request(&listener, &runtime, root).await;
let (root_headers, root_body) = split_response(&root_response);
assert!(root_headers.starts_with(b"HTTP/1.1 200"));
let root_body = std::str::from_utf8(root_body).unwrap();
let bootstrap = root_body
.split_once("bootstrap=\"")
.and_then(|(_, suffix)| suffix.split_once('"'))
.map(|(token, _)| token)
.unwrap();
let hello = frame::encode(FrameType::Hello, 0, &[1]);
let create = |cookie: &str| {
let mut request = format!(
"POST /api/v1/session HTTP/1.1\r\nHost: proxy.example.com\r\nX-Forwarded-For: 192.0.2.10\r\nAuthorization: Bearer {bootstrap}\r\nContent-Type: application/octet-stream\r\n{cookie}Content-Length: {}\r\nConnection: close\r\n\r\n",
hello.len()
)
.into_bytes();
request.extend_from_slice(&hello);
request
};
let nonempty_cookie =
request(&listener, &runtime, create("Cookie: state=unexpected\r\n")).await;
assert!(!nonempty_cookie.starts_with(b"HTTP/1.1 200"));
let duplicate_cookie = request(
&listener,
&runtime,
create("Cookie:\r\nCookie: state=unexpected\r\n"),
)
.await;
assert!(!duplicate_cookie.starts_with(b"HTTP/1.1 200"));
let create_response = request(&listener, &runtime, create("Cookie:\r\n")).await;
let (create_headers, create_body) = split_response(&create_response);
assert!(create_headers.starts_with(b"HTTP/1.1 200"));
assert_eq!(
response_header(create_headers, "x-carrier-mode"),
carrier.as_str()
);
assert_eq!(create_body, frame::encode(FrameType::Welcome, 0, &[]));
let session = response_header(create_headers, "x-session-token").to_string();
let lane = (carrier == WebCarrier::HttpsLanes)
.then_some("X-Lane-ID: 0\r\n")
.unwrap_or_default();
let pong = frame::encode(FrameType::Pong, 0, &[]);
let mut uplink = 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\nCookie:\r\nX-Up-Seq: 1\r\n{lane}Content-Length: {}\r\nConnection: close\r\n\r\n",
pong.len()
)
.into_bytes();
uplink.extend_from_slice(&pong);
let uplink_response = request(&listener, &runtime, uplink).await;
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");
let downlink = format!(
"POST /api/v1/down HTTP/1.1\r\nHost: proxy.example.com\r\nX-Forwarded-For: 192.0.2.10\r\nAuthorization: Bearer {session}\r\nCookie:\r\nX-Down-Cursor: 0\r\n{lane}Content-Length: 0\r\nConnection: close\r\n\r\n"
)
.into_bytes();
let downlink_response = request(&listener, &runtime, downlink).await;
let (downlink_headers, _) = split_response(&downlink_response);
assert!(downlink_headers.starts_with(b"HTTP/1.1 204"));
assert_eq!(response_header(downlink_headers, "x-down-cursor"), "0");
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\nCookie:\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
)
.into_bytes();
let close_response = request(&listener, &runtime, close).await;
assert!(close_response.starts_with(b"HTTP/1.1 204"));
runtime.shutdown().await;
generation.stop_sessions().await;
generation.stop_background_tasks().await;
}
}