@@ -14,6 +14,8 @@ use reqwest::StatusCode;
1414use std:: collections:: hash_map:: DefaultHasher ;
1515use std:: collections:: { HashMap , HashSet } ;
1616use std:: hash:: { Hash , Hasher } ;
17+ use std:: net:: { IpAddr , Ipv4Addr , Ipv6Addr } ;
18+ use std:: ops:: Deref ;
1719use std:: str:: FromStr ;
1820use tracing:: { error, info, warn} ;
1921
@@ -132,6 +134,22 @@ impl Collector for DnsCollector {
132134 . and_then ( |mo| PerformDnsBodyProxy :: from_str ( & mo) . ok ( ) )
133135 . unwrap_or_default ( ) ;
134136
137+ let isp = self
138+ . config
139+ . common_config
140+ . network
141+ . as_ref ( )
142+ . map ( |n| n. isp_regex . clone ( ) )
143+ . unwrap_or_default ( ) ;
144+
145+ let node_id = self
146+ . config
147+ . common_config
148+ . network
149+ . as_ref ( )
150+ . map ( |n| n. node_id . clone ( ) )
151+ . unwrap_or_default ( ) ;
152+
135153 info ! ( ?self . config. common_config, ?country_code, "Sending DNS request" ) ;
136154
137155 match API_CLIENT
@@ -142,6 +160,8 @@ impl Collector for DnsCollector {
142160 . continent_code ( continent_code)
143161 . mobile ( mobile)
144162 . residential ( residential)
163+ . isp_regex ( isp)
164+ . node_id ( node_id)
145165 . proxy ( proxy)
146166 } )
147167 . send ( )
@@ -303,35 +323,250 @@ where
303323 I :: Item : AsRef < str > ,
304324{
305325 let mut providers = HashSet :: new ( ) ;
306- let provider_map: HashMap < & str , & str > = [
307- ( "8.8.8.8" , "Google" ) ,
308- ( "8.8.4.4" , "Google" ) ,
309- ( "1.1.1.1" , "Cloudflare" ) ,
310- ( "1.0.0.1" , "Cloudflare" ) ,
311- ( "9.9.9.9" , "Quad9" ) ,
312- ( "149.112.112.112" , "Quad9" ) ,
313- ( "208.67.222.222" , "OpenDNS" ) ,
314- ( "208.67.220.220" , "OpenDNS" ) ,
315- ( "94.140.14.14" , "AdGuard" ) ,
316- ( "94.140.15.15" , "AdGuard" ) ,
317- ( "185.228.168.9" , "CleanBrowsing" ) ,
318- ( "185.228.169.9" , "CleanBrowsing" ) ,
319- ( "76.76.19.19" , "Alternate DNS" ) ,
320- ( "76.223.122.150" , "Alternate DNS" ) ,
321- ( "76.76.2.0" , "Control D" ) ,
322- ( "76.76.10.0" , "Control D" ) ,
323- ]
324- . iter ( )
325- . cloned ( )
326- . collect ( ) ;
326+ let provider_map = build_provider_map ( ) ;
327327
328328 for ip in ips {
329- let provider = provider_map
330- . get ( ip. as_ref ( ) )
331- . map ( |& p| p. to_string ( ) )
332- . unwrap_or_else ( || ip. as_ref ( ) . to_string ( ) ) ;
329+ let ip_str = ip. as_ref ( ) ;
330+
331+ let ip_addr = match IpAddr :: from_str ( ip_str) {
332+ Ok ( addr) => addr,
333+ Err ( _) => {
334+ providers. insert ( "Invalid IP" . to_string ( ) ) ;
335+ continue ;
336+ }
337+ } ;
338+
339+ let provider = match ip_addr {
340+ IpAddr :: V4 ( ipv4) => {
341+ if is_loopback_v4 ( & ipv4) {
342+ "Localhost" . to_string ( )
343+ } else if is_private_ipv4 ( & ipv4) {
344+ classify_private_network ( & ipv4)
345+ } else if is_tailscale_range ( & ipv4) {
346+ "Tailscale" . to_string ( )
347+ } else if let Some ( provider) = provider_map. get ( ip_str) {
348+ provider. to_string ( )
349+ } else {
350+ classify_public_dns ( & ipv4)
351+ }
352+ }
353+ IpAddr :: V6 ( ipv6) => {
354+ if is_loopback_v6 ( & ipv6) {
355+ "Localhost" . to_string ( )
356+ } else if is_private_ipv6 ( & ipv6) {
357+ classify_private_ipv6 ( & ipv6)
358+ } else if let Some ( provider) = provider_map. get ( ip_str) {
359+ provider. to_string ( )
360+ } else {
361+ classify_public_ipv6_dns ( & ipv6)
362+ }
363+ }
364+ } ;
365+
333366 providers. insert ( provider) ;
334367 }
335368
336369 providers
337370}
371+
372+ fn is_loopback_v4 ( ip : & Ipv4Addr ) -> bool {
373+ ip. octets ( ) [ 0 ] == 127
374+ }
375+
376+ fn is_loopback_v6 ( ip : & Ipv6Addr ) -> bool {
377+ * ip == Ipv6Addr :: new ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 )
378+ }
379+
380+ fn is_private_ipv4 ( ip : & Ipv4Addr ) -> bool {
381+ let octets = ip. octets ( ) ;
382+ match octets[ 0 ] {
383+ 10 => true ,
384+ 172 => ( 16 ..=31 ) . contains ( & octets[ 1 ] ) ,
385+ 192 => octets[ 1 ] == 168 ,
386+ 169 => octets[ 1 ] == 254 ,
387+ _ => false ,
388+ }
389+ }
390+
391+ fn is_private_ipv6 ( ip : & Ipv6Addr ) -> bool {
392+ let segments = ip. segments ( ) ;
393+
394+ // fc00::/7 (unique local)
395+ ( segments[ 0 ] & 0xfe00 ) == 0xfc00 ||
396+ // fe80::/10 (link local)
397+ ( segments[ 0 ] & 0xffc0 ) == 0xfe80
398+ }
399+
400+ fn is_tailscale_range ( ip : & Ipv4Addr ) -> bool {
401+ let octets = ip. octets ( ) ;
402+ octets[ 0 ] == 100 && ( 64 ..=127 ) . contains ( & octets[ 1 ] )
403+ }
404+
405+ fn build_provider_map ( ) -> HashMap < String , String > {
406+ let mut map = HashMap :: new ( ) ;
407+
408+ // Google DNS
409+ for ip in [
410+ "8.8.8.8" ,
411+ "8.8.4.4" ,
412+ "2001:4860:4860::8888" ,
413+ "2001:4860:4860::8844" ,
414+ ]
415+ . iter ( )
416+ {
417+ map. insert ( ( * ip) . to_string ( ) , "Google" . to_string ( ) ) ;
418+ }
419+
420+ // Cloudflare DNS
421+ for ip in [
422+ "1.1.1.1" ,
423+ "1.0.0.1" ,
424+ "1.1.1.2" ,
425+ "1.0.0.2" ,
426+ "2606:4700:4700::1111" ,
427+ "2606:4700:4700::1001" ,
428+ "2606:4700:4700::1112" ,
429+ "2606:4700:4700::1002" ,
430+ ]
431+ . iter ( )
432+ {
433+ map. insert ( ( * ip) . to_string ( ) , "Cloudflare" . to_string ( ) ) ;
434+ }
435+
436+ // Quad9
437+ for ip in [ "9.9.9.9" , "149.112.112.112" , "2620:fe::fe" , "2620:fe::9" ] . iter ( ) {
438+ map. insert ( ( * ip) . to_string ( ) , "Quad9" . to_string ( ) ) ;
439+ }
440+
441+ // OpenDNS
442+ for ip in [
443+ "208.67.222.222" ,
444+ "208.67.220.220" ,
445+ "2620:119:35::35" ,
446+ "2620:119:53::53" ,
447+ ]
448+ . iter ( )
449+ {
450+ map. insert ( ( * ip) . to_string ( ) , "OpenDNS" . to_string ( ) ) ;
451+ }
452+
453+ // AdGuard
454+ for ip in [
455+ "94.140.14.14" ,
456+ "94.140.15.15" ,
457+ "2a10:50c0::ad1:ff" ,
458+ "2a10:50c0::ad2:ff" ,
459+ ]
460+ . iter ( )
461+ {
462+ map. insert ( ( * ip) . to_string ( ) , "AdGuard" . to_string ( ) ) ;
463+ }
464+
465+ // NextDNS (including all Anycast ranges)
466+ let nextdns_ranges = ( 0 ..=255 )
467+ . filter ( |& n| {
468+ matches ! (
469+ n,
470+ 0 | 1 | 11 | 42 | 68 | 99 | 139 | 165 | 185 | 216 | 233 | 241
471+ )
472+ } )
473+ . flat_map ( |n| {
474+ vec ! [
475+ format!( "45.90.28.{}" , n) ,
476+ format!( "45.90.30.{}" , n) ,
477+ format!( "2a07:a8c0::{:x}" , n) ,
478+ format!( "2a07:a8c1::{:x}" , n) ,
479+ ]
480+ } ) ;
481+
482+ for ip in nextdns_ranges {
483+ map. insert ( ip, "NextDNS" . to_string ( ) ) ;
484+ }
485+
486+ map
487+ }
488+
489+ fn classify_private_network ( ip : & Ipv4Addr ) -> String {
490+ let octets = ip. octets ( ) ;
491+ match octets[ 0 ] {
492+ 10 => "Private Network (10.0.0.0/8)" . to_string ( ) ,
493+ 172 if ( 16 ..=31 ) . contains ( & octets[ 1 ] ) => "Private Network (172.16.0.0/12)" . to_string ( ) ,
494+ 192 if octets[ 1 ] == 168 => "Home Network (192.168.0.0/16)" . to_string ( ) ,
495+ 169 if octets[ 1 ] == 254 => "Link-local Network" . to_string ( ) ,
496+ _ => "Unknown Private Network" . to_string ( ) ,
497+ }
498+ }
499+
500+ fn classify_private_ipv6 ( ip : & Ipv6Addr ) -> String {
501+ let segments = ip. segments ( ) ;
502+ if ( segments[ 0 ] & 0xfe00 ) == 0xfc00 {
503+ "Unique Local Address" . to_string ( )
504+ } else if ( segments[ 0 ] & 0xffc0 ) == 0xfe80 {
505+ "Link-local Network" . to_string ( )
506+ } else {
507+ "Private IPv6 Network" . to_string ( )
508+ }
509+ }
510+
511+ fn classify_public_dns ( ip : & Ipv4Addr ) -> String {
512+ let octets = ip. octets ( ) ;
513+
514+ match octets[ 0 ] {
515+ // Reserved for IANA special use
516+ 0 | 127 | 169 | 192 | 198 => "Reserved Range" . to_string ( ) ,
517+
518+ // Major ISP ranges
519+ 1 ..=100 => {
520+ if is_known_isp_range ( ip) {
521+ "ISP DNS" . to_string ( )
522+ } else {
523+ "Unknown Public DNS" . to_string ( )
524+ }
525+ }
526+
527+ // Enterprise ranges
528+ 128 ..=191 => "Enterprise DNS" . to_string ( ) ,
529+
530+ _ => "Unknown Public DNS" . to_string ( ) ,
531+ }
532+ }
533+
534+ fn classify_public_ipv6_dns ( ip : & Ipv6Addr ) -> String {
535+ let segments = ip. segments ( ) ;
536+
537+ match segments[ 0 ] {
538+ // Global Unicast Address (2000::/3)
539+ s if ( 0x2000 ..=0x3FFF ) . contains ( & s) => {
540+ if is_known_ipv6_dns_range ( ip) {
541+ "Known IPv6 DNS Provider" . to_string ( )
542+ } else {
543+ "ISP IPv6 DNS" . to_string ( )
544+ }
545+ }
546+
547+ _ => "Unknown IPv6 DNS" . to_string ( ) ,
548+ }
549+ }
550+
551+ fn is_known_isp_range ( ip : & Ipv4Addr ) -> bool {
552+ let octets = ip. octets ( ) ;
553+
554+ matches ! (
555+ ( octets[ 0 ] , octets[ 1 ] ) ,
556+ ( 24 ..=50 , _) | // ARIN space
557+ ( 62 ..=70 , _) | // RIPE space
558+ ( 80 ..=90 , _) | // RIPE space
559+ ( 98 ..=100 , _) // APNIC space
560+ )
561+ }
562+
563+ fn is_known_ipv6_dns_range ( ip : & Ipv6Addr ) -> bool {
564+ let segments = ip. segments ( ) ;
565+
566+ matches ! (
567+ segments[ 0 ] ,
568+ 0x2001 | // Teredo
569+ 0x2606 | // Various providers
570+ 0x2620 // Various providers
571+ )
572+ }
0 commit comments