ant_quic/compliance_validator/
endpoint_tester.rs1use super::{EndpointResult, EndpointValidationReport, ValidationError};
5use crate::{
6 ClientConfig, EndpointConfig, VarInt,
7 high_level::{Connection, Endpoint},
8 transport_parameters::TransportParameters,
9};
10use std::collections::HashMap;
11use std::net::ToSocketAddrs;
12use std::time::Duration;
13use tokio::time::timeout;
14use tracing::{error, info, warn};
15
16pub const PUBLIC_QUIC_ENDPOINTS: &[&str] = &[
19 "quic.nginx.org:443", "cloudflare.com:443", "www.google.com:443", "facebook.com:443", "cloudflare-quic.com:443", "quic.rocks:4433", "http3-test.litespeedtech.com:4433", "http3-test.litespeedtech.com:4434", "test.privateoctopus.com:4433", "test.privateoctopus.com:4434", "test.pquic.org:443", "www.litespeedtech.com:443", "quic.tech:4433",
35 "quic.westus.cloudapp.azure.com:4433",
36 "h3.vortex.data.msn.com:443",
37];
38
39pub struct EndpointTester {
41 endpoint: Option<Endpoint>,
43 timeout_duration: Duration,
45 custom_endpoints: Vec<String>,
47}
48
49impl Default for EndpointTester {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55impl EndpointTester {
56 pub fn new() -> Self {
58 Self {
59 endpoint: None,
60 timeout_duration: Duration::from_secs(10),
61 custom_endpoints: Vec::new(),
62 }
63 }
64
65 pub fn with_timeout(mut self, duration: Duration) -> Self {
67 self.timeout_duration = duration;
68 self
69 }
70
71 pub fn add_endpoint(&mut self, endpoint: String) {
73 self.custom_endpoints.push(endpoint);
74 }
75
76 async fn init_endpoint(&mut self) -> Result<(), ValidationError> {
78 if self.endpoint.is_none() {
79 let socket = std::net::UdpSocket::bind("0.0.0.0:0").map_err(|e| {
80 ValidationError::ValidationError(format!("Failed to bind socket: {e}"))
81 })?;
82 let runtime = crate::high_level::default_runtime().ok_or_else(|| {
83 ValidationError::ValidationError("No compatible async runtime found".to_string())
84 })?;
85 let endpoint = Endpoint::new(
86 EndpointConfig::default(),
87 None, socket,
89 runtime,
90 )
91 .map_err(|e| {
92 ValidationError::ValidationError(format!("Failed to create endpoint: {e}"))
93 })?;
94
95 self.endpoint = Some(endpoint);
96 }
97 Ok(())
98 }
99
100 pub async fn test_all_endpoints(&mut self) -> EndpointValidationReport {
102 self.init_endpoint().await.unwrap_or_else(|e| {
103 error!("Failed to initialize endpoint: {}", e);
104 });
105
106 let mut all_endpoints = PUBLIC_QUIC_ENDPOINTS
107 .iter()
108 .map(|&s| s.to_string())
109 .collect::<Vec<_>>();
110 all_endpoints.extend(self.custom_endpoints.clone());
111
112 let mut endpoint_results = HashMap::new();
113 let mut successful = 0;
114 let mut common_issues = HashMap::new();
115
116 for endpoint_str in &all_endpoints {
117 info!("Testing endpoint: {}", endpoint_str);
118
119 match self.test_endpoint(endpoint_str).await {
120 Ok(result) => {
121 if result.connected {
122 successful += 1;
123 }
124
125 for issue in &result.issues {
127 *common_issues.entry(issue.clone()).or_insert(0) += 1;
128 }
129
130 endpoint_results.insert(endpoint_str.clone(), result);
131 }
132 Err(e) => {
133 warn!("Failed to test endpoint {}: {}", endpoint_str, e);
134 endpoint_results.insert(
135 endpoint_str.clone(),
136 EndpointResult {
137 endpoint: endpoint_str.clone(),
138 connected: false,
139 quic_versions: vec![],
140 extensions: vec![],
141 issues: vec![format!("Test failed: {}", e)],
142 },
143 );
144 }
145 }
146 }
147
148 let success_rate = if all_endpoints.is_empty() {
149 0.0
150 } else {
151 successful as f64 / all_endpoints.len() as f64
152 };
153
154 let mut common_issues_vec: Vec<_> = common_issues.into_iter().collect();
156 common_issues_vec.sort_by(|a, b| b.1.cmp(&a.1));
157 let common_issues = common_issues_vec
158 .into_iter()
159 .take(5)
160 .map(|(issue, _)| issue)
161 .collect();
162
163 EndpointValidationReport {
164 endpoint_results,
165 success_rate,
166 common_issues,
167 }
168 }
169
170 async fn test_endpoint(&self, endpoint_str: &str) -> Result<EndpointResult, ValidationError> {
172 let addr = endpoint_str
173 .to_socket_addrs()
174 .map_err(|e| ValidationError::ValidationError(format!("Invalid address: {e}")))?
175 .next()
176 .ok_or_else(|| ValidationError::ValidationError("No address resolved".to_string()))?;
177
178 let endpoint = self.endpoint.as_ref().ok_or_else(|| {
179 ValidationError::ValidationError("Endpoint not initialized".to_string())
180 })?;
181
182 let server_name = endpoint_str.split(':').next().unwrap_or(endpoint_str);
184
185 let client_config = create_test_client_config(server_name)?;
187
188 let connecting = match endpoint.connect_with(client_config, addr, server_name) {
190 Ok(connecting) => connecting,
191 Err(e) => {
192 return Ok(EndpointResult {
193 endpoint: endpoint_str.to_string(),
194 connected: false,
195 quic_versions: vec![],
196 extensions: vec![],
197 issues: vec![format!("Failed to start connection: {}", e)],
198 });
199 }
200 };
201
202 let connect_result = timeout(self.timeout_duration, connecting).await;
203
204 match connect_result {
205 Ok(Ok(connection)) => {
206 let result = self.analyze_connection(endpoint_str, connection).await?;
208 Ok(result)
209 }
210 Ok(Err(e)) => {
211 Ok(EndpointResult {
213 endpoint: endpoint_str.to_string(),
214 connected: false,
215 quic_versions: vec![],
216 extensions: vec![],
217 issues: vec![format!("Connection failed: {}", e)],
218 })
219 }
220 Err(_) => {
221 Ok(EndpointResult {
223 endpoint: endpoint_str.to_string(),
224 connected: false,
225 quic_versions: vec![],
226 extensions: vec![],
227 issues: vec!["Connection timeout".to_string()],
228 })
229 }
230 }
231 }
232
233 async fn analyze_connection(
235 &self,
236 endpoint_str: &str,
237 connection: Connection,
238 ) -> Result<EndpointResult, ValidationError> {
239 let mut issues = Vec::new();
240
241 let quic_versions = vec![0x00000001]; let extensions = Vec::new();
247
248 match self.test_data_exchange(&connection).await {
253 Ok(()) => {
254 info!("Data exchange successful with {}", endpoint_str);
255 }
256 Err(e) => {
257 issues.push(format!("Data exchange failed: {e}"));
258 }
259 }
260
261 connection.close(VarInt::from_u32(0), b"test complete");
265
266 Ok(EndpointResult {
267 endpoint: endpoint_str.to_string(),
268 connected: true,
269 quic_versions,
270 extensions,
271 issues,
272 })
273 }
274
275 async fn test_data_exchange(&self, connection: &Connection) -> Result<(), ValidationError> {
277 let (mut send, mut recv) = connection
279 .open_bi()
280 .await
281 .map_err(|e| ValidationError::ValidationError(format!("Failed to open stream: {e}")))?;
282
283 let test_data = b"QUIC compliance test";
285 send.write_all(&test_data[..])
286 .await
287 .map_err(|e| ValidationError::ValidationError(format!("Failed to send data: {e}")))?;
288
289 send.finish().map_err(|e| {
290 ValidationError::ValidationError(format!("Failed to finish stream: {e}"))
291 })?;
292
293 let mut buf = vec![0u8; 1024];
295 let _ = timeout(Duration::from_secs(2), recv.read(&mut buf)).await;
296
297 Ok(())
298 }
299
300 fn check_compliance(&self, params: &TransportParameters) -> Option<Vec<String>> {
302 let mut issues = Vec::new();
303
304 if params.max_udp_payload_size.0 < 1200 {
306 issues.push("max_udp_payload_size < 1200 (RFC 9000 violation)".to_string());
307 }
308
309 if params.ack_delay_exponent.0 > 20 {
311 issues.push("ack_delay_exponent > 20 (RFC 9000 violation)".to_string());
312 }
313
314 if params.max_ack_delay.0 >= (1 << 14) {
316 issues.push("max_ack_delay >= 2^14 (RFC 9000 violation)".to_string());
317 }
318
319 if params.active_connection_id_limit.0 < 2 {
321 issues.push("active_connection_id_limit < 2 (RFC 9000 violation)".to_string());
322 }
323
324 if issues.is_empty() {
325 None
326 } else {
327 Some(issues)
328 }
329 }
330}
331
332fn create_test_client_config(_server_name: &str) -> Result<ClientConfig, ValidationError> {
334 #[cfg(feature = "platform-verifier")]
336 {
337 ClientConfig::try_with_platform_verifier().map_err(|e| {
338 ValidationError::ValidationError(format!("Failed to create client config: {e}"))
339 })
340 }
341
342 #[cfg(not(feature = "platform-verifier"))]
343 {
344 use crate::crypto::rustls::QuicClientConfig;
346 use std::sync::Arc;
347
348 let mut roots = rustls::RootCertStore::empty();
349
350 let cert_result = rustls_native_certs::load_native_certs();
352 for cert in cert_result.certs {
353 roots.add(cert.into()).ok();
354 }
355 if !cert_result.errors.is_empty() {
356 warn!("Failed to load some native certs: {:?}", cert_result.errors);
357 }
358
359 let crypto = rustls::ClientConfig::builder()
361 .with_root_certificates(roots)
362 .with_no_client_auth();
363
364 let quic_crypto = QuicClientConfig::try_from(Arc::new(crypto)).map_err(|e| {
366 ValidationError::ValidationError(format!(
367 "Failed to create QUIC crypto config: {:?}",
368 e
369 ))
370 })?;
371
372 Ok(ClientConfig::new(Arc::new(quic_crypto)))
373 }
374}
375
376pub fn get_recommended_endpoints(requirements: &[&str]) -> Vec<String> {
378 let mut endpoints = Vec::new();
379
380 for req in requirements {
381 match *req {
382 "address_discovery" => {
383 endpoints.push("quic.tech:4433".to_string());
385 }
386 "nat_traversal" => {
387 endpoints.push("test.privateoctopus.com:4433".to_string());
389 }
390 "h3" => {
391 endpoints.push("cloudflare.com:443".to_string());
393 endpoints.push("www.google.com:443".to_string());
394 }
395 _ => {}
396 }
397 }
398
399 endpoints.sort();
401 endpoints.dedup();
402
403 endpoints
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409
410 #[test]
411 fn test_endpoint_tester_creation() {
412 let tester = EndpointTester::new();
413 assert_eq!(tester.timeout_duration, Duration::from_secs(10));
414 assert!(tester.custom_endpoints.is_empty());
415 }
416
417 #[test]
418 fn test_add_endpoint() {
419 let mut tester = EndpointTester::new();
420 tester.add_endpoint("example.com:443".to_string());
421 assert_eq!(tester.custom_endpoints.len(), 1);
422 assert_eq!(tester.custom_endpoints[0], "example.com:443");
423 }
424
425 #[test]
426 fn test_with_timeout() {
427 let tester = EndpointTester::new().with_timeout(Duration::from_secs(30));
428 assert_eq!(tester.timeout_duration, Duration::from_secs(30));
429 }
430
431 #[test]
432 fn test_recommended_endpoints() {
433 let endpoints = get_recommended_endpoints(&["h3"]);
434 assert!(!endpoints.is_empty());
435 assert!(endpoints.contains(&"cloudflare.com:443".to_string()));
436
437 let endpoints = get_recommended_endpoints(&["address_discovery"]);
438 assert!(endpoints.contains(&"quic.tech:4433".to_string()));
439 }
440
441 #[test]
442 fn test_compliance_check() {
443 let tester = EndpointTester::new();
444
445 let mut params = TransportParameters::default();
447 params.max_udp_payload_size = VarInt::from_u32(1500);
448 params.ack_delay_exponent = VarInt::from_u32(3);
449 params.max_ack_delay = VarInt::from_u32(25);
450 params.active_connection_id_limit = VarInt::from_u32(4);
451
452 assert!(tester.check_compliance(¶ms).is_none());
453
454 params.max_udp_payload_size = VarInt::from_u32(1000);
456 params.ack_delay_exponent = VarInt::from_u32(21);
457
458 let issues = tester.check_compliance(¶ms).unwrap();
459 assert_eq!(issues.len(), 2);
460 }
461}