@@ -746,16 +746,26 @@ impl P2PNetwork {
746746
747747 // Add external addresses for announcement to other peers
748748 // This is crucial for NAT traversal - we announce our public IP instead of internal Docker IPs
749- for addr_str in & self . config . external_addrs {
750- match addr_str. parse :: < Multiaddr > ( ) {
751- Ok ( addr) => {
752- swarm. add_external_address ( addr. clone ( ) ) ;
753- info ! ( addr = %addr, "Added external address for announcement" ) ;
754- }
755- Err ( e) => {
756- error ! ( addr = %addr_str, error = %e, "Invalid external address" ) ;
749+ if !self . config . external_addrs . is_empty ( ) {
750+ for addr_str in & self . config . external_addrs {
751+ match addr_str. parse :: < Multiaddr > ( ) {
752+ Ok ( addr) => {
753+ swarm. add_external_address ( addr. clone ( ) ) ;
754+ info ! ( addr = %addr, "Added external address for announcement" ) ;
755+ }
756+ Err ( e) => {
757+ error ! ( addr = %addr_str, error = %e, "Invalid external address" ) ;
758+ }
757759 }
758760 }
761+ } else {
762+ // Auto-detect public IP if no external address configured
763+ if let Some ( external_addr) = detect_public_ip ( ) . await {
764+ swarm. add_external_address ( external_addr. clone ( ) ) ;
765+ info ! ( addr = %external_addr, "Auto-detected public IP for announcement" ) ;
766+ } else {
767+ warn ! ( "Could not detect public IP. Peers may not be able to connect directly. Set EXTERNAL_ADDR to configure manually." ) ;
768+ }
759769 }
760770
761771 // Connect to bootstrap peers
@@ -1138,6 +1148,93 @@ fn extract_peer_id(addr: &Multiaddr) -> Option<PeerId> {
11381148 } )
11391149}
11401150
1151+ /// Detect public IP address using external services
1152+ /// Returns a multiaddr with the detected IP and default P2P port
1153+ async fn detect_public_ip ( ) -> Option < Multiaddr > {
1154+ use crate :: config:: DEFAULT_P2P_PORT ;
1155+
1156+ // Try multiple services for reliability
1157+ let services = [
1158+ "https://api.ipify.org" ,
1159+ "https://ifconfig.me/ip" ,
1160+ "https://icanhazip.com" ,
1161+ "https://ipinfo.io/ip" ,
1162+ ] ;
1163+
1164+ for service in services {
1165+ match tokio:: time:: timeout (
1166+ Duration :: from_secs ( 5 ) ,
1167+ fetch_public_ip ( service) ,
1168+ ) . await {
1169+ Ok ( Some ( ip) ) => {
1170+ // Validate it's a public IP (not private/local)
1171+ if is_public_ip ( & ip) {
1172+ let addr_str = format ! ( "/ip4/{}/tcp/{}" , ip, DEFAULT_P2P_PORT ) ;
1173+ if let Ok ( addr) = addr_str. parse :: < Multiaddr > ( ) {
1174+ return Some ( addr) ;
1175+ }
1176+ }
1177+ }
1178+ Ok ( None ) => continue ,
1179+ Err ( _) => {
1180+ debug ! ( service = %service, "Timeout detecting public IP" ) ;
1181+ continue ;
1182+ }
1183+ }
1184+ }
1185+
1186+ None
1187+ }
1188+
1189+ /// Fetch public IP from a service using curl
1190+ async fn fetch_public_ip ( url : & str ) -> Option < String > {
1191+ let output = tokio:: process:: Command :: new ( "curl" )
1192+ . args ( [ "-s" , "--max-time" , "3" , url] )
1193+ . output ( )
1194+ . await
1195+ . ok ( ) ?;
1196+
1197+ if output. status . success ( ) {
1198+ let ip = String :: from_utf8_lossy ( & output. stdout ) . trim ( ) . to_string ( ) ;
1199+ // Validate it looks like an IP
1200+ if ip. parse :: < std:: net:: Ipv4Addr > ( ) . is_ok ( ) {
1201+ return Some ( ip) ;
1202+ }
1203+ }
1204+
1205+ None
1206+ }
1207+
1208+ /// Check if an IP address is public (not private/local/reserved)
1209+ fn is_public_ip ( ip : & str ) -> bool {
1210+ if let Ok ( addr) = ip. parse :: < std:: net:: Ipv4Addr > ( ) {
1211+ let octets = addr. octets ( ) ;
1212+ // Private ranges: 10.x.x.x, 172.16-31.x.x, 192.168.x.x
1213+ // Loopback: 127.x.x.x
1214+ // Link-local: 169.254.x.x
1215+ if octets[ 0 ] == 10 {
1216+ return false ;
1217+ }
1218+ if octets[ 0 ] == 172 && ( 16 ..=31 ) . contains ( & octets[ 1 ] ) {
1219+ return false ;
1220+ }
1221+ if octets[ 0 ] == 192 && octets[ 1 ] == 168 {
1222+ return false ;
1223+ }
1224+ if octets[ 0 ] == 127 {
1225+ return false ;
1226+ }
1227+ if octets[ 0 ] == 169 && octets[ 1 ] == 254 {
1228+ return false ;
1229+ }
1230+ if octets[ 0 ] == 0 {
1231+ return false ;
1232+ }
1233+ return true ;
1234+ }
1235+ false
1236+ }
1237+
11411238/// Network runner that processes swarm events
11421239pub struct NetworkRunner {
11431240 network : Arc < P2PNetwork > ,
0 commit comments