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 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
|
// 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"
"errors"
"time"
)
// connState is the state of a connection.
type connState int
const (
// A connection is alive when it is first created.
connStateAlive = connState(iota)
// The connection has received a CONNECTION_CLOSE frame from the peer,
// and has not yet sent a CONNECTION_CLOSE in response.
//
// We will send a CONNECTION_CLOSE, and then enter the draining state.
connStatePeerClosed
// The connection is in the closing state.
//
// We will send CONNECTION_CLOSE frames to the peer
// (once upon entering the closing state, and possibly again in response to peer packets).
//
// If we receive a CONNECTION_CLOSE from the peer, we will enter the draining state.
// Otherwise, we will eventually time out and move to the done state.
//
// https://www.rfc-editor.org/rfc/rfc9000#section-10.2.1
connStateClosing
// The connection is in the draining state.
//
// We will neither send packets nor process received packets.
// When the drain timer expires, we move to the done state.
//
// https://www.rfc-editor.org/rfc/rfc9000#section-10.2.2
connStateDraining
// The connection is done, and the conn loop will exit.
connStateDone
)
// lifetimeState tracks the state of a connection.
//
// This is fairly coupled to the rest of a Conn, but putting it in a struct of its own helps
// reason about operations that cause state transitions.
type lifetimeState struct {
state connState
readyc chan struct{} // closed when TLS handshake completes
donec chan struct{} // closed when finalErr is set
localErr error // error sent to the peer
finalErr error // error sent by the peer, or transport error; set before closing donec
connCloseSentTime time.Time // send time of last CONNECTION_CLOSE frame
connCloseDelay time.Duration // delay until next CONNECTION_CLOSE frame sent
drainEndTime time.Time // time the connection exits the draining state
}
func (c *Conn) lifetimeInit() {
c.lifetime.readyc = make(chan struct{})
c.lifetime.donec = make(chan struct{})
}
var (
errNoPeerResponse = errors.New("peer did not respond to CONNECTION_CLOSE")
errConnClosed = errors.New("connection closed")
)
// advance is called when time passes.
func (c *Conn) lifetimeAdvance(now time.Time) (done bool) {
if c.lifetime.drainEndTime.IsZero() || c.lifetime.drainEndTime.After(now) {
return false
}
// The connection drain period has ended, and we can shut down.
// https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2-7
c.lifetime.drainEndTime = time.Time{}
if c.lifetime.state != connStateDraining {
// We were in the closing state, waiting for a CONNECTION_CLOSE from the peer.
c.setFinalError(errNoPeerResponse)
}
c.setState(now, connStateDone)
return true
}
// setState sets the conn state.
func (c *Conn) setState(now time.Time, state connState) {
if c.lifetime.state == state {
return
}
c.lifetime.state = state
switch state {
case connStateClosing, connStateDraining:
if c.lifetime.drainEndTime.IsZero() {
c.lifetime.drainEndTime = now.Add(3 * c.loss.ptoBasePeriod())
}
case connStateDone:
c.setFinalError(nil)
}
if state != connStateAlive {
c.streamsCleanup()
}
}
// confirmHandshake is called when the TLS handshake completes.
func (c *Conn) handshakeDone() {
close(c.lifetime.readyc)
}
// isDraining reports whether the conn is in the draining state.
//
// The draining state is entered once an endpoint receives a CONNECTION_CLOSE frame.
// The endpoint will no longer send any packets, but we retain knowledge of the connection
// until the end of the drain period to ensure we discard packets for the connection
// rather than treating them as starting a new connection.
//
// https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.2
func (c *Conn) isDraining() bool {
switch c.lifetime.state {
case connStateDraining, connStateDone:
return true
}
return false
}
// isAlive reports whether the conn is handling packets.
func (c *Conn) isAlive() bool {
return c.lifetime.state == connStateAlive
}
// sendOK reports whether the conn can send frames at this time.
func (c *Conn) sendOK(now time.Time) bool {
switch c.lifetime.state {
case connStateAlive:
return true
case connStatePeerClosed:
if c.lifetime.localErr == nil {
// We're waiting for the user to close the connection, providing us with
// a final status to send to the peer.
return false
}
// We should send a CONNECTION_CLOSE.
return true
case connStateClosing:
if c.lifetime.connCloseSentTime.IsZero() {
return true
}
maxRecvTime := c.acks[initialSpace].maxRecvTime
if t := c.acks[handshakeSpace].maxRecvTime; t.After(maxRecvTime) {
maxRecvTime = t
}
if t := c.acks[appDataSpace].maxRecvTime; t.After(maxRecvTime) {
maxRecvTime = t
}
if maxRecvTime.Before(c.lifetime.connCloseSentTime.Add(c.lifetime.connCloseDelay)) {
// After sending CONNECTION_CLOSE, ignore packets from the peer for
// a delay. On the next packet received after the delay, send another
// CONNECTION_CLOSE.
return false
}
return true
case connStateDraining:
// We are in the draining state, and will send no more packets.
return false
case connStateDone:
return false
default:
panic("BUG: unhandled connection state")
}
}
// sendConnectionClose reports that the conn has sent a CONNECTION_CLOSE to the peer.
func (c *Conn) sentConnectionClose(now time.Time) {
switch c.lifetime.state {
case connStatePeerClosed:
c.enterDraining(now)
}
if c.lifetime.connCloseSentTime.IsZero() {
// Set the initial delay before we will send another CONNECTION_CLOSE.
//
// RFC 9000 states that we should rate limit CONNECTION_CLOSE frames,
// but leaves the implementation of the limit up to us. Here, we start
// with the same delay as the PTO timer (RFC 9002, Section 6.2.1),
// not including max_ack_delay, and double it on every CONNECTION_CLOSE sent.
c.lifetime.connCloseDelay = c.loss.rtt.smoothedRTT + max(4*c.loss.rtt.rttvar, timerGranularity)
} else if !c.lifetime.connCloseSentTime.Equal(now) {
// If connCloseSentTime == now, we're sending two CONNECTION_CLOSE frames
// coalesced into the same datagram. We only want to increase the delay once.
c.lifetime.connCloseDelay *= 2
}
c.lifetime.connCloseSentTime = now
}
// handlePeerConnectionClose handles a CONNECTION_CLOSE from the peer.
func (c *Conn) handlePeerConnectionClose(now time.Time, err error) {
c.setFinalError(err)
switch c.lifetime.state {
case connStateAlive:
c.setState(now, connStatePeerClosed)
case connStatePeerClosed:
// Duplicate CONNECTION_CLOSE, ignore.
case connStateClosing:
if c.lifetime.connCloseSentTime.IsZero() {
c.setState(now, connStatePeerClosed)
} else {
c.setState(now, connStateDraining)
}
case connStateDraining:
case connStateDone:
}
}
// setFinalError records the final connection status we report to the user.
func (c *Conn) setFinalError(err error) {
select {
case <-c.lifetime.donec:
return // already set
default:
}
c.lifetime.finalErr = err
close(c.lifetime.donec)
}
func (c *Conn) waitReady(ctx context.Context) error {
select {
case <-c.lifetime.readyc:
return nil
case <-c.lifetime.donec:
return c.lifetime.finalErr
default:
}
select {
case <-c.lifetime.readyc:
return nil
case <-c.lifetime.donec:
return c.lifetime.finalErr
case <-ctx.Done():
return ctx.Err()
}
}
// Close closes the connection.
//
// Close is equivalent to:
//
// conn.Abort(nil)
// err := conn.Wait(context.Background())
func (c *Conn) Close() error {
c.Abort(nil)
<-c.lifetime.donec
return c.lifetime.finalErr
}
// Wait waits for the peer to close the connection.
//
// If the connection is closed locally and the peer does not close its end of the connection,
// Wait will return with a non-nil error after the drain period expires.
//
// If the peer closes the connection with a NO_ERROR transport error, Wait returns nil.
// If the peer closes the connection with an application error, Wait returns an ApplicationError
// containing the peer's error code and reason.
// If the peer closes the connection with any other status, Wait returns a non-nil error.
func (c *Conn) Wait(ctx context.Context) error {
if err := c.waitOnDone(ctx, c.lifetime.donec); err != nil {
return err
}
return c.lifetime.finalErr
}
// Abort closes the connection and returns immediately.
//
// If err is nil, Abort sends a transport error of NO_ERROR to the peer.
// If err is an ApplicationError, Abort sends its error code and text.
// Otherwise, Abort sends a transport error of APPLICATION_ERROR with the error's text.
func (c *Conn) Abort(err error) {
if err == nil {
err = localTransportError{code: errNo}
}
c.sendMsg(func(now time.Time, c *Conn) {
c.enterClosing(now, err)
})
}
// abort terminates a connection with an error.
func (c *Conn) abort(now time.Time, err error) {
c.setFinalError(err) // this error takes precedence over the peer's CONNECTION_CLOSE
c.enterClosing(now, err)
}
// abortImmediately terminates a connection.
// The connection does not send a CONNECTION_CLOSE, and skips the draining period.
func (c *Conn) abortImmediately(now time.Time, err error) {
c.setFinalError(err)
c.setState(now, connStateDone)
}
// enterClosing starts an immediate close.
// We will send a CONNECTION_CLOSE to the peer and wait for their response.
func (c *Conn) enterClosing(now time.Time, err error) {
switch c.lifetime.state {
case connStateAlive:
c.lifetime.localErr = err
c.setState(now, connStateClosing)
case connStatePeerClosed:
c.lifetime.localErr = err
}
}
// enterDraining moves directly to the draining state, without sending a CONNECTION_CLOSE.
func (c *Conn) enterDraining(now time.Time) {
switch c.lifetime.state {
case connStateAlive, connStatePeerClosed, connStateClosing:
c.setState(now, connStateDraining)
}
}
// exit fully terminates a connection immediately.
func (c *Conn) exit() {
c.sendMsg(func(now time.Time, c *Conn) {
c.abortImmediately(now, errors.New("connection closed"))
})
}
|