Skip to content

WIP QUIC: Improving initial packet dissection and building. #4773

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

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
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
13 changes: 13 additions & 0 deletions scapy/layers/quic/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0-only
# This file is part of Scapy
# See https://scapy.net/ for more information

"""
[QUIC] Tools for handling QUIC packets and sessions.

TODO:
- Rework organization of QUIC layers. (e.g., layers for QUIC packet, QUIC payload, QUIC frame, etc.)
- Implement cryptographic features for QUIC, including initial encryption contexts based on QUIC Version.
- Implement automaton for Handshake, sessions, etc.
- And more...
"""
9 changes: 9 additions & 0 deletions scapy/layers/quic/all.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0-only
# This file is part of Scapy
# See https://scapy.net/ for more information

"""
Aggregate top level objects from all QUIC modules.
"""

from scapy.layers.quic.packet import *
201 changes: 27 additions & 174 deletions scapy/layers/quic.py → scapy/layers/quic/basefields.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,27 @@
# SPDX-License-Identifier: GPL-2.0-only
# This file is part of Scapy
# See https://scapy.net/ for more information
# Copyright (C) Gabriel Potter <gabriel[]potter[]fr>

"""
QUIC

The draft of a very basic implementation of the structures from [RFC 9000].
This isn't binded to UDP by default as currently too incomplete.

TODO:
- payloads.
- encryption.
- automaton.
- etc.
QUIC base fields, used for QUIC packet parsing/building.
"""

import struct

from scapy.packet import (
Packet,
)

from scapy.fields import (
_EnumField,
BitEnumField,
BitField,
ByteEnumField,
ByteField,
EnumField,
Field,
FieldLenField,
FieldListField,
IntField,
MultipleTypeField,
PacketListField,
ShortField,
StrLenField,
ThreeBytesField,
)

Expand Down Expand Up @@ -69,6 +56,14 @@
0x1E: "HANDSHAKE_DONE",
}

# QUIC Versions
# https://www.iana.org/assignments/quic/quic.xhtml#quic-versions
_quic_versions = {
0x00000000: "QUIC Version Negotiaion", # RFC9000 sect 17.2.1
0x00000001: "QUIC v1", # RFC9000 sect 15
0x6b3342cf: "QUIC v2", # RFC9369 sect 3.1
}


# RFC9000 sect 16
class QuicVarIntField(Field[int, int]):
Expand Down Expand Up @@ -144,53 +139,24 @@ def i2repr(
# RFC9000 sect 17 abstraction


class QUIC(Packet):
match_subclass = True

@classmethod
def dispatch_hook(cls, _pkt=None, *args, **kargs):
"""
Returns the right class for the given data.
"""
if _pkt:
hdr = _pkt[0]
if hdr & 0x80:
# Long Header packets
if hdr & 0x40 == 0:
return QUIC_Version
else:
typ = (hdr & 0x30) >> 4
return {
0: QUIC_Initial,
1: QUIC_0RTT,
2: QUIC_Handshake,
3: QUIC_Retry,
}[typ]
class QuicPacketNumberBitFieldLenField(BitField):
def i2m(self, pkt, x):
if x is None and pkt is not None:
PacketNumber = pkt.PacketNumber or 0
if PacketNumber < 0 or PacketNumber > 0xFFFFFFFF:
raise struct.error("requires 0 <= number <= 0xFFFFFFFF")
if PacketNumber < 0x100:
return 0
elif PacketNumber < 0x10000:
return 1
elif PacketNumber < 0x1000000:
return 2
else:
# Short Header packets
return QUIC_1RTT
return QUIC_Initial

def mysummary(self):
return self.name


# RFC9000 sect 17.2.1


class QUIC_Version(QUIC):
name = "QUIC - Version Negotiation"
fields_desc = [
BitEnumField("HeaderForm", 1, 1, _quic_long_hdr),
BitField("Unused", 0, 7),
IntField("Version", 0),
FieldLenField("DstConnIDLen", None, length_of="DstConnID", fmt="B"),
StrLenField("DstConnID", "", length_from=lambda pkt: pkt.DstConnIDLen),
FieldLenField("SrcConnIDLen", None, length_of="SrcConnID", fmt="B"),
StrLenField("SrcConnID", "", length_from=lambda pkt: pkt.SrcConnIDLen),
FieldListField("SupportedVersions", [], IntField("", 0)),
]

return 3
elif x is None:
return 0
return x

# RFC9000 sect 17.2.2

Expand Down Expand Up @@ -227,116 +193,3 @@ class QUIC_Version(QUIC):
],
ByteField(name, default),
)


class QuicPacketNumberBitFieldLenField(BitField):
def i2m(self, pkt, x):
if x is None and pkt is not None:
PacketNumber = pkt.PacketNumber or 0
if PacketNumber < 0 or PacketNumber > 0xFFFFFFFF:
raise struct.error("requires 0 <= number <= 0xFFFFFFFF")
if PacketNumber < 0x100:
return 0
elif PacketNumber < 0x10000:
return 1
elif PacketNumber < 0x1000000:
return 2
else:
return 3
elif x is None:
return 0
return x


class QUIC_Initial(QUIC):
name = "QUIC - Initial"
Version = 0x00000001
fields_desc = (
[
BitEnumField("HeaderForm", 1, 1, _quic_long_hdr),
BitField("FixedBit", 1, 1),
BitEnumField("LongPacketType", 0, 2, _quic_long_pkttyp),
BitField("Reserved", 0, 2),
QuicPacketNumberBitFieldLenField("PacketNumberLen", None, 2),
]
+ QUIC_Version.fields_desc[2:7]
+ [
QuicVarLenField("TokenLen", None, length_of="Token"),
StrLenField("Token", "", length_from=lambda pkt: pkt.TokenLen),
QuicVarIntField("Length", 0),
QuicPacketNumberField("PacketNumber", 0),
]
)


# RFC9000 sect 17.2.3
class QUIC_0RTT(QUIC):
name = "QUIC - 0-RTT"
LongPacketType = 1
fields_desc = QUIC_Initial.fields_desc[:10] + [
QuicVarIntField("Length", 0),
QuicPacketNumberField("PacketNumber", 0),
]


# RFC9000 sect 17.2.4
class QUIC_Handshake(QUIC):
name = "QUIC - Handshake"
LongPacketType = 2
fields_desc = QUIC_0RTT.fields_desc


# RFC9000 sect 17.2.5
class QUIC_Retry(QUIC):
name = "QUIC - Retry"
LongPacketType = 3
Version = 0x00000001
fields_desc = (
QUIC_Initial.fields_desc[:3]
+ [
BitField("Unused", 0, 4),
]
+ QUIC_Version.fields_desc[2:7]
)


# RFC9000 sect 17.3
class QUIC_1RTT(QUIC):
name = "QUIC - 1-RTT"
fields_desc = [
BitEnumField("HeaderForm", 0, 1, _quic_long_hdr),
BitField("FixedBit", 1, 1),
BitField("SpinBit", 0, 1),
BitField("Reserved", 0, 2),
BitField("KeyPhase", 0, 1),
QuicPacketNumberBitFieldLenField("PacketNumberLen", None, 2),
# FIXME - Destination Connection ID
QuicPacketNumberField("PacketNumber", 0),
]


# RFC9000 sect 19.1
class QUIC_PADDING(Packet):
fields_desc = [
ByteEnumField("Type", 0x00, _quic_payloads),
]


# RFC9000 sect 19.2
class QUIC_PING(Packet):
fields_desc = [
ByteEnumField("Type", 0x01, _quic_payloads),
]


# RFC9000 sect 19.3
class QUIC_ACK(Packet):
fields_desc = [
ByteEnumField("Type", 0x02, _quic_payloads),
]


# Bindings
# bind_bottom_up(UDP, QUIC, dport=443)
# bind_bottom_up(UDP, QUIC, sport=443)
# bind_layers(UDP, QUIC, dport=443, sport=443)
Loading