mirror of
https://github.com/telemt/telemt.git
synced 2026-09-05 18:16:06 +03:00
Rustfmt
This commit is contained in:
@@ -47,10 +47,8 @@ pub(super) fn rebuild(config: &mut ProxyConfig) -> Result<()> {
|
|||||||
})?;
|
})?;
|
||||||
let (client_secret, client_secret_len) =
|
let (client_secret, client_secret_len) =
|
||||||
client_secret(auth_entry.secret, profile.secret_mode);
|
client_secret(auth_entry.secret, profile.secret_mode);
|
||||||
let capability = derive_web_capability(
|
let capability =
|
||||||
&client_secret[..client_secret_len],
|
derive_web_capability(&client_secret[..client_secret_len], vhost.host.as_bytes())?;
|
||||||
vhost.host.as_bytes(),
|
|
||||||
)?;
|
|
||||||
if !capabilities.insert(capability) {
|
if !capabilities.insert(capability) {
|
||||||
return Err(ProxyError::Config(format!(
|
return Err(ProxyError::Config(format!(
|
||||||
"WEB vhost `{}` contains profiles with the same client capability",
|
"WEB vhost `{}` contains profiles with the same client capability",
|
||||||
@@ -97,9 +95,8 @@ pub(super) fn rebuild(config: &mut ProxyConfig) -> Result<()> {
|
|||||||
|
|
||||||
/// Derives the Telegram Desktop WEB capability for one exact secret and host.
|
/// Derives the Telegram Desktop WEB capability for one exact secret and host.
|
||||||
pub(crate) fn derive_web_capability(secret: &[u8], host: &[u8]) -> Result<[u8; 32]> {
|
pub(crate) fn derive_web_capability(secret: &[u8], host: &[u8]) -> Result<[u8; 32]> {
|
||||||
let mut mac = Hmac::<Sha256>::new_from_slice(secret).map_err(|_| {
|
let mut mac = Hmac::<Sha256>::new_from_slice(secret)
|
||||||
ProxyError::Config("WEB capability secret must not be empty".to_string())
|
.map_err(|_| ProxyError::Config("WEB capability secret must not be empty".to_string()))?;
|
||||||
})?;
|
|
||||||
mac.update(WEB_CAPABILITY_CONTEXT);
|
mac.update(WEB_CAPABILITY_CONTEXT);
|
||||||
mac.update(host);
|
mac.update(host);
|
||||||
Ok(mac.finalize().into_bytes().into())
|
Ok(mac.finalize().into_bytes().into())
|
||||||
@@ -159,13 +156,7 @@ fn build_decoy(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
WebDecoyConfig::StaticDirectory { directory, index } => {
|
WebDecoyConfig::StaticDirectory { directory, index } => {
|
||||||
let site = load_static_site(
|
let site = load_static_site(directory, index, limits, static_files, static_bytes)?;
|
||||||
directory,
|
|
||||||
index,
|
|
||||||
limits,
|
|
||||||
static_files,
|
|
||||||
static_bytes,
|
|
||||||
)?;
|
|
||||||
Ok(WebRuntimeDecoy::StaticDirectory(Arc::new(site)))
|
Ok(WebRuntimeDecoy::StaticDirectory(Arc::new(site)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,8 +230,7 @@ fn load_static_directory(
|
|||||||
})?;
|
})?;
|
||||||
if *total_files >= limits.max_static_files {
|
if *total_files >= limits.max_static_files {
|
||||||
return Err(ProxyError::Config(
|
return Err(ProxyError::Config(
|
||||||
"WEB static entries exceed process-wide web.limits.max_static_files"
|
"WEB static entries exceed process-wide web.limits.max_static_files".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
*total_files += 1;
|
*total_files += 1;
|
||||||
@@ -317,8 +307,7 @@ fn load_static_directory(
|
|||||||
})?;
|
})?;
|
||||||
if *total_bytes > limits.max_static_bytes {
|
if *total_bytes > limits.max_static_bytes {
|
||||||
return Err(ProxyError::Config(
|
return Err(ProxyError::Config(
|
||||||
"WEB static snapshots exceed process-wide web.limits.max_static_bytes"
|
"WEB static snapshots exceed process-wide web.limits.max_static_bytes".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let relative = path.strip_prefix(root).map_err(|_| {
|
let relative = path.strip_prefix(root).map_err(|_| {
|
||||||
|
|||||||
@@ -59,9 +59,7 @@ pub(super) fn validate(config: &mut ProxyConfig) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
validate_limits(&config.web.limits)?;
|
validate_limits(&config.web.limits)?;
|
||||||
if config.web.carrier == WebCarrier::HttpsLanes
|
if config.web.carrier == WebCarrier::HttpsLanes && config.web.limits.max_http_handlers < 2 {
|
||||||
&& config.web.limits.max_http_handlers < 2
|
|
||||||
{
|
|
||||||
return config_error("web.carrier=https-lanes requires web.limits.max_http_handlers >= 2");
|
return config_error("web.carrier=https-lanes requires web.limits.max_http_handlers >= 2");
|
||||||
}
|
}
|
||||||
validate_timeouts(&config.web.timeouts)?;
|
validate_timeouts(&config.web.timeouts)?;
|
||||||
@@ -155,11 +153,20 @@ fn validate_limits(limits: &WebLimitsConfig) -> Result<()> {
|
|||||||
("max_streams_per_session", limits.max_streams_per_session),
|
("max_streams_per_session", limits.max_streams_per_session),
|
||||||
("max_streams_global", limits.max_streams_global),
|
("max_streams_global", limits.max_streams_global),
|
||||||
("max_stream_handshakes", limits.max_stream_handshakes),
|
("max_stream_handshakes", limits.max_stream_handshakes),
|
||||||
("pending_bytes_per_session", limits.pending_bytes_per_session),
|
(
|
||||||
|
"pending_bytes_per_session",
|
||||||
|
limits.pending_bytes_per_session,
|
||||||
|
),
|
||||||
("pending_bytes_global", limits.pending_bytes_global),
|
("pending_bytes_global", limits.pending_bytes_global),
|
||||||
("pending_items_per_session", limits.pending_items_per_session),
|
(
|
||||||
|
"pending_items_per_session",
|
||||||
|
limits.pending_items_per_session,
|
||||||
|
),
|
||||||
("pending_items_global", limits.pending_items_global),
|
("pending_items_global", limits.pending_items_global),
|
||||||
("control_bytes_per_session", limits.control_bytes_per_session),
|
(
|
||||||
|
"control_bytes_per_session",
|
||||||
|
limits.control_bytes_per_session,
|
||||||
|
),
|
||||||
("control_bytes_global", limits.control_bytes_global),
|
("control_bytes_global", limits.control_bytes_global),
|
||||||
("max_bootstraps_global", limits.max_bootstraps_global),
|
("max_bootstraps_global", limits.max_bootstraps_global),
|
||||||
("max_bootstraps_per_ip", limits.max_bootstraps_per_ip),
|
("max_bootstraps_per_ip", limits.max_bootstraps_per_ip),
|
||||||
@@ -181,11 +188,16 @@ fn validate_limits(limits: &WebLimitsConfig) -> Result<()> {
|
|||||||
("max_stream_handshakes", limits.max_stream_handshakes),
|
("max_stream_handshakes", limits.max_stream_handshakes),
|
||||||
] {
|
] {
|
||||||
if value > tokio::sync::Semaphore::MAX_PERMITS {
|
if value > tokio::sync::Semaphore::MAX_PERMITS {
|
||||||
return config_error(&format!("web.limits.{field} exceeds Tokio semaphore capacity"));
|
return config_error(&format!(
|
||||||
|
"web.limits.{field} exceeds Tokio semaphore capacity"
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let rates = [
|
let rates = [
|
||||||
("new_bootstraps_per_minute", limits.new_bootstraps_per_minute),
|
(
|
||||||
|
"new_bootstraps_per_minute",
|
||||||
|
limits.new_bootstraps_per_minute,
|
||||||
|
),
|
||||||
("new_bootstraps_burst", limits.new_bootstraps_burst),
|
("new_bootstraps_burst", limits.new_bootstraps_burst),
|
||||||
("new_sessions_per_minute", limits.new_sessions_per_minute),
|
("new_sessions_per_minute", limits.new_sessions_per_minute),
|
||||||
("new_sessions_burst", limits.new_sessions_burst),
|
("new_sessions_burst", limits.new_sessions_burst),
|
||||||
@@ -241,7 +253,9 @@ fn validate_limits(limits: &WebLimitsConfig) -> Result<()> {
|
|||||||
let required_control_bytes_global = control_items_global
|
let required_control_bytes_global = control_items_global
|
||||||
.checked_mul(control_frame_cost)
|
.checked_mul(control_frame_cost)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
ProxyError::Config("web.limits global control byte reservation overflowed usize".to_string())
|
ProxyError::Config(
|
||||||
|
"web.limits global control byte reservation overflowed usize".to_string(),
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
if control_items_per_session >= limits.pending_items_per_session
|
if control_items_per_session >= limits.pending_items_per_session
|
||||||
|| control_items_global >= limits.pending_items_global
|
|| control_items_global >= limits.pending_items_global
|
||||||
@@ -367,10 +381,7 @@ fn validate_vhosts(config: &mut ProxyConfig) -> Result<()> {
|
|||||||
let mut hosts = HashSet::with_capacity(config.web.vhosts.len());
|
let mut hosts = HashSet::with_capacity(config.web.vhosts.len());
|
||||||
let mut profile_count = 0usize;
|
let mut profile_count = 0usize;
|
||||||
for (vhost_idx, vhost) in config.web.vhosts.iter_mut().enumerate() {
|
for (vhost_idx, vhost) in config.web.vhosts.iter_mut().enumerate() {
|
||||||
vhost.host = normalize_web_host(
|
vhost.host = normalize_web_host(&vhost.host, &format!("web.vhosts[{vhost_idx}].host"))?;
|
||||||
&vhost.host,
|
|
||||||
&format!("web.vhosts[{vhost_idx}].host"),
|
|
||||||
)?;
|
|
||||||
if !hosts.insert(vhost.host.clone()) {
|
if !hosts.insert(vhost.host.clone()) {
|
||||||
return config_error(&format!("duplicate WEB vhost host `{}`", vhost.host));
|
return config_error(&format!("duplicate WEB vhost host `{}`", vhost.host));
|
||||||
}
|
}
|
||||||
@@ -404,7 +415,9 @@ fn validate_vhosts(config: &mut ProxyConfig) -> Result<()> {
|
|||||||
.max_streams_per_session
|
.max_streams_per_session
|
||||||
.unwrap_or(limits.max_streams_per_session);
|
.unwrap_or(limits.max_streams_per_session);
|
||||||
if profile.max_sessions == Some(0)
|
if profile.max_sessions == Some(0)
|
||||||
|| profile.max_sessions.is_some_and(|value| value > limits.max_sessions_global)
|
|| profile
|
||||||
|
.max_sessions
|
||||||
|
.is_some_and(|value| value > limits.max_sessions_global)
|
||||||
|| profile.max_streams == Some(0)
|
|| profile.max_streams == Some(0)
|
||||||
|| profile
|
|| profile
|
||||||
.max_streams
|
.max_streams
|
||||||
|
|||||||
@@ -94,10 +94,7 @@ fn web_semaphore_limits_are_rejected_before_runtime_construction() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn web_ipv6_decoy_uses_a_valid_http_authority() {
|
fn web_ipv6_decoy_uses_a_valid_http_authority() {
|
||||||
let ipv6 = WEB_CONFIG.replace(
|
let ipv6 = WEB_CONFIG.replace("http://127.0.0.1:18081", "http://[::1]:18081");
|
||||||
"http://127.0.0.1:18081",
|
|
||||||
"http://[::1]:18081",
|
|
||||||
);
|
|
||||||
let config = load_config_from_temp_toml(&ipv6);
|
let config = load_config_from_temp_toml(&ipv6);
|
||||||
let runtime = config.web.runtime.expect("WEB runtime snapshot");
|
let runtime = config.web.runtime.expect("WEB runtime snapshot");
|
||||||
let vhost = runtime.vhosts.get("proxy.example.com").unwrap();
|
let vhost = runtime.vhosts.get("proxy.example.com").unwrap();
|
||||||
|
|||||||
@@ -359,10 +359,7 @@ pub(crate) struct WebRuntimeProfile {
|
|||||||
/// Runtime-ready ordinary-site fallback.
|
/// Runtime-ready ordinary-site fallback.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum WebRuntimeDecoy {
|
pub(crate) enum WebRuntimeDecoy {
|
||||||
HttpUpstream {
|
HttpUpstream { addr: SocketAddr, authority: String },
|
||||||
addr: SocketAddr,
|
|
||||||
authority: String,
|
|
||||||
},
|
|
||||||
StaticDirectory(Arc<WebStaticSite>),
|
StaticDirectory(Arc<WebStaticSite>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ use tokio_util::task::TaskTracker;
|
|||||||
use crate::config::ProxyConfig;
|
use crate::config::ProxyConfig;
|
||||||
use crate::crypto::SecureRandom;
|
use crate::crypto::SecureRandom;
|
||||||
use crate::ip_tracker::UserIpTracker;
|
use crate::ip_tracker::UserIpTracker;
|
||||||
|
use crate::proxy::authenticated::ClientRuntimeDeps;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::proxy::route_mode::RelayRouteMode;
|
use crate::proxy::route_mode::RelayRouteMode;
|
||||||
use crate::proxy::route_mode::RouteRuntimeController;
|
use crate::proxy::route_mode::RouteRuntimeController;
|
||||||
use crate::proxy::shared_state::ProxySharedState;
|
use crate::proxy::shared_state::ProxySharedState;
|
||||||
use crate::proxy::authenticated::ClientRuntimeDeps;
|
|
||||||
use crate::stats::beobachten::BeobachtenStore;
|
use crate::stats::beobachten::BeobachtenStore;
|
||||||
use crate::stats::{ReplayChecker, Stats};
|
use crate::stats::{ReplayChecker, Stats};
|
||||||
use crate::stream::BufferPool;
|
use crate::stream::BufferPool;
|
||||||
|
|||||||
@@ -301,12 +301,7 @@ impl ListenerSlot {
|
|||||||
);
|
);
|
||||||
tokio::time::timeout(connection_stop_timeout, self.connections.wait())
|
tokio::time::timeout(connection_stop_timeout, self.connections.wait())
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| format!("listener {} connection shutdown timed out", self.spec.addr))?;
|
||||||
format!(
|
|
||||||
"listener {} connection shutdown timed out",
|
|
||||||
self.spec.addr
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,16 +4,16 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use arc_swap::ArcSwap;
|
use arc_swap::ArcSwap;
|
||||||
|
|
||||||
use crate::config::ProxyConfig;
|
|
||||||
use crate::config::ListenerTransport;
|
use crate::config::ListenerTransport;
|
||||||
|
use crate::config::ProxyConfig;
|
||||||
use crate::maestro::generation::RuntimeGeneration;
|
use crate::maestro::generation::RuntimeGeneration;
|
||||||
|
|
||||||
use super::accept::ListenerSlot;
|
use super::accept::ListenerSlot;
|
||||||
use super::bind::{BoundListeners, BoundTcpListener, PreparedTcpListener, prepare_listener};
|
use super::bind::{BoundListeners, BoundTcpListener, PreparedTcpListener, prepare_listener};
|
||||||
use super::plan::{ListenerBindSpec, listener_bind_plan};
|
use super::plan::{ListenerBindSpec, listener_bind_plan};
|
||||||
use crate::web::manager::WebProcessRuntime;
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use super::unix::UnixAcceptHandle;
|
use super::unix::UnixAcceptHandle;
|
||||||
|
use crate::web::manager::WebProcessRuntime;
|
||||||
|
|
||||||
/// Process-owned listener inventory and accept-task lifecycle controller.
|
/// Process-owned listener inventory and accept-task lifecycle controller.
|
||||||
pub(crate) struct ListenerManager {
|
pub(crate) struct ListenerManager {
|
||||||
@@ -54,11 +54,7 @@ impl ListenerManager {
|
|||||||
let addr = listener.spec.addr;
|
let addr = listener.spec.addr;
|
||||||
slots.insert(
|
slots.insert(
|
||||||
addr,
|
addr,
|
||||||
ListenerSlot::start(
|
ListenerSlot::start(listener, active_runtime.clone(), web_runtime.clone()),
|
||||||
listener,
|
|
||||||
active_runtime.clone(),
|
|
||||||
web_runtime.clone(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
@@ -103,7 +99,9 @@ impl ListenerManager {
|
|||||||
.map(|(addr, spec)| (*addr, spec.clone()))
|
.map(|(addr, spec)| (*addr, spec.clone()))
|
||||||
.collect::<BTreeMap<_, _>>();
|
.collect::<BTreeMap<_, _>>();
|
||||||
if web_inventory_changed {
|
if web_inventory_changed {
|
||||||
return Err("WEB listener inventory is process-owned; process restart required".to_string());
|
return Err(
|
||||||
|
"WEB listener inventory is process-owned; process restart required".to_string(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let current_addresses: BTreeSet<_> = self.slots.keys().copied().collect();
|
let current_addresses: BTreeSet<_> = self.slots.keys().copied().collect();
|
||||||
let target_addresses: BTreeSet<_> = target_specs.keys().copied().collect();
|
let target_addresses: BTreeSet<_> = target_specs.keys().copied().collect();
|
||||||
|
|||||||
@@ -406,8 +406,7 @@ pub(crate) fn resolve_reload_config(
|
|||||||
fields.push("logging".to_string());
|
fields.push("logging".to_string());
|
||||||
effective.logging = old.logging.clone();
|
effective.logging = old.logging.clone();
|
||||||
}
|
}
|
||||||
if serde_json::to_value(&old.web.limits).ok()
|
if serde_json::to_value(&old.web.limits).ok() != serde_json::to_value(&desired.web.limits).ok()
|
||||||
!= serde_json::to_value(&desired.web.limits).ok()
|
|
||||||
{
|
{
|
||||||
fields.push("web.limits".to_string());
|
fields.push("web.limits".to_string());
|
||||||
effective.web.limits = old.web.limits.clone();
|
effective.web.limits = old.web.limits.clone();
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ use crate::error::{ProxyError, Result};
|
|||||||
use crate::ip_tracker::UserIpTracker;
|
use crate::ip_tracker::UserIpTracker;
|
||||||
use crate::proxy::direct_relay::handle_via_direct_with_shared_and_conntrack;
|
use crate::proxy::direct_relay::handle_via_direct_with_shared_and_conntrack;
|
||||||
use crate::proxy::handshake::HandshakeSuccess;
|
use crate::proxy::handshake::HandshakeSuccess;
|
||||||
use crate::proxy::middle_relay::{
|
use crate::proxy::middle_relay::{handle_via_middle_proxy, handle_via_middle_proxy_with_conntrack};
|
||||||
handle_via_middle_proxy, handle_via_middle_proxy_with_conntrack,
|
|
||||||
};
|
|
||||||
use crate::proxy::route_mode::{RelayRouteMode, RouteRuntimeController};
|
use crate::proxy::route_mode::{RelayRouteMode, RouteRuntimeController};
|
||||||
use crate::proxy::shared_state::{ConntrackClosePolicy, ProxySharedState};
|
use crate::proxy::shared_state::{ConntrackClosePolicy, ProxySharedState};
|
||||||
use crate::stats::Stats;
|
use crate::stats::Stats;
|
||||||
|
|||||||
+2
-4
@@ -43,9 +43,7 @@ use crate::transport::{UpstreamManager, configure_client_socket, parse_proxy_pro
|
|||||||
|
|
||||||
use crate::proxy::authenticated::{ClientRuntimeDeps, run_authenticated};
|
use crate::proxy::authenticated::{ClientRuntimeDeps, run_authenticated};
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::proxy::authenticated::{
|
use crate::proxy::authenticated::{UserConnectionReservation, acquire_user_connection_reservation};
|
||||||
UserConnectionReservation, acquire_user_connection_reservation,
|
|
||||||
};
|
|
||||||
use crate::proxy::handshake::{
|
use crate::proxy::handshake::{
|
||||||
HandshakeSuccess, TlsResponseWriteOptions, handle_mtproto_handshake_with_shared,
|
HandshakeSuccess, TlsResponseWriteOptions, handle_mtproto_handshake_with_shared,
|
||||||
handle_tls_handshake_with_shared, handle_tls_handshake_with_shared_and_options,
|
handle_tls_handshake_with_shared, handle_tls_handshake_with_shared_and_options,
|
||||||
@@ -53,9 +51,9 @@ use crate::proxy::handshake::{
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::proxy::handshake::{handle_mtproto_handshake, handle_tls_handshake};
|
use crate::proxy::handshake::{handle_mtproto_handshake, handle_tls_handshake};
|
||||||
use crate::proxy::masking::handle_bad_client_with_shared;
|
use crate::proxy::masking::handle_bad_client_with_shared;
|
||||||
use crate::proxy::route_mode::RouteRuntimeController;
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::proxy::route_mode::RelayRouteMode;
|
use crate::proxy::route_mode::RelayRouteMode;
|
||||||
|
use crate::proxy::route_mode::RouteRuntimeController;
|
||||||
use crate::proxy::shared_state::{ConntrackClosePolicy, ProxySharedState};
|
use crate::proxy::shared_state::{ConntrackClosePolicy, ProxySharedState};
|
||||||
|
|
||||||
fn beobachten_ttl(config: &ProxyConfig) -> Duration {
|
fn beobachten_ttl(config: &ProxyConfig) -> Duration {
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ use self::tls_auth::{parse_tls_auth_material, validate_tls_secret_candidate};
|
|||||||
pub(crate) use self::auth_probe::{AuthProbeSaturationState, AuthProbeState};
|
pub(crate) use self::auth_probe::{AuthProbeSaturationState, AuthProbeState};
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub use self::mtproto::handle_mtproto_handshake;
|
pub use self::mtproto::handle_mtproto_handshake;
|
||||||
pub use self::mtproto::handle_mtproto_handshake_with_shared;
|
|
||||||
pub(crate) use self::mtproto::handle_mtproto_handshake_for_web_user;
|
pub(crate) use self::mtproto::handle_mtproto_handshake_for_web_user;
|
||||||
|
pub use self::mtproto::handle_mtproto_handshake_with_shared;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub use self::nonce::{encrypt_tg_nonce, encrypt_tg_nonce_with_ciphers, generate_tg_nonce};
|
pub use self::nonce::{encrypt_tg_nonce, encrypt_tg_nonce_with_ciphers, generate_tg_nonce};
|
||||||
pub use self::session::HandshakeSuccess;
|
pub use self::session::HandshakeSuccess;
|
||||||
|
|||||||
@@ -274,12 +274,7 @@ pub(super) fn mode_enabled_for_proto(
|
|||||||
proto_tag: ProtoTag,
|
proto_tag: ProtoTag,
|
||||||
is_tls: bool,
|
is_tls: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
mode_enabled_for_proto_with_policy(
|
mode_enabled_for_proto_with_policy(config, proto_tag, is_tls, MtprotoModePolicy::Configured)
|
||||||
config,
|
|
||||||
proto_tag,
|
|
||||||
is_tls,
|
|
||||||
MtprotoModePolicy::Configured,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mode_enabled_for_proto_with_policy(
|
fn mode_enabled_for_proto_with_policy(
|
||||||
|
|||||||
@@ -203,15 +203,23 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut matched = exact_user_id.is_some_and(|user_id| try_user_id!(user_id));
|
let mut matched = exact_user_id.is_some_and(|user_id| try_user_id!(user_id));
|
||||||
if exact_user.is_none() && let Some(user_id) = sticky_ip_hint {
|
if exact_user.is_none()
|
||||||
|
&& let Some(user_id) = sticky_ip_hint
|
||||||
|
{
|
||||||
matched = try_user_id!(user_id);
|
matched = try_user_id!(user_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if exact_user.is_none() && !matched && let Some(user_id) = preferred_user_id {
|
if exact_user.is_none()
|
||||||
|
&& !matched
|
||||||
|
&& let Some(user_id) = preferred_user_id
|
||||||
|
{
|
||||||
matched = try_user_id!(user_id);
|
matched = try_user_id!(user_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if exact_user.is_none() && !matched && let Some(user_id) = sticky_prefix_hint {
|
if exact_user.is_none()
|
||||||
|
&& !matched
|
||||||
|
&& let Some(user_id) = sticky_prefix_hint
|
||||||
|
{
|
||||||
matched = try_user_id!(user_id);
|
matched = try_user_id!(user_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-10
@@ -313,16 +313,22 @@ mod tests {
|
|||||||
assert!(page.body.contains("X-Lane-ID"));
|
assert!(page.body.contains("X-Lane-ID"));
|
||||||
assert!(page.body.contains("const when=Date.parse(header)"));
|
assert!(page.body.contains("const when=Date.parse(header)"));
|
||||||
assert!(page.body.contains("},{once:false});"));
|
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("welcomeBytes"));
|
||||||
assert!(page
|
assert!(
|
||||||
.body
|
page.body
|
||||||
.contains("for(const value of splitFrames(data))if(value.id!==lane.id)"));
|
.contains("for(const value of splitFrames(data))if(value.id!==lane.id)")
|
||||||
assert!(page
|
);
|
||||||
.body
|
assert!(
|
||||||
.contains("let frames;try{frames=splitFrames(value)}catch(error){fail();return}"));
|
page.body
|
||||||
assert!(page
|
.contains("let frames;try{frames=splitFrames(value)}catch(error){fail();return}")
|
||||||
.content_security_policy
|
);
|
||||||
.contains("frame-ancestors http://127.0.0.1:*"));
|
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);
|
return Err(FrameError::Incomplete);
|
||||||
}
|
}
|
||||||
let frame_type = FrameType::parse(remaining[0]).ok_or(FrameError::UnknownType)?;
|
let frame_type = FrameType::parse(remaining[0]).ok_or(FrameError::UnknownType)?;
|
||||||
let stream_id = u32::from(remaining[1]) << 16
|
let stream_id =
|
||||||
| u32::from(remaining[2]) << 8
|
u32::from(remaining[1]) << 16 | u32::from(remaining[2]) << 8 | u32::from(remaining[3]);
|
||||||
| u32::from(remaining[3]);
|
let payload_len =
|
||||||
let payload_len = u32::from_be_bytes([
|
u32::from_be_bytes([remaining[4], remaining[5], remaining[6], remaining[7]]) as usize;
|
||||||
remaining[4],
|
|
||||||
remaining[5],
|
|
||||||
remaining[6],
|
|
||||||
remaining[7],
|
|
||||||
]) as usize;
|
|
||||||
if payload_len > limits.max_frame_payload_bytes {
|
if payload_len > limits.max_frame_payload_bytes {
|
||||||
return Err(FrameError::PayloadLimit);
|
return Err(FrameError::PayloadLimit);
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-30
@@ -34,9 +34,9 @@ mod request;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
use decoy::serve_decoy;
|
|
||||||
use activity::{ActivityBody, RequestActivity};
|
use activity::{ActivityBody, RequestActivity};
|
||||||
use body::{CollectBodyError, CollectedBody, collect_body};
|
use body::{CollectBodyError, CollectedBody, collect_body};
|
||||||
|
use decoy::serve_decoy;
|
||||||
use request::{
|
use request::{
|
||||||
bearer_token_hash, binary_content_type, bridge_candidate, canonical_request_host,
|
bearer_token_hash, binary_content_type, bridge_candidate, canonical_request_host,
|
||||||
canonical_u64_header, client_ip, match_profile,
|
canonical_u64_header, client_ip, match_profile,
|
||||||
@@ -84,10 +84,7 @@ pub(crate) async fn serve_connection(
|
|||||||
} else {
|
} else {
|
||||||
service_unavailable()
|
service_unavailable()
|
||||||
};
|
};
|
||||||
let response = response.map(|body| {
|
let response = response.map(|body| ActivityBody::new(body, activity).boxed_unsync());
|
||||||
ActivityBody::new(body, activity)
|
|
||||||
.boxed_unsync()
|
|
||||||
});
|
|
||||||
Ok::<_, Infallible>(response)
|
Ok::<_, Infallible>(response)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -174,12 +171,7 @@ async fn handle_root(
|
|||||||
let Some(profile) = profile.filter(|_| canonical && request.method() == Method::GET) else {
|
let Some(profile) = profile.filter(|_| canonical && request.method() == Method::GET) else {
|
||||||
return serve_decoy(request, vhost, false, &runtime).await;
|
return serve_decoy(request, vhost, false, &runtime).await;
|
||||||
};
|
};
|
||||||
let Some(client_ip) = client_ip(
|
let Some(client_ip) = client_ip(&request, peer, client_ip_source, trusted_proxy_cidrs) else {
|
||||||
&request,
|
|
||||||
peer,
|
|
||||||
client_ip_source,
|
|
||||||
trusted_proxy_cidrs,
|
|
||||||
) else {
|
|
||||||
strip_query(&mut request);
|
strip_query(&mut request);
|
||||||
return serve_decoy(request, vhost, true, &runtime).await;
|
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) {
|
if request.uri().query().is_some() || request.headers().contains_key(header::COOKIE) {
|
||||||
return serve_decoy(request, vhost, true, &runtime).await;
|
return serve_decoy(request, vhost, true, &runtime).await;
|
||||||
}
|
}
|
||||||
let Some(client_ip) = client_ip(
|
let Some(client_ip) = client_ip(&request, peer, client_ip_source, trusted_proxy_cidrs) else {
|
||||||
&request,
|
|
||||||
peer,
|
|
||||||
client_ip_source,
|
|
||||||
trusted_proxy_cidrs,
|
|
||||||
) else {
|
|
||||||
return serve_decoy(request, vhost, true, &runtime).await;
|
return serve_decoy(request, vhost, true, &runtime).await;
|
||||||
};
|
};
|
||||||
let Some(token_hash) = bearer_token_hash(&request) else {
|
let Some(token_hash) = bearer_token_hash(&request) else {
|
||||||
return serve_decoy(request, vhost, true, &runtime).await;
|
return serve_decoy(request, vhost, true, &runtime).await;
|
||||||
};
|
};
|
||||||
match request.uri().path() {
|
match request.uri().path() {
|
||||||
"/api/v1/session" => {
|
"/api/v1/session" => handle_session(request, runtime, vhost, token_hash, client_ip).await,
|
||||||
handle_session(request, runtime, vhost, token_hash, client_ip).await
|
|
||||||
}
|
|
||||||
"/api/v1/up" => handle_up(request, runtime, vhost, token_hash).await,
|
"/api/v1/up" => handle_up(request, runtime, vhost, token_hash).await,
|
||||||
"/api/v1/down" => handle_down(request, runtime, vhost, token_hash).await,
|
"/api/v1/down" => handle_down(request, runtime, vhost, token_hash).await,
|
||||||
_ => serve_decoy(request, vhost, true, &runtime).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 {
|
let Some(lane_id) = carrier_lane(&request, session.carrier()) else {
|
||||||
return serve_decoy(request, vhost, true, &runtime).await;
|
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 {
|
let CollectedBody {
|
||||||
request,
|
request,
|
||||||
body,
|
body,
|
||||||
@@ -510,10 +500,7 @@ fn bad_gateway() -> HttpResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn generic_not_found() -> HttpResponse {
|
fn generic_not_found() -> HttpResponse {
|
||||||
full_response(
|
full_response(StatusCode::NOT_FOUND, Bytes::from_static(b"not found\n"))
|
||||||
StatusCode::NOT_FOUND,
|
|
||||||
Bytes::from_static(b"not found\n"),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn full_response(status: StatusCode, body: Bytes) -> HttpResponse {
|
fn full_response(status: StatusCode, body: Bytes) -> HttpResponse {
|
||||||
@@ -523,11 +510,7 @@ fn full_response(status: StatusCode, body: Bytes) -> HttpResponse {
|
|||||||
.boxed_unsync();
|
.boxed_unsync();
|
||||||
let mut response = Response::new(body);
|
let mut response = Response::new(body);
|
||||||
*response.status_mut() = status;
|
*response.status_mut() = status;
|
||||||
insert_header(
|
insert_header(&mut response, header::CONTENT_LENGTH, &length.to_string());
|
||||||
&mut response,
|
|
||||||
header::CONTENT_LENGTH,
|
|
||||||
&length.to_string(),
|
|
||||||
);
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,10 +37,7 @@ pub(super) struct ActivityBody {
|
|||||||
impl ActivityBody {
|
impl ActivityBody {
|
||||||
/// Binds one response body to its request activity guard.
|
/// Binds one response body to its request activity guard.
|
||||||
pub(super) fn new(inner: HttpBody, activity: RequestActivity) -> Self {
|
pub(super) fn new(inner: HttpBody, activity: RequestActivity) -> Self {
|
||||||
Self {
|
Self { inner, activity }
|
||||||
inner,
|
|
||||||
activity,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use http_body_util::{BodyExt, Empty, Limited};
|
use http_body_util::{BodyExt, Empty, Limited};
|
||||||
use hyper::body::{Body as _, Incoming};
|
|
||||||
use hyper::Request;
|
use hyper::Request;
|
||||||
|
use hyper::body::{Body as _, Incoming};
|
||||||
|
|
||||||
use crate::web::manager::WebProcessRuntime;
|
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 {
|
let Some((reader_budget, body_budget)) = runtime.try_body_budget(limit) else {
|
||||||
return Err(CollectBodyError::Limit);
|
return Err(CollectBodyError::Limit);
|
||||||
};
|
};
|
||||||
let body_timeout = Duration::from_secs(
|
let body_timeout =
|
||||||
runtime
|
Duration::from_secs(runtime.active_generation().config().web.timeouts.body_secs);
|
||||||
.active_generation()
|
|
||||||
.config()
|
|
||||||
.web
|
|
||||||
.timeouts
|
|
||||||
.body_secs,
|
|
||||||
);
|
|
||||||
let body = match tokio::time::timeout(body_timeout, Limited::new(body, limit).collect()).await {
|
let body = match tokio::time::timeout(body_timeout, Limited::new(body, limit).collect()).await {
|
||||||
Ok(Ok(body)) => body.to_bytes(),
|
Ok(Ok(body)) => body.to_bytes(),
|
||||||
_ => {
|
_ => {
|
||||||
|
|||||||
+19
-22
@@ -11,8 +11,7 @@ use hyper_util::rt::TokioIo;
|
|||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
BoxError, HttpBody, HttpResponse, bad_gateway, full_response, generic_not_found,
|
BoxError, HttpBody, HttpResponse, bad_gateway, full_response, generic_not_found, insert_header,
|
||||||
insert_header,
|
|
||||||
};
|
};
|
||||||
use crate::config::{WebRuntimeDecoy, WebRuntimeVhost};
|
use crate::config::{WebRuntimeDecoy, WebRuntimeVhost};
|
||||||
use crate::web::manager::WebProcessRuntime;
|
use crate::web::manager::WebProcessRuntime;
|
||||||
@@ -128,14 +127,13 @@ fn static_entry<B>(
|
|||||||
header::X_CONTENT_TYPE_OPTIONS,
|
header::X_CONTENT_TYPE_OPTIONS,
|
||||||
HeaderValue::from_static("nosniff"),
|
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
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_static_path<'a>(
|
fn resolve_static_path<'a>(path: &str, site: &'a crate::config::WebStaticSite) -> Option<&'a str> {
|
||||||
path: &str,
|
|
||||||
site: &'a crate::config::WebStaticSite,
|
|
||||||
) -> Option<&'a str> {
|
|
||||||
if !path.starts_with('/')
|
if !path.starts_with('/')
|
||||||
|| path.contains('\\')
|
|| path.contains('\\')
|
||||||
|| path.contains("//")
|
|| path.contains("//")
|
||||||
@@ -151,7 +149,10 @@ fn resolve_static_path<'a>(
|
|||||||
path
|
path
|
||||||
};
|
};
|
||||||
if site.assets.contains_key(route) {
|
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") {
|
if route == "/favicon.ico" && site.assets.contains_key("/favicon.svg") {
|
||||||
return Some("/favicon.svg");
|
return Some("/favicon.svg");
|
||||||
@@ -198,23 +199,19 @@ async fn proxy_to_upstream(
|
|||||||
.max_header_bytes;
|
.max_header_bytes;
|
||||||
let mut builder = hyper::client::conn::http1::Builder::new();
|
let mut builder = hyper::client::conn::http1::Builder::new();
|
||||||
builder.max_buf_size(max_header_bytes);
|
builder.max_buf_size(max_header_bytes);
|
||||||
let (mut sender, connection) = match tokio::time::timeout(
|
let (mut sender, connection) =
|
||||||
header_timeout,
|
match tokio::time::timeout(header_timeout, builder.handshake(TokioIo::new(stream))).await {
|
||||||
builder.handshake(TokioIo::new(stream)),
|
Ok(Ok(parts)) => parts,
|
||||||
)
|
_ => return bad_gateway(),
|
||||||
.await
|
};
|
||||||
{
|
|
||||||
Ok(Ok(parts)) => parts,
|
|
||||||
_ => return bad_gateway(),
|
|
||||||
};
|
|
||||||
runtime.spawn_auxiliary(async move {
|
runtime.spawn_auxiliary(async move {
|
||||||
let _ = connection.await;
|
let _ = connection.await;
|
||||||
});
|
});
|
||||||
let mut response = match tokio::time::timeout(header_timeout, sender.send_request(request)).await
|
let mut response =
|
||||||
{
|
match tokio::time::timeout(header_timeout, sender.send_request(request)).await {
|
||||||
Ok(Ok(response)) => response,
|
Ok(Ok(response)) => response,
|
||||||
_ => return bad_gateway(),
|
_ => return bad_gateway(),
|
||||||
};
|
};
|
||||||
remove_hop_by_hop(response.headers_mut());
|
remove_hop_by_hop(response.headers_mut());
|
||||||
response.map(|body| {
|
response.map(|body| {
|
||||||
body.map_err(|error| -> BoxError { Box::new(error) })
|
body.map_err(|error| -> BoxError { Box::new(error) })
|
||||||
|
|||||||
+11
-21
@@ -2,15 +2,13 @@ use std::net::{IpAddr, SocketAddr};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use base64::Engine as _;
|
use base64::Engine as _;
|
||||||
use hyper::header;
|
|
||||||
use hyper::Request;
|
use hyper::Request;
|
||||||
|
use hyper::header;
|
||||||
use ipnetwork::IpNetwork;
|
use ipnetwork::IpNetwork;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use subtle::ConstantTimeEq;
|
use subtle::ConstantTimeEq;
|
||||||
|
|
||||||
use crate::config::{
|
use crate::config::{WebClientIpSource, WebRuntimeProfile, WebRuntimeVhost};
|
||||||
WebClientIpSource, WebRuntimeProfile, WebRuntimeVhost,
|
|
||||||
};
|
|
||||||
use crate::web::manager::TokenHash;
|
use crate::web::manager::TokenHash;
|
||||||
|
|
||||||
/// Parses one lowercase canonical Host value restricted to the public HTTPS port.
|
/// 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;
|
return None;
|
||||||
}
|
}
|
||||||
let host = value.strip_suffix(":443").unwrap_or(value);
|
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;
|
return None;
|
||||||
}
|
}
|
||||||
Some(host)
|
Some(host)
|
||||||
@@ -74,14 +71,14 @@ pub(super) fn bridge_candidate(query: Option<&str>) -> ([u8; 32], bool) {
|
|||||||
return (candidate, false);
|
return (candidate, false);
|
||||||
}
|
}
|
||||||
let mut decoded = [0u8; 32];
|
let mut decoded = [0u8; 32];
|
||||||
let Ok(decoded_len) = base64::engine::general_purpose::URL_SAFE_NO_PAD
|
let Ok(decoded_len) =
|
||||||
.decode_slice(value, &mut decoded)
|
base64::engine::general_purpose::URL_SAFE_NO_PAD.decode_slice(value, &mut decoded)
|
||||||
else {
|
else {
|
||||||
return (candidate, false);
|
return (candidate, false);
|
||||||
};
|
};
|
||||||
let mut canonical = [0u8; 43];
|
let mut canonical = [0u8; 43];
|
||||||
let Ok(encoded_len) = base64::engine::general_purpose::URL_SAFE_NO_PAD
|
let Ok(encoded_len) =
|
||||||
.encode_slice(decoded, &mut canonical)
|
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode_slice(decoded, &mut canonical)
|
||||||
else {
|
else {
|
||||||
return (candidate, false);
|
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 values = request.headers().get_all(header::AUTHORIZATION);
|
||||||
let mut values = values.iter();
|
let mut values = values.iter();
|
||||||
let value = values.next()?.to_str().ok()?;
|
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;
|
return None;
|
||||||
}
|
}
|
||||||
let token = value.strip_prefix("Bearer ")?;
|
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()
|
(decoded_len == decoded.len()
|
||||||
&& encoded_len == canonical.len()
|
&& encoded_len == canonical.len()
|
||||||
&& bool::from(canonical.ct_eq(token.as_bytes())))
|
&& 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.
|
/// 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.
|
/// Parses one canonical unsigned decimal carrier sequence header.
|
||||||
pub(super) fn canonical_u64_header<B>(
|
pub(super) fn canonical_u64_header<B>(request: &Request<B>, name: &'static str) -> Option<u64> {
|
||||||
request: &Request<B>,
|
|
||||||
name: &'static str,
|
|
||||||
) -> Option<u64> {
|
|
||||||
let values = request.headers().get_all(name);
|
let values = request.headers().get_all(name);
|
||||||
let mut values = values.iter();
|
let mut values = values.iter();
|
||||||
let value = values.next()?.to_str().ok()?;
|
let value = values.next()?.to_str().ok()?;
|
||||||
@@ -183,10 +176,7 @@ mod tests {
|
|||||||
.header("x-forwarded-for", "192.0.2.10")
|
.header("x-forwarded-for", "192.0.2.10")
|
||||||
.body(())
|
.body(())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(canonical_request_host(&request), Some("proxy.example.com"));
|
||||||
canonical_request_host(&request),
|
|
||||||
Some("proxy.example.com")
|
|
||||||
);
|
|
||||||
let trusted: [IpNetwork; 1] = ["127.0.0.1/32".parse().unwrap()];
|
let trusted: [IpNetwork; 1] = ["127.0.0.1/32".parse().unwrap()];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client_ip(
|
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");
|
let session = response_header(create_headers, "x-session-token");
|
||||||
assert_eq!(session.len(), 43);
|
assert_eq!(session.len(), 43);
|
||||||
|
|
||||||
let replacement = test_runtime_generation(
|
let replacement =
|
||||||
2,
|
test_runtime_generation(2, runtime_config(capability, WebCarrier::HttpsLanes));
|
||||||
runtime_config(capability, WebCarrier::HttpsLanes),
|
|
||||||
);
|
|
||||||
active_runtime.store(Arc::clone(&replacement));
|
active_runtime.store(Arc::clone(&replacement));
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
|
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
|
||||||
let retry_response = request(&listener, &runtime, create_retry).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();
|
.into_bytes();
|
||||||
let next_root_response = request(&listener, &runtime, next_root).await;
|
let next_root_response = request(&listener, &runtime, next_root).await;
|
||||||
let (_, next_root_body) = split_response(&next_root_response);
|
let (_, next_root_body) = split_response(&next_root_response);
|
||||||
assert!(next_root_body.windows(11).any(|value| value == b"bootstrap='"));
|
assert!(
|
||||||
assert!(next_root_body
|
next_root_body
|
||||||
.windows(21)
|
.windows(11)
|
||||||
.any(|value| value == b"carrier='https-lanes'"));
|
.any(|value| value == b"bootstrap='")
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
next_root_body
|
||||||
|
.windows(21)
|
||||||
|
.any(|value| value == b"carrier='https-lanes'")
|
||||||
|
);
|
||||||
|
|
||||||
let close = format!(
|
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"
|
"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)
|
.map(|(token, _)| token)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let replacement = test_runtime_generation(
|
let replacement = test_runtime_generation(2, runtime_config(capability, WebCarrier::Https));
|
||||||
2,
|
|
||||||
runtime_config(capability, WebCarrier::Https),
|
|
||||||
);
|
|
||||||
active_runtime.store(Arc::clone(&replacement));
|
active_runtime.store(Arc::clone(&replacement));
|
||||||
let hello = frame::encode(FrameType::Hello, 0, &[1]);
|
let hello = frame::encode(FrameType::Hello, 0, &[1]);
|
||||||
let mut create = format!(
|
let mut create = format!(
|
||||||
@@ -305,10 +306,8 @@ async fn unused_bootstrap_is_rejected_after_profile_identity_change() {
|
|||||||
.map(|(token, _)| token)
|
.map(|(token, _)| token)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let replacement = test_runtime_generation(
|
let replacement =
|
||||||
2,
|
test_runtime_generation(2, runtime_config(capability, WebCarrier::HttpsLanes));
|
||||||
runtime_config(capability, WebCarrier::HttpsLanes),
|
|
||||||
);
|
|
||||||
active_runtime.store(Arc::clone(&replacement));
|
active_runtime.store(Arc::clone(&replacement));
|
||||||
let hello = frame::encode(FrameType::Hello, 0, &[1]);
|
let hello = frame::encode(FrameType::Hello, 0, &[1]);
|
||||||
let mut create = format!(
|
let mut create = format!(
|
||||||
@@ -330,10 +329,7 @@ async fn unused_bootstrap_is_rejected_after_profile_identity_change() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn https_lanes_is_advertised_and_requires_canonical_lane_headers() {
|
async fn https_lanes_is_advertised_and_requires_canonical_lane_headers() {
|
||||||
let capability = [9u8; 32];
|
let capability = [9u8; 32];
|
||||||
let generation = test_runtime_generation(
|
let generation = test_runtime_generation(1, runtime_config(capability, WebCarrier::HttpsLanes));
|
||||||
1,
|
|
||||||
runtime_config(capability, WebCarrier::HttpsLanes),
|
|
||||||
);
|
|
||||||
let active_runtime = Arc::new(ArcSwap::from(Arc::clone(&generation)));
|
let active_runtime = Arc::new(ArcSwap::from(Arc::clone(&generation)));
|
||||||
let runtime = WebProcessRuntime::start(active_runtime);
|
let runtime = WebProcessRuntime::start(active_runtime);
|
||||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
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);
|
create.extend_from_slice(&hello);
|
||||||
let create_response = request(&listener, &runtime, create).await;
|
let create_response = request(&listener, &runtime, create).await;
|
||||||
let (create_headers, _) = split_response(&create_response);
|
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 session = response_header(create_headers, "x-session-token").to_string();
|
||||||
|
|
||||||
let pong = frame::encode(FrameType::Pong, 0, &[]);
|
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);
|
let (uplink_headers, _) = split_response(&uplink_response);
|
||||||
assert!(uplink_headers.starts_with(b"HTTP/1.1 204"));
|
assert!(uplink_headers.starts_with(b"HTTP/1.1 204"));
|
||||||
assert_eq!(response_header(uplink_headers, "x-up-ack"), "1");
|
assert_eq!(response_header(uplink_headers, "x-up-ack"), "1");
|
||||||
assert!(!std::str::from_utf8(uplink_headers)
|
assert!(
|
||||||
.unwrap()
|
!std::str::from_utf8(uplink_headers)
|
||||||
.lines()
|
.unwrap()
|
||||||
.any(|line| line.to_ascii_lowercase().starts_with("content-length:")));
|
.lines()
|
||||||
|
.any(|line| line.to_ascii_lowercase().starts_with("content-length:"))
|
||||||
|
);
|
||||||
|
|
||||||
let mut missing_lane = format!(
|
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",
|
"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 {
|
impl WebProcessRuntime {
|
||||||
/// Starts one process-scoped manager using immutable allocation ceilings.
|
/// Starts one process-scoped manager using immutable allocation ceilings.
|
||||||
pub(crate) fn start(
|
pub(crate) fn start(active_runtime: Arc<ArcSwap<RuntimeGeneration>>) -> Arc<Self> {
|
||||||
active_runtime: Arc<ArcSwap<RuntimeGeneration>>,
|
|
||||||
) -> Arc<Self> {
|
|
||||||
let limits = active_runtime.load().config().web.limits.clone();
|
let limits = active_runtime.load().config().web.limits.clone();
|
||||||
let runtime = Arc::new(Self {
|
let runtime = Arc::new(Self {
|
||||||
active_runtime,
|
active_runtime,
|
||||||
@@ -169,9 +167,7 @@ impl WebProcessRuntime {
|
|||||||
|
|
||||||
/// Reserves one logical stream in the inner MTProxy handshake phase.
|
/// Reserves one logical stream in the inner MTProxy handshake phase.
|
||||||
pub(crate) fn try_stream_handshake(&self) -> Option<OwnedSemaphorePermit> {
|
pub(crate) fn try_stream_handshake(&self) -> Option<OwnedSemaphorePermit> {
|
||||||
let permit = Arc::clone(&self.stream_handshakes)
|
let permit = Arc::clone(&self.stream_handshakes).try_acquire_owned().ok();
|
||||||
.try_acquire_owned()
|
|
||||||
.ok();
|
|
||||||
if permit.is_none() {
|
if permit.is_none() {
|
||||||
self.record_stream_rejected();
|
self.record_stream_rejected();
|
||||||
}
|
}
|
||||||
@@ -236,7 +232,11 @@ impl WebProcessRuntime {
|
|||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
remove_expired_locked(&mut state, now);
|
remove_expired_locked(&mut state, now);
|
||||||
if state.closed
|
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
|
>= self.limits.max_bootstraps_per_ip
|
||||||
|| !allow_rate(
|
|| !allow_rate(
|
||||||
&mut state.bootstrap_rate,
|
&mut state.bootstrap_rate,
|
||||||
@@ -313,10 +313,7 @@ impl WebProcessRuntime {
|
|||||||
if !digest_matches {
|
if !digest_matches {
|
||||||
return Err(ManagerError::Authentication);
|
return Err(ManagerError::Authentication);
|
||||||
}
|
}
|
||||||
let session = entry
|
let session = entry.session.as_ref().ok_or(ManagerError::Authentication)?;
|
||||||
.session
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(ManagerError::Authentication)?;
|
|
||||||
return Ok(CreateResult {
|
return Ok(CreateResult {
|
||||||
token: entry.session_token.as_str().to_owned(),
|
token: entry.session_token.as_str().to_owned(),
|
||||||
carrier: session.carrier(),
|
carrier: session.carrier(),
|
||||||
@@ -448,14 +445,11 @@ impl WebProcessRuntime {
|
|||||||
let fits = if control {
|
let fits = if control {
|
||||||
bytes <= self.limits.control_bytes_global
|
bytes <= self.limits.control_bytes_global
|
||||||
&& items <= control_item_reserve
|
&& items <= control_item_reserve
|
||||||
&& state.pending_bytes
|
&& state.pending_bytes <= self.limits.pending_bytes_global.saturating_sub(bytes)
|
||||||
<= self.limits.pending_bytes_global.saturating_sub(bytes)
|
&& state.pending_items <= self.limits.pending_items_global.saturating_sub(items)
|
||||||
&& state.pending_items
|
|
||||||
<= self.limits.pending_items_global.saturating_sub(items)
|
|
||||||
&& state.pending_control_bytes
|
&& state.pending_control_bytes
|
||||||
<= self.limits.control_bytes_global.saturating_sub(bytes)
|
<= self.limits.control_bytes_global.saturating_sub(bytes)
|
||||||
&& state.pending_control_items
|
&& state.pending_control_items <= control_item_reserve.saturating_sub(items)
|
||||||
<= control_item_reserve.saturating_sub(items)
|
|
||||||
} else {
|
} else {
|
||||||
let data_bytes = state
|
let data_bytes = state
|
||||||
.pending_bytes
|
.pending_bytes
|
||||||
@@ -464,14 +458,11 @@ impl WebProcessRuntime {
|
|||||||
.pending_items
|
.pending_items
|
||||||
.saturating_sub(state.pending_control_items);
|
.saturating_sub(state.pending_control_items);
|
||||||
let (byte_limit, item_limit) = if downlink {
|
let (byte_limit, item_limit) = if downlink {
|
||||||
let uplink_bytes = self
|
let uplink_bytes = self.limits.max_body_bytes.saturating_add(
|
||||||
.limits
|
self.limits
|
||||||
.max_body_bytes
|
.max_frames_per_body
|
||||||
.saturating_add(
|
.saturating_mul(crate::web::session::QUEUE_ITEM_COST),
|
||||||
self.limits
|
);
|
||||||
.max_frames_per_body
|
|
||||||
.saturating_mul(crate::web::session::QUEUE_ITEM_COST),
|
|
||||||
);
|
|
||||||
(
|
(
|
||||||
data_byte_limit.saturating_sub(uplink_bytes),
|
data_byte_limit.saturating_sub(uplink_bytes),
|
||||||
data_item_limit.saturating_sub(self.limits.max_frames_per_body),
|
data_item_limit.saturating_sub(self.limits.max_frames_per_body),
|
||||||
@@ -531,5 +522,4 @@ impl WebProcessRuntime {
|
|||||||
fn record_limit_hit(&self) {
|
fn record_limit_hit(&self) {
|
||||||
self.limit_hits.fetch_add(1, Ordering::Relaxed);
|
self.limit_hits.fetch_add(1, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ use std::net::{IpAddr, SocketAddr};
|
|||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use super::state::{
|
use super::state::{allocate_stream_port, allow_rate, decrement_map, release_stream_port};
|
||||||
allocate_stream_port, allow_rate, decrement_map, release_stream_port,
|
|
||||||
};
|
|
||||||
use super::{ProfileKey, WebProcessRuntime};
|
use super::{ProfileKey, WebProcessRuntime};
|
||||||
|
|
||||||
impl WebProcessRuntime {
|
impl WebProcessRuntime {
|
||||||
@@ -43,10 +41,7 @@ impl WebProcessRuntime {
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
state.streams_live += 1;
|
state.streams_live += 1;
|
||||||
*state
|
*state.streams_per_profile.entry(profile_key).or_insert(0) += 1;
|
||||||
.streams_per_profile
|
|
||||||
.entry(profile_key)
|
|
||||||
.or_insert(0) += 1;
|
|
||||||
self.streams_opened.fetch_add(1, Ordering::Relaxed);
|
self.streams_opened.fetch_add(1, Ordering::Relaxed);
|
||||||
Some(peer_port)
|
Some(peer_port)
|
||||||
}
|
}
|
||||||
@@ -105,12 +100,7 @@ mod tests {
|
|||||||
let downlink_bytes = data_bytes - uplink_bytes;
|
let downlink_bytes = data_bytes - uplink_bytes;
|
||||||
let downlink_items = data_items - runtime.limits.max_frames_per_body;
|
let downlink_items = data_items - runtime.limits.max_frames_per_body;
|
||||||
|
|
||||||
assert!(runtime.try_reserve_pending(
|
assert!(runtime.try_reserve_pending(downlink_bytes, downlink_items, false, true,));
|
||||||
downlink_bytes,
|
|
||||||
downlink_items,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
));
|
|
||||||
assert!(runtime.try_reserve_pending(
|
assert!(runtime.try_reserve_pending(
|
||||||
uplink_bytes,
|
uplink_bytes,
|
||||||
runtime.limits.max_frames_per_body,
|
runtime.limits.max_frames_per_body,
|
||||||
@@ -120,11 +110,7 @@ mod tests {
|
|||||||
assert!(!runtime.try_reserve_pending(1, 1, false, true));
|
assert!(!runtime.try_reserve_pending(1, 1, false, true));
|
||||||
|
|
||||||
runtime.release_pending(downlink_bytes, downlink_items, false);
|
runtime.release_pending(downlink_bytes, downlink_items, false);
|
||||||
runtime.release_pending(
|
runtime.release_pending(uplink_bytes, runtime.limits.max_frames_per_body, false);
|
||||||
uplink_bytes,
|
|
||||||
runtime.limits.max_frames_per_body,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
runtime.shutdown().await;
|
runtime.shutdown().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ use std::time::{Duration, Instant};
|
|||||||
|
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
|
use super::state::{ClosedToken, decrement_map, remove_bootstrap_locked, remove_expired_locked};
|
||||||
use super::{ProfileKey, TokenHash, WebProcessRuntime};
|
use super::{ProfileKey, TokenHash, WebProcessRuntime};
|
||||||
use super::state::{
|
|
||||||
ClosedToken, decrement_map, remove_bootstrap_locked, remove_expired_locked,
|
|
||||||
};
|
|
||||||
|
|
||||||
impl WebProcessRuntime {
|
impl WebProcessRuntime {
|
||||||
/// Removes one closed session and retains a bounded host-bound replay marker.
|
/// 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.
|
/// Applies one token-bucket admission decision at a caller-supplied monotonic time.
|
||||||
pub(super) fn allow_rate(
|
pub(super) fn allow_rate(state: &mut RateState, now: Instant, per_minute: u32, burst: u32) -> bool {
|
||||||
state: &mut RateState,
|
|
||||||
now: Instant,
|
|
||||||
per_minute: u32,
|
|
||||||
burst: u32,
|
|
||||||
) -> bool {
|
|
||||||
let burst = f64::from(burst);
|
let burst = f64::from(burst);
|
||||||
if let Some(last) = state.last {
|
if let Some(last) = state.last {
|
||||||
let elapsed = now.saturating_duration_since(last).as_secs_f64();
|
let elapsed = now.saturating_duration_since(last).as_secs_f64();
|
||||||
state.tokens =
|
state.tokens = (state.tokens + elapsed * f64::from(per_minute) / 60.0).min(burst);
|
||||||
(state.tokens + elapsed * f64::from(per_minute) / 60.0).min(burst);
|
|
||||||
} else {
|
} else {
|
||||||
state.tokens = burst;
|
state.tokens = burst;
|
||||||
}
|
}
|
||||||
@@ -216,9 +210,9 @@ where
|
|||||||
|
|
||||||
/// Computes the process-wide item reserve required for session control progress.
|
/// Computes the process-wide item reserve required for session control progress.
|
||||||
pub(super) fn control_item_reserve(limits: &WebLimitsConfig) -> usize {
|
pub(super) fn control_item_reserve(limits: &WebLimitsConfig) -> usize {
|
||||||
limits.max_sessions_global.saturating_mul(
|
limits
|
||||||
16usize.saturating_add(limits.max_streams_per_session.saturating_mul(3)),
|
.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.
|
/// 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) {
|
if !self.queue_window_locked(&mut state, stream_id, count as u32) {
|
||||||
drop(state);
|
drop(state);
|
||||||
self.close();
|
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(()))
|
Poll::Ready(Ok(()))
|
||||||
}
|
}
|
||||||
@@ -368,14 +370,14 @@ impl WebSession {
|
|||||||
|
|
||||||
/// Returns the process queue-capacity notification source while the manager lives.
|
/// Returns the process queue-capacity notification source while the manager lives.
|
||||||
pub(super) fn budget_notify(&self) -> Option<Arc<Notify>> {
|
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) {
|
fn release_stream_reservation(&self, peer_port: u16) {
|
||||||
let removed = self.state.lock().active_peer_ports.remove(&peer_port);
|
let removed = self.state.lock().active_peer_ports.remove(&peer_port);
|
||||||
if removed
|
if removed && let Some(manager) = self.manager.upgrade() {
|
||||||
&& let Some(manager) = self.manager.upgrade()
|
|
||||||
{
|
|
||||||
manager.release_stream(
|
manager.release_stream(
|
||||||
self.profile_key,
|
self.profile_key,
|
||||||
self.client_ip,
|
self.client_ip,
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ use std::sync::Arc;
|
|||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::proxy::shared_state::ConntrackClosePolicy;
|
||||||
use crate::web::frame::FrameType;
|
use crate::web::frame::FrameType;
|
||||||
use crate::web::stream::WebLogicalStream;
|
use crate::web::stream::WebLogicalStream;
|
||||||
use crate::proxy::shared_state::ConntrackClosePolicy;
|
|
||||||
|
|
||||||
use super::{WebSession, inbound_queue_cost};
|
use super::{WebSession, inbound_queue_cost};
|
||||||
|
|
||||||
@@ -70,9 +70,7 @@ impl WebSession {
|
|||||||
});
|
});
|
||||||
(queued, reserved)
|
(queued, reserved)
|
||||||
};
|
};
|
||||||
if reserved
|
if reserved && let Some(manager) = self.manager.upgrade() {
|
||||||
&& let Some(manager) = self.manager.upgrade()
|
|
||||||
{
|
|
||||||
manager.release_stream(
|
manager.release_stream(
|
||||||
self.profile_key,
|
self.profile_key,
|
||||||
self.client_ip,
|
self.client_ip,
|
||||||
|
|||||||
+11
-29
@@ -3,8 +3,7 @@ use std::time::{Duration, Instant};
|
|||||||
use bytes::{BufMut, Bytes, BytesMut};
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
DownBatch, PendingClass, PollResult, QUEUE_ITEM_COST, QueuedFrame, SessionState,
|
DownBatch, PendingClass, PollResult, QUEUE_ITEM_COST, QueuedFrame, SessionState, WebSession,
|
||||||
WebSession,
|
|
||||||
};
|
};
|
||||||
use crate::config::WebCarrier;
|
use crate::config::WebCarrier;
|
||||||
use crate::web::frame::{self, FrameType};
|
use crate::web::frame::{self, FrameType};
|
||||||
@@ -117,9 +116,8 @@ impl WebSession {
|
|||||||
.limits
|
.limits
|
||||||
.pending_bytes_per_session
|
.pending_bytes_per_session
|
||||||
.saturating_sub(self.limits.control_bytes_per_session);
|
.saturating_sub(self.limits.control_bytes_per_session);
|
||||||
let item_reserve = 16usize.saturating_add(
|
let item_reserve =
|
||||||
self.limits.max_streams_per_session.saturating_mul(3),
|
16usize.saturating_add(self.limits.max_streams_per_session.saturating_mul(3));
|
||||||
);
|
|
||||||
let data_item_limit = self
|
let data_item_limit = self
|
||||||
.limits
|
.limits
|
||||||
.pending_items_per_session
|
.pending_items_per_session
|
||||||
@@ -146,14 +144,11 @@ impl WebSession {
|
|||||||
.pending_items
|
.pending_items
|
||||||
.saturating_sub(state.pending_control_items);
|
.saturating_sub(state.pending_control_items);
|
||||||
let (byte_limit, item_limit) = if class == PendingClass::Downlink {
|
let (byte_limit, item_limit) = if class == PendingClass::Downlink {
|
||||||
let uplink_bytes = self
|
let uplink_bytes = self.limits.max_body_bytes.saturating_add(
|
||||||
.limits
|
self.limits
|
||||||
.max_body_bytes
|
.max_frames_per_body
|
||||||
.saturating_add(
|
.saturating_mul(QUEUE_ITEM_COST),
|
||||||
self.limits
|
);
|
||||||
.max_frames_per_body
|
|
||||||
.saturating_mul(QUEUE_ITEM_COST),
|
|
||||||
);
|
|
||||||
(
|
(
|
||||||
data_byte_limit.saturating_sub(uplink_bytes),
|
data_byte_limit.saturating_sub(uplink_bytes),
|
||||||
data_item_limit.saturating_sub(self.limits.max_frames_per_body),
|
data_item_limit.saturating_sub(self.limits.max_frames_per_body),
|
||||||
@@ -172,12 +167,7 @@ impl WebSession {
|
|||||||
let Some(manager) = self.manager.upgrade() else {
|
let Some(manager) = self.manager.upgrade() else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
if !manager.try_reserve_pending(
|
if !manager.try_reserve_pending(bytes, items, control, class == PendingClass::Downlink) {
|
||||||
bytes,
|
|
||||||
items,
|
|
||||||
control,
|
|
||||||
class == PendingClass::Downlink,
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
state.pending_bytes += bytes;
|
state.pending_bytes += bytes;
|
||||||
@@ -301,13 +291,7 @@ impl WebSession {
|
|||||||
control: bool,
|
control: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if self.carrier() == WebCarrier::HttpsLanes {
|
if self.carrier() == WebCarrier::HttpsLanes {
|
||||||
return self.queue_lane_frame_locked(
|
return self.queue_lane_frame_locked(state, frame_type, stream_id, payload, control);
|
||||||
state,
|
|
||||||
frame_type,
|
|
||||||
stream_id,
|
|
||||||
payload,
|
|
||||||
control,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
let cost = frame::HEADER_BYTES + payload.len() + QUEUE_ITEM_COST;
|
let cost = frame::HEADER_BYTES + payload.len() + QUEUE_ITEM_COST;
|
||||||
let class = if control {
|
let class = if control {
|
||||||
@@ -425,9 +409,7 @@ mod tests {
|
|||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::config::{
|
use crate::config::{WebLimitsConfig, WebRuntimeProfile, WebSecretMode, WebTimeoutsConfig};
|
||||||
WebLimitsConfig, WebRuntimeProfile, WebSecretMode, WebTimeoutsConfig,
|
|
||||||
};
|
|
||||||
use crate::web::manager::WebProcessRuntime;
|
use crate::web::manager::WebProcessRuntime;
|
||||||
|
|
||||||
fn session() -> Arc<WebSession> {
|
fn session() -> Arc<WebSession> {
|
||||||
|
|||||||
+15
-18
@@ -7,8 +7,8 @@ use subtle::ConstantTimeEq;
|
|||||||
|
|
||||||
use super::uplink::{inbound_reservation, validate_batch};
|
use super::uplink::{inbound_reservation, validate_batch};
|
||||||
use super::{
|
use super::{
|
||||||
CarrierLane, DownBatch, PendingClass, PollResult, QUEUE_ITEM_COST, QueuedFrame,
|
CarrierLane, DownBatch, PendingClass, PollResult, QUEUE_ITEM_COST, QueuedFrame, SessionState,
|
||||||
SessionState, WebSession, remember_closed,
|
WebSession, remember_closed,
|
||||||
};
|
};
|
||||||
use crate::config::{WebCarrier, WebLimitsConfig};
|
use crate::config::{WebCarrier, WebLimitsConfig};
|
||||||
use crate::web::frame::{self, Frame, FrameType};
|
use crate::web::frame::{self, Frame, FrameType};
|
||||||
@@ -32,9 +32,11 @@ impl WebSession {
|
|||||||
return Err(ManagerError::Protocol);
|
return Err(ManagerError::Protocol);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if frames.iter().copied().any(|value| {
|
if frames
|
||||||
value.stream_id != lane_id || frame::validate_client_shape(value).is_err()
|
.iter()
|
||||||
}) {
|
.copied()
|
||||||
|
.any(|value| value.stream_id != lane_id || frame::validate_client_shape(value).is_err())
|
||||||
|
{
|
||||||
self.close();
|
self.close();
|
||||||
return Err(ManagerError::Protocol);
|
return Err(ManagerError::Protocol);
|
||||||
}
|
}
|
||||||
@@ -48,13 +50,17 @@ impl WebSession {
|
|||||||
state.last_activity = Instant::now();
|
state.last_activity = Instant::now();
|
||||||
if !state.carrier_lanes.contains_key(&lane_id) {
|
if !state.carrier_lanes.contains_key(&lane_id) {
|
||||||
if lane_id != 0
|
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)
|
&& only_late_frames(&frames)
|
||||||
{
|
{
|
||||||
return Ok(sequence);
|
return Ok(sequence);
|
||||||
}
|
}
|
||||||
if lane_id == 0
|
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);
|
drop(state);
|
||||||
self.close();
|
self.close();
|
||||||
@@ -182,12 +188,7 @@ impl WebSession {
|
|||||||
};
|
};
|
||||||
if let Some(batch) = acknowledged {
|
if let Some(batch) = acknowledged {
|
||||||
self.release_locked(&mut state, batch.data_bytes, batch.data_items, false);
|
self.release_locked(&mut state, batch.data_bytes, batch.data_items, false);
|
||||||
self.release_locked(
|
self.release_locked(&mut state, batch.control_bytes, batch.control_items, true);
|
||||||
&mut state,
|
|
||||||
batch.control_bytes,
|
|
||||||
batch.control_items,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
if let Some(stream) = state.streams.get_mut(&lane_id)
|
if let Some(stream) = state.streams.get_mut(&lane_id)
|
||||||
&& let Some(waker) = stream.write_waker.take()
|
&& 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) {
|
pub(super) fn remember_closed_locked(&self, state: &mut SessionState, stream_id: u32) {
|
||||||
let evicted = remember_closed(
|
let evicted = remember_closed(state, stream_id, self.limits.max_tombstones_per_session);
|
||||||
state,
|
|
||||||
stream_id,
|
|
||||||
self.limits.max_tombstones_per_session,
|
|
||||||
);
|
|
||||||
if self.carrier() != WebCarrier::HttpsLanes {
|
if self.carrier() != WebCarrier::HttpsLanes {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ use std::sync::Arc;
|
|||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::config::{
|
use crate::config::{WebRuntimeProfile, WebSecretMode, WebTimeoutsConfig};
|
||||||
WebRuntimeProfile, WebSecretMode, WebTimeoutsConfig,
|
|
||||||
};
|
|
||||||
use crate::web::manager::WebProcessRuntime;
|
use crate::web::manager::WebProcessRuntime;
|
||||||
|
|
||||||
fn session_with_limits(limits: WebLimitsConfig) -> Arc<WebSession> {
|
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());
|
state.carrier_lanes.insert(7, CarrierLane::new());
|
||||||
let encoded = frame::encode(FrameType::Close, 7, &[]);
|
let encoded = frame::encode(FrameType::Close, 7, &[]);
|
||||||
let cost = encoded.len() + QUEUE_ITEM_COST;
|
let cost = encoded.len() + QUEUE_ITEM_COST;
|
||||||
state.carrier_lanes.get_mut(&7).unwrap().pending_frames.push_back(
|
state
|
||||||
QueuedFrame {
|
.carrier_lanes
|
||||||
|
.get_mut(&7)
|
||||||
|
.unwrap()
|
||||||
|
.pending_frames
|
||||||
|
.push_back(QueuedFrame {
|
||||||
encoded: BytesMut::from(encoded.as_ref()),
|
encoded: BytesMut::from(encoded.as_ref()),
|
||||||
frame_type: FrameType::Close,
|
frame_type: FrameType::Close,
|
||||||
stream_id: 7,
|
stream_id: 7,
|
||||||
control: true,
|
control: true,
|
||||||
cost,
|
cost,
|
||||||
},
|
});
|
||||||
);
|
|
||||||
state.pending_bytes = cost;
|
state.pending_bytes = cost;
|
||||||
state.pending_items = 1;
|
state.pending_items = 1;
|
||||||
state.pending_control_bytes = cost;
|
state.pending_control_bytes = cost;
|
||||||
|
|||||||
@@ -138,12 +138,8 @@ impl WebSession {
|
|||||||
FrameType::Open => {
|
FrameType::Open => {
|
||||||
let Some(peer_port) = self.reserve_stream_locked(state) else {
|
let Some(peer_port) = self.reserve_stream_locked(state) else {
|
||||||
self.remember_closed_locked(state, value.stream_id);
|
self.remember_closed_locked(state, value.stream_id);
|
||||||
if !self.queue_control_locked(
|
if !self.queue_control_locked(state, FrameType::Close, value.stream_id, &[])
|
||||||
state,
|
{
|
||||||
FrameType::Close,
|
|
||||||
value.stream_id,
|
|
||||||
&[],
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -169,8 +165,8 @@ impl WebSession {
|
|||||||
bytes: Bytes::copy_from_slice(value.payload),
|
bytes: Bytes::copy_from_slice(value.payload),
|
||||||
offset: 0,
|
offset: 0,
|
||||||
});
|
});
|
||||||
*unused_bytes = unused_bytes
|
*unused_bytes =
|
||||||
.saturating_sub(value.payload.len() + QUEUE_ITEM_COST);
|
unused_bytes.saturating_sub(value.payload.len() + QUEUE_ITEM_COST);
|
||||||
*unused_items = unused_items.saturating_sub(1);
|
*unused_items = unused_items.saturating_sub(1);
|
||||||
if let Some(waker) = stream.read_waker.take() {
|
if let Some(waker) = stream.read_waker.take() {
|
||||||
waker.wake();
|
waker.wake();
|
||||||
@@ -256,8 +252,8 @@ pub(super) fn validate_batch(state: &SessionState, frames: &[Frame<'_>]) -> bool
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let was_closed = state.closed_streams.contains(&value.stream_id)
|
let was_closed =
|
||||||
|| closed.contains(&value.stream_id);
|
state.closed_streams.contains(&value.stream_id) || closed.contains(&value.stream_id);
|
||||||
match value.frame_type {
|
match value.frame_type {
|
||||||
FrameType::Open => {
|
FrameType::Open => {
|
||||||
if live.contains_key(&value.stream_id) || was_closed {
|
if live.contains_key(&value.stream_id) || was_closed {
|
||||||
@@ -308,10 +304,7 @@ pub(super) fn validate_batch(state: &SessionState, frames: &[Frame<'_>]) -> bool
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn inbound_reservation(
|
pub(super) fn inbound_reservation(state: &SessionState, frames: &[Frame<'_>]) -> (usize, usize) {
|
||||||
state: &SessionState,
|
|
||||||
frames: &[Frame<'_>],
|
|
||||||
) -> (usize, usize) {
|
|
||||||
let mut live = state.streams.keys().copied().collect::<HashSet<_>>();
|
let mut live = state.streams.keys().copied().collect::<HashSet<_>>();
|
||||||
let mut bytes = 0usize;
|
let mut bytes = 0usize;
|
||||||
let mut items = 0usize;
|
let mut items = 0usize;
|
||||||
@@ -338,9 +331,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use crate::config::{
|
use crate::config::{WebLimitsConfig, WebRuntimeProfile, WebSecretMode, WebTimeoutsConfig};
|
||||||
WebLimitsConfig, WebRuntimeProfile, WebSecretMode, WebTimeoutsConfig,
|
|
||||||
};
|
|
||||||
use crate::web::manager::WebProcessRuntime;
|
use crate::web::manager::WebProcessRuntime;
|
||||||
|
|
||||||
fn session() -> Arc<WebSession> {
|
fn session() -> Arc<WebSession> {
|
||||||
@@ -383,10 +374,7 @@ mod tests {
|
|||||||
let session = session();
|
let session = session();
|
||||||
let body = frame::encode(FrameType::Pong, 0, &[]);
|
let body = frame::encode(FrameType::Pong, 0, &[]);
|
||||||
session.up_active.store(true, Ordering::Release);
|
session.up_active.store(true, Ordering::Release);
|
||||||
assert_eq!(
|
assert_eq!(session.process_up(1, &body), Err(ManagerError::Concurrent));
|
||||||
session.process_up(1, &body),
|
|
||||||
Err(ManagerError::Concurrent)
|
|
||||||
);
|
|
||||||
assert_eq!(session.state.lock().last_up_sequence, 0);
|
assert_eq!(session.state.lock().last_up_sequence, 0);
|
||||||
session.up_active.store(false, Ordering::Release);
|
session.up_active.store(false, Ordering::Release);
|
||||||
assert_eq!(session.process_up(1, &body), Ok(1));
|
assert_eq!(session.process_up(1, &body), Ok(1));
|
||||||
|
|||||||
Reference in New Issue
Block a user