Skip to content
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
a36e594
Add ModbusLayer first implementation with header parsing only
yahyayozo May 18, 2025
88f69d4
fix static_assert
yahyayozo May 18, 2025
c257494
Merge branch 'dev' into feature/modbus
yahyayozo May 18, 2025
348afc6
Fix formatting in ModbusLayer header file
yahyayozo May 21, 2025
1f65c24
Add setter methods for MODBUS header fields in ModbusLayer
yahyayozo May 21, 2025
4159c6c
Merge branch 'dev' into feature/modbus
yahyayozo May 21, 2025
58c18ec
Refactor ModbusLayer class documentation and fix syntax error
yahyayozo May 23, 2025
d50db3b
Merge branch 'feature/modbus' of https://github.com/yahyayozo/PcapPlu…
yahyayozo May 23, 2025
cfcbc69
Fix endianess handling in ModbusLayer getters and setters
yahyayozo May 23, 2025
28dd176
Add const qualifier to ModbusLayer getter methods
yahyayozo May 23, 2025
34d65a2
Add constructors to ModbusLayer for initializing from user inputs
yahyayozo May 23, 2025
74ab79d
Add Modbus protocol constant to ProtocolType
yahyayozo May 24, 2025
166c15c
Add const qualifier to ModbusLayer getter methods in .h file
yahyayozo May 28, 2025
3624286
Add ModbusLayer and corresponding tests to Packet++Test
yahyayozo May 29, 2025
bcd14b5
Fix length assignment in ModbusLayer constructor to use htobe16
yahyayozo May 31, 2025
9f4ebbd
Refactor ModbusLayerParsingTest to create ModbusLayer from a dat file…
yahyayozo Jun 4, 2025
4ceb41d
Fix formatting in ModbusLayer constructor for improved readability
yahyayozo Jun 15, 2025
adb3f51
Remove redundant ModbusLayerParsingTest case from ModbusTests.cpp
yahyayozo Jun 15, 2025
839aa49
Remove ModbusLayerParsingTest from test definitions and main execution
yahyayozo Jun 15, 2025
dc04c0c
Fix newline at end of file in ModbusLayerCreationTest and TestDefinit…
yahyayozo Jun 15, 2025
f4f5291
Add ModbusLayer parsing test and utility function for port validation
yahyayozo Jun 16, 2025
24c0924
Merge branch 'dev' into feature/modbus
yahyayozo Jun 16, 2025
32da5e5
Add missing includes for iostream and iomanip in ModbusLayer.cpp
yahyayozo Jun 17, 2025
da52bf7
Refactor ModbusLayerCreationTest to include additional assertions for…
yahyayozo Jun 17, 2025
c8bd099
Merge branch 'feature/modbus' of https://github.com/yahyayozo/PcapPlu…
yahyayozo Jun 17, 2025
b96671c
Merge branch 'dev' into feature/modbus
yahyayozo Jul 21, 2025
5dd18fb
Merge branch 'dev' into feature/modbus
yahyayozo Jul 30, 2025
4b38a65
Refactor Modbus header structure and update related methods for consi…
yahyayozo Jul 30, 2025
65b37f6
Merge branch 'feature/modbus' of https://github.com/yahyayozo/PcapPlu…
yahyayozo Jul 30, 2025
32211b8
Fix ModbusLayer::toString method declaration for consistency
yahyayozo Jul 31, 2025
0f001b6
Update ModbusRequest.dat and ModbusTests.cpp for improved test accura…
yahyayozo Aug 3, 2025
adea8b5
Add modbus.pcap example file for packet testing
yahyayozo Aug 3, 2025
530c8cc
Refactor Modbus function code handling for type consistency
yahyayozo Aug 3, 2025
73228fb
Merge branch 'dev' into feature/modbus
yahyayozo Aug 4, 2025
12b1650
adjust modbus creation test
yahyayozo Aug 7, 2025
494d84a
formatting
yahyayozo Aug 7, 2025
737e6de
Merge branch 'dev' into feature/modbus
yahyayozo Aug 7, 2025
9b50332
chore: add Modbus to readme
yahyayozo Aug 8, 2025
75dff34
add the modbus function code as an enum clas
yahyayozo Aug 10, 2025
a5b13c7
added struct for read coil function
yahyayozo Aug 11, 2025
3e56991
Merge branch 'dev' into feature/modbus
yahyayozo Aug 11, 2025
18a80df
refactor
yahyayozo Aug 12, 2025
3c746e9
Merge branch 'dev' into feature/modbus
yahyayozo Aug 12, 2025
ec3e582
fix: wrong header length in modbus
yahyayozo Aug 12, 2025
4a8f115
Merge branch 'feature/modbus' of https://github.com/yahyayozo/PcapPlu…
yahyayozo Aug 12, 2025
c50f58b
some refactoring
yahyayozo Aug 16, 2025
ea86788
Merge branch 'dev' into feature/modbus
yahyayozo Aug 16, 2025
0cd7502
formatting
yahyayozo Aug 16, 2025
fc595a7
formatting
yahyayozo Aug 16, 2025
951fe03
style
yahyayozo Aug 16, 2025
93fb5e7
refactor: adjust comment style and enum case
yahyayozo Aug 23, 2025
a7b926d
refactor
yahyayozo Aug 25, 2025
8bc7c47
refactor: fixing comments
yahyayozo Aug 25, 2025
e3461d0
fix: remove _deps
yahyayozo Aug 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Packet++/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ add_library(
src/Layer.cpp
src/LdapLayer.cpp
src/LLCLayer.cpp
src/ModbusLayer.cpp
src/MplsLayer.cpp
src/NdpLayer.cpp
src/NflogLayer.cpp
Expand Down
177 changes: 177 additions & 0 deletions Packet++/header/ModbusLayer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#pragma once

#include "Layer.h"

/// @file
/// This file contains classes for parsing, creating and editing Modbus packets.

/// @namespace pcpp
/// @brief The main namespace for the PcapPlusPlus lib
namespace pcpp
{

#pragma pack(push, 1)
/// @struct modbus_header
/// MODBUS Application Protocol header
struct modbus_header
{
/// For synchronization between messages of server and client
uint16_t transactionId;
/// 0 for Modbus/TCP
uint16_t protocolId;
/// Number of remaining bytes in this frame starting from the unit id
uint16_t length;
/// Unit identifier
uint8_t unitId;
/// Function code
uint8_t functionCode;
};
#pragma pack(pop)
static_assert(sizeof(modbus_header) == 8, "modbus_header size is not 8 bytes");

/// @class ModbusLayer
/// Represents the MODBUS Application Protocol layer
class ModbusLayer : public Layer
{
public:
/**
* @brief Enum class representing Modbus function codes.
*
* This enumeration defines the standard Modbus function codes used in request and response PDUs.
* Each value corresponds to a specific operation defined by the Modbus protocol.
*
*/
enum class ModbusFunctionCode : uint8_t
{
/** Read coil status (0x01) */
READ_COILS = 1,

/** Read discrete input status (0x02) */
READ_DISCRETE_INPUTS = 2,

/** Read holding registers (0x03) */
READ_HOLDING_REGISTERS = 3,

/** Read input registers (0x04) */
READ_INPUT_REGISTERS = 4,

/** Write a single coil (0x05) */
WRITE_SINGLE_COIL = 5,

/** Write a single holding register (0x06) */
WRITE_SINGLE_REGISTER = 6,

/** Write multiple coils (0x0F) */
WRITE_MULTIPLE_COILS = 15,

/** Write multiple holding registers (0x10) */
WRITE_MULTIPLE_REGISTERS = 16,

/** Report slave ID (0x11) */
REPORT_SLAVE_ID = 17,

/** Limit to check if the function code is valid */
FUNCTION_CODE_LIMIT,

/** Unknown or unsupported function code (0xFF) */
UNKNOWN_FUNCTION = 0xFF
};

/// @struct ModbusReadInputRegisters
/// Represents a Modbus request to read input registers.
struct ModbusReadInputRegisters
{
uint16_t startingAddress; ///< Starting address of the input registers to read
uint16_t quantity; ///< Number of input registers to read
};
Comment on lines +73 to +79
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This struct is only used in getFunctionDataSize(). If we remove it we can remove this struct as well


/// A constructor that creates the layer from an existing packet raw data
/// @param[in] data A pointer to the raw data
/// @param[in] dataLen Size of the data in bytes
/// @param[in] prevLayer A pointer to the previous layer
/// @param[in] packet A pointer to the Packet instance where layer will be stored in
ModbusLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet)
: Layer(data, dataLen, prevLayer, packet, Modbus)
{}

/// A constructor that creates the layer from user inputs
/// @param[in] transactionId Transaction ID
/// @param[in] unitId Unit ID
/// @param[in] functionCode Function code
ModbusLayer(uint16_t transactionId, uint8_t unitId, ModbusFunctionCode functionCode);

/// @brief Check if a port is a valid MODBUS port
/// @param port Port number to check
/// @note MODBUS uses port 502, so this function checks if the port is equal to 502
/// @return true if the port is valid, false otherwise
static bool isModbusPort(uint16_t port)
{
return port == 502;
}

/// @return MODBUS message type
uint16_t getTransactionId() const;

/// @return MODBUS protocol id
uint16_t getProtocolId() const;

/// @return MODBUS remaining bytes in frame starting from the unit id
/// @note This is the length of the MODBUS payload + unit_id, not the entire packet
uint16_t getLength() const;

/// @return MODBUS unit id
uint8_t getUnitId() const;

/// @return MODBUS function code
ModbusFunctionCode getFunctionCode() const;

/// @brief set the MODBUS transaction id
/// @param transactionId transaction id
void setTransactionId(uint16_t transactionId);

/// @brief set the MODBUS header unit id
/// @param unitId unit id
void setUnitId(uint8_t unitId);

/// @brief set the MODBUS header function code
/// @param functionCode function code
void setFunctionCode(ModbusFunctionCode functionCode);

// Overridden methods

/// Does nothing for this layer (ModbusLayer is always last)
void parseNextLayer() override
{}

/// @brief Get the length of the MODBUS header
/// @return Length of the MODBUS header in bytes
size_t getHeaderLen() const override
{
return sizeof(modbus_header);
}

/// Does nothing for this layer
void computeCalculateFields() override
{}

/// @return A string representation of the layer most important data (should look like the layer description in
/// Wireshark)
std::string toString() const override;

/// @return The OSI Model layer this protocol belongs to
OsiModelLayer getOsiModelLayer() const override
{
return OsiModelApplicationLayer;
}

private:
/// @return A pointer to the MODBUS header
modbus_header* getModbusHeader() const;

/// @brief Get the size of the function data based on the function code
/// @param functionCode The MODBUS function code
/// @return The size of the function data in bytes, or -1 if unsupported
int16_t getFunctionDataSize(ModbusFunctionCode functionCode) const;
};

} // namespace pcpp
3 changes: 3 additions & 0 deletions Packet++/header/ProtocolType.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ namespace pcpp
/// Diagnostic over IP protocol (DOIP)
const ProtocolType DOIP = 59;

/// Modbus protocol
const ProtocolType Modbus = 60;

/// An enum representing OSI model layers
enum OsiModelLayer
{
Expand Down
101 changes: 101 additions & 0 deletions Packet++/src/ModbusLayer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#include "ModbusLayer.h"
#include "EndianPortable.h"
#include <iostream>
#include <iomanip>
#include <cstring>

namespace pcpp
{
ModbusLayer::ModbusLayer(uint16_t transactionId, uint8_t unitId, ModbusLayer::ModbusFunctionCode functionCode)
{
const int16_t pduSize = getFunctionDataSize(functionCode);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see that getFunctionDataSize() currently supports only one function code, which is ModbusFunctionCode::READ_INPUT_REGISTERS. In that case, maybe we can remove the functionCode parameter, remove the getFunctionDataSize() method, and set the function code in this constructor to be ModbusFunctionCode::READ_INPUT_REGISTERS

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment wasn't addressed

if (pduSize < 0)
{
std::cerr << "Unsupported function code: " << static_cast<int>(functionCode) << std::endl;
return;
}

const size_t headerLen = sizeof(modbus_header);

m_DataLen = headerLen + pduSize;
m_Data = new uint8_t[m_DataLen]{};
memset(m_Data, 0, m_DataLen);

// Initialize the header fields to default values
modbus_header* header = getModbusHeader();
header->transactionId = htobe16(transactionId);
header->protocolId = 0; // 0 for Modbus/TCP
header->length = htobe16(pduSize + 2); // Length includes unitId and functionCode
header->unitId = unitId;
header->functionCode = static_cast<uint8_t>(functionCode);
}

modbus_header* ModbusLayer::getModbusHeader() const
{
return (modbus_header*)m_Data;
}

uint16_t ModbusLayer::getTransactionId() const
{
return be16toh(getModbusHeader()->transactionId);
}

uint16_t ModbusLayer::getProtocolId() const
{
return be16toh(getModbusHeader()->protocolId);
}

uint16_t ModbusLayer::getLength() const
{
return be16toh(getModbusHeader()->length);
}

uint8_t ModbusLayer::getUnitId() const
{
return getModbusHeader()->unitId;
}

ModbusLayer::ModbusFunctionCode ModbusLayer::getFunctionCode() const
{
return static_cast<ModbusLayer::ModbusFunctionCode>(getModbusHeader()->functionCode);
}

void ModbusLayer::setTransactionId(uint16_t transactionId)
{
getModbusHeader()->transactionId = htobe16(transactionId);
}

void ModbusLayer::setUnitId(uint8_t unitId)
{
getModbusHeader()->unitId = unitId;
}

void ModbusLayer::setFunctionCode(ModbusLayer::ModbusFunctionCode functionCode)
{
if (functionCode >= ModbusLayer::ModbusFunctionCode::FUNCTION_CODE_LIMIT)
{
return;
}
getModbusHeader()->functionCode = static_cast<uint8_t>(functionCode);
}

std::string ModbusLayer::toString() const
{
return "Modbus Layer, Transaction ID: " + std::to_string(getTransactionId()) +
", Protocol ID: " + std::to_string(getProtocolId()) + ", Length: " + std::to_string(getLength()) +
", Unit ID: " + std::to_string(getUnitId()) +
", Function Code: " + std::to_string(static_cast<uint8_t>(getFunctionCode()));
}

int16_t ModbusLayer::getFunctionDataSize(ModbusFunctionCode functionCode) const
{
switch (functionCode)
{
// currently supported function codes
case ModbusFunctionCode::READ_INPUT_REGISTERS:
return sizeof(ModbusReadInputRegisters);
default:
return -1; // For unsupported or unknown function codes
}
}
} // namespace pcpp
5 changes: 5 additions & 0 deletions Packet++/src/TcpLayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "SmtpLayer.h"
#include "LdapLayer.h"
#include "GtpLayer.h"
#include "ModbusLayer.h"
#include "PacketUtils.h"
#include "Logger.h"
#include "DeprecationUtils.h"
Expand Down Expand Up @@ -464,6 +465,10 @@ namespace pcpp
{
constructNextLayer<GtpV2Layer>(payload, payloadLen, m_Packet);
}
else if (ModbusLayer::isModbusPort(portDst))
{
constructNextLayer<ModbusLayer>(payload, payloadLen, m_Packet);
}
else
{
constructNextLayer<PayloadLayer>(payload, payloadLen, m_Packet);
Expand Down
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,16 +277,17 @@ PcapPlusPlus currently supports parsing, editing and creation of packets of the
42. FTP
43. HTTP headers (request & response)
44. LDAP
45. NTP (v3, v4)
46. PEM decoder and encoder
47. Radius
48. S7 Communication (S7comm)
49. SMTP
50. SOME/IP
51. SSH - parsing only (no editing capabilities)
52. Telnet - parsing only (no editing capabilities)
53. X509 certificates - parsing only (no editing capabilities)
54. Generic payload
45. Modbus
46. NTP (v3, v4)
47. PEM decoder and encoder
48. Radius
49. S7 Communication (S7comm)
50. SMTP
51. SOME/IP
52. SSH - parsing only (no editing capabilities)
53. Telnet - parsing only (no editing capabilities)
54. X509 certificates - parsing only (no editing capabilities)
55. Generic payload
## DPDK And PF_RING Support
Expand Down
1 change: 1 addition & 0 deletions Tests/Packet++Test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ add_executable(
Tests/IPv6Tests.cpp
Tests/LdapTests.cpp
Tests/LLCTests.cpp
Tests/ModbusTests.cpp
Tests/NflogTests.cpp
Tests/NtpTests.cpp
Tests/PacketTests.cpp
Expand Down
1 change: 1 addition & 0 deletions Tests/Packet++Test/PacketExamples/ModbusRequest.dat
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
000c29af7ffe109add4e060d080045000040c6e64000400658880a0101ea0a0a0555c8d301f6e072efbac405c33a8018ffffd5d900000101080a37c3fee900ba2a0f001100000006ff0402580064
Binary file not shown.
4 changes: 4 additions & 0 deletions Tests/Packet++Test/TestDefinition.h
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,10 @@ PTF_TEST_CASE(CiscoHdlcParsingTest);
PTF_TEST_CASE(CiscoHdlcLayerCreationTest);
PTF_TEST_CASE(CiscoHdlcLayerEditTest);

// Implemented in ModbusTests.cpp
PTF_TEST_CASE(ModbusLayerCreationTest);
PTF_TEST_CASE(ModbusLayerParsingTest);

// Implemented in X509Tests.cpp
PTF_TEST_CASE(X509ParsingTest);
PTF_TEST_CASE(X509VariantsParsingTest);
Expand Down
Loading
Loading