fix: DoQ closes udp immediately.

This commit is contained in:
Skyxim 2022-07-15 21:54:02 +08:00
parent 947d9d4560
commit a73e690172

View file

@ -25,9 +25,10 @@ var bytesPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
type quicClient struct { type quicClient struct {
addr string addr string
r *Resolver r *Resolver
session quic.Connection connection quic.Connection
proxyAdapter string proxyAdapter string
sync.RWMutex // protects session and bytesPool udp net.PacketConn
sync.RWMutex // protects connection and bytesPool
} }
func newDOQ(r *Resolver, addr, proxyAdapter string) *quicClient { func newDOQ(r *Resolver, addr, proxyAdapter string) *quicClient {
@ -91,44 +92,43 @@ func isActive(s quic.Connection) bool {
} }
} }
// getSession - opens or returns an existing quic.Connection // getConnection - opens or returns an existing quic.Connection
// useCached - if true and cached session exists, return it right away // useCached - if true and cached connection exists, return it right away
// otherwise - forcibly creates a new session // otherwise - forcibly creates a new connection
func (dc *quicClient) getSession(ctx context.Context) (quic.Connection, error) { func (dc *quicClient) getConnection(ctx context.Context) (quic.Connection, error) {
var session quic.Connection var connection quic.Connection
dc.RLock() dc.RLock()
session = dc.session connection = dc.connection
if session != nil && isActive(session) {
if connection != nil && isActive(connection) {
dc.RUnlock() dc.RUnlock()
return session, nil return connection, nil
}
if session != nil {
// we're recreating the session, let's create a new one
_ = session.CloseWithError(0, "")
} }
dc.RUnlock() dc.RUnlock()
dc.Lock() dc.Lock()
defer dc.Unlock() defer dc.Unlock()
connection = dc.connection
var err error if connection != nil {
session, err = dc.openSession(ctx) if isActive(connection) {
if err != nil { return connection, nil
// This does not look too nice, but QUIC (or maybe quic-go) } else {
// doesn't seem stable enough. _ = connection.CloseWithError(quic.ApplicationErrorCode(0), "")
// Maybe retransmissions aren't fully implemented in quic-go?
// Anyways, the simple solution is to make a second try when
// it fails to open the QUIC session.
session, err = dc.openSession(ctx)
if err != nil {
return nil, err
} }
} }
dc.session = session
return session, nil var err error
connection, err = dc.openConnection(ctx)
dc.connection = connection
return connection, err
} }
func (dc *quicClient) openSession(ctx context.Context) (quic.Connection, error) { func (dc *quicClient) openConnection(ctx context.Context) (quic.Connection, error) {
if dc.udp != nil {
_ = dc.udp.Close()
}
tlsConfig := tlsC.GetGlobalFingerprintTLCConfig( tlsConfig := tlsC.GetGlobalFingerprintTLCConfig(
&tls.Config{ &tls.Config{
InsecureSkipVerify: false, InsecureSkipVerify: false,
@ -142,10 +142,10 @@ func (dc *quicClient) openSession(ctx context.Context) (quic.Connection, error)
ConnectionIDLength: 12, ConnectionIDLength: 12,
HandshakeIdleTimeout: time.Second * 8, HandshakeIdleTimeout: time.Second * 8,
MaxIncomingStreams: 4, MaxIncomingStreams: 4,
MaxIdleTimeout: time.Second * 45, MaxIdleTimeout: time.Second * 30,
} }
log.Debugln("opening session to %s", dc.addr) log.Debugln("opening new connection to %s", dc.addr)
var ( var (
udp net.PacketConn udp net.PacketConn
err error err error
@ -186,14 +186,15 @@ func (dc *quicClient) openSession(ctx context.Context) (quic.Connection, error)
session, err := quic.DialContext(ctx, udp, &udpAddr, host, tlsConfig, quicConfig) session, err := quic.DialContext(ctx, udp, &udpAddr, host, tlsConfig, quicConfig)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to open QUIC session: %w", err) return nil, fmt.Errorf("failed to open QUIC connection: %w", err)
} }
dc.udp = udp
return session, nil return session, nil
} }
func (dc *quicClient) openStream(ctx context.Context) (quic.Stream, error) { func (dc *quicClient) openStream(ctx context.Context) (quic.Stream, error) {
session, err := dc.getSession(ctx) session, err := dc.getConnection(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }