Documentation
¶
Overview ¶
Package profinetdcp decodes Profinet DCP (Discovery and Configuration Protocol) frames per IEC 61158-6-10. DCP is the **bootstrap** protocol for Profinet networks — the first protocol an attacker tapping into a Siemens-shop factory floor sees when enumerating devices, and the protocol Profinet IO controllers use at boot time to find their distributed I/O devices and assign them station names + IP addresses.
DCP runs **directly over Ethernet** (EtherType 0x8892) — no IP, no UDP — so it is a Layer 2-only protocol bounded to a single broadcast domain. Operationally, DCP carries:
- **Identify** — multicast "who's there?" used by Siemens TIA Portal, Step7, and Profinet IO controllers at engineering time to enumerate every Profinet-capable device on a wire. Each device responds with its station name, vendor, device ID, role, and current IP parameters.
- **Set** — unicast "set the station name + IP" commands sent by an engineering station or IO controller to bring a fresh device into a known configuration.
- **Get** — unicast attribute query (read the station name, read the IP, read the device options).
- **Hello** — multicast announcement an IO device sends after reboot to declare its presence to controllers.
Wrap-vs-native judgement
Native. IEC 61158-6-10 + the Profinet wiki + Wireshark's pn_dcp dissector fully specify the wire format. DCP frames are a tight FrameID + 10-byte fixed header followed by a TLV block stream with Option/Suboption discriminator bytes. No crypto at the parse layer.
What this package covers
**FrameID** (2 bytes, big-endian; transmitted immediately after the 0x8892 EtherType):
0xFEFE: **DCP Hello** (multicast announcement)
0xFEFD: **DCP Get/Set** (unicast request/response)
0xFEFC: **DCP Identify Request** (multicast)
0xFEFB: **DCP Identify Response** (multicast)
**DCP header** (10 bytes, big-endian):
byte 0: **ServiceID** — 0x03 Get / 0x04 Set / 0x05 Identify / 0x06 Hello.
byte 1: **ServiceType** — 0x00 Request / 0x01 Response Success / 0x05 Response Not Supported.
bytes 2-5: **Xid** (uint32 BE; transaction identifier for request/response pairing).
bytes 6-7: **ResponseDelay** (uint16 BE; on Identify requests, the receiver waits up to this many 10-ms ticks × DCP_TICK_FACTOR before replying to spread the response storm).
bytes 8-9: **DCPDataLength** (uint16 BE; bytes of TLV blocks following).
**TLV block walker** — each block is laid out as:
byte 0: **Option** (1 byte; categorical bucket).
byte 1: **Suboption** (1 byte; per-Option child).
bytes 2-3: **DCPBlockLength** (uint16 BE; bytes of payload following — INCLUDING the 2-byte BlockInfo for response blocks).
bytes 4+: payload (per-Option/Suboption shape).
Inter-block padding: blocks are 16-bit-aligned; odd-length blocks get a 1-byte 0x00 pad.
**7-entry Option name table**: 0x01 `IP` (subopts: MAC / IP_Parameter / Full_IP_Suite) / 0x02 `DeviceProperties` (subopts: Vendor / NameOfStation / DeviceID / DeviceRole / DeviceOptions / AliasName / DeviceInstance / OEMDeviceID) / 0x03 `DHCP` / 0x04 `LLDP` / 0x05 `ControlBlock` (subopts: Start / Stop / Signal / Response / FactoryReset / ResetToFactory) / 0x06 `DeviceInitiative` / 0xFF `AllSelector`.
**Per-Option/Suboption decoder set** (high-runners):
**IP / MAC** (0x01 / 0x01): 6-byte MAC address.
**IP / IP_Parameter** (0x01 / 0x02): IPv4 Address + Subnet Mask + Gateway (each 4 bytes BE).
**IP / Full_IP_Suite** (0x01 / 0x03): IP_Parameter
4 DNS server addresses.
**DeviceProperties / Vendor** (0x02 / 0x01): manufacturer name VISIBLE-STRING.
**DeviceProperties / NameOfStation** (0x02 / 0x02): station name VISIBLE-STRING (the unique IO-device identifier used by the IO controller — e.g. "et200sp.field-01").
**DeviceProperties / DeviceID** (0x02 / 0x03): VendorID (uint16 BE) + DeviceID (uint16 BE).
**DeviceProperties / DeviceRole** (0x02 / 0x04): bitmask — bit 0 IO-Device, bit 1 IO-Controller, bit 2 IO-Multidevice, bit 3 PN-Supervisor.
**DeviceProperties / DeviceOptions** (0x02 / 0x05): list of (Option, Suboption) pairs the device supports.
**DeviceProperties / DeviceInstance** (0x02 / 0x07): 2-byte high + 2-byte low instance identifier.
**DeviceProperties / OEMDeviceID** (0x02 / 0x08): OEM vendor + device IDs (same shape as DeviceID).
**ControlBlock / Signal** (0x05 / 0x03): "flash LED on the target" — the bench-engineering primitive for identifying a device by sight on the rack.
**AllSelector / All** (0xFF / 0xFF): request every option in a single round trip (the standard IdentifyAll body).
What this package does NOT cover (deliberately out of scope)
- **L2 framing** — feed Profinet DCP bytes after the 14-byte Ethernet header (destination MAC, source MAC, EtherType 0x8892). Standard DCP destination is multicast group 01:0E:CF:00:00:00 for Identify / Hello, or unicast for Get/Set targeted at a specific device. VLAN tagging via IEEE 802.1Q with PCP=6 priority is common but is part of the L2 frame and not parsed here.
- **Other Profinet FrameID ranges** — RT cyclic I/O data (FrameID 0x8000-0xBFFF), PTCP timing (0xFF40-0xFF43), Acyclic RT (0xFE00-0xFEFA) — different frame shapes that require their own decoders. This package specifically targets the DCP range (0xFEFB-0xFEFE).
- **BlockInfo field** — response blocks carry a 2-byte BlockInfo at the start of the payload (BlockQualifier + Status); surfaced as raw payload bytes for the per-Option decoders to peek at, but not separately parsed.
- **Profinet IO state-machine** — connection establishment (CR / AR setup), I/O exchange, alarm framing — higher- level analysis driven by GSD file metadata.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Block ¶
type Block struct {
Option int `json:"option"`
OptionName string `json:"option_name"`
Suboption int `json:"suboption"`
SuboptionName string `json:"suboption_name"`
Length int `json:"length"`
PayloadHex string `json:"payload_hex,omitempty"`
// Per-option decoded fields (only one set populated).
MAC string `json:"mac,omitempty"`
IPAddress string `json:"ip_address,omitempty"`
SubnetMask string `json:"subnet_mask,omitempty"`
Gateway string `json:"gateway,omitempty"`
Vendor string `json:"vendor,omitempty"`
NameOfStation string `json:"name_of_station,omitempty"`
VendorID int `json:"vendor_id,omitempty"`
DeviceID int `json:"device_id,omitempty"`
DeviceRoleHex string `json:"device_role_hex,omitempty"`
DeviceRoleDecoded string `json:"device_role_decoded,omitempty"`
}
Block is one TLV record in the DCP body.
type Result ¶
type Result struct {
TotalBytes int `json:"total_bytes"`
// FrameID + name
FrameID int `json:"frame_id"`
FrameIDName string `json:"frame_id_name"`
// DCP header (10 bytes)
ServiceID int `json:"service_id"`
ServiceIDName string `json:"service_id_name"`
ServiceType int `json:"service_type"`
ServiceTypeName string `json:"service_type_name"`
Xid uint32 `json:"xid"`
ResponseDelay int `json:"response_delay"`
DCPDataLength int `json:"dcp_data_length"`
// TLV blocks
Blocks []Block `json:"blocks,omitempty"`
}
Result is the structured decode of a Profinet DCP frame.