|
1 | 1 | use crate::PeerId; |
2 | 2 | use crate::dht::core_engine::{DhtCoreEngine, NodeCapacity, NodeInfo}; |
| 3 | +use crate::security::IPDiversityConfig; |
3 | 4 | use std::time::SystemTime; |
4 | 5 |
|
5 | 6 | #[tokio::test] |
@@ -241,3 +242,181 @@ async fn test_geographic_diversity_counts_region_nodes() -> anyhow::Result<()> { |
241 | 242 | // All should succeed since geographic limit is 50 and we're only adding 3 |
242 | 243 | Ok(()) |
243 | 244 | } |
| 245 | + |
| 246 | +#[tokio::test] |
| 247 | +async fn test_ipv4_floor_override_raises_limit() -> anyhow::Result<()> { |
| 248 | + // Default dynamic limit for a small network is 1 per /32. |
| 249 | + // Setting ipv4_limit_floor = 3 should allow 3 nodes on the same IP. |
| 250 | + let mut engine = DhtCoreEngine::new_for_tests(PeerId::random())?; |
| 251 | + engine.set_ip_diversity_config(IPDiversityConfig { |
| 252 | + ipv4_limit_floor: Some(3), |
| 253 | + ..IPDiversityConfig::default() |
| 254 | + }); |
| 255 | + |
| 256 | + for i in 0..3u8 { |
| 257 | + let node = NodeInfo { |
| 258 | + id: PeerId::random(), |
| 259 | + address: format!("/ip4/192.168.1.1/udp/{}/quic", 9000 + u16::from(i)) |
| 260 | + .parse() |
| 261 | + .unwrap(), |
| 262 | + last_seen: SystemTime::now(), |
| 263 | + capacity: NodeCapacity::default(), |
| 264 | + }; |
| 265 | + engine.add_node(node).await?; |
| 266 | + } |
| 267 | + |
| 268 | + // Fourth node should fail (floor is 3, so limit is 3) |
| 269 | + let node4 = NodeInfo { |
| 270 | + id: PeerId::random(), |
| 271 | + address: "/ip4/192.168.1.1/udp/9003/quic".parse().unwrap(), |
| 272 | + last_seen: SystemTime::now(), |
| 273 | + capacity: NodeCapacity::default(), |
| 274 | + }; |
| 275 | + let result = engine.add_node(node4).await; |
| 276 | + assert!(result.is_err()); |
| 277 | + assert!(result.unwrap_err().to_string().contains("IP diversity:")); |
| 278 | + |
| 279 | + Ok(()) |
| 280 | +} |
| 281 | + |
| 282 | +#[tokio::test] |
| 283 | +async fn test_ipv4_ceiling_override_lowers_limit() -> anyhow::Result<()> { |
| 284 | + // With a large network, dynamic per-IP would be high. |
| 285 | + // Setting ipv4_limit_ceiling = 1 should cap all subnet limits at 1. |
| 286 | + let mut engine = DhtCoreEngine::new_for_tests(PeerId::random())?; |
| 287 | + engine.set_ip_diversity_config(IPDiversityConfig { |
| 288 | + ipv4_limit_ceiling: Some(1), |
| 289 | + // Raise the dynamic limit so ceiling actually constrains it |
| 290 | + max_per_ip_cap: 100, |
| 291 | + max_network_fraction: 1.0, |
| 292 | + ..IPDiversityConfig::default() |
| 293 | + }); |
| 294 | + |
| 295 | + // First node on 10.0.1.1 |
| 296 | + let node1 = NodeInfo { |
| 297 | + id: PeerId::random(), |
| 298 | + address: "/ip4/10.0.1.1/udp/9000/quic".parse().unwrap(), |
| 299 | + last_seen: SystemTime::now(), |
| 300 | + capacity: NodeCapacity::default(), |
| 301 | + }; |
| 302 | + engine.add_node(node1).await?; |
| 303 | + |
| 304 | + // Second node on different IP but same /24 — should fail because ceiling=1 |
| 305 | + let node2 = NodeInfo { |
| 306 | + id: PeerId::random(), |
| 307 | + address: "/ip4/10.0.1.2/udp/9000/quic".parse().unwrap(), |
| 308 | + last_seen: SystemTime::now(), |
| 309 | + capacity: NodeCapacity::default(), |
| 310 | + }; |
| 311 | + let result = engine.add_node(node2).await; |
| 312 | + assert!(result.is_err()); |
| 313 | + assert!(result.unwrap_err().to_string().contains("IP diversity:")); |
| 314 | + |
| 315 | + Ok(()) |
| 316 | +} |
| 317 | + |
| 318 | +#[tokio::test] |
| 319 | +async fn test_ipv6_floor_override_raises_limit() -> anyhow::Result<()> { |
| 320 | + // Default IPv6 /64 limit is 1. Setting ipv6_limit_floor = 5 should allow |
| 321 | + // 5 nodes in the same /64 subnet. |
| 322 | + let mut engine = DhtCoreEngine::new_for_tests(PeerId::random())?; |
| 323 | + engine.set_ip_diversity_config(IPDiversityConfig { |
| 324 | + ipv6_limit_floor: Some(5), |
| 325 | + ..IPDiversityConfig::default() |
| 326 | + }); |
| 327 | + |
| 328 | + for i in 1..=5u128 { |
| 329 | + let node = NodeInfo { |
| 330 | + id: PeerId::random(), |
| 331 | + address: format!("/ip6/2001:db8::{i}/udp/9000/quic").parse().unwrap(), |
| 332 | + last_seen: SystemTime::now(), |
| 333 | + capacity: NodeCapacity::default(), |
| 334 | + }; |
| 335 | + engine.add_node(node).await?; |
| 336 | + } |
| 337 | + |
| 338 | + // Sixth node should fail |
| 339 | + let node6 = NodeInfo { |
| 340 | + id: PeerId::random(), |
| 341 | + address: "/ip6/2001:db8::6/udp/9000/quic".parse().unwrap(), |
| 342 | + last_seen: SystemTime::now(), |
| 343 | + capacity: NodeCapacity::default(), |
| 344 | + }; |
| 345 | + let result = engine.add_node(node6).await; |
| 346 | + assert!(result.is_err()); |
| 347 | + assert!(result.unwrap_err().to_string().contains("IP diversity:")); |
| 348 | + |
| 349 | + Ok(()) |
| 350 | +} |
| 351 | + |
| 352 | +#[tokio::test] |
| 353 | +async fn test_ipv6_ceiling_override_lowers_limit() -> anyhow::Result<()> { |
| 354 | + // With permissive IPv6 config (max_nodes_per_ipv6_64 = usize::MAX), |
| 355 | + // setting ipv6_limit_ceiling = 2 should cap at 2. |
| 356 | + let mut engine = DhtCoreEngine::new_for_tests(PeerId::random())?; |
| 357 | + engine.set_ip_diversity_config(IPDiversityConfig { |
| 358 | + max_nodes_per_ipv6_64: usize::MAX, |
| 359 | + max_nodes_per_ipv6_48: usize::MAX, |
| 360 | + max_nodes_per_ipv6_32: usize::MAX, |
| 361 | + max_per_ip_cap: 100, |
| 362 | + max_network_fraction: 1.0, |
| 363 | + ipv6_limit_ceiling: Some(2), |
| 364 | + ..IPDiversityConfig::default() |
| 365 | + }); |
| 366 | + |
| 367 | + let node1 = NodeInfo { |
| 368 | + id: PeerId::random(), |
| 369 | + address: "/ip6/2001:db8::1/udp/9000/quic".parse().unwrap(), |
| 370 | + last_seen: SystemTime::now(), |
| 371 | + capacity: NodeCapacity::default(), |
| 372 | + }; |
| 373 | + engine.add_node(node1).await?; |
| 374 | + |
| 375 | + let node2 = NodeInfo { |
| 376 | + id: PeerId::random(), |
| 377 | + address: "/ip6/2001:db8::2/udp/9000/quic".parse().unwrap(), |
| 378 | + last_seen: SystemTime::now(), |
| 379 | + capacity: NodeCapacity::default(), |
| 380 | + }; |
| 381 | + engine.add_node(node2).await?; |
| 382 | + |
| 383 | + // Third should fail due to ceiling |
| 384 | + let node3 = NodeInfo { |
| 385 | + id: PeerId::random(), |
| 386 | + address: "/ip6/2001:db8::3/udp/9000/quic".parse().unwrap(), |
| 387 | + last_seen: SystemTime::now(), |
| 388 | + capacity: NodeCapacity::default(), |
| 389 | + }; |
| 390 | + let result = engine.add_node(node3).await; |
| 391 | + assert!(result.is_err()); |
| 392 | + assert!(result.unwrap_err().to_string().contains("IP diversity:")); |
| 393 | + |
| 394 | + Ok(()) |
| 395 | +} |
| 396 | + |
| 397 | +#[tokio::test] |
| 398 | +async fn test_no_override_preserves_dynamic_behavior() -> anyhow::Result<()> { |
| 399 | + // When no overrides are set, behavior should be identical to before. |
| 400 | + // Default dynamic limit for small network = 1 per /32, 3 per /24. |
| 401 | + let mut engine = DhtCoreEngine::new_for_tests(PeerId::random())?; |
| 402 | + |
| 403 | + let node1 = NodeInfo { |
| 404 | + id: PeerId::random(), |
| 405 | + address: "/ip4/192.168.1.1/udp/9000/quic".parse().unwrap(), |
| 406 | + last_seen: SystemTime::now(), |
| 407 | + capacity: NodeCapacity::default(), |
| 408 | + }; |
| 409 | + engine.add_node(node1).await?; |
| 410 | + |
| 411 | + // Same IP should fail (dynamic limit = 1) |
| 412 | + let node2 = NodeInfo { |
| 413 | + id: PeerId::random(), |
| 414 | + address: "/ip4/192.168.1.1/udp/9001/quic".parse().unwrap(), |
| 415 | + last_seen: SystemTime::now(), |
| 416 | + capacity: NodeCapacity::default(), |
| 417 | + }; |
| 418 | + let result = engine.add_node(node2).await; |
| 419 | + assert!(result.is_err()); |
| 420 | + |
| 421 | + Ok(()) |
| 422 | +} |
0 commit comments