Skip to content

Commit

Permalink
quic: basic packet operations
Browse files Browse the repository at this point in the history
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
neild committed May 25, 2023
1 parent d4a2c13 commit 6488c8f
Show file tree
Hide file tree
Showing 2 changed files with 284 additions and 0 deletions.
159 changes: 159 additions & 0 deletions internal/quic/packet.go
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
}
125 changes: 125 additions & 0 deletions internal/quic/packet_test.go
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
}

0 comments on commit 6488c8f

Please sign in to comment.