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 }