From 1fa14ef529f20e08c29af12d0b11f26a7ca3a5fe Mon Sep 17 00:00:00 2001 From: Adrian Duke Date: Fri, 13 Feb 2026 17:51:33 +0000 Subject: [PATCH] protocol/service: Use block list for connections The service learns to consult the policies database to check if a `NodeId` is blocked. When a node is blocked the service will prevent any inbound or outbound connection. In the case of an inbound connection, a disconnect must be submitted to the reactor. In the case of an outbound connection, nodes are filtered out from any connection lists, and prevented from submitting a connect to the reactor. --- CHANGELOG.md | 3 +++ crates/radicle-protocol/src/service.rs | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb6e62ec..b68edd5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## New Features +- The block policy for `NodeId`'s is used for limiting the namespaces fetched + from other nodes. It is now also extended to block connections to the blocked + `NodeId`. - The set of references returned by `references_of` were restricted to `heads`, `tags`, `notes`, `rad`, and `cobs`. The restriction is lifted, and the only references filtered out are `refs/tmp/heads` – used by `radicle-remote-helper` diff --git a/crates/radicle-protocol/src/service.rs b/crates/radicle-protocol/src/service.rs index 2e2a86ff..55813376 100644 --- a/crates/radicle-protocol/src/service.rs +++ b/crates/radicle-protocol/src/service.rs @@ -230,6 +230,8 @@ pub enum ConnectError { "attempted connection to {nid}, via {addr} but addresses of this kind are not supported" )] UnsupportedAddress { nid: NodeId, addr: Address }, + #[error("attempted connection with blocked peer {nid}")] + Blocked { nid: NodeId }, } /// A store for all node data. @@ -1219,6 +1221,16 @@ where } pub fn connected(&mut self, remote: NodeId, addr: Address, link: Link) { + if let Ok(true) = self.policies.is_blocked(&remote) { + self.emitter.emit(Event::PeerDisconnected { + nid: remote, + reason: format!("{remote} is blocked"), + }); + info!(target: "service", "Disconnecting blocked inbound peer {remote}"); + self.outbox.disconnect(remote, DisconnectReason::Policy); + return; + } + info!(target: "service", "Connected to {remote} ({addr}) ({link:?})"); self.emitter.emit(Event::PeerConnected { nid: remote }); @@ -2164,6 +2176,9 @@ where if nid == self.node_id() { return Err(ConnectError::SelfConnection); } + if let Ok(true) = self.policies.is_blocked(&nid) { + return Err(ConnectError::Blocked { nid }); + } if !self.is_supported_address(&addr) { return Err(ConnectError::UnsupportedAddress { nid, addr }); } @@ -2397,6 +2412,7 @@ where .filter(|entry| !entry.address.banned) .filter(|entry| !entry.penalty.is_connect_threshold_reached()) .filter(|entry| !self.sessions.contains_key(&entry.node)) + .filter(|entry| !self.policies.is_blocked(&entry.node).unwrap_or(false)) .filter(|entry| !self.config.external_addresses.contains(&entry.address.addr)) .filter(|entry| &entry.node != self.nid()) .filter(|entry| self.is_supported_address(&entry.address.addr)) @@ -2564,6 +2580,9 @@ where for (nid, session) in self.sessions.iter_mut() { if self.config.is_persistent(nid) { + if self.policies.is_blocked(nid).unwrap_or(false) { + continue; + } if let session::State::Disconnected { retry_at, .. } = &mut session.state { // TODO: Try to reconnect only if the peer was attempted. A disconnect without // even a successful attempt means that we're unlikely to be able to reconnect.