— Architecture, Sockets & the MBAP Header
(Module 3 · Modbus TCP/IP — Modbus over Modern Networks)
Mission for this chapter: Show you every byte, flag, and network nuance that turns the classic 1979 PDU into a routable, multiplex-capable, Ethernet-speed superstar—without hiding the sharp edges (Nagle, half-opens, Unit-ID collisions). By the end you’ll be able to design, capture, optimise, and debug Modbus TCP stacks with the same confidence you now have on RS-485.
Chapter map
§ | Topic | Why you must know it |
---|---|---|
7.1 | Why Modbus jumped onto TCP/IP | Context & trade-offs |
7.2 | OSI-layer mapping & packet walk-through | “Wire to register” end-to-end view |
7.3 | Anatomy of the MBAP header | Byte-level Bible for every capture |
7.4 | Transaction lifecycle & socket management | Concurrency, half-open recovery, keep-alive |
7.5 | Performance & determinism | Nagle, Delayed ACK, segmentation offload |
7.6 | Unit Identifier & gateway multiplexing | Bridging dozens of RTU loops with one IP |
7.7 | Connection-state pitfalls | TIME_WAIT storms, NAT hair-pinning, firewall DPI |
7.8 | Minimal hardening checklist | Port isolation ➝ TLS (MBSec) preview |
7.9 | Implementation patterns & code | Full Python, C( libmodbus), async examples |
7.10 | Diagnostics with Wireshark | Filters, colouring rules, latency graphs |
7.11 | Best-practice recap | One-page crib-sheet |
(Diagram & listing placeholders appear [Fig-7-x] / Listing x.)
7.1 Why Modbus Jumped onto TCP/IP
- Routability — RS-485 stops at 1200 m; Ethernet + IP routing reaches another continent.
- Bandwidth — 100 Mbit/s Fast Ethernet ≈ 10 000× 9 600 Bd RS-485, enabling multi-MBps historians.
- Multi-master freedom — Any PC/HMI may open a socket; no collisions—TCP arbitrates.
- Leverage commodity silicon — PHY, MAC, switch = peanuts; no fieldbus ASIC royalties.
- Piggy-back on IT infrastructure — One cable carries control, MES, camera feeds, VoIP.
⚠ Trade-offs: Loss of implicit determinism (Ethernet CSMA/CD & TCP congestion), infinitely more security exposure, dependence on IPv4/IPv6 address planning.
7.2 Packet Walk-Through — From Wire to Register
[Fig-7-1] Layer “onion” diagram (Ethernet II → IPv4 → TCP → MBAP + PDU).
OSI layer | Modbus piece | Bytes | Field notes |
---|---|---|---|
1/2 | Ethernet II | 14 + CRC | Dest MAC, Src MAC, EtherType 0x0800 |
3 | IPv4 | 20 (no opts) | Src IP, Dst IP, TTL, ID, DF… |
4 | TCP | 20 (no opts) | Src port (=any >1023), Dst port 502 |
5-6 | MBAP | 7 | Transaction-ID, Protocol-ID, Length, Unit-ID |
7 | PDU | n | Function Code + Data |
7.3 MBAP Header — The 7-Byte Contract
Byte offset | Field | Purpose | Typical value |
---|---|---|---|
0-1 | Transaction Identifier | Echoes; lets client match OOO replies | 0x0000-0xFFFF |
2-3 | Protocol Identifier | Always 0x0000 for Modbus | 0x0000 |
4-5 | Length | Unit-ID + PDU byte-count | 0x0006 (read 3 regs) |
6 | Unit Identifier | Slave-ID behind gateway; 0xFF broadcast | 0x11 |
[Fig-7-2] Colour-coded MBAP breakdown with tool-tip call-outs.
▶ Field trivia Protocol-ID ≠ 0 reserved for future multiplex (never standardised). Some vendors hijack
0x000B
for legacy registers—block such packets at firewall.
7.4 Transaction Lifecycle & Socket Management
7.4.1 State diagram (client perspective)
CLOSED → TCP_SYN → ESTABLISHED
↑ ↓timeout ↓RST or 5xx
RETRY ←───┴────────── FIN_WAIT → TIME_WAIT → CLOSED
7.4.2 Concurrency patterns
Pattern | When to use | Caveat |
---|---|---|
Single socket, sequential TID | PLC polling one RTU loop via gateway | ~10 ms turnaround best case |
Pooled sockets (N) | SCADA station hitting 30+ IP slaves | N×TCP buffers – watch RAM |
Async multiplex, many outstanding TID | High-latency WAN links | Must match replies by TID, not sequence |
[Listing 1] Python asyncio
+ pymodbus
snippet opening 8 parallel sockets with independent TIDs.
7.4.3 Idle timeout & keep-alive
- IEC 61131 PLCs often close socket after 5 s idle (resource thrash).
- Enable SO_KEEPALIVE (Linux defaults 2 h—raise to 30 s for WAN).
- If NAT, send zero-length TCP ACK probe every < NAT-timeout (usually 60 s).
7.5 Performance & Determinism
Factor | Effect | Mitigation |
---|---|---|
Nagle Algorithm | Buffers small frames → adds 40-200 ms | TCP_NODELAY |
Delayed ACK | Server waits 40 ms before ACKing | Combine with NODELAY; or send 2 frames/RTT |
Segmentation Offload (TSO) | NIC lumps frames into jumbo | Disable on low-latency motion links |
Switch store-and-forward | 5–10 µs per hop — negligible | Cascade ≤ 3 switches for hard 1 ms loops |
[Fig-7-3] Latency vs packet size graph showing nodal contributions.
Throughput formula (single socket, FC03, 125 regs): Tcyc≈(req+resp)×8link Mbit+RTTLANT_\text{cyc} \approx \frac{(\text{req}+ \text{resp}) \times 8}{\text{link Mbit}} + \text{RTT}_{\text{LAN}}
@100 Mbit: 261 B payload → 15 µs wire + 50 µs switch + 50 µs PLC parse ≈ 115 µs—orders faster than RS-485.
7.6 Unit Identifier & Gateway Multiplexing
7.6.1 How a serial-to-TCP gateway routes
TCP 502 (Unit-ID 17) ─┐
├─> RS-485 bus #1 → Slave 17
TCP 502 (Unit-ID 05) ─┤
Gateway prepends Serial Addr = Unit-ID; appends CRC; pushes out.
7.6.2 Common issues
Symptom | Root cause | Fix |
---|---|---|
All slaves reply “device busy” | Multiple TCP clients hitting same Unit-ID → gateway queues | Use client-side semaphore or dedicate gateway |
Wrong slave answers | Gateway in “transparent” mode ignoring Unit-ID | Switch to “addressed” mode or embed addr in register map |
Broadcast fails | Many gateways drop Unit-ID 0 | Use per-slave write loop instead |
7.7 Connection-State Pitfalls
- TIME_WAIT storms — closing 100 sockets/s on Linux = 100 × 60 s ephemeral hold; tune
net.ipv4.tcp_fin_timeout
or reuse sockets. - NAT hair-pinning — inside → public IP → same router; older NATs drop port 502 → use local VIP.
- Deep Packet Inspection firewalls — will block if Length field doesn’t match actual bytes; verify gateway doesn’t pad payload.
7.8 Minimal Hardening Checklist (pre-MBSec)
Layer | Defence | CLI/Setting |
---|---|---|
Network | VLAN or VRF isolate port 502 | switchport access vlan 30 |
Transport | Restrict ingress to whitelisted IPs | iptables -p tcp --dport 502 -s 10.0.0.0/24 -j ACCEPT |
Application | Disable writes on critical slaves | Gateway ACL / PLC data-diode |
Crypto (preview) | MBSec (TLS 1.3, X.509, server & client auth) | Supported by HMS Anybus SG 4, Schneider M241 fw ≥ 5.3 |
7.9 Implementation Patterns & Code
7.9.1 Python + pymodbus
synchronous example
from pymodbus.client import ModbusTcpClient
cli = ModbusTcpClient('192.168.10.55', port=502)
cli.connect()
for tid in range(1000):
rr = cli.read_holding_registers(0, 6, unit=0x11)
assert rr.registers[0] < 4000 # simple range check
cli.close()
7.9.2 C (libmodbus) non-blocking
Listing 2 shows libmodbus
+ poll()
with three concurrent slaves.
7.9.3 Async IO / epoll pattern
- One socket per slave or one shared socket with Transaction-ID ring.
- Await
reader.readexactly(length)
wherelength
= MBAP.len – 1.
7.10 Diagnostics with Wireshark
Task | Filter / tool | Tip |
---|---|---|
Isolate Modbus traffic | tcp.port == 502 | Colourise foreground green |
Show only PDUs | modbus display filter | Requires Wireshark dissector ≥ v3 |
Highlight slow responses | Statistics → I/O Graph → tcp.time_delta | Peaks > 0.1 s = suspect Nagle |
Reassemble exception | Follow → TCP Stream | FC+0x80 visible |
[Fig-7-4] Screenshot: dual-colour request/response ladder, MBAP decoded in info column.
7.11 Best-Practice Recap
- One PDU, seven-byte MBAP = “carry-anywhere” core—learn fields by heart.
- Disable Nagle (
TCP_NODELAY
) when cycle < 100 ms. - Reuse sockets — reconnect storms trigger firewall throttling.
- Unit-ID discipline: unique per serial slave behind gateway; 255 = broadcast, seldom supported.
- Secure the pipe now—VLAN + ACL at minimum; migrate to MBSec/TLS where vendor offers it.
Visual & downloadable assets to create
ID | Asset | Purpose |
---|---|---|
Fig-7-1 | OSI onion diagram | Mental model |
Fig-7-2 | Annotated MBAP header | Byte cheat-sheet |
Fig-7-3 | Latency vs optimisation graph | Show NODELAY effect |
Fig-7-4 | Wireshark ladder capture | Hands-on reference |
Listing 1-3 | Ready-to-run code files | Git repo / zip download |
Up Next
Chapter 8 — Modbus TCP Implementation: Sockets, NAT & Network Nuances will translate today’s theory into hardened network topologies, static vs DHCP, dual-home gateways, VLAN QoS, and cross-protocol comparisons (EtherNet/IP, Profinet). Keep Wireshark open and prepare to tweak firewall rules.