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 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 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 SimplePacket(BasePacketBlock): """ "The Simple Packet Block (SPB) is a lightweight container for storing the packets coming from the network." - pcapng spec, section 4.4. Other quoted citations are from this section unless otherwise noted. """ magic_number = 0x00000003 __slots__ = [] schema = [ ('packet_len', IntField(32, False), 0), # NOT the captured length ('packet_data', PacketBytes('captured_len'), b''), # we don't actually use this ] readonly_fields = set(('captured_len', 'interface_id')) def _decode(self): """Decodes the raw data of this block into its fields""" stream = six.BytesIO(self._raw) self._decoded = struct_decode(self.schema[:1], stream, self.section.endianness) # Now we can get our ``captured_len`` property which is required # to really know how much data we can load self._decoded['packet_data'] = RawBytes(self.captured_len).load( stream, self.section.endianness) del self._raw @property def interface_id(self): """ "The Simple Packet Block does not contain the Interface ID field. Therefore, it MUST be assumed that all the Simple Packet Blocks have been captured on the interface previously specified in the first Interface Description Block." """ return 0 @property def captured_len(self): """ "...the SnapLen value MUST be used to determine the size of the Packet Data field length." """ snap_len = self.interface.snaplen if snap_len == 0: # unlimited return self.packet_len else: return min(snap_len, self.packet_len) def _encode(self, outstream): fld_size = IntField(32, False) fld_data = RawBytes(0) if len(self.section.interfaces) > 1: # Spec is a bit ambiguous here. Section 4.4 says "it MUST # be assumed that all the Simple Packet Blocks have been captured # on the interface previously specified in the first Interface # Description Block." but later adds "A Simple Packet Block cannot # be present in a Section that has more than one interface because # of the impossibility to refer to the correct one (it does not # contain any Interface ID field)." Why would it say "the first" # IDB and not "the only" IDB if this was really forbidden? strictness.problem( "writing SimplePacket for section with multiple interfaces") if strictness.should_fix(): # Can't fix this. The IDBs have already been written. pass snap_len = self.interface.snaplen if snap_len > 0 and snap_len < self.captured_len: # This isn't a strictness issue, it *will* break other readers # if we write more bytes than the snaplen says to expect. fld_size.encode(self.packet_len, outstream, endianness=self.section.endianness) fld_data.encode(self.packet_data[:snap_len], outstream) else: fld_size.encode(self.packet_len, outstream, endianness=self.section.endianness) fld_data.encode(self.packet_data, outstream)
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"], }, ]
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']}]