Commit c44dcfd5a2a5f56c8885b89f41d1519859a1815c

Authored by Stéphane Raimbault
1 parent 15608980

Make the TCP implementation "protocol independent", i.e. IPv6 capable.

Inspired by the branch ff/ipv6 of Florian Forster but this version uses
a specific PI backend to isolate the IPv4 and IPv6 implementations.

The existing TCP/IPv4 implementation has been kept intact (not rebased
on the PI one) because its data backend requires fewer bytes than the PI.

New functions modbus_new_tcp_pi, modbus_tcp_pi_listen and
modbus_tcp_pi_accept and new backend called modbus_tcp_pi_t.
1 1 Stéphane Raimbault <stephane.raimbault@gmail.com>
2 2 Tobias Doerffel <tobias.doerffel@gmail.com>
  3 +Florian Forster <ff@octo.it>
... ...
src/modbus-tcp-private.h
... ... @@ -31,4 +31,16 @@ typedef struct _modbus_tcp {
31 31 char ip[16];
32 32 } modbus_tcp_t;
33 33  
  34 +#define _MODBUS_TCP_PI_NODE_LENGTH 1025
  35 +#define _MODBUS_TCP_PI_SERVICE_LENGTH 32
  36 +
  37 +typedef struct _modbus_tcp_pi {
  38 + /* TCP port */
  39 + int port;
  40 + /* Node */
  41 + char node[_MODBUS_TCP_PI_NODE_LENGTH];
  42 + /* Service */
  43 + char service[_MODBUS_TCP_PI_SERVICE_LENGTH];
  44 +} modbus_tcp_pi_t;
  45 +
34 46 #endif /* _MODBUS_TCP_PRIVATE_H_ */
... ...
src/modbus-tcp.c
... ... @@ -42,6 +42,8 @@
42 42 # include <netinet/ip.h>
43 43 # include <netinet/tcp.h>
44 44 # include <arpa/inet.h>
  45 +# include <poll.h>
  46 +# include <netdb.h>
45 47 #endif
46 48  
47 49 #if !defined(MSG_NOSIGNAL)
... ... @@ -185,32 +187,17 @@ int _modbus_tcp_check_integrity(modbus_t *ctx, uint8_t *msg, const int msg_lengt
185 187 return msg_length;
186 188 }
187 189  
188   -/* Establishes a modbus TCP connection with a Modbus server. */
189   -static int _modbus_tcp_connect(modbus_t *ctx)
  190 +static int _modbus_tcp_set_ipv4_options(int s)
190 191 {
191 192 int rc;
192 193 int option;
193   - struct sockaddr_in addr;
194   - modbus_tcp_t *ctx_tcp = ctx->backend_data;
195   -
196   -#ifdef OS_WIN32
197   - if (_modbus_tcp_init_win32() == -1) {
198   - return -1;
199   - }
200   -#endif
201   -
202   - ctx->s = socket(PF_INET, SOCK_STREAM, 0);
203   - if (ctx->s == -1) {
204   - return -1;
205   - }
206 194  
207 195 /* Set the TCP no delay flag */
208 196 /* SOL_TCP = IPPROTO_TCP */
209 197 option = 1;
210   - rc = setsockopt(ctx->s, IPPROTO_TCP, TCP_NODELAY,
  198 + rc = setsockopt(s, IPPROTO_TCP, TCP_NODELAY,
211 199 (const void *)&option, sizeof(int));
212 200 if (rc == -1) {
213   - close(ctx->s);
214 201 return -1;
215 202 }
216 203  
... ... @@ -221,14 +208,40 @@ static int _modbus_tcp_connect(modbus_t *ctx)
221 208 **/
222 209 /* Set the IP low delay option */
223 210 option = IPTOS_LOWDELAY;
224   - rc = setsockopt(ctx->s, IPPROTO_IP, IP_TOS,
  211 + rc = setsockopt(s, IPPROTO_IP, IP_TOS,
225 212 (const void *)&option, sizeof(int));
226 213 if (rc == -1) {
227   - close(ctx->s);
228 214 return -1;
229 215 }
230 216 #endif
231 217  
  218 + return 0;
  219 +}
  220 +
  221 +/* Establishes a modbus TCP connection with a Modbus server. */
  222 +static int _modbus_tcp_connect(modbus_t *ctx)
  223 +{
  224 + int rc;
  225 + struct sockaddr_in addr;
  226 + modbus_tcp_t *ctx_tcp = ctx->backend_data;
  227 +
  228 +#ifdef OS_WIN32
  229 + if (_modbus_tcp_init_win32() == -1) {
  230 + return -1;
  231 + }
  232 +#endif
  233 +
  234 + ctx->s = socket(PF_INET, SOCK_STREAM, 0);
  235 + if (ctx->s == -1) {
  236 + return -1;
  237 + }
  238 +
  239 + rc = _modbus_tcp_set_ipv4_options(ctx->s);
  240 + if (rc == -1) {
  241 + close(ctx->s);
  242 + return -1;
  243 + }
  244 +
232 245 if (ctx->debug) {
233 246 printf("Connecting to %s\n", ctx_tcp->ip);
234 247 }
... ... @@ -246,6 +259,61 @@ static int _modbus_tcp_connect(modbus_t *ctx)
246 259 return 0;
247 260 }
248 261  
  262 +/* Establishes a modbus TCP PI connection with a Modbus server. */
  263 +static int _modbus_tcp_pi_connect(modbus_t *ctx)
  264 +{
  265 + int rc;
  266 + struct addrinfo *ai_list;
  267 + struct addrinfo *ai_ptr;
  268 + struct addrinfo ai_hints;
  269 + modbus_tcp_pi_t *ctx_tcp_pi = ctx->backend_data;
  270 +
  271 + memset(&ai_hints, 0, sizeof(ai_hints));
  272 +#ifdef AI_ADDRCONFIG
  273 + ai_hints.ai_flags |= AI_ADDRCONFIG;
  274 +#endif
  275 + ai_hints.ai_family = AF_UNSPEC;
  276 + ai_hints.ai_socktype = SOCK_STREAM;
  277 + ai_hints.ai_addr = NULL;
  278 + ai_hints.ai_canonname = NULL;
  279 + ai_hints.ai_next = NULL;
  280 +
  281 + ai_list = NULL;
  282 + rc = getaddrinfo(ctx_tcp_pi->node, ctx_tcp_pi->service,
  283 + &ai_hints, &ai_list);
  284 + if (rc != 0)
  285 + return rc;
  286 +
  287 + for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) {
  288 + int s;
  289 +
  290 + s = socket(ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
  291 + if (s < 0)
  292 + continue;
  293 +
  294 + if (ai_ptr->ai_family == AF_INET)
  295 + _modbus_tcp_set_ipv4_options(s);
  296 +
  297 + rc = connect(s, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
  298 + if (rc != 0) {
  299 + close(s);
  300 + continue;
  301 + }
  302 +
  303 + ctx->s = s;
  304 + break;
  305 + }
  306 +
  307 + freeaddrinfo(ai_list);
  308 +
  309 + if (ctx->s < 0) {
  310 + errno = ENOTCONN;
  311 + return -1;
  312 + }
  313 +
  314 + return 0;
  315 +}
  316 +
249 317 /* Closes the network connection and socket in TCP mode */
250 318 void _modbus_tcp_close(modbus_t *ctx)
251 319 {
... ... @@ -330,6 +398,98 @@ int modbus_tcp_listen(modbus_t *ctx, int nb_connection)
330 398 return new_socket;
331 399 }
332 400  
  401 +int modbus_tcp_pi_listen(modbus_t *ctx, int nb_connection)
  402 +{
  403 + int rc;
  404 + struct addrinfo *ai_list;
  405 + struct addrinfo *ai_ptr;
  406 + struct addrinfo ai_hints;
  407 + const char *node;
  408 + const char *service;
  409 + int new_socket;
  410 + modbus_tcp_pi_t *ctx_tcp_pi = ctx->backend_data;
  411 +
  412 + if (ctx_tcp_pi->node[0] == 0)
  413 + node = NULL; /* == any */
  414 + else
  415 + node = ctx_tcp_pi->node;
  416 +
  417 + if (ctx_tcp_pi->service[0] == 0)
  418 + service = "502";
  419 + else
  420 + service = ctx_tcp_pi->service;
  421 +
  422 + memset(&ai_hints, 0, sizeof (ai_hints));
  423 + ai_hints.ai_flags |= AI_PASSIVE;
  424 +#ifdef AI_ADDRCONFIG
  425 + ai_hints.ai_flags |= AI_ADDRCONFIG;
  426 +#endif
  427 + ai_hints.ai_family = AF_UNSPEC;
  428 + ai_hints.ai_socktype = SOCK_STREAM;
  429 + ai_hints.ai_addr = NULL;
  430 + ai_hints.ai_canonname = NULL;
  431 + ai_hints.ai_next = NULL;
  432 +
  433 + ai_list = NULL;
  434 + rc = getaddrinfo(node, service, &ai_hints, &ai_list);
  435 + if (rc != 0)
  436 + return -1;
  437 +
  438 + new_socket = -1;
  439 + for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) {
  440 + int s;
  441 +
  442 + s = socket(ai_ptr->ai_family, ai_ptr->ai_socktype,
  443 + ai_ptr->ai_protocol);
  444 + if (s < 0) {
  445 + if (ctx->debug) {
  446 + perror("socket");
  447 + }
  448 + continue;
  449 + } else {
  450 + int yes = 1;
  451 + rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
  452 + (void *) &yes, sizeof (yes));
  453 + if (rc != 0) {
  454 + close(s);
  455 + if (ctx->debug) {
  456 + perror("setsockopt");
  457 + }
  458 + continue;
  459 + }
  460 + }
  461 +
  462 + rc = bind(s, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
  463 + if (rc != 0) {
  464 + close(s);
  465 + if (ctx->debug) {
  466 + perror("bind");
  467 + }
  468 + continue;
  469 + }
  470 +
  471 + rc = listen(s, nb_connection);
  472 + if (rc != 0) {
  473 + close(s);
  474 + if (ctx->debug) {
  475 + perror("listen");
  476 + }
  477 + continue;
  478 + }
  479 +
  480 + new_socket = s;
  481 + break;
  482 + }
  483 + freeaddrinfo(ai_list);
  484 +
  485 + if (new_socket < 0) {
  486 + errno = ENOTCONN;
  487 + return -1;
  488 + }
  489 +
  490 + return new_socket;
  491 +}
  492 +
333 493 /* On success, the function return a non-negative integer that is a descriptor
334 494 for the accepted socket. On error, -1 is returned, and errno is set
335 495 appropriately. */
... ... @@ -338,7 +498,7 @@ int modbus_tcp_accept(modbus_t *ctx, int *socket)
338 498 struct sockaddr_in addr;
339 499 socklen_t addrlen;
340 500  
341   - addrlen = sizeof(struct sockaddr_in);
  501 + addrlen = sizeof(addr);
342 502 ctx->s = accept(*socket, (struct sockaddr *)&addr, &addrlen);
343 503 if (ctx->s == -1) {
344 504 close(*socket);
... ... @@ -347,7 +507,27 @@ int modbus_tcp_accept(modbus_t *ctx, int *socket)
347 507 }
348 508  
349 509 if (ctx->debug) {
350   - printf("The client %s is connected\n", inet_ntoa(addr.sin_addr));
  510 + printf("The client connection from %s is accepted\n",
  511 + inet_ntoa(addr.sin_addr));
  512 + }
  513 +
  514 + return ctx->s;
  515 +}
  516 +
  517 +int modbus_tcp_pi_accept(modbus_t *ctx, int *socket)
  518 +{
  519 + struct sockaddr_storage addr;
  520 + socklen_t addrlen;
  521 +
  522 + addrlen = sizeof(addr);
  523 + ctx->s = accept(*socket, (void *)&addr, &addrlen);
  524 + if (ctx->s == -1) {
  525 + close(*socket);
  526 + *socket = 0;
  527 + }
  528 +
  529 + if (ctx->debug) {
  530 + printf("The client connection is accepted.");
351 531 }
352 532  
353 533 return ctx->s;
... ... @@ -412,9 +592,29 @@ const modbus_backend_t _modbus_tcp_backend = {
412 592 };
413 593  
414 594  
  595 +const modbus_backend_t _modbus_tcp_pi_backend = {
  596 + _MODBUS_BACKEND_TYPE_TCP,
  597 + _MODBUS_TCP_HEADER_LENGTH,
  598 + _MODBUS_TCP_CHECKSUM_LENGTH,
  599 + MODBUS_TCP_MAX_ADU_LENGTH,
  600 + _modbus_set_slave,
  601 + _modbus_tcp_build_request_basis,
  602 + _modbus_tcp_build_response_basis,
  603 + _modbus_tcp_prepare_response_tid,
  604 + _modbus_tcp_send_msg_pre,
  605 + _modbus_tcp_send,
  606 + _modbus_tcp_recv,
  607 + _modbus_tcp_check_integrity,
  608 + _modbus_tcp_pi_connect,
  609 + _modbus_tcp_close,
  610 + _modbus_tcp_flush,
  611 + _modbus_tcp_select,
  612 + _modbus_tcp_filter_request
  613 +};
  614 +
415 615 /* Allocates and initializes the modbus_t structure for TCP.
416   - - ip : "192.168.0.5"
417   - - port : 1099
  616 + - ip: '192.168.0.5'
  617 + - port: 1099
418 618  
419 619 Set the port to MODBUS_TCP_DEFAULT_PORT to use the default one
420 620 (502). It's convenient to use a port number greater than or equal
... ... @@ -425,6 +625,8 @@ modbus_t* modbus_new_tcp(const char *ip, int port)
425 625 {
426 626 modbus_t *ctx;
427 627 modbus_tcp_t *ctx_tcp;
  628 + size_t dest_size;
  629 + size_t ret_size;
428 630  
429 631 #if defined(OS_BSD)
430 632 /* MSG_NOSIGNAL is unsupported on *BSD so we install an ignore
... ... @@ -450,8 +652,84 @@ modbus_t* modbus_new_tcp(const char *ip, int port)
450 652 ctx->backend_data = (modbus_tcp_t *) malloc(sizeof(modbus_tcp_t));
451 653 ctx_tcp = (modbus_tcp_t *)ctx->backend_data;
452 654  
453   - strncpy(ctx_tcp->ip, ip, sizeof(char)*16);
  655 + dest_size = sizeof(char) * 16;
  656 + ret_size = strlcpy(ctx_tcp->ip, ip, dest_size);
  657 + if (ret_size == 0) {
  658 + fprintf(stderr, "The IP string is empty\n");
  659 + modbus_free(ctx);
  660 + errno = EINVAL;
  661 + return NULL;
  662 + }
  663 +
  664 + if (ret_size >= dest_size) {
  665 + fprintf(stderr, "The IP string has been truncated\n");
  666 + modbus_free(ctx);
  667 + errno = EINVAL;
  668 + return NULL;
  669 + }
  670 +
454 671 ctx_tcp->port = port;
455 672  
456 673 return ctx;
457 674 }
  675 +
  676 +/* Allocates and initializes the modbus_t structure for TCP in a protocol
  677 + indepedent fashin, i.e. IPv4/IPv6 agnostic.
  678 +
  679 + - node: host name or IP address of the host to connect to, eg. '192.168.0.5'
  680 + or 'server.com'.
  681 + - service: service name/port number to connect to. Use NULL for the default
  682 + port, 502/TCP.
  683 +*/
  684 +modbus_t* modbus_new_tcp_pi(const char *node, const char *service)
  685 +{
  686 + modbus_t *ctx;
  687 + modbus_tcp_pi_t *ctx_tcp_pi;
  688 + size_t dest_size;
  689 + size_t ret_size;
  690 +
  691 + ctx = (modbus_t *) malloc(sizeof(modbus_t));
  692 + _modbus_init_common(ctx);
  693 +
  694 + /* Could be changed after to reach a remote serial Modbus device */
  695 + ctx->slave = MODBUS_TCP_SLAVE;
  696 +
  697 + ctx->backend = &(_modbus_tcp_pi_backend);
  698 +
  699 + ctx->backend_data = (modbus_tcp_pi_t *) malloc(sizeof(modbus_tcp_pi_t));
  700 + ctx_tcp_pi = (modbus_tcp_pi_t *)ctx->backend_data;
  701 +
  702 + dest_size = sizeof(char) * _MODBUS_TCP_PI_NODE_LENGTH;
  703 + ret_size = strlcpy(ctx_tcp_pi->node, node, dest_size);
  704 + if (ret_size == 0) {
  705 + fprintf(stderr, "The node string is empty\n");
  706 + modbus_free(ctx);
  707 + errno = EINVAL;
  708 + return NULL;
  709 + }
  710 +
  711 + if (ret_size >= dest_size) {
  712 + fprintf(stderr, "The node string has been truncated\n");
  713 + modbus_free(ctx);
  714 + errno = EINVAL;
  715 + return NULL;
  716 + }
  717 +
  718 + dest_size = sizeof(char) * _MODBUS_TCP_PI_SERVICE_LENGTH;
  719 + ret_size = strlcpy(ctx_tcp_pi->service, service, dest_size);
  720 + if (ret_size == 0) {
  721 + fprintf(stderr, "The service string is empty\n");
  722 + modbus_free(ctx);
  723 + errno = EINVAL;
  724 + return NULL;
  725 + }
  726 +
  727 + if (ret_size >= dest_size) {
  728 + fprintf(stderr, "The service string has been truncated\n");
  729 + modbus_free(ctx);
  730 + errno = EINVAL;
  731 + return NULL;
  732 + }
  733 +
  734 + return ctx;
  735 +}
... ...
src/modbus-tcp.h
... ... @@ -41,4 +41,8 @@ modbus_t* modbus_new_tcp(const char *ip_address, int port);
41 41 int modbus_tcp_listen(modbus_t *ctx, int nb_connection);
42 42 int modbus_tcp_accept(modbus_t *ctx, int *socket);
43 43  
  44 +modbus_t* modbus_new_tcp_pi(const char *node, const char *service);
  45 +int modbus_tcp_pi_listen(modbus_t *ctx, int nb_connection);
  46 +int modbus_tcp_pi_accept(modbus_t *ctx, int *socket);
  47 +
44 48 #endif /* _MODBUS_TCP_H_ */
... ...
tests/unit-test-client.c
... ... @@ -26,6 +26,7 @@
26 26  
27 27 enum {
28 28 TCP,
  29 + TCP_PI,
29 30 RTU
30 31 };
31 32  
... ... @@ -48,10 +49,12 @@ int main(int argc, char *argv[])
48 49 if (argc > 1) {
49 50 if (strcmp(argv[1], "tcp") == 0) {
50 51 use_backend = TCP;
  52 + if (strcmp(argv[1], "tcppi") == 0) {
  53 + use_backend = TCP_PI;
51 54 } else if (strcmp(argv[1], "rtu") == 0) {
52 55 use_backend = RTU;
53 56 } else {
54   - printf("Usage:\n %s [tcp|rtu] - Modbus client for unit testing\n\n", argv[0]);
  57 + printf("Usage:\n %s [tcp|tcppi|rtu] - Modbus client for unit testing\n\n", argv[0]);
55 58 exit(1);
56 59 }
57 60 } else {
... ... @@ -61,7 +64,9 @@ int main(int argc, char *argv[])
61 64  
62 65 if (use_backend == TCP) {
63 66 ctx = modbus_new_tcp("127.0.0.1", 1502);
64   - } else {
  67 + } else if (use_backend == TCP_PI) {
  68 + ctx = modbus_new_tcp_pi("::1", "1502");
  69 + } else
65 70 ctx = modbus_new_rtu("/dev/ttyUSB1", 115200, 'N', 8, 1);
66 71 }
67 72 if (ctx == NULL) {
... ...
tests/unit-test-server.c
... ... @@ -26,6 +26,7 @@
26 26  
27 27 enum {
28 28 TCP,
  29 + TCP_PI,
29 30 RTU
30 31 };
31 32  
... ... @@ -43,10 +44,12 @@ int main(int argc, char*argv[])
43 44 if (argc > 1) {
44 45 if (strcmp(argv[1], "tcp") == 0) {
45 46 use_backend = TCP;
  47 + } else if (strcmp(argv[1], "tcppi") == 0) {
  48 + use_backend = TCP_PI;
46 49 } else if (strcmp(argv[1], "rtu") == 0) {
47 50 use_backend = RTU;
48 51 } else {
49   - printf("Usage:\n %s [tcp|rtu] - Modbus server for unit testing\n\n", argv[0]);
  52 + printf("Usage:\n %s [tcp|tcppi|rtu] - Modbus server for unit testing\n\n", argv[0]);
50 53 return -1;
51 54 }
52 55 } else {
... ... @@ -57,6 +60,9 @@ int main(int argc, char*argv[])
57 60 if (use_backend == TCP) {
58 61 ctx = modbus_new_tcp("127.0.0.1", 1502);
59 62 query = malloc(MODBUS_TCP_MAX_ADU_LENGTH);
  63 + } else if (use_backend == TCP_PI) {
  64 + ctx = modbus_new_tcp_pi("::0", "1502");
  65 + query = malloc(MODBUS_TCP_MAX_ADU_LENGTH);
60 66 } else {
61 67 ctx = modbus_new_rtu("/dev/ttyUSB0", 115200, 'N', 8, 1);
62 68 modbus_set_slave(ctx, SERVER_ID);
... ... @@ -96,6 +102,9 @@ int main(int argc, char*argv[])
96 102 if (use_backend == TCP) {
97 103 socket = modbus_tcp_listen(ctx, 1);
98 104 modbus_tcp_accept(ctx, &socket);
  105 + } else if (use_backend == TCP_PI) {
  106 + socket = modbus_tcp_pi_listen(ctx, 1);
  107 + modbus_tcp_pi_accept(ctx, &socket);
99 108 } else {
100 109 rc = modbus_connect(ctx);
101 110 if (rc == -1) {
... ...