Cap concurrent inbound gossip sessions
All checks were successful
CI / check (push) Successful in 5m22s
All checks were successful
CI / check (push) Successful in 5m22s
This commit is contained in:
parent
007b02d380
commit
9806da9c28
1 changed files with 9 additions and 0 deletions
|
|
@ -13,6 +13,7 @@ use subtle::ConstantTimeEq;
|
||||||
// single line can't grow an unbounded buffer, and the handshake can't hang forever.
|
// single line can't grow an unbounded buffer, and the handshake can't hang forever.
|
||||||
const MAX_GOSSIP_LINE: usize = 1 << 20; // 1 MiB per framed message
|
const MAX_GOSSIP_LINE: usize = 1 << 20; // 1 MiB per framed message
|
||||||
const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(30);
|
const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(30);
|
||||||
|
const MAX_INBOUND_SESSIONS: usize = 64; // concurrent unauthenticated accepts before we shed
|
||||||
|
|
||||||
// Read one newline-delimited message, bounded to `max` bytes so a peer streaming
|
// Read one newline-delimited message, bounded to `max` bytes so a peer streaming
|
||||||
// without a newline can't OOM the single-process daemon. `Ok(None)` on EOF.
|
// without a newline can't OOM the single-process daemon. `Ok(None)` on EOF.
|
||||||
|
|
@ -101,12 +102,20 @@ async fn listen(bind: String, engine: Shared, secret: String, origin: String, ou
|
||||||
Err(e) => return tracing::error!(%e, %bind, "gossip bind failed"),
|
Err(e) => return tracing::error!(%e, %bind, "gossip bind failed"),
|
||||||
};
|
};
|
||||||
tracing::info!(%bind, tls = acceptor.is_some(), "gossip listening");
|
tracing::info!(%bind, tls = acceptor.is_some(), "gossip listening");
|
||||||
|
// Cap concurrent half-open/authenticating sessions so someone who can reach the
|
||||||
|
// port can't exhaust tasks/FDs before the handshake rejects them.
|
||||||
|
let limiter = Arc::new(tokio::sync::Semaphore::new(MAX_INBOUND_SESSIONS));
|
||||||
loop {
|
loop {
|
||||||
if let Ok((stream, addr)) = listener.accept().await {
|
if let Ok((stream, addr)) = listener.accept().await {
|
||||||
|
let Ok(permit) = limiter.clone().try_acquire_owned() else {
|
||||||
|
tracing::warn!(%addr, "gossip inbound session cap reached; dropping connection");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
tracing::info!(%addr, "gossip peer accepted");
|
tracing::info!(%addr, "gossip peer accepted");
|
||||||
let (engine, secret, origin, outbound, acceptor) =
|
let (engine, secret, origin, outbound, acceptor) =
|
||||||
(engine.clone(), secret.clone(), origin.clone(), outbound.clone(), acceptor.clone());
|
(engine.clone(), secret.clone(), origin.clone(), outbound.clone(), acceptor.clone());
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
|
let _permit = permit; // released when the session ends
|
||||||
let served = match acceptor {
|
let served = match acceptor {
|
||||||
Some(acc) => match acc.accept(stream).await {
|
Some(acc) => match acc.accept(stream).await {
|
||||||
Ok(tls) => session(tls, engine, secret, origin, outbound).await,
|
Ok(tls) => session(tls, engine, secret, origin, outbound).await,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue