IPv6 Datagram Reassembly¶
pcapkit.foundation.reassembly.ipv6 contains
IPv6
only, which reconstructs fragmented IPv6 packets back to
origin. Please refer to Base Class for more information.
- class pcapkit.foundation.reassembly.ipv6.IPv6(*, strict=True, store=True, timeout=None)[source]¶
Bases:
IPReassembly for IPv6 payload.
- Parameters:
strict (
bool) – if return all datagrams (including those not implemented) when submitstore (
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 import IPv6 # Initialise instance: >>> ipv6_reassembly = IPv6() # Call reassembly: >>> ipv6_reassembly(packet_dict) # Fetch result: >>> result = ipv6_reassembly.datagram
- __protocol_type__: Type[ProtocolBase] = <class 'pcapkit.protocols.internet.ipv6.IPv6'>¶
Protocol of current reassembly object.
Terminology¶
- reasm.ipv6.packet¶
Data structure for IPv6 datagram reassembly (
IPv6.reassembly) is as following, withipv6_infothe IPv6infoandipv6_frag_infothe Fragment header’s:hdr_len = ipv6_info.hdr_len - ipv6_frag.length payload = bytearray(ipv6_info.fragment.payload) packet_dict = dict( bufid = ( ipv6_info.src, # source IP address ipv6_info.dst, # destination IP address ipv6_frag_info.id, # identification ipv6_frag_info.next, # next header field in IPv6 Fragment Header ), num = frame.info.number, # original packet range number fo = ipv6_frag_info.offset, # fragment offset, in octets ihl = hdr_len, # header length, only headers before IPv6-Frag mf = ipv6_frag_info.mf, # more fragment flag tl = hdr_len + len(payload), # total length, header includes header = ipv6_info.fragment .header[:hdr_len], # raw bytes type header before IPv6-Frag payload = payload, # raw bytearray type payload after IPv6-Frag timestamp = float( frame.info.time_epoch), # capture timestamp )
Note
ihl,headerandtlall stop short of the 8-octet Fragment header, because RFC 8200 Section 4.5 says it is not present in the reassembled packet.IPv6.hdr_lendoes count it – it is a header length, and the Fragment header is one of the extension headers it has walked – so the adapters subtract it back off. All four adapters (pcap,pcapng,dpktandscapy) agree on the three fields; they used to report three different values fortlalone, which is what #415 was about.Note
The reassembly key is the Fragment header’s Identification (RFC 8200 Section 4.5), not the IPv6 header’s Flow Label. The label is optional and routinely zero, so keying on it collapses every datagram between one address pair into a single buffer and interleaves their fragments.
- reasm.ipv6.datagram¶
Data structure for reassembled IPv6 datagram (element from
IPv6.datagramtuple) is as following:(tuple) datagram |--> (Info) data | |--> 'completed' : (Completion) COMPLETE --> reassembled in whole | |--> 'id' : (Info) original packet identifier | | |--> 'src' --> (IPv6Address) ipv6.src | | |--> 'dst' --> (IPv6Address) ipv6.dst | | |--> 'id' --> (int) ipv6_frag.id | | |--> 'proto' --> (TransType) ipv6_frag.next | |--> 'index' : (tuple) packet numbers | | |--> (int) original packet range number | |--> 'header' : (bytes) header before IPv6-Frag | |--> 'payload' : (bytes) reassembled IPv6 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' --> (IPv6Address) ipv6.src | | |--> 'dst' --> (IPv6Address) ipv6.dst | | |--> 'id' --> (int) ipv6_frag.id | | |--> 'proto' --> (TransType) ipv6_frag.next | |--> 'index' : (tuple) packet numbers | | |--> (int) original packet range number | |--> 'header' : (bytes) header before IPv6-Frag | |--> 'payload' : (tuple) partially reassembled IPv6 payload | | |--> (bytes) IPv6 payload fragment | | |--> ... | |--> 'packet' : (None) | |--> 'conflict' : (tuple) octet ranges on which two fragments disagreed | | |--> (tuple) (first, last), absolute and inclusive | | |--> ... |--> (Info) data ...
Note
headeris the fragment’s unfragmentable part with one field rewritten: the Next Header field of its last header carries the Fragment header’s Next Header value, as RFC 8200 Section 4.5 requires of a reassembled packet. Without that rewrite the datagram would still advertise a Fragment header (44) on a datagram that is no longer a fragment. The Payload Length field is not adjusted, so it still describes the first fragment rather than the reassembled datagram; uselen(payload)instead.Note
packetis analysed on the first read, not when the datagram is submitted. A datagram is submitted for every frame – an unfragmented one included, since nothing upstream filters it out – and the analysis is a second full parse of the payload, so running it eagerly charged 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, fixed for TCP by #443) – so a contested range never leaves a hole on its own, andconflictis what lets a caller tell a clean datagram from a contested one. See #477.- reasm.ipv6.buffer¶
Data structure for internal buffering when performing reassembly algorithms (
IPv6._buffer) is as following:(dict) buffer --> memory buffer for reassembly |--> (tuple) BUFID : (dict) | |--> ipv6.src | | |--> ipv6.dst | | |--> ipv6_frag.id | | |--> ipv6_frag.next | | |--> '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' | |--> 'conflict' : (list) octet ranges on which an arriving | | fragment disagreed with bytes already in | | 'datagram' | | |--> (tuple) (first, last), absolute and | | inclusive | | |--> ... |--> (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 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.