Skip to content

Commit 6488c8f

Browse files
committed
quic: basic packet operations
The type of a QUIC packet can be identified by inspecting its first byte, and the destination connection ID can be determined without decrypting and parsing the entire packet. For golang/go#58547 Change-Id: Ie298c0f6c0017343168a0974543e37ab7a569b0f Reviewed-on: https://go-review.googlesource.com/c/net/+/475437 Run-TryBot: Damien Neil <dneil@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Matt Layher <mdlayher@gmail.com> Reviewed-by: Jonathan Amsterdam <jba@google.com>
1 parent d4a2c13 commit 6488c8f

File tree

2 files changed

+284
-0
lines changed

2 files changed

+284
-0
lines changed

internal/quic/packet.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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+
package quic
6+
7+
// packetType is a QUIC packet type.
8+
// https://www.rfc-editor.org/rfc/rfc9000.html#section-17
9+
type packetType byte
10+
11+
const (
12+
packetTypeInvalid = packetType(iota)
13+
packetTypeInitial
14+
packetType0RTT
15+
packetTypeHandshake
16+
packetTypeRetry
17+
packetType1RTT
18+
packetTypeVersionNegotiation
19+
)
20+
21+
// Bits set in the first byte of a packet.
22+
const (
23+
headerFormLong = 0x80 // https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2-3.2.1
24+
headerFormShort = 0x00 // https://www.rfc-editor.org/rfc/rfc9000.html#section-17.3.1-4.2.1
25+
fixedBit = 0x40 // https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2-3.4.1
26+
reservedBits = 0x0c // https://www.rfc-editor.org/rfc/rfc9000#section-17.2-8.2.1
27+
)
28+
29+
// Long Packet Type bits.
30+
// https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2-3.6.1
31+
const (
32+
longPacketTypeInitial = 0 << 4
33+
longPacketType0RTT = 1 << 4
34+
longPacketTypeHandshake = 2 << 4
35+
longPacketTypeRetry = 3 << 4
36+
)
37+
38+
// Frame types.
39+
// https://www.rfc-editor.org/rfc/rfc9000.html#section-19
40+
const (
41+
frameTypePadding = 0x00
42+
frameTypePing = 0x01
43+
frameTypeAck = 0x02
44+
frameTypeAckECN = 0x03
45+
frameTypeResetStream = 0x04
46+
frameTypeStopSending = 0x05
47+
frameTypeCrypto = 0x06
48+
frameTypeNewToken = 0x07
49+
frameTypeStreamBase = 0x08 // low three bits carry stream flags
50+
frameTypeMaxData = 0x10
51+
frameTypeMaxStreamData = 0x11
52+
frameTypeMaxStreamsBidi = 0x12
53+
frameTypeMaxStreamsUni = 0x13
54+
frameTypeDataBlocked = 0x14
55+
frameTypeStreamDataBlocked = 0x15
56+
frameTypeStreamsBlockedBidi = 0x16
57+
frameTypeStreamsBlockedUni = 0x17
58+
frameTypeNewConnectionID = 0x18
59+
frameTypeRetireConnectionID = 0x19
60+
frameTypePathChallenge = 0x1a
61+
frameTypePathResponse = 0x1b
62+
frameTypeConnectionCloseTransport = 0x1c
63+
frameTypeConnectionCloseApplication = 0x1d
64+
frameTypeHandshakeDone = 0x1e
65+
)
66+
67+
// The low three bits of STREAM frames.
68+
// https://www.rfc-editor.org/rfc/rfc9000.html#section-19.8
69+
const (
70+
streamOffBit = 0x04
71+
streamLenBit = 0x02
72+
streamFinBit = 0x01
73+
)
74+
75+
// isLongHeader returns true if b is the first byte of a long header.
76+
func isLongHeader(b byte) bool {
77+
return b&headerFormLong == headerFormLong
78+
}
79+
80+
// getPacketType returns the type of a packet.
81+
func getPacketType(b []byte) packetType {
82+
if len(b) == 0 {
83+
return packetTypeInvalid
84+
}
85+
if !isLongHeader(b[0]) {
86+
if b[0]&fixedBit != fixedBit {
87+
return packetTypeInvalid
88+
}
89+
return packetType1RTT
90+
}
91+
if len(b) < 5 {
92+
return packetTypeInvalid
93+
}
94+
if b[1] == 0 && b[2] == 0 && b[3] == 0 && b[4] == 0 {
95+
// Version Negotiation packets don't necessarily set the fixed bit.
96+
return packetTypeVersionNegotiation
97+
}
98+
if b[0]&fixedBit != fixedBit {
99+
return packetTypeInvalid
100+
}
101+
switch b[0] & 0x30 {
102+
case longPacketTypeInitial:
103+
return packetTypeInitial
104+
case longPacketType0RTT:
105+
return packetType0RTT
106+
case longPacketTypeHandshake:
107+
return packetTypeHandshake
108+
case longPacketTypeRetry:
109+
return packetTypeRetry
110+
}
111+
return packetTypeInvalid
112+
}
113+
114+
// dstConnIDForDatagram returns the destination connection ID field of the
115+
// first QUIC packet in a datagram.
116+
func dstConnIDForDatagram(pkt []byte) (id []byte, ok bool) {
117+
if len(pkt) < 1 {
118+
return nil, false
119+
}
120+
var n int
121+
var b []byte
122+
if isLongHeader(pkt[0]) {
123+
if len(pkt) < 6 {
124+
return nil, false
125+
}
126+
n = int(pkt[5])
127+
b = pkt[6:]
128+
} else {
129+
n = connIDLen
130+
b = pkt[1:]
131+
}
132+
if len(b) < n {
133+
return nil, false
134+
}
135+
return b[:n], true
136+
}
137+
138+
// A longPacket is a long header packet.
139+
type longPacket struct {
140+
ptype packetType
141+
reservedBits uint8
142+
version uint32
143+
num packetNumber
144+
dstConnID []byte
145+
srcConnID []byte
146+
payload []byte
147+
148+
// The extra data depends on the packet type:
149+
// Initial: Token.
150+
// Retry: Retry token and integrity tag.
151+
extra []byte
152+
}
153+
154+
// A shortPacket is a short header (1-RTT) packet.
155+
type shortPacket struct {
156+
reservedBits uint8
157+
num packetNumber
158+
payload []byte
159+
}

internal/quic/packet_test.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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+
package quic
6+
7+
import (
8+
"bytes"
9+
"encoding/hex"
10+
"strings"
11+
"testing"
12+
)
13+
14+
func TestPacketHeader(t *testing.T) {
15+
for _, test := range []struct {
16+
name string
17+
packet []byte
18+
isLongHeader bool
19+
packetType packetType
20+
dstConnID []byte
21+
}{{
22+
// Initial packet from https://www.rfc-editor.org/rfc/rfc9001#section-a.1
23+
// (truncated)
24+
name: "rfc9001_a1",
25+
packet: unhex(`
26+
c000000001088394c8f03e5157080000 449e7b9aec34d1b1c98dd7689fb8ec11
27+
`),
28+
isLongHeader: true,
29+
packetType: packetTypeInitial,
30+
dstConnID: unhex(`8394c8f03e515708`),
31+
}, {
32+
// Initial packet from https://www.rfc-editor.org/rfc/rfc9001#section-a.3
33+
// (truncated)
34+
name: "rfc9001_a3",
35+
packet: unhex(`
36+
cf000000010008f067a5502a4262b500 4075c0d95a482cd0991cd25b0aac406a
37+
`),
38+
isLongHeader: true,
39+
packetType: packetTypeInitial,
40+
dstConnID: []byte{},
41+
}, {
42+
// Retry packet from https://www.rfc-editor.org/rfc/rfc9001#section-a.4
43+
name: "rfc9001_a4",
44+
packet: unhex(`
45+
ff000000010008f067a5502a4262b574 6f6b656e04a265ba2eff4d829058fb3f
46+
0f2496ba
47+
`),
48+
isLongHeader: true,
49+
packetType: packetTypeRetry,
50+
dstConnID: []byte{},
51+
}, {
52+
// Short header packet from https://www.rfc-editor.org/rfc/rfc9001#section-a.5
53+
name: "rfc9001_a5",
54+
packet: unhex(`
55+
4cfe4189655e5cd55c41f69080575d7999c25a5bfb
56+
`),
57+
isLongHeader: false,
58+
packetType: packetType1RTT,
59+
dstConnID: unhex(`fe4189655e5cd55c`),
60+
}, {
61+
// Version Negotiation packet.
62+
name: "version_negotiation",
63+
packet: unhex(`
64+
80 00000000 01ff0001020304
65+
`),
66+
isLongHeader: true,
67+
packetType: packetTypeVersionNegotiation,
68+
dstConnID: []byte{0xff},
69+
}, {
70+
// Too-short packet.
71+
name: "truncated_after_connid_length",
72+
packet: unhex(`
73+
cf0000000105
74+
`),
75+
isLongHeader: true,
76+
packetType: packetTypeInitial,
77+
dstConnID: nil,
78+
}, {
79+
// Too-short packet.
80+
name: "truncated_after_version",
81+
packet: unhex(`
82+
cf00000001
83+
`),
84+
isLongHeader: true,
85+
packetType: packetTypeInitial,
86+
dstConnID: nil,
87+
}, {
88+
// Much too short packet.
89+
name: "truncated_in_version",
90+
packet: unhex(`
91+
cf000000
92+
`),
93+
isLongHeader: true,
94+
packetType: packetTypeInvalid,
95+
dstConnID: nil,
96+
}} {
97+
t.Run(test.name, func(t *testing.T) {
98+
if got, want := isLongHeader(test.packet[0]), test.isLongHeader; got != want {
99+
t.Errorf("packet %x:\nisLongHeader(packet) = %v, want %v", test.packet, got, want)
100+
}
101+
if got, want := getPacketType(test.packet), test.packetType; got != want {
102+
t.Errorf("packet %x:\ngetPacketType(packet) = %v, want %v", test.packet, got, want)
103+
}
104+
gotConnID, gotOK := dstConnIDForDatagram(test.packet)
105+
wantConnID, wantOK := test.dstConnID, test.dstConnID != nil
106+
if !bytes.Equal(gotConnID, wantConnID) || gotOK != wantOK {
107+
t.Errorf("packet %x:\ndstConnIDForDatagram(packet) = {%x}, %v; want {%x}, %v", test.packet, gotConnID, gotOK, wantConnID, wantOK)
108+
}
109+
})
110+
}
111+
}
112+
113+
func unhex(s string) []byte {
114+
b, err := hex.DecodeString(strings.Map(func(c rune) rune {
115+
switch c {
116+
case ' ', '\t', '\n':
117+
return -1
118+
}
119+
return c
120+
}, s))
121+
if err != nil {
122+
panic(err)
123+
}
124+
return b
125+
}

0 commit comments

Comments
 (0)