Skip to content

Commit 39c9d01

Browse files
committed
quic: don't send CONNECTION_CLOSE after stateless reset
After receiving a stateless reset, we must enter the draining state and send no further packets (including CONNECTION_CLOSE). We were sending one last CONNECTION_CLOSE after the user closed the Conn; fix this. RFC 9000, Section 10.3.1. Change-Id: I6a9cc6019470a25476df518022a32eefe0c50fcd Reviewed-on: https://go-review.googlesource.com/c/net/+/540117 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Jonathan Amsterdam <[email protected]>
1 parent 45fa414 commit 39c9d01

File tree

4 files changed

+27
-18
lines changed

4 files changed

+27
-18
lines changed

internal/quic/conn_close.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func (c *Conn) lifetimeAdvance(now time.Time) (done bool) {
6262
c.lifetime.drainEndTime = time.Time{}
6363
if c.lifetime.finalErr == nil {
6464
// The peer never responded to our CONNECTION_CLOSE.
65-
c.enterDraining(errNoPeerResponse)
65+
c.enterDraining(now, errNoPeerResponse)
6666
}
6767
return true
6868
}
@@ -152,11 +152,17 @@ func (c *Conn) sendOK(now time.Time) bool {
152152
}
153153

154154
// enterDraining enters the draining state.
155-
func (c *Conn) enterDraining(err error) {
155+
func (c *Conn) enterDraining(now time.Time, err error) {
156156
if c.isDraining() {
157157
return
158158
}
159-
if e, ok := c.lifetime.localErr.(localTransportError); ok && e.code != errNo {
159+
if err == errStatelessReset {
160+
// If we've received a stateless reset, then we must not send a CONNECTION_CLOSE.
161+
// Setting connCloseSentTime here prevents us from doing so.
162+
c.lifetime.finalErr = errStatelessReset
163+
c.lifetime.localErr = errStatelessReset
164+
c.lifetime.connCloseSentTime = now
165+
} else if e, ok := c.lifetime.localErr.(localTransportError); ok && e.code != errNo {
160166
// If we've terminated the connection due to a peer protocol violation,
161167
// record the final error on the connection as our reason for termination.
162168
c.lifetime.finalErr = c.lifetime.localErr
@@ -239,14 +245,14 @@ func (c *Conn) abort(now time.Time, err error) {
239245
// The connection does not send a CONNECTION_CLOSE, and skips the draining period.
240246
func (c *Conn) abortImmediately(now time.Time, err error) {
241247
c.abort(now, err)
242-
c.enterDraining(err)
248+
c.enterDraining(now, err)
243249
c.exited = true
244250
}
245251

246252
// exit fully terminates a connection immediately.
247253
func (c *Conn) exit() {
248254
c.sendMsg(func(now time.Time, c *Conn) {
249-
c.enterDraining(errors.New("connection closed"))
255+
c.enterDraining(now, errors.New("connection closed"))
250256
c.exited = true
251257
})
252258
}

internal/quic/conn_recv.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func (c *Conn) handleDatagram(now time.Time, dgram *datagram) {
5656
if len(buf) == len(dgram.b) && len(buf) > statelessResetTokenLen {
5757
var token statelessResetToken
5858
copy(token[:], buf[len(buf)-len(token):])
59-
c.handleStatelessReset(token)
59+
c.handleStatelessReset(now, token)
6060
}
6161
// Invalid data at the end of a datagram is ignored.
6262
break
@@ -525,7 +525,7 @@ func (c *Conn) handleConnectionCloseTransportFrame(now time.Time, payload []byte
525525
if n < 0 {
526526
return -1
527527
}
528-
c.enterDraining(peerTransportError{code: code, reason: reason})
528+
c.enterDraining(now, peerTransportError{code: code, reason: reason})
529529
return n
530530
}
531531

@@ -534,7 +534,7 @@ func (c *Conn) handleConnectionCloseApplicationFrame(now time.Time, payload []by
534534
if n < 0 {
535535
return -1
536536
}
537-
c.enterDraining(&ApplicationError{Code: code, Reason: reason})
537+
c.enterDraining(now, &ApplicationError{Code: code, Reason: reason})
538538
return n
539539
}
540540

@@ -556,9 +556,9 @@ func (c *Conn) handleHandshakeDoneFrame(now time.Time, space numberSpace, payloa
556556

557557
var errStatelessReset = errors.New("received stateless reset")
558558

559-
func (c *Conn) handleStatelessReset(resetToken statelessResetToken) {
559+
func (c *Conn) handleStatelessReset(now time.Time, resetToken statelessResetToken) {
560560
if !c.connIDState.isValidStatelessResetToken(resetToken) {
561561
return
562562
}
563-
c.enterDraining(errStatelessReset)
563+
c.enterDraining(now, errStatelessReset)
564564
}

internal/quic/listener.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -253,12 +253,18 @@ func (l *Listener) handleUnknownDestinationDatagram(m *datagram) {
253253
if len(m.b) < minimumValidPacketSize {
254254
return
255255
}
256+
var now time.Time
257+
if l.testHooks != nil {
258+
now = l.testHooks.timeNow()
259+
} else {
260+
now = time.Now()
261+
}
256262
// Check to see if this is a stateless reset.
257263
var token statelessResetToken
258264
copy(token[:], m.b[len(m.b)-len(token):])
259265
if c := l.connsMap.byResetToken[token]; c != nil {
260266
c.sendMsg(func(now time.Time, c *Conn) {
261-
c.handleStatelessReset(token)
267+
c.handleStatelessReset(now, token)
262268
})
263269
return
264270
}
@@ -290,12 +296,6 @@ func (l *Listener) handleUnknownDestinationDatagram(m *datagram) {
290296
// https://www.rfc-editor.org/rfc/rfc9000#section-10.3-16
291297
return
292298
}
293-
var now time.Time
294-
if l.testHooks != nil {
295-
now = l.testHooks.timeNow()
296-
} else {
297-
now = time.Now()
298-
}
299299
cids := newServerConnIDs{
300300
srcConnID: p.srcConnID,
301301
dstConnID: p.dstConnID,

internal/quic/stateless_reset_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"errors"
1515
"net/netip"
1616
"testing"
17+
"time"
1718
)
1819

1920
func TestStatelessResetClientSendsStatelessResetTokenTransportParameter(t *testing.T) {
@@ -154,7 +155,9 @@ func TestStatelessResetSuccessfulNewConnectionID(t *testing.T) {
154155
if err := tc.conn.Wait(canceledContext()); !errors.Is(err, errStatelessReset) {
155156
t.Errorf("conn.Wait() = %v, want errStatelessReset", err)
156157
}
157-
tc.wantIdle("closed connection is idle")
158+
tc.wantIdle("closed connection is idle in draining")
159+
tc.advance(1 * time.Second) // long enough to exit the draining state
160+
tc.wantIdle("closed connection is idle after draining")
158161
}
159162

160163
func TestStatelessResetSuccessfulTransportParameter(t *testing.T) {

0 commit comments

Comments
 (0)