class EnhancedPacket(BasePacketBlock): magic_number = 0x00000006 schema = [ ('interface_id', IntField(32, False)), ('timestamp_high', IntField(32, False)), ('timestamp_low', IntField(32, False)), ('packet_payload_info', PacketDataField()), ('options', OptionsField([ (2, 'epb_flags'), # todo: is this endianness dependent? (3, 'epb_hash'), # todo: process the hash value (4, 'epb_dropcount', 'u64'), ])) ] @property def captured_len(self): return self.packet_payload_info[0] @property def packet_len(self): return self.packet_payload_info[1] @property def packet_data(self): return self.packet_payload_info[2]
class InterfaceStatistics(SectionMemberBlock, BlockWithTimestampMixin, BlockWithInterfaceMixin): """ "The Interface Statistics Block (ISB) contains the capture statistics for a given interface [...]. The statistics are referred to the interface defined in the current Section identified by the Interface ID field." - pcapng spec, section 4.6. Other quoted citations are from this section unless otherwise noted. """ magic_number = 0x00000005 __slots__ = [] schema = [ ('interface_id', IntField(32, False), 0), ('timestamp_high', IntField(32, False), 0), ('timestamp_low', IntField(32, False), 0), ( 'options', OptionsField([ Option(2, 'isb_starttime', 'u64'), # todo: consider resolution Option(3, 'isb_endtime', 'u64'), Option(4, 'isb_ifrecv', 'u64'), Option(5, 'isb_ifdrop', 'u64'), Option(6, 'isb_filteraccept', 'u64'), Option(7, 'isb_osdrop', 'u64'), Option(8, 'isb_usrdeliv', 'u64'), ]), None), ]
class Packet(BasePacketBlock): magic_number = 0x00000002 schema = [ ('interface_id', IntField(16, False)), ('drops_count', IntField(16, False)), ('timestamp_high', IntField(32, False)), ('timestamp_low', IntField(32, False)), ('packet_payload_info', PacketDataField()), ('options', OptionsField([ (2, 'epb_flags', 'u32'), # A flag! (3, 'epb_hash'), # Variable size! ])) ] @property def captured_len(self): return self.packet_payload_info[0] @property def packet_len(self): return self.packet_payload_info[1] @property def packet_data(self): return self.packet_payload_info[2]
class EnhancedPacket(BasePacketBlock, BlockWithTimestampMixin): """ "An Enhanced Packet Block (EPB) is the standard container for storing the packets coming from the network." - pcapng spec, section 4.3. Other quoted citations are from this section unless otherwise noted. """ magic_number = 0x00000006 __slots__ = [] schema = [ ('interface_id', IntField(32, False), 0), ('timestamp_high', IntField(32, False), 0), ('timestamp_low', IntField(32, False), 0), ('captured_len', IntField(32, False), 0), ('packet_len', IntField(32, False), 0), ('packet_data', PacketBytes('captured_len'), b''), ( 'options', OptionsField([ Option(2, 'epb_flags', 'epb_flags'), Option(3, 'epb_hash', 'type+bytes', multiple=True), # todo: process the hash value Option(4, 'epb_dropcount', 'u64'), ]), None) ]
class NameResolution(SectionMemberBlock): """ "The Name Resolution Block (NRB) is used to support the correlation of numeric addresses (present in the captured packets) and their corresponding canonical names [...]. Having the literal names saved in the file prevents the need for performing name resolution at a later time, when the association between names and addresses may be different from the one in use at capture time." - pcapng spec, section 4.5. Other quoted citations are from this section unless otherwise noted. """ magic_number = 0x00000004 __slots__ = [] schema = [ ("records", ListField(NameResolutionRecordField()), []), ( "options", OptionsField( [ Option(2, "ns_dnsname", "string"), Option(3, "ns_dnsIP4addr", "ipv4"), Option(4, "ns_dnsIP6addr", "ipv6"), ] ), None, ), ]
class EnhancedPacket(BasePacketBlock, BlockWithTimestampMixin): """ "An Enhanced Packet Block (EPB) is the standard container for storing the packets coming from the network." - pcapng spec, section 4.3. Other quoted citations are from this section unless otherwise noted. """ magic_number = 0x00000006 __slots__ = [] schema = [ ("interface_id", IntField(32, False), 0), ("timestamp_high", IntField(32, False), 0), ("timestamp_low", IntField(32, False), 0), ("captured_len", IntField(32, False), 0), ("packet_len", IntField(32, False), 0), ("packet_data", PacketBytes("captured_len"), b""), ( "options", OptionsField( [ Option(2, "epb_flags", "epb_flags"), Option(3, "epb_hash", "type+bytes", multiple=True), # todo: process Option(4, "epb_dropcount", "u64"), ] ), None, ), ]
class ObsoletePacket(BasePacketBlock, BlockWithTimestampMixin): """ "The Packet Block is obsolete, and MUST NOT be used in new files. [...] A Packet Block was a container for storing packets coming from the network." - pcapng spec, Appendix A. Other quoted citations are from this appendix unless otherwise noted. """ magic_number = 0x00000002 __slots__ = [] schema = [ ("interface_id", IntField(16, False), 0), ("drops_count", IntField(16, False), 0), ("timestamp_high", IntField(32, False), 0), ("timestamp_low", IntField(32, False), 0), ("captured_len", IntField(32, False), 0), ("packet_len", IntField(32, False), 0), ("packet_data", PacketBytes("captured_len"), None), ( # The options have the same definitions as their epb_ equivalents "options", OptionsField( [ Option(2, "pack_flags", "epb_flags"), Option(3, "pack_hash", "type+bytes", multiple=True), ] ), None, ), ] def enhanced(self): """Return an EnhancedPacket with this block's attributes.""" opts_dict = dict(self.options) opts_dict["epb_dropcount"] = self.drops_count for a in ("flags", "hash"): try: opts_dict["epb_" + a] = opts_dict.pop("pack_" + a) except KeyError: pass return self.section.new_member( EnhancedPacket, interface_id=self.interface_id, timestamp_high=self.timestamp_high, timestamp_low=self.timestamp_low, packet_len=self.packet_len, packet_data=self.packet_data, options=opts_dict, ) # Do this check in _write() instead of _encode() to ensure the block gets written # with the correct magic number. def _write(self, outstream): strictness.problem("Packet Block is obsolete and must not be used") if strictness.should_fix(): self.enhanced()._write(outstream) else: super(ObsoletePacket, self)._write(outstream)
class SectionHeader(Block): magic_number = 0x0a0d0d0a schema = [('version_major', IntField(16, False)), ('version_minor', IntField(16, False)), ('section_length', IntField(64, True)), ('options', OptionsField([ (2, 'shb_hardware', 'string'), (3, 'shb_os', 'string'), (4, 'shb_userappl', 'string'), ]))] def __init__(self, raw=None, endianness="<"): self._raw = raw self._decoded = None self.endianness = endianness self._interfaces_id = itertools.count(0) self.interfaces = {} self.interface_stats = {} def _decode(self): return struct_decode(self.schema, io.BytesIO(self._raw), endianness=self.endianness) def _encode(self, outstream): write_int(0x1A2B3C4D, outstream, 32) struct_encode(self.schema, self, outstream, endianness=self.endianness) def register_interface(self, interface): """Helper method to register an interface within this section""" assert isinstance(interface, InterfaceDescription) interface_id = next(self._interfaces_id) interface.interface_id = interface_id self.interfaces[interface_id] = interface def add_interface_stats(self, interface_stats): """Helper method to register interface stats within this section""" assert isinstance(interface_stats, InterfaceStatistics) self.interface_stats[interface_stats.interface_id] = interface_stats @property def version(self): return (self.version_major, self.version_minor) @property def length(self): return self.section_length def __repr__(self): return ('<{name} version={version} endianness={endianness} ' 'length={length} options={options}>').format( name=self.__class__.__name__, version='.'.join(str(x) for x in self.version), endianness=repr(self.endianness), length=self.length, options=repr(self.options))
class InterfaceDescription(SectionMemberBlock): magic_number = 0x00000001 schema = [ ('link_type', IntField(16, False)), # todo: enc/decode ('reserved', RawBytes(2)), ('snaplen', IntField(32, False)), ( 'options', OptionsField([ (2, 'if_name', 'string'), (3, 'if_description', 'string'), (4, 'if_IPv4addr', 'ipv4+mask'), (5, 'if_IPv6addr', 'ipv6+prefix'), (6, 'if_MACaddr', 'macaddr'), (7, 'if_EUIaddr', 'euiaddr'), (8, 'if_speed', 'u64'), (9, 'if_tsresol'), # Just keep the raw data (10, 'if_tzone', 'u32'), (11, 'if_filter', 'string'), (12, 'if_os', 'string'), (13, 'if_fcslen', 'u8'), (14, 'if_tsoffset', 'i64'), ])) ] @property # todo: cache this property def timestamp_resolution(self): # ------------------------------------------------------------ # Resolution of timestamps. If the Most Significant Bit is # equal to zero, the remaining bits indicates the resolution # of the timestamp as as a negative power of 10 (e.g. 6 means # microsecond resolution, timestamps are the number of # microseconds since 1/1/1970). If the Most Significant Bit is # equal to one, the remaining bits indicates the resolution as # as negative power of 2 (e.g. 10 means 1/1024 of second). If # this option is not present, a resolution of 10^-6 is assumed # (i.e. timestamps have the same resolution of the standard # 'libpcap' timestamps). # ------------------------------------------------------------ if 'if_tsresol' in self.options: return unpack_timestamp_resolution(self.options['if_tsresol']) return 1e-6 @property def statistics(self): # todo: ensure we always have an interface id -> how?? return self.section.interface_stats.get(self.interface_id) @property def link_type_description(self): try: return link_types.LINKTYPE_DESCRIPTIONS[self.link_type] except KeyError: return 'Unknown link type: 0x{0:04x}'.format(self.link_type)
class NameResolution(SectionMemberBlock): magic_number = 0x00000004 schema = [ ('records', ListField(NameResolutionRecordField())), ('options', OptionsField([ (2, 'ns_dnsname', 'string'), (3, 'ns_dnsIP4addr', 'ipv4'), (4, 'ns_dnsIP6addr', 'ipv6'), ])), ]
class ObsoletePacket(BasePacketBlock, BlockWithTimestampMixin): """ "The Packet Block is obsolete, and MUST NOT be used in new files. [...] A Packet Block was a container for storing packets coming from the network." - pcapng spec, Appendix A. Other quoted citations are from this appendix unless otherwise noted. """ magic_number = 0x00000002 __slots__ = [] schema = [ ('interface_id', IntField(16, False), 0), ('drops_count', IntField(16, False), 0), ('timestamp_high', IntField(32, False), 0), ('timestamp_low', IntField(32, False), 0), ('captured_len', IntField(32, False), 0), ('packet_len', IntField(32, False), 0), ('packet_data', PacketBytes('captured_len'), b''), ( 'options', OptionsField([ Option(2, 'pack_flags', 'epb_flags'), # Same definition as epb_flags Option(3, 'pack_hash', 'type+bytes', multiple=True), # Same definition as epb_hash ]), None) ] def enhanced(self): """Return an EnhancedPacket with this block's attributes.""" opts_dict = dict(self.options) opts_dict['epb_dropcount'] = self.drops_count for a in ('flags', 'hash'): try: opts_dict['epb_' + a] = opts_dict.pop('pack_' + a) except KeyError: pass return self.section.new_member(EnhancedPacket, interface_id=self.interface_id, timestamp_high=self.timestamp_high, timestamp_low=self.timestamp_low, packet_len=self.packet_len, packet_data=self.packet_data, options=opts_dict) def write(self, outstream): strictness.problem("Packet Block is obsolete and must not be used") if strictness.should_fix(): self.enhanced().write(outstream) else: super(ObsoletePacket, self).write(outstream)
class InterfaceStatistics(SectionMemberBlock, BlockWithTimestampMixin, BlockWithInterfaceMixin): magic_number = 0x00000005 schema = [ ('interface_id', IntField(32, False)), ('timestamp_high', IntField(32, False)), ('timestamp_low', IntField(32, False)), ('options', OptionsField([ (2, 'isb_starttime', 'u64'), # todo: consider resolution (3, 'isb_endtime', 'u64'), (4, 'isb_ifrecv', 'u64'), (5, 'isb_ifdrop', 'u64'), (6, 'isb_filteraccept', 'u64'), (7, 'isb_osdrop', 'u64'), (8, 'isb_usrdeliv', 'u64'), ])), ]
def test_unpack_dummy_packet(): schema = [ ('a_string', RawBytes(8), ''), ('a_number', IntField(32, False), 0), ('options', OptionsField([]), None), ('pb_captured_len', IntField(32, False), 0), ('pb_orig_len', IntField(32, False), 0), ('packet_data', PacketBytes('pb_captured_len'), b''), ('spb_orig_len', IntField(32, False), 0), ('simple_packet_data', PacketBytes('spb_orig_len'), b''), ('name_res', ListField(NameResolutionRecordField()), []), ('another_number', IntField(32, False), 0), ] # Note: NULLs are for padding! data = io.BytesIO( b'\x01\x23\x45\x67\x89\xab\xcd\xef' b'\x00\x00\x01\x00' # Options b'\x00\x01\x00\x0cHello world!' b'\x00\x01\x00\x0fSpam eggs bacon\x00' b'\x00\x02\x00\x0fSome other text\x00' b'\x00\x00\x00\x00' # Enhanced Packet data b'\x00\x00\x00\x12' b'\x00\x01\x00\x00' b'These are 18 bytes\x00\x00' # Simple packet data b'\x00\x00\x00\x0d' b'Simple packet\x00\x00\x00' # List of name resolution items b'\x00\x01' # IPv4 b'\x00\x13' # Length: 19bytes b'\x0a\x22\x33\x44www.example.com\x00' # 19 bytes (10.34.51.68) b'\x00\x01' # IPv4 b'\x00\x13' # Length: 19bytes b'\xc0\xa8\x14\x01www.example.org\x00' # 19 bytes (192.168.20.1) b'\x00\x02' # IPv6 b'\x00\x1e' # 30 bytes b'\x00\x11\x22\x33\x44\x55\x66\x77' b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff' b'v6.example.net\x00\x00' b'\x00\x00\x00\x00' # End marker # Another number, to check end b'\xaa\xbb\xcc\xdd' ) unpacked = struct_decode(schema, data, endianness='>') assert unpacked['a_string'] == b'\x01\x23\x45\x67\x89\xab\xcd\xef' assert unpacked['a_number'] == 0x100 assert isinstance(unpacked['options'], Options) assert len(unpacked['options']) == 2 assert unpacked['options']['opt_comment'] == 'Hello world!' assert unpacked['options'][2] == b'Some other text' assert unpacked['pb_captured_len'] == 0x12 assert unpacked['pb_orig_len'] == 0x10000 assert unpacked['packet_data'] == b'These are 18 bytes' assert unpacked['spb_orig_len'] == 13 assert unpacked['simple_packet_data'] == b'Simple packet' assert unpacked['name_res'] == [ {'address': '10.34.51.68', 'names': ['www.example.com'], 'type': 1}, {'address': '192.168.20.1', 'names': ['www.example.org'], 'type': 1}, {'type': 2, 'address': '11:2233:4455:6677:8899:aabb:ccdd:eeff', 'names': ['v6.example.net']}]
class InterfaceDescription(SectionMemberBlock): """ "An Interface Description Block (IDB) is the container for information describing an interface on which packet data is captured." - pcapng spec, section 4.2. Other quoted citations are from this section unless otherwise noted. """ magic_number = 0x00000001 __slots__ = [] schema = [ ('link_type', IntField(16, False), 0), # todo: enc/decode ('reserved', IntField(16, False), 0), ('snaplen', IntField(32, False), 0), ( 'options', OptionsField([ Option(2, 'if_name', 'string'), Option(3, 'if_description', 'string'), Option(4, 'if_IPv4addr', 'ipv4+mask', multiple=True), Option(5, 'if_IPv6addr', 'ipv6+prefix', multiple=True), Option(6, 'if_MACaddr', 'macaddr'), Option(7, 'if_EUIaddr', 'euiaddr'), Option(8, 'if_speed', 'u64'), Option(9, 'if_tsresol', 'bytes'), # Just keep the raw data Option(10, 'if_tzone', 'u32'), Option(11, 'if_filter', 'type+bytes'), Option(12, 'if_os', 'string'), Option(13, 'if_fcslen', 'u8'), Option(14, 'if_tsoffset', 'i64'), Option(15, 'if_hardware', 'string'), ]), None) ] @property # todo: cache this property def timestamp_resolution(self): # ------------------------------------------------------------ # Resolution of timestamps. If the Most Significant Bit is # equal to zero, the remaining bits indicates the resolution # of the timestamp as as a negative power of 10 (e.g. 6 means # microsecond resolution, timestamps are the number of # microseconds since 1/1/1970). If the Most Significant Bit is # equal to one, the remaining bits indicates the resolution as # as negative power of 2 (e.g. 10 means 1/1024 of second). If # this option is not present, a resolution of 10^-6 is assumed # (i.e. timestamps have the same resolution of the standard # 'libpcap' timestamps). # ------------------------------------------------------------ if 'if_tsresol' in self.options: return unpack_timestamp_resolution(self.options['if_tsresol']) return 1e-6 @property def statistics(self): # todo: ensure we always have an interface id -> how?? return self.section.interface_stats.get(self.interface_id) @property def link_type_description(self): try: return link_types.LINKTYPE_DESCRIPTIONS[self.link_type] except KeyError: return 'Unknown link type: 0x{0:04x}'.format(self.link_type)
class SectionHeader(Block): """ "The Section Header Block (SHB) is mandatory. It identifies the beginning of a section of the capture file. The Section Header Block does not contain data but it rather identifies a list of blocks (interfaces, packets) that are logically correlated." - pcapng spec, section 4.1. Other quoted citations are from this section unless otherwise noted. """ magic_number = 0x0a0d0d0a __slots__ = [ 'endianness', '_interfaces_id', 'interfaces', 'interface_stats', ] schema = [('version_major', IntField(16, False), 1), ('version_minor', IntField(16, False), 0), ('section_length', IntField(64, True), -1), ('options', OptionsField([ Option(2, 'shb_hardware', 'string'), Option(3, 'shb_os', 'string'), Option(4, 'shb_userappl', 'string'), ]), None)] def __init__(self, endianness="<", **kwargs): super(SectionHeader, self).__init__(**kwargs) self.endianness = endianness self._interfaces_id = itertools.count(0) self.interfaces = {} self.interface_stats = {} def _encode(self, outstream): write_int(0x1A2B3C4D, outstream, 32, endianness=self.endianness) super(SectionHeader, self)._encode(outstream) def new_member(self, cls, **kwargs): """Helper method to create a block that's a member of this section""" assert issubclass(cls, SectionMemberBlock) blk = cls(section=self, **kwargs) # Some blocks (eg. SPB) don't have options if any([x[0] == 'options' for x in blk.schema]): blk.options.endianness = self.endianness if isinstance(blk, InterfaceDescription): self.register_interface(blk) elif isinstance(blk, InterfaceStatistics): self.add_interface_stats(blk) return blk def register_interface(self, interface): """Helper method to register an interface within this section""" assert isinstance(interface, InterfaceDescription) interface_id = next(self._interfaces_id) interface.interface_id = interface_id self.interfaces[interface_id] = interface def add_interface_stats(self, interface_stats): """Helper method to register interface stats within this section""" assert isinstance(interface_stats, InterfaceStatistics) self.interface_stats[interface_stats.interface_id] = interface_stats @property def version(self): return (self.version_major, self.version_minor) @property def length(self): return self.section_length # Block.decode() assumes all blocks have sections -- technically true... @property def section(self): return self def __repr__(self): return ('<{name} version={version} endianness={endianness} ' 'length={length} options={options}>').format( name=self.__class__.__name__, version='.'.join(str(x) for x in self.version), endianness=repr(self.endianness), length=self.length, options=repr(self.options))
def test_unpack_dummy_packet(): schema = [ ('a_string', RawBytes(8)), ('a_number', IntField(32, False)), ('options', OptionsField([])), ('packet_data', PacketDataField()), ('simple_packet_data', SimplePacketDataField()), ('name_res', ListField(NameResolutionRecordField())), ('another_number', IntField(32, False)), ] # Note: NULLs are for padding! data = io.BytesIO( '\x01\x23\x45\x67\x89\xab\xcd\xef' '\x00\x00\x01\x00' # Options '\x00\x01\x00\x0cHello world!' '\x00\x01\x00\x0fSpam eggs bacon\x00' '\x00\x02\x00\x0fSome other text\x00' '\x00\x00\x00\x00' # Enhanced Packet data '\x00\x00\x00\x12' '\x00\x01\x00\x00' 'These are 18 bytes\x00\x00' # Simple packet data '\x00\x00\x00\x0d' 'Simple packet\x00\x00\x00' # List of name resolution items '\x00\x01' # IPv4 '\x00\x13' # Length: 19bytes '\x0a\x22\x33\x44www.example.com\x00' # 19 bytes (10.34.51.68) '\x00\x01' # IPv4 '\x00\x13' # Length: 19bytes '\xc0\xa8\x14\x01www.example.org\x00' # 19 bytes (192.168.20.1) '\x00\x02' # IPv6 '\x00\x1e' # 30 bytes '\x00\x11\x22\x33\x44\x55\x66\x77' '\x88\x99\xaa\xbb\xcc\xdd\xee\xff' 'v6.example.net\x00\x00' '\x00\x00\x00\x00' # End marker # Another number, to check end '\xaa\xbb\xcc\xdd' ) unpacked = struct_decode(schema, data, endianness='>') assert unpacked['a_string'] == '\x01\x23\x45\x67\x89\xab\xcd\xef' assert unpacked['a_number'] == 0x100 assert isinstance(unpacked['options'], Options) assert len(unpacked['options']) == 2 assert unpacked['options']['opt_comment'] == 'Hello world!' assert unpacked['options'][2] == 'Some other text' assert unpacked['packet_data'] == (0x12, 0x10000, 'These are 18 bytes') assert unpacked['simple_packet_data'] == (13, 'Simple packet') assert unpacked['name_res'] == [ {'address': '\x0a\x22\x33\x44', 'name': 'www.example.com', 'type': 1}, {'address': '\xc0\xa8\x14\x01', 'name': 'www.example.org', 'type': 1}, {'type': 2, 'address': '\x00\x11\x22\x33\x44\x55\x66\x77' '\x88\x99\xaa\xbb\xcc\xdd\xee\xff', 'name': 'v6.example.net'}]
def test_unpack_dummy_packet(): schema = [ ("a_string", RawBytes(8), ""), ("a_number", IntField(32, False), 0), ("options", OptionsField([]), None), ("pb_captured_len", IntField(32, False), 0), ("pb_orig_len", IntField(32, False), 0), ("packet_data", PacketBytes("pb_captured_len"), b""), ("spb_orig_len", IntField(32, False), 0), ("simple_packet_data", PacketBytes("spb_orig_len"), b""), ("name_res", ListField(NameResolutionRecordField()), []), ("another_number", IntField(32, False), 0), ] # Note: NULLs are for padding! data = io.BytesIO( b"\x01\x23\x45\x67\x89\xab\xcd\xef" b"\x00\x00\x01\x00" # Options b"\x00\x01\x00\x0cHello world!" b"\x00\x01\x00\x0fSpam eggs bacon\x00" b"\x00\x02\x00\x0fSome other text\x00" b"\x00\x00\x00\x00" # Enhanced Packet data b"\x00\x00\x00\x12" b"\x00\x01\x00\x00" b"These are 18 bytes\x00\x00" # Simple packet data b"\x00\x00\x00\x0d" b"Simple packet\x00\x00\x00" # List of name resolution items b"\x00\x01" # IPv4 b"\x00\x13" # Length: 19bytes b"\x0a\x22\x33\x44www.example.com\x00" # 19 bytes (10.34.51.68) b"\x00\x01" # IPv4 b"\x00\x13" # Length: 19bytes b"\xc0\xa8\x14\x01www.example.org\x00" # 19 bytes (192.168.20.1) b"\x00\x02" # IPv6 b"\x00\x1e" # 30 bytes b"\x00\x11\x22\x33\x44\x55\x66\x77" b"\x88\x99\xaa\xbb\xcc\xdd\xee\xff" b"v6.example.net\x00\x00" b"\x00\x00\x00\x00" # End marker # Another number, to check end b"\xaa\xbb\xcc\xdd" ) unpacked = struct_decode(schema, data, endianness=">") assert unpacked["a_string"] == b"\x01\x23\x45\x67\x89\xab\xcd\xef" assert unpacked["a_number"] == 0x100 assert isinstance(unpacked["options"], Options) assert len(unpacked["options"]) == 2 assert unpacked["options"]["opt_comment"] == "Hello world!" assert unpacked["options"][2] == b"Some other text" assert unpacked["pb_captured_len"] == 0x12 assert unpacked["pb_orig_len"] == 0x10000 assert unpacked["packet_data"] == b"These are 18 bytes" assert unpacked["spb_orig_len"] == 13 assert unpacked["simple_packet_data"] == b"Simple packet" assert unpacked["name_res"] == [ {"address": "10.34.51.68", "names": ["www.example.com"], "type": 1}, {"address": "192.168.20.1", "names": ["www.example.org"], "type": 1}, { "type": 2, "address": "11:2233:4455:6677:8899:aabb:ccdd:eeff", "names": ["v6.example.net"], }, ]