class DCPControlBlock(Packet): fields_desc = [ ByteEnumField("option", 5, DCP_OPTIONS), MultiEnumField("sub_option", 4, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), LenField("dcp_block_length", 3), ByteEnumField("response", 2, DCP_OPTIONS), MultiEnumField("response_sub_option", 2, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), ByteEnumField("block_error", 0, BLOCK_ERRORS), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s
class DCPFullIPBlock(Packet): fields_desc = [ ByteEnumField("option", 1, DCP_OPTIONS), MultiEnumField("sub_option", 3, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), LenField("dcp_block_length", None), ShortEnumField("block_info", 1, IP_BLOCK_INFOS), IPField("ip", "192.168.0.2"), IPField("netmask", "255.255.255.0"), IPField("gateway", "192.168.0.1"), FieldListField("dnsaddr", [], IPField("", "0.0.0.0"), count_from=lambda x: 4), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s
class DCPNameOfStationBlock(Packet): fields_desc = [ ByteEnumField("option", 2, DCP_OPTIONS), MultiEnumField("sub_option", 2, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), FieldLenField("dcp_block_length", None, length_of="name_of_station", adjust=lambda p, x: x + 2), # FieldLenField("dcp_block_length", None, length_of= "name_of_station", adjust=lambda p,x: x +2), # TODO it should take the len from name_of_station, but it takes the len of the rest of the packet ShortEnumField("block_info", 0, BLOCK_INFOS), StrLenField( "name_of_station", "et200sp", length_from=lambda x: x.dcp_block_length - 2), # this works PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s
class DCPAliasNameBlock(Packet): fields_desc = [ ByteEnumField("option", 2, DCP_OPTIONS), MultiEnumField("sub_option", 6, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), FieldLenField("dcp_block_length", None, length_of="alias_name", adjust=lambda p, x: x + 2), ShortEnumField("block_info", 0, BLOCK_INFOS), StrLenField("alias_name", "et200sp", length_from=lambda x: x.dcp_block_length - 2), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s
class SAPHDBOptionPartRow(PacketNoPadded): """SAP HANA SQL Command Network Protocol Option Part Row This packet represents a row in an Option Part. Each row is comprised of a key, type and value. """ part_kind = None option_keys = None name = "SAP HANA SQL Command Network Protocol Option Part Row" fields_desc = [ MultiEnumField("key", 0, hdb_option_part_key_vals, depends_on=lambda x: x.part_kind, fmt="<b"), EnumField("type", 0, hdb_data_type_vals, fmt="<b"), ConditionalField(FieldLenField("length", None, length_of="value", fmt="<h"), lambda x: x.type in [29, 30, 33]), MultipleTypeField( [ (LESignedByteField("value", 0), lambda x: x.type == 1), (LESignedShortField("value", 0), lambda x: x.type == 2), (LESignedIntField("value", 0), lambda x: x.type == 3), (LESignedLongField("value", 0), lambda x: x.type == 4), (Field("value", 0, fmt="<d"), lambda x: x.type == 7), (YesNoByteField("value", 0), lambda x: x.type == 28), (StrFixedLenField("value", None, length_from=lambda x: x.length), lambda x: x.type in [29, 30, 33]), ], StrField("value", ""), ), ]
class DeviceOption(Packet): fields_desc = [ ByteEnumField("option", 2, DCP_OPTIONS), MultiEnumField("sub_option", 5, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), ] def extract_padding(self, s): return '', s
class IKEv2_payload_Transform(IKEv2_class): name = "IKE Transform" fields_desc = [ ByteEnumField("next_payload", None, {0: "last", 3: "Transform"}), ByteField("res", 0), ShortField("length", 8), ByteEnumField("transform_type", None, IKEv2Transforms), ByteField("res2", 0), MultiEnumField("transform_id", None, IKEv2TransformNum, depends_on=lambda pkt: pkt.transform_type, fmt="H"), # noqa: E501 ConditionalField(IKEv2_Key_Length_Attribute("key_length"), lambda pkt: pkt.length > 8), # noqa: E501 ]
class DCPBaseBlock(Packet): """ base class for all DCP Blocks """ fields_desc = [ ByteEnumField("option", 1, DCP_OPTIONS), MultiEnumField("sub_option", 2, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), FieldLenField("dcp_block_length", None, length_of="data"), ShortEnumField("block_info", 0, BLOCK_INFOS), StrLenField("data", "", length_from=lambda x: x.dcp_block_length), ] def extract_padding(self, s): return '', s
class DCPMACBlock(Packet): fields_desc = [ ByteEnumField("option", 1, DCP_OPTIONS), MultiEnumField("sub_option", 1, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), FieldLenField("dcp_block_length", None), ShortEnumField("block_info", 0, BLOCK_INFOS), MACField("mac", "00:00:00:00:00:00"), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s
class _RadiusAttrIntEnumVal(_SpecificRadiusAttr): """ Implements a RADIUS attribute which value field is 4 bytes long integer. """ __slots__ = ["val"] fields_desc = [ ByteEnumField("type", 6, _radius_attribute_types), ByteField("len", 6), MultiEnumField("value", 0, _radius_attrs_values, depends_on=lambda p: p.type, fmt="I") ]
class DCPDeviceInstanceBlock(Packet): fields_desc = [ ByteEnumField("option", 2, DCP_OPTIONS), MultiEnumField("sub_option", 7, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), LenField("dcp_block_length", 4), ShortEnumField("block_info", 0, BLOCK_INFOS), XByteField("device_instance_high", 0x00), XByteField("device_instance_low", 0x01), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s
class DCPDeviceRoleBlock(Packet): fields_desc = [ ByteEnumField("option", 2, DCP_OPTIONS), MultiEnumField("sub_option", 4, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), LenField("dcp_block_length", 4), ShortEnumField("block_info", 0, BLOCK_INFOS), ByteEnumField("device_role_details", 1, DCP_DEVICE_ROLES), XByteField("reserved", 0x00), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s
class DCPDeviceIDBlock(Packet): fields_desc = [ ByteEnumField("option", 2, DCP_OPTIONS), MultiEnumField("sub_option", 3, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), LenField("dcp_block_length", None), ShortEnumField("block_info", 0, BLOCK_INFOS), XShortField("vendor_id", 0x002a), XShortField("device_id", 0x0313), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s
class DCPManufacturerSpecificBlock(Packet): fields_desc = [ ByteEnumField("option", 2, DCP_OPTIONS), MultiEnumField("sub_option", 1, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), FieldLenField("dcp_block_length", None), ShortEnumField("block_info", 0, BLOCK_INFOS), StrLenField("device_vendor_value", "et200sp", length_from=lambda x: x.dcp_block_length - 2), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s
class DCPDeviceInitiativeBlock(Packet): """ device initiative DCP block """ fields_desc = [ ByteEnumField("option", 6, DCP_OPTIONS), MultiEnumField("sub_option", 1, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), FieldLenField("dcp_block_length", None, length_of="device_initiative"), ShortEnumField("block_info", 0, BLOCK_INFOS), ShortField("device_initiative", 1), ] def extract_padding(self, s): return '', s
class DCPBaseBlock(Packet): """ base class for all DCP Blocks """ fields_desc = [ ByteEnumField("option", 1, DCP_OPTIONS), MultiEnumField("sub_option", 2, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), FieldLenField("dcp_block_length", None, length_of="data"), # TODO ShortEnumField("block_info", 0, BLOCK_INFOS), StrLenField("data", "", length_from=lambda x: x.dcp_block_length), ] # needed for dissecting multiple blocks # https://stackoverflow.com/questions/8073508/scapy-adding-new-protocol-with-complex-field-groupings def extract_padding(self, s): return '', s
class DCPDeviceOptionsBlock(Packet): fields_desc = [ ByteEnumField("option", 2, DCP_OPTIONS), MultiEnumField("sub_option", 5, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), LenField("dcp_block_length", None), # TODO ShortEnumField("block_info", 0, BLOCK_INFOS), PacketListField("device_options", [], DeviceOption, length_from=lambda p: p.dcp_block_length - 2), PadField(StrLenField("padding", b"\x00", length_from=lambda p: p.dcp_block_length % 2), 1, padwith=b"\x00") ] def extract_padding(self, s): return '', s
class LLTD(Packet): name = "LLTD" answer_hashret = { # (tos, function) tuple mapping (answer -> query), used by # .hashret() (1, 1): (0, 0), (0, 12): (0, 11), } fields_desc = [ ByteField("version", 1), ByteEnumField("tos", 0, { 0: "Topology discovery", 1: "Quick discovery", 2: "QoS diagnostics", }), ByteField("reserved", 0), MultiEnumField("function", 0, { 0: { 0: "Discover", 1: "Hello", 2: "Emit", 3: "Train", 4: "Probe", 5: "Ack", 6: "Query", 7: "QueryResp", 8: "Reset", 9: "Charge", 10: "Flat", 11: "QueryLargeTlv", 12: "QueryLargeTlvResp", }, 1: { 0: "Discover", 1: "Hello", 8: "Reset", }, 2: { 0: "QosInitializeSink", 1: "QosReady", 2: "QosProbe", 3: "QosQuery", 4: "QosQueryResp", 5: "QosReset", 6: "QosError", 7: "QosAck", 8: "QosCounterSnapshot", 9: "QosCounterResult", 10: "QosCounterLease", }, }, depends_on=lambda pkt: pkt.tos, fmt="B"), MACField("real_dst", None), MACField("real_src", None), ConditionalField(ShortField("xid", 0), lambda pkt: pkt.function in [0, 8]), ConditionalField(ShortField("seq", 0), lambda pkt: pkt.function not in [0, 8]), ] def post_build(self, pkt, pay): if (self.real_dst is None or self.real_src is None) and \ isinstance(self.underlayer, Ether): eth = self.underlayer if self.real_dst is None: pkt = (pkt[:4] + eth.fields_desc[0].i2m(eth, eth.dst) + pkt[10:]) if self.real_src is None: pkt = (pkt[:10] + eth.fields_desc[1].i2m(eth, eth.src) + pkt[16:]) return pkt + pay def mysummary(self): if isinstance(self.underlayer, Ether): return self.underlayer.sprintf( 'LLTD %src% > %dst% %LLTD.tos% - %LLTD.function%') else: return self.sprintf('LLTD %tos% - %function%') def hashret(self): tos, function = self.tos, self.function return "%c%c" % self.answer_hashret.get((tos, function), (tos, function)) def answers(self, other): if not isinstance(other, LLTD): return False if self.tos == 0: if self.function == 0 and isinstance(self.payload, LLTDDiscover) \ and len(self[LLTDDiscover].stations_list) == 1: # "Topology discovery - Discover" with one MAC address # discovered answers a "Quick discovery - Hello" return other.tos == 1 and \ other.function == 1 and \ LLTDAttributeHostID in other and \ other[LLTDAttributeHostID].mac == \ self[LLTDDiscover].stations_list[0] elif self.function == 12: # "Topology discovery - QueryLargeTlvResp" answers # "Topology discovery - QueryLargeTlv" with same .seq # value return other.tos == 0 and other.function == 11 \ and other.seq == self.seq elif self.tos == 1: if self.function == 1 and isinstance(self.payload, LLTDHello): # "Quick discovery - Hello" answers a "Topology # discovery - Discover" return other.tos == 0 and other.function == 0 and \ other.real_src == self.current_mapper_address return False
class ProfinetDCP(Packet): """ Profinet DCP Packet Requests are handled via ConditionalField because here only 1 Block is used every time. Response can contain 1..n Blocks, for that you have to use one ProfinetDCP Layer with one or multiple DCP*Block Layers:: ProfinetDCP / DCPNameOfStationBlock / DCPDeviceIDBlock ... Example for a DCP Identify All Request:: Ether(dst="01:0e:cf:00:00:00") / ProfinetIO(frameID=DCP_IDENTIFY_REQUEST_FRAME_ID) / ProfinetDCP(service_id=DCP_SERVICE_ID_IDENTIFY, service_type=DCP_REQUEST, option=255, sub_option=255, dcp_data_length=4) Example for a DCP Identify Response:: Ether(dst=dst_mac) / ProfinetIO(frameID=DCP_IDENTIFY_RESPONSE_FRAME_ID) / ProfinetDCP( service_id=DCP_SERVICE_ID_IDENTIFY, service_type=DCP_RESPONSE) / DCPNameOfStationBlock(name_of_station="device1") Example for a DCP Set Request:: Ether(dst=mac) / ProfinetIO(frameID=DCP_GET_SET_FRAME_ID) / ProfinetDCP(service_id=DCP_SERVICE_ID_SET, service_type=DCP_REQUEST, option=2, sub_option=2, dcp_data_length=14, dcp_block_length=10, name_of_station=name, reserved=0) """ name = "Profinet DCP" # a DCP PDU consists of some fields and 1..n DCP Blocks fields_desc = [ ByteEnumField("service_id", 5, DCP_SERVICE_ID), ByteEnumField("service_type", 0, DCP_SERVICE_TYPE), XIntField("xid", 0x01000001), # XShortField('reserved', 0), ShortField('reserved', 0), LenField("dcp_data_length", None), # DCP REQUEST specific ConditionalField(ByteEnumField("option", 2, DCP_OPTIONS), lambda pkt: pkt.service_type == 0), ConditionalField( MultiEnumField("sub_option", 3, DCP_SUBOPTIONS, fmt='B', depends_on=lambda p: p.option), lambda pkt: pkt.service_type == 0), # calculate the len fields - workaround ConditionalField(LenField("dcp_block_length", 0), lambda pkt: pkt.service_type == 0), # DCP SET REQUEST # ConditionalField(ShortEnumField("block_qualifier", 1, BLOCK_QUALIFIERS), lambda pkt: pkt.service_id == 4 and pkt.service_type == 0), # Name Of Station ConditionalField(StrLenField("name_of_station_set_req", "et200sp", length_from=lambda x: x.dcp_block_length - 2), lambda pkt: pkt.service_id == 4 and pkt.service_type == 0 and pkt.option == 2 and pkt.sub_option == 2), # MAC ConditionalField(MACField("mac", "00:00:00:00:00:00"), lambda pkt: pkt.service_id == 4 and pkt.service_type == 0 and pkt.option == 1 and pkt.sub_option == 1), # IP ConditionalField(IPField("ip", "192.168.0.2"), lambda pkt: pkt.service_id == 4 and pkt.service_type == 0 and pkt.option == 1 and pkt.sub_option == 2), ConditionalField(IPField("netmask", "255.255.255.0"), lambda pkt: pkt.service_id == 4 and pkt.service_type == 0 and pkt.option == 1 and pkt.sub_option == 2), ConditionalField(IPField("gateway", "192.168.0.1"), lambda pkt: pkt.service_id == 4 and pkt.service_type == 0 and pkt.option == 1 and pkt.sub_option == 2), # DCP IDENTIFY REQUEST # # Name of station ConditionalField(StrLenField("name_of_station_identify_req", "et200sp", length_from=lambda x: x.dcp_block_length), lambda pkt: pkt.service_id == 5 and pkt.service_type == 0 and pkt.option == 2 and pkt.sub_option == 2), # Alias name ConditionalField(StrLenField("alias_name", "et200sp", length_from=lambda x: x.dcp_block_length), lambda pkt: pkt.service_id == 5 and pkt.service_type == 0 and pkt.option == 2 and pkt.sub_option == 6), # implement further REQUEST fields if needed .... # DCP RESPONSE BLOCKS # ConditionalField( PacketListField("dcp_blocks", [], guess_dcp_block_class, length_from=lambda p: p.dcp_data_length), lambda pkt: pkt.service_type == 1), ] def post_build(self, pkt, pay): # add padding to ensure min packet length padding = MIN_PACKET_LENGTH - (len(pkt + pay)) pay += b"\0" * padding return Packet.post_build(self, pkt, pay)