Skip to content

Commit a6a24dd

Browse files
committed
quic: source address and ECN support in the network layer
Make the abstraction over UDP connections higher level, and add support for setting the source address and ECN bits in sent packets, and receving the destination address and ECN bits in received packets. There is no good way that I can find to identify the source IP address of packets we send. Look up the destination IP address of the first packet received on each connection, and use this as the source address for all future packets we send. This avoids unexpected path migration, where the address we send from changes without our knowing it. Reject received packets sent from an unexpected peer address. In the future, when we support path migration, we will want to relax these restrictions. ECN bits may be used to detect network congestion. We don't make use of them at this time, but this CL adds the necessary UDP layer support to do so in the future. This CL also lays the groundwork for using more efficient platform APIs to send/receive packets in the future. (sendmmsg/recvmmsg/GSO/GRO) These features require platform-specific APIs. Add support for Darwin and Linux to start with, with a graceful fallback on other OSs. For golang/go#58547 Change-Id: I1c97cc0d3e52fff18e724feaaac4a50d3df671bc Reviewed-on: https://go-review.googlesource.com/c/net/+/565255 Reviewed-by: Jonathan Amsterdam <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 2a8baea commit a6a24dd

17 files changed

+676
-95
lines changed

internal/quic/conn.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type Conn struct {
2525
config *Config
2626
testHooks connTestHooks
2727
peerAddr netip.AddrPort
28+
localAddr netip.AddrPort
2829

2930
msgc chan any
3031
donec chan struct{} // closed when conn loop exits
@@ -97,7 +98,7 @@ func newConn(now time.Time, side connSide, cids newServerConnIDs, peerAddr netip
9798
side: side,
9899
endpoint: e,
99100
config: config,
100-
peerAddr: peerAddr,
101+
peerAddr: unmapAddrPort(peerAddr),
101102
msgc: make(chan any, 1),
102103
donec: make(chan struct{}),
103104
peerAckDelayExponent: -1,
@@ -317,7 +318,11 @@ func (c *Conn) loop(now time.Time) {
317318
}
318319
switch m := m.(type) {
319320
case *datagram:
320-
c.handleDatagram(now, m)
321+
if !c.handleDatagram(now, m) {
322+
if c.logEnabled(QLogLevelPacket) {
323+
c.logPacketDropped(m)
324+
}
325+
}
321326
m.recycle()
322327
case timerEvent:
323328
// A connection timer has expired.

internal/quic/conn_recv.go

+25-13
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,33 @@ package quic
88

99
import (
1010
"bytes"
11-
"context"
1211
"encoding/binary"
1312
"errors"
1413
"time"
1514
)
1615

17-
func (c *Conn) handleDatagram(now time.Time, dgram *datagram) {
16+
func (c *Conn) handleDatagram(now time.Time, dgram *datagram) (handled bool) {
17+
if !c.localAddr.IsValid() {
18+
// We don't have any way to tell in the general case what address we're
19+
// sending packets from. Set our address from the destination address of
20+
// the first packet received from the peer.
21+
c.localAddr = dgram.localAddr
22+
}
23+
if dgram.peerAddr.IsValid() && dgram.peerAddr != c.peerAddr {
24+
if c.side == clientSide {
25+
// "If a client receives packets from an unknown server address,
26+
// the client MUST discard these packets."
27+
// https://www.rfc-editor.org/rfc/rfc9000#section-9-6
28+
return false
29+
}
30+
// We currently don't support connection migration,
31+
// so for now the server also drops packets from an unknown address.
32+
return false
33+
}
1834
buf := dgram.b
1935
c.loss.datagramReceived(now, len(buf))
2036
if c.isDraining() {
21-
return
37+
return false
2238
}
2339
for len(buf) > 0 {
2440
var n int
@@ -28,7 +44,7 @@ func (c *Conn) handleDatagram(now time.Time, dgram *datagram) {
2844
if c.side == serverSide && len(dgram.b) < paddedInitialDatagramSize {
2945
// Discard client-sent Initial packets in too-short datagrams.
3046
// https://www.rfc-editor.org/rfc/rfc9000#section-14.1-4
31-
return
47+
return false
3248
}
3349
n = c.handleLongHeader(now, ptype, initialSpace, c.keysInitial.r, buf)
3450
case packetTypeHandshake:
@@ -37,10 +53,10 @@ func (c *Conn) handleDatagram(now time.Time, dgram *datagram) {
3753
n = c.handle1RTT(now, buf)
3854
case packetTypeRetry:
3955
c.handleRetry(now, buf)
40-
return
56+
return true
4157
case packetTypeVersionNegotiation:
4258
c.handleVersionNegotiation(now, buf)
43-
return
59+
return true
4460
default:
4561
n = -1
4662
}
@@ -58,20 +74,16 @@ func (c *Conn) handleDatagram(now time.Time, dgram *datagram) {
5874
var token statelessResetToken
5975
copy(token[:], buf[len(buf)-len(token):])
6076
if c.handleStatelessReset(now, token) {
61-
return
77+
return true
6278
}
6379
}
6480
// Invalid data at the end of a datagram is ignored.
65-
if c.logEnabled(QLogLevelPacket) {
66-
c.log.LogAttrs(context.Background(), QLogLevelPacket,
67-
"connectivity:packet_dropped",
68-
)
69-
}
70-
break
81+
return false
7182
}
7283
c.idleHandlePacketReceived(now)
7384
buf = buf[n:]
7485
}
86+
return true
7587
}
7688

7789
func (c *Conn) handleLongHeader(now time.Time, ptype packetType, space numberSpace, k fixedKeys, buf []byte) int {

internal/quic/conn_send.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,10 @@ func (c *Conn) maybeSend(now time.Time) (next time.Time) {
179179
}
180180
}
181181

182-
c.endpoint.sendDatagram(buf, c.peerAddr)
182+
c.endpoint.sendDatagram(datagram{
183+
b: buf,
184+
peerAddr: c.peerAddr,
185+
})
183186
}
184187
}
185188

internal/quic/conn_test.go

+7
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ func (tc *testConn) writeFrames(ptype packetType, frames ...debugFrame) {
453453
dstConnID: dstConnID,
454454
srcConnID: tc.peerConnID,
455455
}},
456+
addr: tc.conn.peerAddr,
456457
}
457458
if ptype == packetTypeInitial && tc.conn.side == serverSide {
458459
d.paddedSize = 1200
@@ -656,6 +657,12 @@ func (tc *testConn) wantPacket(expectation string, want *testPacket) {
656657
}
657658

658659
func packetEqual(a, b *testPacket) bool {
660+
if a == nil && b == nil {
661+
return true
662+
}
663+
if a == nil || b == nil {
664+
return false
665+
}
659666
ac := *a
660667
ac.frames = nil
661668
ac.header = 0

internal/quic/dgram.go

+20-3
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,25 @@ import (
1212
)
1313

1414
type datagram struct {
15-
b []byte
16-
addr netip.AddrPort
15+
b []byte
16+
localAddr netip.AddrPort
17+
peerAddr netip.AddrPort
18+
ecn ecnBits
1719
}
1820

21+
// Explicit Congestion Notification bits.
22+
//
23+
// https://www.rfc-editor.org/rfc/rfc3168.html#section-5
24+
type ecnBits byte
25+
26+
const (
27+
ecnMask = 0b000000_11
28+
ecnNotECT = 0b000000_00
29+
ecnECT1 = 0b000000_01
30+
ecnECT0 = 0b000000_10
31+
ecnCE = 0b000000_11
32+
)
33+
1934
var datagramPool = sync.Pool{
2035
New: func() any {
2136
return &datagram{
@@ -26,7 +41,9 @@ var datagramPool = sync.Pool{
2641

2742
func newDatagram() *datagram {
2843
m := datagramPool.Get().(*datagram)
29-
m.b = m.b[:cap(m.b)]
44+
*m = datagram{
45+
b: m.b[:cap(m.b)],
46+
}
3047
return m
3148
}
3249

internal/quic/endpoint.go

+41-49
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ import (
2222
//
2323
// Multiple goroutines may invoke methods on an Endpoint simultaneously.
2424
type Endpoint struct {
25-
config *Config
26-
udpConn udpConn
27-
testHooks endpointTestHooks
28-
resetGen statelessResetTokenGenerator
29-
retry retryState
25+
config *Config
26+
packetConn packetConn
27+
testHooks endpointTestHooks
28+
resetGen statelessResetTokenGenerator
29+
retry retryState
3030

3131
acceptQueue queue[*Conn] // new inbound connections
3232
connsMap connsMap // only accessed by the listen loop
@@ -42,13 +42,12 @@ type endpointTestHooks interface {
4242
newConn(c *Conn)
4343
}
4444

45-
// A udpConn is a UDP connection.
46-
// It is implemented by net.UDPConn.
47-
type udpConn interface {
45+
// A packetConn is the interface to sending and receiving UDP packets.
46+
type packetConn interface {
4847
Close() error
49-
LocalAddr() net.Addr
50-
ReadMsgUDPAddrPort(b, control []byte) (n, controln, flags int, _ netip.AddrPort, _ error)
51-
WriteToUDPAddrPort(b []byte, addr netip.AddrPort) (int, error)
48+
LocalAddr() netip.AddrPort
49+
Read(f func(*datagram))
50+
Write(datagram) error
5251
}
5352

5453
// Listen listens on a local network address.
@@ -65,13 +64,17 @@ func Listen(network, address string, config *Config) (*Endpoint, error) {
6564
if err != nil {
6665
return nil, err
6766
}
68-
return newEndpoint(udpConn, config, nil)
67+
pc, err := newNetUDPConn(udpConn)
68+
if err != nil {
69+
return nil, err
70+
}
71+
return newEndpoint(pc, config, nil)
6972
}
7073

71-
func newEndpoint(udpConn udpConn, config *Config, hooks endpointTestHooks) (*Endpoint, error) {
74+
func newEndpoint(pc packetConn, config *Config, hooks endpointTestHooks) (*Endpoint, error) {
7275
e := &Endpoint{
7376
config: config,
74-
udpConn: udpConn,
77+
packetConn: pc,
7578
testHooks: hooks,
7679
conns: make(map[*Conn]struct{}),
7780
acceptQueue: newQueue[*Conn](),
@@ -90,8 +93,7 @@ func newEndpoint(udpConn udpConn, config *Config, hooks endpointTestHooks) (*End
9093

9194
// LocalAddr returns the local network address.
9295
func (e *Endpoint) LocalAddr() netip.AddrPort {
93-
a, _ := e.udpConn.LocalAddr().(*net.UDPAddr)
94-
return a.AddrPort()
96+
return e.packetConn.LocalAddr()
9597
}
9698

9799
// Close closes the Endpoint.
@@ -114,7 +116,7 @@ func (e *Endpoint) Close(ctx context.Context) error {
114116
conns = append(conns, c)
115117
}
116118
if len(e.conns) == 0 {
117-
e.udpConn.Close()
119+
e.packetConn.Close()
118120
}
119121
}
120122
e.connsMu.Unlock()
@@ -200,34 +202,18 @@ func (e *Endpoint) connDrained(c *Conn) {
200202
defer e.connsMu.Unlock()
201203
delete(e.conns, c)
202204
if e.closing && len(e.conns) == 0 {
203-
e.udpConn.Close()
205+
e.packetConn.Close()
204206
}
205207
}
206208

207209
func (e *Endpoint) listen() {
208210
defer close(e.closec)
209-
for {
210-
m := newDatagram()
211-
// TODO: Read and process the ECN (explicit congestion notification) field.
212-
// https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-13.4
213-
n, _, _, addr, err := e.udpConn.ReadMsgUDPAddrPort(m.b, nil)
214-
if err != nil {
215-
// The user has probably closed the endpoint.
216-
// We currently don't surface errors from other causes;
217-
// we could check to see if the endpoint has been closed and
218-
// record the unexpected error if it has not.
219-
return
220-
}
221-
if n == 0 {
222-
continue
223-
}
211+
e.packetConn.Read(func(m *datagram) {
224212
if e.connsMap.updateNeeded.Load() {
225213
e.connsMap.applyUpdates()
226214
}
227-
m.addr = addr
228-
m.b = m.b[:n]
229215
e.handleDatagram(m)
230-
}
216+
})
231217
}
232218

233219
func (e *Endpoint) handleDatagram(m *datagram) {
@@ -277,7 +263,7 @@ func (e *Endpoint) handleUnknownDestinationDatagram(m *datagram) {
277263
// If this is a 1-RTT packet, there's nothing productive we can do with it.
278264
// Send a stateless reset if possible.
279265
if !isLongHeader(m.b[0]) {
280-
e.maybeSendStatelessReset(m.b, m.addr)
266+
e.maybeSendStatelessReset(m.b, m.peerAddr)
281267
return
282268
}
283269
p, ok := parseGenericLongHeaderPacket(m.b)
@@ -291,7 +277,7 @@ func (e *Endpoint) handleUnknownDestinationDatagram(m *datagram) {
291277
return
292278
default:
293279
// Unknown version.
294-
e.sendVersionNegotiation(p, m.addr)
280+
e.sendVersionNegotiation(p, m.peerAddr)
295281
return
296282
}
297283
if getPacketType(m.b) != packetTypeInitial {
@@ -309,15 +295,15 @@ func (e *Endpoint) handleUnknownDestinationDatagram(m *datagram) {
309295
if e.config.RequireAddressValidation {
310296
var ok bool
311297
cids.retrySrcConnID = p.dstConnID
312-
cids.originalDstConnID, ok = e.validateInitialAddress(now, p, m.addr)
298+
cids.originalDstConnID, ok = e.validateInitialAddress(now, p, m.peerAddr)
313299
if !ok {
314300
return
315301
}
316302
} else {
317303
cids.originalDstConnID = p.dstConnID
318304
}
319305
var err error
320-
c, err := e.newConn(now, serverSide, cids, m.addr)
306+
c, err := e.newConn(now, serverSide, cids, m.peerAddr)
321307
if err != nil {
322308
// The accept queue is probably full.
323309
// We could send a CONNECTION_CLOSE to the peer to reject the connection.
@@ -329,7 +315,7 @@ func (e *Endpoint) handleUnknownDestinationDatagram(m *datagram) {
329315
m = nil // don't recycle, sendMsg takes ownership
330316
}
331317

332-
func (e *Endpoint) maybeSendStatelessReset(b []byte, addr netip.AddrPort) {
318+
func (e *Endpoint) maybeSendStatelessReset(b []byte, peerAddr netip.AddrPort) {
333319
if !e.resetGen.canReset {
334320
// Config.StatelessResetKey isn't set, so we don't send stateless resets.
335321
return
@@ -370,17 +356,21 @@ func (e *Endpoint) maybeSendStatelessReset(b []byte, addr netip.AddrPort) {
370356
b[0] &^= headerFormLong // clear long header bit
371357
b[0] |= fixedBit // set fixed bit
372358
copy(b[len(b)-statelessResetTokenLen:], token[:])
373-
e.sendDatagram(b, addr)
359+
e.sendDatagram(datagram{
360+
b: b,
361+
peerAddr: peerAddr,
362+
})
374363
}
375364

376-
func (e *Endpoint) sendVersionNegotiation(p genericLongPacket, addr netip.AddrPort) {
365+
func (e *Endpoint) sendVersionNegotiation(p genericLongPacket, peerAddr netip.AddrPort) {
377366
m := newDatagram()
378367
m.b = appendVersionNegotiation(m.b[:0], p.srcConnID, p.dstConnID, quicVersion1)
379-
e.sendDatagram(m.b, addr)
368+
m.peerAddr = peerAddr
369+
e.sendDatagram(*m)
380370
m.recycle()
381371
}
382372

383-
func (e *Endpoint) sendConnectionClose(in genericLongPacket, addr netip.AddrPort, code transportError) {
373+
func (e *Endpoint) sendConnectionClose(in genericLongPacket, peerAddr netip.AddrPort, code transportError) {
384374
keys := initialKeys(in.dstConnID, serverSide)
385375
var w packetWriter
386376
p := longPacket{
@@ -399,12 +389,14 @@ func (e *Endpoint) sendConnectionClose(in genericLongPacket, addr netip.AddrPort
399389
if len(buf) == 0 {
400390
return
401391
}
402-
e.sendDatagram(buf, addr)
392+
e.sendDatagram(datagram{
393+
b: buf,
394+
peerAddr: peerAddr,
395+
})
403396
}
404397

405-
func (e *Endpoint) sendDatagram(p []byte, addr netip.AddrPort) error {
406-
_, err := e.udpConn.WriteToUDPAddrPort(p, addr)
407-
return err
398+
func (e *Endpoint) sendDatagram(dgram datagram) error {
399+
return e.packetConn.Write(dgram)
408400
}
409401

410402
// A connsMap is an endpoint's mapping of conn ids and reset tokens to conns.

0 commit comments

Comments
 (0)