WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit d7834e7

Browse files
authored
Filtering for private range IPs in p2p discovery (#882)
1 parent 7aecc20 commit d7834e7

File tree

6 files changed

+328
-254
lines changed

6 files changed

+328
-254
lines changed

core/src/network/p2p.rs

Lines changed: 315 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -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

444472
fn 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)]
506534
mod 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

Comments
 (0)