blob: 18f6a690a4baf0200b0994b2db3f63f2c2a9f683 [file] [log] [blame]
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.21
package quic
import (
"context"
"crypto/tls"
"fmt"
"testing"
"time"
)
func TestHandshakeTimeoutExpiresServer(t *testing.T) {
const timeout = 5 * time.Second
tc := newTestConn(t, serverSide, func(c *Config) {
c.HandshakeTimeout = timeout
})
tc.ignoreFrame(frameTypeAck)
tc.ignoreFrame(frameTypeNewConnectionID)
tc.writeFrames(packetTypeInitial,
debugFrameCrypto{
data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial],
})
// Server starts its end of the handshake.
// Client acks these packets to avoid starting the PTO timer.
tc.wantFrameType("server sends Initial CRYPTO flight",
packetTypeInitial, debugFrameCrypto{})
tc.writeAckForAll()
tc.wantFrameType("server sends Handshake CRYPTO flight",
packetTypeHandshake, debugFrameCrypto{})
tc.writeAckForAll()
if got, want := tc.timerDelay(), timeout; got != want {
t.Errorf("connection timer = %v, want %v (handshake timeout)", got, want)
}
// Client sends a packet, but this does not extend the handshake timer.
tc.advance(1 * time.Second)
tc.writeFrames(packetTypeHandshake, debugFrameCrypto{
data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake][:1], // partial data
})
tc.wantIdle("handshake is not complete")
tc.advance(timeout - 1*time.Second)
tc.wantFrame("server closes connection after handshake timeout",
packetTypeHandshake, debugFrameConnectionCloseTransport{
code: errConnectionRefused,
})
}
func TestHandshakeTimeoutExpiresClient(t *testing.T) {
const timeout = 5 * time.Second
tc := newTestConn(t, clientSide, func(c *Config) {
c.HandshakeTimeout = timeout
})
tc.ignoreFrame(frameTypeAck)
tc.ignoreFrame(frameTypeNewConnectionID)
// Start the handshake.
// The client always sets a PTO timer until it gets an ack for a handshake packet
// or confirms the handshake, so proceed far enough through the handshake to
// let us not worry about PTO.
tc.wantFrameType("client sends Initial CRYPTO flight",
packetTypeInitial, debugFrameCrypto{})
tc.writeAckForAll()
tc.writeFrames(packetTypeInitial,
debugFrameCrypto{
data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial],
})
tc.writeFrames(packetTypeHandshake,
debugFrameCrypto{
data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake],
})
tc.wantFrameType("client sends Handshake CRYPTO flight",
packetTypeHandshake, debugFrameCrypto{})
tc.writeAckForAll()
tc.wantIdle("client is waiting for end of handshake")
if got, want := tc.timerDelay(), timeout; got != want {
t.Errorf("connection timer = %v, want %v (handshake timeout)", got, want)
}
tc.advance(timeout)
tc.wantFrame("client closes connection after handshake timeout",
packetTypeHandshake, debugFrameConnectionCloseTransport{
code: errConnectionRefused,
})
}
func TestIdleTimeoutExpires(t *testing.T) {
for _, test := range []struct {
localMaxIdleTimeout time.Duration
peerMaxIdleTimeout time.Duration
wantTimeout time.Duration
}{{
localMaxIdleTimeout: 10 * time.Second,
peerMaxIdleTimeout: 20 * time.Second,
wantTimeout: 10 * time.Second,
}, {
localMaxIdleTimeout: 20 * time.Second,
peerMaxIdleTimeout: 10 * time.Second,
wantTimeout: 10 * time.Second,
}, {
localMaxIdleTimeout: 0,
peerMaxIdleTimeout: 10 * time.Second,
wantTimeout: 10 * time.Second,
}, {
localMaxIdleTimeout: 10 * time.Second,
peerMaxIdleTimeout: 0,
wantTimeout: 10 * time.Second,
}} {
name := fmt.Sprintf("local=%v/peer=%v", test.localMaxIdleTimeout, test.peerMaxIdleTimeout)
t.Run(name, func(t *testing.T) {
tc := newTestConn(t, serverSide, func(p *transportParameters) {
p.maxIdleTimeout = test.peerMaxIdleTimeout
}, func(c *Config) {
c.MaxIdleTimeout = test.localMaxIdleTimeout
})
tc.handshake()
if got, want := tc.timeUntilEvent(), test.wantTimeout; got != want {
t.Errorf("new conn timeout=%v, want %v (idle timeout)", got, want)
}
tc.advance(test.wantTimeout - 1)
tc.wantIdle("connection is idle and alive prior to timeout")
ctx := canceledContext()
if err := tc.conn.Wait(ctx); err != context.Canceled {
t.Fatalf("conn.Wait() = %v, want Canceled", err)
}
tc.advance(1)
tc.wantIdle("connection exits after timeout")
if err := tc.conn.Wait(ctx); err != errIdleTimeout {
t.Fatalf("conn.Wait() = %v, want errIdleTimeout", err)
}
})
}
}
func TestIdleTimeoutKeepAlive(t *testing.T) {
for _, test := range []struct {
idleTimeout time.Duration
keepAlive time.Duration
wantTimeout time.Duration
}{{
idleTimeout: 30 * time.Second,
keepAlive: 10 * time.Second,
wantTimeout: 10 * time.Second,
}, {
idleTimeout: 10 * time.Second,
keepAlive: 30 * time.Second,
wantTimeout: 5 * time.Second,
}, {
idleTimeout: -1, // disabled
keepAlive: 30 * time.Second,
wantTimeout: 30 * time.Second,
}} {
name := fmt.Sprintf("idle_timeout=%v/keepalive=%v", test.idleTimeout, test.keepAlive)
t.Run(name, func(t *testing.T) {
tc := newTestConn(t, serverSide, func(c *Config) {
c.MaxIdleTimeout = test.idleTimeout
c.KeepAlivePeriod = test.keepAlive
})
tc.handshake()
if got, want := tc.timeUntilEvent(), test.wantTimeout; got != want {
t.Errorf("new conn timeout=%v, want %v (keepalive timeout)", got, want)
}
tc.advance(test.wantTimeout - 1)
tc.wantIdle("connection is idle prior to timeout")
tc.advance(1)
tc.wantFrameType("keep-alive ping is sent", packetType1RTT,
debugFramePing{})
})
}
}
func TestIdleLongTermKeepAliveSent(t *testing.T) {
// This test examines a connection sitting idle and sending periodic keep-alive pings.
const keepAlivePeriod = 30 * time.Second
tc := newTestConn(t, clientSide, func(c *Config) {
c.KeepAlivePeriod = keepAlivePeriod
c.MaxIdleTimeout = -1
})
tc.handshake()
// The handshake will have completed a little bit after the point at which the
// keepalive timer was set. Send two PING frames to the conn, triggering an immediate ack
// and resetting the timer.
tc.writeFrames(packetType1RTT, debugFramePing{})
tc.writeFrames(packetType1RTT, debugFramePing{})
tc.wantFrameType("conn acks received pings", packetType1RTT, debugFrameAck{})
for i := 0; i < 10; i++ {
tc.wantIdle("conn has nothing more to send")
if got, want := tc.timeUntilEvent(), keepAlivePeriod; got != want {
t.Errorf("i=%v conn timeout=%v, want %v (keepalive timeout)", i, got, want)
}
tc.advance(keepAlivePeriod)
tc.wantFrameType("keep-alive ping is sent", packetType1RTT,
debugFramePing{})
tc.writeAckForAll()
}
}
func TestIdleLongTermKeepAliveReceived(t *testing.T) {
// This test examines a connection sitting idle, but receiving periodic peer
// traffic to keep the connection alive.
const idleTimeout = 30 * time.Second
tc := newTestConn(t, serverSide, func(c *Config) {
c.MaxIdleTimeout = idleTimeout
})
tc.handshake()
for i := 0; i < 10; i++ {
tc.advance(idleTimeout - 1*time.Second)
tc.writeFrames(packetType1RTT, debugFramePing{})
if got, want := tc.timeUntilEvent(), maxAckDelay-timerGranularity; got != want {
t.Errorf("i=%v conn timeout=%v, want %v (max_ack_delay)", i, got, want)
}
tc.advanceToTimer()
tc.wantFrameType("conn acks received ping", packetType1RTT, debugFrameAck{})
}
// Connection is still alive.
ctx := canceledContext()
if err := tc.conn.Wait(ctx); err != context.Canceled {
t.Fatalf("conn.Wait() = %v, want Canceled", err)
}
}