golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/quic/idle.go (about)

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