dnp3ctl — DNP3 (IEEE 1815) Command-Line Tool: Poll, Monitor, Operate, Synchronize Time, Transfer Files & Simulate an Outstation
dnp3ctl is a command-line toolkit for DNP3 (IEEE 1815), the SCADA protocol between masters and outstations in electric, water and oil and gas utilities, in both roles. As a master it polls an RTU, IED or gateway, reads any object and range, watches the unsolicited responses it sends, operates controls (select-before-operate, direct operate, no-ack; CROB and analog set-points), synchronizes its clock, restarts it, freezes counters, reads its device attributes and transfers files. As an outstation it simulates an RTU from a model of points, controls and files, so that a SCADA master or a protocol gateway has something to talk to.
It is built for commissioning and site acceptance tests, point-to-point checks, troubleshooting a link between a master and its outstations, and hardware-free test setups. Output can be plain text, a table or JSON with stable exit codes, so it works in scripts and CI. It speaks TCP, TLS, UDP and serial, and Secure Authentication v5. It is written in Go on top of go-dnp3.
Quick links: Quick start · Features · Installation · DNP3 in two minutes · Connecting · Commands · Simulated outstation · FAQ
Responsible use: dnp3ctl sends real SCADA requests. Only use it on outstations and networks you own or are authorized to test.
client operateoperates breakers and changes set-points;client restart,time-sync,freeze,assign-class,manage,write-attribute,write-deadband,clear-restartandunsolicitedchange the outstation itself, andclient file putandrmchange its files (see SECURITY.md): know what an index controls before you send to it. An outstation discards the events a master has read: polling an outstation that is in service with its master's link address takes those events away from it.
- See the whole process image —
client pollruns an integrity poll and lists every point with its flags;client readreads any object (g30v5,g1, …) and range, and events with their time stamps. - Watch an outstation live —
client monitorstays connected, enables unsolicited responses and prints every event as it arrives, as text, a table or JSON Lines. - Operate, carefully —
client operatesends control relay output blocks (latch, pulse, trip, close) and analog set-points, with select-before-operate, direct operate or direct operate without acknowledgement. Everything is validated first, and a control is sent exactly once. - Everything else a master does — time synchronization (LAN and non-LAN), cold and warm restarts, enable and disable unsolicited, assign class, freeze and freeze-and-clear, device attributes, delay measurement.
- Files — list the directories of an outstation, fetch disturbance records and logs, write and delete files (group 70).
- Find what is there —
discoversweeps subnets for outstations with a single link status request, and reports the link address each answers from. - Simulate an RTU —
server startserves an outstation from a YAML model: values that change by themselves, event classes and buffers per master, unsolicited responses, controls with select-before-operate, restarts, attributes, files and uploads; several outstations behind one port, serial line or UDP socket. - Every transport — TCP, TLS with mutual authentication, UDP and serial lines, and the dual end point where the outstation connects to the master; Secure Authentication v5 with session key change or a pre-shared key, aggressive mode and security statistics.
- Script-friendly —
--format text|json|table, a single JSON error object on failure, stable exit codes andDNP3CTL_*environment variables. - Pre-built binaries — Linux, macOS and Windows (x86-64 and ARM) from GitHub Releases, with SHA-256 checksums; no runtime to install.
Download a binary from GitHub Releases (see Installation for copy-paste commands for Linux, macOS and Windows, or build from source). No outstation at hand? Start the built-in demo outstation in one terminal:
dnp3ctl server startand talk to it from another (replace 127.0.0.1 with your outstation, and 10 with its link address):
# What does this outstation hold? (integrity poll)
dnp3ctl client poll --ip 127.0.0.1 --outstation 10
# Watch events as they arrive, until Ctrl-C
dnp3ctl client monitor --ip 127.0.0.1 --integrity
# Open a breaker with select-before-operate
dnp3ctl client operate --ip 127.0.0.1 --type crob --index 0 --op latch-off
# Change a set-point
dnp3ctl client operate --ip 127.0.0.1 --type analog --index 0 --value 11.2
# Synchronize its clock
dnp3ctl client time-sync --ip 127.0.0.1
# Which outstations are on this subnet?
dnp3ctl discover --subnets 192.168.1.0/24
# The same poll as JSON, for a script
dnp3ctl client poll --ip 127.0.0.1 --format json$ dnp3ctl client poll --ip 127.0.0.1
Integrity poll of outstation 10 at tcp://127.0.0.1:20000: 14 objects in 1 ms IIN: NEED_TIME|DEVICE_RESTART
g1v2 binary-input 0 on [ONLINE]
g1v2 binary-input 1 off [ONLINE]
g3v2 double-bit-input 0 on [ONLINE]
g10v2 binary-output 0 on [ONLINE]
g20v1 counter 0 123456 [ONLINE]
g30v5 analog-input 0 11.02 [ONLINE]
g30v1 analog-input 1 212 [ONLINE]
g40v3 analog-output 0 11 [ONLINE]
g110v8 octet-string 0 FEEDER-7
...Points are numbers: the object (g30v5: group 30, analog input, variation 5) says what a value is and how it is encoded, the index which point of that type it is. See DNP3 in two minutes.
DNP3 test tools are mostly graphical: protocol test harnesses and simulators with a window per channel. Protocol libraries are the other end of the range: they do everything, once you have written a program. dnp3ctl sits in between.
| dnp3ctl | GUI test tools and simulators | Protocol libraries | Wireshark | |
|---|---|---|---|---|
| Poll, monitor, operate without writing code | ✅ | ✅ | — | — |
| Works over SSH, on a headless gateway or a jump host | ✅ | varies | ✅ | partial (tshark) |
| Scriptable: JSON output, exit codes, environment variables | ✅ | rarely | ✅ (it is code) | partial |
| Simulate an outstation from a text file | ✅ | ✅ | with code | — |
| Serial lines and UDP as well as TCP and TLS | ✅ | ✅ | most | — |
| Runs in CI, in a container, on a Raspberry Pi | ✅ | varies | ✅ | ✅ |
| Decodes traffic between two other parties | — | some | — | ✅ |
| Single self-contained binary, Linux / macOS / Windows | ✅ | varies | — | — |
Typical capabilities by category; individual products vary.
They combine well: use dnp3ctl to drive and check an outstation from the command line, a GUI tool when you want to click through a point list, and Wireshark when you need to see what two other devices say to each other.
- Commissioning and site acceptance tests — Poll the outstation, compare with the point list, operate each output with select-before-operate and watch the events arrive.
- Point-to-point tests — Leave
client monitorrunning while someone operates the primary equipment; every event is printed with its time stamp. - Troubleshooting a link —
client ping,discoverand--debugshow whether the outstation answers, at which link address, how fast, and what exactly is on the wire. - Master and gateway development — Point your SCADA master or protocol gateway at
server startand get an outstation that answers polls, executes controls, reports unsolicited and refuses what you tell it to. - Network inventory — Sweep subnets for DNP3 outstations and their link addresses.
- Automation and CI — Use
--format json, exit codes and environment variables in scripts and pipelines; run the simulated outstation as a test fixture. - Collecting records — Fetch disturbance records and logs from an outstation's file system.
- Why dnp3ctl?
- Quick start
- How it compares
- Use cases
- Features
- Installation
- DNP3 in two minutes
- Usage
- Environment variable configuration
- FAQ
- Project structure
- Development
- Version history
- Contributing
- License
| Feature | Description |
|---|---|
| Poll | Integrity poll (events of classes 1-3, then class 0), event poll of chosen classes, or a poll of exactly the classes given; filters by object and index. |
| Read | READ of any object and variation, any variation (g30), ranges, index lists and counts (events). |
| Monitor | Stay connected and stream every value: unsolicited responses, and optional integrity and periodic event polls. Reconnects, filters, --count and --duration. |
| Operate | Control relay output blocks (latch on/off, pulse on/off, trip, close; count, on and off time) and analog output blocks (32-bit, 16-bit, single and double precision); select-before-operate, direct operate and direct operate without acknowledgement; several controls in one request; broadcast to every outstation on the channel. |
| Time | Time synchronization by the LAN procedure (RECORD_CURRENT_TIME), the non-LAN procedure (DELAY_MEASURE with delay compensation), or a plain write of a given time. |
| Restart | COLD_RESTART and WARM_RESTART, with the delay the outstation reports. |
| Unsolicited | ENABLE_UNSOLICITED and DISABLE_UNSOLICITED per class. |
| Assign class | ASSIGN_CLASS of objects and ranges to an event class. |
| Freeze | IMMED_FREEZE and FREEZE_CLEAR of counters or analog inputs, with or without acknowledgement; FREEZE_AT_TIME at a time to come, repeated at an interval; broadcast. |
| Attributes | Device attributes (group 0) of the standard set or of any set the outstation defines: all, one by name or number, or the list of supported variations; write a writable attribute. |
| Deadbands | Read (g34) and write the deadbands of analog inputs. |
| Management | INITIALIZE_DATA, INITIALIZE_APPL, START_APPL, STOP_APPL and SAVE_CONFIG; clear the DEVICE_RESTART indication. |
| Ping | DELAY_MEASURE at an interval, with round trip and the outstation's processing delay. |
| Files | File transfer (group 70): list a directory, file info, fetch, write, append, delete; file authentication with credentials from a file or the environment. |
| Objects | List the groups and variations dnp3ctl encodes and decodes. |
| Discover | Find DNP3 outstations on subnets with a link status request (no application layer request) and report their link address. |
| Server | A simulated outstation from a YAML or JSON model: every point type, times and intervals (g50v4), event classes and deadbands, event buffers per master, unsolicited responses, controls with select-before-operate and forced refusals, command events, restarts, time synchronization, device attributes and further attribute sets, scheduled freezes, files and uploads, simulated values; several outstations at their own link addresses behind one port, serial line or UDP socket; TCP, TLS, UDP or serial, listening or connecting to its master (--dial); allow-list; Secure Authentication with security statistics. |
| Transports | TCP, TLS (IEEE 1815-2012 Annex B / IEC 62351-3) with server verification and client certificates, UDP and serial lines, selected with --url; the dual end point (annex A.3), where the master waits for the outstation to connect, with --listen. See Connecting to an outstation. |
| Link layer | Master and outstation link addresses, confirmed or unconfirmed user data, link timeout and retries, keep-alive. |
| Secure Authentication | SAv5 with session key change (an update key, AES key wrap) or a pre-shared session key, read from a file or the environment (never the command line); HMAC-SHA-256 or HMAC-SHA-1; aggressive mode (--sa-aggressive); the security statistics (g121) by name. |
| Robustness | Timeouts per fragment and per request, retries of the requests that are safe to repeat (never controls), reconnects in monitor. |
--format |
Every command that prints a result accepts --format text|json|table (or DNP3CTL_FORMAT); streams write JSON Lines. server generate-config --format picks yaml or json, since it writes a model file, not a result. |
--debug |
Trace every link frame and application fragment sent and received, decoded, to stderr. |
| Completion | Shell completion for bash, zsh, fish and PowerShell; flags with a fixed set of values (--format, --mode, --op, --object, …) complete to those values. |
The following tree view gives the overview of available commands.
dnp3ctl
├── client
│ ├── poll Integrity, event or class poll
│ ├── read Read objects by group, variation and range
│ ├── monitor Print every value the outstation reports, until interrupted
│ ├── operate CROB or analog set-point (sbo | direct | direct-no-ack)
│ ├── time-sync Synchronize the clock (lan | non-lan | write)
│ ├── restart COLD_RESTART or WARM_RESTART
│ ├── unsolicited
│ │ ├── enable ENABLE_UNSOLICITED
│ │ └── disable DISABLE_UNSOLICITED
│ ├── assign-class ASSIGN_CLASS
│ ├── freeze IMMED_FREEZE / FREEZE_CLEAR / FREEZE_AT_TIME, or broadcast
│ ├── attributes Device attributes (group 0), of any set
│ ├── write-attribute Write a device attribute
│ ├── write-deadband Write the deadband of analog inputs (g34)
│ ├── manage Management functions (INITIALIZE_DATA, START_APPL, SAVE_CONFIG, …)
│ ├── clear-restart Clear the DEVICE_RESTART indication
│ ├── ping DELAY_MEASURE with round-trip time
│ ├── file
│ │ ├── ls List a directory
│ │ ├── info File or directory descriptor
│ │ ├── get Fetch a file
│ │ ├── put Write a file
│ │ └── rm Delete a file
│ └── objects List the groups and variations dnp3ctl knows
├── server
│ ├── start Serve a simulated outstation from a model (or the built-in demo)
│ └── generate-config Write an example outstation model
├── discover Find DNP3 outstations on subnets
├── completion Generate shell completion script (bash|zsh|fish|powershell)
└── version Print version
The easiest way to install dnp3ctl is to download a pre-built binary from the GitHub Releases page. No Go toolchain is required.
| Platform | CPU | Download |
|---|---|---|
| Linux | x86-64 | dnp3ctl-<version>-linux-amd64.tar.gz |
| Linux | 64-bit ARM (Raspberry Pi 4/5 with a 64-bit OS, AWS Graviton) | dnp3ctl-<version>-linux-arm64.tar.gz |
| Linux | 32-bit ARM, ARMv7 (Raspberry Pi 2 and later with a 32-bit OS) | dnp3ctl-<version>-linux-armv7.tar.gz |
| macOS | Apple Silicon (M1 and later) | dnp3ctl-<version>-darwin-arm64.tar.gz |
| macOS | Intel | dnp3ctl-<version>-darwin-amd64.tar.gz |
| Windows | x86-64 | dnp3ctl-<version>-windows-amd64.zip |
| Windows | ARM64 | dnp3ctl-<version>-windows-arm64.zip |
Each release also contains dnp3ctl-<version>-checksums.txt (SHA-256) so you can verify your download. The commands below fetch the latest release, verify its checksum and install it. Afterwards, check that it works:
dnp3ctl version# 1. Find the latest version and your CPU architecture
VERSION=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/otfabric/dnp3ctl/releases/latest | sed 's|.*/v||')
case "$(uname -m)" in
x86_64) ARCH=amd64 ;;
aarch64|arm64) ARCH=arm64 ;;
armv7l) ARCH=armv7 ;;
*) echo "Unsupported architecture: $(uname -m)" >&2; exit 1 ;;
esac
# 2. Download the archive and the checksum file
FILE="dnp3ctl-${VERSION}-linux-${ARCH}.tar.gz"
BASE="https://github.com/otfabric/dnp3ctl/releases/download/v${VERSION}"
curl -fsSLO "${BASE}/${FILE}"
curl -fsSLO "${BASE}/dnp3ctl-${VERSION}-checksums.txt"
# 3. Verify the download (should print "OK")
grep "${FILE}" "dnp3ctl-${VERSION}-checksums.txt" | sha256sum -c -
# 4. Extract and install (the binary in the archive carries the version and platform in its name)
rm -rf dnp3ctl-dl && mkdir -p dnp3ctl-dl && tar -xzf "${FILE}" -C dnp3ctl-dl
sudo install -m 0755 "dnp3ctl-dl/dnp3ctl-${VERSION}-linux-${ARCH}" /usr/local/bin/dnp3ctlNo sudo? Install into your home directory instead (and make sure ~/.local/bin is in your PATH):
mkdir -p ~/.local/bin
install -m 0755 "dnp3ctl-dl/dnp3ctl-${VERSION}-linux-${ARCH}" ~/.local/bin/dnp3ctlTo use a serial port without root, add yourself to the group that owns it (dialout on Debian and Ubuntu, uucp on Arch), then log in again.
# 1. Find the latest version and your CPU architecture (arm64 = Apple Silicon, x86_64 = Intel)
VERSION=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/otfabric/dnp3ctl/releases/latest | sed 's|.*/v||')
case "$(uname -m)" in
arm64) ARCH=arm64 ;;
x86_64) ARCH=amd64 ;;
*) echo "Unsupported architecture: $(uname -m)" >&2; exit 1 ;;
esac
# 2. Download the archive and the checksum file
FILE="dnp3ctl-${VERSION}-darwin-${ARCH}.tar.gz"
BASE="https://github.com/otfabric/dnp3ctl/releases/download/v${VERSION}"
curl -fsSLO "${BASE}/${FILE}"
curl -fsSLO "${BASE}/dnp3ctl-${VERSION}-checksums.txt"
# 3. Verify the download (should print "OK")
grep "${FILE}" "dnp3ctl-${VERSION}-checksums.txt" | shasum -a 256 -c -
# 4. Extract and install (the binary in the archive carries the version and platform in its name)
rm -rf dnp3ctl-dl && mkdir -p dnp3ctl-dl && tar -xzf "${FILE}" -C dnp3ctl-dl
sudo install -m 0755 "dnp3ctl-dl/dnp3ctl-${VERSION}-darwin-${ARCH}" /usr/local/bin/dnp3ctlIf you downloaded the archive with a web browser instead of curl and macOS refuses to run the binary ("cannot be verified"), remove the quarantine flag: xattr -d com.apple.quarantine /usr/local/bin/dnp3ctl.
# 1. Find the latest version and your CPU architecture
$version = (Invoke-RestMethod https://api.github.com/repos/otfabric/dnp3ctl/releases/latest).tag_name.TrimStart('v')
$arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'amd64' }
# 2. Download the archive and the checksum file
$file = "dnp3ctl-$version-windows-$arch.zip"
$base = "https://github.com/otfabric/dnp3ctl/releases/download/v$version"
Invoke-WebRequest "$base/$file" -OutFile "$env:TEMP\$file"
Invoke-WebRequest "$base/dnp3ctl-$version-checksums.txt" -OutFile "$env:TEMP\dnp3ctl-checksums.txt"
# 3. Verify the download (throws an error on mismatch)
$expected = ((Select-String -Path "$env:TEMP\dnp3ctl-checksums.txt" -Pattern $file).Line -split '\s+')[0]
$actual = (Get-FileHash "$env:TEMP\$file" -Algorithm SHA256).Hash
if ($expected -ne $actual) { throw "Checksum mismatch for $file" }
# 4. Extract to a per-user folder and name the program dnp3ctl.exe
# (the .exe in the archive carries the version and platform in its name)
$dir = "$env:LOCALAPPDATA\Programs\dnp3ctl"
New-Item -ItemType Directory -Force -Path $dir | Out-Null
Remove-Item -Recurse -Force "$env:TEMP\dnp3ctl-dl" -ErrorAction SilentlyContinue
Expand-Archive "$env:TEMP\$file" -DestinationPath "$env:TEMP\dnp3ctl-dl" -Force
Copy-Item "$env:TEMP\dnp3ctl-dl\dnp3ctl-$version-windows-$arch.exe" "$dir\dnp3ctl.exe" -Force
# 5. Add dnp3ctl to your user PATH (then open a new terminal)
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
if ($userPath -notlike "*$dir*") {
[Environment]::SetEnvironmentVariable('Path', "$userPath;$dir", 'User')
}If Windows SmartScreen warns about an unrecognised app, choose More info → Run anyway.
- Open the latest release.
- Download the archive for your platform from the table above.
- Extract it, rename the binary to
dnp3ctl(dnp3ctl.exeon Windows) and move it to a folder on yourPATH.
Run the same commands again. They always fetch the latest release, unpack it into an emptied dnp3ctl-dl folder and overwrite the previous binary.
Requires Go 1.25 or later:
git clone https://github.com/otfabric/dnp3ctl.git
cd dnp3ctl
make buildmake build runs make check first, which needs staticcheck, golangci-lint and govulncheck on your PATH, and writes the binary to bin/dnp3ctl (see Building). Without those tools, go build -o bin/dnp3ctl . builds it alone. With a Go toolchain you can also install straight from the module (dnp3ctl version then reports the version the go toolchain records: the module version with go install, such as v0.6.3, and with a plain go build in a clone the version it derives from the git tags, such as v0.6.3+dirty, with the commit; dev when none is recorded, as in a copy without git):
go install github.com/otfabric/dnp3ctl@latestDNP3 (IEEE 1815) carries SCADA data between a master (the control centre, a SCADA front end: the client) and outstations (RTUs, IEDs, gateways: the servers), over TCP and UDP on port 20000, over TLS on port 19999, or over serial lines.
| Term | Meaning | In dnp3ctl |
|---|---|---|
| Link address | Every station has one, 0-65519. A frame names the station it is for and the one it is from: an outstation ignores frames for another address, and many answer only the masters they are configured for. 65532 is the self address, which some outstations accept whatever their own address | --outstation (default 10), --master (default 1) |
| Object (group and variation) | What a value is, and how it is encoded: group 1 binary inputs, 3 double-bit inputs, 10 binary outputs, 20 counters, 21 frozen counters, 30 analog inputs, 40 analog outputs, 110 octet strings; the event groups 2, 4, 11, 22, 23, 32, 42, 111. The variation selects the encoding: g30v5 is an analog input as a single-precision float with flags, g30v0 "any variation" |
--object, client objects |
| Index | The number of a point within its type | --index, --range |
| Flags | Quality: ONLINE, RESTART, COMM_LOST, REMOTE_FORCED, LOCAL_FORCED, and per type OVER_RANGE, CHATTER_FILTER, … A point without ONLINE is not valid |
shown with every value |
| Classes | Class 0 is the current value of every point (static data). Classes 1, 2 and 3 are event classes: a point assigned to one produces an event, with a time stamp, each time it changes. An outstation keeps events in a buffer per master until that master has read and confirmed them | client poll --mode, client assign-class |
| Integrity poll | A READ of classes 1, 2, 3 and 0: all pending events, then all current values. What a master does after connecting | client poll |
| Unsolicited responses | Events the outstation sends by itself, when the master has enabled them for their class | client monitor, client unsolicited |
| IIN | Internal indications, two octets in every response: DEVICE_RESTART, NEED_TIME, events pending per class, EVENT_BUFFER_OVERFLOW, and the error bits NO_FUNC_CODE_SUPPORT, OBJECT_UNKNOWN, PARAMETER_ERROR with which an outstation refuses a request |
shown with poll results |
| Select before operate | A control in two steps: SELECT, which the outstation answers with whether it would execute it, then OPERATE with exactly the same control. A CROB (control relay output block) operates a binary output; an analog output block sets a set-point | client operate --mode sbo |
| Secure Authentication | SAv5: the outstation challenges critical requests (controls, writes, restarts, …) and executes them only when the master answers with a MAC over the request, made with a key both share | --sa-key-file, DNP3CTL_SA_KEY |
There are no names on the wire: an outstation does not tell you that analog input 0 is "Voltage L1". The point list of the outstation is where indexes get their meaning.
dnp3ctl [command] [flags]
dnp3ctl client poll
dnp3ctl client read
dnp3ctl client monitor
dnp3ctl client operate
dnp3ctl client time-sync
dnp3ctl client restart
dnp3ctl client unsolicited enable|disable
dnp3ctl client assign-class
dnp3ctl client freeze
dnp3ctl client attributes
dnp3ctl client write-attribute
dnp3ctl client write-deadband
dnp3ctl client manage
dnp3ctl client clear-restart
dnp3ctl client ping
dnp3ctl client file ls|info|get|put|rm
dnp3ctl client objects
dnp3ctl server start
dnp3ctl server generate-config
dnp3ctl discover
dnp3ctl versionEvery command has --help, with a description and examples. Some have a shorter name as well: client integrity (poll), control (operate), time (time-sync), attrs or info (attributes), and client file list or dir (ls), stat (info), download (get), upload (put), delete or del (rm).
Global flag: --debug — trace every link frame and application fragment sent and received to stderr, decoded, and print the chain of causes after an error.
Output format (--format): every command that prints a result accepts --format text (default), json or table, or set DNP3CTL_FORMAT. It controls only the result on stdout (server generate-config --format picks yaml or json for the model instead: it writes a model file, not a result, and its errors are always text). Progress, connection state, summaries and the debug trace go to stderr, so stdout can be piped or parsed as it is. The JSON shapes are a stable contract: see docs/json-output.md.
Streams: client monitor, client ping and server start write one record as it happens: one line (text), one JSON object per line (json, JSON Lines) or one table row (table). They run until interrupted with Ctrl-C, which ends them normally; client ping stops after --count measurements (default 4; --count 0 runs until interrupted), client monitor after --count values or --duration seconds when given, server start after --duration.
Fatal errors: with --format json a command writes a single JSON error object to stdout on failure, {"error": {"kind", "code", "message"}}; with text or table it writes a single error: line to stderr and leaves stdout empty. A usage error (exit code 2: an unknown command or flag, a missing required flag, a value a flag cannot hold) follows --format too, given on the command line or, for a command that has --format, in DNP3CTL_FORMAT: the same JSON error object with "kind": "usage" and "code": "invalid_input". Exit codes are stable:
| Code | Meaning |
|---|---|
| 0 | Success |
| 2 | Usage: unknown command or flag, missing required flag, a value a flag cannot hold |
| 3 | Invalid input: nothing was sent |
| 4 | Transport: cannot connect, connection lost, TLS failure |
| 5 | Timeout: no answer |
| 6 | The outstation refused the request (IIN error, control status, file status, authentication), or a protocol error |
| 7 | Partial success: a valid result that holds failures |
| 10 | Output or internal error |
| 130 | Interrupted before a result |
See docs/exitcodes.md for the details and the code values.
Shell completion: dnp3ctl completion bash (or zsh, fish, powershell) prints a completion script; for example source <(dnp3ctl completion bash). Flags with a fixed set of values complete to those values, --object to the objects dnp3ctl knows.
Every dnp3ctl client command takes the outstation as a URL, as an address and a port, or, when the outstation connects to the master, the address to wait on. The three styles are mutually exclusive on the command line, and one of them is required. A style on the command line wins over the environment: with --ip given, DNP3CTL_URL and DNP3CTL_MASTER_LISTEN are ignored.
| Flags | Connects to |
|---|---|
--url tcp://host[:port] |
DNP3 over TCP; port 20000 when omitted |
--url tls://host[:port] |
DNP3 over TLS (IEEE 1815-2012 Annex B, IEC 62351-3); port 19999 when omitted |
--url udp://host[:port] |
DNP3 over UDP; port 20000 when omitted. dnp3ctl sends from any free port unless --udp-local-port names one, for outstations that answer only a configured master endpoint (often port 20000) |
--url serial:///dev/ttyUSB0 |
A serial line (serial://COM3 on Windows), with --baud (default 9600), --data-bits (8), --parity (none, even, odd) and --stop-bits (1) |
--ip host [--port n] |
TCP on port 20000 or n; TLS when a --tls… flag is given |
--listen host[:port] |
Waits for the outstation to connect: the dual end point (IEEE 1815-2012 annex A.3), for outstations behind a firewall or on a dial-out link. Port 20000 or --port when --listen names none (:20000 listens on every interface); TLS when a --tls… flag is given |
The outstation connects (dual end point). With --listen dnp3ctl opens the port, waits up to --timeout (default 10 s) for the outstation to connect, and closes the port when the command ends; client monitor waits for the outstation again whenever the connection is lost. The result's target is the address the outstation connected from. Over TLS dnp3ctl is the TLS server: --tls-cert and --tls-key are its server certificate, and --tls-ca requires the outstation to present a certificate of that CA (--tls-server-name and --tls-insecure do not apply). An outstation that does not connect in time ends the command with exit code 4. See SECURITY.md.
# Wait on port 20000 for the outstation, then run an integrity poll
dnp3ctl client poll --listen :20000
# The same over TLS; the outstation must present a certificate of ca.pem
dnp3ctl client monitor --listen 10.0.0.1:19999 --tls-cert scada.pem --tls-key scada.key --tls-ca ca.pemLink addresses. --outstation is the link address of the outstation (default 10) and --master the one of dnp3ctl (default 1). Both must match what the outstation is configured with: an outstation does not answer frames for another address, and many answer only the masters they know. A request that times out (exit code 5) against an outstation that accepts the connection is most often a wrong --outstation; dnp3ctl discover --subnets <host> --outstations 1,2,3,4,10 finds the right one.
Timeouts and retries. --timeout bounds connecting and each request, in milliseconds (default 10000); --response-timeout the wait for each fragment of a response (default 5000). --retries n repeats a request that timed out or lost its connection up to n times, with backoff, but only for requests that are safe to repeat: reads, polls, time synchronization, unsolicited control, assign class and file reads. Controls, freezes, restarts and writes are never repeated.
Link layer. --link-confirmed sends user data as CONFIRMED_USER_DATA (unconfirmed is the default, as IEEE 1815-2012 recommends on TCP); --link-timeout and --link-retries govern the link layer answers; --keep-alive the idle time after which REQUEST_LINK_STATUS is sent (default 60 s).
TLS
| Flag | Meaning |
|---|---|
--tls |
Use TLS with --ip (a tls:// URL implies it) |
--tls-ca |
PEM file with the CA certificates to verify the outstation with; the system roots by default |
--tls-cert, --tls-key |
PEM files with the client certificate and its key, for an outstation that authenticates its masters |
--tls-server-name |
The name to verify the outstation's certificate against, when it is not the host you connect to |
--tls-insecure |
Do not verify the outstation's certificate. For test setups only |
dnp3ctl client poll --url tls://rtu.example.net --tls-ca ca.pem --tls-cert scada.pem --tls-key scada.keySecure Authentication v5. An outstation that runs SAv5 challenges critical requests. Give dnp3ctl its update key, with which dnp3ctl negotiates the session keys (session key change), or, for an outstation without key change, a pre-shared session key. Keys are hex, in a file or in the environment; they are never accepted on the command line, where they would end up in the shell history and the process list. Give one kind of key, not both.
| Flag / variable | Meaning |
|---|---|
--sa-update-key-file |
File holding the update key as hex (whitespace and a 0x prefix are ignored); 16 octets (AES-128 key wrap) or 32 (AES-256) |
DNP3CTL_SA_UPDATE_KEY |
The update key as hex, instead of a file |
--sa-key-change-interval |
Seconds between session key changes while connected (default 900); with an update key only |
--sa-key-file |
File holding a pre-shared session key as hex; at least 16 octets |
DNP3CTL_SA_KEY |
The pre-shared session key as hex, instead of a file |
--sa-user |
User number (default 1) |
--sa-mac |
hmac-sha256-16 (default), hmac-sha256-8, hmac-sha1-10 or hmac-sha1-8 |
--sa-aggressive |
Aggressive mode: once the outstation has challenged one request, send critical requests with their MAC, without waiting for a challenge (in client operate --mode sbo, the OPERATE after the SELECT). Needs a key |
dnp3ctl client operate --ip 192.168.1.20 --type crob --index 0 --op latch-on --sa-update-key-file /etc/dnp3/outstation10.update.key
# The security statistics the outstation keeps for this master (g121v1), by name
dnp3ctl client read --ip 192.168.1.20 --object g121v1# Integrity poll: all pending events, then every current value
dnp3ctl client poll --ip 192.168.1.20
# The events of class 1 only
dnp3ctl client poll --ip 192.168.1.20 --mode event --class 1
# The static data, analog inputs 0-9 only, as a table
dnp3ctl client poll --ip 192.168.1.20 --mode class --class 0 --object g30 --index 0-9 --format tableA poll reads events, and an outstation discards what a master has read and confirmed: use a link address of your own (--master) if the outstation keeps a buffer per master, or poll only what you need. When the outstation answers with values and also sets an IIN error bit, the values are listed with the error and the exit code is 7.
# All analog inputs as single-precision floats with flags
dnp3ctl client read --ip 192.168.1.20 --object g30v5
# Binary inputs 0-15, in the variation the outstation chooses
dnp3ctl client read --ip 192.168.1.20 --object g1 --range 0-15
# Counters 0, 3 and 7 and the binary outputs, in one request
dnp3ctl client read --ip 192.168.1.20 --object g20,g10 --range 0,3,7
# At most 10 analog input events, with their time stamps
dnp3ctl client read --ip 192.168.1.20 --object g32 --count 10
# The frozen counters after a freeze
dnp3ctl client read --ip 192.168.1.20 --object g21v5
# The deadbands of the analog inputs (g34)
dnp3ctl client read --ip 192.168.1.20 --object g34v1
# Times and intervals, such as a schedule (g50v4: not in class 0)
dnp3ctl client read --ip 192.168.1.20 --object g50v4
# BCD and unsigned integer points (g101, g102)
dnp3ctl client read --ip 192.168.1.20 --object g101v1,g102v1dnp3ctl client objects lists every object and variation (--group 30 the variations of one group, --events the event objects only); --object completes to them in the shell.
# Everything the outstation reports, starting with an integrity poll, until Ctrl-C
dnp3ctl client monitor --ip 192.168.1.20 --integrity
# Analog input events only, as JSON Lines, into a file for one minute
dnp3ctl client monitor --ip 192.168.1.20 --object g32 --duration 60 --format json > events.jsonl
# An outstation without unsolicited responses: poll its events every 5 seconds
dnp3ctl client monitor --ip 192.168.1.20 --unsolicited none --poll-interval 52026-10-20T09:15:17.284Z unsol g2v2 binary-input 1 on [ONLINE] @ 2026-10-20T09:15:17.281Z
2026-10-20T09:15:19.102Z unsol g32v7 analog-input 0 11.03 [ONLINE] @ 2026-10-20T09:15:19.100ZThe monitor enables unsolicited responses for classes 1-3 (--unsolicited) after connecting, after every reconnect and after the outstation reports a restart (which makes it forget them), clears the outstation's restart indication, and reconnects when the connection is lost unless --no-reconnect is set.
An outstation takes a second connection from the same master link address for that master reconnecting, and drops the first. To run other commands while a monitor is connected, give them another --master (the outstation must answer it).
# Close a breaker with select-before-operate (the default mode)
dnp3ctl client operate --ip 192.168.1.20 --type crob --index 0 --op latch-on
# Trip it with a direct operate and a 500 ms pulse
dnp3ctl client operate --ip 192.168.1.20 --type crob --index 0 --op trip --on-time 500 --mode direct
# Pulse an output three times: 200 ms on, 800 ms off
dnp3ctl client operate --ip 192.168.1.20 --type crob --index 4 --op pulse-on --count 3 --on-time 200 --off-time 800
# Several controls in one request: three outputs, or two set-points
dnp3ctl client operate --ip 192.168.1.20 --type crob --index 0,1,4 --op latch-on --mode direct
dnp3ctl client operate --ip 192.168.1.20 --type analog --index 0,1 --value 11.2,230
# Set-points: a float, and a 16-bit integer without waiting for an answer
dnp3ctl client operate --ip 192.168.1.20 --type analog --index 0 --value 11.2
dnp3ctl client operate --ip 192.168.1.20 --type analog --index 1 --value 1200 --variation 2 --mode direct-no-ackOperate (sbo) g12v1 index 0: LATCH_ON on outstation 10 at tcp://192.168.1.20:20000: SUCCESS in 4.2 ms--op |
CROB | Typical use |
|---|---|---|
latch-on, latch-off |
LATCH_ON, LATCH_OFF | an output that stays where it is put |
pulse-on, pulse-off |
PULSE_ON, PULSE_OFF with --on-time, --off-time, --count |
a momentary output |
close, trip |
PULSE_ON with trip-close code CLOSE or TRIP | a breaker with paired close and trip outputs on one index |
nul |
NUL | test the path without operating anything |
With several indexes the controls go in one request and the result lists a status for each. When a direct operate is refused for some of them, the others were executed: the result shows every status and the exit code is 7. With select-before-operate a refused SELECT ends the operation, so a refusal exits 6 and names every status.
A control the outstation refuses ends with exit code 6 and the status it echoed (NO_SELECT, LOCAL, NOT_SUPPORTED, OUT_OF_RANGE, …). Everything is validated before anything is sent, and a control is sent exactly once, also with --retries: after a timeout (exit code 5) it may or may not have been executed.
# Synchronize the clock: LAN procedure, non-LAN procedure on a serial line, or a fixed time
dnp3ctl client time-sync --ip 192.168.1.20
dnp3ctl client time-sync --url serial:///dev/ttyUSB0 --baud 19200 --method non-lan
dnp3ctl client time-sync --ip 192.168.1.20 --method write --time 2026-01-31T12:00:00Z
# Restart the DNP3 application, or the whole outstation
dnp3ctl client restart --ip 192.168.1.20 --type warm
dnp3ctl client restart --ip 192.168.1.20 --type cold
# Enable unsolicited responses for classes 1 and 2; disable them all
dnp3ctl client unsolicited enable --ip 192.168.1.20 --class 1,2
dnp3ctl client unsolicited disable --ip 192.168.1.20
# Put analog inputs 0-7 in class 2
dnp3ctl client assign-class --ip 192.168.1.20 --class 2 --object g30 --range 0-7
# Freeze all counters; freeze and clear counters 0-3
dnp3ctl client freeze --ip 192.168.1.20
dnp3ctl client freeze --ip 192.168.1.20 --object g20 --range 0-3 --clear
# Freeze at midnight UTC on 1 January 2027 and every hour from then on (FREEZE_AT_TIME)
dnp3ctl client freeze --ip 192.168.1.20 --at 2027-01-01T00:00:00Z --interval 1h# Freeze the counters of every outstation on a serial line at the same moment
dnp3ctl client freeze --url serial:///dev/ttyUSB0 --broadcast no-confirm --no-ack
# Trip output 3 of every outstation, without answers
dnp3ctl client operate --url serial:///dev/ttyUSB0 --type crob --index 3 --op trip --mode direct-no-ack --broadcast no-confirm--broadcast sends the request to every outstation on the channel at one of the three broadcast addresses (no-confirm, confirm or optional: whether the outstations ask for an application confirmation). Nobody answers a broadcast, so the result reports the request as sent; each outstation executes it and sets IIN1.0 BROADCAST in its next response, which a poll shows. Controls need --mode direct or direct-no-ack: select-before-operate needs an answer.
A broadcast cannot be combined with Secure Authentication, and dnp3ctl refuses the combination (exit code 3). An outstation running SAv5 discards a critical request sent to a broadcast address (a control, a freeze) unless it carries an aggressive mode MAC. That MAC covers an earlier challenge of the same connection, and every dnp3ctl command opens a connection of its own, so its broadcast would be discarded without a word.
# Save the configuration, or stop the outstation's applications
dnp3ctl client manage --ip 192.168.1.20 --function save-configuration
dnp3ctl client manage --ip 192.168.1.20 --function stop-application
# Clear DEVICE_RESTART after a restart (the one-shot commands leave it set)
dnp3ctl client clear-restart --ip 192.168.1.20
# Write the deadband of analog inputs 0-2, and read it back
dnp3ctl client write-deadband --ip 192.168.1.20 --index 0,1,2 --value 0.5
dnp3ctl client read --ip 192.168.1.20 --object g34 --range 0-2# Manufacturer, product, versions, serial number, point counts, …
dnp3ctl client attributes --ip 192.168.1.20
dnp3ctl client attributes --ip 192.168.1.20 --variation serial-number
# Which attributes does it support, and which are writable?
dnp3ctl client attributes --ip 192.168.1.20 --variation list
# An attribute set the outstation defines, and writing one of its attributes
dnp3ctl client attributes --ip 192.168.1.20 --set 1
dnp3ctl client write-attribute --ip 192.168.1.20 --set 1 --variation 2 --value 42 --data-type uint
# Name the location of the outstation (a user-assigned attribute of set 0)
dnp3ctl client write-attribute --ip 192.168.1.20 --variation location --value "Substation North"
# Four delay measurements, one per second
dnp3ctl client ping --ip 192.168.1.20# What does the outstation offer?
dnp3ctl client file ls --ip 192.168.1.20
dnp3ctl client file ls --ip 192.168.1.20 --path /records
dnp3ctl client file info --ip 192.168.1.20 --path /records/disturbance-0001.dat
# Fetch a file, or pipe it
dnp3ctl client file get --ip 192.168.1.20 --path /records/disturbance-0001.dat
dnp3ctl client file get --ip 192.168.1.20 --path /logs/events.csv --output - | tail
# Write, append to and delete a file
dnp3ctl client file put --ip 192.168.1.20 --input settings.xml --path /config/settings.xml
dnp3ctl client file put --ip 192.168.1.20 --input today.csv --path /logs/events.csv --append
dnp3ctl client file rm --ip 192.168.1.20 --path /config/old.xml
# An outstation that wants credentials first (the password is never a flag)
DNP3CTL_FILE_PASSWORD=… dnp3ctl client file ls --ip 192.168.1.20 --file-user engineerfile ls lists / without --path. file get writes to the base name of --path in the current directory, or to --output (- for stdout), and does not overwrite an existing file without --force. file put --permissions sets the permissions of the new file (octal 644 or rw-r--r--, the default). --block-size limits the blocks asked for or sent (by default the outstation chooses), and --file-password-file reads the password of --file-user from a file.
# Outstations at link address 10 (or that accept the self address) on a subnet
dnp3ctl discover --subnets 192.168.1.0/24
# Try several link addresses
dnp3ctl discover --subnets 10.0.1.0/24 --outstations 1,2,3,4,10,65532 --format tableThe probe sends one link layer REQUEST_LINK_STATUS per link address and waits for LINK_STATUS: no application layer request is made, nothing is read and no event is taken.
# The built-in demo outstation on port 20000
dnp3ctl server start
# Write its model (as YAML with comments, or --format json), edit it, serve it
dnp3ctl server generate-config --output outstation.yaml
dnp3ctl server start --config outstation.yaml
# On another port, on localhost only, events as JSON Lines
dnp3ctl server start --config outstation.yaml --listen 127.0.0.1 --port 20001 --format json
# TLS for two masters only; UDP; a serial line
dnp3ctl server start --config outstation.yaml --tls-cert server.pem --tls-key server.key --tls-client-ca ca.pem --allow 10.0.0.5,10.0.0.6
dnp3ctl server start --config outstation.yaml --udp
dnp3ctl server start --config outstation.yaml --serial /dev/ttyUSB0 --baud 19200
# With Secure Authentication v5
dnp3ctl server start --config outstation.yaml --sa-update-key-file outstation.update.key
# Several outstations behind one port (multi-drop), each at the link address of its model
dnp3ctl server start --config rtu10.yaml --config rtu11.yaml --config rtu12.yaml
dnp3ctl client poll --ip 127.0.0.1 --outstation 11
# Connect to the master instead of listening (dual end point), over TLS
dnp3ctl server start --config outstation.yaml --dial tls://scada.example.net --tls-ca ca.pem --tls-cert rtu.pem --tls-key rtu.keyThe outstation is described by a model: its link address, its points with their initial values and event classes, its controls and its files.
outstation:
name: Demo feeder RTU
address: 10
unsolicited: {enabled: true}
restart_delay: 2s
attributes: {manufacturer: otfabric, product: dnp3ctl demo outstation}
points:
- {type: binary-input, index: 0, name: Breaker closed, value: true, class: 1}
- {type: binary-output, index: 0, name: Breaker control status, value: true, class: 1}
- type: analog-input
index: 0
name: Voltage L1 (kV)
value: 11.02
class: 2
deadband: 0.01
static_variation: 5
simulate: {mode: sine, interval: 2s, min: 10.9, max: 11.1}
- {type: counter, index: 0, name: Energy (kWh), value: 123456, class: 3}
controls:
- {type: crob, index: 0, name: Breaker, target: {type: binary-output, index: 0}, select_required: true}
- {type: crob, index: 2, name: Local panel, status: LOCAL}
- {type: analog, index: 0, name: Voltage set-point, min: 10, max: 12}
files:
- {name: /records/disturbance-0001.dat, size: 8192}
uploads:
accept: trueWhat it does:
- Answers integrity, event and class polls and reads of every point type, in any variation; keeps an event buffer per master that survives reconnects and is confirmed by the master.
- Reports unsolicited the events of the classes a master enables, with confirmation and retries.
- Executes controls on the point named as their
target, with select-before-operate, direct operate and direct operate without acknowledgement.select_requiredrefuses a direct operate with NO_SELECT;min/maxrefuse a set-point with OUT_OF_RANGE;statusanswers every control with a refusal of your choice, to test a master. An output withcommand_event_classreports every executed control as a command event (g13, g43) with the commanded value and the status. - Serves times and intervals (
time-and-intervalpoints, g50v4): a time with a repeat interval, such as a schedule, read by group and variation. - Changes values by itself where the model says
simulate(toggle,ramp,sine,random,count), producing events of the point's class with the outstation's time. - Answers time synchronization, delay measurement, cold and warm restarts (with
restart_delay), assign class, freezes (also scheduled with FREEZE_AT_TIME), reads of deadbands (g34), device attributes of set 0 and of furtherattribute_sets(with writable variations), writes of deadbands, the management functions (accepted, or refused whererefused_functionslists them), broadcasts, and file transfer with uploads and appends.indicationsin the model (local-control,device-trouble,config-corrupt) sets LOCAL_CONTROL, DEVICE_TROUBLE or CONFIG_CORRUPT, to test how a master reacts. - With Secure Authentication challenges critical requests, accepts aggressive mode, and keeps the security statistics per master (g121, events g122 in class 1 by default).
Every event is written to stdout as it happens:
2026-10-20T00:06:21.028Z listening tcp://127.0.0.1:20000 Demo feeder RTU, link address 10
2026-10-20T00:06:21.668Z connected 127.0.0.1:60893
2026-10-20T00:06:21.668Z control SELECT g12v1 index=0 LATCH_OFF SUCCESS master 1
2026-10-20T00:06:21.668Z request 127.0.0.1:60893 SELECT 0.0 ms
2026-10-20T00:06:21.668Z control OPERATE g12v1 index=0 LATCH_OFF SUCCESS select-before-operate, master 1, binary-output 0 = false
2026-10-20T00:06:21.668Z request 127.0.0.1:60893 OPERATE 0.1 ms
2026-10-20T00:06:21.669Z disconnected 127.0.0.1:60893 dnp3: connection lost: closed by peerSeveral outstations. Give --config once per outstation (or comma-separated, or in DNP3CTL_CONFIG) and they are served together, behind the same port, serial line or UDP socket, as outstations on a multi-drop line are: each answers the requests to the link address of its model, keeps its own points, event buffers and controls, and a broadcast reaches all of them. Two models with the same link address are refused. Events of one outstation name its link address ([11] in text, outstation in JSON, the OUTSTATION column of the table); listening lists them all. --dial and --udp-peer serve a single outstation.
| Flag | Meaning |
|---|---|
--config |
The model file (YAML or JSON). Without it the built-in demo outstation is served. Repeat it (or separate files with commas) to serve several outstations, each at the link address of its model |
--listen, --port |
Address and port to listen on. All interfaces and 20000 by default (19999 with TLS) |
--dial |
Connect to the master instead of listening, at tcp://host[:port] or tls://host[:port] (the dual end point), and connect again whenever the connection ends, after 1 s and up to 1 min between attempts. The event log starts with dialing |
--udp |
Serve UDP instead of TCP. The outstation answers the endpoint the last datagram came from, so it serves one master at a time. The event log says connected when the first datagram arrives, naming its sender as remote; a socket no master sent to logs neither connected nor disconnected |
--udp-peer |
With --udp: the master's address (host:port). Unsolicited responses reach it before it has sent anything, and connected names it at once; without it they start once a master has sent a request |
--serial, --baud, --data-bits, --parity, --stop-bits |
Serve on a serial port instead of the network |
--tls-cert, --tls-key |
Serve TLS with this certificate |
--tls-client-ca |
Require a client certificate of this CA (mutual TLS) |
--tls-ca |
With a tls:// --dial: verify the master's certificate against this CA (the system roots by default); --tls-cert and --tls-key are then the certificate presented to the master, and optional |
--allow |
Only accept clients from these addresses or CIDR prefixes (TCP and TLS) |
--max-sessions |
Maximum number of simultaneous connections (default 16) |
--sa-reply-timeout, --sa-statistics-class, --sa-statistics-threshold |
With secure authentication: how long a challenge waits for its reply (default 10000 ms), and the class (default 1) and threshold (default 10 increments) of security statistic events |
--sa-update-key-file, --sa-key-file, --sa-user, --sa-mac |
Require Secure Authentication v5, with session key change by this update key, or with this pre-shared session key (or DNP3CTL_SA_UPDATE_KEY, DNP3CTL_SA_KEY) |
--link-confirmed, --link-timeout, --link-retries, --keep-alive |
As for the client |
--duration |
Stop after this many seconds, for use in test pipelines |
--format |
The event log on stdout: text (default), json (JSON Lines) or table |
server generate-config writes to stdout, or to --output, which it does not overwrite without --force.
The full reference of the model is in docs/server-config.md. The server listens on all interfaces and accepts any client unless told otherwise: see SECURITY.md.
Every flag can also be set through an environment variable, which is convenient for automation and for flags you would otherwise repeat on every command (DNP3CTL_URL, DNP3CTL_OUTSTATION, DNP3CTL_FORMAT). A flag on the command line wins over the environment. A value that cannot be parsed (a port that is not a number) stops dnp3ctl at startup. A flag that is required must still be given on the command line: --object (client read, assign-class), --type and --index (operate), --type (restart), --class (assign-class), --function (manage), --variation and --value (write-attribute), --index and --value (write-deadband), --path (client file info, get, put, rm; not ls, which lists /), --input (file put) and --subnets (discover). The same rule holds across connection styles: when a flag of one style (--url, --ip, --listen of a client; --dial, --serial, or --listen, --port, --udp, --udp-peer, --allow of server start) is on the command line, the environment variables of the other styles are ignored, so --ip works with DNP3CTL_URL set. Two styles both on the command line, or both in the environment, are refused as mutually exclusive. The global flag --debug has no environment variable.
Three secrets are read only from the environment or a file, never from a flag: DNP3CTL_SA_UPDATE_KEY and DNP3CTL_SA_KEY (the Secure Authentication keys, as hex; or --sa-update-key-file, --sa-key-file) and DNP3CTL_FILE_PASSWORD (the password of --file-user; or --file-password-file).
export DNP3CTL_URL=tcp://192.168.1.20:20000
export DNP3CTL_OUTSTATION=10
dnp3ctl client poll
dnp3ctl client read --object g30v5All DNP3CTL_* environment variables
| Variable | Type | Description | Flag | Default |
|---|---|---|---|---|
| DNP3CTL_ALLOW | strings | server start: Only accept TCP and TLS clients from these addresses or CIDR prefixes (default: any) | --allow |
|
| DNP3CTL_APPEND | bool | client file put: Add the content to the end of the file instead of replacing it (OPEN_FILE in APPEND mode) | --append |
|
| DNP3CTL_ASSIGN_CLASS | uint8 | client assign-class: Class to assign, 0-3 (0 = no events) | --class |
|
| DNP3CTL_ATTRIBUTE | string | client attributes: Attribute to read: all, list (the list of attribute variations), a variation number 1-254 or, in set 0, a name such as manufacturer; client write-attribute: Attribute to write: a variation number 1-253 or, in set 0, a name such as location | --variation |
all (attributes); required for write-attribute |
| DNP3CTL_ATTRIBUTE_SET | uint16 | client attributes, client write-attribute: Device attribute set: 0 is the standard set of IEEE 1815-2012, other sets are defined by the outstation | --set |
|
| DNP3CTL_ATTRIBUTE_TYPE | string | client write-attribute: Data type of the value: vstr (visible string), uint, int or float | --data-type |
vstr |
| DNP3CTL_ATTRIBUTE_VALUE | string | client write-attribute: Value to write | --value |
|
| DNP3CTL_BAUD | uint32 | every client command, server start: Serial baud rate (0 = 9600) | --baud |
|
| DNP3CTL_BLOCK_SIZE | uint16 | client file ls, client file get, client file put, client file rm: Largest block to ask for or send, in octets (0 = the outstation chooses) | --block-size |
|
| DNP3CTL_BROADCAST | string | client operate, client freeze: Send to every outstation on the channel instead of --outstation, at one of the broadcast addresses: no-confirm, confirm or optional (whether the outstations ask for an application confirmation); nobody answers, and each outstation sets IIN1.0 BROADCAST in its next response | --broadcast |
|
| DNP3CTL_CLASS | string | client poll: Classes for --mode event (1-3) or class (0-3), comma-separated (default 1,2,3); client unsolicited enable: Event classes, comma-separated, 1-3 (default all three) | --class |
|
| DNP3CTL_CONFIG | strings | server start: Outstation model file (YAML or JSON); without it a built-in demo outstation is served. Give it several times (or comma-separated) to serve several outstations, each at its own link address, behind the same port, serial line or UDP socket | --config |
|
| DNP3CTL_CONFIG_FORMAT | string | server generate-config: Format of the model: yaml or json | --format |
yaml |
| DNP3CTL_CONTROL_TYPE | string | client operate: Control: crob (control relay output block, g12v1) or analog (analog output block, g41) | --type |
|
| DNP3CTL_COUNT | uint32 | client read: Read at most this many objects (events); cannot be combined with --range; client monitor: Stop after this many values (0 = until interrupted); client ping: Number of delay measurements before exiting (0 = until interrupted) | --count |
4 (ping); 0 (read, monitor) |
| DNP3CTL_DATA_BITS | uint8 | every client command, server start: Serial data bits, 5-8 (0 = 8) | --data-bits |
|
| DNP3CTL_DEADBAND | string | client write-deadband: The deadband: a number of 0 or more | --value |
|
| DNP3CTL_DEADBAND_INDEX | string | client write-deadband: Analog inputs whose deadband is written: an index, or several comma-separated | --index |
|
| DNP3CTL_DEADBAND_VARIATION | uint8 | client write-deadband: Variation of g34: 1 (16-bit), 2 (32-bit) or 3 (single-precision float); 0 = 2 for whole numbers that fit, 3 otherwise | --variation |
|
| DNP3CTL_DIAL | string | server start: Connect to the master instead of listening (dual end point): tcp://host[:port] or tls://host[:port] (ports default to 20000, and 19999 for tls); reconnects whenever the connection ends | --dial |
|
| DNP3CTL_DISCOVER_FORCE_LARGE | bool | discover: Allow a scan of more hosts than the built-in safety cap | --force-large-scan |
|
| DNP3CTL_DURATION | uint32 | client monitor, server start: Stop after this many seconds (0 = until interrupted) | --duration |
|
| DNP3CTL_EVENTS | bool | client objects: Only list event objects | --events |
|
| DNP3CTL_FILE_PASSWORD_FILE | string | client file ls, client file get, client file put, client file rm: File holding the password of --file-user | --file-password-file |
|
| DNP3CTL_FILE_PATH | string | client file ls: Directory to list; client file info: File or directory on the outstation; client file get: File on the outstation; client file put: File to create on the outstation; client file rm: File to delete on the outstation | --path |
/ (file ls); required elsewhere |
| DNP3CTL_FILE_USER | string | client file ls, client file get, client file put, client file rm: User name to authenticate file operations with (AUTHENTICATE_FILE); the password comes from --file-password-file or DNP3CTL_FILE_PASSWORD | --file-user |
|
| DNP3CTL_FORCE | bool | client file get: Overwrite an existing --output file; server generate-config: Overwrite an existing file | --force |
|
| DNP3CTL_FREEZE_AT | string | client freeze: Freeze at this time instead of at once (FREEZE_AT_TIME): an RFC 3339 time such as 2026-01-31T12:00:00Z | --at |
|
| DNP3CTL_FREEZE_CLEAR | bool | client freeze: Reset the counters to zero after the freeze (FREEZE_CLEAR) | --clear |
|
| DNP3CTL_FREEZE_INTERVAL | string | client freeze: With --at: repeat the freeze at this interval, such as 15m or 1h (default: once) | --interval |
|
| DNP3CTL_GROUP | uint8 | client objects: Only list the variations of this group (0 = all groups) | --group |
|
| DNP3CTL_INDEX | string | client poll: Only show these point indexes: list and ranges (e.g. 0,5-9); client monitor: Only show these point indexes: list and ranges | --index |
|
| DNP3CTL_INPUT | string | client file put: Local file whose content is sent | --input |
|
| DNP3CTL_INTEGRITY | bool | client monitor: Run an integrity poll after connecting (and after every reconnect), so the stream starts with all current values | --integrity |
|
| DNP3CTL_INTERVAL | uint32 | client ping: Interval between measurements in milliseconds | --interval |
1000 |
| DNP3CTL_IP | string | Outstation IP address or host name, over TCP (used when --url is not set) | --ip |
|
| DNP3CTL_KEEP_ALIVE | uint32 | every client command, server start: Idle seconds after which REQUEST_LINK_STATUS is sent (0 = 60) | --keep-alive |
|
| DNP3CTL_LINK_CONFIRMED | bool | every client command, server start: Send user data as CONFIRMED_USER_DATA (default unconfirmed, as IEEE 1815-2012 recommends on TCP) | --link-confirmed |
|
| DNP3CTL_LINK_RETRIES | uint8 | every client command, server start: Repeats of an unanswered primary link frame | --link-retries |
|
| DNP3CTL_LINK_TIMEOUT | uint32 | every client command, server start: Wait in milliseconds for a link layer answer (0 = 2000) | --link-timeout |
|
| DNP3CTL_LISTEN | string | server start: Address to listen on (TCP, TLS and UDP) | --listen |
|
| DNP3CTL_MANAGE_FUNCTION | string | client manage: Management function: initialize-data, initialize-application, start-application, stop-application or save-configuration | --function |
|
| DNP3CTL_MASTER | uint16 | every client command: Link address of this master; discover: Link address the probe is sent from | --master |
1 |
| DNP3CTL_MASTER_LISTEN | string | Wait for the outstation to connect instead of connecting to it (dual end point): the address to listen on, host, host:port or :port (without a port in it, --port); TLS with --tls-cert and --tls-key; mutually exclusive with --url and --ip | --listen |
|
| DNP3CTL_MAX_SESSIONS | uint16 | server start: Maximum number of simultaneous connections (0 = 16) | --max-sessions |
|
| DNP3CTL_NO_ACK | bool | client freeze: Send the no-acknowledgement form (IMMED_FREEZE_NR, FREEZE_CLEAR_NR): no response | --no-ack |
|
| DNP3CTL_NO_RECONNECT | bool | client monitor: Exit when the connection is lost instead of reconnecting | --no-reconnect |
|
| DNP3CTL_OBJECT | string | client poll: Only show these objects: groups or group/variations, comma-separated (e.g. g30,g1v2,32); client read: Objects to read, comma-separated: g30v5 (or 30.5), or g30 / g30v0 for any variation; client monitor: Only show these objects: groups or group/variations, comma-separated; client assign-class: Static objects whose points are assigned, comma-separated (e.g. g30, g1); client freeze: Objects to freeze, comma-separated: g20 (counters) or g30 (analog inputs) | --object |
|
| DNP3CTL_OFF_TIME | uint32 | client operate: Off time of a pulse in milliseconds | --off-time |
|
| DNP3CTL_ON_TIME | uint32 | client operate: On time of a pulse in milliseconds | --on-time |
|
| DNP3CTL_OP | string | client operate: Operation of a crob: latch-on, latch-off, pulse-on, pulse-off, close (pulse on, trip-close code close), trip (pulse on, trip-close code trip) or nul | --op |
|
| DNP3CTL_OPERATE_INDEX | string | client operate: Point index of the output, or several comma-separated (0,1,4) for several controls in one request | --index |
|
| DNP3CTL_OPERATE_MODE | string | client operate: Procedure: sbo (select, then operate), direct (direct operate) or direct-no-ack (direct operate, no response) | --mode |
sbo |
| DNP3CTL_OP_COUNT | uint8 | client operate: Number of times a crob is executed | --count |
1 |
| DNP3CTL_OUTPUT | string | client file get: File to write the content to (- = stdout; default: the base name of --path); server generate-config: File to write the model to (- = stdout) | --output |
- (generate-config); base name of --path (file get) |
| DNP3CTL_FORMAT | string | (DNP3CTL_OUTPUT_FORMAT stays accepted) every client command, client objects, discover: Output format: text, json, or table; server start: Format of the event log on stdout: text, json, or table |
--format |
text |
| DNP3CTL_OUTSTATION | uint16 | Link address of the outstation | --outstation |
10 |
| DNP3CTL_OUTSTATIONS | strings | discover: Link addresses to send the probe to, comma-separated; 65532 is the self address, which outstations that support it answer whatever their own address | --outstations |
[10,65532] |
| DNP3CTL_PARALLEL | uint16 | discover: Number of concurrent probes (1-256) | --parallel |
32 |
| DNP3CTL_PARITY | string | every client command, server start: Serial parity: none, even or odd (default none) | --parity |
|
| DNP3CTL_PERMISSIONS | string | client file put: Permissions of the new file: octal (644) or rwx form (rw-r--r--) | --permissions |
|
| DNP3CTL_POLL_INTERVAL | uint32 | client monitor: Also poll the events of classes 1-3 every this many seconds (0 = do not poll) | --poll-interval |
|
| DNP3CTL_POLL_MODE | string | client poll: What to read: integrity (events of classes 1-3, then the static data of class 0), event (events of --class) or class (exactly the classes of --class, 0 included) | --mode |
integrity |
| DNP3CTL_PORT | uint16 | every client command: Outstation TCP port (used when --url is not set), or the port of --listen; server start: Port to listen on (0 = 20000, or 19999 with TLS) | --port |
20000 (client); server start: 20000, 19999 with TLS |
| DNP3CTL_PORTS | strings | discover: TCP ports to probe | --ports |
[20000] |
| DNP3CTL_RANGE | string | client read: Point indexes to read: a range (0-9), a list (1,4,7) or empty for all points; client assign-class, client freeze: Point indexes: a range (0-9), a list (1,4,7) or empty for all points | --range |
|
| DNP3CTL_RESPONSE_TIMEOUT | uint32 | Wait in milliseconds for each fragment of a response (0 = 5000) | --response-timeout |
|
| DNP3CTL_RESTART_TYPE | string | client restart: Restart: cold (full restart of the outstation) or warm (restart of the DNP3 application) | --type |
|
| DNP3CTL_RETRIES | uint8 | Retries of a request that timed out or lost its connection; only for requests that are safe to repeat (reads, time, unsolicited control, assign class, file reads), never for controls, freezes, restarts or writes | --retries |
|
| DNP3CTL_SA_AGGRESSIVE | bool | Send critical requests in Secure Authentication aggressive mode, with their MAC instead of waiting for a challenge, once the outstation has challenged one; needs a key | --sa-aggressive |
|
| DNP3CTL_SA_KEY_CHANGE_INTERVAL | uint32 | Seconds between session key changes while connected, with an update key (0 = 900) | --sa-key-change-interval |
|
| DNP3CTL_SA_KEY_FILE | string | every client command, server start: File holding a pre-shared session key of Secure Authentication v5, as hex (or set DNP3CTL_SA_KEY); enables secure authentication | --sa-key-file |
|
| DNP3CTL_SA_MAC | string | every client command, server start: Secure authentication MAC algorithm: hmac-sha256-16 (default), hmac-sha256-8, hmac-sha1-10 or hmac-sha1-8 | --sa-mac |
|
| DNP3CTL_SA_REPLY_TIMEOUT | uint32 | server start: Secure authentication: milliseconds a challenge waits for the master's reply (0 = 10000) | --sa-reply-timeout |
|
| DNP3CTL_SA_STATISTICS_CLASS | uint8 | server start: Secure authentication: event class 1-3 of security statistic events, g122 (0 = 1) | --sa-statistics-class |
|
| DNP3CTL_SA_STATISTICS_THRESHOLD | uint32 | server start: Secure authentication: increments of a security statistic between two of its events (0 = 10) | --sa-statistics-threshold |
|
| DNP3CTL_SA_UPDATE_KEY_FILE | string | every client command, server start: File holding the update key of Secure Authentication v5, 16 or 32 octets as hex (or set DNP3CTL_SA_UPDATE_KEY); enables secure authentication with session key change | --sa-update-key-file |
|
| DNP3CTL_SA_USER | uint16 | every client command, server start: Secure authentication user number | --sa-user |
1 |
| DNP3CTL_SERIAL | string | server start: Serve on this serial port (e.g. /dev/ttyUSB0 or COM3) instead of the network; line settings with --baud, --data-bits, --parity, --stop-bits | --serial |
|
| DNP3CTL_STOP_BITS | uint8 | every client command, server start: Serial stop bits, 1 or 2 (0 = 1) | --stop-bits |
|
| DNP3CTL_SUBNETS | strings | discover: Subnets, ranges or addresses to probe: CIDR (192.168.1.0/24), range (192.168.1.10-20) or a single address; comma-separated | --subnets |
|
| DNP3CTL_TIME | string | client time-sync: Time to write with --method write: now, or an RFC 3339 time such as 2026-01-31T12:00:00Z | --time |
|
| DNP3CTL_TIMEOUT | uint32 | every client command: Timeout in milliseconds for connecting and for each request, retries included (0 = 10000); discover: Timeout in milliseconds per probe (0 = 1000) | --timeout |
|
| DNP3CTL_TIME_METHOD | string | client time-sync: Procedure: lan (RECORD_CURRENT_TIME, then the time of that request), non-lan (DELAY_MEASURE, then the time corrected by the delay) or write (the time of --time, without correction) | --method |
lan |
| DNP3CTL_TLS | bool | Use TLS with --ip (implied by a tls:// URL) | --tls |
|
| DNP3CTL_TLS_CA | string | every client command: PEM file with the CA certificates to verify the outstation with (default: system roots); server start: With a tls:// --dial: PEM file with the CA certificates to verify the master with (default: system roots) | --tls-ca |
|
| DNP3CTL_TLS_CERT | string | every client command: PEM file with the own certificate (client authentication); server start: PEM file with the server certificate; with --tls-key it makes the server a TLS server (with a tls:// --dial: the certificate presented to the master) | --tls-cert |
|
| DNP3CTL_TLS_CLIENT_CA | string | server start: PEM file with the CA certificates masters must present a certificate of (mutual TLS) | --tls-client-ca |
|
| DNP3CTL_TLS_INSECURE | bool | Do not verify the outstation certificate (testing only) | --tls-insecure |
|
| DNP3CTL_TLS_KEY | string | every client command, server start: PEM file with the private key of --tls-cert | --tls-key |
|
| DNP3CTL_TLS_SERVER_NAME | string | Name to verify the outstation certificate against (default: the host) | --tls-server-name |
|
| DNP3CTL_UDP | bool | server start: Serve DNP3 over UDP instead of TCP (one master at a time) | --udp |
|
| DNP3CTL_UDP_LOCAL_PORT | uint16 | With udp:// URLs: send from this local UDP port, for outstations that answer only a configured master endpoint (often 20000); 0 = any free port | --udp-local-port |
|
| DNP3CTL_UDP_PEER | string | server start: With --udp: the master's address (host:port), so that unsolicited responses reach it before it has sent anything | --udp-peer |
|
| DNP3CTL_UNSOLICITED | string | client monitor: Classes to enable unsolicited responses for after connecting, comma-separated, or none | --unsolicited |
1,2,3 |
| DNP3CTL_URL | string | Outstation URL: tcp://host[:port], tls://host[:port], udp://host[:port] or serial:///dev/ttyUSB0 (ports default to 20000, and 19999 for tls); mutually exclusive with --ip/--port | --url |
|
| DNP3CTL_VALUE | string | client operate: Set-point value of an analog control: one for every index, or one per index, comma-separated | --value |
|
| DNP3CTL_VARIATION | uint8 | client operate: Variation of an analog control: 1 (32-bit), 2 (16-bit), 3 (single-precision float), 4 (double-precision float); 0 = 1 for whole numbers that fit, 3 otherwise | --variation |
|
| DNP3CTL_SA_KEY | hex | Secure Authentication pre-shared session key (instead of --sa-key-file); environment only |
— | |
| DNP3CTL_SA_UPDATE_KEY | hex | Secure Authentication update key (instead of --sa-update-key-file); environment only |
— | |
| DNP3CTL_FILE_PASSWORD | string | Password of --file-user (instead of --file-password-file); environment only |
— |
dnp3ctl is a command-line tool for DNP3 (IEEE 1815), the SCADA protocol between masters and outstations. As a client (master) it polls, reads and monitors an outstation, operates controls, synchronizes its clock, restarts it and transfers files; as a server it simulates an outstation from a model file. It also finds outstations on a network.
Run an integrity poll: dnp3ctl client poll --ip 192.168.1.20 --outstation 10. Add --format table or --format json for other output. See Poll an outstation.
dnp3ctl client monitor --ip 192.168.1.20 --integrity prints the current values and then every event, with its time stamp, until you press Ctrl-C. --format json gives one JSON object per line. See Monitor events.
dnp3ctl client operate --ip 192.168.1.20 --type crob --index 0 --op latch-on selects, waits for the answer and operates; select-before-operate is the default mode. See Operate an output.
dnp3ctl server start serves a built-in demo outstation on port 20000. dnp3ctl server generate-config --output outstation.yaml writes its model for you to edit, and dnp3ctl server start --config outstation.yaml serves yours. See Simulate an outstation and docs/server-config.md.
- The link addresses. An outstation ignores frames for another link address, and many answer only the masters they are configured for: check
--outstationand--master.dnp3ctl discover --subnets <host> --outstations 1,2,3,4,10,65532asks a list of addresses at the link layer. - Is it DNP3 at all?
discovertells in a second, without any application layer request. - Secure Authentication. A control that fails with
authentication_failed(exit code 6) needs the key:--sa-update-key-file(or--sa-key-filefor a pre-shared session key). - What is on the wire.
--debugshows every link frame and application fragment in both directions.
The outstation answered with values and set an error bit in its internal indications: PARAMETER_ERROR for indexes it does not have, OBJECT_UNKNOWN for an object it does not support. The values it did send are in the result, with the error. A request it refuses without any value exits with 6.
The library underneath, go-dnp3, runs an interoperability suite as master and as outstation against opendnp3, DSC go-dnp3 and dnp3py, in the containers of otfabric/dnp3-interop (v0.10.0, contracts 1.3 to 1.5: among others device attribute sets of every type, broadcast, freeze at time, deadbands, file append and ACTIVATE_CONFIG), and its outstation runs the DNP Users Group's IED certification procedures (dnp3c: 3.1 rev 1 at subset levels 2 and 3, 2.7 at level 2) in CI. That is not a certification. dnp3ctl's own tests run every command against its simulated outstation over TCP, TLS, UDP and a serial stream. If you use dnp3ctl with a device it has trouble with, please open an issue with the --debug trace.
Yes. --url tls://host (port 19999 by default) with --tls-ca, and --tls-cert / --tls-key for an outstation that wants a client certificate; the simulated outstation serves TLS with --tls-cert and --tls-key. Secure Authentication v5 works on both sides, with session key change by an update key (--sa-update-key-file or DNP3CTL_SA_UPDATE_KEY) or with a pre-shared session key (--sa-key-file or DNP3CTL_SA_KEY); aggressive mode with --sa-aggressive, and client read --object g121v1 shows the security statistics; the update key change and user management are not implemented (see SECURITY.md).
Yes: --url serial:///dev/ttyUSB0 --baud 19200 (or serial://COM3 on Windows) for the client, and server start --serial /dev/ttyUSB0 for the outstation. On a multi-drop line every outstation has its own link address: address one with --outstation.
It can. Events an outstation has reported to a master are gone from its buffer: a poll or a monitor with the link address of the regular master takes those events away from it. Use a link address of your own, if the outstation keeps a buffer per master, or a read of class 0 or of static objects only. A connection with the link address of a master that is connected replaces that master's connection. An outstation may also accept few connections.
Pass --format json (or set DNP3CTL_FORMAT=json). Progress goes to stderr, the result goes to stdout, and a failure returns one JSON error object with a stable exit code. dnp3ctl server start --config model.yaml --duration 60 gives a pipeline an outstation to test against. See General CLI usage, docs/json-output.md and docs/exitcodes.md.
Repository layout
dnp3ctl/
├── cmd/ (root command, version, completion)
│ ├── client (poll, read, monitor, operate, time-sync, restart, unsolicited, assign-class, freeze, attributes, write-attribute, write-deadband, manage, clear-restart, ping, file, objects)
│ ├── discover (subnet discovery)
│ └── server (start, generate-config)
├── internal/
│ ├── config (CLI & env: configuration types, flag and environment binding, outstation URLs)
│ ├── cli (shared Cobra helpers: enum completion, required flags, debug)
│ ├── format (stdout **text/json/table** via `Write`, streaming output via `StreamWriter`, table rendering)
│ ├── runner (command wiring, formatted and streaming runs, fatal JSON/text rendering)
│ ├── errs (typed CLI errors, stable codes, exit-code mapping)
│ ├── dnp (what the commands do: connections, TLS, serial, secure authentication, requests, monitor, files, discovery, error classification)
│ ├── station (the simulated outstation: model, validation, controls, simulation, files)
│ ├── stationtest (the simulated outstation for tests, with throw-away TLS certificates)
│ └── types (DTOs for JSON/text/table output)
├── docs/ (exit codes, JSON output contract, outstation model reference)
├── Makefile (Build & test commands)
└── go.mod (Dependencies)A toplevel Makefile is provided for convenience. Run make (or make help) for the full list. Common targets:
| Target | Purpose |
|---|---|
make test |
Unit and end-to-end tests with race detector (go test -race ./...) |
make coverage |
Tests + coverage.out across the whole module; fails below 85% total statement coverage (COVERAGE_MIN) |
make compile |
Full-module typecheck (go build ./...) |
make lint |
compile then staticcheck |
make lint-ci |
compile, go vet, golangci-lint (uses .golangci.yml) |
make vuln |
govulncheck over the module and its dependencies |
make cross |
Typecheck for 32-bit targets (linux/arm/v7 is a release target, where int has 32 bits) |
make fmt / fmt-check |
Format with the gofmt of the Go version in go.mod (the one CI uses); fmt-check fails if that gofmt or the locally installed one would change any file |
make check |
fmt, fmt-check, lint, lint-ci, vet, cross, vuln, test, coverage |
make build |
Runs check then writes bin/dnp3ctl |
make build-all / release-all |
Cross-compile and package archives |
make build writes the binary to bin/dnp3ctl (not the repo root).
make build
make build-allmake test
make coverageTests need no hardware and no network beyond the loopback interface: internal/stationtest serves the simulated outstation of dnp3ctl server on a free port, and the tests of every command run the real command tree against it, over TCP, TLS and UDP; serial lines are tested over an in-memory pipe.
Release notes for each version are maintained in RELEASE.md.
Bug reports, outstation-compatibility notes and pull requests are welcome. Please open an issue to discuss larger changes first, and run make check before submitting. See CONTRIBUTING.md for the development setup and guidelines.
Please report security vulnerabilities privately, as described in SECURITY.md, not in a public issue.
If dnp3ctl is useful to you, a star on GitHub helps other engineers find it.
This project is licensed under the MIT License. See LICENSE.