Skip to content

Commit 57e4cc7

Browse files
committed
quic: handle PATH_CHALLENGE and PATH_RESPONSE frames
We do not support path migration yet, and will ignore packets sent from anything other than the peer's original address. Handle PATH_CHALLENGE frames by sending a PATH_RESPONSE. Handle PATH_RESPONSE frames by closing the connection (since we never send a challenge to respond to). For golang/go#58547 Change-Id: I828b9dcb23e17f5edf3d605b8f04efdafb392807 Reviewed-on: https://go-review.googlesource.com/c/net/+/565795 Reviewed-by: Jonathan Amsterdam <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent a6a24dd commit 57e4cc7

12 files changed

+255
-32
lines changed

internal/quic/conn.go

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type Conn struct {
3737
connIDState connIDState
3838
loss lossState
3939
streams streamsState
40+
path pathState
4041

4142
// Packet protection keys, CRYPTO streams, and TLS state.
4243
keysInitial fixedKeyPair

internal/quic/conn_loss_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,29 @@ func TestLostRetireConnectionIDFrame(t *testing.T) {
663663
})
664664
}
665665

666+
func TestLostPathResponseFrame(t *testing.T) {
667+
// "Responses to path validation using PATH_RESPONSE frames are sent just once."
668+
// https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.12
669+
lostFrameTest(t, func(t *testing.T, pto bool) {
670+
tc := newTestConn(t, clientSide)
671+
tc.handshake()
672+
tc.ignoreFrame(frameTypeAck)
673+
tc.ignoreFrame(frameTypePing)
674+
675+
data := pathChallengeData{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}
676+
tc.writeFrames(packetType1RTT, debugFramePathChallenge{
677+
data: data,
678+
})
679+
tc.wantFrame("response to PATH_CHALLENGE",
680+
packetType1RTT, debugFramePathResponse{
681+
data: data,
682+
})
683+
684+
tc.triggerLossOrPTO(packetType1RTT, pto)
685+
tc.wantIdle("lost PATH_RESPONSE frame is not retransmitted")
686+
})
687+
}
688+
666689
func TestLostHandshakeDoneFrame(t *testing.T) {
667690
// "The HANDSHAKE_DONE frame MUST be retransmitted until it is acknowledged."
668691
// https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.16

internal/quic/conn_recv.go

+36-8
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ func (c *Conn) handleDatagram(now time.Time, dgram *datagram) (handled bool) {
4646
// https://www.rfc-editor.org/rfc/rfc9000#section-14.1-4
4747
return false
4848
}
49-
n = c.handleLongHeader(now, ptype, initialSpace, c.keysInitial.r, buf)
49+
n = c.handleLongHeader(now, dgram, ptype, initialSpace, c.keysInitial.r, buf)
5050
case packetTypeHandshake:
51-
n = c.handleLongHeader(now, ptype, handshakeSpace, c.keysHandshake.r, buf)
51+
n = c.handleLongHeader(now, dgram, ptype, handshakeSpace, c.keysHandshake.r, buf)
5252
case packetType1RTT:
53-
n = c.handle1RTT(now, buf)
53+
n = c.handle1RTT(now, dgram, buf)
5454
case packetTypeRetry:
5555
c.handleRetry(now, buf)
5656
return true
@@ -86,7 +86,7 @@ func (c *Conn) handleDatagram(now time.Time, dgram *datagram) (handled bool) {
8686
return true
8787
}
8888

89-
func (c *Conn) handleLongHeader(now time.Time, ptype packetType, space numberSpace, k fixedKeys, buf []byte) int {
89+
func (c *Conn) handleLongHeader(now time.Time, dgram *datagram, ptype packetType, space numberSpace, k fixedKeys, buf []byte) int {
9090
if !k.isSet() {
9191
return skipLongHeaderPacket(buf)
9292
}
@@ -125,7 +125,7 @@ func (c *Conn) handleLongHeader(now time.Time, ptype packetType, space numberSpa
125125
c.logLongPacketReceived(p, buf[:n])
126126
}
127127
c.connIDState.handlePacket(c, p.ptype, p.srcConnID)
128-
ackEliciting := c.handleFrames(now, ptype, space, p.payload)
128+
ackEliciting := c.handleFrames(now, dgram, ptype, space, p.payload)
129129
c.acks[space].receive(now, space, p.num, ackEliciting)
130130
if p.ptype == packetTypeHandshake && c.side == serverSide {
131131
c.loss.validateClientAddress()
@@ -138,7 +138,7 @@ func (c *Conn) handleLongHeader(now time.Time, ptype packetType, space numberSpa
138138
return n
139139
}
140140

141-
func (c *Conn) handle1RTT(now time.Time, buf []byte) int {
141+
func (c *Conn) handle1RTT(now time.Time, dgram *datagram, buf []byte) int {
142142
if !c.keysAppData.canRead() {
143143
// 1-RTT packets extend to the end of the datagram,
144144
// so skip the remainder of the datagram if we can't parse this.
@@ -175,7 +175,7 @@ func (c *Conn) handle1RTT(now time.Time, buf []byte) int {
175175
if c.logEnabled(QLogLevelPacket) {
176176
c.log1RTTPacketReceived(p, buf)
177177
}
178-
ackEliciting := c.handleFrames(now, packetType1RTT, appDataSpace, p.payload)
178+
ackEliciting := c.handleFrames(now, dgram, packetType1RTT, appDataSpace, p.payload)
179179
c.acks[appDataSpace].receive(now, appDataSpace, p.num, ackEliciting)
180180
return len(buf)
181181
}
@@ -252,7 +252,7 @@ func (c *Conn) handleVersionNegotiation(now time.Time, pkt []byte) {
252252
c.abortImmediately(now, errVersionNegotiation)
253253
}
254254

255-
func (c *Conn) handleFrames(now time.Time, ptype packetType, space numberSpace, payload []byte) (ackEliciting bool) {
255+
func (c *Conn) handleFrames(now time.Time, dgram *datagram, ptype packetType, space numberSpace, payload []byte) (ackEliciting bool) {
256256
if len(payload) == 0 {
257257
// "An endpoint MUST treat receipt of a packet containing no frames
258258
// as a connection error of type PROTOCOL_VIOLATION."
@@ -373,6 +373,16 @@ func (c *Conn) handleFrames(now time.Time, ptype packetType, space numberSpace,
373373
return
374374
}
375375
n = c.handleRetireConnectionIDFrame(now, space, payload)
376+
case frameTypePathChallenge:
377+
if !frameOK(c, ptype, __01) {
378+
return
379+
}
380+
n = c.handlePathChallengeFrame(now, dgram, space, payload)
381+
case frameTypePathResponse:
382+
if !frameOK(c, ptype, ___1) {
383+
return
384+
}
385+
n = c.handlePathResponseFrame(now, space, payload)
376386
case frameTypeConnectionCloseTransport:
377387
// Transport CONNECTION_CLOSE is OK in all spaces.
378388
n = c.handleConnectionCloseTransportFrame(now, payload)
@@ -546,6 +556,24 @@ func (c *Conn) handleRetireConnectionIDFrame(now time.Time, space numberSpace, p
546556
return n
547557
}
548558

559+
func (c *Conn) handlePathChallengeFrame(now time.Time, dgram *datagram, space numberSpace, payload []byte) int {
560+
data, n := consumePathChallengeFrame(payload)
561+
if n < 0 {
562+
return -1
563+
}
564+
c.handlePathChallenge(now, dgram, data)
565+
return n
566+
}
567+
568+
func (c *Conn) handlePathResponseFrame(now time.Time, space numberSpace, payload []byte) int {
569+
data, n := consumePathResponseFrame(payload)
570+
if n < 0 {
571+
return -1
572+
}
573+
c.handlePathResponse(now, data)
574+
return n
575+
}
576+
549577
func (c *Conn) handleConnectionCloseTransportFrame(now time.Time, payload []byte) int {
550578
code, _, reason, n := consumeConnectionCloseTransportFrame(payload)
551579
if n < 0 {

internal/quic/conn_send.go

+7
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,13 @@ func (c *Conn) appendFrames(now time.Time, space numberSpace, pnum packetNumber,
271271
return
272272
}
273273

274+
// PATH_RESPONSE
275+
if pad, ok := c.appendPathFrames(); !ok {
276+
return
277+
} else if pad {
278+
defer c.w.appendPaddingTo(smallestMaxDatagramSize)
279+
}
280+
274281
// All stream-related frames. This should come last in the packet,
275282
// so large amounts of STREAM data don't crowd out other frames
276283
// we may need to send.

internal/quic/conn_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ type testConn struct {
168168
sentDatagrams [][]byte
169169
sentPackets []*testPacket
170170
sentFrames []debugFrame
171+
lastDatagram *testDatagram
171172
lastPacket *testPacket
172173

173174
recvDatagram chan *datagram
@@ -576,6 +577,7 @@ func (tc *testConn) readDatagram() *testDatagram {
576577
}
577578
p.frames = frames
578579
}
580+
tc.lastDatagram = d
579581
return d
580582
}
581583

internal/quic/frame_debug.go

+11-6
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ func parseDebugFrame(b []byte) (f debugFrame, n int) {
7777
// debugFramePadding is a sequence of PADDING frames.
7878
type debugFramePadding struct {
7979
size int
80+
to int // alternate for writing packets: pad to
8081
}
8182

8283
func parseDebugFramePadding(b []byte) (f debugFramePadding, n int) {
@@ -95,6 +96,10 @@ func (f debugFramePadding) write(w *packetWriter) bool {
9596
if w.avail() == 0 {
9697
return false
9798
}
99+
if f.to > 0 {
100+
w.appendPaddingTo(f.to)
101+
return true
102+
}
98103
for i := 0; i < f.size && w.avail() > 0; i++ {
99104
w.b = append(w.b, frameTypePadding)
100105
}
@@ -584,7 +589,7 @@ func (f debugFrameRetireConnectionID) LogValue() slog.Value {
584589

585590
// debugFramePathChallenge is a PATH_CHALLENGE frame.
586591
type debugFramePathChallenge struct {
587-
data uint64
592+
data pathChallengeData
588593
}
589594

590595
func parseDebugFramePathChallenge(b []byte) (f debugFramePathChallenge, n int) {
@@ -593,7 +598,7 @@ func parseDebugFramePathChallenge(b []byte) (f debugFramePathChallenge, n int) {
593598
}
594599

595600
func (f debugFramePathChallenge) String() string {
596-
return fmt.Sprintf("PATH_CHALLENGE Data=%016x", f.data)
601+
return fmt.Sprintf("PATH_CHALLENGE Data=%x", f.data)
597602
}
598603

599604
func (f debugFramePathChallenge) write(w *packetWriter) bool {
@@ -603,13 +608,13 @@ func (f debugFramePathChallenge) write(w *packetWriter) bool {
603608
func (f debugFramePathChallenge) LogValue() slog.Value {
604609
return slog.GroupValue(
605610
slog.String("frame_type", "path_challenge"),
606-
slog.String("data", fmt.Sprintf("%016x", f.data)),
611+
slog.String("data", fmt.Sprintf("%x", f.data)),
607612
)
608613
}
609614

610615
// debugFramePathResponse is a PATH_RESPONSE frame.
611616
type debugFramePathResponse struct {
612-
data uint64
617+
data pathChallengeData
613618
}
614619

615620
func parseDebugFramePathResponse(b []byte) (f debugFramePathResponse, n int) {
@@ -618,7 +623,7 @@ func parseDebugFramePathResponse(b []byte) (f debugFramePathResponse, n int) {
618623
}
619624

620625
func (f debugFramePathResponse) String() string {
621-
return fmt.Sprintf("PATH_RESPONSE Data=%016x", f.data)
626+
return fmt.Sprintf("PATH_RESPONSE Data=%x", f.data)
622627
}
623628

624629
func (f debugFramePathResponse) write(w *packetWriter) bool {
@@ -628,7 +633,7 @@ func (f debugFramePathResponse) write(w *packetWriter) bool {
628633
func (f debugFramePathResponse) LogValue() slog.Value {
629634
return slog.GroupValue(
630635
slog.String("frame_type", "path_response"),
631-
slog.String("data", fmt.Sprintf("%016x", f.data)),
636+
slog.String("data", fmt.Sprintf("%x", f.data)),
632637
)
633638
}
634639

internal/quic/packet_codec_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ func TestFrameEncodeDecode(t *testing.T) {
517517
s: "PATH_CHALLENGE Data=0123456789abcdef",
518518
j: `{"frame_type":"path_challenge","data":"0123456789abcdef"}`,
519519
f: debugFramePathChallenge{
520-
data: 0x0123456789abcdef,
520+
data: pathChallengeData{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
521521
},
522522
b: []byte{
523523
0x1a, // Type (i) = 0x1a,
@@ -527,7 +527,7 @@ func TestFrameEncodeDecode(t *testing.T) {
527527
s: "PATH_RESPONSE Data=0123456789abcdef",
528528
j: `{"frame_type":"path_response","data":"0123456789abcdef"}`,
529529
f: debugFramePathResponse{
530-
data: 0x0123456789abcdef,
530+
data: pathChallengeData{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
531531
},
532532
b: []byte{
533533
0x1b, // Type (i) = 0x1b,

internal/quic/packet_parser.go

+5-6
Original file line numberDiff line numberDiff line change
@@ -463,18 +463,17 @@ func consumeRetireConnectionIDFrame(b []byte) (seq int64, n int) {
463463
return seq, n
464464
}
465465

466-
func consumePathChallengeFrame(b []byte) (data uint64, n int) {
466+
func consumePathChallengeFrame(b []byte) (data pathChallengeData, n int) {
467467
n = 1
468-
var nn int
469-
data, nn = consumeUint64(b[n:])
470-
if nn < 0 {
471-
return 0, -1
468+
nn := copy(data[:], b[n:])
469+
if nn != len(data) {
470+
return data, -1
472471
}
473472
n += nn
474473
return data, n
475474
}
476475

477-
func consumePathResponseFrame(b []byte) (data uint64, n int) {
476+
func consumePathResponseFrame(b []byte) (data pathChallengeData, n int) {
478477
return consumePathChallengeFrame(b) // identical frame format
479478
}
480479

internal/quic/packet_writer.go

+7-10
Original file line numberDiff line numberDiff line change
@@ -243,10 +243,7 @@ func (w *packetWriter) appendPingFrame() (added bool) {
243243
return false
244244
}
245245
w.b = append(w.b, frameTypePing)
246-
// Mark this packet as ack-eliciting and in-flight,
247-
// but there's no need to record the presence of a PING frame in it.
248-
w.sent.ackEliciting = true
249-
w.sent.inFlight = true
246+
w.sent.markAckEliciting() // no need to record the frame itself
250247
return true
251248
}
252249

@@ -495,23 +492,23 @@ func (w *packetWriter) appendRetireConnectionIDFrame(seq int64) (added bool) {
495492
return true
496493
}
497494

498-
func (w *packetWriter) appendPathChallengeFrame(data uint64) (added bool) {
495+
func (w *packetWriter) appendPathChallengeFrame(data pathChallengeData) (added bool) {
499496
if w.avail() < 1+8 {
500497
return false
501498
}
502499
w.b = append(w.b, frameTypePathChallenge)
503-
w.b = binary.BigEndian.AppendUint64(w.b, data)
504-
w.sent.appendAckElicitingFrame(frameTypePathChallenge)
500+
w.b = append(w.b, data[:]...)
501+
w.sent.markAckEliciting() // no need to record the frame itself
505502
return true
506503
}
507504

508-
func (w *packetWriter) appendPathResponseFrame(data uint64) (added bool) {
505+
func (w *packetWriter) appendPathResponseFrame(data pathChallengeData) (added bool) {
509506
if w.avail() < 1+8 {
510507
return false
511508
}
512509
w.b = append(w.b, frameTypePathResponse)
513-
w.b = binary.BigEndian.AppendUint64(w.b, data)
514-
w.sent.appendAckElicitingFrame(frameTypePathResponse)
510+
w.b = append(w.b, data[:]...)
511+
w.sent.markAckEliciting() // no need to record the frame itself
515512
return true
516513
}
517514

0 commit comments

Comments
 (0)