Repository navigation
node: size inbound limits from the connection budget and count refusals - #663
Merged
Merged
Conversation
Every ladder run on bananas had the data plane at a few percent: a
provider called by a fleet answered the first callers and then nothing,
and the callers saw QUIC CONNECTION_REFUSED or a TCP reset before TLS. The
node ran libp2p's default resource manager, which admits eight inbound
connections per source address and scales the rest from the memory the
process sees (126 inbound on the canary pod). The per-address refusal
happens before any trace event, so libp2p_rcmgr_blocked_resources stayed
at zero, and the QUIC transport takes the decision in its ConnContext
hook, before the handshake, which is the CONNECTION_REFUSED the callers
reported. Loopback is exempt, so no local run showed it. On a non-loopback
address, 60 callers from one host reached a provider 15 times.
The node now builds its resource manager the way the router does, moved to
internal/p2plimits for both: limits sized from the node's connection
budget (400 inbound, the connection manager's high watermark), a per-source
cap of a quarter of it, and every refusal counted in
agentmesh_node_inbound_connections_refused_total{reason} and logged once
per source per minute, with the cap in force exported as
agentmesh_node_conns_per_source_ip_limit. The same 300 callers from one
host now reach the provider up to the cap, and the rest are in the counter.
The router's limits followed the default watermark rather than the
configured one, and its connection cap sat at the watermark, so a router
refused connections before it counted itself full enough to shed members:
the rollout of a mesh at 10,000 members read 92,702 scope_limit refusals
and no go-away. The cap is now twice the configured watermark; trimming
and shedding work between the two.
The testnets deploy routers with --high-watermark from ROUTER_HIGH_WATERMARK,
6000 by default: with two routers per member, six routers hold 10,000
members with one router out. The router pod requests the memory a router
at that watermark uses.
Contributor
There was a problem hiding this comment.
Code Review
This pull request introduces a shared p2plimits package to centralize libp2p resource manager limits, per-IP connection caps, and refusal logging for both nodes and routers. It configures the node to use this resource manager, updates Prometheus metrics to track inbound connection refusals, and updates documentation and tests accordingly. The review feedback focuses on correctness and defensive programming, recommending safe defaults for ConnsPerSourceIP in the router configuration to prevent breaking functionality when unconfigured, as well as adding nil checks for the warnf logging function and the endpoint multiaddr in the p2plimits package to prevent potential nil pointer dereference panics.
Review on google#663. A per-source cap of zero would refuse every peer and is an error from NewResourceManager instead; the router and node apply their defaults before building it, so neither reaches it. The QUIC transport hands the manager no address when it could not build one, and that refusal is now attributed to an unknown source rather than dereferenced; a nil logger only counts.
This was referenced Oct 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every ladder run on bananas (runs 7–9, up to 10,000 resident members) had the data plane at a few percent: a provider called by a fleet answered the first callers and then nothing, and callers saw
CONNECTION_REFUSED (remote)over QUIC orconnection reset by peerbefore TLS.Root cause. The node ran libp2p's default resource manager: eight inbound connections per source address, the rest scaled from the memory the process sees (126 inbound on the canary pod). The per-address refusal happens before any trace event, so
libp2p_rcmgr_blocked_resourcesstayed at zero, and the QUIC transport takes the decision in itsConnContexthook before the handshake, which is theCONNECTION_REFUSEDthe callers reported. Loopback is exempt, so no local run showed it. Reproduced on a non-loopback address: 60 callers from one host reached a provider 15 times.Fix. The node builds its resource manager the way the router does, moved to
internal/p2plimitsfor both: limits sized from the node's connection budget (400 inbound, its connection manager's high watermark), a per-source cap of a quarter of it, every refusal counted inagentmesh_node_inbound_connections_refused_total{reason}and logged once per source per minute, and the cap exported asagentmesh_node_conns_per_source_ip_limit. The same 300 callers from one host now reach the provider up to the cap, and the rest show in the counter (per_ip_limit575,rate_limit4034).Router. Its limits followed the default watermark rather than the configured one, and its connection cap sat at the watermark, so a router refused connections before it counted itself full enough to shed: the rollout of the 10k mesh read 92,702
scope_limitrefusals and no go-away. The cap is now twice the configured watermark.Deploy. Routers get
--high-watermarkfromROUTER_HIGH_WATERMARK, 6000 by default: with two routers per member, six routers hold 10,000 members with one router out for a rollout. The router pod requests the memory a router at that watermark uses (1 GiB, 500m CPU).Tests:
p2plimits(refusal classification, log rate limiting, a live per-IP cap over the resource manager),TestNodeLimitsFollowTheConnectionBudget,TestHubLimitsFollowTheConnectionBudgetupdated for the configured watermark and headroom, node metrics test asserts the new series. Lint, unit, integration and generated-file checks pass.