@@ -8,6 +8,7 @@ use libp2p::{
88 autonat, identify,
99 identity:: { self , ed25519, Keypair } ,
1010 kad:: { self , Mode , PeerRecord , QueryStats , Record , RecordKey } ,
11+ multiaddr:: Protocol ,
1112 ping,
1213 swarm:: { behaviour:: toggle:: Toggle , NetworkBehaviour } ,
1314 Multiaddr , PeerId , Swarm , SwarmBuilder ,
@@ -417,28 +418,55 @@ fn keypair(secret_key: &SecretKey) -> Result<identity::Keypair> {
417418 Ok ( keypair)
418419}
419420
420- // Returns [`true`] if the address appears to be globally reachable
421- // Take from the unstable std::net implementation
422- pub fn is_global ( ip : Ipv4Addr ) -> bool {
423- !( ip. octets ( ) [ 0 ] == 0
424- || ip. is_private ( )
425- || ip. is_loopback ( )
426- || ip. is_link_local ( )
427- // addresses reserved for future protocols (`192.0.0.0/24`)
428- // .9 and .10 are documented as globally reachable so they're excluded
429- || (
430- ip. octets ( ) [ 0 ] == 192 && ip. octets ( ) [ 1 ] == 0 && ip. octets ( ) [ 2 ] == 0
431- && ip. octets ( ) [ 3 ] != 9 && ip. octets ( ) [ 3 ] != 10
432- )
433- || ip. is_documentation ( )
434- || ip. is_broadcast ( ) )
421+ /// Checks for IPv4 addresses reserved for future protocols
422+ fn is_reserved_ip ( ip : & Ipv4Addr ) -> bool {
423+ ip. octets ( ) [ 0 ] == 192
424+ && ip. octets ( ) [ 1 ] == 0
425+ && ip. octets ( ) [ 2 ] == 0
426+ && ip. octets ( ) [ 3 ] != 9
427+ && ip. octets ( ) [ 3 ] != 10
435428}
436429
437- // Returns [`true`] if the multi-address IP appears to be globally reachable
438- pub fn is_multiaddr_global ( address : & Multiaddr ) -> bool {
439- address
440- . iter ( )
441- . any ( |protocol| matches ! ( protocol, libp2p:: multiaddr:: Protocol :: Ip4 ( ip) if is_global( ip) ) )
430+ /// Checks if the multi-address appears to be globally reachable
431+ pub fn is_global_address ( addr : & Multiaddr ) -> bool {
432+ for protocol in addr. iter ( ) {
433+ // Check private IPv4 ranges
434+ match protocol {
435+ Protocol :: Ip4 ( ip) => {
436+ // Also includes loopback (127.0.0.0/8) and link-local (169.254.0.0/16)
437+ if !ip. is_private ( )
438+ && !ip. is_loopback ( )
439+ && !ip. is_link_local ( )
440+ && !ip. is_documentation ( )
441+ && !ip. is_broadcast ( )
442+ && !is_reserved_ip ( & ip)
443+ {
444+ return true ;
445+ }
446+ } ,
447+ // Check private IPv6 ranges:
448+ Protocol :: Ip6 ( ip) => {
449+ // Also check for loopback and link-local
450+ if !ip. is_unique_local ( ) && !ip. is_loopback ( ) && !ip. is_unicast_link_local ( ) {
451+ return true ;
452+ }
453+ } ,
454+ // Check DNS entries for localhost (case insensitive)
455+ Protocol :: Dns ( host)
456+ | Protocol :: Dns4 ( host)
457+ | Protocol :: Dns6 ( host)
458+ | Protocol :: Dnsaddr ( host) => {
459+ let host = host. to_lowercase ( ) ;
460+ // Check for common private/local hostnames
461+ if host != "localhost" && !host. ends_with ( ".local" ) && !host. ends_with ( ".localhost" )
462+ {
463+ return true ;
464+ }
465+ } ,
466+ _ => continue ,
467+ }
468+ }
469+ false
442470}
443471
444472fn get_or_init_keypair ( cfg : & LibP2PConfig , db : impl Database ) -> Result < identity:: Keypair > {
@@ -505,19 +533,272 @@ pub fn extract_block_num(key: RecordKey) -> Result<u32> {
505533#[ cfg( test) ]
506534mod tests {
507535 use super :: * ;
508- use test_case:: test_case;
509-
510- #[ test_case( "/ip4/159.73.143.3/tcp/37000" => true ; "Global IPv4" ) ]
511- #[ test_case( "/ip4/192.168.0.1/tcp/37000" => false ; "Local (192.168) IPv4" ) ]
512- #[ test_case( "/ip4/172.16.10.11/tcp/37000" => false ; "Local (172.16) IPv4" ) ]
513- #[ test_case( "/ip4/127.0.0.1/tcp/37000" => false ; "Loopback IPv4" ) ]
514- #[ test_case( "" => false ; "Empty multiaddr" ) ]
515- fn test_is_multiaddr_global ( addr_str : & str ) -> bool {
516- let addr = if addr_str. is_empty ( ) {
517- Multiaddr :: empty ( )
518- } else {
519- addr_str. parse ( ) . unwrap ( )
520- } ;
521- is_multiaddr_global ( & addr)
536+
537+ #[ test]
538+ fn dht_key_parse_record_key ( ) {
539+ let row_key: DHTKey = RecordKey :: new ( & "1:2" ) . try_into ( ) . unwrap ( ) ;
540+ assert_eq ! ( row_key, DHTKey :: Row ( 1 , 2 ) ) ;
541+
542+ let cell_key: DHTKey = RecordKey :: new ( & "3:2:1" ) . try_into ( ) . unwrap ( ) ;
543+ assert_eq ! ( cell_key, DHTKey :: Cell ( 3 , 2 , 1 ) ) ;
544+
545+ let result: Result < DHTKey > = RecordKey :: new ( & "1:2:4:3" ) . try_into ( ) ;
546+ _ = result. unwrap_err ( ) ;
547+
548+ let result: Result < DHTKey > = RecordKey :: new ( & "123" ) . try_into ( ) ;
549+ _ = result. unwrap_err ( ) ;
550+ }
551+
552+ #[ test]
553+ fn test_ipv4_global ( ) {
554+ // Test public IPv4 addresses that should be considered global
555+ let global_addrs = vec ! [
556+ "/ip4/8.8.8.8/tcp/53" , // Google DNS
557+ "/ip4/1.1.1.1/tcp/53" , // Cloudflare DNS
558+ "/ip4/208.67.222.222/tcp/53" , // OpenDNS
559+ "/ip4/172.15.255.255/tcp/80" , // Just outside private range
560+ "/ip4/172.32.0.1/tcp/80" , // Just outside private range
561+ "/ip4/9.255.255.255/tcp/80" , // Just outside private range
562+ "/ip4/11.0.0.1/tcp/80" , // Just outside private range
563+ "/ip4/192.167.255.255/tcp/80" , // Just outside private range
564+ "/ip4/192.169.0.1/tcp/80" , // Just outside private range
565+ "/ip4/192.0.0.9/tcp/80" , // Globally reachable exception
566+ "/ip4/192.0.0.10/tcp/80" , // Globally reachable exception
567+ ] ;
568+
569+ for addr_str in global_addrs {
570+ let addr = Multiaddr :: from_str ( addr_str) . unwrap ( ) ;
571+ assert ! (
572+ is_global_address( & addr) ,
573+ "Failed to detect global IP: {}" ,
574+ addr_str
575+ ) ;
576+ }
577+ }
578+
579+ #[ test]
580+ fn test_ipv4_non_global ( ) {
581+ // Test private/local IPv4 addresses that should NOT be considered global
582+ let non_global_addrs = vec ! [
583+ // Private ranges
584+ "/ip4/10.0.0.1/tcp/8080" ,
585+ "/ip4/172.16.0.1/tcp/8080" ,
586+ "/ip4/192.168.1.1/tcp/8080" ,
587+ // Loopback
588+ "/ip4/127.0.0.1/tcp/8080" ,
589+ // Link-local
590+ "/ip4/169.254.1.1/tcp/8080" ,
591+ // Documentation
592+ "/ip4/192.0.2.1/tcp/8080" ,
593+ "/ip4/198.51.100.1/tcp/8080" ,
594+ "/ip4/203.0.113.1/tcp/8080" ,
595+ // Broadcast
596+ "/ip4/255.255.255.255/tcp/8080" ,
597+ // Reserved (except the globally reachable ones)
598+ "/ip4/192.0.0.1/tcp/8080" ,
599+ "/ip4/192.0.0.100/tcp/8080" ,
600+ ] ;
601+
602+ for addr_str in non_global_addrs {
603+ let addr = Multiaddr :: from_str ( addr_str) . unwrap ( ) ;
604+ assert ! (
605+ !is_global_address( & addr) ,
606+ "Incorrectly identified as global: {}" ,
607+ addr_str
608+ ) ;
609+ }
610+ }
611+
612+ #[ test]
613+ fn test_ipv6_global ( ) {
614+ // Test global IPv6 addresses
615+ let global_addrs = vec ! [
616+ "/ip6/2001:db8::1/tcp/8080" , // Documentation range (but considered global by std lib)
617+ "/ip6/2001:4860:4860::8888/tcp/53" , // Google DNS
618+ "/ip6/2606:4700:4700::1111/tcp/53" , // Cloudflare DNS
619+ "/ip6/2001::/tcp/80" , // Global unicast
620+ "/ip6/ff02::1/tcp/8080" , // Multicast (global)
621+ "/ip6/fec0::1/tcp/8080" , // Just outside link-local range
622+ "/ip6/fbff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/tcp/8080" , // Just outside unique local
623+ "/ip6/fe00::1/tcp/8080" , // Just outside unique local
624+ ] ;
625+
626+ for addr_str in global_addrs {
627+ let addr = Multiaddr :: from_str ( addr_str) . unwrap ( ) ;
628+ assert ! (
629+ is_global_address( & addr) ,
630+ "Failed to detect global IPv6: {}" ,
631+ addr_str
632+ ) ;
633+ }
634+ }
635+
636+ #[ test]
637+ fn test_ipv6_non_global ( ) {
638+ // Test non-global IPv6 addresses
639+ let non_global_addrs = vec ! [
640+ // Unique local addresses (fc00::/7)
641+ "/ip6/fc00::1/tcp/8080" ,
642+ "/ip6/fd00::1/tcp/8080" ,
643+ // Link-local addresses (fe80::/10)
644+ "/ip6/fe80::1/tcp/8080" ,
645+ "/ip6/fe80::dead:beef/tcp/8080" ,
646+ // Loopback address (::1)
647+ "/ip6/::1/tcp/8080" ,
648+ ] ;
649+
650+ for addr_str in non_global_addrs {
651+ let addr = Multiaddr :: from_str ( addr_str) . unwrap ( ) ;
652+ assert ! (
653+ !is_global_address( & addr) ,
654+ "Incorrectly identified as global: {}" ,
655+ addr_str
656+ ) ;
657+ }
658+ }
659+
660+ #[ test]
661+ fn test_dns_global ( ) {
662+ // Test global DNS addresses
663+ let global_dns_addrs = vec ! [
664+ "/dns/example.com/tcp/80" ,
665+ "/dns4/google.com/tcp/443" ,
666+ "/dns6/ipv6.google.com/tcp/443" ,
667+ "/dnsaddr/bootstrap.libp2p.io/tcp/443" ,
668+ "/dns/peer.example.org/tcp/4001" ,
669+ "/dns/github.com/tcp/443" ,
670+ "/dns/stackoverflow.com/tcp/443" ,
671+ "/dns/cloudflare.com/tcp/80" ,
672+ ] ;
673+
674+ for addr_str in global_dns_addrs {
675+ let addr = Multiaddr :: from_str ( addr_str) . unwrap ( ) ;
676+ assert ! (
677+ is_global_address( & addr) ,
678+ "Failed to detect global DNS: {}" ,
679+ addr_str
680+ ) ;
681+ }
682+ }
683+
684+ #[ test]
685+ fn test_dns_non_global ( ) {
686+ // Test non-global DNS addresses
687+ let non_global_dns_addrs = vec ! [
688+ "/dns/localhost/tcp/8080" ,
689+ "/dns4/localhost/tcp/8080" ,
690+ "/dns/LOCALHOST/tcp/8080" , // Case insensitive
691+ "/dns/myserver.local/tcp/8080" ,
692+ "/dns/printer.local/tcp/631" ,
693+ "/dns/device.localhost/tcp/80" ,
694+ "/dns/test.localhost/tcp/443" ,
695+ ] ;
696+
697+ for addr_str in non_global_dns_addrs {
698+ let addr = Multiaddr :: from_str ( addr_str) . unwrap ( ) ;
699+ assert ! (
700+ !is_global_address( & addr) ,
701+ "Incorrectly identified as global: {}" ,
702+ addr_str
703+ ) ;
704+ }
705+ }
706+
707+ #[ test]
708+ fn test_multiaddr_without_ip_or_dns ( ) {
709+ // Test multiaddresses that don't contain IP or DNS addresses
710+ let non_ip_addrs = vec ! [
711+ "/tcp/8080" ,
712+ "/udp/53" ,
713+ "/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N" ,
714+ "/memory/1234" ,
715+ ] ;
716+
717+ for addr_str in non_ip_addrs {
718+ let addr = Multiaddr :: from_str ( addr_str) . unwrap ( ) ;
719+ assert ! (
720+ !is_global_address( & addr) ,
721+ "Non-IP/DNS address incorrectly identified as global: {}" ,
722+ addr_str
723+ ) ;
724+ }
725+ }
726+
727+ #[ test]
728+ fn test_mixed_protocols_global ( ) {
729+ // Test that the function returns true if ANY protocol in the chain is global
730+ let mixed_addrs = vec ! [
731+ // Global IP first, then private DNS - should be global
732+ "/ip4/8.8.8.8/dns/localhost/tcp/80" ,
733+ // Private IP first, then global DNS - should be global
734+ "/ip4/192.168.1.1/dns/example.com/tcp/80" ,
735+ // Multiple global protocols - should be global
736+ "/ip4/1.1.1.1/dns/google.com/tcp/80" ,
737+ ] ;
738+
739+ for addr_str in mixed_addrs {
740+ let addr = Multiaddr :: from_str ( addr_str) . unwrap ( ) ;
741+ assert ! (
742+ is_global_address( & addr) ,
743+ "Mixed protocol address should be global: {}" ,
744+ addr_str
745+ ) ;
746+ }
747+ }
748+
749+ #[ test]
750+ fn test_mixed_protocols_non_global ( ) {
751+ // Test addresses where all IP/DNS protocols are non-global
752+ let non_global_addrs = vec ! [
753+ "/ip4/192.168.1.1/dns/localhost/tcp/80" ,
754+ "/ip4/10.0.0.1/dns/device.local/tcp/80" ,
755+ "/ip6/fc00::1/dns/test.localhost/tcp/80" ,
756+ ] ;
757+
758+ for addr_str in non_global_addrs {
759+ let addr = Multiaddr :: from_str ( addr_str) . unwrap ( ) ;
760+ assert ! (
761+ !is_global_address( & addr) ,
762+ "All-private protocol address should not be global: {}" ,
763+ addr_str
764+ ) ;
765+ }
766+ }
767+
768+ #[ test]
769+ fn test_is_reserved_ip_integration_with_is_global ( ) {
770+ use std:: net:: Ipv4Addr ;
771+
772+ // Test how reserved IPs work with is_global_address
773+ let test_cases = vec ! [
774+ ( "192.0.0.1" , true , false ) , // Reserved and should NOT be global
775+ ( "192.0.0.9" , false , true ) , // Not reserved and IS global
776+ ( "192.0.0.10" , false , true ) , // Not reserved and IS global
777+ ( "192.0.0.100" , true , false ) , // Reserved and should NOT be global
778+ ] ;
779+
780+ for ( ip_str, is_reserved_expected, should_be_global) in test_cases {
781+ let ip: Ipv4Addr = ip_str. parse ( ) . unwrap ( ) ;
782+ let multiaddr_str = format ! ( "/ip4/{}/tcp/80" , ip_str) ;
783+ let addr = Multiaddr :: from_str ( & multiaddr_str) . unwrap ( ) ;
784+
785+ // Test is_reserved_ip function
786+ assert_eq ! (
787+ is_reserved_ip( & ip) ,
788+ is_reserved_expected,
789+ "is_reserved_ip failed for {}" ,
790+ ip_str
791+ ) ;
792+
793+ // Test is_global_address function
794+ assert_eq ! (
795+ is_global_address( & addr) ,
796+ should_be_global,
797+ "is_global_address test failed for {}: expected {}, got {}" ,
798+ ip_str,
799+ should_be_global,
800+ is_global_address( & addr)
801+ ) ;
802+ }
522803 }
523804}
0 commit comments