Skip to content

Commit d141092

Browse files
authored
fix(network): canonicalize inbound peer addresses at the accept boundary (#11129)
2 parents 67eac5c + a9cfdd8 commit d141092

5 files changed

Lines changed: 409 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ and this project adheres to [Semantic Versioning](https://semver.org).
3434
enforcing the cap on the total monetary base
3535
([#10817](https://github.com/ZcashFoundation/zebra/pull/10817))
3636

37+
### Security
38+
39+
- Inbound connections are canonicalized when they are accepted, so an IPv4 peer that connects to a
40+
dual-stack listener as an IPv4-mapped IPv6 address (`::ffff:A.B.C.D`) is keyed on its canonical
41+
IPv4 address. Previously the mapped address became the peer set key, so a ban issued for that
42+
peer's IPv4 address did not disconnect it while it stayed connected, and the same peer counted
43+
twice towards the per-IP inbound connection limit
44+
([#10695](https://github.com/ZcashFoundation/zebra/issues/10695)).
45+
3746
## [Zebra 6.2.3](https://github.com/ZcashFoundation/zebra/releases/tag/v6.2.3) - 2026-07-27
3847

3948
This is an optional release with network hardenings for operators that experience issues with their nodes peer set connectivity or otherwise want to be proactive about avoiding such issues.

zebra-network/CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
([#11096](https://github.com/ZcashFoundation/zebra/pull/11096)).
1515
- Connection-attempt, terminal-outcome, and remote-version metrics with bounded network,
1616
direction, address-family, lifecycle-stage, outcome, and implementation labels.
17+
- `constants::MISBEHAVIOR_FLUSH_INTERVAL`, the interval between flushes of batched peer
18+
misbehaviour updates into the address book. This was previously an unnamed literal in
19+
`init_with_block_gossip_peer_ips()`; the value is unchanged outside this crate's tests
20+
([#11129](https://github.com/ZcashFoundation/zebra/pull/11129)).
1721

1822
### Changed
1923

2024
- Peer-set, crawler-handshake, and address-book gauges now include a `network` label, so multiple
2125
network instances in one process do not overwrite each other's values.
2226

27+
### Security
28+
29+
- Inbound connections are canonicalized at the accept boundary, so an IPv4 peer that connects to a
30+
dual-stack listener as an IPv4-mapped IPv6 address (`::ffff:A.B.C.D`) is keyed on its canonical
31+
IPv4 address. Previously the mapped address became the peer set key, so a ban issued for that
32+
peer's IPv4 address did not disconnect it while it stayed connected, and the same peer counted
33+
twice towards the per-IP inbound connection limit
34+
([#10695](https://github.com/ZcashFoundation/zebra/issues/10695)).
35+
2336
## [11.0.0] - 2026-07-27
2437

2538
### Breaking Changes

zebra-network/src/constants.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,20 @@ pub const MIN_PEER_SET_LOG_INTERVAL: Duration = Duration::from_secs(60);
401401
/// disconnected and banned.
402402
pub const MAX_PEER_MISBEHAVIOR_SCORE: u32 = 100;
403403

404+
/// The interval between flushes of batched peer misbehaviour updates into the address book.
405+
///
406+
/// Misbehaviour updates are batched so peers can't keep the address book mutex locked by
407+
/// repeatedly sending invalid blocks or transactions.
408+
#[cfg(not(test))]
409+
pub const MISBEHAVIOR_FLUSH_INTERVAL: Duration = Duration::from_secs(30);
410+
411+
/// The interval between flushes of batched peer misbehaviour updates into the address book.
412+
///
413+
/// Tests use a much shorter interval, so that tests which wait for a misbehaviour update to
414+
/// turn into a ban don't have to wait for a production flush cycle.
415+
#[cfg(test)]
416+
pub const MISBEHAVIOR_FLUSH_INTERVAL: Duration = Duration::from_millis(100);
417+
404418
/// The maximum number of banned IP addresses to be stored in-memory at any time.
405419
pub const MAX_BANNED_IPS: usize = 20_000;
406420

zebra-network/src/peer_set/initialize.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ use crate::{
4848
},
4949
peer_cache_updater::peer_cache_updater,
5050
peer_set::{set::MorePeers, ActiveConnectionCounter, CandidateSet, ConnectionTracker, PeerSet},
51-
protocol::external::canonical_socket_addr,
51+
protocol::external::{canonical_peer_addr, canonical_socket_addr},
5252
AddressBook, BoxError, Config, PeerSocketAddr, Request, Response,
5353
};
5454

@@ -172,7 +172,7 @@ where
172172
// Batch misbehaviour updates so peers can't keep the address book mutex locked
173173
// by repeatedly sending invalid blocks or transactions.
174174
let mut flush_timer =
175-
IntervalStream::new(tokio::time::interval(Duration::from_secs(30)));
175+
IntervalStream::new(tokio::time::interval(constants::MISBEHAVIOR_FLUSH_INTERVAL));
176176

177177
loop {
178178
tokio::select! {
@@ -711,7 +711,14 @@ where
711711
};
712712

713713
if let Ok((tcp_stream, addr)) = inbound_result {
714-
let addr: PeerSocketAddr = addr.into();
714+
// # Security
715+
//
716+
// Canonicalize the accepted address before it is used as a key. On a
717+
// dual-stack listener an IPv4 peer connects as IPv4-mapped IPv6, but
718+
// bans and misbehaviour updates are keyed on the canonical IPv4 address.
719+
// This address also becomes the peer set key and the per-IP limiter key
720+
// below, so canonicalizing once here keeps all of them in the same form.
721+
let addr: PeerSocketAddr = canonical_peer_addr(addr);
715722
record_connection_attempt_started(&config.network, ConnectionDirection::Inbound, addr);
716723

717724
// # Security

0 commit comments

Comments
 (0)