@@ -31,10 +31,16 @@ const TEST_DOMAINS: &[&str] = &[
3131 "cloudflare.com" ,
3232 "microsoft.com" ,
3333 "github.com" ,
34- "netflix.com"
34+ "netflix.com" ,
35+ "amazon.com" ,
36+ "facebook.com" ,
37+ "wikipedia.org" ,
38+ "reddit.com"
3539] ;
3640
37- const TEST_ROUNDS : u32 = 3 ;
41+ const TEST_ROUNDS : u32 = 5 ;
42+ const TIMEOUT_SECS : u64 = 3 ;
43+ const COOLDOWN_MS : u64 = 100 ;
3844
3945#[ derive( Debug ) ]
4046struct TestResult {
@@ -43,21 +49,31 @@ struct TestResult {
4349 min_latency : Duration ,
4450 max_latency : Duration ,
4551 success_rate : f64 ,
52+ failed_domains : Vec < String > ,
53+ median_duration : Duration ,
4654}
4755
4856async fn measure_latency ( addr : & str ) -> Option < Duration > {
4957 let start = Instant :: now ( ) ;
50- if let Ok ( mut stream) = tokio:: net:: TcpStream :: connect ( format ! ( "{}:53" , addr) ) . await {
51- let _ = stream. shutdown ( ) . await ;
52- Some ( start. elapsed ( ) )
53- } else {
54- None
58+ match tokio:: time:: timeout (
59+ Duration :: from_secs ( TIMEOUT_SECS ) ,
60+ tokio:: net:: TcpStream :: connect ( format ! ( "{}:53" , addr) )
61+ ) . await {
62+ Ok ( Ok ( mut stream) ) => {
63+ let _ = stream. shutdown ( ) . await ;
64+ Some ( start. elapsed ( ) )
65+ } ,
66+ _ => None
5567 }
5668}
5769
5870async fn test_dns_speed ( provider : & DnsProvider ) -> TestResult {
5971 let mut opts = ResolverOpts :: default ( ) ;
60- opts. timeout = Duration :: from_secs ( 5 ) ;
72+ opts. timeout = Duration :: from_secs ( TIMEOUT_SECS ) ;
73+ opts. attempts = 1 ;
74+ opts. use_hosts_file = false ;
75+ opts. cache_size = 0 ;
76+ opts. edns0 = false ;
6177
6278 let socket_addr = format ! ( "{}:53" , provider. ip)
6379 . parse :: < SocketAddr > ( )
@@ -73,45 +89,59 @@ async fn test_dns_speed(provider: &DnsProvider) -> TestResult {
7389 ) ;
7490
7591 let resolver = TokioAsyncResolver :: tokio ( config, opts) ;
76- let mut total_duration = Duration :: default ( ) ;
77- let mut successful_queries = 0 ;
92+ let mut durations = Vec :: new ( ) ;
93+ let mut failed_domains = Vec :: new ( ) ;
7894 let mut total_queries = 0 ;
79- let mut min_latency = Duration :: from_secs ( 5 ) ;
80- let mut max_latency = Duration :: default ( ) ;
81-
82- if let Some ( latency) = measure_latency ( provider. ip ) . await {
83- min_latency = latency;
84- max_latency = latency;
85- }
8695
8796 let _ = resolver. lookup_ip ( Name :: from_ascii ( "example.com" ) . unwrap ( ) ) . await ;
88- sleep ( Duration :: from_millis ( 100 ) ) . await ;
97+ sleep ( Duration :: from_millis ( COOLDOWN_MS ) ) . await ;
8998
90- for _ in 0 ..TEST_ROUNDS {
99+ for round in 0 ..TEST_ROUNDS {
91100 for domain in TEST_DOMAINS {
92101 total_queries += 1 ;
93- let query_start = Instant :: now ( ) ;
94102
103+ let tcp_latency = measure_latency ( provider. ip ) . await ;
104+ if tcp_latency. is_none ( ) {
105+ failed_domains. push ( format ! ( "{} (TCP Failed)" , domain) ) ;
106+ continue ;
107+ }
108+
109+ let query_start = Instant :: now ( ) ;
95110 match resolver. lookup_ip ( Name :: from_ascii ( domain) . unwrap ( ) ) . await {
96111 Ok ( _) => {
97- let duration = query_start. elapsed ( ) ;
98- total_duration += duration;
99- successful_queries += 1 ;
100- min_latency = min_latency. min ( duration) ;
101- max_latency = max_latency. max ( duration) ;
112+ durations. push ( query_start. elapsed ( ) ) ;
102113 } ,
103- Err ( _) => continue ,
114+ Err ( _) => {
115+ failed_domains. push ( domain. to_string ( ) ) ;
116+ }
104117 }
105118
106- sleep ( Duration :: from_millis ( 50 ) ) . await ;
119+ sleep ( Duration :: from_millis ( COOLDOWN_MS ) ) . await ;
120+ }
121+
122+ if round < TEST_ROUNDS - 1 {
123+ sleep ( Duration :: from_millis ( COOLDOWN_MS * 2 ) ) . await ;
107124 }
108125 }
109126
127+ durations. sort ( ) ;
128+ let successful_queries = durations. len ( ) ;
110129 let success_rate = ( successful_queries as f64 ) / ( total_queries as f64 ) * 100.0 ;
111- let avg_duration = if successful_queries > 0 {
112- total_duration / successful_queries
130+
131+ let avg_duration = if !durations. is_empty ( ) {
132+ Duration :: from_secs_f64 (
133+ durations. iter ( ) . map ( |d| d. as_secs_f64 ( ) ) . sum :: < f64 > ( ) / successful_queries as f64
134+ )
135+ } else {
136+ Duration :: from_secs ( TIMEOUT_SECS )
137+ } ;
138+
139+ let min_latency = durations. first ( ) . copied ( ) . unwrap_or ( Duration :: from_secs ( TIMEOUT_SECS ) ) ;
140+ let max_latency = durations. last ( ) . copied ( ) . unwrap_or ( Duration :: from_secs ( TIMEOUT_SECS ) ) ;
141+ let median_duration = if !durations. is_empty ( ) {
142+ durations[ durations. len ( ) / 2 ]
113143 } else {
114- Duration :: from_secs ( 5 )
144+ Duration :: from_secs ( TIMEOUT_SECS )
115145 } ;
116146
117147 TestResult {
@@ -120,49 +150,60 @@ async fn test_dns_speed(provider: &DnsProvider) -> TestResult {
120150 min_latency,
121151 max_latency,
122152 success_rate,
153+ failed_domains,
154+ median_duration,
123155 }
124156}
125157
126158#[ tokio:: main]
127159async fn main ( ) {
128- println ! ( "Testing DNS query speeds... \n " ) ;
160+ println ! ( "DNS Speed Test ( Testing {} domains × {} rounds) \n " , TEST_DOMAINS . len ( ) , TEST_ROUNDS ) ;
129161
130162 let mut results = Vec :: new ( ) ;
131163
132164 for provider in DNS_PROVIDERS {
133165 print ! ( "Testing {}... " , provider. name) ;
134166 let result = test_dns_speed ( provider) . await ;
135167 println ! ( "{:.2} ms (Success rate: {:.1}%)" ,
136- result. avg_duration . as_secs_f64( ) * 1000.0 ,
168+ result. median_duration . as_secs_f64( ) * 1000.0 ,
137169 result. success_rate
138170 ) ;
139171 results. push ( result) ;
140172 }
141173
142- results. sort_by ( |a, b| a. avg_duration . cmp ( & b. avg_duration ) ) ;
174+ results. sort_by ( |a, b| a. median_duration . cmp ( & b. median_duration ) ) ;
143175
144- println ! ( "\n Detailed Results (sorted by speed):" ) ;
145- println ! ( "{:-<75 }" , "" ) ;
146- println ! ( "{:<15} {:>10} {:>12} {:>12} {:>15}" ,
147- "Provider" , "Avg (ms)" , "Min (ms)" , "Max (ms)" , "Success Rate" ) ;
148- println ! ( "{:-<75 }" , "" ) ;
176+ println ! ( "\n Detailed Results (sorted by median speed):" ) ;
177+ println ! ( "{:-<90 }" , "" ) ;
178+ println ! ( "{:<15} {:>10} {:>10} {:> 12} {:>12} {:>15}" ,
179+ "Provider" , "Median" , " Avg (ms)", "Min (ms)" , "Max (ms)" , "Success Rate" ) ;
180+ println ! ( "{:-<90 }" , "" ) ;
149181
150182 for result in & results {
151183 println ! (
152- "{:<15} {:>10.2} {:>12.2} {:>12.2} {:>14.1}%" ,
184+ "{:<15} {:>10.2} {:>10.2} {:> 12.2} {:>12.2} {:>14.1}%" ,
153185 result. provider,
186+ result. median_duration. as_secs_f64( ) * 1000.0 ,
154187 result. avg_duration. as_secs_f64( ) * 1000.0 ,
155188 result. min_latency. as_secs_f64( ) * 1000.0 ,
156189 result. max_latency. as_secs_f64( ) * 1000.0 ,
157190 result. success_rate
158191 ) ;
192+
193+ if !result. failed_domains . is_empty ( ) {
194+ println ! ( " Failed domains: {}" , result. failed_domains. join( ", " ) ) ;
195+ }
159196 }
160197
161198 if let Some ( fastest) = results. first ( ) {
162- println ! ( "\n Fastest DNS provider: {} ({:.2} ms, {:.1}% success rate)" ,
199+ println ! ( "\n Fastest DNS provider: {} ({:.2} ms median , {:.1}% success rate)" ,
163200 fastest. provider,
164- fastest. avg_duration . as_secs_f64( ) * 1000.0 ,
201+ fastest. median_duration . as_secs_f64( ) * 1000.0 ,
165202 fastest. success_rate
166203 ) ;
167204 }
205+
206+ println ! ( "\n Press Enter to exit..." ) ;
207+ let mut input = String :: new ( ) ;
208+ std:: io:: stdin ( ) . read_line ( & mut input) . unwrap ( ) ;
168209}
0 commit comments