// 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) } }