-
Notifications
You must be signed in to change notification settings - Fork 719
#1754 Add Modbus Support #1823
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
#1754 Add Modbus Support #1823
Changes from all commits
a36e594
88f69d4
c257494
348afc6
1f65c24
4159c6c
58c18ec
d50db3b
cfcbc69
28dd176
34d65a2
74ab79d
166c15c
3624286
bcd14b5
9f4ebbd
4ceb41d
adb3f51
839aa49
dc04c0c
f4f5291
24c0924
32da5e5
da52bf7
c8bd099
b96671c
5dd18fb
4b38a65
65b37f6
32211b8
0f001b6
adea8b5
530c8cc
73228fb
12b1650
494d84a
737e6de
9b50332
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
#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"); | ||
|
||
/// @enum modbus_function_code | ||
enum modbus_function_code | ||
{ | ||
MODBUS_READ_COILS = 1, | ||
MODBUS_READ_DISCRETE_INPUTS = 2, | ||
MODBUS_READ_HOLDING_REGISTERS = 3, | ||
MODBUS_READ_INPUT_REGISTERS = 4, | ||
MODBUS_WRITE_SINGLE_COIL = 5, | ||
MODBUS_WRITE_SINGLE_REGISTER = 6, | ||
MODBUS_WRITE_MULTIPLE_COILS = 15, | ||
MODBUS_WRITE_MULTIPLE_REGISTERS = 16 | ||
}; | ||
|
||
/// @class ModbusLayer | ||
/// Represents the MODBUS Application Protocol layer | ||
class ModbusLayer : public Layer | ||
{ | ||
public: | ||
/// 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, uint8_t functionCode); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we now have an enum for function code, maybe use it here? |
||
|
||
/// @brief Check if a port is a valid MODBUS port | ||
/// @param port | ||
/// @return true if the port is valid, false otherwise | ||
static bool isModbusPort(uint16_t port) | ||
{ | ||
return port == 502; | ||
} | ||
|
||
/// @return A pointer to the MODBUS header | ||
modbus_header* getModbusHeader() const; | ||
|
||
/// @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 | ||
modbus_function_code 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(uint8_t 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); | ||
} | ||
|
||
/// Each layer can compute field values automatically using this method. This is an abstract method | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please update the comment to:
|
||
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; | ||
} | ||
}; | ||
|
||
} // namespace pcpp |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
#include "ModbusLayer.h" | ||
#include "EndianPortable.h" | ||
#include <iostream> | ||
#include <iomanip> | ||
#include <cstring> | ||
|
||
namespace pcpp | ||
{ | ||
ModbusLayer::ModbusLayer(uint16_t transactionId, uint8_t unitId, uint8_t functionCode) | ||
{ | ||
const size_t headerLen = sizeof(modbus_header); | ||
m_DataLen = headerLen; | ||
m_Data = new uint8_t[headerLen]; | ||
memset(m_Data, 0, headerLen); | ||
|
||
// 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(2); // minimum length of the MODBUS payload + unit_id | ||
header->unitId = unitId; | ||
header->functionCode = 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; | ||
} | ||
|
||
modbus_function_code ModbusLayer::getFunctionCode() const | ||
{ | ||
return static_cast<modbus_function_code>(getModbusHeader()->functionCode); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should handle a case when |
||
} | ||
|
||
void ModbusLayer::setTransactionId(uint16_t transactionId) | ||
{ | ||
getModbusHeader()->transactionId = htobe16(transactionId); | ||
} | ||
|
||
void ModbusLayer::setUnitId(uint8_t unitId) | ||
{ | ||
getModbusHeader()->unitId = unitId; | ||
} | ||
|
||
void ModbusLayer::setFunctionCode(uint8_t functionCode) | ||
{ | ||
getModbusHeader()->functionCode = 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(getFunctionCode()); | ||
} | ||
|
||
} // namespace pcpp |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -287,6 +287,7 @@ PcapPlusPlus currently supports parsing, editing and creation of packets of the | |
52. Telnet - parsing only (no editing capabilities) | ||
53. X509 certificates - parsing only (no editing capabilities) | ||
54. Generic payload | ||
55. Modbus | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
## DPDK And PF_RING Support | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
0002b3ce705100207800620d08004500003085a14000800660eb0a0000390a0000030a1201f66197f1e370f1ad6f5018fa9c18f500000000000000020a11 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
#include "../TestDefinition.h" | ||
#include "../Utils/TestUtils.h" | ||
#include "Packet.h" | ||
#include "ModbusLayer.h" | ||
#include "EndianPortable.h" | ||
#include "SystemUtils.h" | ||
|
||
PTF_TEST_CASE(ModbusLayerCreationTest) | ||
seladb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
|
||
timeval time; | ||
gettimeofday(&time, nullptr); | ||
|
||
// Transaction ID: 0, Unit ID: 10, Function Code: 17 | ||
READ_FILE_AND_CREATE_PACKET(1, "PacketExamples/ModbusRequest.dat"); | ||
|
||
pcpp::Packet realPacket(&rawPacket1); | ||
PTF_ASSERT_TRUE(realPacket.isPacketOfType(pcpp::Modbus)); | ||
pcpp::ModbusLayer* modbusLayerFromRealPacket = realPacket.getLayerOfType<pcpp::ModbusLayer>(); | ||
|
||
pcpp::ModbusLayer modbusLayer(0, 10, 17); | ||
|
||
PTF_ASSERT_BUF_COMPARE(modbusLayer.getData(), modbusLayerFromRealPacket->getData(), modbusLayer.getHeaderLen()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd also test that the lengths are similar: PTF_ASSERT_EQUAL(modbusLayer.getDataLen(), modbusLayerFromRealPacket->getDataLen()); |
||
|
||
PTF_ASSERT_EQUAL(modbusLayer.getOsiModelLayer(), pcpp::OsiModelApplicationLayer); | ||
|
||
modbusLayer.setTransactionId(54321); | ||
PTF_ASSERT_EQUAL(modbusLayer.getTransactionId(), 54321); | ||
modbusLayer.setUnitId(2); | ||
PTF_ASSERT_EQUAL(modbusLayer.getUnitId(), 2); | ||
modbusLayer.setFunctionCode(6); | ||
PTF_ASSERT_EQUAL(modbusLayer.getFunctionCode(), 6); | ||
|
||
// just to pass the codecov | ||
modbusLayer.computeCalculateFields(); | ||
|
||
} // ModbusLayerCreationTest | ||
|
||
PTF_TEST_CASE(ModbusLayerParsingTest) | ||
{ | ||
timeval time; | ||
gettimeofday(&time, nullptr); | ||
|
||
READ_FILE_AND_CREATE_PACKET(1, "PacketExamples/ModbusRequest.dat"); | ||
|
||
pcpp::Packet packet(&rawPacket1); | ||
PTF_ASSERT_TRUE(packet.isPacketOfType(pcpp::Modbus)); | ||
|
||
pcpp::ModbusLayer* modbusLayer = packet.getLayerOfType<pcpp::ModbusLayer>(); | ||
PTF_ASSERT_NOT_NULL(modbusLayer); | ||
PTF_ASSERT_EQUAL(modbusLayer->getTransactionId(), 0); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you get a packet where transaction ID is not 0? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment was not addressed |
||
PTF_ASSERT_EQUAL(modbusLayer->getProtocolId(), 0); | ||
PTF_ASSERT_EQUAL(modbusLayer->getLength(), 2); | ||
PTF_ASSERT_EQUAL(modbusLayer->getUnitId(), 10); | ||
PTF_ASSERT_EQUAL(modbusLayer->getFunctionCode(), 17); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's update it to compare to the enum value? |
||
|
||
PTF_ASSERT_EQUAL(modbusLayer->toString(), | ||
"Modbus Layer, Transaction ID: 0, Protocol ID: 0, Length: 2, Unit ID: 10, Function Code: 17"); | ||
} // ModbusLayerParsingTest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
modbus_function_code
toModbusFunctionCode
?enum class
that inherits fromuint8_t
and put it insideModbusLayer
?Unknown
value that could be returned ingetFunctionCode()
if the packet value doesn't match any of the enum values