upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/sync/naughty_list.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/sync/naughty_list.rs')
-rw-r--r--src/sync/naughty_list.rs74
1 files changed, 42 insertions, 32 deletions
diff --git a/src/sync/naughty_list.rs b/src/sync/naughty_list.rs
index 311b9bb..35fcc0f 100644
--- a/src/sync/naughty_list.rs
+++ b/src/sync/naughty_list.rs
@@ -1,8 +1,9 @@
1//! Naughty List Tracker for Relays with Persistent Infrastructure Issues 1//! Naughty List Tracker for Remote Servers with Persistent Infrastructure Issues
2//! 2//!
3//! This module tracks relays with persistent configuration/infrastructure problems 3//! This module tracks remote servers (Nostr relays and git remote domains) with
4//! (DNS failures, TLS certificate errors, protocol violations) separately from 4//! persistent configuration/infrastructure problems (DNS failures, TLS certificate
5//! transient network issues (timeouts, connection refused). 5//! errors, protocol violations) separately from transient network issues (timeouts,
6//! connection refused).
6//! 7//!
7//! ## Failure Classification 8//! ## Failure Classification
8//! 9//!
@@ -23,14 +24,14 @@
23use dashmap::DashMap; 24use dashmap::DashMap;
24use std::time::Instant; 25use std::time::Instant;
25 26
26/// Category of persistent relay failure that qualifies for the naughty list 27/// Category of persistent remote server failure that qualifies for the naughty list
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub enum NaughtyCategory { 29pub enum NaughtyCategory {
29 /// DNS lookup failures (domain doesn't resolve) 30 /// DNS lookup failures (domain doesn't resolve)
30 DnsLookupFailed, 31 DnsLookupFailed,
31 /// TLS certificate errors (expired, invalid, mismatch) 32 /// TLS certificate errors (expired, invalid, mismatch)
32 TlsCertificateInvalid, 33 TlsCertificateInvalid,
33 /// WebSocket or Nostr protocol violations 34 /// WebSocket or Nostr protocol violations (relay-specific, won't trigger for git)
34 ProtocolError, 35 ProtocolError,
35} 36}
36 37
@@ -51,7 +52,7 @@ impl std::fmt::Display for NaughtyCategory {
51 } 52 }
52} 53}
53 54
54/// Naughty list entry for a relay with persistent issues 55/// Naughty list entry for a remote server (relay URL or git domain) with persistent issues
55#[derive(Debug, Clone)] 56#[derive(Debug, Clone)]
56pub struct NaughtyEntry { 57pub struct NaughtyEntry {
57 /// Category of the persistent failure 58 /// Category of the persistent failure
@@ -66,15 +67,19 @@ pub struct NaughtyEntry {
66 pub occurrence_count: u32, 67 pub occurrence_count: u32,
67} 68}
68 69
69/// Tracks relays with persistent infrastructure/configuration issues 70/// Tracks remote servers with persistent infrastructure/configuration issues
71///
72/// Used for both:
73/// - Nostr relay URLs (e.g., "wss://relay.example.com")
74/// - Git remote domains (e.g., "git.example.com")
70/// 75///
71/// Separate from HealthTracker's backoff logic - this is specifically for 76/// Separate from HealthTracker's backoff logic - this is specifically for
72/// relays with configuration problems that are unlikely to be fixed quickly. 77/// servers with configuration problems that are unlikely to be fixed quickly.
73#[derive(Debug)] 78#[derive(Debug)]
74pub struct NaughtyListTracker { 79pub struct NaughtyListTracker {
75 /// Map of relay URL to naughty entry 80 /// Map of relay URL or git domain to naughty entry
76 entries: DashMap<String, NaughtyEntry>, 81 entries: DashMap<String, NaughtyEntry>,
77 /// How many hours before removing a relay from the naughty list 82 /// How many hours before removing a server from the naughty list
78 expiration_hours: u64, 83 expiration_hours: u64,
79} 84}
80 85
@@ -147,21 +152,26 @@ impl NaughtyListTracker {
147 None 152 None
148 } 153 }
149 154
150 /// Record a naughty relay (adds new entry or updates existing) 155 /// Record a naughty server (adds new entry or updates existing)
151 /// 156 ///
152 /// # Arguments 157 /// # Arguments
153 /// 158 ///
154 /// * `relay_url` - The relay URL 159 /// * `server_url_or_domain` - The relay URL or git domain
155 /// * `category` - The naughty category 160 /// * `category` - The naughty category
156 /// * `reason` - The full error message 161 /// * `reason` - The full error message
157 /// 162 ///
158 /// # Returns 163 /// # Returns
159 /// 164 ///
160 /// `true` if this is a new naughty entry (first occurrence), `false` if updating existing 165 /// `true` if this is a new naughty entry (first occurrence), `false` if updating existing
161 pub fn record(&self, relay_url: &str, category: NaughtyCategory, reason: String) -> bool { 166 pub fn record(
167 &self,
168 server_url_or_domain: &str,
169 category: NaughtyCategory,
170 reason: String,
171 ) -> bool {
162 let now = Instant::now(); 172 let now = Instant::now();
163 173
164 if let Some(mut entry) = self.entries.get_mut(relay_url) { 174 if let Some(mut entry) = self.entries.get_mut(server_url_or_domain) {
165 // Update existing entry 175 // Update existing entry
166 entry.last_seen = now; 176 entry.last_seen = now;
167 entry.occurrence_count = entry.occurrence_count.saturating_add(1); 177 entry.occurrence_count = entry.occurrence_count.saturating_add(1);
@@ -170,7 +180,7 @@ impl NaughtyListTracker {
170 } else { 180 } else {
171 // Create new entry 181 // Create new entry
172 self.entries.insert( 182 self.entries.insert(
173 relay_url.to_string(), 183 server_url_or_domain.to_string(),
174 NaughtyEntry { 184 NaughtyEntry {
175 category, 185 category,
176 reason, 186 reason,
@@ -183,17 +193,17 @@ impl NaughtyListTracker {
183 } 193 }
184 } 194 }
185 195
186 /// Check if a relay is on the naughty list (not expired) 196 /// Check if a server is on the naughty list (not expired)
187 /// 197 ///
188 /// # Arguments 198 /// # Arguments
189 /// 199 ///
190 /// * `relay_url` - The relay URL to check 200 /// * `server_url_or_domain` - The relay URL or git domain to check
191 /// 201 ///
192 /// # Returns 202 /// # Returns
193 /// 203 ///
194 /// `true` if the relay is currently on the naughty list 204 /// `true` if the server is currently on the naughty list
195 pub fn is_naughty(&self, relay_url: &str) -> bool { 205 pub fn is_naughty(&self, server_url_or_domain: &str) -> bool {
196 if let Some(entry) = self.entries.get(relay_url) { 206 if let Some(entry) = self.entries.get(server_url_or_domain) {
197 let age = Instant::now().duration_since(entry.first_seen); 207 let age = Instant::now().duration_since(entry.first_seen);
198 let expiration = std::time::Duration::from_secs(self.expiration_hours * 3600); 208 let expiration = std::time::Duration::from_secs(self.expiration_hours * 3600);
199 age < expiration 209 age < expiration
@@ -206,23 +216,23 @@ impl NaughtyListTracker {
206 /// 216 ///
207 /// # Arguments 217 /// # Arguments
208 /// 218 ///
209 /// * `relay_url` - The relay URL to look up 219 /// * `server_url_or_domain` - The relay URL or git domain to look up
210 /// 220 ///
211 /// # Returns 221 /// # Returns
212 /// 222 ///
213 /// A cloned `NaughtyEntry` if the relay is on the naughty list and not expired 223 /// A cloned `NaughtyEntry` if the server is on the naughty list and not expired
214 pub fn get_entry(&self, relay_url: &str) -> Option<NaughtyEntry> { 224 pub fn get_entry(&self, server_url_or_domain: &str) -> Option<NaughtyEntry> {
215 self.entries.get(relay_url).map(|e| e.clone()) 225 self.entries.get(server_url_or_domain).map(|e| e.clone())
216 } 226 }
217 227
218 /// Remove expired entries from the naughty list 228 /// Remove expired entries from the naughty list
219 /// 229 ///
220 /// Entries older than `expiration_hours` are removed to allow relays 230 /// Entries older than `expiration_hours` are removed to allow servers
221 /// to be retried after infrastructure issues are potentially fixed. 231 /// to be retried after infrastructure issues are potentially fixed.
222 /// 232 ///
223 /// # Returns 233 /// # Returns
224 /// 234 ///
225 /// Vector of relay URLs that were removed from the naughty list 235 /// Vector of server URLs/domains that were removed from the naughty list
226 pub fn expire_old_entries(&self) -> Vec<String> { 236 pub fn expire_old_entries(&self) -> Vec<String> {
227 let now = Instant::now(); 237 let now = Instant::now();
228 let expiration = std::time::Duration::from_secs(self.expiration_hours * 3600); 238 let expiration = std::time::Duration::from_secs(self.expiration_hours * 3600);
@@ -242,11 +252,11 @@ impl NaughtyListTracker {
242 expired 252 expired
243 } 253 }
244 254
245 /// Get all naughty relays (for metrics and monitoring) 255 /// Get all naughty servers (for metrics and monitoring)
246 /// 256 ///
247 /// # Returns 257 /// # Returns
248 /// 258 ///
249 /// Vector of (relay_url, entry) tuples for all relays currently on the naughty list 259 /// Vector of (server_url_or_domain, entry) tuples for all servers currently on the naughty list
250 pub fn get_all(&self) -> Vec<(String, NaughtyEntry)> { 260 pub fn get_all(&self) -> Vec<(String, NaughtyEntry)> {
251 self.entries 261 self.entries
252 .iter() 262 .iter()
@@ -254,7 +264,7 @@ impl NaughtyListTracker {
254 .collect() 264 .collect()
255 } 265 }
256 266
257 /// Get the count of relays in a specific category 267 /// Get the count of servers in a specific category
258 /// 268 ///
259 /// # Arguments 269 /// # Arguments
260 /// 270 ///
@@ -262,7 +272,7 @@ impl NaughtyListTracker {
262 /// 272 ///
263 /// # Returns 273 /// # Returns
264 /// 274 ///
265 /// Number of relays in the specified category 275 /// Number of servers in the specified category
266 pub fn count_by_category(&self, category: NaughtyCategory) -> usize { 276 pub fn count_by_category(&self, category: NaughtyCategory) -> usize {
267 self.entries 277 self.entries
268 .iter() 278 .iter()
@@ -270,7 +280,7 @@ impl NaughtyListTracker {
270 .count() 280 .count()
271 } 281 }
272 282
273 /// Get total number of relays on the naughty list 283 /// Get total number of servers on the naughty list
274 pub fn total_count(&self) -> usize { 284 pub fn total_count(&self) -> usize {
275 self.entries.len() 285 self.entries.len()
276 } 286 }