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