DeltaV Industrial Ethernet Communication

The DeltaV protocol is used in the Emerson DeltaV system. In contrast to most other systems, this is a combination of PLC and Control System. As Emerson seems to insist on the devices not being PLCs, but controllers, well call them this way in this document. Same with the Control System, these seem to be called OS (Operator System).

Disclaimer

As we had absolutely no information on the details of the protocol, we started by taking an existing DeltaV training system and replaced the network switch with a hub and used this to take network captures of the entire network traffic of the DeltaV network.

These network captures and some knowledge of the normal system operation were all we had.

All information we got, we got by inspecting these captures, building hypothesis and implementing tooling to verify these assumptions.

Definitely we will not have interpreted everything correct and we’ll probably do a lot of updating of this page.

Due to this uncertainty we are only implementing a promiscuous mode driver, which will be able to read ongoing traffic without the ability to actively interact with the system.

Read here for a document on how we proceeded with reverse engineering this protocol.

The Wrapper Protocol

All communication seems to be done using the UDP protocol on port 18507.

When working with the dumps it seems that this version of the DeltaV protocol was implemented by wrapping another protocol within a thin wrapper protocol. We have seen this for almost all other industrial controller types. We are hereby assuming that the inner protocol is the version of the DeltaV protocol, which is used for serial communication.

The general structure of such a wrapper packet seems to look like this:

deltav wrapper packet

After the fixed header of 0xFACE comes a short value providing the Payload Length.

After the payload probably comes the message type. Here we have observed the internal structure of packets with the same type to be very similar, so we’re quite sure this is a type.

The Message Id seems to be a counter existing for every communication between two partners, which is incremented for every packet sent to the other partner.

We have observed every unit to have a unique Sender Id, however a controller has been seen to have a differing id when communicating with a different operator system. What we did notice however, was that controllers usually had numerical ids while the OSes had hexadecimal letters. So an id of 0x0002 would probably imply a controller and an id of 0x000d would imply an operator system. But we could be wrong with this assumption and this just being a result of convention.

The 3 bytes after the id seem to relate to a timestamp. Inspecting the packets and correlating this to the timestamps in our packet captures, lead us to the assumption that this timestamp uses the first 14 bits for encoding seconds and the last 10 bits for encoding the sub-second fraction. While a 3 byte timestamp is only able to measure a 10 minute time-slot and is probably not usable for absolute time measuring, it might be useful to measure transfer times. For this a 4 byte value would have been a waste of network capacity and a 2 byte value wouldn’t be able to measure more than 60 seconds, which might be too short when taking into account packet resending and network timeouts. So for now we are assuming this format is used.

The following 3 bytes seem to have a constant value of 0x800400 for most packets, except the ones with a type of 0x03 and 0x04. For these two types the value seems to be 0x004400. Here we currently can’t make an assumption on the meaning of these fields. However as the 0x003 and 0x004 seem to be the first packets used for establishing the connection between the OS and the controller, these could somehow relate to the connection state.

After these mysterious 3 bytes comes the actual payload of the packet. The details of these will be discussed later in this document.

At the end comes a short value that seems to be a completely different value for every packet. As the message id and timestamp are different for every message, we are assuming this is a simple checksum. However we currently have no idea on how this is calculated and we haven’t invested any time in finding this out.

UDP being connectionless the DeltaV network protocol seems to require acknowledging. This is done by sending a packet back to the originator, but with a length of 0x0000 and the same type and mesasge-id` as the packet that is acknowledged.

High Level View of the Protocol

In this section we’ll describe the general structure of how the communication looks like - which message types are sent when and in which sequence.

Connecting an OS to a Controller

Failed to generate image: Could not find the 'seqdiag', 'seqdiag3' executable in PATH; add it to the PATH or specify its location using the 'seqdiag' document attribute
{
    Controller; OS;

    edge_length = 400;
    span_height = 18;
    default_fontsize = 12;
    activation = none;

    OS ->  Controller [label = "Type 0x0003 (Connection Request)"];
    OS <-  Controller [label = "Type 0x0004 (Connection Response)"];

    OS ->  Controller [label = "Type 0x0002 Subtype 0x0501 (Version Information)"];
    OS <-- Controller [label = "Ack"];

    OS <- Controller [label = "Type 0x0002 Subtype 0x0502 (Version Information)"];
    OS --> Controller [label = "Ack"];
}

Regular sync

It seems that every 15 seconds two packets are exchanged.

The type of these is always 0x0006.

One thing that is quite obvious, is that these are the only messages, for which the message id doesn’t seem to be incremented. So these messages have the exact same id as the following request being sent out.

We are assuming that these are sort of heartbeat messages and additionally sync the message ids so the remote could see if it lost messages in the last 15 seconds.

But that is just an assumption.

Failed to generate image: Could not find the 'seqdiag', 'seqdiag3' executable in PATH; add it to the PATH or specify its location using the 'seqdiag' document attribute
{
    Controller; OS;

    edge_length = 400;
    span_height = 18;
    default_fontsize = 12;
    activation = none;

    OS -> Controller [label = "Type 0x0006 (Sync)"];
    OS <-- Controller [label = "Type 0x0006 (Sync)"];
}

Data changes in the Controller

In general it seems as if all sub-types regarding normal data changes start with 0x04.

If the value of a subscribed value changes in the controller, a message type 0x0002 with sub-type 0x0403 is sent.

Failed to generate image: Could not find the 'seqdiag', 'seqdiag3' executable in PATH; add it to the PATH or specify its location using the 'seqdiag' document attribute
{
    Controller; OS;

    edge_length = 400;
    span_height = 18;
    default_fontsize = 12;
    activation = none;

    OS <- Controller [label = "Type 0x0002 Subtype 0x0403 (...)"];
    OS --> Controller [label = "Ack"];
}

Alarms in the Controller

In general it seems as if all sub-types regarding events and alarms start with 0x03.

Failed to generate image: Could not find the 'seqdiag', 'seqdiag3' executable in PATH; add it to the PATH or specify its location using the 'seqdiag' document attribute
{
    Controller; OS;

    edge_length = 400;
    span_height = 18;
    default_fontsize = 12;
    activation = none;

    OS <- Controller [label = "Type 0x0002 Subtype 0x030? (...)"];
    OS --> Controller [label = "Ack"];
}