[Fix] Limit hosted-user MTP sessions
This commit is contained in:
parent
e19c3c3d12
commit
6fa8ac8b5f
4 changed files with 49 additions and 4 deletions
|
|
@ -87,4 +87,4 @@ Replacement uploads temporarily count both old and new assets until commit.
|
|||
|
||||
Signed relays expire after 30 days less five minutes; replay rows remain for 30 days. `max_relay_future_skew_millis` defaults to 300000, or five minutes, and can be reduced for deployment clock tolerance. Fixed five-minute margin keeps freshness within replay retention even if configuration changes. Timestamps use Unix milliseconds.
|
||||
|
||||
Asset and blob list responses contain at most 128 entries. Send the returned positive `Offset` as the next request cursor; `Offset: 0` ends pagination. `web.max_mtp_sessions` defaults to 256 and limits concurrent authenticated MTP sessions. Legacy web administration requires the non-default `legacy-web-admin` feature.
|
||||
Asset and blob list responses contain at most 128 entries. Send the returned positive `Offset` as the next request cursor; `Offset: 0` ends pagination. `web.max_mtp_sessions` defaults to 256 and limits concurrent authenticated MTP sessions. `web.max_mtp_sessions_per_user` defaults to four sessions per hosted user. Legacy web administration requires the non-default `legacy-web-admin` feature.
|
||||
|
|
|
|||
|
|
@ -577,6 +577,7 @@ async fn main() -> ExitCode {
|
|||
}),
|
||||
required: web.required,
|
||||
max_mtp_sessions: web.max_mtp_sessions,
|
||||
max_mtp_sessions_per_user: web.max_mtp_sessions_per_user,
|
||||
authority_discovery: iota_identity::AuthorityDiscoveryDocument {
|
||||
version: 1,
|
||||
service: iota_identity::AuthorityKind::Iota,
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ pub enum ConfigError {
|
|||
InvalidRelayRouterKey(String),
|
||||
#[error("relay router certificate path must not be empty")]
|
||||
MissingRelayRouterCertificate,
|
||||
#[error("web.max_mtp_sessions must be greater than zero")]
|
||||
#[error("web MTP session limits must be greater than zero")]
|
||||
InvalidMaxMtpSessions,
|
||||
#[error("max_ipc_clients must be greater than zero")]
|
||||
InvalidMaxIpcClients,
|
||||
|
|
@ -155,6 +155,8 @@ pub struct WebSettings {
|
|||
pub required: bool,
|
||||
#[serde(default = "default_max_mtp_sessions")]
|
||||
pub max_mtp_sessions: usize,
|
||||
#[serde(default = "default_max_mtp_sessions_per_user")]
|
||||
pub max_mtp_sessions_per_user: usize,
|
||||
#[serde(default)]
|
||||
pub direct_endpoints: Vec<String>,
|
||||
#[serde(default)]
|
||||
|
|
@ -163,6 +165,9 @@ pub struct WebSettings {
|
|||
const fn default_max_mtp_sessions() -> usize {
|
||||
256
|
||||
}
|
||||
const fn default_max_mtp_sessions_per_user() -> usize {
|
||||
4
|
||||
}
|
||||
fn default_web_bind() -> String {
|
||||
"127.0.0.1".into()
|
||||
}
|
||||
|
|
@ -180,6 +185,7 @@ impl Default for WebSettings {
|
|||
key: None,
|
||||
required: false,
|
||||
max_mtp_sessions: default_max_mtp_sessions(),
|
||||
max_mtp_sessions_per_user: default_max_mtp_sessions_per_user(),
|
||||
direct_endpoints: Vec::new(),
|
||||
relay_hints: Vec::new(),
|
||||
}
|
||||
|
|
@ -270,7 +276,7 @@ pub fn validate_config(config: &IotaConfig) -> Result<(), ConfigError> {
|
|||
if config.storage_limits.max_user_blobs <= 0 {
|
||||
return Err(ConfigError::InvalidStorageLimit("max_user_blobs"));
|
||||
}
|
||||
if config.web.max_mtp_sessions == 0 {
|
||||
if config.web.max_mtp_sessions == 0 || config.web.max_mtp_sessions_per_user == 0 {
|
||||
return Err(ConfigError::InvalidMaxMtpSessions);
|
||||
}
|
||||
if config.max_ipc_clients == 0 {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,12 @@ use iota_identity::{LocalDescriptorPublisher, LocalUserId, LocalUserStore, Signe
|
|||
use iota_logger::log;
|
||||
use mtp::host::HostConfig;
|
||||
use mtp::webserver::{HttpRequest, HttpResponse, MTPWebServer, WebServerConfig};
|
||||
use std::{net::IpAddr, path::PathBuf, sync::Arc};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::IpAddr,
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex as StdMutex},
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
|
@ -41,6 +46,7 @@ pub struct WebConfig {
|
|||
pub tls: Option<TlsConfig>,
|
||||
pub required: bool,
|
||||
pub max_mtp_sessions: usize,
|
||||
pub max_mtp_sessions_per_user: usize,
|
||||
pub authority_discovery: iota_identity::AuthorityDiscoveryDocument,
|
||||
pub local_users: Arc<dyn LocalUserStore>,
|
||||
pub descriptor_publisher: Arc<dyn LocalDescriptorPublisher>,
|
||||
|
|
@ -64,6 +70,23 @@ impl std::fmt::Display for WebServerError {
|
|||
}
|
||||
impl std::error::Error for WebServerError {}
|
||||
|
||||
struct UserSessionGuard {
|
||||
sessions: Arc<StdMutex<HashMap<u64, usize>>>,
|
||||
user_id: u64,
|
||||
}
|
||||
|
||||
impl Drop for UserSessionGuard {
|
||||
fn drop(&mut self) {
|
||||
let mut sessions = self.sessions.lock().unwrap();
|
||||
if let Some(count) = sessions.get_mut(&self.user_id) {
|
||||
*count -= 1;
|
||||
if *count == 0 {
|
||||
sessions.remove(&self.user_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WebServerHandle {
|
||||
cancellation: CancellationToken,
|
||||
join: Mutex<Option<JoinHandle<()>>>,
|
||||
|
|
@ -292,6 +315,8 @@ pub async fn start(
|
|||
let cancellation = parent.child_token();
|
||||
let task_cancellation = cancellation.clone();
|
||||
let session_limit = Arc::new(tokio::sync::Semaphore::new(config.max_mtp_sessions));
|
||||
let user_sessions = Arc::new(StdMutex::new(HashMap::<u64, usize>::new()));
|
||||
let max_user_sessions = config.max_mtp_sessions_per_user;
|
||||
let join = tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
|
|
@ -301,9 +326,22 @@ pub async fn start(
|
|||
log!("Rejected MTP connection: session limit reached");
|
||||
continue;
|
||||
};
|
||||
let user_guard = if connection.client_id & (1_u64 << 63) == 0 {
|
||||
let mut sessions = user_sessions.lock().unwrap();
|
||||
let count = sessions.entry(connection.client_id).or_default();
|
||||
if *count >= max_user_sessions {
|
||||
log!("Rejected MTP connection: per-user session limit reached");
|
||||
continue;
|
||||
}
|
||||
*count += 1;
|
||||
Some(UserSessionGuard { sessions: user_sessions.clone(), user_id: connection.client_id })
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let handler = mtp_handler.clone();
|
||||
tokio::spawn(async move {
|
||||
let _permit = permit;
|
||||
let _user_guard = user_guard;
|
||||
handler.accept(connection).await;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue