forked from quic-go/quic-go
send a Public Reset when receiving a CHLO with the FHL2 tag
Fixes #411. Chrome sends the FHL2 when it wants to perform a head-of-line blocking experiment, introduced in QUIC version 36 (see https://codereview.chromium.org/2115033002). We don’t support this experiment. By sending a Public Reset when receiving this tag we force Chrome to use the TCP fallback.
This commit is contained in:
@@ -47,6 +47,11 @@ type cryptoSetupServer struct {
|
||||
|
||||
var _ CryptoSetup = &cryptoSetupServer{}
|
||||
|
||||
// ErrHOLExperiment is returned when the client sends the FHL2 tag in the CHLO
|
||||
// this is an expiremnt implemented by Chrome in QUIC 36, which we don't support
|
||||
// TODO: remove this when dropping support for QUIC 36
|
||||
var ErrHOLExperiment = qerr.Error(qerr.InvalidCryptoMessageParameter, "HOL experiment. Unsupported")
|
||||
|
||||
// NewCryptoSetup creates a new CryptoSetup instance for a server
|
||||
func NewCryptoSetup(
|
||||
connID protocol.ConnectionID,
|
||||
@@ -95,6 +100,10 @@ func (h *cryptoSetupServer) HandleCryptoStream() error {
|
||||
}
|
||||
|
||||
func (h *cryptoSetupServer) handleMessage(chloData []byte, cryptoData map[Tag][]byte) (bool, error) {
|
||||
if _, isHOLExperiment := cryptoData[TagFHL2]; isHOLExperiment {
|
||||
return false, ErrHOLExperiment
|
||||
}
|
||||
|
||||
sniSlice, ok := cryptoData[TagSNI]
|
||||
if !ok {
|
||||
return false, qerr.Error(qerr.CryptoMessageParameterNotFound, "SNI required")
|
||||
|
||||
@@ -222,6 +222,14 @@ var _ = Describe("Crypto setup", func() {
|
||||
binary.LittleEndian.PutUint64(xlct, crypto.HashCert(cert))
|
||||
})
|
||||
|
||||
It("doesn't support Chrome's head-of-line blocking experiment", func() {
|
||||
WriteHandshakeMessage(&stream.dataToRead, TagCHLO, map[Tag][]byte{
|
||||
TagFHL2: []byte("foobar"),
|
||||
})
|
||||
err := cs.HandleCryptoStream()
|
||||
Expect(err).To(MatchError(ErrHOLExperiment))
|
||||
})
|
||||
|
||||
It("generates REJ messages", func() {
|
||||
response, err := cs.handleInchoateCHLO("", bytes.Repeat([]byte{'a'}, protocol.ClientHelloMinimumSize), nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
@@ -50,6 +50,11 @@ const (
|
||||
// TagSFCW is the initial stream flow control receive window.
|
||||
TagSFCW Tag = 'S' + 'F'<<8 + 'C'<<16 + 'W'<<24
|
||||
|
||||
// TagFHL2 forces head of line blocking.
|
||||
// Chrome experiment (see https://codereview.chromium.org/2115033002)
|
||||
// unsupported by quic-go
|
||||
TagFHL2 Tag = 'F' + 'H'<<8 + 'L'<<16 + '2'<<24
|
||||
|
||||
// TagSTK is the source-address token
|
||||
TagSTK Tag = 'S' + 'T'<<8 + 'K'<<16
|
||||
// TagSNO is the server nonce
|
||||
|
||||
@@ -542,7 +542,7 @@ func (s *session) closeImpl(e error, remoteClose bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if quicErr.ErrorCode == qerr.DecryptionFailure {
|
||||
if quicErr.ErrorCode == qerr.DecryptionFailure || quicErr == handshake.ErrHOLExperiment {
|
||||
// If we send a public reset, don't send a CONNECTION_CLOSE
|
||||
s.closeChan <- nil
|
||||
return s.sendPublicReset(s.lastRcvdPacketNumber)
|
||||
|
||||
@@ -3,6 +3,7 @@ package quic
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"reflect"
|
||||
@@ -671,6 +672,15 @@ var _ = Describe("Session", func() {
|
||||
Expect(atomic.LoadUint32(&sess.closed) != 0).To(BeTrue())
|
||||
Expect(mconn.written).To(BeEmpty()) // no CONNECTION_CLOSE or PUBLIC_RESET sent
|
||||
})
|
||||
|
||||
It("sends a Public Reset if the client is initiating the head-of-line blocking experiment", func() {
|
||||
sess.Close(handshake.ErrHOLExperiment)
|
||||
Expect(closeCallbackCalled).To(BeTrue())
|
||||
Expect(mconn.written).To(HaveLen(1))
|
||||
fmt.Println(string(mconn.written[0]))
|
||||
Expect(mconn.written[0][0] & 0x02).ToNot(BeZero()) // Public Reset
|
||||
Expect(sess.runClosed).ToNot(Receive()) // channel should be drained by Close()
|
||||
})
|
||||
})
|
||||
|
||||
Context("receiving packets", func() {
|
||||
|
||||
Reference in New Issue
Block a user