mirror of
https://github.com/telemt/telemt.git
synced 2026-10-29 12:07:15 +03:00
WEB Session Lifecycle Observability + Bridge Recovery Drafts
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
@@ -297,6 +297,7 @@ fn lease_deadline(
|
||||
fn sanitize_transport_request<B>(request: &mut Request<B>) {
|
||||
for name in [
|
||||
header::AUTHORIZATION,
|
||||
header::ACCEPT,
|
||||
header::CONTENT_LENGTH,
|
||||
header::CONTENT_TYPE,
|
||||
header::UPGRADE,
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
use bytes::Bytes;
|
||||
use hyper::header::{self, HeaderValue};
|
||||
use hyper::{Request, StatusCode};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::request::{bearer_token_hash, compatible_cookie_header};
|
||||
use super::response::{full_response, insert_header};
|
||||
use super::{HttpResponse, RequestBody};
|
||||
use crate::config::{WebRuntimeProfile, WebRuntimeVhost};
|
||||
use crate::web::manager::{BootstrapResult, TokenHash};
|
||||
|
||||
pub(super) const MEDIA_TYPE: &str = "application/vnd.telemt.web-recovery+json";
|
||||
const MAX_RESPONSE_BYTES: usize = 1024;
|
||||
|
||||
/// Positive representation selected by exact recovery headers.
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) enum RootRepresentation {
|
||||
/// Render the ordinary transient bridge document.
|
||||
Bridge,
|
||||
/// Return fresh recovery policy and optionally retire one current bearer.
|
||||
Recovery(Option<TokenHash>),
|
||||
/// Hide malformed recovery material behind the configured decoy.
|
||||
Invalid,
|
||||
}
|
||||
|
||||
/// Classifies recovery headers without changing capability authentication.
|
||||
pub(super) fn classify(request: &Request<RequestBody>) -> RootRepresentation {
|
||||
let accepts = request.headers().get_all(header::ACCEPT);
|
||||
let mut values = accepts.iter();
|
||||
let first = values.next();
|
||||
let exact = first.is_some_and(|value| value.as_bytes() == MEDIA_TYPE.as_bytes())
|
||||
&& values.next().is_none();
|
||||
let recovery_present = accepts
|
||||
.iter()
|
||||
.any(|value| value.as_bytes() == MEDIA_TYPE.as_bytes());
|
||||
let authorization_present = request.headers().contains_key(header::AUTHORIZATION);
|
||||
if !exact {
|
||||
return if authorization_present || recovery_present {
|
||||
RootRepresentation::Invalid
|
||||
} else {
|
||||
RootRepresentation::Bridge
|
||||
};
|
||||
}
|
||||
if !compatible_cookie_header(request) {
|
||||
return RootRepresentation::Invalid;
|
||||
}
|
||||
if !authorization_present {
|
||||
return RootRepresentation::Recovery(None);
|
||||
}
|
||||
bearer_token_hash(request)
|
||||
.map(|hash| RootRepresentation::Recovery(Some(hash)))
|
||||
.unwrap_or(RootRepresentation::Invalid)
|
||||
}
|
||||
|
||||
/// Builds the bounded no-store recovery representation.
|
||||
pub(super) fn response(
|
||||
bootstrap: &BootstrapResult,
|
||||
vhost: &WebRuntimeVhost,
|
||||
profile: &WebRuntimeProfile,
|
||||
limits: &crate::config::WebLimitsConfig,
|
||||
timeouts: &crate::config::WebTimeoutsConfig,
|
||||
) -> Option<HttpResponse> {
|
||||
let document = RecoveryDocument {
|
||||
version: 1,
|
||||
bootstrap: &bootstrap.token,
|
||||
limits: RecoveryLimits {
|
||||
carrier_batch_bytes: limits.carrier_batch_bytes,
|
||||
pending_bytes_per_session: limits.pending_bytes_per_session,
|
||||
pending_items_per_session: limits.pending_items_per_session,
|
||||
max_streams_per_session: profile.max_streams_per_session,
|
||||
},
|
||||
timeouts: RecoveryTimeouts {
|
||||
long_poll_secs: timeouts.long_poll_secs,
|
||||
bridge_request_secs: timeouts.bridge_request_secs,
|
||||
bridge_retry_secs: timeouts.bridge_retry_secs,
|
||||
bridge_recovery_secs: timeouts.bridge_recovery_secs,
|
||||
websocket_open_secs: timeouts.websocket_open_secs,
|
||||
reconnect_grace_secs: timeouts.reconnect_grace_secs,
|
||||
},
|
||||
negotiation: RecoveryNegotiation {
|
||||
enabled: profile.carrier_negotiation_enabled,
|
||||
candidate_count: profile.carriers.len(),
|
||||
deadlines_secs: profile.carrier_negotiation_deadlines_secs,
|
||||
carrier_probe_coalesce_ms: timeouts.carrier_probe_coalesce_ms,
|
||||
},
|
||||
};
|
||||
let body = serde_json::to_vec(&document).ok()?;
|
||||
if body.len() > MAX_RESPONSE_BYTES || profile.host != vhost.host {
|
||||
return None;
|
||||
}
|
||||
let mut response = full_response(StatusCode::OK, Bytes::from(body));
|
||||
insert_header(&mut response, header::CONTENT_TYPE, MEDIA_TYPE);
|
||||
response
|
||||
.headers_mut()
|
||||
.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store"));
|
||||
response.headers_mut().insert(
|
||||
header::REFERRER_POLICY,
|
||||
HeaderValue::from_static("no-referrer"),
|
||||
);
|
||||
response.headers_mut().insert(
|
||||
header::X_CONTENT_TYPE_OPTIONS,
|
||||
HeaderValue::from_static("nosniff"),
|
||||
);
|
||||
Some(response)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RecoveryDocument<'a> {
|
||||
#[serde(rename = "v")]
|
||||
version: u8,
|
||||
bootstrap: &'a str,
|
||||
limits: RecoveryLimits,
|
||||
timeouts: RecoveryTimeouts,
|
||||
negotiation: RecoveryNegotiation,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RecoveryLimits {
|
||||
carrier_batch_bytes: usize,
|
||||
pending_bytes_per_session: usize,
|
||||
pending_items_per_session: usize,
|
||||
max_streams_per_session: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RecoveryTimeouts {
|
||||
long_poll_secs: u64,
|
||||
bridge_request_secs: u64,
|
||||
bridge_retry_secs: u64,
|
||||
bridge_recovery_secs: u64,
|
||||
websocket_open_secs: u64,
|
||||
reconnect_grace_secs: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RecoveryNegotiation {
|
||||
enabled: bool,
|
||||
candidate_count: usize,
|
||||
deadlines_secs: [u64; 4],
|
||||
carrier_probe_coalesce_ms: u64,
|
||||
}
|
||||
@@ -10,7 +10,9 @@ use tokio_util::sync::CancellationToken;
|
||||
use super::ConnectionIo;
|
||||
use crate::web::http::activity::UpgradeDeadlineLease;
|
||||
use crate::web::manager::{WebProcessRuntime, WebSocketBudgetLease, WebSocketConnection};
|
||||
use crate::web::session::{WebSession, WebSocketLaneReservation, WebSocketProbeReservation};
|
||||
use crate::web::session::{
|
||||
SessionCloseReason, WebSession, WebSocketLaneReservation, WebSocketProbeReservation,
|
||||
};
|
||||
use crate::web::trace::{TraceDirection, TraceWebSocketContext};
|
||||
|
||||
const READ_BUFFER_BYTES: usize = 64 * 1024;
|
||||
@@ -103,7 +105,7 @@ pub(super) async fn run_upgraded(
|
||||
if let Some(reservation) = lane_reservation {
|
||||
session.close_websocket_lane(reservation);
|
||||
} else if !acknowledge_commit || session.is_carrier_committed() {
|
||||
session.close();
|
||||
session.close(SessionCloseReason::WebSocketEnded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +196,7 @@ async fn run_multiplex(
|
||||
started,
|
||||
);
|
||||
if !session.websocket_commit_ack_written(connection.id()) {
|
||||
session.close();
|
||||
session.close(SessionCloseReason::Protocol);
|
||||
return Err(());
|
||||
}
|
||||
} else if acknowledge_commit
|
||||
|
||||
@@ -8,7 +8,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::session::{WebSession, WebSocketLaneReservation};
|
||||
use crate::web::session::{SessionCloseReason, WebSession, WebSocketLaneReservation};
|
||||
use crate::web::trace::{TraceDirection, TraceWebSocketContext};
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -92,7 +92,7 @@ pub(super) async fn run_lane(
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
session.close();
|
||||
session.close(SessionCloseReason::Protocol);
|
||||
return Err(());
|
||||
}
|
||||
record_message(
|
||||
@@ -104,7 +104,7 @@ pub(super) async fn run_lane(
|
||||
started,
|
||||
);
|
||||
if !session.websocket_commit_ack_written(connection.id()) {
|
||||
session.close();
|
||||
session.close(SessionCloseReason::Protocol);
|
||||
return Err(());
|
||||
}
|
||||
} else if acknowledge_commit
|
||||
|
||||
Reference in New Issue
Block a user