Skip to content

DYODE_python3 1.5.0 Interface Control Document

nc-intezra edited this page Oct 24, 2026 · 1 revision

DYODE 1.5.0 · nc-intezra/dyode_python3

Interface Control Document

Prepared against the CMS Target Life Cycle (TLC) Interface Control Document template.

Version 0.1 — Draft for review

Date 10/23/2026

System under control DYODE 1.5.0 (tag v1.5.0, commit ab214c9, released 21 October 2026)

Source system DYODE Input box (sending-side network)

Target system DYODE Output box (receiving-side network)

Basis and open items

This draft was built from DYODE's public README, the v1.5.0 release notes and the project wiki (which documents release 1.5.0), plus the original Wavestone install notes. It is not a line-by-line review of the source code. Process diagrams are reproduced from the wiki. Anything marked TBD has to be confirmed in the repository (config schema, wire layout, defaults) before approval. Template sections that assume two-way exchange are kept and marked as not applicable, with the reason given.

Contents

    1. Introduction
    1. Assumptions / Constraints / Risks
    1. Interface Overview
    1. Detailed Interface Requirements
    1. Quality Assurance
  • Appendix A: Record of Changes
  • Appendix B: Glossary
  • Appendix C: Referenced Documents
  • Appendix D: Approvals
  • Appendix E: Open Items

List of Figures

  • Figure 1 — Boxes and networks
  • Figure 2 — Processes on each box
  • Figure 3 — The two transports
  • Figure 4 — The journey of a file
  • Figure 5 — One batch, over time
  • Figure 6 — The receiver's view of a batch
  • Figure 7 — How lost packets are handled (FEC)
  • Figure 8 — Log streaming path

1. Introduction

This ICD describes the relationship between the DYODE Input box (the source system) and the DYODE Output box (the target system), and the external systems attached to each. DYODE (“Do Your Own Diode”) is a low-cost, open-source data diode for industrial control systems. It moves data in one direction only, from a sending-side network to a receiving-side network, over a light-based link with no return path. It is often placed between operational technology (OT) and information technology (IT) networks, but this ICD treats the two sides as generic networks.

The ICD covers:

  • the common one-way transport between the two boxes;
  • folder transfer across it, together with the local file-share endpoints the installer can set up on either box;
  • log streaming, Modbus mirroring and screen sharing, also carried over the transport.

Security and privacy. The interface carries operational data: process values, logs, files and screen images. DYODE is not designed to carry PII or PHI, but logs and transferred files may contain them. The data owner must classify the content before go-live. TBD

1.1 Overview

Hardware. Each link has two Linux computers and a one-way light path between them, either enforced by a third-party certified data diode or the DIY diode solution created with media converters within the https://github.com/wavestone-cdt/dyode project that inspired this fork. Wavestone's full version is a 19-inch rack unit that uses copper-to-optical media converters; the original install notes list three converters and three NICs per computer. The light version (DYODE v2) is a compact, low-cost Raspberry Pi pair that replaces the Ethernet link with a serial line through an optocoupler; it carries Modbus only. Folder transfer, log streaming and screen sharing need the full version (DYODE v1).

Software. DYODE is written in Python and needs Python 3.11 or later. It uses udpcast for file transfer, pymodbus (pinned to ≥3.12,<3.13 in 1.5.0) for Modbus, systemd to run the service, and nftables for the diode-port firewall. Optional services are NFS, Samba and OpenSSH. Supported systems are Ubuntu 24.04 and 26.04 LTS (amd64, arm64) and 64-bit Raspberry Pi OS 12 and 13. Ubuntu 22.04 is not supported.

Processes. The input box runs dyode_in.py and the output box runs dyode_out.py. Each starts a supervisor that runs every configured module in its own process and restarts a crashed module after a 5-second guard; systemd restarts the supervisor itself (Restart=always). See Figure 2.

Configuration. Both boxes carry the same config.yaml and, from 1.5.0, the same dyode.key. The setup wizard (dyode_setup.py) detects interfaces, records the diode NIC's MAC address, configures modules, and writes the config and a systemd unit.

2. Assumptions / Constraints / Risks

2.1 Assumptions

  • The deployment is DYODE full (v1) with both boxes on release 1.5.0.
  • Data flows from the sending side (input box) to the receiving side (output box). Each deployment direction is documented by its own ICD.
  • config.yaml and dyode.key are identical on both boxes, and auth.required is left at its default (authentication on).
  • Both boxes are installed with install.sh, including receive-side tuning and the diode-port firewall (no --no-tuning or --no-firewall).
  • Each box has a dedicated diode NIC that carries only diode traffic.

2.2 Constraints

  • No return channel. The receiver cannot acknowledge, request retransmission or apply flow control. Reliability depends on redundancy, sequencing and checks at the receiver.
  • Throughput. The link bitrate, the receiving NIC's buffers and the receiving disk's sustained write speed all limit throughput. No kernel setting removes the disk limit.
  • Reassembly bounds. The receiver drops oversized pieces, messages announcing more pieces than a module ever sends, partial messages older than 10 s, and anything over 32 MB held per listener.
  • Light version. Carries Modbus only, over a serial line.
  • Viewers. At most max_streams (default 8) screen viewers.
  • Platform. Tested on Ubuntu 24.04 LTS, Ubuntu 26.04 LTS and 64-bit Raspberry Pi OS (Raspbian).

2.3 Risks

Risk Impact Mitigation
Disk slower than link Writes stall and the transfer aborts. Measure sustained disk speed and set the bitrate from it.
Key exposure or mismatch A mismatch stops the service. An exposed key lets receiving-side hosts forge signed messages. Protect dyode.key as a credential; define rotation. TBD
Authentication disabled Spoofed file announcements, log lines or Modbus values are accepted. Keep auth.required: true; alert on the start-up warning.
Host compromise Signing protects the messages, not the boxes. Harden the hosts, patch them and monitor them under the site baseline.
Silent data loss Consumers act on incomplete data. Log gaps are reported by sequence range (logs_gap); stall errors report UDP RcvbufErrors.

3. Interface Overview

3.1 Interface Overview

flowchart TB
    subgraph SNET["Sending-side network"]
        direction LR
        PLC["PLC"]
        SRC["File sources,<br/>syslog senders"]
    end
    subgraph INBOX["Input box"]
        direction LR
        NIC_S["Sending-side NIC"]
        DIN["dyode_in.py"]
        NIC_D_IN["Diode-facing NIC<br/>dyode_in.ip / interface"]
    end
    DIODE{{"Data diode<br/>(fiber or optocoupler)"}}
    subgraph OUTBOX["Output box"]
        direction LR
        NIC_D_OUT["Diode-facing NIC<br/>dyode_out.ip / interface"]
        DOUT["dyode_out.py"]
        NIC_R["Receiving-side NIC"]
    end
    subgraph RNET["Receiving-side network"]
        direction LR
        CLIENTS["SCADA, file shares,<br/>SIEM, browsers"]
    end
    PLC <--> NIC_S
    SRC --> NIC_S
    NIC_S --- DIN --- NIC_D_IN
    NIC_D_IN ==>|"UDP only"| DIODE ==> NIC_D_OUT
    NIC_D_OUT --- DOUT --- NIC_R
    NIC_R <--> CLIENTS
Loading

Figure 1 — Boxes and networks. Source: DYODE wiki, Architecture (labels adapted to sending / receiving side).

ID Interface From → To Module type Version
I-1 Diode transport Input box → Output box All modules Full, light
I-2 Folder transfer Input folder → Output folder folder Full
I-3 Local file shares Users ↔ Input drop folder; Output received folder → users Installer-managed Either box
I-4 Log streaming Syslog sources / log files → Output files or SIEM logs Full
I-5 Modbus mirror PLC → Input → Output → receiving-side Modbus clients modbus Full (UDP), light (serial)
I-6 Screen sharing Screen source → receiving-side viewers screen Full

3.1.1 Interface Controls

The template's seven OSI-layer tables (Tables 1–7) are combined below, one row per layer.

OSI layer Interface type From To Description / other information
7 Application File folders; NFS / SMB / SFTP; syslog; Modbus TCP; screen frames Sending-side sources, Input box Output box, receiving-side consumers Module services I-2 to I-6. The Modbus mirror on the output side is read-only.
6 Presentation DYO1 framing carrying DYA1 authentication Input box Output box Every message is HMAC-SHA256 signed. Log batches are compressed. File manifests carry SHA-256 hashes. Field layout TBD.
5 Session None across the diode — — No sessions are possible one way. TCP sessions exist only on each side's local endpoints.
4 Transport UDP across the diode; TCP/UDP locally Input box Output box Each module's base UDP port is set in config.yaml. Files use udpcast with forward error correction; messages use DYO1 datagrams of at most 1400 bytes, so no IP fragmentation (Figure 3). Light version: serial line carrying newline-delimited JSON.
3 Network IPv4 dyode_in.ip dyode_out.ip Diode-port datagrams are accepted only from the input box on the diode NIC. IPv6 is dropped. rp_filter=1 on the diode NIC.
2 Data link Ethernet Input diode NIC Output diode NIC ARP replies cannot cross the diode, so the input box pins dyode_out.mac as a permanent neighbor entry. The arp-keeper re-asserts it every arp_interval seconds (default 60). If the entry cannot be set, dyode_in.py refuses to start.
1 Physical Optical, transmit-only Input box Output box Either a third-party certified data diode, or the Wavestone DIY build — full: copper–optical media converters and fiber; light: optocoupler board (open-source Gerber and case files).

3.2 Functional Allocation

Input box: watches input folders; receives syslog and follows log files; polls the configured Modbus registers and coils; captures screen frames; signs, frames and sends each message over the diode NIC. Output box: filters diode traffic (nftables); reassembles within bounds; verifies signatures and counters; writes received files; writes log files and forwards logs to the SIEM; serves the read-only Modbus mirror; serves screen viewers.

End users don't use the diode directly. Data moves on these triggers: a file dropped in an input folder, a log line arriving, the Modbus polling interval TBD (the original code polled every 1 s), or a screen capture. Operators work through install.sh, dyode_setup.py, systemd and dyode_logs.py --stats.

flowchart TB
    subgraph IN["Input box"]
        SDI["systemd<br/>dyode-in.service"] --> SUPI["dyode_in.py<br/>supervisor"]
        SUPI --> FI["folder module(s)<br/>udp-sender"]
        SUPI --> LI["logs module(s)<br/>syslog + tail"]
        SUPI --> MI["modbus module(s)<br/>PLC poller"]
        SUPI --> SI["screen module(s)"]
        SUPI --> ARP["arp-keeper"]
    end
    subgraph OUT["Output box"]
        SDO["systemd<br/>dyode-out.service"] --> SUPO["dyode_out.py<br/>supervisor"]
        SUPO --> FO["folder module(s)<br/>udp-receiver + verify thread"]
        SUPO --> LO["logs module(s)<br/>writer / forwarder"]
        SUPO --> MO["modbus module(s)<br/>Modbus TCP server"]
        SUPO --> SO["screen module(s)<br/>HTTP server"]
    end
Loading

Figure 2 — Processes on each box. Source: DYODE wiki, Architecture (labels adapted to sending / receiving side).

3.3 Data Transfer

Each module builds a message and wraps it in DYO1 framing. The message carries a DYA1 authentication block: an HMAC-SHA256 tag made with dyode.key, a random sender id and a counter. The input box splits the message into DYO1 datagrams of at most 1400 bytes and sends them to the module's port on the output box. The output box drops any datagram that does not arrive from the input box on the diode NIC. It then reassembles the message within the bounds in 2.2, verifies the tag, rejects replays and passes the message to the module. Folder payloads go over udpcast and are accepted only when their SHA-256 matches a signed manifest. Log batches are numbered, compressed and sent twice, about half a second apart. Any message with a missing piece is dropped whole, never delivered half-corrupted.

flowchart LR
    subgraph T1["Bulk files: folder modules"]
        direction LR
        A1["File"] --> B1["udp-sender<br/>--async + FEC"] --> C1["udp-receiver"] --> D1["SHA-256 check"]
    end
    subgraph T2["Messages: logs, modbus, screen"]
        direction LR
        A2["Message<br/>(JSON / JPEG / batch)"] --> B2["Split into 1400-byte<br/>DYO1 datagrams"] --> C2["Reassembler<br/>(CRC32 check)"] --> D2["Whole message<br/>or nothing"]
    end
Loading

Figure 3 — The two transports. Source: DYODE wiki, Architecture (labels adapted to sending / receiving side).

T1 ~~~ T2

3.4 Transactions

The template expects request/response transactions. That is not applicable across the diode: every exchange is a one-way message with no response. The output box logs its validation results locally.

Message type Module Content Response
Folder manifest I-2 Files announced for transfer, with SHA-256 hashes None
File payload I-2 File content over udpcast None
Log batch I-4 Numbered, compressed batch of log lines (sent twice) None
Heartbeat I-4 Liveness signal for stale-link detection None
Modbus update I-5 Values for the configured register and coil ranges None
Screen frame I-6 Captured screen image None

3.5 Security and Integrity

  • Physical one-way path: no data can travel from the receiving side to the sending side.
  • Message authentication (1.5.0): HMAC-SHA256 with a shared key; replay protection with sender id and counter. DYODE refuses to start if the key is missing, unless auth.required: false is set, in which case it runs unauthenticated and logs a warning.
  • Receiver firewall (D-02): nftables rules on receiving boxes; listeners bind to the diode NIC.
  • Bounded reassembly (D-05): a datagram flood cannot exhaust memory.
  • Read-only Modbus (D-03): writes and out-of-range reads are refused and logged.
  • Smaller items (D-10 to D-12): NFS subtree_check; screen viewer listens on the receiving-side address and caps viewers; the wizard warns when the syslog allow_from list is empty.
  • Install integrity: the offline bundle is checked against SHA256SUMS before anything changes.

4. Detailed Interface Requirements

Requirement IDs (R-n.m) are assigned here for traceability to Section 5.

4.1 Requirements for I-1 Diode Transport

4.1.1 Assumptions Dedicated diode NICs; IPv4; shared key and config on both boxes.

4.1.2 Technical requirements R-1.1 Every message is HMAC-SHA256 signed (DYA1 inside DYO1). R-1.2 The receiver rejects replayed messages, using the sender id and counter. R-1.3 The receiver drops diode-port datagrams unless they come from the input box on the diode NIC; IPv6 is never accepted. R-1.4 Reassembly enforces the 10 s and 32 MB-per-listener bounds and the piece-count and size limits. R-1.5 The service does not start without dyode.key while auth.required is true.

4.1.3 Processing steps Build → sign → frame → fragment → send (UDP) → firewall filter → reassemble → verify → hand to module.

4.1.4 Processing time Partial messages expire after 10 s. End-to-end latency targets TBD.

4.1.5 Message format Each DYO1 datagram carries a header: magic DYO1 (4 B, ignores stray traffic), message id (4 B), index / total (2 + 2 B, reassembly in any order) and a CRC32 of the whole message (4 B). Payload ≤ 1400 B. The DYA1 authentication block (tag, sender id, counter) sits inside this framing; its byte layout TBD from source.

4.1.6 Communication UDP over the dedicated diode NIC; one base port per module in config.yaml.

4.1.7 Security Section 3.5. The systemd unit is bound to the diode NIC (BindsTo=), waits for its address, stops when the NIC disappears and starts again when it returns.

4.2 Requirements for I-2 Folder Transfer

4.2.1 Assumptions Full version only. The input folder is fed by users or systems on the sending side (directly or through the I-3 drop share). Files of any size; tested to 10 GB.

4.2.2 Technical requirements R-2.1 Files are accepted only when their SHA-256 matches a signed manifest. R-2.2 The receiving box keeps /etc/sysctl.d/90-dyode-receive.conf: a larger UDP receive buffer, continuous writeback, and vm.dirty_bytes at 20% of RAM (minimum 128 MB, maximum 2 GB). R-2.3 A stalled transfer reports whether packets were dropped on the box (RcvbufErrors) or lost before reaching it. R-2.4 Manifest paths must stay inside out: no absolute paths, no .. or .dyode* parts. Symbolic links in in are never sent. R-2.5 Received files land in a staging folder outside out and are moved in atomically, so shares never expose half-received files. R-2.6 When a batch closes with files missing, each missing file is named in the log.

4.2.3 Processing steps Pick up (inotify, with a 30 s rescan) → wait for settle → hash and send a manifest → send each file with FEC → retire it to .dyode_sent/ → receive into staging → verify SHA-256 against an open batch → atomic move into out/<path>. See Figures 4–6. Up to four batches stay open at once.

4.2.4 Processing time Defaults: settle 2 s, autostart 5 hellos, file_gap 0.5 s, start_timeout 300 s, batch_timeout 300 s, keep_sent_hours 24. Throughput is capped by bitrate (8 Mbit/s shared across folder modules when unset), the single-threaded FEC encoder (about 350–400 Mbit/s with the default 8x16/64) and the receiving disk. Verified on v1.5.0 builds: 1.3 GB and 5 GB at 900 Mbit/s arrived with identical SHA-256. Site bitrate TBD.

4.2.5 Message format JSON manifest carrying the dyode-manifest-v1 magic, a batch id, and each file's relative path, size and SHA-256; followed by each file as a udpcast payload.

4.2.6 Communication UDP from the module base port, using udp-sender --async / udp-receiver with FEC 8x16/64 by default: up to 16 lost blocks per stripe are rebuilt; more aborts the file (Figure 7).

4.2.7 Security The staging folder cannot be shared (see I-3). External programs are run with argument lists, never a shell. A damaged or substituted file is rejected by SHA-256.

flowchart TB
    subgraph SEND["Input box"]
        direction TB
        DROP["File written to<br/>the 'in' folder"] --> SETTLE{"Unchanged for<br/>'settle' seconds?"}
        SETTLE -- "no, still being written" --> DROP
        SETTLE -- yes --> MAN["Build a manifest:<br/>path, size, SHA-256<br/>of every ready file"]
        MAN --> SENDM["udp-sender: manifest"]
        SENDM --> SENDF["udp-sender: each file,<br/>with FEC repair data"]
        SENDF --> RETIRE["Move file to .dyode_sent<br/>(kept 24 h, then deleted)"]
    end
    DIODE{{"Data diode"}}
    subgraph RECV["Output box"]
        direction TB
        RX["udp-receiver writes<br/>into the staging folder"] --> Q["Queue for verification"]
        Q --> ISMAN{"Is it a<br/>manifest?"}
        ISMAN -- yes --> OPEN["Open a batch:<br/>expect these files"]
        ISMAN -- no --> HASH["SHA-256 of the<br/>received file"]
        HASH --> MATCH{"Matches a file<br/>an open batch expects?"}
        MATCH -- yes --> PLACE["Atomic move into<br/>'out/path'"]
        MATCH -- no --> REJECT["Discard and log<br/>'matching no expected file'"]
    end
    SENDM ==> DIODE
    SENDF ==> DIODE
    DIODE ==> RX
Loading

Figure 4 — The journey of a file. Source: DYODE wiki, Folder Transfers (labels adapted to sending / receiving side).

sequenceDiagram
    autonumber
    participant W as in/ folder
    participant S as Input module
    participant D as Diode
    participant R as Output module
    participant V as Verify thread
    participant O as out/ folder
    W->>S: a.csv, sub/b.bin settle
    S->>S: hash both, write manifest
    S->>D: udp-sender manifest.json
    D->>R: udp-receiver to staging
    R->>V: queue manifest
    V->>V: open batch with 2 files expected
    S->>D: udp-sender a.csv
    D->>R: staging
    R->>V: queue
    S->>W: move a.csv to .dyode_sent/
    V->>V: SHA-256 matches a.csv
    V->>O: move to out/a.csv
    S->>D: udp-sender sub/b.bin
    D->>R: staging
    R->>V: queue
    S->>W: move sub/b.bin to .dyode_sent/
    V->>O: move to out/sub/b.bin
    V->>V: batch complete
Loading

Figure 5 — One batch, over time. Source: DYODE wiki, Folder Transfers (labels adapted to sending / receiving side).

stateDiagram-v2
    [*] --> Open: manifest received
    Open --> Open: file verified and stored (clock reset)
    Open --> Open: data still arriving (clock reset)
    Open --> Complete: last expected file stored
    Open --> TimedOut: batch_timeout with nothing arriving
    Open --> Superseded: a newer batch re-sends the missing files
    Open --> Evicted: more than 4 batches open
    Complete --> [*]
    TimedOut --> [*]: missing files named in the log
    Superseded --> [*]
    Evicted --> [*]: missing files named in the log
Loading

Figure 6 — The receiver's view of a batch. Source: DYODE wiki, Folder Transfers (labels adapted to sending / receiving side).

flowchart LR
    F["File"] --> SL["Slice: 512 data blocks<br/>in 8 stripes of 64"]
    SL --> FEC["Plus 16 repair blocks<br/>per stripe"]
    FEC --> LINK["Sent at 'bitrate'"]
    LINK --> CHK{"Any stripe lost<br/>more than 16 blocks?"}
    CHK -- no --> OK["Slice rebuilt"]
    CHK -- yes --> FAIL["udp-receiver aborts:<br/>'Missed blocks: ...'"]
Loading

Figure 7 — How lost packets are handled (FEC). Source: DYODE wiki, Folder Transfers (labels adapted to sending / receiving side).

4.3 Requirements for I-3 Local File Shares

4.3.1 Assumptions Optional. Set up by install.sh after the wizard, or unattended with flags. These shares stay on one side and never cross the diode.

4.3.2 Technical requirements R-3.1 The output box shares received files read-only by default; the input box's drop folder is read-write. R-3.2 Share settings live in one managed block in /etc/exports and smb.conf; smb.conf is checked with testparm; a rejected export is rolled back; every changed file keeps a backup. R-3.3 The installer refuses: sharing the staging folder or a folder containing it, NFS to everyone, SMB guest access, SMB as root, and paths or names that could inject settings. R-3.4 NFS exports of a folder inside a larger filesystem use subtree_check.

4.3.3 Processing steps Users mount the share or connect with SFTP; re-running the same command changes nothing.

4.3.4 Processing time Not applicable.

4.3.5 Message format NFS, SMB, SSH/SFTP (standard protocols).

4.3.6 Communication --share-nfs PATH=CLIENTS[:rw], --share-smb PATH=USER[:rw], --smb-password-file, --shares-only; client-only options through --with.

4.3.7 Security Client lists and SMB users defined per site TBD.

4.4 Requirements for I-4 Log Streaming

4.4.1 Assumptions Full version only. Sending-side devices can send syslog to the input box, or write log files on it.

4.4.2 Technical requirements R-4.1 Accept syslog over UDP and TCP, with LF and RFC 6587 octet-counted framing; follow log files through rotation. R-4.2 Accept only sources in allow_from. R-4.3 Number and compress batches and send each twice, spaced in time. R-4.4 Report lost batches by sequence range (logs_gap), never drop them silently. R-4.5 Heartbeat with stale-link detection; bandwidth cap; control-character escaping.

4.4.3 Processing steps Collect → bounded buffer → batch (boot id + sequence number) → zlib compress → bandwidth cap → send two copies about 0.5 s apart → drop duplicates, reorder, detect gaps → write to received.log and/or forward to the SIEM (Figure 8). A sender restart appears as a new boot id (logs_sender_restart), not a gap.

4.4.4 Processing time Heartbeat every heartbeat s; the output box warns after stale_after s of silence. Default values and batch interval TBD; see the wiki Configuration Reference.

4.4.5 Message format Output written to received.log (or one file per sending host with split_by_source: true), logrotate-friendly, in prefixed (default), raw or json format. Batch byte layout TBD.

4.4.6 Communication Input box: listen_udp (e.g. 514), listen_tcp (e.g. 601), tail globs with tail_state offsets. Output box: forward to udp:// or tcp:// SIEM targets.

4.4.7 Security Source allow-list; signed batches (R-1.1).

flowchart TB
    subgraph SRC["Input box: sources"]
        direction LR
        U["syslog over UDP<br/>listen_udp"]
        T["syslog over TCP<br/>listen_tcp"]
        F["files, like tail -F<br/>tail"]
    end
    SRC --> BUF["Bounded buffer<br/>(counts overflow)"]
    BUF --> BATCH["Batch:<br/>boot id + sequence no.<br/>+ lines"]
    BATCH --> Z["zlib compress"]
    Z --> CAP["Bandwidth cap<br/>max_kbps"]
    CAP --> C1["Copy 1"]
    CAP --> C2["Copy 2, ~0.5 s later"]
    C1 ==> D{{"Diode"}}
    C2 ==> D
    D ==> RX["Output box:<br/>drop duplicates,<br/>reorder, detect gaps"]
    RX --> FILE["received.log<br/>(or one file per source)"]
    RX --> FWD["Forward to SIEM<br/>udp:// or tcp://"]
Loading

Figure 8 — Log streaming path. Source: DYODE wiki, Log Streaming (labels adapted to sending / receiving side).

4.5 Requirements for I-5 Modbus Mirror

4.5.1 Assumptions The PLC exposes Modbus TCP and is reachable from the input box. Available in both versions: framed UDP on the full version, serial on the light version.

4.5.2 Technical requirements R-5.1 Only the configured registers and coils ranges are mirrored. R-5.2 Client writes get exception 01 (Illegal Function) and are logged. R-5.3 Reads outside the configured ranges get exception 02 (Illegal Data Address); unmapped: zero keeps the old answer of 0. R-5.4 Updates crossing the diode are signed (R-1.1); unsigned values are not applied. R-5.5 Stale Modbus data is reported in the log and as a JSON event.

4.5.3 Processing steps The input box polls the PLC and sends an update; the output box verifies it and refreshes the mirror; receiving-side clients read the mirror.

4.5.4 Processing time Polling interval and staleness threshold TBD.

4.5.5 Message format Modbus TCP (standard) on both local sides. Config fields, per the original example: type, port, ip, port_out, registers, coils. Current schema Verify.

4.5.6 Communication PLC on TCP 502 → input box. Output box serves TCP port_out to receiving-side clients.

4.5.7 Security Mirror is read-only; receiving-side client allow-list TBD.

4.6 Requirements for I-6 Screen Sharing

4.6.1 Assumptions Full version only. The input box watches a folder of JPEG screenshots produced on the sending side.

4.6.2 Technical requirements R-6.1 The viewer listens on the receiving-side address by default, not on all addresses. R-6.2 At most max_streams (8) viewers at once. R-6.3 Frames are signed (R-1.1).

4.6.3 Processing steps New screenshot → sign → send as DYO1 datagrams → reassemble and verify → serve as a live MJPEG stream over HTTP.

4.6.4 Processing time Frame rate and latency TBD.

4.6.5 Message format JPEG frames across the diode; MJPEG over HTTP to browsers on the receiving side.

4.6.6 Communication UDP across the diode; HTTP viewer port on the output box TBD.

4.6.7 Security View-only; no input path to the source.

5. Quality Assurance

Req. Method Verification
R-1.1, R-1.2 Test Capture a valid message on the diode segment, replay it, and confirm the receiver rejects it. Alter one byte and confirm the signature check fails.
R-1.3 Test Send a datagram to a diode port from a receiving-side host and over IPv6; confirm both are dropped.
R-1.4 Test Flood partial messages past 32 MB and 10 s; confirm memory stays bounded.
R-1.5 Demonstration Remove dyode.key; confirm the service refuses to start.
R-2.1–R-2.6 Test Transfer 1 GB and 5 GB sets at the site bitrate; compare SHA-256; withhold one file and confirm it is named as missing; attempt a .. path and a symlink; read the stall diagnostics.
R-3.1–R-3.4 Inspection / Test Review the managed blocks; attempt each refused share configuration.
R-4.1–R-4.5 Test / Analysis Send syslog in both framings; block delivery for a period; confirm a logs_gap entry and the dyode_logs.py --stats totals.
R-5.2, R-5.3 Test Issue a write and an out-of-range read from a receiving-side client; expect exceptions 01 and 02 and log entries.
R-6.1, R-6.2 Inspection / Test Check the listen address; open a ninth viewer and confirm it is refused.

A. Record of Changes

Version Date Author / Owner Description of change
0.1 10/23/2026 TBD Initial draft mapping DYODE 1.5.0 to the CMS TLC ICD template.

B. Glossary

Term Definition
Data diode A device that lets data travel in one direction only.
OT / ICS Operational technology / industrial control systems: plant-floor controllers, sensors and networks.
DYO1 / DYA1 DYODE's message framing and the authentication block carried inside it (1.5.0).
HMAC-SHA256 A keyed message fingerprint; without the key, a matching tag cannot be produced.
Modbus TCP An industrial protocol exposing numbered registers and coils over TCP.
udpcast An open-source tool for sending files over UDP without acknowledgements.
nftables The Linux packet-filtering framework used for the diode-port firewall.
SIEM Security information and event management platform.
RcvbufErrors Kernel counter of UDP packets dropped because a receive buffer was full.

C. Referenced Documents

Document Location
CMS TLC Interface Control Document template cms.gov — TLC downloads, Interface-Control-Document.docx
DYODE README and PYTHON3_MIGRATION.md github.com/nc-intezra/dyode_python3
Release notes v1.5.0 github.com/nc-intezra/dyode_python3/releases
DYODE wiki: Home, Architecture, Folder Transfers, Log Streaming (source of Figures 1–8) github.com/nc-intezra/dyode_python3/wiki (pages kept in docs/wiki/)
CAVEATS.md; DYODE_v1_full/LOG_STREAMING.md; wiki Configuration Reference Repository (not reviewed for this draft)
Original DYODE install notes github.com/wavestone-cdt/dyode

D. Approvals

The undersigned acknowledge that they have reviewed this ICD and agree with the information presented in it.

Role Name Signature Date
Business Owner
Sending-Side System Owner (Input box)
Receiving-Side System Owner (Output box)
Information System Security Officer

E. Open Items

The template's Appendix E (Notes to the Author) is removed in a finished ICD. This appendix replaces it with the items that must be closed before approval.

  • DYA1 authentication block layout and log-batch layout (4.1.5, 4.4.5).
  • Default module ports and the screen viewer HTTP port (4.6.6).
  • Log heartbeat, stale_after and batch interval defaults; Modbus polling interval; screen frame rate (4.4.4, 4.5.4, 4.6.4).
  • Current config.yaml schema against the wiki Configuration Reference (4.5.5).
  • Key generation, distribution and rotation procedure (2.3).
  • Data classification for logs and files (Section 1).
  • Site values: bitrate, receiving-side client allow-lists, NFS clients and SMB users (4.2.4, 4.5.7, 4.3.7).

Clone this wiki locally