@@ -555,6 +555,7 @@ func (m *Mesh) dialAndPump(parentCtx context.Context, peer Peer, sess *peerSessi
555555 sess .mu .Lock ()
556556 sess .consumedAuth = [2 ]string {}
557557 sess .consumedAt = time.Time {}
558+ sess .linkUp = false
558559 sess .abortAttempt = abort
559560 sess .mu .Unlock ()
560561 defer func () {
@@ -565,6 +566,7 @@ func (m *Mesh) dialAndPump(parentCtx context.Context, peer Peer, sess *peerSessi
565566 if sess .abortAttempt != nil {
566567 sess .abortAttempt = nil
567568 }
569+ sess .linkUp = false
568570 sess .mu .Unlock ()
569571 }()
570572
@@ -694,6 +696,9 @@ func (m *Mesh) dialAndPump(parentCtx context.Context, peer Peer, sess *peerSessi
694696
695697 log .Printf ("[%s] link up — peer vip=127.50.0.%d, allowlist=%v (udp=%d, tcp=%d)" ,
696698 peer .ID , peer .Vip , allowlistPorts (cfg .Allowlist ), udpPortCount , len (tcpListeners ))
699+ sess .mu .Lock ()
700+ sess .linkUp = true
701+ sess .mu .Unlock ()
697702 if m .onLinkUp != nil {
698703 m .onLinkUp (peer .ID )
699704 }
@@ -1057,10 +1062,27 @@ type peerSession struct {
10571062 agent * ice.Agent // replaced on every dialICE attempt; guarded by mu
10581063 consumedAuth [2 ]string // ufrag,pwd dialICE pulled off authCh; zero before
10591064 consumedAt time.Time // when consumedAuth was last set; zero before
1065+ linkUp bool // true after QUIC + forwarders are established
10601066 abortAttempt context.CancelFunc // non-nil for the duration of an attempt
10611067 candidateBuffer []ice.Candidate // remote candidates seen since last peer-auth; replayed into each new agent
10621068}
10631069
1070+ const (
1071+ // pion transitions Checking -> Disconnected after this period without
1072+ // a viable selected pair. Disconnected is log-only for mesh-conn.
1073+ iceDisconnectedTimeout = 30 * time .Second
1074+
1075+ // pion transitions Disconnected -> Failed after this additional
1076+ // period. The ICE state callback aborts the attempt on Failed.
1077+ iceFailedTimeout = 60 * time .Second
1078+
1079+ // dialAttemptTimeout is only a guardrail for a stuck pion call path:
1080+ // it must be longer than pion's own failure budget so mesh-conn does
1081+ // not cancel a legitimate slow ICE attempt before the ICE state
1082+ // machine reaches Failed.
1083+ dialAttemptTimeout = iceDisconnectedTimeout + iceFailedTimeout + 15 * time .Second
1084+ )
1085+
10641086// supersedeGrace is the minimum time an attempt must have been
10651087// running with consumedAuth set before pollLoop will supersede it on
10661088// a fresh peer auth. Inside this window we trust the in-flight ICE
@@ -1157,10 +1179,6 @@ func (m *Mesh) dialICE(remoteID string, sess *peerSession, attemptCtx context.Co
11571179 // Disconnected stretch in the log timeline. We keep it at
11581180 // 30 s rather than 0 so the existing log signal is preserved
11591181 // if pion ever does have a sustained outage but recovers.
1160- const (
1161- iceDisconnectedTimeout = 30 * time .Second
1162- iceFailedTimeout = 60 * time .Second
1163- )
11641182 disconnectedTimeout := iceDisconnectedTimeout
11651183 failedTimeout := iceFailedTimeout
11661184 agentCfg := & ice.AgentConfig {
@@ -1282,10 +1300,10 @@ waitRemoteAuth:
12821300 m .onAttemptStarted (remoteID )
12831301 }
12841302
1285- // 60s safety net; pion's default connectivity-check window is 30s
1286- // so if Dial/Accept hasn't succeeded by then the state callback
1287- // will have already fired abortAttempt .
1288- dialTimer := time .AfterFunc (60 * time . Second , func () {
1303+ // Safety net for a wedged Dial/Accept call path. This intentionally
1304+ // exceeds pion's Checking+Disconnected failure budget; ordinary
1305+ // failure is handled by the ICE Failed state callback above .
1306+ dialTimer := time .AfterFunc (dialAttemptTimeout , func () {
12891307 sess .mu .Lock ()
12901308 if sess .abortAttempt != nil {
12911309 sess .abortAttempt ()
@@ -1419,7 +1437,10 @@ func (m *Mesh) pollLoop(ctx context.Context) {
14191437 sess .mu .Lock ()
14201438 sess .candidateBuffer = nil
14211439 zero := [2 ]string {}
1422- if sess .abortAttempt != nil &&
1440+ if sess .linkUp {
1441+ log .Printf ("[%s] fresh auth (ufrag=%s) queued for next attempt; current link is up" ,
1442+ msg .From , newAuth [0 ])
1443+ } else if sess .abortAttempt != nil &&
14231444 sess .consumedAuth != zero &&
14241445 sess .consumedAuth != newAuth {
14251446 elapsed := time .Since (sess .consumedAt )
0 commit comments