瀏覽代碼

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.
Stéphane Raimbault 14 年之前
父節點
當前提交
c44dcfd5a2
共有 6 個文件被更改,包括 336 次插入27 次删除
  1. 1 0
      AUTHORS
  2. 12 0
      src/modbus-tcp-private.h
  3. 302 24
      src/modbus-tcp.c
  4. 4 0
      src/modbus-tcp.h
  5. 7 2
      tests/unit-test-client.c
  6. 10 1
      tests/unit-test-server.c

+ 1 - 0
AUTHORS

@@ -1,2 +1,3 @@
 Stéphane Raimbault <stephane.raimbault@gmail.com>
 Tobias Doerffel <tobias.doerffel@gmail.com>
+Florian Forster <ff@octo.it>

+ 12 - 0
src/modbus-tcp-private.h

@@ -31,4 +31,16 @@ typedef struct _modbus_tcp {
     char ip[16];
 } modbus_tcp_t;
 
+#define _MODBUS_TCP_PI_NODE_LENGTH    1025
+#define _MODBUS_TCP_PI_SERVICE_LENGTH   32
+
+typedef struct _modbus_tcp_pi {
+    /* TCP port */
+    int port;
+    /* Node */
+    char node[_MODBUS_TCP_PI_NODE_LENGTH];
+    /* Service */
+    char service[_MODBUS_TCP_PI_SERVICE_LENGTH];
+} modbus_tcp_pi_t;
+
 #endif /* _MODBUS_TCP_PRIVATE_H_ */

+ 302 - 24
src/modbus-tcp.c

@@ -42,6 +42,8 @@
 # include <netinet/ip.h>
 # include <netinet/tcp.h>
 # include <arpa/inet.h>
+# include <poll.h>
+# include <netdb.h>
 #endif
 
 #if !defined(MSG_NOSIGNAL)
@@ -185,32 +187,17 @@ int _modbus_tcp_check_integrity(modbus_t *ctx, uint8_t *msg, const int msg_lengt
     return msg_length;
 }
 
-/* Establishes a modbus TCP connection with a Modbus server. */
-static int _modbus_tcp_connect(modbus_t *ctx)
+static int _modbus_tcp_set_ipv4_options(int s)
 {
     int rc;
     int option;
-    struct sockaddr_in addr;
-    modbus_tcp_t *ctx_tcp = ctx->backend_data;
-
-#ifdef OS_WIN32
-    if (_modbus_tcp_init_win32() == -1) {
-        return -1;
-    }
-#endif
-
-    ctx->s = socket(PF_INET, SOCK_STREAM, 0);
-    if (ctx->s == -1) {
-        return -1;
-    }
 
     /* Set the TCP no delay flag */
     /* SOL_TCP = IPPROTO_TCP */
     option = 1;
-    rc = setsockopt(ctx->s, IPPROTO_TCP, TCP_NODELAY,
+    rc = setsockopt(s, IPPROTO_TCP, TCP_NODELAY,
                     (const void *)&option, sizeof(int));
     if (rc == -1) {
-        close(ctx->s);
         return -1;
     }
 
@@ -221,14 +208,40 @@ static int _modbus_tcp_connect(modbus_t *ctx)
      **/
     /* Set the IP low delay option */
     option = IPTOS_LOWDELAY;
-    rc = setsockopt(ctx->s, IPPROTO_IP, IP_TOS,
+    rc = setsockopt(s, IPPROTO_IP, IP_TOS,
                     (const void *)&option, sizeof(int));
     if (rc == -1) {
-        close(ctx->s);
         return -1;
     }
 #endif
 
+    return 0;
+}
+
+/* Establishes a modbus TCP connection with a Modbus server. */
+static int _modbus_tcp_connect(modbus_t *ctx)
+{
+    int rc;
+    struct sockaddr_in addr;
+    modbus_tcp_t *ctx_tcp = ctx->backend_data;
+
+#ifdef OS_WIN32
+    if (_modbus_tcp_init_win32() == -1) {
+        return -1;
+    }
+#endif
+
+    ctx->s = socket(PF_INET, SOCK_STREAM, 0);
+    if (ctx->s == -1) {
+        return -1;
+    }
+
+    rc = _modbus_tcp_set_ipv4_options(ctx->s);
+    if (rc == -1) {
+        close(ctx->s);
+        return -1;
+    }
+
     if (ctx->debug) {
         printf("Connecting to %s\n", ctx_tcp->ip);
     }
@@ -246,6 +259,61 @@ static int _modbus_tcp_connect(modbus_t *ctx)
     return 0;
 }
 
+/* Establishes a modbus TCP PI connection with a Modbus server. */
+static int _modbus_tcp_pi_connect(modbus_t *ctx)
+{
+    int rc;
+    struct addrinfo *ai_list;
+    struct addrinfo *ai_ptr;
+    struct addrinfo ai_hints;
+    modbus_tcp_pi_t *ctx_tcp_pi = ctx->backend_data;
+
+    memset(&ai_hints, 0, sizeof(ai_hints));
+#ifdef AI_ADDRCONFIG
+    ai_hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+    ai_hints.ai_family = AF_UNSPEC;
+    ai_hints.ai_socktype = SOCK_STREAM;
+    ai_hints.ai_addr = NULL;
+    ai_hints.ai_canonname = NULL;
+    ai_hints.ai_next = NULL;
+
+    ai_list = NULL;
+    rc = getaddrinfo(ctx_tcp_pi->node, ctx_tcp_pi->service,
+                     &ai_hints, &ai_list);
+    if (rc != 0)
+        return rc;
+
+    for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) {
+        int s;
+
+        s = socket(ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
+        if (s < 0)
+            continue;
+
+        if (ai_ptr->ai_family == AF_INET)
+            _modbus_tcp_set_ipv4_options(s);
+
+        rc = connect(s, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
+        if (rc != 0) {
+            close(s);
+            continue;
+        }
+
+        ctx->s = s;
+        break;
+    }
+
+    freeaddrinfo(ai_list);
+
+    if (ctx->s < 0) {
+        errno = ENOTCONN;
+        return -1;
+    }
+
+    return 0;
+}
+
 /* Closes the network connection and socket in TCP mode */
 void _modbus_tcp_close(modbus_t *ctx)
 {
@@ -330,6 +398,98 @@ int modbus_tcp_listen(modbus_t *ctx, int nb_connection)
     return new_socket;
 }
 
+int modbus_tcp_pi_listen(modbus_t *ctx, int nb_connection)
+{
+    int rc;
+    struct addrinfo *ai_list;
+    struct addrinfo *ai_ptr;
+    struct addrinfo ai_hints;
+    const char *node;
+    const char *service;
+    int new_socket;
+    modbus_tcp_pi_t *ctx_tcp_pi = ctx->backend_data;
+
+    if (ctx_tcp_pi->node[0] == 0)
+        node = NULL; /* == any */
+    else
+        node = ctx_tcp_pi->node;
+
+    if (ctx_tcp_pi->service[0] == 0)
+        service = "502";
+    else
+        service = ctx_tcp_pi->service;
+
+    memset(&ai_hints, 0, sizeof (ai_hints));
+    ai_hints.ai_flags |= AI_PASSIVE;
+#ifdef AI_ADDRCONFIG
+    ai_hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+    ai_hints.ai_family = AF_UNSPEC;
+    ai_hints.ai_socktype = SOCK_STREAM;
+    ai_hints.ai_addr = NULL;
+    ai_hints.ai_canonname = NULL;
+    ai_hints.ai_next = NULL;
+
+    ai_list = NULL;
+    rc = getaddrinfo(node, service, &ai_hints, &ai_list);
+    if (rc != 0)
+        return -1;
+
+    new_socket = -1;
+    for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) {
+        int s;
+
+        s = socket(ai_ptr->ai_family, ai_ptr->ai_socktype,
+                            ai_ptr->ai_protocol);
+        if (s < 0) {
+            if (ctx->debug) {
+                perror("socket");
+            }
+            continue;
+        } else {
+            int yes = 1;
+            rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
+                            (void *) &yes, sizeof (yes));
+            if (rc != 0) {
+                close(s);
+                if (ctx->debug) {
+                    perror("setsockopt");
+                }
+                continue;
+            }
+        }
+
+        rc = bind(s, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
+        if (rc != 0) {
+            close(s);
+            if (ctx->debug) {
+                perror("bind");
+            }
+            continue;
+        }
+
+        rc = listen(s, nb_connection);
+        if (rc != 0) {
+            close(s);
+            if (ctx->debug) {
+                perror("listen");
+            }
+            continue;
+        }
+
+        new_socket = s;
+        break;
+    }
+    freeaddrinfo(ai_list);
+
+    if (new_socket < 0) {
+        errno = ENOTCONN;
+        return -1;
+    }
+
+    return new_socket;
+}
+
 /* On success, the function return a non-negative integer that is a descriptor
    for the accepted socket. On error, -1 is returned, and errno is set
    appropriately. */
@@ -338,7 +498,7 @@ int modbus_tcp_accept(modbus_t *ctx, int *socket)
     struct sockaddr_in addr;
     socklen_t addrlen;
 
-    addrlen = sizeof(struct sockaddr_in);
+    addrlen = sizeof(addr);
     ctx->s = accept(*socket, (struct sockaddr *)&addr, &addrlen);
     if (ctx->s == -1) {
         close(*socket);
@@ -347,7 +507,27 @@ int modbus_tcp_accept(modbus_t *ctx, int *socket)
     }
 
     if (ctx->debug) {
-        printf("The client %s is connected\n", inet_ntoa(addr.sin_addr));
+        printf("The client connection from %s is accepted\n",
+               inet_ntoa(addr.sin_addr));
+    }
+
+    return ctx->s;
+}
+
+int modbus_tcp_pi_accept(modbus_t *ctx, int *socket)
+{
+    struct sockaddr_storage addr;
+    socklen_t addrlen;
+
+    addrlen = sizeof(addr);
+    ctx->s = accept(*socket, (void *)&addr, &addrlen);
+    if (ctx->s == -1) {
+        close(*socket);
+        *socket = 0;
+    }
+
+    if (ctx->debug) {
+        printf("The client connection is accepted.");
     }
 
     return ctx->s;
@@ -412,9 +592,29 @@ const modbus_backend_t _modbus_tcp_backend = {
 };
 
 
+const modbus_backend_t _modbus_tcp_pi_backend = {
+    _MODBUS_BACKEND_TYPE_TCP,
+    _MODBUS_TCP_HEADER_LENGTH,
+    _MODBUS_TCP_CHECKSUM_LENGTH,
+    MODBUS_TCP_MAX_ADU_LENGTH,
+    _modbus_set_slave,
+    _modbus_tcp_build_request_basis,
+    _modbus_tcp_build_response_basis,
+    _modbus_tcp_prepare_response_tid,
+    _modbus_tcp_send_msg_pre,
+    _modbus_tcp_send,
+    _modbus_tcp_recv,
+    _modbus_tcp_check_integrity,
+    _modbus_tcp_pi_connect,
+    _modbus_tcp_close,
+    _modbus_tcp_flush,
+    _modbus_tcp_select,
+    _modbus_tcp_filter_request
+};
+
 /* Allocates and initializes the modbus_t structure for TCP.
-   - ip : "192.168.0.5"
-   - port : 1099
+   - ip: '192.168.0.5'
+   - port: 1099
 
    Set the port to MODBUS_TCP_DEFAULT_PORT to use the default one
    (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)
 {
     modbus_t *ctx;
     modbus_tcp_t *ctx_tcp;
+    size_t dest_size;
+    size_t ret_size;
 
 #if defined(OS_BSD)
     /* 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)
     ctx->backend_data = (modbus_tcp_t *) malloc(sizeof(modbus_tcp_t));
     ctx_tcp = (modbus_tcp_t *)ctx->backend_data;
 
-    strncpy(ctx_tcp->ip, ip, sizeof(char)*16);
+    dest_size = sizeof(char) * 16;
+    ret_size = strlcpy(ctx_tcp->ip, ip, dest_size);
+    if (ret_size == 0) {
+        fprintf(stderr, "The IP string is empty\n");
+        modbus_free(ctx);
+        errno = EINVAL;
+        return NULL;
+    }
+
+    if (ret_size >= dest_size) {
+        fprintf(stderr, "The IP string has been truncated\n");
+        modbus_free(ctx);
+        errno = EINVAL;
+        return NULL;
+    }
+
     ctx_tcp->port = port;
 
     return ctx;
 }
+
+/* Allocates and initializes the modbus_t structure for TCP in a protocol
+   indepedent fashin, i.e. IPv4/IPv6 agnostic.
+
+   - node: host name or IP address of the host to connect to, eg. '192.168.0.5'
+     or 'server.com'.
+   - service: service name/port number to connect to. Use NULL for the default
+     port, 502/TCP.
+*/
+modbus_t* modbus_new_tcp_pi(const char *node, const char *service)
+{
+    modbus_t *ctx;
+    modbus_tcp_pi_t *ctx_tcp_pi;
+    size_t dest_size;
+    size_t ret_size;
+
+    ctx = (modbus_t *) malloc(sizeof(modbus_t));
+    _modbus_init_common(ctx);
+
+    /* Could be changed after to reach a remote serial Modbus device */
+    ctx->slave = MODBUS_TCP_SLAVE;
+
+    ctx->backend = &(_modbus_tcp_pi_backend);
+
+    ctx->backend_data = (modbus_tcp_pi_t *) malloc(sizeof(modbus_tcp_pi_t));
+    ctx_tcp_pi = (modbus_tcp_pi_t *)ctx->backend_data;
+
+    dest_size = sizeof(char) * _MODBUS_TCP_PI_NODE_LENGTH;
+    ret_size = strlcpy(ctx_tcp_pi->node, node, dest_size);
+    if (ret_size == 0) {
+        fprintf(stderr, "The node string is empty\n");
+        modbus_free(ctx);
+        errno = EINVAL;
+        return NULL;
+    }
+
+    if (ret_size >= dest_size) {
+        fprintf(stderr, "The node string has been truncated\n");
+        modbus_free(ctx);
+        errno = EINVAL;
+        return NULL;
+    }
+
+    dest_size = sizeof(char) * _MODBUS_TCP_PI_SERVICE_LENGTH;
+    ret_size = strlcpy(ctx_tcp_pi->service, service, dest_size);
+    if (ret_size == 0) {
+        fprintf(stderr, "The service string is empty\n");
+        modbus_free(ctx);
+        errno = EINVAL;
+        return NULL;
+    }
+
+    if (ret_size >= dest_size) {
+        fprintf(stderr, "The service string has been truncated\n");
+        modbus_free(ctx);
+        errno = EINVAL;
+        return NULL;
+    }
+
+    return ctx;
+}

+ 4 - 0
src/modbus-tcp.h

@@ -41,4 +41,8 @@ modbus_t* modbus_new_tcp(const char *ip_address, int port);
 int modbus_tcp_listen(modbus_t *ctx, int nb_connection);
 int modbus_tcp_accept(modbus_t *ctx, int *socket);
 
+modbus_t* modbus_new_tcp_pi(const char *node, const char *service);
+int modbus_tcp_pi_listen(modbus_t *ctx, int nb_connection);
+int modbus_tcp_pi_accept(modbus_t *ctx, int *socket);
+
 #endif /* _MODBUS_TCP_H_ */

+ 7 - 2
tests/unit-test-client.c

@@ -26,6 +26,7 @@
 
 enum {
     TCP,
+    TCP_PI,
     RTU
 };
 
@@ -48,10 +49,12 @@ int main(int argc, char *argv[])
     if (argc > 1) {
         if (strcmp(argv[1], "tcp") == 0) {
             use_backend = TCP;
+        if (strcmp(argv[1], "tcppi") == 0) {
+            use_backend = TCP_PI;
         } else if (strcmp(argv[1], "rtu") == 0) {
             use_backend = RTU;
         } else {
-            printf("Usage:\n  %s [tcp|rtu] - Modbus client for unit testing\n\n", argv[0]);
+            printf("Usage:\n  %s [tcp|tcppi|rtu] - Modbus client for unit testing\n\n", argv[0]);
             exit(1);
         }
     } else {
@@ -61,7 +64,9 @@ int main(int argc, char *argv[])
 
     if (use_backend == TCP) {
         ctx = modbus_new_tcp("127.0.0.1", 1502);
-    } else {
+    } else if (use_backend == TCP_PI) {
+        ctx = modbus_new_tcp_pi("::1", "1502");
+    } else
         ctx = modbus_new_rtu("/dev/ttyUSB1", 115200, 'N', 8, 1);
     }
     if (ctx == NULL) {

+ 10 - 1
tests/unit-test-server.c

@@ -26,6 +26,7 @@
 
 enum {
     TCP,
+    TCP_PI,
     RTU
 };
 
@@ -43,10 +44,12 @@ int main(int argc, char*argv[])
     if (argc > 1) {
         if (strcmp(argv[1], "tcp") == 0) {
             use_backend = TCP;
+        } else if (strcmp(argv[1], "tcppi") == 0) {
+            use_backend = TCP_PI;
         } else if (strcmp(argv[1], "rtu") == 0) {
             use_backend = RTU;
         } else {
-            printf("Usage:\n  %s [tcp|rtu] - Modbus server for unit testing\n\n", argv[0]);
+            printf("Usage:\n  %s [tcp|tcppi|rtu] - Modbus server for unit testing\n\n", argv[0]);
             return -1;
         }
     } else {
@@ -57,6 +60,9 @@ int main(int argc, char*argv[])
     if (use_backend == TCP) {
         ctx = modbus_new_tcp("127.0.0.1", 1502);
         query = malloc(MODBUS_TCP_MAX_ADU_LENGTH);
+    } else if (use_backend == TCP_PI) {
+        ctx = modbus_new_tcp_pi("::0", "1502");
+        query = malloc(MODBUS_TCP_MAX_ADU_LENGTH);
     } else {
         ctx = modbus_new_rtu("/dev/ttyUSB0", 115200, 'N', 8, 1);
         modbus_set_slave(ctx, SERVER_ID);
@@ -96,6 +102,9 @@ int main(int argc, char*argv[])
     if (use_backend == TCP) {
         socket = modbus_tcp_listen(ctx, 1);
         modbus_tcp_accept(ctx, &socket);
+    } else if (use_backend == TCP_PI) {
+        socket = modbus_tcp_pi_listen(ctx, 1);
+        modbus_tcp_pi_accept(ctx, &socket);
     } else {
         rc = modbus_connect(ctx);
         if (rc == -1) {