@@ -1043,6 +1043,94 @@ mod tests {
10431043 Ok ( ( ) )
10441044 }
10451045
1046+ /// Reproduces <https://github.com/n0-computer/iroh/issues/4114> with a
1047+ /// manual retry-then-validate accept loop (no Router / incoming filter).
1048+ ///
1049+ /// Both peers bind the wildcard address (every interface, dual-stack);
1050+ /// the client dials all of the server's discovered addresses. On a
1051+ /// multi-homed host the client sprays from several source IPs, so a retry
1052+ /// token minted for one source can be answered from another →
1053+ /// `INVALID_TOKEN`. Runs many handshakes and reports the failure rate.
1054+ ///
1055+ /// On this (April 2026) code we expect failures > 0 on a multi-homed box.
1056+ #[ tokio:: test]
1057+ #[ traced_test]
1058+ async fn addr_retry_multihomed ( ) -> Result {
1059+ use crate :: endpoint:: IncomingAddr ;
1060+
1061+ const ITERS : usize = 10 ;
1062+ let mut failures = 0usize ;
1063+ let mut first_err: Option < String > = None ;
1064+ let mut ip_count = 0usize ;
1065+ let mut max_client_sources = 0usize ;
1066+ let mut multi_source_iters = 0usize ;
1067+
1068+ for i in 0 ..ITERS {
1069+ let server = Endpoint :: builder ( presets:: Minimal )
1070+ . alpns ( vec ! [ ECHO_ALPN . to_vec( ) ] )
1071+ . bind ( )
1072+ . await ?;
1073+ let client = Endpoint :: builder ( presets:: Minimal ) . bind ( ) . await ?;
1074+
1075+ let addr = server. addr ( ) ;
1076+ ip_count = addr. addrs . iter ( ) . filter ( |a| a. is_ip ( ) ) . count ( ) ;
1077+
1078+ let sources = Arc :: new ( std:: sync:: Mutex :: new ( std:: collections:: HashSet :: <
1079+ std:: net:: IpAddr ,
1080+ > :: new ( ) ) ) ;
1081+ let sources_task = sources. clone ( ) ;
1082+ let server_accept = server. clone ( ) ;
1083+ let accept = tokio:: spawn ( async move {
1084+ while let Some ( incoming) = server_accept. accept ( ) . await {
1085+ if let IncomingAddr :: Ip ( sa) = incoming. remote_addr ( ) {
1086+ sources_task. lock ( ) . unwrap ( ) . insert ( sa. ip ( ) ) ;
1087+ }
1088+ if incoming. remote_addr_validated ( ) {
1089+ if let Ok ( accepting) = incoming. accept ( ) {
1090+ let _ = accepting. await ;
1091+ }
1092+ } else {
1093+ let _ = incoming. retry ( ) ;
1094+ }
1095+ }
1096+ } ) ;
1097+
1098+ let conn =
1099+ tokio:: time:: timeout ( Duration :: from_secs ( 8 ) , client. connect ( addr, ECHO_ALPN ) )
1100+ . await ;
1101+ match conn {
1102+ Ok ( Ok ( _) ) => { }
1103+ Ok ( Err ( err) ) => {
1104+ failures += 1 ;
1105+ first_err. get_or_insert_with ( || format ! ( "iter {i}: {err:#}" ) ) ;
1106+ }
1107+ Err ( _) => {
1108+ failures += 1 ;
1109+ first_err. get_or_insert_with ( || format ! ( "iter {i}: connect timed out" ) ) ;
1110+ }
1111+ }
1112+
1113+ accept. abort ( ) ;
1114+ client. close ( ) . await ;
1115+ server. close ( ) . await ;
1116+
1117+ let n = sources. lock ( ) . unwrap ( ) . len ( ) ;
1118+ max_client_sources = max_client_sources. max ( n) ;
1119+ if n >= 2 {
1120+ multi_source_iters += 1 ;
1121+ }
1122+ }
1123+
1124+ // Demo branch: fail unconditionally so the tally prints in CI output
1125+ // (a passing test swallows it). `multi_source_iters` tells us whether
1126+ // the runner was actually multi-homed; `failures` is the #4114 signal.
1127+ panic ! (
1128+ "addr_retry_multihomed: failures={failures}/{ITERS} \
1129+ server_ip_count={ip_count} max_client_sources={max_client_sources} \
1130+ multi_source_iters={multi_source_iters}/{ITERS} first_err={first_err:?}",
1131+ ) ;
1132+ }
1133+
10461134 /// Verify that returning `Retry` for a relay connection also causes
10471135 /// the remote to retry with a token. The "validation" has no
10481136 /// security meaning over a relay, but it does impose a round-trip
0 commit comments