# Module 12: Modbus TCP

> MBAP, unit identifiers, ports, and what changes (and doesn't) moving from RTU.

**Phase:** Physical Network & Transport  
**Estimated time:** 8 min  
**Release status:** Published  
**Content version:** 1.0.0  
**Source:** https://learnmodbus.studioseventeen.io/lesson/modbus-tcp

## What you'll be able to do
- Parse the 7-byte MBAP header from a capture.
- Explain when Unit ID matters and when 0xFF is fine.
- Predict what a Modbus TCP frame for FC 03 looks like end-to-end.

## Three things people get wrong
1. **Appending a CRC over TCP.** TCP handles integrity. No CRC bytes belong in a Modbus TCP frame.
2. **Hard-coding Unit ID 1.** Through a gateway, Unit ID picks the serial address. Hard-coding 1 breaks multi-drop topologies.
3. **Ignoring MBAP length.** Length covers Unit ID + PDU. Mis-calculating it confuses servers that strictly validate.

## From the field
**Port 502 is not magic**

An IT team blocked port 502 'because it's unencrypted' without realizing the SCADA box used it for every poll. Production trends went flat for an hour. The eventual compromise was a Modbus Security gateway on 802 and a documented allowlist — same protocol, different posture.

## References
- [Modbus Messaging on TCP/IP V1.0b](https://www.modbus.org/file/secure/messagingimplementationguide.pdf) — MBAP header — section 4.1

## Lesson

# Module 12 Lesson: Modbus TCP And MBAP
## Learner Outcome

By the end of this module, learners can label the Modbus TCP ADU, explain the MBAP header, distinguish true Modbus TCP from RTU-over-TCP, use Transaction Identifier and Unit Identifier evidence correctly, and identify when a TCP-side problem should be handled before changing register-map assumptions.

## Key Concepts

- Modbus TCP carries the Modbus PDU over TCP/IP.
- True Modbus TCP uses a 7-byte MBAP header and does not include the serial address, RTU CRC, ASCII LRC, or RTU timing gaps.
- The MBAP header contains Transaction Identifier, Protocol Identifier, Length, and Unit Identifier.
- MBAP fields are big-endian.
- TCP port `502` is registered for `mbap`; Modbus Security uses a different TLS-protected service path covered later.
- The Unit Identifier matters most through gateways, where it may select a downstream serial server.
- TCP connection success does not prove the Unit Identifier, function code, PDU address, quantity, or downstream path is correct.

## Key Terms

- MBAP: Modbus Application Protocol header used by Modbus TCP.
- Transaction Identifier: MBAP field used to associate a response with a request.
- Protocol Identifier: MBAP field that is `0x0000` for Modbus.
- Length: MBAP field that counts following bytes, including Unit Identifier plus PDU.
- Unit Identifier: MBAP field commonly relevant through gateways; accepted values are product/path-specific.
- True Modbus TCP: Modbus PDU carried with an MBAP header and no RTU CRC.
- RTU-over-TCP: product/tool-specific transport of serial-style bytes over a TCP socket.

## Source-Grounded Explanation

Modbus TCP is not RTU with an Ethernet cable. The PDU still carries the Modbus operation, but the ADU changes:

```text
Modbus PDU:      Function Code | Data
Serial RTU ADU:  Address | Function Code | Data | CRC Low | CRC High
Modbus TCP ADU:  MBAP Header | Function Code | Data
```

MBAP header:

| Field | Size | Course Meaning | Source Boundary |
|---|---:|---|---|
| Transaction Identifier | 2 bytes | Pairs a response with a request. | Official TCP guide; client/server behavior still needs tool examples. |
| Protocol Identifier | 2 bytes | `0x0000` for Modbus. | Official TCP guide. |
| Length | 2 bytes | Number of following bytes, including Unit Identifier and PDU. | Official TCP guide; learners should count bytes. |
| Unit Identifier | 1 byte | Identifies a remote unit, commonly through gateways, or a server-specific unit. | Gateway/native-device behavior is product-specific. |

All MBAP fields are big-endian. True Modbus TCP has no Modbus CRC because it relies on TCP/IP transport checks for delivery and integrity at that layer. This is not security: classic Modbus TCP is still plaintext and normally lacks authentication or authorization.

### Unit Identifier

The Unit Identifier is a common source of field confusion:

- Native Modbus TCP devices may ignore or require specific request Unit Identifier values. Responses should echo the request Unit Identifier, but accepted request values remain vendor/manual-specific.
- TCP-to-serial gateways often use it to choose the downstream serial server address.
- A wrong Unit Identifier through a gateway can look like a timeout, wrong target, or gateway exception.
- The course should not teach one universal Unit Identifier value. Use the device manual, gateway manual, or a controlled test.

### Message Boundaries

TCP is a stream. A Modbus TCP receiver should not assume one TCP segment equals one Modbus message. It should use the MBAP Length field and function-specific structure to reconstruct the ADU. For learners, the practical habit is to count from the Unit Identifier through the end of the PDU and compare that count to the Length field.

## Practical Example: Read Holding Register `40010`

Scenario: A Modbus TCP client reads one holding register from a simulator or saved byte fixture.

Request:

```modbus-tcp
00 2A 00 00 00 06 FF 03 00 09 00 01
```

| Field | Example Value |
|---|---|
| Transport | Modbus TCP |
| TCP port | `502` registered service name `mbap` |
| Transaction Identifier | `0x002A` |
| Protocol Identifier | `0x0000` |
| Length | `0x0006`, counting Unit Identifier plus PDU bytes that follow |
| Unit Identifier | `0xFF` in this fixture |
| Function code | `0x03` Read Holding Registers |
| Documented address | `40010` if the map uses one-based `40001` notation |
| Actual PDU address | `0x0009` |
| Quantity | `0x0001` register |
| Data type and scale | unsigned 16-bit, scale `/10 V` |

Normal response fixture:

```modbus-tcp
00 2A 00 00 00 05 FF 03 02 09 C4
```

| Response Field | Meaning |
|---|---|
| Transaction Identifier `0x002A` | Matches the request. |
| Protocol Identifier `0x0000` | Modbus. |
| Length `0x0005` | Unit Identifier plus function, byte count, and two data bytes. |
| Unit Identifier `0xFF` | Echoes the request fixture value. |
| Function `0x03` | Normal read response. |
| Byte count `0x02` and data `09 C4` | One 16-bit register, raw value `2500`; with the synthetic map scale, this means `250.0 V`. |

Exception response fixture:

```modbus-tcp
00 2A 00 00 00 03 FF 83 02
```

This is a valid Modbus TCP exception response: same transaction, protocol `0`, length `3`, Unit Identifier `0xFF`, exception function `0x83`, and exception code `0x02` Illegal Data Address.

## RTU-Over-TCP Contrast

True Modbus TCP:

```modbus-tcp
00 2A 00 00 00 06 FF 03 00 09 00 01
```

RTU-style bytes sometimes carried over a TCP socket by nonstandard or gateway-specific products:

```modbus-rtu
11 03 00 09 00 01 56 98
```

The second byte string has a serial address and RTU CRC, not an MBAP header. Some client tools expose a mode such as "RTU over TCP" or "Modbus RTU encapsulated in TCP." Treat that as product/tool behavior and verify it against the gateway or device manual.

## Common Pitfalls

- Looking for an RTU CRC in true Modbus TCP traffic.
- Treating TCP connection success as proof that the Unit Identifier or downstream gateway path is correct.
- Assuming TCP segment boundaries are Modbus message boundaries.
- Treating Unit Identifier as irrelevant on every network.
- Selecting RTU-over-TCP when the device expects true Modbus TCP, or the reverse.
- Treating port `502` as a security control. It is a service port, not authentication.
- Changing register addresses when the actual problem is blocked TCP, wrong Unit Identifier, wrong mode, gateway timeout, or a malformed MBAP length.

## Instructor Flow

1. Start with the PDU from Module 11 and wrap it in MBAP instead of RTU or ASCII.
2. Label one request byte string and count the MBAP Length field.
3. Compare normal response and exception response fixtures.
4. Show RTU-over-TCP bytes and ask why they are not true Modbus TCP.
5. Show the offline generated PCAP fixture as optional instructor evidence, but leave Wireshark filters, screenshots, live simulator capture, and PyModbus runs as tool-verified extensions until versions and expected outputs are pinned.

## Check-Your-Understanding

1. Which MBAP field pairs a response with its request?
2. Why does true Modbus TCP not include the RTU CRC?
3. In `00 2A 00 00 00 06 FF 03 00 09 00 01`, what bytes are counted by the Length field?
4. Why might a gateway path fail even when TCP port `502` is open?
5. What clue tells you that `11 03 00 09 00 01 56 98` is not a true Modbus TCP ADU?

## Source Notes

- Official TCP basis: Modbus Messaging on TCP/IP Implementation Guide V1.0b for MBAP fields, Modbus TCP ADU structure, connection/gateway concepts, and port `502` usage.
- Official protocol basis: Modbus Application Protocol Specification V1.1b3 for PDU structure, function code `03`, exception-response shape, and exception code `02`.
- Primary registry basis: IANA Service Name and Transport Protocol Port Number Registry for `mbap` on TCP `502`.
- Official citation targets: [official-citation-map.md](https://learnmodbus.studioseventeen.io/resource/official-citation-map) rows for MBAP header fields, MBAP Length field, Unit Identifier and gateway routing, TCP connection behavior and concurrency, IANA service name `mbap` on port `502`, PDU vs ADU and general frame structure, normal response vs exception response, and exception codes and exception response PDU shape.
- Tool behavior: Wireshark display filters, PyModbus behavior, simulator output, and packet-capture screenshots require version-pinned verification before release.
- Synthetic source: saved byte fixtures and `fixtures/packet-captures/modbus_tcp_synthetic_v1.pcap` are course-authored examples, not production or real-device traffic.

## Completion Checkpoint

Before moving on, learners should count the MBAP Length field, identify the Unit Identifier, separate MBAP from PDU bytes, and explain why true Modbus TCP does not include an RTU CRC.

## Reusable Assets

- [Modbus TCP packet inspection worksheet](https://learnmodbus.studioseventeen.io/resource/modbus-tcp-packet-inspection-worksheet)
- [Modbus TCP MBAP inspection lab](https://learnmodbus.studioseventeen.io/lesson/modbus-tcp?page=lab-1)
- [Generated synthetic PCAP fixture](https://learnmodbus.studioseventeen.io/api/public/fixtures/modbus_tcp_synthetic_v1.pcap)
- [Module 12 quiz](https://learnmodbus.studioseventeen.io/lesson/modbus-tcp?page=quiz)
- [RTU/ASCII frame worksheet](https://learnmodbus.studioseventeen.io/resource/rtu-ascii-frame-worksheet)

## Diagram source

```mermaid
flowchart LR
  subgraph MBAP["MBAP header"]
    TI["Tx ID (2)"] --> PI["Proto ID (2)"] --> LN["Length (2)"] --> UI["Unit ID (1)"]
  end
  MBAP --> PDU["PDU<br/>FC + data"]
```

## Labs

### Lab 1

# Lab 5: Modbus TCP MBAP Inspection Without A Capture Tool
## Learner Outcome

Learners can inspect saved Modbus TCP byte strings, label MBAP fields, count the Length field, distinguish normal and exception responses, and recognize RTU-over-TCP bytes that are not true Modbus TCP.

## Prerequisites

- Module 2: PDU and ADU.
- Module 4: function codes and exception responses.
- Module 5: documented address versus PDU address.
- Module 11: RTU/ASCII frames and CRC/LRC.
- Module 12 lesson through MBAP fields.

## Safety And Scope

This is a no-hardware and no-network lab. It uses saved byte strings and a synthetic generated PCAP fixture, not live captures. Do not scan networks or connect to production devices. Later Wireshark extensions must use version-pinned tools and expected outputs.

## Scenario

A learner receives saved byte strings from a training simulator. The task is to label the Modbus TCP ADU before changing register-map assumptions.

## Packet Cards

| Card | Saved Bytes | Context |
|---|---|---|
| A | `00 2A 00 00 00 06 FF 03 00 09 00 01` | True Modbus TCP request. Read one holding register. |
| B | `00 2A 00 00 00 05 FF 03 02 00 FA` | True Modbus TCP normal response. |
| C | `00 2A 00 00 00 03 FF 83 02` | True Modbus TCP exception response. |
| D | `11 03 00 09 00 01 56 98` | RTU-style bytes carried elsewhere; not true Modbus TCP. |
| E | `00 2A 00 00 00 06 FF 03 02 00 FA` | Looks like a response but has a Length mismatch. |
| F | `00 2A 00 01 00 06 FF 03 00 09 00 01` | Protocol Identifier is not `0x0000`. |

## Procedure

1. For each card, classify the byte string as true Modbus TCP, Modbus TCP response with a defect, RTU-style bytes, or invalid/needs rejection.
2. Label Transaction Identifier, Protocol Identifier, Length, Unit Identifier, function code, and PDU fields when present.
3. Count the bytes covered by the MBAP Length field.
4. Predict the diagnostic bucket: normal response, exception response, malformed/invalid ADU, wrong mode, or needs tool/device evidence.
5. State what not to change yet.

## Worksheet

| Card | Classification | Transaction ID | Protocol ID | Length Check | Unit ID | PDU Meaning | Expected Result | What Not To Change Yet |
|---|---|---|---|---|---|---|---|---|
| A |  |  |  |  |  |  |  |  |
| B |  |  |  |  |  |  |  |  |
| C |  |  |  |  |  |  |  |  |
| D |  |  |  |  |  |  |  |  |
| E |  |  |  |  |  |  |  |  |
| F |  |  |  |  |  |  |  |  |

## Answer Key

| Card | Classification | Transaction ID | Protocol ID | Length Check | Unit ID | PDU Meaning | Expected Result | What Not To Change Yet |
|---|---|---|---|---|---|---|---|---|
| A | True Modbus TCP request | `0x002A` | `0x0000` | `6` bytes: Unit ID + function + address + quantity | `0xFF` | FC `03`, PDU address `0x0009`, quantity `1` | normal response or exception depending on server/map | data type, scale, word order |
| B | True Modbus TCP normal response | `0x002A` | `0x0000` | `5` bytes: Unit ID + function + byte count + two data bytes | `0xFF` | FC `03`, byte count `2`, raw `00 FA` | valid normal response | TCP mode, Unit ID, address if value is plausible |
| C | True Modbus TCP exception response | `0x002A` | `0x0000` | `3` bytes: Unit ID + exception function + code | `0xFF` | exception function `83`, code `02` Illegal Data Address | valid exception response | TCP port, CRC, physical serial wiring |
| D | RTU-style bytes, not true Modbus TCP | none | none | no MBAP header | serial address `0x11`, not Unit ID | FC `03`, PDU address `0x0009`, quantity `1`, RTU CRC `56 98` | wrong mode if expected true Modbus TCP | MBAP Unit ID or Length, because there is no MBAP |
| E | Defective Modbus TCP-like response | `0x002A` | `0x0000` | says `6`, but only `5` bytes follow | `0xFF` | FC `03`, byte count `2`, raw `00 FA` | malformed/invalid ADU evidence | register address and scale |
| F | Invalid/unsupported for Modbus TCP fixture | `0x002A` | `0x0001` | length would otherwise count | `0xFF` | FC `03`, PDU address `0x0009`, quantity `1` | protocol ID is not Modbus `0` | Unit ID and register map until protocol ID is resolved |

## Expected Observations

- A true Modbus TCP ADU starts with an MBAP header, not a serial server address and CRC.
- A valid exception response is still a Modbus response.
- The Length field gives a fast way to catch malformed byte strings.
- Unit Identifier evidence matters most when a gateway or product manual says it matters.

## Troubleshooting Notes

- TCP connection success does not prove the Modbus ADU is valid.
- If the Protocol Identifier is not `0`, stay in the packet/protocol evidence layer.
- If the byte string is RTU-over-TCP, switch the client/tool mode or verify the gateway manual before changing register addresses.
- Keep Wireshark display-filter names out of the answer key until verified against the course toolchain.

## Check Questions

1. Which card is a normal Modbus TCP response?
2. Which card proves an exception response can still be a valid Modbus response?
3. Which card is RTU-style framing rather than true Modbus TCP?
4. Which card has a bad Length field?
5. Why should a wrong Unit Identifier be considered before changing PDU address through a TCP-to-serial gateway?

## Instructor Notes

- Ask learners to count Length aloud from Unit Identifier through the PDU.
- Use Card D to connect Module 11 and Module 12 without blending the two modes.
- Optional generated PCAP fixture: `fixtures/packet-captures/modbus_tcp_synthetic_v1.pcap`.
- Add a Wireshark version-pinned extension only after `packet-capture-lab-notes.md` and [Packet Tool Evidence Appendix](https://learnmodbus.studioseventeen.io/resource/packet-tool-evidence-appendix) contain verified field names, fixture hash evidence, expected-output comparison, and screenshots.

## Source Notes

- Official TCP source: Modbus Messaging on TCP/IP Implementation Guide V1.0b for MBAP fields and TCP ADU structure.
- Official protocol source: Modbus Application Protocol Specification V1.1b3 for function `03`, exception response shape, and exception code `02`.
- Primary registry source: IANA service-name registry for TCP `502` service name `mbap`.
- Synthetic source: packet cards and `fixtures/packet-captures/modbus_tcp_synthetic_v1.pcap` are course-authored fixtures, not captured production or real-device traffic.

## Knowledge check

# Module 12 Quiz: Modbus TCP And MBAP
## Questions

### Multiple Choice

1. Which fields are in the MBAP header?
   - A. Serial address, function code, data, CRC.
   - B. Transaction Identifier, Protocol Identifier, Length, Unit Identifier.
   - C. Colon, ASCII hex, LRC, CRLF.
   - D. Scale, unit, word order, quality.

2. In `00 2A 00 00 00 06 FF 03 00 09 00 01`, what does Length `0x0006` count?
   - A. The whole Ethernet frame.
   - B. The following bytes: Unit Identifier plus the PDU.
   - C. Only the function code.
   - D. The RTU CRC.

3. Which statement is safest about true Modbus TCP?
   - A. It includes the RTU CRC after the PDU.
   - B. It always ignores the Unit Identifier.
   - C. It carries an MBAP header plus PDU and has no serial CRC/LRC.
   - D. It is secure because it uses port `502`.

4. A TCP-to-serial gateway accepts a TCP connection on port `502`, but the downstream serial device does not answer. Which field should be checked before changing the register address?
   - A. Unit Identifier.
   - B. Word order.
   - C. Engineering-unit scale.
   - D. ASCII LRC.

5. Which byte string is not true Modbus TCP?
   - A. `00 2A 00 00 00 06 FF 03 00 09 00 01`
   - B. `00 2A 00 00 00 05 FF 03 02 00 FA`
   - C. `11 03 00 09 00 01 56 98`
   - D. `00 2A 00 00 00 03 FF 83 02`

6. In the Modbus TCP request `00 2A 00 00 00 06 FF 03 00 09 00 01`, which labeling of the MBAP header bytes is correct?
   - A. Transaction Identifier `00 2A`, Protocol Identifier `00 00`, Length `00 06`, Unit Identifier `FF`.
   - B. Unit Identifier `00 2A`, Length `00 00`, Protocol Identifier `00 06`, Transaction Identifier `FF`.
   - C. Protocol Identifier `00 2A`, Transaction Identifier `00 00`, Unit Identifier `00 06`, Length `FF`.
   - D. Serial address `00`, function code `2A`, CRC `00 00`, Length `00 06`.

7. In a true Modbus TCP MBAP header, what value should the Protocol Identifier carry, and which registered TCP port does the service use?
   - A. Protocol Identifier `0x0006`, port `102`.
   - B. Protocol Identifier `0xFFFF`, port `44818`.
   - C. Protocol Identifier `0x0000`, port `502`.
   - D. Protocol Identifier equal to the Transaction Identifier, port `502`.

### Short Answer

1. Explain why true Modbus TCP has no RTU CRC.
2. Explain what Transaction Identifier is used for.
3. Give one native-device and one gateway reason why Unit Identifier handling must be verified.
4. Why should learners count the MBAP Length field before interpreting a value?

### Applied Scenario

A learner sees this saved Modbus TCP byte string:

```text
00 2A 00 00 00 05 FF 03 02 00 FA
```

Tasks:

1. Label the Transaction Identifier.
2. Label the Protocol Identifier.
3. Explain why the Length field is `0x0005`.
4. Label the Unit Identifier.
5. Classify the response as normal, exception, malformed, or wrong mode.
6. Explain what not to change yet.

## Answer Key

1. Multiple choice: B. MBAP contains Transaction Identifier, Protocol Identifier, Length, and Unit Identifier.
2. Multiple choice: B. Length counts the bytes following the Length field: Unit Identifier plus PDU.
3. Multiple choice: C. True Modbus TCP carries MBAP plus PDU and does not include serial CRC/LRC. Port `502` is not a security control.
4. Multiple choice: A. Through a TCP-to-serial gateway, Unit Identifier often selects the downstream serial server.
5. Multiple choice: C. `11 03 00 09 00 01 56 98` is RTU-style framing with serial address and CRC, not true Modbus TCP.
6. Multiple choice: A. The MBAP header is Transaction Identifier `00 2A`, Protocol Identifier `00 00`, Length `00 06`, Unit Identifier `FF`, followed by the PDU `03 00 09 00 01`. The other options scramble field order or apply serial RTU framing to a TCP ADU.
7. Multiple choice: C. The Protocol Identifier is `0x0000` for Modbus, and the registered service `mbap` uses TCP port `502`. `0x0006` is this frame's Length, `0xFFFF` is not a valid Protocol Identifier, and ports `102`/`44818` belong to other protocols.

## Distractor Review Notes

| Question | Correct | Why The Distractors Are Wrong |
|---:|---|---|
| 1 | B | A is serial RTU framing, C is ASCII framing, and D is source-map meaning rather than MBAP structure. |
| 2 | B | A counts outside Modbus TCP ADU semantics, C ignores the rest of the bytes after Length, and D adds a serial error check not present in true TCP. |
| 3 | C | A adds RTU CRC to true TCP, B overgeneralizes Unit Identifier handling, and D treats port `502` as security. |
| 4 | A | B/C are value interpretation details, and D is an ASCII check byte rather than a TCP gateway routing field. |
| 5 | C | A/B/D are valid MBAP plus PDU shapes; C is serial-style framing without an MBAP header. |
| 6 | A | B/C shuffle the MBAP field order, and D reads the TCP ADU as an RTU frame with serial address and CRC. |
| 7 | C | A uses the Length value as the Protocol Identifier and the wrong port, B invents an invalid Protocol Identifier and port, and D confuses Protocol Identifier with Transaction Identifier. |

Short answer:

1. True Modbus TCP relies on TCP/IP transport checks for delivery/integrity at that layer and uses MBAP length/framing instead of RTU timing and CRC. This does not make it authenticated or encrypted.
2. Transaction Identifier pairs a response with its request, especially when multiple requests or connections may be active.
3. Native devices may ignore, require, or document a fixed Unit Identifier; gateways may use Unit Identifier to route to a downstream serial server.
4. A wrong Length field can indicate malformed bytes or wrong mode. Interpreting data values before confirming the ADU shape can send troubleshooting into the wrong layer.

Applied scenario:

1. Transaction Identifier: `0x002A`.
2. Protocol Identifier: `0x0000`.
3. Length `0x0005` counts `FF 03 02 00 FA`: Unit Identifier, function, byte count, and two data bytes.
4. Unit Identifier: `0xFF`.
5. Normal Modbus TCP read response.
6. Do not change TCP mode, Unit Identifier, PDU address, data type, word order, scale, or unit without a symptom. First record the raw value `00 FA` and decode it against the source map.

## Source Notes

- Official citation targets: [official-citation-map.md](https://learnmodbus.studioseventeen.io/resource/official-citation-map) rows for MBAP header fields, MBAP Length field, Unit Identifier and gateway routing, TCP connection behavior and concurrency, IANA service name `mbap` on port `502`, PDU vs ADU and general frame structure, normal response vs exception response, exception codes and exception response PDU shape, and RTU/ASCII frame fields where true TCP is contrasted with serial framing.
- Saved byte strings remain course-authored fixtures until packet-capture and tool-version evidence is pinned.

---
_Learn Modbus · learnmodbus.studioseventeen.io · Module 12/16 · v1.0.0 · Exported for offline / agent use._
