diff options
Diffstat (limited to 'src/sync/naughty_list.rs')
| -rw-r--r-- | src/sync/naughty_list.rs | 74 |
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 @@ | |||
| 23 | use dashmap::DashMap; | 24 | use dashmap::DashMap; |
| 24 | use std::time::Instant; | 25 | use 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)] |
| 28 | pub enum NaughtyCategory { | 29 | pub 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)] |
| 56 | pub struct NaughtyEntry { | 57 | pub 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)] |
| 74 | pub struct NaughtyListTracker { | 79 | pub 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 | } |