@@ -81,6 +81,11 @@ pub struct NetworkEndpoint {
8181 /// Box name (for DNS resolution).
8282 pub box_name : String ,
8383
84+ /// Additional DNS names that also resolve to this endpoint's IP (e.g. the
85+ /// bare Compose service name alongside the `{project}-{service}` box name).
86+ #[ serde( default ) ]
87+ pub aliases : Vec < String > ,
88+
8489 /// Assigned IPv4 address.
8590 pub ip_address : Ipv4Addr ,
8691
@@ -432,6 +437,17 @@ impl NetworkConfig {
432437
433438 /// Allocate an IP and register a new endpoint for a box.
434439 pub fn connect ( & mut self , box_id : & str , box_name : & str ) -> Result < NetworkEndpoint , String > {
440+ self . connect_with_aliases ( box_id, box_name, & [ ] )
441+ }
442+
443+ /// Connect a box, also registering extra DNS aliases that resolve to its IP
444+ /// (e.g. the bare Compose service name in addition to the box name).
445+ pub fn connect_with_aliases (
446+ & mut self ,
447+ box_id : & str ,
448+ box_name : & str ,
449+ aliases : & [ String ] ,
450+ ) -> Result < NetworkEndpoint , String > {
435451 if self . endpoints . contains_key ( box_id) {
436452 return Err ( format ! (
437453 "box '{}' is already connected to network '{}'" ,
@@ -447,6 +463,11 @@ impl NetworkConfig {
447463 let endpoint = NetworkEndpoint {
448464 box_id : box_id. to_string ( ) ,
449465 box_name : box_name. to_string ( ) ,
466+ aliases : aliases
467+ . iter ( )
468+ . filter ( |a| !a. is_empty ( ) && * a != box_name)
469+ . cloned ( )
470+ . collect ( ) ,
450471 ip_address : ip,
451472 mac_address : mac,
452473 } ;
@@ -487,7 +508,13 @@ impl NetworkConfig {
487508 self . endpoints
488509 . values ( )
489510 . filter ( |ep| ep. box_id != exclude_box_id)
490- . map ( |ep| ( ep. ip_address . to_string ( ) , ep. box_name . clone ( ) ) )
511+ . flat_map ( |ep| {
512+ let ip = ep. ip_address . to_string ( ) ;
513+ // The box name plus any aliases (e.g. the bare service name) all
514+ // resolve to this peer's IP.
515+ std:: iter:: once ( ( ip. clone ( ) , ep. box_name . clone ( ) ) )
516+ . chain ( ep. aliases . iter ( ) . map ( move |a| ( ip. clone ( ) , a. clone ( ) ) ) )
517+ } )
491518 . collect ( )
492519 }
493520
@@ -783,6 +810,35 @@ mod tests {
783810 assert ! ( peers. iter( ) . any( |( _, name) | name == "db" ) ) ;
784811 }
785812
813+ #[ test]
814+ fn test_connect_with_aliases_resolvable_as_peer ( ) {
815+ let mut net = NetworkConfig :: new ( "mynet" , "10.88.0.0/24" ) . unwrap ( ) ;
816+ // Compose-style: box name is "proj-db", bare service name "db" is an alias.
817+ let ep = net
818+ . connect_with_aliases ( "box-db" , "proj-db" , & [ "db" . to_string ( ) ] )
819+ . unwrap ( ) ;
820+ assert_eq ! ( ep. aliases, vec![ "db" . to_string( ) ] ) ;
821+ net. connect ( "box-web" , "proj-web" ) . unwrap ( ) ;
822+
823+ let peers = net. peer_endpoints ( "box-web" ) ;
824+ let db_ip = ep. ip_address . to_string ( ) ;
825+ // Both the box name AND the bare alias resolve to db's IP.
826+ assert ! ( peers
827+ . iter( )
828+ . any( |( ip, name) | ip == & db_ip && name == "proj-db" ) ) ;
829+ assert ! ( peers. iter( ) . any( |( ip, name) | ip == & db_ip && name == "db" ) ) ;
830+ }
831+
832+ #[ test]
833+ fn test_connect_alias_skips_empty_and_self_duplicate ( ) {
834+ let mut net = NetworkConfig :: new ( "mynet" , "10.88.0.0/24" ) . unwrap ( ) ;
835+ // An alias equal to the box name or empty is dropped.
836+ let ep = net
837+ . connect_with_aliases ( "b" , "web" , & [ "" . to_string ( ) , "web" . to_string ( ) ] )
838+ . unwrap ( ) ;
839+ assert ! ( ep. aliases. is_empty( ) ) ;
840+ }
841+
786842 #[ test]
787843 fn test_peer_endpoints_empty_when_alone ( ) {
788844 let mut net = NetworkConfig :: new ( "mynet" , "10.88.0.0/24" ) . unwrap ( ) ;
@@ -821,13 +877,19 @@ mod tests {
821877 let ep = NetworkEndpoint {
822878 box_id : "abc123" . to_string ( ) ,
823879 box_name : "web" . to_string ( ) ,
880+ aliases : vec ! [ "app" . to_string( ) ] ,
824881 ip_address : Ipv4Addr :: new ( 10 , 88 , 0 , 2 ) ,
825882 mac_address : "02:42:0a:58:00:02" . to_string ( ) ,
826883 } ;
827884
828885 let json = serde_json:: to_string ( & ep) . unwrap ( ) ;
829886 let parsed: NetworkEndpoint = serde_json:: from_str ( & json) . unwrap ( ) ;
830887 assert_eq ! ( parsed, ep) ;
888+
889+ // Backward compat: older stored endpoints without `aliases` deserialize.
890+ let legacy = r#"{"box_id":"x","box_name":"web","ip_address":"10.88.0.3","mac_address":"02:42:0a:58:00:03"}"# ;
891+ let parsed_legacy: NetworkEndpoint = serde_json:: from_str ( legacy) . unwrap ( ) ;
892+ assert ! ( parsed_legacy. aliases. is_empty( ) ) ;
831893 }
832894
833895 // --- NetworkPolicy tests ---
0 commit comments