Versions
-
OS: Ubuntu 20.04
-
libmodbus: 3.1.12
libmodbus Specific
-
Server: Modbus TCP server using modbus_new_tcp, receives one request with modbus_receive(), then responds with modbus_reply().
-
Client: Modbus TCP client using modbus_report_slave_id() (FC 0x11) over TCP and prints response bytes.
Description
I tested whether MODBUS_FC_REPORT_SLAVE_ID (FC 0x11, serial-line-only in the Modbus spec) is accepted on a TCP backend.
Observed behavior:
- A TCP client sends FC
0x11 via modbus_report_slave_id().
- The TCP server processes the request and returns a valid response payload.
- The response body contains the
LMB marker plus version string bytes, e.g. LMB3.1.12.
What went wrong:
- FC
0x11 is specified as serial-line-only, but it is still handled over TCP.
Expected behavior:
- On TCP backend, FC
0x11 should be rejected (for example with illegal function) or otherwise disabled by default.
Code and Logs
/* Server */
#include <errno.h>
#include <modbus.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <winsock2.h>
#else
#include <unistd.h>
#endif
int main(int argc, char *argv[])
{
const char *bind_ip = (argc > 1) ? argv[1] : "0.0.0.0";
int port = (argc > 2) ? atoi(argv[2]) : 1502;
modbus_t *ctx = NULL;
modbus_mapping_t *mapping = NULL;
int listen_fd = -1;
uint8_t req[MODBUS_TCP_MAX_ADU_LENGTH];
int rc;
ctx = modbus_new_tcp(bind_ip, port);
if (ctx == NULL) {
fprintf(stderr, "modbus_new_tcp failed\n");
return 1;
}
modbus_set_debug(ctx, 1);
listen_fd = modbus_tcp_listen(ctx, 1);
if (listen_fd == -1) {
fprintf(stderr, "modbus_tcp_listen failed: %s\n", modbus_strerror(errno));
modbus_free(ctx);
return 1;
}
if (modbus_tcp_accept(ctx, &listen_fd) == -1) {
fprintf(stderr, "modbus_tcp_accept failed: %s\n", modbus_strerror(errno));
#ifndef _WIN32
close(listen_fd);
#endif
modbus_free(ctx);
return 1;
}
mapping = modbus_mapping_new(1, 1, 1, 1);
if (mapping == NULL) {
fprintf(stderr, "modbus_mapping_new failed: %s\n", modbus_strerror(errno));
modbus_close(ctx);
#ifndef _WIN32
close(listen_fd);
#endif
modbus_free(ctx);
return 1;
}
printf("server started ip=%s port=%d\n", bind_ip, port);
rc = modbus_receive(ctx, req);
if (rc <= 0) {
fprintf(stderr, "modbus_receive failed: %s\n", modbus_strerror(errno));
modbus_mapping_free(mapping);
modbus_close(ctx);
#ifndef _WIN32
close(listen_fd);
#endif
modbus_free(ctx);
return 1;
}
printf("server got request_length=%d function=0x%02X\n", rc, req[7]);
if (modbus_reply(ctx, req, rc, mapping) == -1) {
fprintf(stderr, "modbus_reply failed: %s\n", modbus_strerror(errno));
modbus_mapping_free(mapping);
modbus_close(ctx);
#ifndef _WIN32
close(listen_fd);
#endif
modbus_free(ctx);
return 1;
}
printf("server replied to FC 0x11 over TCP\n");
modbus_mapping_free(mapping);
modbus_close(ctx);
#ifndef _WIN32
close(listen_fd);
#endif
modbus_free(ctx);
return 0;
}
/* Client */
#include <errno.h>
#include <modbus.h>
#include <stdio.h>
#include <stdlib.h>
static void print_hex(const uint8_t *buf, int len)
{
int i;
for (i = 0; i < len; i++) {
printf("%02X", buf[i]);
if (i + 1 < len) {
printf(" ");
}
}
printf("\n");
}
int main(int argc, char *argv[])
{
const char *ip = (argc > 1) ? argv[1] : "127.0.0.1";
int port = (argc > 2) ? atoi(argv[2]) : 1502;
modbus_t *ctx = NULL;
uint8_t report[MODBUS_MAX_PDU_LENGTH];
int rc;
int i;
int found_lmb = 0;
ctx = modbus_new_tcp(ip, port);
if (ctx == NULL) {
fprintf(stderr, "modbus_new_tcp failed\n");
return 1;
}
modbus_set_debug(ctx, 1);
if (modbus_set_slave(ctx, 1) == -1) {
fprintf(stderr, "modbus_set_slave failed: %s\n", modbus_strerror(errno));
modbus_free(ctx);
return 1;
}
if (modbus_connect(ctx) == -1) {
fprintf(stderr, "modbus_connect failed: %s\n", modbus_strerror(errno));
modbus_free(ctx);
return 1;
}
printf("client started ip=%s port=%d\n", ip, port);
printf("sending FC0x11 via modbus_report_slave_id over TCP\n");
rc = modbus_report_slave_id(ctx, MODBUS_MAX_PDU_LENGTH, report);
if (rc == -1) {
fprintf(stderr, "modbus_report_slave_id failed: %s\n", modbus_strerror(errno));
modbus_close(ctx);
modbus_free(ctx);
return 1;
}
printf("report length=%d\n", rc);
print_hex(report, rc);
if (rc > 1) {
printf("run_indicator_status=0x%02X\n", report[1]);
}
for (i = 0; i + 2 < rc; i++) {
if (report[i] == 'L' && report[i + 1] == 'M' && report[i + 2] == 'B') {
found_lmb = 1;
printf("fingerprint detected at offset=%d\n", i);
break;
}
}
if (!found_lmb) {
printf("no LMB fingerprint found in response body\n");
}
modbus_close(ctx);
modbus_free(ctx);
return 0;
}
$ ./server
Client connection accepted from 127.0.0.1.
server started ip=0.0.0.0 port=1502
Waiting for an indication...
<00><01><00><00><00><02><01><11>
server got request_length=8 function=0x11
[00][01][00][00][00][0E][01][11][0B][B4][FF][4C][4D][42][33][2E][31][2E][31][32]
server replied to FC 0x11 over TCP
$ ./client
Connecting to 127.0.0.1:1502
client started ip=127.0.0.1 port=1502
sending FC0x11 via modbus_report_slave_id over TCP
[00][01][00][00][00][02][01][11]
Waiting for a confirmation...
<00><01><00><00><00><0E><01><11><0B><B4><FF><4C><4D><42><33><2E><31><2E><31><32>
report length=11
B4 FF 4C 4D 42 33 2E 31 2E 31 32
run_indicator_status=0xFF
fingerprint detected at offset=2
Versions
OS: Ubuntu 20.04
libmodbus: 3.1.12
libmodbus Specific
Server: Modbus TCP server using
modbus_new_tcp, receives one request withmodbus_receive(), then responds withmodbus_reply().Client: Modbus TCP client using
modbus_report_slave_id()(FC0x11) over TCP and prints response bytes.Description
I tested whether
MODBUS_FC_REPORT_SLAVE_ID(FC0x11, serial-line-only in the Modbus spec) is accepted on a TCP backend.Observed behavior:
0x11viamodbus_report_slave_id().LMBmarker plus version string bytes, e.g.LMB3.1.12.What went wrong:
0x11is specified as serial-line-only, but it is still handled over TCP.Expected behavior:
0x11should be rejected (for example with illegal function) or otherwise disabled by default.Code and Logs