IPv4 Datagram Reassembly¶
pcapkit.foundation.reassembly.ipv4 contains
IPv4 only, which reconstructs
fragmented IPv4 packets back to origin; see IP Reassembly Base Class.
- class pcapkit.foundation.reassembly.ipv4.IPv4[_PT: Info, _DT: Info, _IT: tuple, _BT: Info](*, strict=True, store=True, timeout=None)[source]¶
Bases:
IPReassembly for IPv4 payload.
- Parameters:
strict (
bool) – ifTrue, report a datagram that is not completely reassembled as the tuple of its received runs; otherwise as one contiguous payload, its holes zero-filled – or, while the total length is unknown, only the prefix up to the first holestore (
bool) – if store reassembled datagram in memory, i.e.,self._dtgram(if not, datagram will be discarded after callback)timeout (
float|None) – reassembly timeout in seconds, on the capture’s own clock;Noneselects__timeout__
Example
>>> from pcapkit.foundation.reassembly.ipv4 import IPv4 # Initialise instance: >>> ipv4_reassembly = IPv4() # Call reassembly: >>> ipv4_reassembly(packet_dict) # Fetch result: >>> result = ipv4_reassembly.datagram
- __protocol_type__: Type[ProtocolBase] = <class 'pcapkit.protocols.internet.ipv4.protocol.IPv4'>¶
Protocol of current reassembly object.
- __callback_fn__: list[Callable[[List[_DT]], None]]¶
List of callback functions upon reassembled datagram.
- __timeout__: float = 60.0¶
Default reassembly timeout, in seconds, on the capture’s clock.
RFC 1122 Section 3.3.2 is the governing text and it is emphatic on both halves: “There MUST be a reassembly timeout. The reassembly timeout value SHOULD be a fixed value, not set from the remaining TTL. It is recommended that the value lie between 60 seconds and 120 seconds.” Its DISCUSSION explains why it overrode RFC 791: gateways came to treat TTL as a hop count rather than elapsed seconds, so a TTL-derived deadline discards datagrams that were merely slow.
RFC 791 Section 3.2 is therefore not followed to the letter. Its scheme is
TIMER <- MAX(TIMER,TTL)seeded from a “Timer Lower Bound”, for which “the current recommendation for the initial timer setting is 15 seconds” – an initial lower bound that a later fragment’s TTL raises, up to the 4.25-minute TTL ceiling, not a 15-second deadline. It is ruled out here: the recommendation is superseded, and TTL is not inPacketat all, so the fragment model would have to grow a field to express a scheme RFC 1122 asks implementations not to use.60 seconds is the low end of RFC 1122’s range, and it makes IPv4 agree with the 60 seconds RFC 8200 Section 4.5 mandates for IPv6 – so one timestamp-driven eviction path serves both, which is the whole reason the buffer models carry a timestamp.
- Type:
Terminology¶
- reasm.ipv4.packet¶
Data structure for IPv4 datagram reassembly (
IPv4.reassembly) is as follows, withipv4the protocol instance (frame['IPv4']) andipv4_infoitsinfo– the header fields come off the latter, the raw octets off the former:packet_dict = dict( bufid = ( ipv4_info.src, # source IP address ipv4_info.dst, # destination IP address ipv4_info.id, # identification ipv4_info.protocol, # payload protocol type ), num = frame.info.number, # original packet range number fo = ipv4_info.offset, # fragment offset, in octets ihl = ipv4_info.hdr_len, # internet header length mf = ipv4_info.flags.mf, # more fragment flag tl = ipv4_info.len, # total length, header includes header = ipv4.packet.header, # raw bytes type header payload = bytearray( ipv4.packet.payload), # raw bytearray type payload timestamp = float( frame.info.time_epoch), # capture timestamp )
- reasm.ipv4.datagram¶
Data structure for reassembled IPv4 datagram (element from
IPv4.datagramtuple) is as follows:(tuple) datagram |--> (Info) data | |--> 'completed' : (Completion) COMPLETE --> reassembled in whole | |--> 'id' : (Info) original packet identifier | | |--> 'src' --> (IPv4Address) ipv4.src | | |--> 'dst' --> (IPv4Address) ipv4.dst | | |--> 'id' --> (int) ipv4.id | | |--> 'proto' --> (TransType) ipv4.protocol | |--> 'index' : (tuple) packet numbers | | |--> (int) original packet range number | |--> 'header' : (bytes) IPv4 header | |--> 'payload' : (bytes) reassembled IPv4 payload | |--> 'packet' : (Protocol) parsed reassembled payload | |--> 'conflict' : (tuple) octet ranges on which two fragments disagreed | | |--> (tuple) (first, last), absolute and inclusive | | |--> ... |--> (Info) data | |--> 'completed' : (Completion) PARTIAL or TIMEOUT --> incomplete | |--> 'id' : (Info) original packet identifier | | |--> 'src' --> (IPv4Address) ipv4.src | | |--> 'dst' --> (IPv4Address) ipv4.dst | | |--> 'id' --> (int) ipv4.id | | |--> 'proto' --> (TransType) ipv4.protocol | |--> 'index' : (tuple) packet numbers | | |--> (int) original packet range number | |--> 'header' : (bytes) IPv4 header | |--> 'payload' : (tuple) partially reassembled IPv4 payload | | |--> (bytes) IPv4 payload fragment | | |--> ... | |--> 'packet' : (None) | |--> 'conflict' : (tuple) octet ranges on which two fragments disagreed | | |--> (tuple) (first, last), absolute and inclusive | | |--> ... |--> (Info) data ...
Note
packetis whatip_datagram()parsespayloadto, unless__protocol_type__names another protocol, and it is analysed on the first read, not when the datagram is submitted. A datagram is submitted for every frame whose DF flag is clear – an unfragmented one included – and the analysis is a second full parse of the payload, so running it eagerly would charge every caller for a result most never read. Reading the attribute, or any mapping view of it (datagram['packet'],to_dict(),items(),repr()), runs it and keeps the result; seeDeferred.Note
completedandconflictare independent signals: a datagram can beCOMPLETEand still carry a non-emptyconflict. RFC 791 resolves an overlapping fragment’s disagreement itself – “this procedure will use the more recently arrived copy in the data buffer” – the opposite resolution from TCP’s first-write-wins (RFC 9293 Section 3.10) – so a contested range never leaves a hole on its own, andconflictis what lets a caller tell a clean datagram from a contested one.- reasm.ipv4.buffer¶
Data structure for internal buffering when performing reassembly algorithms (
IPv4._buffer) is as follows:(dict) buffer --> memory buffer for reassembly |--> (tuple) BUFID : (dict) | |--> ipv4.src | | |--> ipv4.dst | | |--> ipv4.id | | |--> ipv4.protocol | | |--> 'TDL' : (int) total data length | |--> 'RCVBT' : (bytearray) fragment received bit table | | |--> (bytes) b'\\x00' -> not received | | |--> (bytes) b'\\x01' -> received | | |--> (bytes) ... | |--> 'index' : (list) list of reassembled packets | | |--> (int) packet range number | |--> 'header' : (bytes) header buffer | |--> 'datagram' : (bytearray) data buffer, holes set to b'\\x00' | |--> 'timestamp' : (float) capture timestamp of the | | first-arriving fragment | |--> 'conflict' : (list) octet ranges on which an arriving | | fragment disagreed with bytes already in | | 'datagram' | | |--> (tuple) (first, last), absolute and | | inclusive | | |--> ... | |--> 'extent' : (list) always empty: IPv4 keeps no extents |--> (tuple) BUFID ...
Note
conflictis only ever appended to, and is checked againstdatagramandRCVBTbefore an arriving fragment’s own write and bookkeeping touch them – seeIP._detect_conflicts.RCVBTrecords receipt in 8-octet blocks, which is coarser than the octet a conflict needs;TDLis what recovers the exact extent for the one block marked received that can be partially real – the final fragment’s own tail – so a conflict here is never wider than the octets that genuinely disagreed, even inside that block. Any other partial last block – a non-final, truncated or clipped fragment’s – is left unmarked (#1567), and its octets are not compared: a later fragment that overwrites them records no conflict (#1585).Note
A buffer is abandoned once the reassembly timeout elapses on the capture’s clock – 60 seconds by default, per RFC 1122 Section 3.3.2 for IPv4 and RFC 8200 Section 4.5 for IPv6, counted from the first-arriving fragment. Its datagram is reported with
completedset toCompletion.TIMEOUTrather thanPARTIAL, which is what tells “these fragments are gone” apart from “these fragments had not arrived yet”. Seeexpire().