forked from golang/net
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Matt Layher <[email protected]> Reviewed-by: Jonathan Amsterdam <[email protected]>
- Loading branch information
Showing
2 changed files
with
284 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
// Copyright 2023 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package quic | ||
|
||
// packetType is a QUIC packet type. | ||
// https://www.rfc-editor.org/rfc/rfc9000.html#section-17 | ||
type packetType byte | ||
|
||
const ( | ||
packetTypeInvalid = packetType(iota) | ||
packetTypeInitial | ||
packetType0RTT | ||
packetTypeHandshake | ||
packetTypeRetry | ||
packetType1RTT | ||
packetTypeVersionNegotiation | ||
) | ||
|
||
// Bits set in the first byte of a packet. | ||
const ( | ||
headerFormLong = 0x80 // https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2-3.2.1 | ||
headerFormShort = 0x00 // https://www.rfc-editor.org/rfc/rfc9000.html#section-17.3.1-4.2.1 | ||
fixedBit = 0x40 // https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2-3.4.1 | ||
reservedBits = 0x0c // https://www.rfc-editor.org/rfc/rfc9000#section-17.2-8.2.1 | ||
) | ||
|
||
// Long Packet Type bits. | ||
// https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2-3.6.1 | ||
const ( | ||
longPacketTypeInitial = 0 << 4 | ||
longPacketType0RTT = 1 << 4 | ||
longPacketTypeHandshake = 2 << 4 | ||
longPacketTypeRetry = 3 << 4 | ||
) | ||
|
||
// Frame types. | ||
// https://www.rfc-editor.org/rfc/rfc9000.html#section-19 | ||
const ( | ||
frameTypePadding = 0x00 | ||
frameTypePing = 0x01 | ||
frameTypeAck = 0x02 | ||
frameTypeAckECN = 0x03 | ||
frameTypeResetStream = 0x04 | ||
frameTypeStopSending = 0x05 | ||
frameTypeCrypto = 0x06 | ||
frameTypeNewToken = 0x07 | ||
frameTypeStreamBase = 0x08 // low three bits carry stream flags | ||
frameTypeMaxData = 0x10 | ||
frameTypeMaxStreamData = 0x11 | ||
frameTypeMaxStreamsBidi = 0x12 | ||
frameTypeMaxStreamsUni = 0x13 | ||
frameTypeDataBlocked = 0x14 | ||
frameTypeStreamDataBlocked = 0x15 | ||
frameTypeStreamsBlockedBidi = 0x16 | ||
frameTypeStreamsBlockedUni = 0x17 | ||
frameTypeNewConnectionID = 0x18 | ||
frameTypeRetireConnectionID = 0x19 | ||
frameTypePathChallenge = 0x1a | ||
frameTypePathResponse = 0x1b | ||
frameTypeConnectionCloseTransport = 0x1c | ||
frameTypeConnectionCloseApplication = 0x1d | ||
frameTypeHandshakeDone = 0x1e | ||
) | ||
|
||
// The low three bits of STREAM frames. | ||
// https://www.rfc-editor.org/rfc/rfc9000.html#section-19.8 | ||
const ( | ||
streamOffBit = 0x04 | ||
streamLenBit = 0x02 | ||
streamFinBit = 0x01 | ||
) | ||
|
||
// isLongHeader returns true if b is the first byte of a long header. | ||
func isLongHeader(b byte) bool { | ||
return b&headerFormLong == headerFormLong | ||
} | ||
|
||
// getPacketType returns the type of a packet. | ||
func getPacketType(b []byte) packetType { | ||
if len(b) == 0 { | ||
return packetTypeInvalid | ||
} | ||
if !isLongHeader(b[0]) { | ||
if b[0]&fixedBit != fixedBit { | ||
return packetTypeInvalid | ||
} | ||
return packetType1RTT | ||
} | ||
if len(b) < 5 { | ||
return packetTypeInvalid | ||
} | ||
if b[1] == 0 && b[2] == 0 && b[3] == 0 && b[4] == 0 { | ||
// Version Negotiation packets don't necessarily set the fixed bit. | ||
return packetTypeVersionNegotiation | ||
} | ||
if b[0]&fixedBit != fixedBit { | ||
return packetTypeInvalid | ||
} | ||
switch b[0] & 0x30 { | ||
case longPacketTypeInitial: | ||
return packetTypeInitial | ||
case longPacketType0RTT: | ||
return packetType0RTT | ||
case longPacketTypeHandshake: | ||
return packetTypeHandshake | ||
case longPacketTypeRetry: | ||
return packetTypeRetry | ||
} | ||
return packetTypeInvalid | ||
} | ||
|
||
// dstConnIDForDatagram returns the destination connection ID field of the | ||
// first QUIC packet in a datagram. | ||
func dstConnIDForDatagram(pkt []byte) (id []byte, ok bool) { | ||
if len(pkt) < 1 { | ||
return nil, false | ||
} | ||
var n int | ||
var b []byte | ||
if isLongHeader(pkt[0]) { | ||
if len(pkt) < 6 { | ||
return nil, false | ||
} | ||
n = int(pkt[5]) | ||
b = pkt[6:] | ||
} else { | ||
n = connIDLen | ||
b = pkt[1:] | ||
} | ||
if len(b) < n { | ||
return nil, false | ||
} | ||
return b[:n], true | ||
} | ||
|
||
// A longPacket is a long header packet. | ||
type longPacket struct { | ||
ptype packetType | ||
reservedBits uint8 | ||
version uint32 | ||
num packetNumber | ||
dstConnID []byte | ||
srcConnID []byte | ||
payload []byte | ||
|
||
// The extra data depends on the packet type: | ||
// Initial: Token. | ||
// Retry: Retry token and integrity tag. | ||
extra []byte | ||
} | ||
|
||
// A shortPacket is a short header (1-RTT) packet. | ||
type shortPacket struct { | ||
reservedBits uint8 | ||
num packetNumber | ||
payload []byte | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
// Copyright 2023 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package quic | ||
|
||
import ( | ||
"bytes" | ||
"encoding/hex" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
func TestPacketHeader(t *testing.T) { | ||
for _, test := range []struct { | ||
name string | ||
packet []byte | ||
isLongHeader bool | ||
packetType packetType | ||
dstConnID []byte | ||
}{{ | ||
// Initial packet from https://www.rfc-editor.org/rfc/rfc9001#section-a.1 | ||
// (truncated) | ||
name: "rfc9001_a1", | ||
packet: unhex(` | ||
c000000001088394c8f03e5157080000 449e7b9aec34d1b1c98dd7689fb8ec11 | ||
`), | ||
isLongHeader: true, | ||
packetType: packetTypeInitial, | ||
dstConnID: unhex(`8394c8f03e515708`), | ||
}, { | ||
// Initial packet from https://www.rfc-editor.org/rfc/rfc9001#section-a.3 | ||
// (truncated) | ||
name: "rfc9001_a3", | ||
packet: unhex(` | ||
cf000000010008f067a5502a4262b500 4075c0d95a482cd0991cd25b0aac406a | ||
`), | ||
isLongHeader: true, | ||
packetType: packetTypeInitial, | ||
dstConnID: []byte{}, | ||
}, { | ||
// Retry packet from https://www.rfc-editor.org/rfc/rfc9001#section-a.4 | ||
name: "rfc9001_a4", | ||
packet: unhex(` | ||
ff000000010008f067a5502a4262b574 6f6b656e04a265ba2eff4d829058fb3f | ||
0f2496ba | ||
`), | ||
isLongHeader: true, | ||
packetType: packetTypeRetry, | ||
dstConnID: []byte{}, | ||
}, { | ||
// Short header packet from https://www.rfc-editor.org/rfc/rfc9001#section-a.5 | ||
name: "rfc9001_a5", | ||
packet: unhex(` | ||
4cfe4189655e5cd55c41f69080575d7999c25a5bfb | ||
`), | ||
isLongHeader: false, | ||
packetType: packetType1RTT, | ||
dstConnID: unhex(`fe4189655e5cd55c`), | ||
}, { | ||
// Version Negotiation packet. | ||
name: "version_negotiation", | ||
packet: unhex(` | ||
80 00000000 01ff0001020304 | ||
`), | ||
isLongHeader: true, | ||
packetType: packetTypeVersionNegotiation, | ||
dstConnID: []byte{0xff}, | ||
}, { | ||
// Too-short packet. | ||
name: "truncated_after_connid_length", | ||
packet: unhex(` | ||
cf0000000105 | ||
`), | ||
isLongHeader: true, | ||
packetType: packetTypeInitial, | ||
dstConnID: nil, | ||
}, { | ||
// Too-short packet. | ||
name: "truncated_after_version", | ||
packet: unhex(` | ||
cf00000001 | ||
`), | ||
isLongHeader: true, | ||
packetType: packetTypeInitial, | ||
dstConnID: nil, | ||
}, { | ||
// Much too short packet. | ||
name: "truncated_in_version", | ||
packet: unhex(` | ||
cf000000 | ||
`), | ||
isLongHeader: true, | ||
packetType: packetTypeInvalid, | ||
dstConnID: nil, | ||
}} { | ||
t.Run(test.name, func(t *testing.T) { | ||
if got, want := isLongHeader(test.packet[0]), test.isLongHeader; got != want { | ||
t.Errorf("packet %x:\nisLongHeader(packet) = %v, want %v", test.packet, got, want) | ||
} | ||
if got, want := getPacketType(test.packet), test.packetType; got != want { | ||
t.Errorf("packet %x:\ngetPacketType(packet) = %v, want %v", test.packet, got, want) | ||
} | ||
gotConnID, gotOK := dstConnIDForDatagram(test.packet) | ||
wantConnID, wantOK := test.dstConnID, test.dstConnID != nil | ||
if !bytes.Equal(gotConnID, wantConnID) || gotOK != wantOK { | ||
t.Errorf("packet %x:\ndstConnIDForDatagram(packet) = {%x}, %v; want {%x}, %v", test.packet, gotConnID, gotOK, wantConnID, wantOK) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func unhex(s string) []byte { | ||
b, err := hex.DecodeString(strings.Map(func(c rune) rune { | ||
switch c { | ||
case ' ', '\t', '\n': | ||
return -1 | ||
} | ||
return c | ||
}, s)) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return b | ||
} |