ant-quic 0.27.47

QUIC transport protocol with advanced NAT traversal for P2P networks
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
// Copyright 2024 Saorsa Labs Ltd.
//
// This Saorsa Network Software is licensed under the General Public License (GPL), version 3.
// Please see the file LICENSE-GPL, or visit <http://www.gnu.org/licenses/> for the full text.
//
// Full details available at https://saorsalabs.com/licenses

//! Security regression tests for ant-quic
//!
//! v0.13.0+: Updated for symmetric P2P node architecture - no roles.
//! Tests for specific security improvements made in recent commits to ensure
//! they don't regress and that the system handles security-sensitive scenarios safely.

#![allow(clippy::unwrap_used, clippy::expect_used)]

use ant_quic::nat_traversal_api::{NatTraversalConfig, NatTraversalEndpoint, NatTraversalError};
use std::{
    net::{IpAddr, Ipv4Addr, SocketAddr},
    sync::Arc,
    time::Duration,
};

/// Helper to create a basic peer config for testing
/// v0.13.0+: No role - all nodes are symmetric P2P nodes
fn test_peer_config() -> NatTraversalConfig {
    NatTraversalConfig {
        known_peers: vec!["127.0.0.1:9000".parse().unwrap()],
        max_candidates: 10,
        coordination_timeout: Duration::from_secs(5),
        enable_symmetric_nat: true,
        enable_relay_fallback: false,
        max_concurrent_attempts: 5,
        bind_addr: None, // Let system choose - tests random port functionality
        prefer_rfc_nat_traversal: true,
        pqc: None,
        timeouts: Default::default(),
        identity_key: None,
        relay_nodes: vec![],
        enable_relay_service: true,
        allow_ipv4_mapped: true,
        transport_registry: None,
        max_message_size: ant_quic::P2pConfig::DEFAULT_MAX_MESSAGE_SIZE,
        max_concurrent_uni_streams: 100,
        additional_bind_addrs: Vec::new(),
    }
}

/// Helper to create a server config with bind address
/// v0.13.0+: No role - all nodes are symmetric P2P nodes
fn test_server_config() -> NatTraversalConfig {
    NatTraversalConfig {
        known_peers: vec![],
        max_candidates: 20,
        coordination_timeout: Duration::from_secs(10),
        enable_symmetric_nat: true,
        enable_relay_fallback: false,
        max_concurrent_attempts: 10,
        bind_addr: Some("127.0.0.1:0".parse().unwrap()),
        prefer_rfc_nat_traversal: true,
        pqc: None,
        timeouts: Default::default(),
        identity_key: None,
        relay_nodes: vec![],
        enable_relay_service: true,
        allow_ipv4_mapped: true,
        transport_registry: None,
        max_message_size: ant_quic::P2pConfig::DEFAULT_MAX_MESSAGE_SIZE,
        max_concurrent_uni_streams: 100,
        additional_bind_addrs: Vec::new(),
    }
}

fn assert_endpoint_bound(endpoint: &NatTraversalEndpoint, expected_ip: IpAddr) -> SocketAddr {
    let quic_ep = endpoint
        .get_endpoint()
        .expect("endpoint should expose inner QUIC endpoint");
    let addr = quic_ep
        .local_addr()
        .expect("endpoint should have a local socket address");

    assert_ne!(addr.port(), 0, "endpoint should bind a non-zero port");
    assert_eq!(addr.ip(), expected_ip, "endpoint should bind expected IP");

    addr
}

fn is_udp_bind_blocked(error: &NatTraversalError) -> bool {
    matches!(
        error,
        NatTraversalError::NetworkError(message)
            if message.contains("Failed to bind UDP socket")
                && (message.contains("Operation not permitted")
                    || message.contains("Permission denied"))
    )
}

fn endpoint_or_skip_udp_blocked(
    result: Result<NatTraversalEndpoint, NatTraversalError>,
    context: &str,
) -> Option<NatTraversalEndpoint> {
    match result {
        Ok(endpoint) => Some(endpoint),
        Err(error) => {
            assert!(
                is_udp_bind_blocked(&error),
                "{context} failed before endpoint construction: {error}"
            );
            println!("Skipping {context}: UDP bind blocked by test environment: {error}");
            None
        }
    }
}

fn assert_config_error_contains(
    result: &Result<NatTraversalEndpoint, NatTraversalError>,
    expected_fragment: &str,
) {
    assert!(
        matches!(
            result,
            Err(NatTraversalError::ConfigError(message)) if message.contains(expected_fragment)
        ),
        "invalid config should fail with ConfigError containing {expected_fragment}"
    );
}

/// Test that endpoint creation with None bind_addr doesn't panic
/// Regression test for commit 6e633cd9 - protocol obfuscation improvements
#[tokio::test]
async fn test_random_port_binding_no_panic() {
    // This tests the create_random_port_bind_addr() function indirectly
    // by ensuring None bind_addr is handled safely

    let config = test_peer_config(); // bind_addr is None

    if let Some(endpoint) = endpoint_or_skip_udp_blocked(
        NatTraversalEndpoint::new(config, None, None).await,
        "random port binding",
    ) {
        let addr = assert_endpoint_bound(&endpoint, IpAddr::V4(Ipv4Addr::UNSPECIFIED));
        println!("✓ Random port binding succeeded: {addr}");
    }
}

/// Test that error conditions don't cause panics
/// Regression test for commit a7d1de11 - robust error handling
#[tokio::test]
async fn test_error_handling_no_panic() {
    // Test various potentially problematic configurations

    // Test 1: Zero timeouts
    let config1 = NatTraversalConfig {
        known_peers: vec!["127.0.0.1:9000".parse().unwrap()],
        max_candidates: 10,
        coordination_timeout: Duration::from_secs(0), // Zero timeout
        enable_symmetric_nat: true,
        enable_relay_fallback: false,
        max_concurrent_attempts: 5,
        bind_addr: Some("127.0.0.1:0".parse().unwrap()),
        prefer_rfc_nat_traversal: true,
        pqc: None,
        timeouts: Default::default(),
        identity_key: None,
        relay_nodes: vec![],
        enable_relay_service: true,
        allow_ipv4_mapped: true,
        transport_registry: None,
        max_message_size: ant_quic::P2pConfig::DEFAULT_MAX_MESSAGE_SIZE,
        max_concurrent_uni_streams: 100,
        additional_bind_addrs: Vec::new(),
    };

    let result1 = NatTraversalEndpoint::new(config1, None, None).await;
    assert_config_error_contains(&result1, "coordination_timeout");

    // Test 2: Zero max candidates
    let config2 = NatTraversalConfig {
        known_peers: vec!["127.0.0.1:9000".parse().unwrap()],
        max_candidates: 0, // Zero candidates
        coordination_timeout: Duration::from_secs(10),
        enable_symmetric_nat: true,
        enable_relay_fallback: false,
        max_concurrent_attempts: 5,
        bind_addr: Some("127.0.0.1:0".parse().unwrap()),
        prefer_rfc_nat_traversal: true,
        pqc: None,
        timeouts: Default::default(),
        identity_key: None,
        relay_nodes: vec![],
        enable_relay_service: true,
        allow_ipv4_mapped: true,
        transport_registry: None,
        max_message_size: ant_quic::P2pConfig::DEFAULT_MAX_MESSAGE_SIZE,
        max_concurrent_uni_streams: 100,
        additional_bind_addrs: Vec::new(),
    };

    let result2 = NatTraversalEndpoint::new(config2, None, None).await;
    assert_config_error_contains(&result2, "max_candidates");
}

/// Test concurrent endpoint creation doesn't cause race conditions
/// Related to mutex safety improvements
#[tokio::test]
async fn test_concurrent_creation_safety() {
    const NUM_CONCURRENT: usize = 10;

    // Create many endpoints concurrently
    let handles: Vec<_> = (0..NUM_CONCURRENT)
        .map(|i| {
            tokio::spawn(async move {
                let mut config = test_peer_config();
                // Use different bind ports to avoid conflicts
                config.bind_addr = Some(format!("127.0.0.1:{}", 10000 + i).parse().unwrap());

                let result = NatTraversalEndpoint::new(config, None, None).await;
                (i, result.is_ok())
            })
        })
        .collect();

    // Wait for all to complete
    let results: Vec<_> = futures_util::future::join_all(handles)
        .await
        .into_iter()
        .map(|r| r.expect("Task should not panic"))
        .collect();

    // Check that no tasks panicked
    assert_eq!(results.len(), NUM_CONCURRENT, "All tasks should complete");

    let successful = results.iter().filter(|(_, success)| *success).count();
    println!("✓ Concurrent creation test: {successful}/{NUM_CONCURRENT} succeeded");
}

/// Test statistics access doesn't panic with concurrent access
/// Tests mutex safety in statistics gathering
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_statistics_concurrent_access() {
    const NUM_CONCURRENT: usize = 20;

    let config = test_server_config();

    let endpoint_result = NatTraversalEndpoint::new(config, None, None).await;

    if let Ok(endpoint) = endpoint_result {
        let endpoint = Arc::new(endpoint);
        let barrier = Arc::new(tokio::sync::Barrier::new(NUM_CONCURRENT));

        // Concurrent statistics access
        let handles: Vec<_> = (0..NUM_CONCURRENT)
            .map(|_| {
                let ep = Arc::clone(&endpoint);
                let barrier = Arc::clone(&barrier);
                tokio::spawn(async move {
                    barrier.wait().await;
                    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| ep.get_statistics()))
                })
            })
            .collect();

        let results = tokio::time::timeout(
            Duration::from_secs(5),
            futures_util::future::join_all(handles),
        )
        .await
        .expect("concurrent statistics calls should complete");

        // Check that no statistics call panicked
        for (i, result) in results.into_iter().enumerate() {
            let result = result.expect("statistics task should join");
            assert!(result.is_ok(), "Statistics call {i} should not panic");
        }

        println!("✓ Concurrent statistics access completed safely");
    }
}

/// Test that malformed configurations are handled safely
#[tokio::test]
async fn test_malformed_config_handling() {
    // v0.13.0+: Test a node with no known peers (valid - can be connected to)
    let no_peers_config = NatTraversalConfig {
        known_peers: vec![], // No known peers - node waits for incoming connections
        max_candidates: 10,
        coordination_timeout: Duration::from_secs(10),
        enable_symmetric_nat: true,
        enable_relay_fallback: false,
        max_concurrent_attempts: 5,
        bind_addr: Some("127.0.0.1:0".parse().unwrap()),
        prefer_rfc_nat_traversal: true,
        pqc: None,
        timeouts: Default::default(),
        identity_key: None,
        relay_nodes: vec![],
        enable_relay_service: true,
        allow_ipv4_mapped: true,
        transport_registry: None,
        max_message_size: ant_quic::P2pConfig::DEFAULT_MAX_MESSAGE_SIZE,
        max_concurrent_uni_streams: 100,
        additional_bind_addrs: Vec::new(),
    };

    if let Some(endpoint) = endpoint_or_skip_udp_blocked(
        NatTraversalEndpoint::new(no_peers_config, None, None).await,
        "no peers config",
    ) {
        let addr = assert_endpoint_bound(&endpoint, IpAddr::V4(Ipv4Addr::LOCALHOST));
        println!("✓ No peers config accepted: {addr}");
    }

    // Test extremely large values that could cause overflow
    let extreme_config = NatTraversalConfig {
        known_peers: vec!["127.0.0.1:9000".parse().unwrap()],
        max_candidates: usize::MAX, // Maximum possible value
        coordination_timeout: Duration::from_secs(u64::MAX / 1000), // Very large timeout
        enable_symmetric_nat: true,
        enable_relay_fallback: false,
        max_concurrent_attempts: usize::MAX,
        bind_addr: Some("127.0.0.1:0".parse().unwrap()),
        prefer_rfc_nat_traversal: true,
        pqc: None,
        timeouts: Default::default(),
        identity_key: None,
        relay_nodes: vec![],
        enable_relay_service: true,
        allow_ipv4_mapped: true,
        transport_registry: None,
        max_message_size: ant_quic::P2pConfig::DEFAULT_MAX_MESSAGE_SIZE,
        max_concurrent_uni_streams: 100,
        additional_bind_addrs: Vec::new(),
    };

    let result2 = NatTraversalEndpoint::new(extreme_config, None, None).await;
    assert_config_error_contains(&result2, "max_candidates");
}

/// Test input sanitization for potential security issues
#[tokio::test]
async fn test_input_sanitization() {
    // Test with many known peers (potential DoS vector)
    let many_peers: Vec<_> = (9000..9200)
        .map(|port| format!("127.0.0.1:{port}").parse().unwrap())
        .collect();

    let large_peer_config = NatTraversalConfig {
        known_peers: many_peers, // 200 known peers
        max_candidates: 10,
        coordination_timeout: Duration::from_secs(10),
        enable_symmetric_nat: true,
        enable_relay_fallback: false,
        max_concurrent_attempts: 5,
        bind_addr: Some("127.0.0.1:0".parse().unwrap()),
        prefer_rfc_nat_traversal: true,
        pqc: None,
        timeouts: Default::default(),
        identity_key: None,
        relay_nodes: vec![],
        enable_relay_service: true,
        allow_ipv4_mapped: true,
        transport_registry: None,
        max_message_size: ant_quic::P2pConfig::DEFAULT_MAX_MESSAGE_SIZE,
        max_concurrent_uni_streams: 100,
        additional_bind_addrs: Vec::new(),
    };

    // This should either work or fail gracefully, not exhaust memory or panic
    let start_time = std::time::Instant::now();
    let result = NatTraversalEndpoint::new(large_peer_config, None, None).await;
    let duration = start_time.elapsed();

    // Should complete within reasonable time
    assert!(
        duration < Duration::from_secs(30),
        "Large config processing took too long"
    );

    match result {
        Ok(_) => println!("✓ Large peer list handled successfully in {duration:?}"),
        Err(e) => println!("✓ Large peer list rejected safely in {duration:?}: {e}"),
    }
}

/// Test resource cleanup and prevent leaks
#[tokio::test]
async fn test_resource_cleanup() {
    // Create and drop many endpoints to test for resource leaks
    for i in 0..20 {
        let mut config = test_peer_config();
        config.bind_addr = Some(format!("127.0.0.1:{}", 11000 + i).parse().unwrap());

        let endpoint_result = NatTraversalEndpoint::new(config, None, None).await;

        if let Ok(endpoint) = endpoint_result {
            // Use the endpoint briefly
            let _stats = endpoint.get_statistics();

            // Endpoint will be dropped here - test cleanup
        }

        // Small delay to allow cleanup
        tokio::time::sleep(Duration::from_millis(10)).await;
    }

    println!("✓ Resource cleanup test completed - no obvious leaks");
}

#[cfg(test)]
mod specific_regression_tests {
    use super::*;

    /// Specific test for commit 6e633cd9: enhanced protocol obfuscation
    #[tokio::test]
    async fn test_commit_6e633cd9_protocol_obfuscation() {
        // Test that the create_random_port_bind_addr function is used
        // when bind_addr is None

        let config_with_none = NatTraversalConfig {
            known_peers: vec!["127.0.0.1:9000".parse().unwrap()],
            max_candidates: 10,
            coordination_timeout: Duration::from_secs(10),
            enable_symmetric_nat: true,
            enable_relay_fallback: false,
            max_concurrent_attempts: 5,
            bind_addr: None, // This should trigger random port binding
            prefer_rfc_nat_traversal: true,
            pqc: None,
            timeouts: Default::default(),
            identity_key: None,
            relay_nodes: vec![],
            enable_relay_service: true,
            allow_ipv4_mapped: true,
            transport_registry: None,
            max_message_size: ant_quic::P2pConfig::DEFAULT_MAX_MESSAGE_SIZE,
            max_concurrent_uni_streams: 100,
            additional_bind_addrs: Vec::new(),
        };

        if let Some(endpoint) = endpoint_or_skip_udp_blocked(
            NatTraversalEndpoint::new(config_with_none, None, None).await,
            "protocol obfuscation random port binding",
        ) {
            let addr = assert_endpoint_bound(&endpoint, IpAddr::V4(Ipv4Addr::UNSPECIFIED));
            println!("✓ Random port binding successful: {addr}");
        }
    }

    /// Specific test for commit a7d1de11: robust error handling
    #[tokio::test]
    async fn test_commit_a7d1de11_robust_error_handling() {
        // Test scenarios that previously could cause panics due to unwrap() usage

        // v0.13.0+: Problematic config test - zeros for everything
        let problematic_config = NatTraversalConfig {
            known_peers: vec!["127.0.0.1:9000".parse().unwrap()],
            max_candidates: 0,
            coordination_timeout: Duration::from_secs(0),
            enable_symmetric_nat: false,
            enable_relay_fallback: false,
            max_concurrent_attempts: 0,
            bind_addr: None,
            prefer_rfc_nat_traversal: true,
            pqc: None,
            timeouts: Default::default(),
            identity_key: None,
            relay_nodes: vec![],
            enable_relay_service: true,
            allow_ipv4_mapped: true,
            transport_registry: None,
            max_message_size: ant_quic::P2pConfig::DEFAULT_MAX_MESSAGE_SIZE,
            max_concurrent_uni_streams: 100,
            additional_bind_addrs: Vec::new(),
        };

        let result = NatTraversalEndpoint::new(problematic_config, None, None).await;
        assert_config_error_contains(&result, "max_candidates");
    }
}