diff --git a/protocols/gossipsub/src/behaviour.rs b/protocols/gossipsub/src/behaviour.rs index d26125671b8..2c5083ceb1d 100644 --- a/protocols/gossipsub/src/behaviour.rs +++ b/protocols/gossipsub/src/behaviour.rs @@ -386,6 +386,17 @@ where { /// Creates a Gossipsub [`Behaviour`] struct given a set of parameters specified via a /// [`Config`]. This has no subscription filter and uses no compression. + /// + /// The default subscription filter wired in by `F::default()` is + /// [`AllowAllSubscriptionFilter`], which imposes no cap on per-RPC or per-peer subscription + /// growth. For deployments that accept connections from untrusted peers, prefer + /// [`Behaviour::new_with_subscription_filter_and_transform`] with + /// [`MaxCountSubscriptionFilter`] or [`WhitelistSubscriptionFilter`]. See the crate root + /// docs for the "Production hardening" section. + /// + /// [`AllowAllSubscriptionFilter`]: crate::subscription_filter::AllowAllSubscriptionFilter + /// [`MaxCountSubscriptionFilter`]: crate::subscription_filter::MaxCountSubscriptionFilter + /// [`WhitelistSubscriptionFilter`]: crate::subscription_filter::WhitelistSubscriptionFilter pub fn new(privacy: MessageAuthenticity, config: Config) -> Result { Self::new_with_subscription_filter_and_transform( privacy, diff --git a/protocols/gossipsub/src/lib.rs b/protocols/gossipsub/src/lib.rs index 36608e5f289..84e837904df 100644 --- a/protocols/gossipsub/src/lib.rs +++ b/protocols/gossipsub/src/lib.rs @@ -37,6 +37,52 @@ //! metadata. Peers use control messages to broadcast and request known messages and //! subscribe/unsubscribe from topics in the mesh network. //! +//! # Production hardening +//! +//! [`Behaviour::new`] uses the default [`subscription_filter::AllowAllSubscriptionFilter`], +//! which imposes no upper bound on the number of subscription entries decoded per inbound +//! RPC frame or on the number of topics a single peer can accumulate in its per-peer set. +//! This is appropriate for development, integration tests, and closed networks where every +//! connected peer is trusted, but it should not be used for deployments that accept +//! connections from untrusted peers: a single attacker peer can repeatedly subscribe to +//! unique topics and drive the receiver's gossipsub state to grow linearly for the +//! lifetime of its connection. +//! +//! Production deployments should construct [`Behaviour`] with an explicit +//! [`subscription_filter::TopicSubscriptionFilter`] via +//! [`Behaviour::new_with_subscription_filter_and_transform`] (or the +//! `_with_subscription_filter` variant). Two implementations are provided: +//! +//! - [`subscription_filter::WhitelistSubscriptionFilter`] — accepts only a fixed, pre-declared +//! topic set. Recommended when the full topic set is known at construction time (for example, +//! Ethereum 2 consensus clients enumerate the `/eth2///ssz_snappy` strings +//! before peering). +//! - [`subscription_filter::MaxCountSubscriptionFilter`] — wraps an inner filter with per-RPC and +//! per-peer accumulation caps. Recommended when the topic set is open-ended; choose the cap based +//! on the legitimate topic fan-out of the application plus a comfort margin. +//! +//! A minimal hardened constructor: +//! +//! ```rust,ignore +//! use libp2p::gossipsub::{ +//! AllowAllSubscriptionFilter, Behaviour, ConfigBuilder, IdentityTransform, +//! MaxCountSubscriptionFilter, MessageAuthenticity, +//! }; +//! +//! let filter = MaxCountSubscriptionFilter { +//! filter: AllowAllSubscriptionFilter::default(), +//! max_subscribed_topics: 16_384, +//! max_subscriptions_per_request: 500, +//! }; +//! let behaviour: Behaviour> = +//! Behaviour::new_with_subscription_filter_and_transform( +//! MessageAuthenticity::Signed(local_key), +//! ConfigBuilder::default().build()?, +//! filter, +//! IdentityTransform, +//! )?; +//! ``` +//! //! # Important Discrepancies //! //! This section outlines the current implementation's potential discrepancies from that of other diff --git a/protocols/gossipsub/src/subscription_filter.rs b/protocols/gossipsub/src/subscription_filter.rs index 2e8944758d8..72d268160f3 100644 --- a/protocols/gossipsub/src/subscription_filter.rs +++ b/protocols/gossipsub/src/subscription_filter.rs @@ -84,7 +84,31 @@ pub trait TopicSubscriptionFilter { // some useful implementers -/// Allows all subscriptions +/// A subscription filter that imposes **no policy**: every topic a peer announces is accepted +/// and recorded in the per-peer subscribed-topics set. +/// +/// This is the default filter used by [`crate::Behaviour::new`]. +/// +/// # When to use +/// +/// Suitable for closed networks, integration tests, local development, and deployments where +/// every connected peer is fully trusted. The filter performs no validation and no accounting, +/// which keeps it light-weight for those settings. +/// +/// # Production deployments +/// +/// In a deployment that accepts connections from untrusted peers, `AllowAllSubscriptionFilter` +/// places no upper bound on: +/// +/// * the number of subscription entries decoded per inbound RPC frame, or +/// * the number of topics a single peer can accumulate in its per-peer subscribed-topics set. +/// +/// Because each accepted topic costs a heap allocation in the receiver's bookkeeping, a peer +/// that repeatedly subscribes to unique topics can drive the gossipsub state to grow linearly +/// for the lifetime of its connection. Prefer [`MaxCountSubscriptionFilter`] (or +/// [`WhitelistSubscriptionFilter`] if the topic set is known up-front) and wire it through +/// [`crate::Behaviour::new_with_subscription_filter_and_transform`]. See the +/// "Production hardening" section in the crate root for a worked example. #[derive(Default, Clone)] pub struct AllowAllSubscriptionFilter {} @@ -94,7 +118,12 @@ impl TopicSubscriptionFilter for AllowAllSubscriptionFilter { } } -/// Allows only whitelisted subscriptions +/// Accepts subscriptions only for a fixed, pre-declared topic set. +/// +/// Recommended for deployments where the full set of valid topics is known at construction time +/// (for example, Ethereum 2 consensus clients enumerate `/eth2///ssz_snappy` +/// strings before peering). Any inbound subscription for a topic outside the whitelist is +/// dropped before it is recorded against the peer. #[derive(Default, Clone)] pub struct WhitelistSubscriptionFilter(pub HashSet); @@ -104,7 +133,29 @@ impl TopicSubscriptionFilter for WhitelistSubscriptionFilter { } } -/// Adds a max count to a given subscription filter +/// Wraps another [`TopicSubscriptionFilter`] with per-RPC and per-peer accumulation caps. +/// +/// `max_subscriptions_per_request` bounds the number of `Subscribe` / `Unsubscribe` actions +/// that may be applied per inbound RPC frame; `max_subscribed_topics` bounds the cardinality of +/// the per-peer subscribed-topics set after the inner filter has run. Subscriptions that would +/// breach either bound are rejected before the peer's bookkeeping is updated. +/// +/// This is the recommended default for production deployments that accept connections from +/// untrusted peers. A common pattern is: +/// +/// ```rust,ignore +/// use libp2p::gossipsub::{AllowAllSubscriptionFilter, MaxCountSubscriptionFilter}; +/// +/// let filter = MaxCountSubscriptionFilter { +/// filter: AllowAllSubscriptionFilter::default(), +/// max_subscribed_topics: 16_384, +/// max_subscriptions_per_request: 500, +/// }; +/// ``` +/// +/// Choose `max_subscribed_topics` to accommodate the legitimate topic fan-out of your +/// application plus a comfort margin (the value above is a generic upper bound; consensus +/// clients with a known topic count typically use a much smaller cap). pub struct MaxCountSubscriptionFilter { pub filter: T, pub max_subscribed_topics: usize,