This commit is contained in:
Alexey
2026-08-23 10:56:00 +03:00
parent 596149cab8
commit 840b6f563a
31 changed files with 232 additions and 346 deletions
+7 -18
View File
@@ -47,10 +47,8 @@ pub(super) fn rebuild(config: &mut ProxyConfig) -> Result<()> {
})?;
let (client_secret, client_secret_len) =
client_secret(auth_entry.secret, profile.secret_mode);
let capability = derive_web_capability(
&client_secret[..client_secret_len],
vhost.host.as_bytes(),
)?;
let capability =
derive_web_capability(&client_secret[..client_secret_len], vhost.host.as_bytes())?;
if !capabilities.insert(capability) {
return Err(ProxyError::Config(format!(
"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.
pub(crate) fn derive_web_capability(secret: &[u8], host: &[u8]) -> Result<[u8; 32]> {
let mut mac = Hmac::<Sha256>::new_from_slice(secret).map_err(|_| {
ProxyError::Config("WEB capability secret must not be empty".to_string())
})?;
let mut mac = Hmac::<Sha256>::new_from_slice(secret)
.map_err(|_| ProxyError::Config("WEB capability secret must not be empty".to_string()))?;
mac.update(WEB_CAPABILITY_CONTEXT);
mac.update(host);
Ok(mac.finalize().into_bytes().into())
@@ -159,13 +156,7 @@ fn build_decoy(
})
}
WebDecoyConfig::StaticDirectory { directory, index } => {
let site = load_static_site(
directory,
index,
limits,
static_files,
static_bytes,
)?;
let site = load_static_site(directory, index, limits, static_files, static_bytes)?;
Ok(WebRuntimeDecoy::StaticDirectory(Arc::new(site)))
}
}
@@ -239,8 +230,7 @@ fn load_static_directory(
})?;
if *total_files >= limits.max_static_files {
return Err(ProxyError::Config(
"WEB static entries exceed process-wide web.limits.max_static_files"
.to_string(),
"WEB static entries exceed process-wide web.limits.max_static_files".to_string(),
));
}
*total_files += 1;
@@ -317,8 +307,7 @@ fn load_static_directory(
})?;
if *total_bytes > limits.max_static_bytes {
return Err(ProxyError::Config(
"WEB static snapshots exceed process-wide web.limits.max_static_bytes"
.to_string(),
"WEB static snapshots exceed process-wide web.limits.max_static_bytes".to_string(),
));
}
let relative = path.strip_prefix(root).map_err(|_| {
+27 -14
View File
@@ -59,9 +59,7 @@ pub(super) fn validate(config: &mut ProxyConfig) -> Result<()> {
}
validate_limits(&config.web.limits)?;
if config.web.carrier == WebCarrier::HttpsLanes
&& config.web.limits.max_http_handlers < 2
{
if config.web.carrier == WebCarrier::HttpsLanes && config.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)?;
@@ -155,11 +153,20 @@ fn validate_limits(limits: &WebLimitsConfig) -> Result<()> {
("max_streams_per_session", limits.max_streams_per_session),
("max_streams_global", limits.max_streams_global),
("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_items_per_session", limits.pending_items_per_session),
(
"pending_items_per_session",
limits.pending_items_per_session,
),
("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),
("max_bootstraps_global", limits.max_bootstraps_global),
("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),
] {
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 = [
("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_sessions_per_minute", limits.new_sessions_per_minute),
("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
.checked_mul(control_frame_cost)
.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
|| 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 profile_count = 0usize;
for (vhost_idx, vhost) in config.web.vhosts.iter_mut().enumerate() {
vhost.host = normalize_web_host(
&vhost.host,
&format!("web.vhosts[{vhost_idx}].host"),
)?;
vhost.host = normalize_web_host(&vhost.host, &format!("web.vhosts[{vhost_idx}].host"))?;
if !hosts.insert(vhost.host.clone()) {
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
.unwrap_or(limits.max_streams_per_session);
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
@@ -94,10 +94,7 @@ fn web_semaphore_limits_are_rejected_before_runtime_construction() {
#[test]
fn web_ipv6_decoy_uses_a_valid_http_authority() {
let ipv6 = WEB_CONFIG.replace(
"http://127.0.0.1:18081",
"http://[::1]:18081",
);
let ipv6 = WEB_CONFIG.replace("http://127.0.0.1:18081", "http://[::1]:18081");
let config = load_config_from_temp_toml(&ipv6);
let runtime = config.web.runtime.expect("WEB runtime snapshot");
let vhost = runtime.vhosts.get("proxy.example.com").unwrap();
+1 -4
View File
@@ -359,10 +359,7 @@ pub(crate) struct WebRuntimeProfile {
/// Runtime-ready ordinary-site fallback.
#[derive(Debug)]
pub(crate) enum WebRuntimeDecoy {
HttpUpstream {
addr: SocketAddr,
authority: String,
},
HttpUpstream { addr: SocketAddr, authority: String },
StaticDirectory(Arc<WebStaticSite>),
}