Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for QUIC v2 #16

Merged
merged 6 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 108 additions & 62 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -1,74 +1,66 @@
# Clang-format configuration for Zeek. This configuration requires
# at least clang-format 12.0.1 to format correctly.

Language: Cpp
Standard: c++17

BreakBeforeBraces: Whitesmiths

# BraceWrapping:
# AfterCaseLabel: true
# AfterClass: false
# AfterControlStatement: Always
# AfterEnum: false
# AfterFunction: true
# AfterNamespace: false
# AfterStruct: false
# AfterUnion: false
# AfterExternBlock: false
# BeforeCatch: true
# BeforeElse: true
# BeforeWhile: false
# IndentBraces: true
# SplitEmptyFunction: false
# SplitEmptyRecord: false
# SplitEmptyNamespace: false
# Copyright (c) 2020-2023 by the Zeek Project. See LICENSE for details.

---
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignTrailingComments: false
AllowShortBlocksOnASingleLine: Empty
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Inline
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: false
AllowShortLambdasOnASingleLine: Empty
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BinPackParameters: true
BreakConstructorInitializers: BeforeColon
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: true
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
ColumnLimit: 100
ConstructorInitializerAllOnOneLineOrOnePerLine: false
FixNamespaceComments: false
IndentCaseLabels: true
IndentCaseBlocks: false
IndentExternBlock: NoIndent
IndentPPDirectives: None
IndentWidth: 4
NamespaceIndentation: None
PointerAlignment: Left
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: true
SpaceInEmptyParentheses: false
SpacesInAngles: false
SpacesInConditionalStatement: true
SpacesInContainerLiterals: false
SpacesInParentheses: false
TabWidth: 4
UseTab: AlignWithSpaces

# Setting this to a high number causes clang-format to prefer breaking somewhere else
# over breaking after the assignment operator in a line that's over the column limit
PenaltyBreakAssignment: 100

BreakBeforeTernaryOperators: false
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 120
CommentPragmas: 'NOLINT'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Regroup

# Include categories go like this:
Expand Down Expand Up @@ -98,3 +90,57 @@ IncludeCategories:
Priority: 4
- Regex: '.*'
Priority: 5

IncludeIsMainRegex: '$'
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: '^BEGIN_'
MacroBlockEnd: '^END_'
MaxEmptyLinesToKeep: 2
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 500
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 1000
PointerAlignment: Left
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: false
SpaceAfterLogicalNot: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpacesInConditionalStatement: true
Standard: Cpp11
StatementMacros:
- STANDARD_OPERATOR_1
TabWidth: 4
UseTab: Never
---
Language: Json
...
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ repos:
exclude: ^testing/Baseline/
repos:
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: 'v13.0.0'
rev: 'v17.0.3'
hooks:
- id: clang-format
- repo: https://github.com/crate-ci/typos
Expand Down
3 changes: 3 additions & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[default.extend-words]
# `inout` is used as a keyword in Spicy, but looks like a typo of `input`.
inout = "inout"
5 changes: 2 additions & 3 deletions analyzer/QUIC.evt
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ protocol analyzer QUIC over UDP:

import QUIC;

# Make the enum available.
export QUIC::LongPacketType;

on QUIC::InitialPacket -> event QUIC::initial_packet($conn, $is_orig, self.header.version, self.header.dest_conn_id, self.header.src_conn_id);

on QUIC::RetryPacket -> event QUIC::retry_packet($conn, $is_orig, self.header.version, self.header.dest_conn_id, self.header.src_conn_id, self.retry_token, self.integrity_tag);
Expand All @@ -25,3 +22,5 @@ on QUIC::ZeroRTTPacket -> event QUIC::zero_rtt_packet($conn, $is_orig, self.head

on QUIC::ConnectionClosePayload -> event QUIC::connection_close_frame($conn, $is_orig, self.header.version, self.header.dest_conn_id, self.header.src_conn_id,
self.error_code.result, self.reason_phrase);

on QUIC::UnhandledVersion -> event QUIC::unhandled_version($conn, $is_orig, self.header.version, self.header.dest_conn_id, self.header.src_conn_id);
93 changes: 75 additions & 18 deletions analyzer/QUIC.spicy
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import zeek;

# The interface to the C++ code that handles the decryption of the INITIAL packet payload using well-known keys
public function decrypt_crypto_payload(
version: uint32,
all_data: bytes,
connection_id: bytes,
encrypted_offset: uint64,
Expand All @@ -23,11 +24,10 @@ public function decrypt_crypto_payload(
# Can we decrypt?
function can_decrypt(long_header: LongHeaderPacket, context: ConnectionIDInfo, is_client: bool): bool {

if ( long_header.first_byte.packet_type != LongPacketType::INITIAL )
if ( ! long_header.is_initial )
return False;

# decrypt_crypto_payload() has known secrets for version 1, nothing else.
if ( long_header.version != 0x00000001 )
if ( long_header.version != Version1 && long_header.version != Version2 )
return False;

if ( is_client )
Expand Down Expand Up @@ -80,14 +80,26 @@ type ConnectionIDInfo = struct {
##############
# Definitions
##############
const Version1: uint32 = 0x00000001;
const Version2: uint32 = 0x6b3343cf;

type LongPacketType = enum {
type LongPacketTypeV1 = enum {
INITIAL = 0,
ZERO_RTT = 1,
HANDSHAKE = 2,
RETRY = 3,
};

# V2 changed packet types to avoid ossification.
#
# https://www.rfc-editor.org/rfc/rfc9369.html#name-long-header-packet-types
type LongPacketTypeV2 = enum {
INITIAL = 1,
ZERO_RTT = 2,
HANDSHAKE = 3,
RETRY = 0,
};

type HeaderForm = enum {
SHORT = 0,
LONG = 1,
Expand Down Expand Up @@ -154,17 +166,66 @@ type VariableLengthInteger = unit {
# Long packets
# Generic units
##############
public type LongHeaderPacketV1 = unit(inout outer: LongHeaderPacket) {
switch ( LongPacketTypeV1(outer.first_byte.packet_type) ) {
LongPacketTypeV1::INITIAL -> initial_hdr : InitialPacket(outer) {
outer.is_initial = True;
outer.encrypted_offset = outer.offset() +
self.initial_hdr.length.bytes_to_parse +
self.initial_hdr.token_length.bytes_to_parse +
self.initial_hdr.token_length.result;
outer.payload_length = self.initial_hdr.length.result;
}

LongPacketTypeV1::ZERO_RTT -> zerortt_hdr : ZeroRTTPacket(outer);
LongPacketTypeV1::HANDSHAKE -> handshake_hdr : HandshakePacket(outer);
LongPacketTypeV1::RETRY -> retry_hdr : RetryPacket(outer) {
outer.is_retry = True;
}
};
};

public type LongHeaderPacketV2 = unit(inout outer: LongHeaderPacket) {
switch ( LongPacketTypeV2(outer.first_byte.packet_type) ) {
LongPacketTypeV2::INITIAL -> initial_hdr : InitialPacket(outer) {
outer.is_initial = True;
outer.encrypted_offset = outer.offset() +
self.initial_hdr.length.bytes_to_parse +
self.initial_hdr.token_length.bytes_to_parse +
self.initial_hdr.token_length.result;
outer.payload_length = self.initial_hdr.length.result;
}

LongPacketTypeV2::ZERO_RTT -> zerortt_hdr : ZeroRTTPacket(outer);
LongPacketTypeV2::HANDSHAKE -> handshake_hdr : HandshakePacket(outer);
LongPacketTypeV2::RETRY -> retry_hdr : RetryPacket(outer) {
outer.is_retry = True;
}
};
};

# Just eat the data for event raising.
public type UnhandledVersion = unit(header: LongHeaderPacket) {
var header: LongHeaderPacket = header;
@if SPICY_VERSION >= 10800
payload: skip bytes &eod;
@else
payload: bytes &eod;
@endif
};

public type LongHeaderPacket = unit {
var encrypted_offset: uint64;
var payload_length: uint64;
var client_conn_id_length: uint8;
var server_conn_id_length: uint8;
var is_initial: bool;
var is_retry: bool;

first_byte: bitfield(8) {
header_form: 7 &convert=cast<HeaderForm>(cast<uint8>($$));
fixed_bit: 6;
packet_type: 4..5 &convert=cast<LongPacketType>(cast<uint8>($$));
packet_type: 4..5;
type_specific_bits: 0..3 &convert=cast<uint8>($$);
};

Expand All @@ -174,18 +235,12 @@ public type LongHeaderPacket = unit {
src_conn_id_len: uint8 { self.client_conn_id_length = $$; }
src_conn_id: bytes &size=self.client_conn_id_length;

switch ( self.first_byte.packet_type ) {
LongPacketType::INITIAL -> initial_hdr : InitialPacket(self) {
self.encrypted_offset = self.offset() +
self.initial_hdr.length.bytes_to_parse +
self.initial_hdr.token_length.bytes_to_parse +
self.initial_hdr.token_length.result;
self.payload_length = self.initial_hdr.length.result;
switch ( self.version ) {
Version1 -> v1: LongHeaderPacketV1(self);
Version2 -> v2: LongHeaderPacketV2(self);
* -> unknown: UnhandledVersion(self) {
throw "unhandled QUIC version 0x%x" % self.version;
}

LongPacketType::ZERO_RTT -> zerortt_hdr : ZeroRTTPacket(self);
LongPacketType::HANDSHAKE -> handshake_hdr : HandshakePacket(self);
LongPacketType::RETRY -> retry_hdr : RetryPacket(self);
};
};

Expand Down Expand Up @@ -401,7 +456,7 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {

# If we see a retry packet from the responder, reset the decryption
# context such that the next DCID from the client is used for decryption.
if ( self.long_header.first_byte.packet_type == LongPacketType::RETRY ) {
if ( self.long_header.is_retry ) {
context.client_initial_processed = False;
context.server_initial_processed = False;
context.initial_destination_conn_id = b"";
Expand Down Expand Up @@ -430,6 +485,7 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
# This means that here, we can try to decrypt the initial packet!
# All data is accessible via the `long_header` unit
self.decrypted_data = decrypt_crypto_payload(
self.long_header.version,
self.all_data,
self.long_header.dest_conn_id,
self.long_header.encrypted_offset,
Expand All @@ -449,6 +505,7 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
# Assuming that the client set up the connection, this can be considered the first
# received Initial from the client. So disable change of ConnectionID's afterwards
self.decrypted_data = decrypt_crypto_payload(
self.long_header.version,
self.all_data,
context.initial_destination_conn_id,
self.long_header.encrypted_offset,
Expand All @@ -467,7 +524,7 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
# are restablished and decryption is no longer possible
#
# TODO: verify if this is actually correct per RFC
if ( self.long_header.first_byte.packet_type != LongPacketType::RETRY && ! from_client ) {
if ( ! self.long_header.is_retry && ! from_client ) {
context.server_initial_processed = True;
context.client_initial_processed = True;
}
Expand Down
Loading
Loading