602 lines
20 KiB
Python
602 lines
20 KiB
Python
|
# coding: utf8
|
||
|
|
||
|
# This file is part of Scapy
|
||
|
# Scapy is free software: you can redistribute it and/or modify
|
||
|
# it under the terms of the GNU General Public License as published by
|
||
|
# the Free Software Foundation, either version 2 of the License, or
|
||
|
# any later version.
|
||
|
#
|
||
|
# Scapy is distributed in the hope that it will be useful,
|
||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
# GNU General Public License for more details.
|
||
|
#
|
||
|
# You should have received a copy of the GNU General Public License
|
||
|
# along with Scapy. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
# Copyright (C) 2019 Stefan Mehner (stefan.mehner@b-tu.de)
|
||
|
|
||
|
# scapy.contrib.description = Profinet DCP layer
|
||
|
# scapy.contrib.status = loads
|
||
|
|
||
|
from scapy.compat import orb
|
||
|
from scapy.all import Packet, bind_layers, Padding
|
||
|
from scapy.fields import ByteEnumField, ShortField, XShortField, \
|
||
|
ShortEnumField, FieldLenField, XByteField, XIntField, MultiEnumField, \
|
||
|
IPField, MACField, StrLenField, PacketListField, PadField, \
|
||
|
ConditionalField, LenField
|
||
|
|
||
|
# minimum packet is 60 bytes.. 14 bytes are Ether()
|
||
|
MIN_PACKET_LENGTH = 44
|
||
|
|
||
|
#####################################################
|
||
|
# Constants #
|
||
|
#####################################################
|
||
|
|
||
|
DCP_GET_SET_FRAME_ID = 0xFEFD
|
||
|
DCP_IDENTIFY_REQUEST_FRAME_ID = 0xFEFE
|
||
|
DCP_IDENTIFY_RESPONSE_FRAME_ID = 0xFEFF
|
||
|
|
||
|
DCP_REQUEST = 0x00
|
||
|
DCP_RESPONSE = 0x01
|
||
|
|
||
|
DCP_SERVICE_ID_GET = 0x03
|
||
|
DCP_SERVICE_ID_SET = 0x04
|
||
|
DCP_SERVICE_ID_IDENTIFY = 0x05
|
||
|
|
||
|
DCP_SERVICE_ID = {
|
||
|
0x00: "reserved",
|
||
|
0x01: "Manufacturer specific",
|
||
|
0x02: "Manufacturer specific",
|
||
|
0x03: "Get",
|
||
|
0x04: "Set",
|
||
|
0x05: "Identify",
|
||
|
0x06: "Hello",
|
||
|
}
|
||
|
|
||
|
DCP_SERVICE_TYPE = {
|
||
|
0x00: "Request",
|
||
|
0x01: "Response Success",
|
||
|
0x05: "Response - Request not supported",
|
||
|
}
|
||
|
|
||
|
DCP_DEVICE_ROLES = {
|
||
|
0x00: "IO Supervisor",
|
||
|
0x01: "IO Device",
|
||
|
0x02: "IO Controller",
|
||
|
|
||
|
}
|
||
|
|
||
|
DCP_OPTIONS = {
|
||
|
0x00: "reserved",
|
||
|
0x01: "IP",
|
||
|
0x02: "Device properties",
|
||
|
0x03: "DHCP",
|
||
|
0x04: "Reserved",
|
||
|
0x05: "Control",
|
||
|
0x06: "Device Initiative",
|
||
|
0xff: "All Selector"
|
||
|
}
|
||
|
DCP_OPTIONS.update({i: "reserved" for i in range(0x07, 0x7f)})
|
||
|
DCP_OPTIONS.update({i: "Manufacturer specific" for i in range(0x80, 0xfe)})
|
||
|
|
||
|
DCP_SUBOPTIONS = {
|
||
|
# ip
|
||
|
0x01: {
|
||
|
0x00: "Reserved",
|
||
|
0x01: "MAC Address",
|
||
|
0x02: "IP Parameter"
|
||
|
},
|
||
|
# device properties
|
||
|
0x02: {
|
||
|
0x00: "Reserved",
|
||
|
0x01: "Manufacturer specific (Type of Station)",
|
||
|
0x02: "Name of Station",
|
||
|
0x03: "Device ID",
|
||
|
0x04: "Device Role",
|
||
|
0x05: "Device Options",
|
||
|
0x06: "Alias Name",
|
||
|
0x07: "Device Instance",
|
||
|
0x08: "OEM Device ID",
|
||
|
},
|
||
|
# dhcp
|
||
|
0x03: {
|
||
|
0x0c: "Host name",
|
||
|
0x2b: "Vendor specific",
|
||
|
0x36: "Server identifier",
|
||
|
0x37: "Parameter request list",
|
||
|
0x3c: "Class identifier",
|
||
|
0x3d: "DHCP client identifier",
|
||
|
0x51: "FQDN, Fully Qualified Domain Name",
|
||
|
0x61: "UUID/GUID-based Client",
|
||
|
0xff: "Control DHCP for address resolution"
|
||
|
},
|
||
|
# control
|
||
|
0x05: {
|
||
|
0x00: "Reserved",
|
||
|
0x01: "Start Transaction",
|
||
|
0x02: "End Transaction",
|
||
|
0x03: "Signal",
|
||
|
0x04: "Response",
|
||
|
0x05: "Reset Factory Settings",
|
||
|
0x06: "Reset to Factory"
|
||
|
},
|
||
|
# device initiative
|
||
|
0x06: {
|
||
|
0x00: "Reserved",
|
||
|
0x01: "Device Initiative"
|
||
|
},
|
||
|
0xff: {
|
||
|
0xff: "ALL Selector"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BLOCK_INFOS = {
|
||
|
0x00: "Reserved",
|
||
|
}
|
||
|
BLOCK_INFOS.update({i: "reserved" for i in range(0x01, 0xff)})
|
||
|
|
||
|
|
||
|
IP_BLOCK_INFOS = {
|
||
|
0x0000: "IP not set",
|
||
|
0x0001: "IP set",
|
||
|
0x0002: "IP set by DHCP",
|
||
|
0x0080: "IP not set (address conflict detected)",
|
||
|
0x0081: "IP set (address conflict detected)",
|
||
|
0x0082: "IP set by DHCP (address conflict detected)",
|
||
|
}
|
||
|
IP_BLOCK_INFOS.update({i: "reserved" for i in range(0x0003, 0x007f)})
|
||
|
|
||
|
BLOCK_ERRORS = {
|
||
|
0x00: "Ok",
|
||
|
0x01: "Option unsupp.",
|
||
|
0x02: "Suboption unsupp. or no DataSet avail.",
|
||
|
0x03: "Suboption not set",
|
||
|
0x04: "Resource Error",
|
||
|
0x05: "SET not possible by local reasons",
|
||
|
0x06: "In operation, SET not possible",
|
||
|
}
|
||
|
|
||
|
BLOCK_QUALIFIERS = {
|
||
|
0x0000: "Use the value temporary",
|
||
|
0x0001: "Save the value permanent",
|
||
|
}
|
||
|
BLOCK_QUALIFIERS.update({i: "reserved" for i in range(0x0002, 0x00ff)})
|
||
|
|
||
|
|
||
|
#####################################################
|
||
|
# DCP Blocks #
|
||
|
#####################################################
|
||
|
|
||
|
# GENERIC DCP BLOCK
|
||
|
|
||
|
# DCP RESPONSE BLOCKS
|
||
|
|
||
|
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
|
||
|
|
||
|
|
||
|
# OPTION: IP
|
||
|
|
||
|
class DCPIPBlock(Packet):
|
||
|
fields_desc = [
|
||
|
ByteEnumField("option", 1, DCP_OPTIONS),
|
||
|
MultiEnumField("sub_option", 2, 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"),
|
||
|
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 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
|
||
|
|
||
|
|
||
|
# OPTION: Device Properties
|
||
|
|
||
|
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 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),
|
||
|
|
||
|
ShortEnumField("block_info", 0, BLOCK_INFOS),
|
||
|
StrLenField("name_of_station", "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 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 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
|
||
|
|
||
|
|
||
|
# one DeviceOptionsBlock can contain 1..n different options
|
||
|
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 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),
|
||
|
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 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 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 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
|
||
|
|
||
|
|
||
|
def guess_dcp_block_class(packet, **kargs):
|
||
|
"""
|
||
|
returns the correct dcp block class needed to dissect the current tag
|
||
|
if nothing can be found -> dcp base block will be used
|
||
|
|
||
|
:param packet: the current packet
|
||
|
:return: dcp block class
|
||
|
"""
|
||
|
# packet = unicode(packet, "utf-8")
|
||
|
option = orb(packet[0])
|
||
|
suboption = orb(packet[1])
|
||
|
|
||
|
# NOTE implement the other functions if needed
|
||
|
|
||
|
class_switch_case = {
|
||
|
# IP
|
||
|
0x01:
|
||
|
{
|
||
|
0x01: "DCPMACBlock",
|
||
|
0x02: "DCPIPBlock"
|
||
|
},
|
||
|
# Device Properties
|
||
|
0x02:
|
||
|
{
|
||
|
0x01: "DCPManufacturerSpecificBlock",
|
||
|
0x02: "DCPNameOfStationBlock",
|
||
|
0x03: "DCPDeviceIDBlock",
|
||
|
0x04: "DCPDeviceRoleBlock",
|
||
|
0x05: "DCPDeviceOptionsBlock",
|
||
|
0x06: "DCPAliasNameBlock",
|
||
|
0x07: "DCPDeviceInstanceBlock",
|
||
|
0x08: "OEM Device ID"
|
||
|
},
|
||
|
# DHCP
|
||
|
0x03:
|
||
|
{
|
||
|
0x0c: "Host name",
|
||
|
0x2b: "Vendor specific",
|
||
|
0x36: "Server identifier",
|
||
|
0x37: "Parameter request list",
|
||
|
0x3c: "Class identifier",
|
||
|
0x3d: "DHCP client identifier",
|
||
|
0x51: "FQDN, Fully Qualified Domain Name",
|
||
|
0x61: "UUID/GUID-based Client",
|
||
|
0xff: "Control DHCP for address resolution"
|
||
|
},
|
||
|
# Control
|
||
|
0x05:
|
||
|
{
|
||
|
0x00: "Reserved (0x00)",
|
||
|
0x01: "Start Transaction (0x01)",
|
||
|
0x02: "End Transaction (0x02)",
|
||
|
0x03: "Signal (0x03)",
|
||
|
0x04: "DCPControlBlock",
|
||
|
0x05: "Reset Factory Settings (0x05)",
|
||
|
0x06: "Reset to Factory (0x06)"
|
||
|
},
|
||
|
# Device Inactive
|
||
|
0x06:
|
||
|
{
|
||
|
0x00: "Reserved (0x00)",
|
||
|
0x01: "Device Initiative (0x01)"
|
||
|
},
|
||
|
# ALL Selector
|
||
|
0xff:
|
||
|
{
|
||
|
0xff: "ALL Selector (0xff)"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
try:
|
||
|
c = class_switch_case[option][suboption]
|
||
|
except KeyError:
|
||
|
c = "DCPBaseBlock"
|
||
|
|
||
|
cls = globals()[c]
|
||
|
return cls(packet, **kargs)
|
||
|
|
||
|
|
||
|
# GENERIC DCP PACKET
|
||
|
|
||
|
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", "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", "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)
|
||
|
|
||
|
|
||
|
bind_layers(ProfinetDCP, Padding)
|