Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions supervisor/tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,16 @@ func isQuicBroken(cause error) bool {
return true
}

// A CRYPTO_ERROR raised by the peer means the TLS handshake was rejected,
// for example with no_application_protocol (0x178) when something on the
// path intercepts QUIC without supporting the protocols cloudflared offers.
// Retrying QUIC cannot recover from that, so treat it like a broken
// transport and let the connection fall back.
if transportErr, ok := errors.AsType[*quic.TransportError](cause); ok &&
transportErr.Remote && transportErr.ErrorCode.IsCryptoError() {
return true
}

return false
}

Expand Down
30 changes: 30 additions & 0 deletions supervisor/tunnel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,36 @@ func TestWaitForBackoffFallback(t *testing.T) {
assert.False(t, ok)
}

// A QUIC handshake the peer rejects with a CRYPTO_ERROR (e.g. TLS alert 120,
// no_application_protocol, from a middlebox intercepting QUIC) cannot be
// recovered by retrying QUIC, so it must switch to the fallback protocol
// straight away instead of retrying until the backoff is exhausted.
func TestFallbackOnRemoteCryptoError(t *testing.T) {
maxRetries := uint(3)
backoff := retry.NewBackoff(maxRetries, 40*time.Millisecond, false)
backoff.Clock.After = immediateTimeAfter
log := zerolog.Nop()
protocolSelector, err := connection.NewProtocolSelector("auto", &log)
require.NoError(t, err)

protoFallback := &protocolFallback{backoff, protocolSelector.Current(), false}
assert.Equal(t, connection.QUIC, protoFallback.protocol)

// 0x178 is CRYPTO_ERROR for TLS alert 120 (no_application_protocol).
cause := &connection.EdgeQuicDialError{
Cause: &quic.TransportError{
Remote: true,
ErrorCode: quic.TransportErrorCode(0x178),
ErrorMessage: "tls: no application protocol",
},
}

protoFallback.BackoffTimer() // simulate retry
ok := selectNextProtocol(&log, protoFallback, protocolSelector, cause)
assert.True(t, ok)
assert.Equal(t, connection.HTTP2, protoFallback.protocol)
}

func TestIsRetryableStartupError(t *testing.T) {
t.Parallel()

Expand Down