In my previous article, Inside DNP3’s Event-Driven Telemetry Model , we explored how DNP3 (IEEE 1815) organizes information through object groups, event classes, and Internal Indications (IIN). That discussion focused on the protocol's architecture and how an outstation exposes operational state to a SCADA master.
Understanding the specification, however, is only half of the story.
The more interesting question is what happens inside an implementation when frames begin arriving on the wire. How does an outstation process malformed transport fragments? What state transitions occur when control commands arrive out of sequence? How are administrative function codes handled internally? What actually changes when Secure Authentication (SA v5) is introduced?
To answer those questions, I built an isolated DNP3 research laboratory entirely in Python. Rather than relying on vendor implementations or third-party protocol libraries, I implemented custom outstation targets, protocol parsers, and master-side test harnesses to observe protocol behavior from the raw socket layer upward. This made it possible to inspect state transitions, reproduce protocol edge cases, and verify implementation behavior under controlled conditions.
Lab Artifacts & Code Availability
The accompanying repository contains:
- A custom DNP3 outstation engine implementation
- A Secure Authentication (SA v5) target outstation
- Individual master-side test suites for each research phase
- Detailed technical guides documenting protocol behavior and implementation observations
You can explore the complete project here:
GitHub Repository
This article summarizes five distinct phases of that research:
- Reconnaissance and protocol fingerprinting
- Control execution and Select-Before-Operate validation
- Administrative state transitions
- Transport-layer behavior and unsolicited messaging
- Secure Authentication (SA v5) verification
Phase 1 — Reconnaissance & Internal Indication Analysis
Before attempting any control operations, it is useful to understand what information a DNP3 endpoint exposes during normal communication.
Unlike many industrial protocols that require several exchanges before meaningful information becomes available, a standard Class 0 Integrity Read (FC 0x01) immediately returns an Application Layer response containing the Internal Indications (IIN) field.
Those two bytes provide a surprisingly useful snapshot of the outstation's operational state.
Depending on the implementation, the response may reveal whether:
- The device has recently restarted
- Time synchronization is required
- Event buffers contain pending data
- Hardware faults have been detected
- Unsupported function codes were received
- Parameter validation has failed
Because the IIN field is part of normal protocol behavior, it represents one of the earliest opportunities to understand how an outstation is currently operating.
The first phase of the lab simply performs a baseline integrity poll and parses those response flags.
On the opposite side of the connection, the custom outstation processes the incoming FT3 frame, builds the Application Layer response, and returns the corresponding IIN values.
One observation from this phase is the persistence of the Device Restart indication (IIN1.7). Following initialization, this flag remains asserted until the master acknowledges the restart condition. Although simple, this mechanism allows supervisory systems to distinguish between continuous operation and a recently restarted device without relying on external monitoring.
This phase also establishes an important baseline for later experiments. Once administrative commands, transport anomalies, and authentication mechanisms are introduced, changes in the IIN field become an effective way to observe how internal state evolves throughout the protocol.
Phase 2 — Control Execution & Select-Before-Operate Validation
One of DNP3's defining characteristics is its emphasis on deterministic control execution.
Rather than immediately actuating field devices whenever a command arrives, IEEE 1815 defines a Select-Before-Operate (SBO) workflow for Control Relay Output Blocks (CROB). The design intentionally separates validation from execution, reducing the likelihood of accidental operations caused by communication errors or malformed traffic.
Under the SBO model, a control operation normally follows two stages:
- Select (-
FC 0x03): The master requests control of a specific output point. The outstation validates the request and temporarily reserves that point. - Operate (-
FC 0x04): If the reservation remains valid, the outstation executes the requested operation and clears the reservation state.
The protocol also defines Direct Operate (FC 0x05), which bypasses the reservation phase entirely and requests immediate execution.
To understand how the implementation enforced these different execution paths, I evaluated two scenarios:
- Issuing a Direct Operate request without a preceding Select
- Transmitting an Operate request against an unarmed point
The master-side test suite executed both cases sequentially.
The first experiment demonstrated that Direct Operate followed its intended execution path. Because FC 0x05 explicitly bypasses the reservation stage, the simulated outstation accepted the request and executed the control action immediately.
The second experiment intentionally violated the SBO state machine: an OPERATE request (FC 0x04) was transmitted without a matching active SELECT. Rather than executing the command, the outstation rejected the request because no reservation existed for the specified control point.
The internal logs clearly illustrate that transition.
Although these experiments were performed against a custom implementation, they demonstrate why protocol state machines matter just as much as packet syntax. A correctly implemented DNP3 endpoint does more than decode bytes, it continuously tracks execution context, validates command ordering, and determines whether a requested action is currently valid.
Phase 3 — Administrative State Transitions
Control commands are only one part of the DNP3 application layer.
IEEE 1815 also defines a collection of administrative function codes that influence the lifecycle of an outstation itself. Rather than manipulating individual field points, these commands affect the runtime environment responsible for processing telemetry, maintaining event history, and coordinating system time.
|
|
|
|
|---|---|---|
|
| Cold Restart | Triggers a full device reboot and memory re-initialization sequence. |
|
| Warm Restart | Reloads partial application context while preserving core runtime settings. |
|
| Stop Application | Halts application-layer execution while maintaining active link connections. |
|
| Write Time | Updates the outstation internal system clock and clears time-sync flags. |
Unlike CROB operations, these requests alter the state of the outstation rather than the state of an individual control point.
To observe these transitions, I executed each function sequentially against the simulated outstation while monitoring both protocol responses and internal application state.
Key Behavioral Observations
Time Synchronization
The Write Time function (FC 0x18) updates the outstation's internal clock. In the lab, submitting a new timestamp immediately modified the runtime clock and cleared the Need Time indication (IIN1.4), demonstrating how time synchronization directly affects protocol state.
Warm Restart
Issuing a Warm Restart (FC 0x0E) temporarily interrupted application processing while preserving much of the runtime context. The response included a restart delay object, allowing the master to estimate when normal communications could safely resume.
Stop Application
The most interesting behavior occurred after issuing Stop Application (FC 0x12). Rather than terminating the network connection, the transport and link layers continued accepting frames while the application layer stopped processing operational requests. Subsequent read operations were therefore rejected, not because the TCP session had failed, but because the application itself had transitioned into a halted state.
The outstation logs clearly illustrate these transitions.
From a network perspective, the device still appeared reachable. From an application perspective, however, it was no longer servicing requests.
Phase 4 — Transport Reassembly & Unsolicited Messaging
The DNP3 Transport Pseudo-Layer is often overlooked because it consists of only a single control byte. Despite its simplicity, that byte is responsible for coordinating fragmentation and reassembly across every multi-frame Application Protocol Data Unit (APDU).
7 6 5 4 3 2 1 0
+-------+-------+-----------------------+
| FIR | FIN | SEQUENCE (0-63) |
+-------+-------+-----------------------+
The transport header contains three pieces of information:
- FIR (First Fragment): Indicates the beginning of an APDU stream.
- FIN (Final Fragment): Marks the final fragment in the stream.
- Sequence Number: A six-bit counter used to order fragments.
Reassembly State Validation
The first experiment transmitted a frame marked as the final fragment (FIR=0, FIN=1) without ever establishing an active fragment stream. From the parser's perspective, this meant receiving the end of a conversation that had never started.
Rather than attempting to process the payload, the parser detected the invalid state transition and rejected the frame. The corresponding runtime logs clearly show the parser identifying the missing fragment context (FIR=False) before terminating reassembly and issuing an IIN2.2 Parameter Error.
Unsolicited Response Processing (FC 0x82)
The final experiment in this phase focused on Unsolicited Responses (FC 0x82). Unlike conventional request-response communication, unsolicited messages allow an outstation to report significant events (such as analog threshold crossings or alarm conditions) without waiting for the master to poll.
Within the laboratory, I generated a synthetic unsolicited response carrying an Analog Input object (Group 30 Variation 1) to observe how the implementation processed an event arriving outside the normal polling cycle. Because the frame matched the expected protocol format, the simulated receiver accepted the message and updated its internal state accordingly, setting an overrange condition on Point 0.
Phase 5 — DNP3 Secure Authentication (SA v5) Verification
One of the most significant limitations of legacy DNP3 is the absence of built-in authentication for critical control operations. Any device capable of reaching an outstation can potentially issue administrative or control function codes unless additional security controls are deployed.
To address this, IEEE 1815-2012 introduced DNP3 Secure Authentication Version 5 (SA v5), later aligned with IEC 62351-5. Rather than encrypting protocol traffic, SA v5 protects sensitive operations through a challenge-response mechanism that verifies message authenticity and integrity before control commands are executed.
To better understand this workflow, I implemented a dedicated Secure Authentication outstation (outstation-sa.py) alongside a companion master test harness (master_test_runner.py).
Walking Through the Authentication Workflow
The authentication sequence follows a five-step state machine:
- Baseline Read (-
FC 0x01): Standard telemetry reads operate normally without cryptographic overhead. - Challenge Negotiation (-
FC 0x20, Object 120 Variation 1): Before executing a protected operation, the master requests or receives an Object Group 120 Variation 1 challenge containing a random nonce and sequence counter. - Challenge Reply (-
FC 0x20, Object 120 Variation 2): Using the pre-shared session key, the master computes an HMAC over the challenge data and submits the resulting authentication payload. - Session Key Status (-
Group 120 Variation 4): The outstation validates the digest, marks the session as authenticated, and returns the status payload. - Protected Control Execution: Privileged operations, such as CROB Select (-
FC 0x03), are processed normally under the active authenticated session.
Engineering Observations & Takeaways
Building this implementation reinforced an important distinction: Secure Authentication is not encryption. Application payloads remain fully visible on the wire, keeping DNP3 traffic completely inspectable by OT intrusion detection platforms. Instead, SA v5 guarantees the integrity and authenticity of critical commands.
Across all five phases, four key takeaways stand out:
- DNP3 is fundamentally stateful:Operational correctness relies heavily on transport stream state, control sequencing, and active session context.
- Audit Direct Operate Usage:-
FC 0x05bypasses safety arming windows. Industrial PCLs should strictly limit Direct Operate permissions. - Transport correctness is non-negotiable:Robust sequence tracking and stream timeout handling are essential to defend against state table exhaustion.
- Prioritize SA v5 Migration:SA v5 mitigates arbitrary frame injection and administrative disruption without requiring total network hardware replacement.
Moving from parsing capture files to implementing outstation parsers from scratch provides an invaluable perspective on industrial software security. Understanding the internal state machine turns raw packets into actionable defensive engineering.
All experiments were performed inside an isolated local emulation environment for protocol research, defensive engineering, and analysis. No testing was conducted against production industrial control infrastructure.