Testing and interop
Two kinds of OT Fabric repositories share the testing of PROFINET. profinet-interop owns the reference implementations: adapters around independent PROFINET stacks behind one container contract, pinned by commit or version and hash. It knows nothing about any library under test. go-profinet is a consumer: its interop tests drive the images on Docker bridge networks, own the scenarios and assertions, and write the results that profinet-interop’s matrix is generated from.
profinet-interop
Section titled “profinet-interop”profinet-interop v0.6.0 (container contract 1.2) packages:
| Adapter | Implementation | Version | Licence | Role | go-profinet tested as |
|---|---|---|---|---|---|
pnet |
rt-labs p-net (C) | ea5e554 (2023-11-10) |
GPL-3.0-only or commercial | IO-Device | IO-Controller, DCP requester, LLDP agent |
pnio-rs |
pnio-rs (Rust) | v0.1.0 | MIT OR Apache-2.0 | IO-Device | IO-Controller, DCP requester |
profinet-py |
profinet-py (Python) | 0.6.6 | GPL-3.0-only or commercial | IO-Controller, DCP tool | IO-Device, DCP responder |
pnio-dcp |
pnio-dcp (Python) | 1.2.0 | MIT | DCP tool | DCP responder |
umrp |
umrp (C, user space) | v4.0 | GPL-2.0 | MRP ring node | MRP manager, client and automatic manager |
mrp |
microchip-ung/mrp (C, on the kernel’s bridge MRP) | 177717e |
GPL-2.0 | MRP ring node | MRP manager, client and automatic manager |
netsnmp |
Net-SNMP manager | 5.9.3 | BSD-style | SNMP manager | SNMP agent of the IO-Device |
scapy, tshark |
scapy contrib pnio, Wireshark tshark | 2.6.1, 4.0.17 | GPL-2.0 | Decoders | Every capture |
The p-net tree is the last complete public GPLv3 snapshot: since 2024 the public branch of p-net is an evaluation version without build files, ports or sample application. That tree predates p-net’s v1.0.2 security fixes, so it is a lab oracle for isolated test networks only. Two candidates were evaluated and not used: profinet-python (no licence, and a Connect that compliant devices reject) and the PI Community Stack (members only, under an agreement that forbids combining it with copyleft code).
Every upstream is pinned in versions.yaml. No image is published: most of the implementations are GPL, so every user builds the images from upstream. A weekly workflow compares each pin with its latest upstream release, builds and smoke-tests a candidate, and keeps an issue open while any pin is behind; pins are never moved automatically.
Running the reference stacks
Section titled “Running the reference stacks”-
Build the images, tagged
profinet-interop-<adapter>:dev:Terminal window git clone https://github.com/otfabric/profinet-interop && cd profinet-interopmake images -
Every container needs
NET_RAWandNET_ADMIN, and except for the MRP ring nodes, a Docker bridge network with a static address:Terminal window docker network create --subnet 10.77.0.0/24 pndocker run -d --name pnet --network pn --ip 10.77.0.10 \--cap-add NET_RAW --cap-add NET_ADMIN profinet-interop-pnet:dev device --station pnet-dut -
Talk to it, with another adapter or your own code:
Terminal window docker run --rm --network pn --ip 10.77.0.4 --cap-add NET_RAW --cap-add NET_ADMIN \profinet-interop-pnio-dcp:dev dcp identify-all
Every image has the same entrypoint and commands for its roles: print-capabilities and version, device (runs until stopped and writes JSON Lines events), controller (one scenario, one report), dcp <operation>, decode and check for the decoders, node and status for the MRP ring nodes, and snmp for Net-SNMP. LLDP does not cross a Linux bridge unless the bridge forwards its group address, so the harness sets group_fwd_mask, and reports LLDP as not applicable where it cannot. The container contract has every flag and document.
go-profinet against the references
Section titled “go-profinet against the references”go-profinet’s Interop workflow runs on pull requests, pushes to main and weekly; a Kernel MRP workflow runs the microchip-ung/mrp tests in micro-VMs with a Linux kernel built with bridge MRP and without switchdev, which hosted runners do not have. Every exchange is captured and decoded by scapy and tshark, and a frame that cannot decode, or that tshark warns about, fails the test unless it is an attributed deviation of the peer.
| Peer | go-profinet as | Pass | Fail | n/a | Peer deviation |
|---|---|---|---|---|---|
| rt-labs p-net | IO-Controller, DCP requester, LLDP agent | 56 | 0 | 1 | 0 |
| pnio-rs | IO-Controller, DCP requester | 13 | 0 | 3 | 0 |
| profinet-py | IO-Device, DCP responder | 32 | 0 | 1 | 3 |
| pnio-dcp | DCP responder | 8 | 0 | 0 | 0 |
| umrp | MRP manager, client and automatic manager | 32 | 0 | 0 | 2 |
| microchip-ung/mrp | MRP manager, client and automatic manager | 31 | 0 | 1 | 0 |
| Net-SNMP | SNMP agent of the IO-Device | 29 | 0 | 0 | 0 |
| scapy and tshark | Decoder of every capture | 47 | 0 | 0 | 9 |
| Total | 248 | 0 | 6 | 14 |
The n/a are limits of the peers: pnio-rs has no diagnosis records and no DCP Set, p-net and pnio-rs refuse re-parameterization in data exchange, profinet-py ignores a GSDML’s UsedInSlots, and the kernel flushes its forwarding table where the mrp adapter cannot observe it. The deviations are profinet-py’s I&M1 write with a block two octets too long, umrp’s automatic manager reclaiming the manager role as a client, and decoder limits. The full matrix, feature by feature with the evidence of each check, is MATRIX.md, summarized in go-profinet’s FEATURE-STATUS.md.
make interop-images # clones otfabric/profinet-interop and builds its imagesmake interop # runs the suite; results in interop-results/make interop-matrix # regenerates profinet-interop's matrix from themBeyond interop
Section titled “Beyond interop”go-profinet’s own tests run a controller and devices on the in-memory virtual Ethernet: end-to-end tests of every service, chaos tests with seeded loss, duplication, reordering, corruption and jitter and pulled cables, a fuzz target for every decoder, and real AF_PACKET tests on a veth pair in a privileged container. Coverage is held at 90 % or more, and a release needs 25 green soak runs. profinetctl’s tests run its command tree against go-profinet devices on the virtual network, and make pnet-smoke runs the DCP, record, I&M, diagnosis, topology and AR commands against p-net on a real Linux bridge.
What is and is not validated
Section titled “What is and is not validated”Validated: DCP in both roles against p-net, pnio-rs, profinet-py and pnio-dcp; the controller’s legacy startup against p-net and advanced startup against p-net and pnio-rs, with cyclic data at 1 ms against p-net, records, I&M, diagnosis, alarms, ModuleDiff, shared device with two go-profinet controllers on p-net and port checks that p-net evaluates; the device against profinet-py’s controller with cyclic data, alarms, records over a device access AR and re-parameterization; MRP rings with umrp and microchip-ung/mrp as manager, client and automatic manager, including cut cables; the SNMP agent against Net-SNMP.
Not validated: there is no second open IO-Controller to test the device against, other than profinet-py. No commercial PLC, device or engineering tool has been tested, and no GSDML file generated by go-profinet has been imported by TIA Portal or CODESYS yet. Npcap on Windows is not tested. System redundancy S2 has no open peer and is an experiment.
Findings about the references
Section titled “Findings about the references”Running the stacks against go-profinet changed go-profinet where it disagreed with every reference, because the standard is not public and the stacks agree with each other:
- p-net ties an AR to the RPC activity of its Connect, so a PrmEnd on another activity is acknowledged but never processed.
- p-net starts its input provider only after PrmEnd, so a consumer watchdog armed at the Connect expires.
- p-net, pnio-rs and profinet-py all number the first alarm 0xFFFF, not 0.
- p-net does not answer DCP Get with the AllSelector, and ignores another requester for 3 seconds after a Get or Set.
- A Linux bridge with LLDP forwarding returns LLDP frames to their sender, so p-net lists itself as its own neighbour.
- pnio-rs accepts only RT_CLASS_2 frame IDs, so a controller must use the advanced startup with it.
Recorded and not worked around: p-net plugs any module a controller expects instead of reporting it in the ModuleDiffBlock, so a wrong module cannot be detected against p-net; pnio-rs answers diagnosis indices and I&M4 with invalid index and rejects a Connect without an exact ModuleDiff match.