fix(dialer): prevent hang on HTTP+ICMP by enabling QUIC keepalive by default
The ICMP dialer required explicit keepalive:true to send QUIC PING frames, while the regular QUIC dialer enabled keepalive by default (10s period). Without PING frames, quic-go's 30s idle timeout silently killed connections, causing HTTP+ICMP tunnels to hang after periods of inactivity. Fixes go-gost/gost#352 Also in this commit: - Release session mutex before calling GetConn() in both ICMP and QUIC dialers, so a dead session doesn't block all Dial() calls while OpenStreamSync blocks - Add 30s timeout context to OpenStreamSync to prevent indefinite blocking if the QUIC session dies between liveness check and stream open - Guard session cache deletion with pointer identity check to avoid evicting a newly created session during a race
This commit is contained in:
+4
-1
@@ -3,6 +3,7 @@ package quic
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/quic-go/quic-go"
|
||||||
)
|
)
|
||||||
@@ -12,7 +13,9 @@ type quicSession struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (session *quicSession) GetConn() (*quicConn, error) {
|
func (session *quicSession) GetConn() (*quicConn, error) {
|
||||||
stream, err := session.session.OpenStreamSync(context.Background())
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
stream, err := session.session.OpenStreamSync(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ func (d *icmpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
|||||||
}
|
}
|
||||||
|
|
||||||
d.sessionMutex.Lock()
|
d.sessionMutex.Lock()
|
||||||
defer d.sessionMutex.Unlock()
|
|
||||||
|
|
||||||
session, ok := d.sessions[addr]
|
session, ok := d.sessions[addr]
|
||||||
if session != nil && session.IsClosed() {
|
if session != nil && session.IsClosed() {
|
||||||
@@ -95,6 +94,7 @@ func (d *icmpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
|||||||
pc, err = icmp_pkg.ListenPacket("ip4:icmp", "")
|
pc, err = icmp_pkg.ListenPacket("ip4:icmp", "")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
d.sessionMutex.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,16 +109,22 @@ func (d *icmpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
d.logger.Error(err)
|
d.logger.Error(err)
|
||||||
pc.Close()
|
pc.Close()
|
||||||
|
d.sessionMutex.Unlock()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.sessions[addr] = session
|
d.sessions[addr] = session
|
||||||
}
|
}
|
||||||
|
d.sessionMutex.Unlock()
|
||||||
|
|
||||||
conn, err = session.GetConn()
|
conn, err = session.GetConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
d.sessionMutex.Lock()
|
||||||
|
if d.sessions[addr] == session {
|
||||||
|
delete(d.sessions, addr)
|
||||||
|
}
|
||||||
session.Close()
|
session.Close()
|
||||||
delete(d.sessions, addr)
|
d.sessionMutex.Unlock()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-4
@@ -14,14 +14,21 @@ type metadata struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *icmpDialer) parseMetadata(md mdata.Metadata) (err error) {
|
func (d *icmpDialer) parseMetadata(md mdata.Metadata) (err error) {
|
||||||
if mdutil.GetBool(md, "keepalive") {
|
const (
|
||||||
d.md.keepAlivePeriod = mdutil.GetDuration(md, "ttl")
|
keepAlive = "keepAlive"
|
||||||
|
keepAlivePeriod = "ttl"
|
||||||
|
handshakeTimeout = "handshakeTimeout"
|
||||||
|
maxIdleTimeout = "maxIdleTimeout"
|
||||||
|
)
|
||||||
|
|
||||||
|
if md == nil || !md.IsExists(keepAlive) || mdutil.GetBool(md, keepAlive) {
|
||||||
|
d.md.keepAlivePeriod = mdutil.GetDuration(md, keepAlivePeriod)
|
||||||
if d.md.keepAlivePeriod <= 0 {
|
if d.md.keepAlivePeriod <= 0 {
|
||||||
d.md.keepAlivePeriod = 10 * time.Second
|
d.md.keepAlivePeriod = 10 * time.Second
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d.md.handshakeTimeout = mdutil.GetDuration(md, "handshakeTimeout")
|
d.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout)
|
||||||
d.md.maxIdleTimeout = mdutil.GetDuration(md, "maxIdleTimeout")
|
d.md.maxIdleTimeout = mdutil.GetDuration(md, maxIdleTimeout)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -3,6 +3,7 @@ package quic
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/quic-go/quic-go"
|
||||||
)
|
)
|
||||||
@@ -12,7 +13,9 @@ type quicSession struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (session *quicSession) GetConn() (*quicConn, error) {
|
func (session *quicSession) GetConn() (*quicConn, error) {
|
||||||
stream, err := session.session.OpenStreamSync(context.Background())
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
stream, err := session.session.OpenStreamSync(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ func (d *quicDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
|||||||
}
|
}
|
||||||
|
|
||||||
d.sessionMutex.Lock()
|
d.sessionMutex.Lock()
|
||||||
defer d.sessionMutex.Unlock()
|
|
||||||
|
|
||||||
session, ok := d.sessions[addr]
|
session, ok := d.sessions[addr]
|
||||||
if session != nil && session.IsClosed() {
|
if session != nil && session.IsClosed() {
|
||||||
@@ -73,11 +72,13 @@ func (d *quicDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
|||||||
|
|
||||||
c, err := options.Dialer.Dial(ctx, "udp", "")
|
c, err := options.Dialer.Dial(ctx, "udp", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
d.sessionMutex.Unlock()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pc, ok := c.(net.PacketConn)
|
pc, ok := c.(net.PacketConn)
|
||||||
if !ok {
|
if !ok {
|
||||||
c.Close()
|
c.Close()
|
||||||
|
d.sessionMutex.Unlock()
|
||||||
return nil, errors.New("quic: wrong connection type")
|
return nil, errors.New("quic: wrong connection type")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,16 +90,22 @@ func (d *quicDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
d.logger.Error(err)
|
d.logger.Error(err)
|
||||||
pc.Close()
|
pc.Close()
|
||||||
|
d.sessionMutex.Unlock()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
d.sessions[addr] = session
|
d.sessions[addr] = session
|
||||||
}
|
}
|
||||||
|
d.sessionMutex.Unlock()
|
||||||
|
|
||||||
conn, err = session.GetConn()
|
conn, err = session.GetConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
d.sessionMutex.Lock()
|
||||||
|
if d.sessions[addr] == session {
|
||||||
|
delete(d.sessions, addr)
|
||||||
|
}
|
||||||
session.Close()
|
session.Close()
|
||||||
delete(d.sessions, addr)
|
d.sessionMutex.Unlock()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user