msousa@0: /* msousa@0: * Copyright (c) 2002,2016 Mario de Sousa (msousa@fe.up.pt) msousa@0: * msousa@0: * This file is part of the Modbus library for Beremiz and matiec. msousa@0: * msousa@0: * This Modbus library is free software: you can redistribute it and/or modify msousa@0: * it under the terms of the GNU Lesser General Public License as published by msousa@0: * the Free Software Foundation, either version 3 of the License, or msousa@0: * (at your option) any later version. msousa@0: * msousa@0: * This program is distributed in the hope that it will be useful, but msousa@0: * WITHOUT ANY WARRANTY; without even the implied warranty of msousa@0: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser msousa@0: * General Public License for more details. msousa@0: * msousa@0: * You should have received a copy of the GNU Lesser General Public License msousa@0: * along with this Modbus library. If not, see . msousa@0: * msousa@0: * This code is made available on the understanding that it will not be msousa@0: * used in safety-critical situations without a full and competent review. msousa@0: */ msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: #include /* File control definitions */ msousa@0: #include /* Standard input/output */ msousa@0: #include msousa@0: #include msousa@0: #include /* POSIX terminal control definitions */ msousa@0: #include /* Time structures for select() */ msousa@0: #include /* POSIX Symbolic Constants */ msousa@0: #include msousa@0: #include /* Error definitions */ msousa@0: #include /* clock_gettime() */ msousa@0: #include msousa@0: #include msousa@0: #include /* required for htons() and ntohs() */ msousa@0: #include /* TCP level socket options */ msousa@0: #include /* IP level socket options */ msousa@0: msousa@0: #include msousa@0: #include /* sched_yield() */ msousa@0: msousa@0: msousa@0: msousa@0: #include "sin_util.h" /* internet socket utility functions... */ msousa@0: #include "mb_layer1.h" /* The public interface this file implements... */ msousa@0: #include "mb_tcp_private.h" msousa@0: msousa@0: msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** Include common code... **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: msousa@0: #include "mb_time_util.h" msousa@0: msousa@0: msousa@0: //#define ERRMSG msousa@0: #define ERRMSG_HEAD "Modbus/TCP: " msousa@0: msousa@0: msousa@0: // #define DEBUG /* uncomment to see the data sent and received */ msousa@0: msousa@0: msousa@0: #ifdef DEBUG msousa@0: #ifndef ERRMSG msousa@0: #define ERRMSG msousa@0: #endif msousa@0: #endif msousa@0: msousa@0: msousa@0: msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**** Forward Declarations ****/ msousa@0: /**** and Defaults ****/ msousa@0: /**** ****/ msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: msousa@0: msousa@0: /* A Node Descriptor metadata, msousa@0: * Due to the fact that modbus TCP is connection oriented, msousa@0: * and that if the client detects an error the connection msousa@0: * must be shut down and re-established automatically, msousa@0: * the modbus TCP layer needs to keep the address of the remote server. msousa@0: * msousa@0: * We do this by implementing a node descriptor table, in which each msousa@0: * entry will have the remote address, and the file descriptor msousa@0: * of the socket currently in use. msousa@0: * msousa@0: * We do not pass the file descriptor up to the next higher layer. We msousa@0: * send them the node descriptor instead... msousa@0: */ msousa@0: #define MB_MASTER_NODE 12 msousa@0: #define MB_LISTEN_NODE 14 msousa@0: #define MB_SLAVE_NODE 16 msousa@0: #define MB_FREE_NODE 18 msousa@0: typedef sa_family_t nd_type_t; msousa@0: msousa@0: typedef struct { msousa@0: int fd; /* socket descriptor == file descriptor */ msousa@0: /* NOTE: msousa@0: * Modbus TCP says that on error, we should close msousa@0: * a connection and retry with a new connection. msousa@0: * Since it takes time for a socket to close msousa@0: * a connection if the remote server is down, msousa@0: * we close the connection on the socket, close the msousa@0: * socket itself, and create a new one for the new msousa@0: * connection. There will be times when the node will msousa@0: * not have any valid socket, and it will have to msousa@0: * be created on the fly. msousa@0: * When the node does not have a valid socket, msousa@0: * fd will be set to -1 msousa@0: */ msousa@0: int node_type; /* What kind of use we are giving to this node... msousa@0: * If node_type == MB_MASTER_NODE msousa@0: * The node descriptor was initialised by the msousa@0: * modbus_connect() function. msousa@0: * The node descriptor is being used by a master msousa@0: * device, and the addr contains the address of the slave. msousa@0: * Remember that in this case fd may be >= 0 while msousa@0: * we have a valid connection, or it may be < 0 when msousa@0: * the connection needs to be reset. msousa@0: * If node_type == MB_LISTEN_NODE msousa@0: * The node descriptor was initialised by the msousa@0: * modbus_listen() function. msousa@0: * The node is merely used to accept() new connection msousa@0: * requests. The new slave connections will use another msousa@0: * node to transfer data. msousa@0: * In this case fd must be >= 0. msousa@0: * fd < 0 is an ilegal state and should never occur. msousa@0: * If node_type == MB_SLAVE_NODE msousa@0: * The node descriptor was initialised when a new msousa@0: * connection request arrived on a MB_LISTEN type node. msousa@0: * The node descriptor is being used by a slave device, msousa@0: * and is currently being used to connect to a master. msousa@0: * In this case fd must be >= 0. msousa@0: * fd < 0 is an ilegal state and should never occur. msousa@0: * If node_type == FREE_ND msousa@0: * The node descriptor is currently not being used. msousa@0: * In this case fd is set to -1, but is really irrelevant. msousa@0: */ msousa@0: struct sockaddr_in addr; /* The internet adress we are using. msousa@0: * If node_type == MB_MASTER_NODE msousa@0: * addr will be the address of the remote slave msousa@0: * If node_type == MB_LISTEN_NODE msousa@0: * addr will be the address of the local listening port and network interface msousa@0: * If node_type == MB_SLAVE_NODE msousa@0: * addr will be the address of the local port and network interface msousa@0: * of the connection to the specific client. msousa@0: */ msousa@0: int listen_node; /* When a slave accepts a connection through a MB_LISTEN_NODE, it will msousa@0: * will use an empty node for the new connection, and configure this new node msousa@0: * to use the type MB_SLAVE_NODE. msousa@0: * The listen_node entry is only used by nodes of type MB_SLAVE_NODE. msousa@0: * In this case, listen_node will be the node of type MB_LISTEN_NODE through msousa@0: * which the connection request came through... msousa@0: */ msousa@0: int close_on_silence; /* A flag used only by Master Nodes. msousa@0: * When (close_on_silence > 0), then the connection to the msousa@0: * slave device will be shut down whenever the msousa@0: * modbus_tcp_silence_init() function is called. msousa@0: * Remember that the connection will be automatically msousa@0: * re-established the next time the user wishes to communicate msousa@0: * with the same slave (using this same node descripto). msousa@0: * If the user wishes to comply with the sugestion msousa@0: * in the OpenModbus Spec, (s)he should set this flag msousa@0: * if a silence interval longer than 1 second is expected. msousa@0: */ msousa@0: int print_connect_error; /* flag to guarantee we only print an error the first time we msousa@0: * attempt to connect to a emote server. msousa@0: * Stops us from generting a cascade of errors while the slave msousa@0: * is down. msousa@0: * Flag will get reset every time we successfully msousa@0: * establish a connection, so a message is once again generated msousa@0: * on the next error. msousa@0: */ msousa@0: u8 *recv_buf; /* This node's receive buffer msousa@0: * The library supports multiple simultaneous connections, msousa@0: * and may need to receive multiple frames through mutiple nodes concurrently. msousa@0: * To make the library thread-safe, we use one buffer for each node. msousa@0: */ msousa@0: } nd_entry_t; msousa@0: msousa@0: msousa@0: /* please make sure to lock the node table mutex before calling this function */ msousa@0: static int nd_entry_init(nd_entry_t *nde) { msousa@0: nde->addr.sin_family = AF_INET ; msousa@0: nde->node_type = MB_FREE_NODE; msousa@0: nde->fd = -1; /* not currently connected... */ msousa@0: /* initialise recv buffer */ msousa@0: nde->recv_buf = malloc(sizeof(u8) * RECV_BUFFER_SIZE); msousa@0: if (nde->recv_buf == NULL) msousa@0: return -1; msousa@0: return 0; msousa@0: } msousa@0: msousa@0: /* please make sure to lock the node table mutex before calling this function */ msousa@0: static int nd_entry_done(nd_entry_t *nde) { msousa@0: free(nde->recv_buf); msousa@0: return 0; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: typedef struct { msousa@0: /* the array of node descriptors, and current size... */ msousa@0: nd_entry_t *node; /* array of node entries. if NULL => node table not initialized */ msousa@0: int node_count; /* total number of nodes in the node[] array */ msousa@0: int free_node_count; /* number of free nodes in the node[] array */ msousa@0: pthread_mutex_t mutex; msousa@0: } nd_table_t; msousa@0: msousa@0: msousa@0: msousa@0: static int nd_table_done(nd_table_t *ndt) { msousa@0: int count; msousa@0: msousa@0: if (ndt->node == NULL) msousa@0: return 0; msousa@0: msousa@0: /* lock the mutex */ msousa@0: while (pthread_mutex_lock(&ndt->mutex) != 0) sched_yield(); msousa@0: msousa@0: /* initialise the state of each node in the array... */ msousa@0: for (count = 0; count < ndt->node_count; count++) { msousa@0: nd_entry_done(&ndt->node[count]); msousa@0: } /* for() */ msousa@0: msousa@0: free(ndt->node); msousa@0: pthread_mutex_unlock (&ndt->mutex); msousa@0: pthread_mutex_destroy(&ndt->mutex); msousa@0: *ndt = (nd_table_t){.node=NULL, .node_count=0, .free_node_count=0}; msousa@0: msousa@0: return 0; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: #if 1 msousa@0: /* nd_table_init() msousa@0: * Version 1 of the nd_table_init() function. msousa@0: * If called more than once, 2nd and any subsequent calls will msousa@0: * be interpreted as a request to confirm that it was already correctly msousa@0: * initialized with the requested number of nodes. msousa@0: */ msousa@0: static int nd_table_init(nd_table_t *ndt, int nd_count) { msousa@0: int count; msousa@0: msousa@0: if (ndt->node != NULL) { msousa@0: /* this function has already been called, and the node table is already initialised */ msousa@0: return (ndt->node_count == nd_count)?0:-1; msousa@0: } msousa@0: msousa@0: /* initialise the node table mutex... */ msousa@0: pthread_mutex_init(&ndt->mutex, NULL); msousa@0: if (pthread_mutex_lock(&ndt->mutex) != 0) { msousa@0: #ifdef DEBUG msousa@0: perror("pthread_mutex_lock()"); msousa@0: fprintf(stderr, "[%lu] Unable to lock newly crated mutex while creating new node table!\n", pthread_self()); msousa@0: #endif msousa@0: pthread_mutex_destroy(&ndt->mutex); msousa@0: return -1; msousa@0: } msousa@0: msousa@0: /* initialise the node descriptor metadata array... */ msousa@0: ndt->node = malloc(sizeof(nd_entry_t) * nd_count); msousa@0: if (ndt->node == NULL) { msousa@0: #ifdef DEBUG msousa@0: perror("malloc()"); msousa@0: fprintf(stderr, "[%lu] Out of memory: error initializing node address buffer\n", pthread_self()); msousa@0: #endif msousa@0: #ifdef ERRMSG msousa@0: perror("malloc()"); msousa@0: fprintf(stderr, ERRMSG_HEAD "Out of memory. Error initializing node address buffer\n"); msousa@0: #endif msousa@0: pthread_mutex_unlock (&ndt->mutex); msousa@0: pthread_mutex_destroy(&ndt->mutex); msousa@0: return -1; msousa@0: } msousa@0: msousa@0: /* initialise the state of each node in the array... */ msousa@0: for (count = 0; count < nd_count; count++) { msousa@0: if (nd_entry_init(&ndt->node[count]) < 0) { msousa@0: pthread_mutex_unlock(&ndt->mutex); msousa@0: nd_table_done(ndt); msousa@0: return -1; msousa@0: } msousa@0: ndt->node_count = count+1; msousa@0: ndt->free_node_count = count+1; msousa@0: } /* for() */ msousa@0: msousa@0: ndt->node_count = nd_count; msousa@0: ndt->free_node_count = nd_count; msousa@0: msousa@0: pthread_mutex_unlock(&ndt->mutex); msousa@0: return nd_count; /* number of succesfully created nodes! */ msousa@0: } msousa@0: msousa@0: msousa@0: #else msousa@0: /* nd_table_init() msousa@0: * Version 2 of the nd_table_init() function. msousa@0: * If called more than once, 2nd and any subsequent calls will msousa@0: * be interpreted as a request to reserve an extra new_nd_count msousa@0: * number of nodes. This will be done using realloc(). msousa@0: */ msousa@0: static int nd_table_init(nd_table_t *ndt, int new_nd_count) { msousa@0: int count; msousa@0: msousa@0: if (ndt->node == NULL) { msousa@0: /* Node table nt yet initialized => we must initialise the node table mutex... */ msousa@0: pthread_mutex_init(&ndt->mutex, NULL); msousa@0: } msousa@0: msousa@0: /* lock the mutex */ msousa@0: while (pthread_mutex_lock(&ndt->mutex) != 0) sched_yield(); msousa@0: msousa@0: /* initialise the node descriptor metadata array... */ msousa@0: ndt->node = realloc(ndt->node, sizeof(nd_entry_t) * (ndt->node_count + new_nd_count)); msousa@0: if (ndt->node == NULL) { msousa@0: #ifdef DEBUG msousa@0: perror("malloc()"); msousa@0: fprintf(stderr, "[%lu] Out of memory: error initializing node address buffer\n", pthread_self()); msousa@0: #endif msousa@0: #ifdef ERRMSG msousa@0: perror("malloc()"); msousa@0: fprintf(stderr, ERRMSG_HEAD "Out of memory. Error initializing node address buffer\n"); msousa@0: #endif msousa@0: pthread_mutex_unlock (&ndt->mutex); msousa@0: pthread_mutex_destroy(&ndt->mutex); msousa@0: return -1; msousa@0: } msousa@0: msousa@0: /* initialise the state of each newly added node in the array... */ msousa@0: for (count = ndt->node_count; count < ndt->node_count + new_nd_count; count++) { msousa@0: if (nd_entry_init(&ndt->node[count]) < 0) { msousa@0: pthread_mutex_unlock(&ndt->mutex); msousa@0: return -1; msousa@0: } msousa@0: } /* for() */ msousa@0: ndt->node_count += new_nd_count; msousa@0: ndt->free_node_count += new_nd_count; msousa@0: msousa@0: pthread_mutex_unlock(&ndt->mutex); msousa@0: return new_nd_count; /* number of succesfully created nodes! */ msousa@0: } msousa@0: #endif msousa@0: msousa@0: msousa@0: static int nd_table_get_free_node(nd_table_t *ndt, nd_type_t nd_type) { msousa@0: int count; msousa@0: msousa@0: /* lock the mutex */ msousa@0: while (pthread_mutex_lock(&ndt->mutex) != 0) sched_yield(); msousa@0: msousa@0: /* check for free nodes... */ msousa@0: if (ndt->free_node_count <= 0) { msousa@0: /* no free nodes... */ msousa@0: pthread_mutex_unlock(&ndt->mutex); msousa@0: return -1; msousa@0: } msousa@0: msousa@0: /* Decrement the free node counter...*/ msousa@0: ndt->free_node_count--; msousa@0: msousa@0: /* search for a free node... */ msousa@0: for (count = 0; count < ndt->node_count; count++) { msousa@0: if(ndt->node[count].node_type == MB_FREE_NODE) { msousa@0: /* found one!! Allocate it to the new type! */ msousa@0: ndt->node[count].node_type = nd_type; msousa@0: pthread_mutex_unlock(&ndt->mutex); msousa@0: return count; msousa@0: } msousa@0: } /* for() */ msousa@0: msousa@0: /* Strange... We should have free nodes, but we didn't finda any! */ msousa@0: /* Let's try to get into a consistent state, and return an error! */ msousa@0: ndt->free_node_count = 0; msousa@0: pthread_mutex_unlock(&ndt->mutex); msousa@0: return -1; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: static void nd_table_close_node(nd_table_t *ndt, int nd) { msousa@0: msousa@0: /* lock the mutex */ msousa@0: while (pthread_mutex_lock(&ndt->mutex) != 0) sched_yield(); msousa@0: msousa@0: if(ndt->node[nd].node_type == MB_FREE_NODE) { msousa@0: /* Node already free... */ msousa@0: pthread_mutex_unlock(&ndt->mutex); msousa@0: return; msousa@0: } msousa@0: msousa@0: /* Increment the free node counter...*/ msousa@0: ndt->free_node_count++; msousa@0: /* Mark the node as being free. */ msousa@0: ndt->node[nd].node_type = MB_FREE_NODE; msousa@0: msousa@0: pthread_mutex_unlock(&ndt->mutex); msousa@0: return; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**** Global Library State ****/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: msousa@0: msousa@0: /* The node descriptor table... */ msousa@0: /* NOTE: The node_table_ Must be initialized correctly here! */ msousa@0: static nd_table_t nd_table_ = {.node=NULL, .node_count=0, .free_node_count=0}; msousa@0: msousa@0: msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**** Local Utility functions... ****/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: msousa@0: msousa@0: #define min(a,b) ((ab)?a:b) msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** Configure socket for Modbus **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: msousa@0: msousa@0: static int configure_socket(int socket_id) { msousa@0: msousa@0: /* configure the socket */ msousa@0: /* Set it to be non-blocking. This is safe because we always use select() before reading from it! msousa@0: * It is also required for the connect() call. The default timeout in the TCP stack is much too long msousa@0: * (typically blocks for 128 s ??) when the connect does not succedd imediately! msousa@0: */ msousa@0: if (fcntl(socket_id, F_SETFL, O_NONBLOCK) < 0) { msousa@0: #ifdef ERRMSG msousa@0: perror("fcntl()"); msousa@0: fprintf(stderr, ERRMSG_HEAD "Error configuring socket 'non-blocking' option.\n"); msousa@0: #endif msousa@0: return -1; msousa@0: } msousa@0: msousa@0: /* configure the socket */ Edouard@12: { Edouard@12: int optval; Edouard@12: socklen_t optlen = sizeof(optval); Edouard@12: optval = 1; Edouard@12: if(setsockopt(socket_id, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) { Edouard@12: #ifdef ERRMSG Edouard@12: perror("setsockopt()"); Edouard@12: fprintf(stderr, ERRMSG_HEAD "Error configuring socket 'KeepAlive' option.\n"); Edouard@12: #endif Edouard@12: return -1; Edouard@12: } Edouard@12: } msousa@0: /* set the TCP no delay flag. */ msousa@0: {int bool_opt = 1; msousa@0: if (setsockopt(socket_id, SOL_TCP, TCP_NODELAY, msousa@0: (const void *)&bool_opt, sizeof(bool_opt)) msousa@0: < 0) { msousa@0: #ifdef ERRMSG msousa@0: perror("setsockopt()"); msousa@0: fprintf(stderr, ERRMSG_HEAD "Error configuring socket 'TCP no delay' option.\n"); msousa@0: #endif msousa@0: return -1; msousa@0: } msousa@0: } msousa@0: msousa@0: /* set the IP low delay option. */ msousa@0: {int priority_opt = IPTOS_LOWDELAY; msousa@0: if (setsockopt(socket_id, SOL_IP, IP_TOS, msousa@0: (const void *)&priority_opt, sizeof(priority_opt)) msousa@0: < 0) { msousa@0: #ifdef ERRMSG msousa@0: perror("setsockopt()"); msousa@0: fprintf(stderr, ERRMSG_HEAD "Error configuring socket 'IP low delay' option.\n"); msousa@0: #endif msousa@0: return -1; msousa@0: } msousa@0: } msousa@0: msousa@0: #if 0 msousa@0: /* send buffer */ msousa@0: /* NOTE: For slave devices, that may be receiving multiple msousa@0: * requests before they have a chance to reply to the first, msousa@0: * it probably is a good idea to have a large receive buffer. msousa@0: * So it is best to leave it with the default configuration, as it is msousa@0: * larger than the largest Modbus TCP frame. msousa@0: * msousa@0: * For the send buffer, a smaller buffer should suffice. msousa@0: * However, it probably does not make sense to msousa@0: * waste time asking for a smaller buffer, since the larger msousa@0: * default buffer has already been allocated (the socket has already msousa@0: * been created!) msousa@0: * msousa@0: * We might just as well leave out the configuration of the socket msousa@0: * buffer size... msousa@0: */ msousa@0: #define SOCK_BUF_SIZE 300 /* The size proposed in the Modbus TCP spec. */ msousa@0: {int sock_buf_size; msousa@0: sock_buf_size = SOCK_BUF_SIZE; msousa@0: if (setsockopt(socket_id, SOL_SOCKET, SO_SNDBUF, msousa@0: (const void *)&sock_buf_size, sizeof(sock_buf_size)) msousa@0: < 0) msousa@0: return -1; msousa@0: /* recv buffer */ msousa@0: sock_buf_size = SOCK_BUF_SIZE; msousa@0: if (setsockopt(socket_id, SOL_SOCKET, SO_RCVBUF, msousa@0: (const void *)&sock_buf_size, sizeof(sock_buf_size)) msousa@0: < 0) msousa@0: return -1; msousa@0: } msousa@0: #endif msousa@0: msousa@0: return 0; msousa@0: } msousa@0: msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** Connect socket to remote host **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: msousa@0: /* This function will create a new socket, and connect it to a remote host... */ msousa@0: static inline int open_connection(int nd, const struct timespec *timeout) { msousa@0: int socket_id, con_res; msousa@0: msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] open_connection(): called, nd = %d\n", pthread_self(), nd); msousa@0: #endif msousa@0: msousa@0: if (nd_table_.node[nd].fd >= 0) msousa@0: /* nd already connected) */ msousa@0: return nd_table_.node[nd].fd; msousa@0: msousa@0: if (nd_table_.node[nd].addr.sin_family != AF_INET) msousa@0: /* invalid remote address, or invalid nd */ msousa@0: return -1; msousa@0: msousa@0: /* lets try to connect... */ msousa@0: /* create the socket */ msousa@0: if ((socket_id = socket(PF_INET, DEF_TYPE, 0 /* protocol_num */)) < 0) { msousa@0: #ifdef DEBUG msousa@0: perror("socket()"); msousa@0: fprintf(stderr, "[%lu] Error creating socket\n", pthread_self()); msousa@0: #endif msousa@0: #ifdef ERRMSG msousa@0: perror("socket()"); msousa@0: fprintf(stderr, ERRMSG_HEAD "Error creating socket\n"); msousa@0: #endif msousa@0: return -1; msousa@0: } msousa@0: msousa@0: /* configure the socket - includes setting non-blocking option! */ msousa@0: if (configure_socket(socket_id) < 0) { msousa@0: close(socket_id); msousa@0: return -1; msousa@0: }; msousa@0: msousa@0: /* establish the connection to remote host */ msousa@0: con_res = connect(socket_id, msousa@0: (struct sockaddr *)&(nd_table_.node[nd].addr), msousa@0: sizeof(nd_table_.node[nd].addr)); msousa@0: msousa@0: /* The following condition is not strictly necessary msousa@0: * (we could let the code fall through) msousa@0: * but it does make the code easier to read/understand... msousa@0: */ msousa@0: if (con_res >= 0) msousa@0: goto success_exit; /* connected succesfully on first try! */ msousa@0: msousa@0: if (con_res < 0) { msousa@0: if ((errno != EINPROGRESS) && (errno != EALREADY)) msousa@0: goto error_exit; /* error in connection request! */ msousa@0: msousa@0: /* connection request is ongoing */ msousa@0: /* EINPROGRESS -> first call to connect, EALREADY -> subsequent calls to connect */ msousa@0: /* Must wait for connect to complete at most 'timeout' seconds */ msousa@0: {fd_set fdset; msousa@0: int res, so_error; msousa@0: socklen_t len; msousa@0: struct timespec end_time, *et_ptr; msousa@0: msousa@0: et_ptr = NULL; msousa@0: if (timeout != NULL) { msousa@0: et_ptr = &end_time; msousa@0: *et_ptr = timespec_add_curtime(*timeout); msousa@0: } msousa@0: msousa@0: FD_ZERO(&fdset); msousa@0: FD_SET(socket_id, &fdset); msousa@0: msousa@0: res = my_select(socket_id+1, NULL, &fdset, et_ptr); msousa@0: if (res < 0) goto error_exit; /* error on call to select */ msousa@0: if (res == 0) goto error_exit; /* timeout */ msousa@0: /* (res > 0) -> connection attemt completed. May have been success or failure! */ msousa@0: msousa@0: len = sizeof(so_error); msousa@0: res = getsockopt(socket_id, SOL_SOCKET, SO_ERROR, &so_error, &len); msousa@0: if (res < 0) goto error_exit; /* error on call to getsockopt */ msousa@0: if (so_error != 0) goto error_exit; /* error on connection attempt */ msousa@0: goto success_exit; /* succesfully completed connection attempt! */ msousa@0: /* goto sucess_exit is not strcitly necessary - we could let the code fall through! */ msousa@0: } msousa@0: } msousa@0: msousa@0: success_exit: msousa@0: nd_table_.node[nd].fd = socket_id; msousa@0: /* Succesfully established connection => print a message next time we have error. */ msousa@0: nd_table_.node[nd].print_connect_error = 1; msousa@0: msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] open_connection(): returning...\n", pthread_self()); msousa@0: #endif msousa@0: return socket_id; msousa@0: msousa@0: error_exit: msousa@0: #ifdef ERRMSG msousa@0: if (nd_table_.node[nd].print_connect_error > 0) { msousa@0: perror("connect()"); msousa@0: fprintf(stderr, ERRMSG_HEAD "Error establishing socket connection.\n"); msousa@0: /* do not print more error messages for this node... */ msousa@0: nd_table_.node[nd].print_connect_error = 0; msousa@0: } msousa@0: #endif msousa@0: close(socket_id); msousa@0: return -1; msousa@0: } msousa@0: msousa@0: msousa@0: /* This function will accept a new connection request, and attribute it to a new node... */ msousa@0: static inline int accept_connection(int nd) { msousa@0: int socket_id, new_nd; msousa@0: msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] accept_connection(): called, nd = %d\n", pthread_self(), nd); msousa@0: #endif msousa@0: msousa@0: /* NOTE: We MUST accccept8) all connection requests, even if no new node is available. msousa@0: * => We first accept the connection request, and only later look for a node. msousa@0: * If no node is free/available for this new connections request, the msousa@0: * connection will be accepted and immediately closed. msousa@0: * Reason: msousa@0: * When the library is used for a Modbus/TCP server and no free node is msousa@0: * available, if we do not accept() all newly arrived connection requests msousa@0: * we would enter an infinite loop calling msousa@0: * - select() (in modbus_tcp_read()) msousa@0: * - and accept_connection(). msousa@0: * Note that select() will continue to return immediately if the msousa@0: * connection request is not accept()ted! msousa@0: */ msousa@0: /* lets accept new connection request... */ msousa@0: if ((socket_id = accept(nd_table_.node[nd].fd, NULL, NULL)) < 0) { msousa@0: #ifdef ERRMSG msousa@0: perror("accept()"); msousa@0: fprintf(stderr, ERRMSG_HEAD "Error while waiting for connection request from new client\n"); msousa@0: #endif msousa@0: /* error establishing new connection... */ msousa@0: return -1; msousa@0: } msousa@0: msousa@0: /* find a free node */ msousa@0: if ((new_nd = nd_table_get_free_node(&nd_table_, MB_SLAVE_NODE)) < 0) { msousa@0: /* no available free nodes for the new connection... */ msousa@0: close(socket_id); msousa@0: return -1; msousa@0: } msousa@0: msousa@0: /* configure the socket - includes setting the non-blocking option! */ msousa@0: if (configure_socket(socket_id) < 0) { msousa@0: nd_table_close_node(&nd_table_, new_nd); /* first free up the un-used node. */ msousa@0: close(socket_id); msousa@0: return -1; msousa@0: } msousa@0: msousa@0: /* set up the node entry and update the fd sets */ msousa@0: nd_table_.node[new_nd].fd = socket_id; msousa@0: nd_table_.node[new_nd].listen_node = nd; msousa@0: msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] accept_connection(): returning new_nd = %d\n", pthread_self(), new_nd); msousa@0: #endif msousa@0: return new_nd; msousa@0: } msousa@0: msousa@0: msousa@0: static inline void close_connection(int nd) { msousa@0: if (nd_table_.node[nd].fd >= 0) { msousa@0: /* disconnect the tcp connection */ msousa@0: shutdown(nd_table_.node[nd].fd, SHUT_RDWR); msousa@0: #ifdef ERRMSG msousa@0: int res = msousa@0: #endif msousa@0: close(nd_table_.node[nd].fd); msousa@0: #ifdef ERRMSG msousa@0: if (res < 0) { msousa@0: perror("close()"); msousa@0: fprintf(stderr, ERRMSG_HEAD "Error closing socket\n"); msousa@0: } msousa@0: #endif msousa@0: nd_table_.node[nd].fd = -1; msousa@0: } msousa@0: msousa@0: if (nd_table_.node[nd].node_type == MB_SLAVE_NODE) { msousa@0: /* If it is a slave node, we will not be receiving any more data over this disconnected node, msousa@0: * (MB_SLAVE_NODE do not get re-connected!), so we free the node... msousa@0: */ msousa@0: nd_table_close_node(&nd_table_, nd); msousa@0: } msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** Data format conversion **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: msousa@0: /* msousa@0: * Functions to convert u16 variables msousa@0: * between network and host byte order msousa@0: * msousa@0: * NOTE: Modbus uses MSByte first, just like msousa@0: * tcp/ip, so we use the htons() and msousa@0: * ntoh() functions to guarantee msousa@0: * code portability. msousa@0: */ msousa@0: msousa@0: static inline u16 mb_hton(u16 h_value) { msousa@0: /* return h_value; */ msousa@0: return htons(h_value); msousa@0: } msousa@0: msousa@0: static inline u16 mb_ntoh(u16 m_value) { msousa@0: /* return m_value; */ msousa@0: return ntohs(m_value); msousa@0: } msousa@0: msousa@0: static inline u8 msb(u16 value) { msousa@0: /* return Most Significant Byte of value; */ msousa@0: return (value >> 8) & 0xFF; msousa@0: } msousa@0: msousa@0: static inline u8 lsb(u16 value) { msousa@0: /* return Least Significant Byte of value; */ msousa@0: return value & 0xFF; msousa@0: } msousa@0: msousa@0: #define u16_v(char_ptr) (*((u16 *)(&(char_ptr)))) msousa@0: msousa@0: msousa@0: /************************************/ msousa@0: /** **/ msousa@0: /** Build/Check a frame header **/ msousa@0: /** **/ msousa@0: /************************************/ msousa@0: msousa@0: /* A modbus TCP frame header has 6 bytes... msousa@0: * header[0-1] -> transaction id msousa@0: * header[2-3] -> must be 0 msousa@0: * header[4-5] -> frame data length (must be <= 255) msousa@0: */ msousa@0: #if TCP_HEADER_LENGTH < 6 msousa@0: #error This code assumes a header size of 6 bytes, but TCP_HEADER_LENGTH < 6 msousa@0: #endif msousa@0: msousa@0: static inline void build_header(u8 *header, msousa@0: u16 transaction_id, msousa@0: u16 byte_count) msousa@0: { msousa@0: u16_v(header[0]) = mb_hton(transaction_id); msousa@0: header[2] = 0; msousa@0: header[3] = 0; msousa@0: u16_v(header[4]) = mb_hton(byte_count); msousa@0: } msousa@0: msousa@0: msousa@0: static inline int check_header(u8 *header, msousa@0: u16 *transaction_id, msousa@0: u16 *byte_count) msousa@0: { msousa@0: if ((header[2] != 0) || (header[3] != 0)) msousa@0: return -1; msousa@0: msousa@0: *transaction_id = mb_ntoh(*(u16 *)(header + 0)); msousa@0: *byte_count = mb_ntoh(*(u16 *)(header + 4)); msousa@0: msousa@0: if (*byte_count > MAX_L2_FRAME_LENGTH) msousa@0: return -1; msousa@0: msousa@0: return 0; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**** Sending of Modbus TCP Frames ****/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: msousa@0: // pthread_mutex_t sendmsg_mutex = PTHREAD_MUTEX_INITIALIZER; msousa@0: msousa@0: /* NOTE: this function MUST be thread safe!! */ msousa@0: int modbus_tcp_write(int nd, /* node descriptor */ msousa@0: u8 *data, msousa@0: size_t data_length, msousa@0: u16 transaction_id, msousa@0: const struct timespec *transmit_timeout msousa@0: ) msousa@0: { msousa@0: #define data_vector_size 2 msousa@0: msousa@0: u8 header[TCP_HEADER_LENGTH]; msousa@0: struct iovec data_vector[data_vector_size] = { msousa@0: {(void *)header, TCP_HEADER_LENGTH}, msousa@0: {NULL, 0}}; msousa@0: struct msghdr msg = {NULL, 0, data_vector, data_vector_size, NULL, 0, 0}; msousa@0: int res, bytes_sent; msousa@0: msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] modbus_tcp_write(): called... nd=%d\n", pthread_self(), nd); msousa@0: #endif msousa@0: msousa@0: if ((nd >= nd_table_.node_count) || (nd < 0)) msousa@0: /* invalid node descriptor... */ msousa@0: return -1; msousa@0: msousa@0: #ifdef DEBUG msousa@0: // printf("[%lu] locking mutex...\n", pthread_self()); msousa@0: #endif msousa@0: // while (pthread_mutex_lock(&sendmsg_mutex) != 0); msousa@0: msousa@0: /************************* msousa@0: * prepare the header... * msousa@0: *************************/ msousa@0: build_header(header, transaction_id, data_length); msousa@0: #ifdef DEBUG msousa@0: /* Print the hex value of each character that is about to be msousa@0: * sent over the bus. msousa@0: */ msousa@0: { int i; msousa@0: printf("modbus_tcp_write(): sending data...\n"); msousa@0: for(i = 0; i < TCP_HEADER_LENGTH; i++) msousa@0: printf("[0x%2X]", header[i]); msousa@0: for(i = 0; i < data_length; i++) msousa@0: printf("[0x%2X]", data[i]); msousa@0: printf("\n"); msousa@0: } msousa@0: #endif msousa@0: msousa@0: /****************************************** msousa@0: * do we need to re-establish connection? * msousa@0: ******************************************/ msousa@0: if (open_connection(nd, transmit_timeout) < 0) { msousa@0: #ifdef DEBUG msousa@0: fprintf(stderr, "[%lu] modbus_tcp_write(): could not establish connection...\n", pthread_self()); msousa@0: #endif msousa@0: #ifdef ERRMSG msousa@0: fprintf(stderr, ERRMSG_HEAD "could not establish connection...\n"); msousa@0: #endif msousa@0: return -1; msousa@0: } msousa@0: msousa@0: /********************** msousa@0: * write to output... * msousa@0: **********************/ msousa@0: /* TWO ALTERNATIVE IMPLEMENTATIONS !!! */ msousa@0: #if 0 msousa@0: /* write header */ msousa@0: bytes_sent = 0; msousa@0: while (1) { msousa@0: res = write(nd_table_.node[nd].fd, header+bytes_sent, TCP_HEADER_LENGTH-bytes_sent); msousa@0: if (res < 0) { msousa@0: if ((errno != EAGAIN ) && (errno != EINTR )) { msousa@0: /* error sending message... */ msousa@0: close_connection(nd); msousa@0: return -1; msousa@0: } else { msousa@0: continue; msousa@0: } msousa@0: } else { msousa@0: /* res >= 0 */ msousa@0: bytes_sent += res; msousa@0: if (bytes_sent >= TCP_HEADER_LENGTH) { msousa@0: break; msousa@0: } msousa@0: } msousa@0: } msousa@0: msousa@0: /* write data */ msousa@0: bytes_sent = 0; msousa@0: while (1) { msousa@0: res = write(nd_table_.node[nd].fd, data+bytes_sent, data_length-bytes_sent); msousa@0: if (res < 0) { msousa@0: if ((errno != EAGAIN ) && (errno != EINTR )) { msousa@0: /* error sending message... */ msousa@0: close_connection(nd); msousa@0: return -1; msousa@0: } else { msousa@0: continue; msousa@0: } msousa@0: } else { msousa@0: /* res >= 0 */ msousa@0: bytes_sent += res; msousa@0: if (bytes_sent >= data_length) { msousa@0: /* query succesfully sent! */ msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] modbus_tcp_write(): sent %d bytes\n", pthread_self(), TCP_HEADER_LENGTH+data_length); msousa@0: #endif msousa@0: return data_length; msousa@0: } msousa@0: } msousa@0: } msousa@0: msousa@0: /********************** msousa@0: * write to output... * msousa@0: **********************/ msousa@0: #else msousa@0: /* We are optimising for the most likely case, and in doing that msousa@0: * we are making the least likely case have worse behaviour! msousa@0: * Read on for an explanation... msousa@0: * msousa@0: * - The optimised behaviour for the most likely case: msousa@0: * We have set the NO_DELAY flag on the socket, so the IP datagram msousa@0: * is not delayed and is therefore sent as soon as any data is written to msousa@0: * the socket. msousa@0: * In order to send the whole message in a single IP datagram, we have to msousa@0: * write both the the header and the data with a single call to write() msousa@0: * In order to not to have to copy the data around just to add the msousa@0: * message header, we use sendmsg() instead of write()! msousa@0: * msousa@0: * - The worse behaviour for the least likely case: msousa@0: * If for some reason only part of the data is sent with the first call to msousa@0: * write(), a datagram is sent right away, and the subsequent data will msousa@0: * be sent in another datagram. :-( msousa@0: */ msousa@0: /* NOTE: since snedmsg() is not thread safe, we use a mutex to protect access to this function... */ msousa@0: msousa@0: data_vector[data_vector_size - 1].iov_base = data; msousa@0: data_vector[data_vector_size - 1].iov_len = data_length; msousa@0: data_vector[ 0].iov_base = header; msousa@0: data_vector[ 0].iov_len = TCP_HEADER_LENGTH; msousa@0: bytes_sent = 0; msousa@0: while (1) { msousa@0: int sendmsg_errno; msousa@0: /* Please see the comment just above the main loop!! */ msousa@0: res = sendmsg(nd_table_.node[nd].fd, &msg, 0); msousa@0: sendmsg_errno = errno; msousa@0: if (res < 0) { msousa@0: if ((sendmsg_errno != EAGAIN ) && (sendmsg_errno != EINTR )) { msousa@0: /* error sending message... */ msousa@0: close_connection(nd); msousa@0: return -1; msousa@0: } else { msousa@0: continue; msousa@0: } msousa@0: } else { msousa@0: /* res >= 0 */ msousa@0: bytes_sent += res; msousa@0: if (bytes_sent >= data_length + TCP_HEADER_LENGTH) { msousa@0: /* query succesfully sent! */ msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] modbus_tcp_write(): sent %d bytes\n", pthread_self(), bytes_sent); msousa@0: #endif msousa@0: // pthread_mutex_unlock(&sendmsg_mutex); msousa@0: #ifdef DEBUG msousa@0: // printf("[%lu] unlocked mutex...\n", pthread_self()); msousa@0: #endif msousa@0: return data_length; msousa@0: } msousa@0: msousa@0: /* adjust the data_vector... */ msousa@0: if (res < data_vector[0].iov_len) { msousa@0: u8* tmp = data_vector[0].iov_base; msousa@0: tmp += res; msousa@0: data_vector[0].iov_len -= res; msousa@0: data_vector[0].iov_base = tmp; msousa@0: } else { msousa@0: u8* tmp = data_vector[1].iov_base; msousa@0: tmp += res; msousa@0: res -= data_vector[0].iov_len; msousa@0: data_vector[0].iov_len = 0; msousa@0: data_vector[1].iov_len -= res; msousa@0: data_vector[1].iov_base = tmp; msousa@0: } msousa@0: } msousa@0: } /* while (1) */ msousa@0: #endif msousa@0: msousa@0: /* humour the compiler... */ msousa@0: // pthread_mutex_unlock(&sendmsg_mutex); msousa@0: #ifdef DEBUG msousa@0: // printf("[%lu] unlocked mutex...\n", pthread_self()); msousa@0: #endif msousa@0: return -1; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**** Receiving Modbus TCP Frames ****/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: msousa@0: msousa@0: /* A helper function to modbus_tcp_read() msousa@0: * msousa@0: * WARNING: The semantics of this function are not what you would expect! msousa@0: * msousa@0: * if (data_already_available != 0) msousa@0: * It assumes that select() has already been called before msousa@0: * this function got called, and we are therefore guaranteed msousa@0: * to have at least one byte to read off the socket (the fd). msousa@0: * msousa@0: * if (data_already_available == 0) msousa@0: * it starts off by calling select()! msousa@0: * msousa@0: * msousa@0: * NOTE: Ususal select semantics for (a: end_time == NULL) and msousa@0: * (b: *end_time == 0) also apply. msousa@0: * msousa@0: * (a) Indefinite timeout msousa@0: * (b) Try once, and and quit if no data available. msousa@0: */ msousa@0: /* RETURNS: number of bytes read msousa@0: * -1 read error! msousa@0: * -2 timeout msousa@0: */ msousa@0: static int read_bytes(int fd, msousa@0: u8 *data, msousa@0: int max_data_count, msousa@0: const struct timespec *end_time, msousa@0: int data_already_available) msousa@0: { msousa@0: fd_set rfds; msousa@0: int res, data_count; msousa@0: msousa@0: data_count = 0; msousa@0: msousa@0: while (data_count < max_data_count) { msousa@0: /*============================* msousa@0: * wait for data availability * msousa@0: *============================*/ msousa@0: if (data_already_available == 0) { msousa@0: int sel_res; msousa@0: FD_ZERO(&rfds); msousa@0: FD_SET(fd, &rfds); msousa@0: sel_res = my_select(fd + 1, &rfds, NULL, end_time); msousa@0: if (sel_res < 0) msousa@0: return -1; msousa@0: if (sel_res == 0) msousa@0: /* timeout! */ msousa@0: return -2; msousa@0: } msousa@0: msousa@0: /*============================* msousa@0: * read the available data... * msousa@0: *============================*/ msousa@0: res = read(fd, data + data_count, max_data_count - data_count); msousa@0: if (res == 0) { msousa@0: /* We are guaranteed to have data to read off the fd since we called msousa@0: * select(), but read() returned 0 bytes. msousa@0: * This means that the remote process has closed down the connection, msousa@0: * so we return 0. msousa@0: */ msousa@0: return 0; msousa@0: } msousa@0: msousa@0: if (res < 0) { msousa@0: if (errno != EINTR) msousa@0: return -1; msousa@0: else msousa@0: res = 0; msousa@0: } msousa@0: #ifdef DEBUG msousa@0: {/* display the hex code of each character received */ msousa@0: int i; msousa@0: for (i=0; i < res; i++) msousa@0: printf("<0x%2X>", *(data + data_count + i)); msousa@0: } msousa@0: #endif msousa@0: data_count += res; msousa@0: data_already_available = 0; msousa@0: } /* while ()*/ msousa@0: msousa@0: /* data read succesfully... */ msousa@0: return data_count; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: /***************************************/ msousa@0: /** **/ msousa@0: /** Read a Modbus TCP frame **/ msousa@0: /** off a single identified node. **/ msousa@0: /** **/ msousa@0: /***************************************/ msousa@0: msousa@0: /* This private function will read a Modbus TCP frame off a single identified node msousa@0: * that we know before hand that has data ready to be read off it. The data may or may not be msousa@0: * a valid Modbus TCP frame. It is up to this function to figure that out. msousa@0: */ msousa@0: /* NOTES: msousa@0: * - We re-use the recv_buf_ to load the frame header, so we have to make msousa@0: * sure that the buffer is large enough to take it... msousa@0: */ msousa@0: /* RETURNS: number of bytes read msousa@0: * -1 on read from file/node error msousa@0: * -2 on timeout msousa@0: */ msousa@0: #if RECV_BUFFER_SIZE < TCP_HEADER_LENGTH msousa@0: #error The receive buffer is smaller than the frame header length. msousa@0: #endif msousa@0: msousa@0: static int modbus_tcp_read_frame(int nd, msousa@0: u16 *transaction_id, msousa@0: struct timespec *ts_ptr) { msousa@0: int fd, res; msousa@0: u16 frame_length; msousa@0: msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] modbus_tcp_read_frame(): reading off nd=%d\n", pthread_self(), nd); msousa@0: #endif msousa@0: /*=========================* msousa@0: * read a Modbus TCP frame * msousa@0: *=========================*/ msousa@0: /* assume error... */ msousa@0: fd = nd_table_.node[nd].fd; msousa@0: msousa@0: /*-------------* msousa@0: * read header * msousa@0: *-------------*/ msousa@0: if ((res = read_bytes(fd, nd_table_.node[nd].recv_buf, TCP_HEADER_LENGTH, ts_ptr, 1)) != TCP_HEADER_LENGTH) { msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] modbus_tcp_read_frame(): frame with insuficient bytes for a valid header...\n", pthread_self()); msousa@0: #endif msousa@0: if (res < 0) return res; msousa@0: return -1; msousa@0: } msousa@0: msousa@0: /* let's check for header consistency... */ msousa@0: if (check_header(nd_table_.node[nd].recv_buf, transaction_id, &frame_length) < 0) { msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] modbus_tcp_read_frame(): frame with non valid header...\n", pthread_self()); msousa@0: #endif msousa@0: return -1; msousa@0: } msousa@0: msousa@0: /*-----------* msousa@0: * read data * msousa@0: *-----------*/ msousa@0: if ((res = read_bytes(fd, nd_table_.node[nd].recv_buf, frame_length, ts_ptr, 0)) != frame_length) { msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] modbus_tcp_read_frame(): frame with non valid frame length...\n", pthread_self()); msousa@0: #endif msousa@0: if (res < 0) return res; msousa@0: return -1; msousa@0: } msousa@0: msousa@0: /* frame received succesfully... */ msousa@0: #ifdef DEBUG msousa@0: printf("\n"); msousa@0: #endif msousa@0: return frame_length; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: /***************************************/ msousa@0: /** **/ msousa@0: /** Read a Modbus TCP frame **/ msousa@0: /** OR Accept connection requests **/ msousa@0: /** off possibly multiple node... **/ msousa@0: /** **/ msousa@0: /***************************************/ msousa@0: msousa@0: /* The public function that reads a valid modbus frame. msousa@0: * The frame is read from...: msousa@0: * - if (nd >= 0) and (nd is of type MB_MASTER_NODE or MB_SLAVE_NODE) msousa@0: * The frame is read from the node descriptor nd msousa@0: * - if (nd >= 0) and (nd is of type MB_LISTEN_NODE) msousa@0: * The frame is read from the all node descriptors of type MB_SLAVE_NODE that were msousa@0: * opened as a consequence of a connection request to the nd slave. msousa@0: * In this case, new connection requests to nd will also be accepted! msousa@0: * - if (nd == -1) msousa@0: * The frame is read from any valid and initialised node descriptor. msousa@0: * In this case, new connection requests to any nd of type MB_LISTEN_NODE will also be accepted! msousa@0: * In this case, the node where the data is eventually read from is returned in *nd. msousa@0: * msousa@0: * The send_data and send_length parameters are ignored... msousa@0: * (However, these parameters must stay in order to keep the function msousa@0: * interface identical to the ASCII and RTU versons!) msousa@0: * msousa@0: * return value: The length (in bytes) of the valid frame, msousa@0: * -1 on error msousa@0: * msousa@0: * NOTE: Ususal select semantics for (a: recv_timeout == NULL) and msousa@0: * (b: *recv_timeout == 0) also apply. msousa@0: * msousa@0: * (a) Indefinite timeout msousa@0: * (b) Try once, and and quit if no data available. msousa@0: */ msousa@0: msousa@0: /* RETURNS: number of bytes read msousa@0: * -1 on read from file/node error msousa@0: * -2 on timeout msousa@0: */ msousa@0: int modbus_tcp_read(int *nd, /* node descriptor */ msousa@0: u8 **recv_data_ptr, msousa@0: u16 *transaction_id, msousa@0: const u8 *send_data, /* ignored ! */ msousa@0: int send_length, /* ignored ! */ msousa@0: const struct timespec *recv_timeout) { msousa@0: msousa@0: struct timespec end_time, *ts_ptr; msousa@0: u8 *local_recv_data_ptr; msousa@0: u16 local_transaction_id = 0; msousa@0: msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] modbus_tcp_read(): called... nd=%d\n", pthread_self(), *nd); msousa@0: #endif msousa@0: msousa@0: if (nd == NULL) msousa@0: return -1; msousa@0: msousa@0: if (*nd >= nd_table_.node_count) msousa@0: /* invalid *nd */ msousa@0: /* remember that *nd < 0 is valid!! */ msousa@0: return -1; msousa@0: msousa@0: if (recv_data_ptr == NULL) msousa@0: recv_data_ptr = &local_recv_data_ptr; msousa@0: if (transaction_id == NULL) msousa@0: transaction_id = &local_transaction_id; msousa@0: msousa@0: /* We will potentially call read() multiple times to read in a single frame. msousa@0: * We therefore determine the absolute time_out, and use this as a parameter msousa@0: * for each call to read_bytes() instead of using a relative timeout. msousa@0: * msousa@0: * NOTE: see also the timeout related comment in the read_bytes() function! msousa@0: */ msousa@0: ts_ptr = NULL; msousa@0: if (recv_timeout != NULL) { msousa@0: ts_ptr = &end_time; msousa@0: *ts_ptr = timespec_add_curtime(*recv_timeout); msousa@0: } msousa@0: msousa@0: /* If we must read off a single node... */ msousa@0: if (*nd >= 0) msousa@0: /* but the node does not have a valid fd */ msousa@0: if ((nd_table_.node[*nd].node_type == MB_FREE_NODE) || msousa@0: (nd_table_.node[*nd].fd < 0)) msousa@0: /* then we return an error... */ msousa@0: return -1; msousa@0: msousa@0: /* We will loop forever... msousa@0: * We jump out of the loop and return from the function as soon as: msousa@0: * - we receive a valid modbus message; msousa@0: * OR msousa@0: * - we time out. msousa@0: * msousa@0: * NOTE: This loop will close connections through which we receive invalid frames. msousa@0: * This means that the set of nodes through which we may receive data may change with each msousa@0: * loop iteration. => We need to re-calculate the fds in each loop iteration! msousa@0: */ msousa@0: msousa@0: while (1) { msousa@0: int nd_count, fd_high; msousa@0: fd_set rfds; msousa@0: msousa@0: /* We prepare our fd sets here so we can later call select() */ msousa@0: FD_ZERO(&rfds); msousa@0: fd_high = -1; msousa@0: msousa@0: for (nd_count = 0; nd_count < nd_table_.node_count; nd_count++) { msousa@0: if (nd_table_.node[nd_count].node_type != MB_FREE_NODE) msousa@0: { msousa@0: if ((*nd < 0) // we select from all nodes msousa@0: || (*nd == nd_count) // we select from this specific node msousa@0: // we are listening on a MB_LISTEN_NODE, so we must also receive requests sent to slave nodes msousa@0: // whose connection requests arrived through this MB_LISTEN_NDODE msousa@0: || ((nd_table_.node[nd_count].node_type == MB_SLAVE_NODE) && (nd_table_.node[nd_count].listen_node == *nd))) msousa@0: { msousa@0: /* check if valid fd */ msousa@0: if (nd_table_.node[nd_count].fd >= 0) { msousa@0: /* Add the descriptor to the fd set... */ msousa@0: FD_SET(nd_table_.node[nd_count].fd, &rfds); msousa@0: fd_high = max(fd_high, nd_table_.node[nd_count].fd); msousa@0: } msousa@0: } msousa@0: } msousa@0: } /* for(;;) */ msousa@0: msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] modbus_tcp_read(): while(1) looping. fd_high = %d, nd=%d\n", pthread_self(), fd_high, *nd); msousa@0: #endif msousa@0: msousa@0: if (fd_high == -1) msousa@0: /* we will not be reading from any node! */ msousa@0: return -1; msousa@0: msousa@0: /* We now call select and wait for activity on the nodes we are listening to */ msousa@0: { int sel_res = my_select(fd_high + 1, &rfds, NULL, ts_ptr); msousa@0: if (sel_res < 0) msousa@0: return -1; msousa@0: if (sel_res == 0) msousa@0: /* timeout! */ msousa@0: return -2; msousa@0: } msousa@0: msousa@0: /* figure out which nd is ready to be read... */ msousa@0: for (nd_count = 0; nd_count < nd_table_.node_count; nd_count++) { msousa@0: if ((nd_table_.node[nd_count].node_type != MB_FREE_NODE) && msousa@0: (nd_table_.node[nd_count].fd >= 0)) { msousa@0: if (FD_ISSET(nd_table_.node[nd_count].fd, &rfds)) { msousa@0: /* Found the node descriptor... */ msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] modbus_tcp_read(): my_select() returned due to activity on node nd=%d\n", pthread_self(), nd_count); msousa@0: #endif msousa@0: if (nd_table_.node[nd_count].node_type == MB_LISTEN_NODE) { msousa@0: /* We must accept a new connection... msousa@0: * No need to check for errors. msousa@0: * If one occurs, there is nothing we can do... msousa@0: */ msousa@0: accept_connection(nd_count); msousa@0: } else { msousa@0: /* it is a MB_SLAVE_NODE or a MB_MASTER_NODE */ msousa@0: /* We will read a frame off this nd */ msousa@0: int res; msousa@0: res = modbus_tcp_read_frame(nd_count, transaction_id, ts_ptr); msousa@0: if (res > 0) { msousa@0: *nd = nd_count; msousa@0: *recv_data_ptr = nd_table_.node[nd_count].recv_buf; msousa@0: return res; msousa@0: } msousa@0: if (res < 0) { msousa@0: /* We had an error reading the frame... msousa@0: * We handle it by closing the connection, as specified by msousa@0: * the modbus TCP protocol! msousa@0: * msousa@0: * NOTE: The error may have been a timeout, which means this function should return immediately. msousa@0: * However, in this case we let the execution loop once again msousa@0: * in the while(1) loop. My_select() will be called again msousa@0: * and the timeout detected. The timeout error code (-2) msousa@0: * will then be returned correctly! msousa@0: */ msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] modbus_tcp_read(): error reading frame. Closing connection...\n", pthread_self()); msousa@0: #endif msousa@0: /* We close the socket... */ msousa@0: close_connection(nd_count); msousa@0: } msousa@0: } msousa@0: /* we have found the node descriptor, so let's jump out of the for(;;) loop */ msousa@0: break; msousa@0: } msousa@0: } msousa@0: } /* for(;;) */ msousa@0: msousa@0: /* We were unsuccesfull reading a frame, so we try again... */ msousa@0: } /* while (1) */ msousa@0: msousa@0: /* humour the compiler... */ msousa@0: return -1; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**** Initialising and Shutting Down Library ****/ msousa@0: /**** ****/ msousa@0: /**** ****/ msousa@0: /**************************************************************/ msousa@0: /**************************************************************/ msousa@0: msousa@0: msousa@0: /* Ugly hack... msousa@0: * Beremiz will be calling modbus_tcp_init() multiple times (through modbus_init() ) msousa@0: * (once for each plugin instance) msousa@0: * It will also be calling modbus_tcp_done() the same number of times msousa@0: * We only want to really shutdown the library the last time it is called. msousa@0: * We therefore keep a counter of how many times modbus_tcp_init() is called, msousa@0: * and decrement it in modbus_tcp_done() msousa@0: */ msousa@0: int modbus_tcp_init_counter = 0; msousa@0: msousa@0: /******************************/ msousa@0: /** **/ msousa@0: /** Load Default Values **/ msousa@0: /** **/ msousa@0: /******************************/ msousa@0: msousa@0: static void set_defaults(const char **service) { msousa@0: /* Set the default values, if required... */ msousa@0: if (*service == NULL) msousa@0: *service = DEF_SERVICE; msousa@0: } msousa@0: msousa@0: msousa@0: /******************************/ msousa@0: /** **/ msousa@0: /** Initialise Library **/ msousa@0: /** **/ msousa@0: /******************************/ msousa@0: /* returns the number of nodes succesfully initialised... msousa@0: * returns -1 on error. msousa@0: */ msousa@0: int modbus_tcp_init(int nd_count, msousa@0: optimization_t opt /* ignored... */, msousa@0: int *extra_bytes) { msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] modbus_tcp_init(): called...\n", pthread_self()); msousa@0: printf("[%lu] creating %d nodes:\n", pthread_self(), nd_count); msousa@0: #endif msousa@0: msousa@0: modbus_tcp_init_counter++; msousa@0: msousa@0: /* set the extra_bytes value... */ msousa@0: /* Please see note before the modbus_rtu_write() function for a msousa@0: * better understanding of this extremely ugly hack... This will be msousa@0: * in the mb_rtu.c file!! msousa@0: * msousa@0: * The number of extra bytes that must be allocated to the data buffer msousa@0: * before calling modbus_tcp_write() msousa@0: */ msousa@0: if (extra_bytes != NULL) msousa@0: *extra_bytes = 0; msousa@0: msousa@0: if (0 == nd_count) msousa@0: /* no need to initialise this layer! */ msousa@0: return 0; msousa@0: if (nd_count <= 0) msousa@0: /* invalid node count... */ msousa@0: goto error_exit_1; msousa@0: msousa@0: /* initialise the node table... */ msousa@0: if (nd_table_init(&nd_table_, nd_count) < 0) msousa@0: goto error_exit_1; msousa@0: msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] modbus_tcp_init(): %d node(s) opened succesfully\n", pthread_self(), nd_count); msousa@0: #endif msousa@0: return nd_count; /* number of succesfully created nodes! */ msousa@0: msousa@0: /* msousa@0: error_exit_2: msousa@0: nd_table_done(&nd_table_); msousa@0: */ msousa@0: error_exit_1: msousa@0: if (extra_bytes != NULL) msousa@0: *extra_bytes = 0; msousa@0: return -1; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: /******************************/ msousa@0: /** **/ msousa@0: /** Open a Master Node **/ msousa@0: /** **/ msousa@0: /******************************/ msousa@0: int modbus_tcp_connect(node_addr_t node_addr) { msousa@0: int node_descriptor; msousa@0: struct sockaddr_in tmp_addr; msousa@0: msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] modbus_tcp_connect(): called...\n", pthread_self()); msousa@0: printf("[%lu] %s:%s\n", pthread_self(), msousa@0: node_addr.addr.tcp.host, msousa@0: node_addr.addr.tcp.service); msousa@0: #endif msousa@0: msousa@0: /* Check for valid address family */ msousa@0: if (node_addr.naf != naf_tcp) msousa@0: /* wrong address type... */ msousa@0: return -1; msousa@0: msousa@0: /* set the default values... */ msousa@0: set_defaults(&(node_addr.addr.tcp.service)); msousa@0: msousa@0: /* Check the parameters we were passed... */ msousa@0: if(sin_initaddr(&tmp_addr, msousa@10: node_addr.addr.tcp.host, 1, // 1 => allow host NULL, "" or "*" -> INADDR_ANY msousa@10: node_addr.addr.tcp.service, 1, // 1 => allow serivce NULL or "" -> port = 0 msousa@0: DEF_PROTOCOL) msousa@0: < 0) { msousa@0: #ifdef ERRMSG msousa@0: fprintf(stderr, ERRMSG_HEAD "Error parsing/resolving address %s:%s\n", msousa@0: node_addr.addr.tcp.host, msousa@0: node_addr.addr.tcp.service); msousa@0: #endif msousa@0: return -1; msousa@0: } msousa@0: msousa@0: /* find a free node descriptor */ msousa@0: if ((node_descriptor = nd_table_get_free_node(&nd_table_, MB_MASTER_NODE)) < 0) msousa@0: /* if no free nodes to initialize, then we are finished... */ msousa@0: return -1; msousa@0: msousa@0: nd_table_.node[node_descriptor].addr = tmp_addr; msousa@0: nd_table_.node[node_descriptor].fd = -1; /* not currently connected... */ msousa@0: nd_table_.node[node_descriptor].close_on_silence = node_addr.addr.tcp.close_on_silence; msousa@0: msousa@0: if (nd_table_.node[node_descriptor].close_on_silence < 0) msousa@0: nd_table_.node[node_descriptor].close_on_silence = DEF_CLOSE_ON_SILENCE; msousa@0: msousa@0: /* WE have never tried to connect, so print an error the next time we try to connect */ msousa@0: nd_table_.node[node_descriptor].print_connect_error = 1; msousa@0: msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] modbus_tcp_connect(): returning nd=%d\n", pthread_self(), node_descriptor); msousa@0: #endif msousa@0: return node_descriptor; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: /******************************/ msousa@0: /** **/ msousa@0: /** Open a Slave Node **/ msousa@0: /** **/ msousa@0: /******************************/ msousa@0: msousa@0: int modbus_tcp_listen(node_addr_t node_addr) { msousa@0: int fd, nd; msousa@0: msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] modbus_tcp_listen(): called...\n", pthread_self()); msousa@0: printf("[%lu] %s:%s\n", pthread_self(), msousa@0: node_addr.addr.tcp.host, msousa@0: node_addr.addr.tcp.service); msousa@0: #endif msousa@0: msousa@0: /* Check for valid address family */ msousa@0: if (node_addr.naf != naf_tcp) msousa@0: /* wrong address type... */ msousa@0: goto error_exit_0; msousa@0: msousa@0: /* set the default values... */ msousa@0: set_defaults(&(node_addr.addr.tcp.service)); msousa@0: msousa@0: /* create a socket and bind it to the appropriate port... */ msousa@0: fd = sin_bindsock(node_addr.addr.tcp.host, msousa@0: node_addr.addr.tcp.service, msousa@0: DEF_PROTOCOL); msousa@0: if (fd < 0) { msousa@0: #ifdef ERRMSG msousa@0: fprintf(stderr, ERRMSG_HEAD "Could not bind to socket %s:%s\n", msousa@0: ((node_addr.addr.tcp.host==NULL)?"#ANY#":node_addr.addr.tcp.host), msousa@0: node_addr.addr.tcp.service); msousa@0: #endif msousa@0: goto error_exit_0; msousa@0: } msousa@0: if (listen(fd, DEF_MAX_PENDING_CONNECTION_REQUESTS) < 0) msousa@0: goto error_exit_0; msousa@0: msousa@0: /* find a free node descriptor */ msousa@0: if ((nd = nd_table_get_free_node(&nd_table_, MB_LISTEN_NODE)) < 0) { msousa@0: /* if no free nodes to initialize, then we are finished... */ msousa@0: goto error_exit_1; msousa@0: } msousa@0: msousa@0: /* nd_table_.node[nd].addr = tmp_addr; */ /* does not apply for MB_LISTEN_NODE */ msousa@0: nd_table_.node[nd].fd = fd; /* not currently connected... */ msousa@0: msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] modbus_tcp_listen(): returning nd=%d\n", pthread_self(), nd); msousa@0: #endif msousa@0: return nd; msousa@0: msousa@0: error_exit_1: msousa@0: close(fd); msousa@0: error_exit_0: msousa@0: return -1; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: /******************************/ msousa@0: /** **/ msousa@0: /** Close a node **/ msousa@0: /** **/ msousa@0: /******************************/ msousa@0: msousa@0: int modbus_tcp_close(int nd) { msousa@0: #ifdef DEBUG msousa@0: fprintf(stderr, "[%lu] modbus_tcp_close(): called... nd=%d\n", pthread_self(), nd); msousa@0: #endif msousa@0: msousa@0: if ((nd < 0) || (nd >= nd_table_.node_count)) { msousa@0: /* invalid nd */ msousa@0: #ifdef DEBUG msousa@0: fprintf(stderr, "[%lu] modbus_tcp_close(): invalid node %d. Should be < %d\n", pthread_self(), nd, nd_table_.node_count); msousa@0: #endif msousa@0: return -1; msousa@0: } msousa@0: msousa@0: if (nd_table_.node[nd].node_type == MB_FREE_NODE) msousa@0: /* already free node */ msousa@0: return 0; msousa@0: msousa@0: close_connection(nd); msousa@0: msousa@0: nd_table_close_node(&nd_table_, nd); msousa@0: msousa@0: return 0; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: /**********************************/ msousa@0: /** **/ msousa@0: /** Close all open connections **/ msousa@0: /** **/ msousa@0: /**********************************/ msousa@0: msousa@0: int modbus_tcp_silence_init(void) { msousa@0: int nd; msousa@0: msousa@0: #ifdef DEBUG msousa@0: printf("[%lu] modbus_tcp_silence_init(): called...\n", pthread_self()); msousa@0: #endif msousa@0: msousa@0: /* close all master connections that remain open... */ msousa@0: for (nd = 0; nd < nd_table_.node_count; nd++) msousa@0: if (nd_table_.node[nd].node_type == MB_MASTER_NODE) msousa@0: if (nd_table_.node[nd].close_on_silence > 0) msousa@0: /* node is is being used for a master device, msousa@0: * and wishes to be closed... ...so we close it! msousa@0: */ msousa@0: close_connection(nd); msousa@0: msousa@0: return 0; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: /******************************/ msousa@0: /** **/ msousa@0: /** Shutdown the Library **/ msousa@0: /** **/ msousa@0: /******************************/ msousa@0: msousa@0: int modbus_tcp_done(void) { msousa@0: int i; msousa@0: msousa@0: modbus_tcp_init_counter--; msousa@0: if (modbus_tcp_init_counter != 0) return 0; /* ignore this request */ msousa@0: msousa@0: /* close all the connections... */ msousa@0: for (i = 0; i < nd_table_.node_count; i++) msousa@0: modbus_tcp_close(i); msousa@0: msousa@0: /* Free memory... */ msousa@0: nd_table_done(&nd_table_); msousa@0: msousa@0: return 0; msousa@0: } msousa@0: msousa@0: msousa@0: msousa@0: msousa@0: double modbus_tcp_get_min_timeout(int baud, msousa@0: int parity, msousa@0: int data_bits, msousa@0: int stop_bits) { msousa@0: return 0; msousa@0: }