1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
|
// 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 (
"time"
)
// idleState tracks connection idle events.
//
// Before the handshake is confirmed, the idle timeout is Config.HandshakeTimeout.
//
// After the handshake is confirmed, the idle timeout is
// the minimum of Config.MaxIdleTimeout and the peer's max_idle_timeout transport parameter.
//
// If KeepAlivePeriod is set, keep-alive pings are sent.
// Keep-alives are only sent after the handshake is confirmed.
//
// https://www.rfc-editor.org/rfc/rfc9000#section-10.1
type idleState struct {
// idleDuration is the negotiated idle timeout for the connection.
idleDuration time.Duration
// idleTimeout is the time at which the connection will be closed due to inactivity.
idleTimeout time.Time
// nextTimeout is the time of the next idle event.
// If nextTimeout == idleTimeout, this is the idle timeout.
// Otherwise, this is the keep-alive timeout.
nextTimeout time.Time
// sentSinceLastReceive is set if we have sent an ack-eliciting packet
// since the last time we received and processed a packet from the peer.
sentSinceLastReceive bool
}
// receivePeerMaxIdleTimeout handles the peer's max_idle_timeout transport parameter.
func (c *Conn) receivePeerMaxIdleTimeout(peerMaxIdleTimeout time.Duration) {
localMaxIdleTimeout := c.config.maxIdleTimeout()
switch {
case localMaxIdleTimeout == 0:
c.idle.idleDuration = peerMaxIdleTimeout
case peerMaxIdleTimeout == 0:
c.idle.idleDuration = localMaxIdleTimeout
default:
c.idle.idleDuration = min(localMaxIdleTimeout, peerMaxIdleTimeout)
}
}
func (c *Conn) idleHandlePacketReceived(now time.Time) {
if !c.handshakeConfirmed.isSet() {
return
}
// "An endpoint restarts its idle timer when a packet from its peer is
// received and processed successfully."
// https://www.rfc-editor.org/rfc/rfc9000#section-10.1-3
c.idle.sentSinceLastReceive = false
c.restartIdleTimer(now)
}
func (c *Conn) idleHandlePacketSent(now time.Time, sent *sentPacket) {
// "An endpoint also restarts its idle timer when sending an ack-eliciting packet
// if no other ack-eliciting packets have been sent since
// last receiving and processing a packet."
// https://www.rfc-editor.org/rfc/rfc9000#section-10.1-3
if c.idle.sentSinceLastReceive || !sent.ackEliciting || !c.handshakeConfirmed.isSet() {
return
}
c.idle.sentSinceLastReceive = true
c.restartIdleTimer(now)
}
func (c *Conn) restartIdleTimer(now time.Time) {
if !c.isAlive() {
// Connection is closing, disable timeouts.
c.idle.idleTimeout = time.Time{}
c.idle.nextTimeout = time.Time{}
return
}
var idleDuration time.Duration
if c.handshakeConfirmed.isSet() {
idleDuration = c.idle.idleDuration
} else {
idleDuration = c.config.handshakeTimeout()
}
if idleDuration == 0 {
c.idle.idleTimeout = time.Time{}
} else {
// "[...] endpoints MUST increase the idle timeout period to be
// at least three times the current Probe Timeout (PTO)."
// https://www.rfc-editor.org/rfc/rfc9000#section-10.1-4
idleDuration = max(idleDuration, 3*c.loss.ptoPeriod())
c.idle.idleTimeout = now.Add(idleDuration)
}
// Set the time of our next event:
// The idle timer if no keep-alive is set, or the keep-alive timer if one is.
c.idle.nextTimeout = c.idle.idleTimeout
keepAlive := c.config.keepAlivePeriod()
switch {
case !c.handshakeConfirmed.isSet():
// We do not send keep-alives before the handshake is complete.
case keepAlive <= 0:
// Keep-alives are not enabled.
case c.idle.sentSinceLastReceive:
// We have sent an ack-eliciting packet to the peer.
// If they don't acknowledge it, loss detection will follow up with PTO probes,
// which will function as keep-alives.
// We don't need to send further pings.
case idleDuration == 0:
// The connection does not have a negotiated idle timeout.
// Send keep-alives anyway, since they may be required to keep middleboxes
// from losing state.
c.idle.nextTimeout = now.Add(keepAlive)
default:
// Schedule our next keep-alive.
// If our configured keep-alive period is greater than half the negotiated
// connection idle timeout, we reduce the keep-alive period to half
// the idle timeout to ensure we have time for the ping to arrive.
c.idle.nextTimeout = now.Add(min(keepAlive, idleDuration/2))
}
}
func (c *Conn) appendKeepAlive(now time.Time) bool {
if c.idle.nextTimeout.IsZero() || c.idle.nextTimeout.After(now) {
return true // timer has not expired
}
if c.idle.nextTimeout.Equal(c.idle.idleTimeout) {
return true // no keepalive timer set, only idle
}
if c.idle.sentSinceLastReceive {
return true // already sent an ack-eliciting packet
}
if c.w.sent.ackEliciting {
return true // this packet is already ack-eliciting
}
// Send an ack-eliciting PING frame to the peer to keep the connection alive.
return c.w.appendPingFrame()
}
var errHandshakeTimeout error = localTransportError{
code: errConnectionRefused,
reason: "handshake timeout",
}
func (c *Conn) idleAdvance(now time.Time) (shouldExit bool) {
if c.idle.idleTimeout.IsZero() || now.Before(c.idle.idleTimeout) {
return false
}
c.idle.idleTimeout = time.Time{}
c.idle.nextTimeout = time.Time{}
if !c.handshakeConfirmed.isSet() {
// Handshake timeout has expired.
// If we're a server, we're refusing the too-slow client.
// If we're a client, we're giving up.
// In either case, we're going to send a CONNECTION_CLOSE frame and
// enter the closing state rather than unceremoniously dropping the connection,
// since the peer might still be trying to complete the handshake.
c.abort(now, errHandshakeTimeout)
return false
}
// Idle timeout has expired.
//
// "[...] the connection is silently closed and its state is discarded [...]"
// https://www.rfc-editor.org/rfc/rfc9000#section-10.1-1
return true
}
|