Skip to content

Commit 0adcadf

Browse files
committed
quic: send and receive datagrams
Add the ability for Conns to send and receive datagrams. No socket handling yet; this only functions in tests for now. Extend testConn to permit tests to send packets to Conns and observe the packets Conns send. There's a circular dependency here: We can't test Handshake and 1-RTT packets until we have the handshake implemented, but we can't implement the handshake without the ability to send and receive Handshake and 1-RTT packets. This CL adds the ability to send and receive those packets; tests for those paths will follow with the handshake implementation. For golang/go#58547 Change-Id: I4e7f88f5f039baf7e01f68a53639022866786af9 Reviewed-on: https://go-review.googlesource.com/c/net/+/509017 Run-TryBot: Damien Neil <dneil@google.com> Reviewed-by: Jonathan Amsterdam <jba@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
1 parent 16cc77a commit 0adcadf

16 files changed

+1184
-17
lines changed

internal/quic/conn.go

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,42 +9,87 @@ package quic
99
import (
1010
"errors"
1111
"fmt"
12+
"net/netip"
1213
"time"
1314
)
1415

1516
// A Conn is a QUIC connection.
1617
//
1718
// Multiple goroutines may invoke methods on a Conn simultaneously.
1819
type Conn struct {
20+
side connSide
21+
listener connListener
22+
testHooks connTestHooks
23+
peerAddr netip.AddrPort
24+
1925
msgc chan any
2026
donec chan struct{} // closed when conn loop exits
2127
exited bool // set to make the conn loop exit immediately
2228

23-
testHooks connTestHooks
29+
w packetWriter
30+
acks [numberSpaceCount]ackState // indexed by number space
31+
connIDState connIDState
32+
tlsState tlsState
33+
loss lossState
2434

2535
// idleTimeout is the time at which the connection will be closed due to inactivity.
2636
// https://www.rfc-editor.org/rfc/rfc9000#section-10.1
2737
maxIdleTimeout time.Duration
2838
idleTimeout time.Time
39+
40+
peerAckDelayExponent int8 // -1 when unknown
41+
42+
// Tests only: Send a PING in a specific number space.
43+
testSendPingSpace numberSpace
44+
testSendPing sentVal
45+
}
46+
47+
// The connListener is the Conn's Listener.
48+
// Defined as an interface so we can swap it out in tests.
49+
type connListener interface {
50+
sendDatagram(p []byte, addr netip.AddrPort) error
2951
}
3052

3153
// connTestHooks override conn behavior in tests.
3254
type connTestHooks interface {
3355
nextMessage(msgc chan any, nextTimeout time.Time) (now time.Time, message any)
3456
}
3557

36-
func newConn(now time.Time, hooks connTestHooks) (*Conn, error) {
58+
func newConn(now time.Time, side connSide, initialConnID []byte, peerAddr netip.AddrPort, l connListener, hooks connTestHooks) (*Conn, error) {
3759
c := &Conn{
38-
donec: make(chan struct{}),
39-
testHooks: hooks,
40-
maxIdleTimeout: defaultMaxIdleTimeout,
41-
idleTimeout: now.Add(defaultMaxIdleTimeout),
60+
side: side,
61+
listener: l,
62+
peerAddr: peerAddr,
63+
msgc: make(chan any, 1),
64+
donec: make(chan struct{}),
65+
testHooks: hooks,
66+
maxIdleTimeout: defaultMaxIdleTimeout,
67+
idleTimeout: now.Add(defaultMaxIdleTimeout),
68+
peerAckDelayExponent: -1,
4269
}
4370

4471
// A one-element buffer allows us to wake a Conn's event loop as a
4572
// non-blocking operation.
4673
c.msgc = make(chan any, 1)
4774

75+
if c.side == clientSide {
76+
if err := c.connIDState.initClient(newRandomConnID); err != nil {
77+
return nil, err
78+
}
79+
initialConnID = c.connIDState.dstConnID()
80+
} else {
81+
if err := c.connIDState.initServer(newRandomConnID, initialConnID); err != nil {
82+
return nil, err
83+
}
84+
}
85+
86+
// The smallest allowed maximum QUIC datagram size is 1200 bytes.
87+
// TODO: PMTU discovery.
88+
const maxDatagramSize = 1200
89+
c.loss.init(c.side, maxDatagramSize, now)
90+
91+
c.tlsState.init(c.side, initialConnID)
92+
4893
go c.loop(now)
4994
return c, nil
5095
}
@@ -76,7 +121,14 @@ func (c *Conn) loop(now time.Time) {
76121
}
77122

78123
for !c.exited {
79-
nextTimeout := c.idleTimeout
124+
sendTimeout := c.maybeSend(now) // try sending
125+
126+
// Note that we only need to consider the ack timer for the App Data space,
127+
// since the Initial and Handshake spaces always ack immediately.
128+
nextTimeout := sendTimeout
129+
nextTimeout = firstTime(nextTimeout, c.idleTimeout)
130+
nextTimeout = firstTime(nextTimeout, c.loss.timer)
131+
nextTimeout = firstTime(nextTimeout, c.acks[appDataSpace].nextAck)
80132

81133
var m any
82134
if hooks != nil {
@@ -100,6 +152,9 @@ func (c *Conn) loop(now time.Time) {
100152
now = time.Now()
101153
}
102154
switch m := m.(type) {
155+
case *datagram:
156+
c.handleDatagram(now, m)
157+
m.recycle()
103158
case timerEvent:
104159
// A connection timer has expired.
105160
if !now.Before(c.idleTimeout) {
@@ -109,6 +164,7 @@ func (c *Conn) loop(now time.Time) {
109164
c.exited = true
110165
return
111166
}
167+
c.loss.advance(now, c.handleAckOrLoss)
112168
case func(time.Time, *Conn):
113169
// Send a func to msgc to run it on the main Conn goroutine
114170
m(now, c)
@@ -146,10 +202,30 @@ func (c *Conn) runOnLoop(f func(now time.Time, c *Conn)) error {
146202
return nil
147203
}
148204

205+
// abort terminates a connection with an error.
206+
func (c *Conn) abort(now time.Time, err error) {
207+
// TODO: Send CONNECTION_CLOSE frames.
208+
c.exit()
209+
}
210+
149211
// exit fully terminates a connection immediately.
150212
func (c *Conn) exit() {
151213
c.runOnLoop(func(now time.Time, c *Conn) {
152214
c.exited = true
153215
})
154216
<-c.donec
155217
}
218+
219+
// firstTime returns the earliest non-zero time, or zero if both times are zero.
220+
func firstTime(a, b time.Time) time.Time {
221+
switch {
222+
case a.IsZero():
223+
return b
224+
case b.IsZero():
225+
return a
226+
case a.Before(b):
227+
return a
228+
default:
229+
return b
230+
}
231+
}

internal/quic/conn_loss.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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 "fmt"
10+
11+
// handleAckOrLoss deals with the final fate of a packet we sent:
12+
// Either the peer acknowledges it, or we declare it lost.
13+
//
14+
// In order to handle packet loss, we must retain any information sent to the peer
15+
// until the peer has acknowledged it.
16+
//
17+
// When information is acknowledged, we can discard it.
18+
//
19+
// When information is lost, we mark it for retransmission.
20+
// See RFC 9000, Section 13.3 for a complete list of information which is retransmitted on loss.
21+
// https://www.rfc-editor.org/rfc/rfc9000#section-13.3
22+
func (c *Conn) handleAckOrLoss(space numberSpace, sent *sentPacket, fate packetFate) {
23+
// The list of frames in a sent packet is marshaled into a buffer in the sentPacket
24+
// by the packetWriter. Unmarshal that buffer here. This code must be kept in sync with
25+
// packetWriter.append*.
26+
//
27+
// A sent packet meets its fate (acked or lost) only once, so it's okay to consume
28+
// the sentPacket's buffer here.
29+
for !sent.done() {
30+
switch f := sent.next(); f {
31+
default:
32+
panic(fmt.Sprintf("BUG: unhandled lost frame type %x", f))
33+
case frameTypeAck:
34+
// Unlike most information, loss of an ACK frame does not trigger
35+
// retransmission. ACKs are sent in response to ack-eliciting packets,
36+
// and always contain the latest information available.
37+
//
38+
// Acknowledgement of an ACK frame may allow us to discard information
39+
// about older packets.
40+
largest := packetNumber(sent.nextInt())
41+
if fate == packetAcked {
42+
c.acks[space].handleAck(largest)
43+
}
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)