Skip to content

Commit 09ead29

Browse files
committed
feat(foundation): reassemble fragmented DTLS handshake messages (#2440)
- add foundation.reassembly.dtls.DTLS, keyed by UDP 4-tuple and DTLS epoch - DTLS.read decodes the fragment completing a handshake message into ClientHello/ServerHello - overlapping fragments are accepted (RFC 6347 4.2.3) - opt-in only; Extractor-level wiring left as a follow-up - isort-clean reassembly and DTLS modules - DTLS.read reassembles only the datagram's first record (Known Limitations) Tests: tests/foundation/reassembly/ -- 197 passed, 897 subtests.
1 parent b6efe66 commit 09ead29

14 files changed

Lines changed: 1541 additions & 32 deletions

File tree

‎docs/source/changelog/1.5.0.rst‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -958,7 +958,7 @@ Changed
958958
:rfc:`791` specifies the opposite resolution, and records the disagreement
959959
instead (:pr:`482`).
960960
* renames with no compatibility alias left behind: ``HoleDiscriptor`` is spelled
961-
:class:`.HoleDescriptor` and its package alias ``TCP_HoleDiscriptor`` is
961+
:class:`~pcapkit.foundation.reassembly.data.tcp.HoleDescriptor` and its package alias ``TCP_HoleDiscriptor`` is
962962
``TCP_HoleDescriptor`` (:pr:`350`).
963963
* subclass registration is **opt-in** for :class:`.Engine`, :class:`.Reassembly`,
964964
:class:`.TraceFlow` and ``dumpkit``'s :class:`.Dumper` (:issue:`514`). Each registers

‎docs/source/contributing/pep.rst‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ without keys.
246246
**Not done: reassembling a handshake message DTLS has fragmented across
247247
several records** (:rfc:`6347#section-4.2.3`, :issue:`2440`). That needs a
248248
:class:`~pcapkit.foundation.reassembly.reassembly.Reassembly` subclass keyed
249-
on the UDP 5-tuple plus epoch. A fragmented message's body is kept as that one
249+
on the UDP 4-tuple plus epoch. A fragmented message's body is kept as that one
250250
fragment's raw octets -- see
251251
:attr:`DTLSHandshake.is_fragmented <pcapkit.protocols.data.application.dtls.DTLSHandshake.is_fragmented>`
252252
-- rather than decoded from a slice of the message. An unfragmented message is
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
=================================
2+
DTLS Handshake-Message Reassembly
3+
=================================
4+
5+
.. module:: pcapkit.foundation.reassembly.dtls
6+
7+
:mod:`pcapkit.foundation.reassembly.dtls` contains
8+
:class:`~pcapkit.foundation.reassembly.dtls.DTLS` only, which reconstructs a
9+
DTLS handshake message split across several records back to origin
10+
(:rfc:`6347#section-4.2.3`).
11+
12+
.. autoclass:: pcapkit.foundation.reassembly.dtls.DTLS
13+
:no-members:
14+
:show-inheritance:
15+
16+
.. autoproperty:: last
17+
18+
.. automethod:: reassembly
19+
.. automethod:: submit
20+
.. automethod:: _datagram_from_fragment
21+
.. automethod:: _received_runs
22+
.. automethod:: _update_hole_descriptors
23+
24+
.. autoattribute:: __protocol_name__
25+
.. autoattribute:: __timeout__
26+
27+
Algorithm
28+
=========
29+
30+
.. seealso::
31+
32+
Holes are inclusive byte offsets into the message. They never wrap, and
33+
the upper bound is known from the start: every fragment carries the
34+
message's total ``length`` (:rfc:`6347#section-4.2.3`).
35+
36+
A DTLS handshake record may carry more than one message fragment --
37+
:rfc:`6347#section-4.2.3` allows several handshake messages (or fragments of
38+
them) to be coalesced into one record, and :meth:`SSL._read_tls_handshake_messages
39+
<pcapkit.protocols.application.ssl.SSL._read_tls_handshake_messages>`
40+
reads however many fit rather than assuming exactly one. A fragment names the
41+
message it belongs to (``message_seq``), the message's total length
42+
(``length``), and its own byte range within that message (``fragment_offset``,
43+
``fragment_length``). A message is reassembled by punching the arriving
44+
fragment's range out of the message's hole list; it is whole once no hole
45+
remains.
46+
47+
Terminology
48+
===========
49+
50+
.. glossary::
51+
52+
reasm.dtls.packet
53+
Data structure for **DTLS handshake-fragment reassembly**
54+
(:meth:`DTLS.reassembly <pcapkit.foundation.reassembly.dtls.DTLS.reassembly>`)
55+
is :class:`~pcapkit.foundation.reassembly.data.dtls.Packet` -- one
56+
fragment's own ``bufid``, ``message_seq``, ``fragment_offset``,
57+
``fragment_length`` and raw ``payload``, built by
58+
:meth:`DTLS.read <pcapkit.protocols.application.dtls.DTLS.read>` from
59+
a single :class:`~pcapkit.protocols.data.application.dtls.DTLSHandshake`.
60+
61+
reasm.dtls.datagram
62+
Data structure for **reassembled DTLS handshake message** (element of
63+
:attr:`DTLS.datagram <pcapkit.foundation.reassembly.reassembly.Reassembly.datagram>`)
64+
is :class:`~pcapkit.foundation.reassembly.data.dtls.Datagram` --
65+
``completed``, the original packet identifier, the packet numbers
66+
that built it, and ``body``, the reassembled payload (a contiguous
67+
:obj:`bytes`, or -- under ``strict=True`` and an incomplete message --
68+
a :obj:`tuple` of the octet runs actually received).
69+
70+
reasm.dtls.buffer
71+
Data structure for internal buffering
72+
(:attr:`DTLS._buffer <pcapkit.foundation.reassembly.reassembly.Reassembly._buffer>`)
73+
is :class:`~pcapkit.foundation.reassembly.data.dtls.Buffer` -- every
74+
message under reconstruction for one
75+
:data:`~pcapkit.foundation.reassembly.data.dtls.BufferID` (one UDP
76+
association at one DTLS epoch), each keyed by its
77+
:attr:`~pcapkit.foundation.reassembly.data.dtls.Packet.message_seq` and
78+
held as a :class:`~pcapkit.foundation.reassembly.data.dtls.Fragment` --
79+
its hole descriptor list
80+
(:class:`~pcapkit.foundation.reassembly.data.dtls.HoleDescriptor`) and
81+
its zero-filled payload buffer.
82+
83+
Data Models
84+
===========
85+
86+
.. module:: pcapkit.foundation.reassembly.data.dtls
87+
.. currentmodule:: pcapkit.foundation.reassembly.dtls
88+
89+
.. autoclass:: pcapkit.foundation.reassembly.data.dtls.Packet
90+
:members:
91+
:show-inheritance:
92+
93+
.. autodata:: pcapkit.foundation.reassembly.data.dtls.BufferID
94+
95+
.. autoclass:: pcapkit.foundation.reassembly.data.dtls.DatagramID
96+
:members:
97+
:show-inheritance:
98+
99+
.. autoclass:: pcapkit.foundation.reassembly.data.dtls.Datagram
100+
:members:
101+
:show-inheritance:
102+
103+
.. autoclass:: pcapkit.foundation.reassembly.data.dtls.HoleDescriptor
104+
:members:
105+
:show-inheritance:
106+
107+
.. autoclass:: pcapkit.foundation.reassembly.data.dtls.Fragment
108+
:members:
109+
:show-inheritance:
110+
111+
.. autoclass:: pcapkit.foundation.reassembly.data.dtls.Buffer
112+
:members:
113+
:show-inheritance:
114+
115+
Type Variables
116+
==============
117+
118+
.. data:: _AT
119+
:module: pcapkit.foundation.reassembly.data.dtls
120+
:type: ipaddress.IPv4Address | ipaddress.IPv6Address
121+
122+
IP address type.

‎docs/source/pcapkit/foundation/reassembly/index.rst‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ Packets Reassembly
55
.. module:: pcapkit.foundation.reassembly
66

77
:mod:`pcapkit.foundation.reassembly` implements datagram reassembly
8-
of IP and TCP packets, based on the algorithms described in :rfc:`791`
9-
and :rfc:`815`.
8+
of IP packets, TCP segments and DTLS handshake messages, based on the
9+
algorithms described in :rfc:`791`, :rfc:`815` and :rfc:`6347`.
1010

1111
.. seealso::
1212

@@ -19,6 +19,7 @@ and :rfc:`815`.
1919
reassembly
2020
ip/index
2121
tcp
22+
dtls
2223

2324
All reassembly classes are implemented as :class:`~pcapkit.foundation.reassembly.reassembly.ReassemblyBase`
2425
subclasses, which are responsible for processing extracted packets and
@@ -30,7 +31,7 @@ diagram of the class hierarchy of :mod:`pcapkit.foundation.reassembly`:
3031
flowchart LR
3132
A{{ReassemblyMeta}} -.->|metaclass| B(ReassemblyBase)
3233

33-
B --> IP & TCP
34+
B --> IP & TCP & DTLS
3435
IP --> IPv4 & IPv6
3536

3637
B --> C(Reassembly)
@@ -45,6 +46,7 @@ diagram of the class hierarchy of :mod:`pcapkit.foundation.reassembly`:
4546
click IPv4 "ip/ipv4.html#pcapkit.foundation.reassembly.ipv4.IPv4"
4647
click IPv6 "ip/ipv6.html#pcapkit.foundation.reassembly.ipv6.IPv6"
4748
click TCP "tcp.html#pcapkit.foundation.reassembly.tcp.TCP"
49+
click DTLS "dtls.html#pcapkit.foundation.reassembly.dtls.DTLS"
4850

4951
Auxiliary Data
5052
==============

‎pcapkit/all.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
'ReassemblyManager', # Reassembly Manager
8383
'IPv4_Reassembly', 'IPv6_Reassembly', # IP Reassembly
8484
'TCP_Reassembly', # TCP Reassembly
85+
'DTLS_Reassembly', # DTLS Reassembly
8586

8687
# pcapkit.foundation.registry
8788
'register_protocol',

‎pcapkit/foundation/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
__all__ = [
2121
'Extractor',
2222

23-
'IPv4_Reassembly', 'IPv6_Reassembly', 'TCP_Reassembly',
23+
'IPv4_Reassembly', 'IPv6_Reassembly', 'TCP_Reassembly', 'DTLS_Reassembly',
2424
'ReassemblyManager',
2525

2626
'TCP_TraceFlow',

‎pcapkit/foundation/reassembly/__init__.py‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,25 @@
66
.. module:: pcapkit.foundation.reassembly
77
88
:mod:`pcapkit.foundation.reassembly`, based on algorithms described
9-
in :rfc:`791` and :rfc:`815`, implements datagram reassembly
10-
of IP and TCP packets.
9+
in :rfc:`791`, :rfc:`815` and :rfc:`6347`, implements datagram
10+
reassembly of IP packets, TCP segments and DTLS handshake messages.
1111
1212
"""
13-
# Base Class for Reassembly
14-
from pcapkit.foundation.reassembly.reassembly import Reassembly
13+
# Reassembly for DTLS handshake messages
14+
from pcapkit.foundation.reassembly.dtls import DTLS as DTLS_Reassembly
1515
from pcapkit.foundation.reassembly.ip import IP as IP_Reassembly
16-
1716
# Reassembly for IP
1817
from pcapkit.foundation.reassembly.ipv4 import IPv4 as IPv4_Reassembly
1918
from pcapkit.foundation.reassembly.ipv6 import IPv6 as IPv6_Reassembly
20-
19+
# Base Class for Reassembly
20+
from pcapkit.foundation.reassembly.reassembly import Reassembly
2121
# Reassembly for TCP
2222
from pcapkit.foundation.reassembly.tcp import TCP as TCP_Reassembly
2323

2424
__all__ = [
2525
'IPv4_Reassembly', 'IPv6_Reassembly', # IP Reassembly
2626
'TCP_Reassembly', # TCP Reassembly
27+
'DTLS_Reassembly', # DTLS Reassembly
2728
'ReassemblyManager', # Reassembly Manager, defined below
2829
]
2930

‎pcapkit/foundation/reassembly/data/__init__.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@
2020
from pcapkit.foundation.reassembly.data.tcp import HoleDescriptor as TCP_HoleDescriptor
2121
from pcapkit.foundation.reassembly.data.tcp import Packet as TCP_Packet
2222

23+
# DTLS handshake-message reassembly
24+
from pcapkit.foundation.reassembly.data.dtls import Buffer as DTLS_Buffer
25+
from pcapkit.foundation.reassembly.data.dtls import BufferID as DTLS_BufferID
26+
from pcapkit.foundation.reassembly.data.dtls import Datagram as DTLS_Datagram
27+
from pcapkit.foundation.reassembly.data.dtls import DatagramID as DTLS_DatagramID
28+
from pcapkit.foundation.reassembly.data.dtls import Fragment as DTLS_Fragment
29+
from pcapkit.foundation.reassembly.data.dtls import HoleDescriptor as DTLS_HoleDescriptor
30+
from pcapkit.foundation.reassembly.data.dtls import Packet as DTLS_Packet
31+
2332
__all__ = [
2433
'ReassemblyData', 'Completion', 'Deferred',
2534

@@ -28,4 +37,7 @@
2837

2938
'TCP_Packet', 'TCP_DatagramID', 'TCP_Datagram', 'TCP_Buffer',
3039
'TCP_Fragment', 'TCP_HoleDescriptor', 'TCP_BufferID',
40+
41+
'DTLS_Packet', 'DTLS_DatagramID', 'DTLS_Datagram', 'DTLS_Buffer',
42+
'DTLS_Fragment', 'DTLS_HoleDescriptor', 'DTLS_BufferID',
3143
]

0 commit comments

Comments
 (0)