From 548471717a2548d7110e86af08771dcdefd0c599 Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Sat, 20 Jun 2026 20:25:18 +0800 Subject: [PATCH] fix(dialer): detect dead QUIC sessions in ICMP/QUIC dialer caches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a QUIC session's MaxIdleTimeout expires, the underlying *quic.Conn dies but the dead quicSession stays in the sessions map. The next Dial() finds it and calls session.GetConn() which calls OpenStreamSync(context.Background()) against the dead connection — blocking indefinitely. Add IsClosed() to both quicSession types (using the session context's Done() channel) and add the liveness guard in both Dial() methods, matching the existing pattern in KCP and MASQUE dialers. Fixes go-gost/gost#225 --- dialer/icmp/conn.go | 9 +++++++++ dialer/icmp/dialer.go | 4 ++++ dialer/quic/conn.go | 9 +++++++++ dialer/quic/dialer.go | 4 ++++ 4 files changed, 26 insertions(+) diff --git a/dialer/icmp/conn.go b/dialer/icmp/conn.go index cac6aa7e..b6797bf2 100644 --- a/dialer/icmp/conn.go +++ b/dialer/icmp/conn.go @@ -23,6 +23,15 @@ func (session *quicSession) GetConn() (*quicConn, error) { }, nil } +func (session *quicSession) IsClosed() bool { + select { + case <-session.session.Context().Done(): + return true + default: + return false + } +} + func (session *quicSession) Close() error { return session.session.CloseWithError(quic.ApplicationErrorCode(0), "closed") } diff --git a/dialer/icmp/dialer.go b/dialer/icmp/dialer.go index dff41ad2..bd87d403 100644 --- a/dialer/icmp/dialer.go +++ b/dialer/icmp/dialer.go @@ -78,6 +78,10 @@ func (d *icmpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO defer d.sessionMutex.Unlock() session, ok := d.sessions[addr] + if session != nil && session.IsClosed() { + delete(d.sessions, addr) // session is dead + ok = false + } if !ok { options := &dialer.DialOptions{} for _, opt := range opts { diff --git a/dialer/quic/conn.go b/dialer/quic/conn.go index cac6aa7e..b6797bf2 100644 --- a/dialer/quic/conn.go +++ b/dialer/quic/conn.go @@ -23,6 +23,15 @@ func (session *quicSession) GetConn() (*quicConn, error) { }, nil } +func (session *quicSession) IsClosed() bool { + select { + case <-session.session.Context().Done(): + return true + default: + return false + } +} + func (session *quicSession) Close() error { return session.session.CloseWithError(quic.ApplicationErrorCode(0), "closed") } diff --git a/dialer/quic/dialer.go b/dialer/quic/dialer.go index 55812787..a12a1008 100644 --- a/dialer/quic/dialer.go +++ b/dialer/quic/dialer.go @@ -61,6 +61,10 @@ func (d *quicDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO defer d.sessionMutex.Unlock() session, ok := d.sessions[addr] + if session != nil && session.IsClosed() { + delete(d.sessions, addr) // session is dead + ok = false + } if !ok { options := &dialer.DialOptions{} for _, opt := range opts {