/* * OpenMODBUS/TCP to RS-232/485 MODBUS RTU gateway * * conn.c - connections management procedures * * Copyright (c) 2002-2003, Victor Antonovich (avmlink@vlink.ru) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * $Id: conn.c,v 1.1.1.1 2003/09/13 20:38:33 kapyar Exp $ */ #include "conn.h" #include "queue.h" #include "state.h" /* global variables */ extern int server_sd; extern queue_t queue; extern ttydata_t tty; extern cfg_t cfg; conn_t *actconn; /* last active connection */ int max_sd; /* major descriptor in the select() sets */ void conn_tty_start(ttydata_t *tty, conn_t *conn); ssize_t conn_read(int d, void *buf, size_t nbytes); ssize_t conn_write(int d, void *buf, size_t nbytes); int conn_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); #define FD_MSET(d, s) do { FD_SET(d, s); max_sd = MAX(d, max_sd); } while (0); /* * Connections startup initialization * Parameters: none * Return: RC_OK in case of success, RC_ERR otherwise */ int conn_init(void) { /* tty device initialization */ #ifdef TRXCTL tty_init(&tty, cfg.ttyport, cfg.ttyspeed, cfg.trxcntl); #else tty_init(&tty, cfg.ttyport, cfg.ttyspeed); #endif if (tty_open(&tty) != RC_OK) { #ifdef LOG log(0, "conn_init():" " can't open tty device %s (%s)", cfg.ttyport, strerror(errno)); #endif return RC_ERR; } state_tty_set(&tty, TTY_READY); /* create server socket */ if ((server_sd = sock_create_server("", cfg.serverport, TRUE)) < 0) { #ifdef LOG log(0, "conn_init():" " can't create listen() socket (%s)", strerror(errno)); #endif return RC_ERR; } /* connections queue initialization */ queue_init(&queue); return RC_OK; } /* * Open new client connection * Parameters: none * Return: none */ void conn_open(void) { int sd; conn_t *newconn; struct sockaddr_in rmt_addr; if ((sd = sock_accept(server_sd, &rmt_addr, TRUE)) == RC_ERR) { /* error in conn_accept() */ #ifdef LOG log(0, "conn_open(): error in accept() (%s)", strerror(errno)); #endif return; } #ifdef LOG log(2, "conn_open(): accepting connection from %s", inet_ntoa(rmt_addr.sin_addr)); #endif /* compare descriptor of connection with FD_SETSIZE */ if (sd >= FD_SETSIZE) { #ifdef LOG log(1, "conn_open(): FD_SETSIZE limit reached," " connection from %s will be dropped", inet_ntoa(rmt_addr.sin_addr)); #endif close(sd); return; } /* check current number of connections */ if (queue.len == cfg.maxconn) { #ifdef LOG log(1, "conn_open(): number of connections limit reached," " connection from %s will be dropped", inet_ntoa(rmt_addr.sin_addr)); #endif close(sd); return; } /* enqueue connection */ newconn = queue_new_elem(&queue); newconn->sd = sd; memcpy((void *) &newconn->sockaddr, &rmt_addr, sizeof(struct sockaddr_in)); state_conn_set(newconn, CONN_HEADER); } /* * Close client connection * Parameters: CONN - ptr to connection to close * Return: pointer to next queue element */ conn_t * conn_close(conn_t *conn) { conn_t *nextconn; #ifdef LOG log(2, "conn_close(): closing connection from %s", inet_ntoa(conn->sockaddr.sin_addr)); #endif /* close socket */ close(conn->sd); /* get pointer to next element */ nextconn = queue_next_elem(&queue, conn); /* dequeue connection */ queue_delete_elem(&queue, conn); if (actconn == conn) actconn = nextconn; return nextconn; } /* * Start tty transaction * Parameters: TTY - ptr to tty structure, * CONN - ptr to active connection * Return: none */ void conn_tty_start(ttydata_t *tty, conn_t *conn) { (void)memcpy((void *)tty->txbuf, (void *)(conn->buf + HDRSIZE), MB_HDR(conn->buf, MB_LENGTH_L)); modbus_crc_write(tty->txbuf, MB_HDR(conn->buf, MB_LENGTH_L)); tty->txlen = MB_HDR(conn->buf, MB_LENGTH_L) + CRCSIZE; state_tty_set(tty, TTY_RQST); actconn = conn; } /* * Read() wrapper. Read nomore BYTES from descriptor D in buffer BUF * Return: number of successfully readed bytes, * RC_ERR in case of error. */ ssize_t conn_read(int d, void *buf, size_t nbytes) { int rc; do { /* trying read from descriptor while breaked by signals */ rc = read(d, buf, nbytes); } while (rc == -1 && errno == EINTR); return (rc < 0) ? RC_ERR : rc; } /* * Write() wrapper. Write nomore BYTES to descriptor D from buffer BUF * Return: number of successfully written bytes, * RC_ERR in case of error. */ ssize_t conn_write(int d, void *buf, size_t nbytes) { int rc; do { /* trying write to descriptor while breaked by signals */ rc = write(d, buf, nbytes); } while (rc == -1 && errno == EINTR); return (rc < 0) ? RC_ERR : rc; } #if 0 /* * Select() wrapper with signal checking. * Return: number number of ready descriptors, * RC_ERR in case of error. */ int conn_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) { int rc; do { /* trying to select() while breaked by signals */ rc = select(nfds, readfds, writefds, exceptfds, timeout); } while (rc == -1 && errno == EINTR); return (rc < 0) ? RC_ERR : rc; } #endif /* * Connections serving loop * Parameters: none * Return: none */ void conn_loop(void) { int rc, max_sd, len, min_timeout; fd_set sdsetrd, sdsetwr; struct timeval ts, tts, t_out; unsigned long tval, tout_sec, tout = 0ul; conn_t *curconn = NULL; while (TRUE) { /* check for the signals */ if (sig_flag) sig_exec(); /* update FD_SETs */ FD_ZERO(&sdsetrd); max_sd = server_sd; FD_MSET(server_sd, &sdsetrd); FD_ZERO(&sdsetwr); /* update FD_SETs by TCP connections */ len = queue.len; curconn = queue.beg; min_timeout = cfg.conntimeout; while (len--) { switch (curconn->state) { case CONN_HEADER: case CONN_RQST: FD_MSET(curconn->sd, &sdsetrd); break; case CONN_RESP: FD_MSET(curconn->sd, &sdsetwr); break; } min_timeout = MIN(min_timeout, curconn->timeout); curconn = queue_next_elem(&queue, curconn); } /* update FD_SETs by tty connection */ FD_MSET(tty.fd, &sdsetrd); if (tty.state == TTY_RQST) FD_MSET(tty.fd, &sdsetwr); if (tty.timer) { /* tty.timer is non-zero in TTY_PAUSE, TTY_RESP states */ t_out.tv_sec = tty.timer / 1000000ul; t_out.tv_usec = tty.timer % 1000000ul; } else { t_out.tv_usec = 0ul; if (cfg.conntimeout) t_out.tv_sec = min_timeout; /* minor timeout value */ else t_out.tv_sec = 10ul; /* XXX default timeout value */ } (void)gettimeofday(&ts, NULL); /* make timestamp */ #ifdef DEBUG log(7, "conn_loop(): select(): max_sd = %d, t_out = %06lu:%06lu ", max_sd, t_out.tv_sec, t_out.tv_usec); #endif rc = select(max_sd + 1, &sdsetrd, &sdsetwr, NULL, &t_out); if (rc < 0) { /* some error caused while select() */ if (errno == EINTR) continue; /* process signals */ /* unrecoverable error in select(), exiting */ #ifdef LOG log(0, "conn_loop(): error in select() (%s)", strerror(errno)); #endif break; } /* calculating elapsed time */ (void)gettimeofday(&tts, NULL); tval = 1000000ul * (tts.tv_sec - ts.tv_sec) + (tts.tv_usec - ts.tv_usec); /* modify tty timer */ if (tty.timer) { /* tty timer is active */ if (tty.timer <= tval) switch (tty.state) { /* timer expired */ case TTY_PAUSE: /* inter-request pause elapsed */ /* looking for connections in CONN_TTY state */ curconn = state_conn_search(&queue, actconn, CONN_TTY); if (curconn != NULL) conn_tty_start(&tty, curconn); else state_tty_set(&tty, TTY_READY); break; case TTY_RESP: /* checking for received data */ if (FD_ISSET(tty.fd, &sdsetrd)) break; /* response timeout handling */ if (!tty.ptrbuf) {/* there no bytes received */ #ifdef DEBUG log(5, "tty: response timeout", tty.ptrbuf); #endif if (!tty.trynum) modbus_ex_write(actconn->buf, MB_EX_TIMEOUT); else { /* retry request */ #ifdef DEBUG log(5, "tty: attempt to retry request (%u of %u)", cfg.maxtry - tty.trynum + 1, cfg.maxtry); #endif state_tty_set(&tty, TTY_RQST); break; } } else { /* some data received */ #ifdef DEBUG log(5, "tty: response readed (total %d bytes)", tty.ptrbuf); #endif if (tty.ptrbuf >= MB_MIN_LEN && modbus_crc_correct(tty.rxbuf, tty.ptrbuf)) { /* received response is correct, make OpenMODBUS response */ #ifdef DEBUG log(5, "tty: response is correct"); #endif (void)memcpy((void *)(actconn->buf + HDRSIZE), (void *)tty.rxbuf, tty.ptrbuf - CRCSIZE); WORD_WR_BE(actconn->buf + MB_LENGTH_H, tty.ptrbuf - CRCSIZE); } else { /* received response is incomplete or CRC failed */ #ifdef DEBUG log(5, "tty: response is incorrect"); #endif if (!tty.trynum) modbus_ex_write(actconn->buf, MB_EX_CRC); else { /* retry request */ #ifdef DEBUG log(5, "tty: attempt to retry request (%u of %u)", cfg.maxtry - tty.trynum + 1, cfg.maxtry); #endif state_tty_set(&tty, TTY_RQST); break; } } } /* switch connection to response state */ state_conn_set(actconn, CONN_RESP); /* make inter-request pause */ state_tty_set(&tty, TTY_PAUSE); break; } else tty.timer -= tval; } if (cfg.conntimeout) { /* expire staled connections */ tout += tval; tout_sec = tout / 1000000ul; if (tout_sec) { /* at least one second elapsed, check for staled connections */ len = queue.len; curconn = queue.beg; while (len--) { curconn->timeout -= tout_sec; if (curconn->timeout <= 0) { /* timeout expired */ if (curconn->state == CONN_TTY) { /* deadlock in CONN_TTY state, exiting */ #ifdef LOG log(0, "conn[%s]: state CONN_TTY deadlock, exiting!", inet_ntoa(curconn->sockaddr.sin_addr)); #endif exit (-1); } /* purge connection */ #ifdef LOG log(2, "conn[%s]: timeout, closing connection", inet_ntoa(curconn->sockaddr.sin_addr)); #endif curconn = conn_close(curconn); continue; } curconn = queue_next_elem(&queue, curconn); } tout = tout % 1000000ul; } } if (rc == 0) continue; /* timeout caused, we will do select() again */ /* checking for pending connections */ if (FD_ISSET(server_sd, &sdsetrd)) conn_open(); /* tty processing */ if (tty.state == TTY_RQST) if (FD_ISSET(tty.fd, &sdsetwr)) { rc = conn_write(tty.fd, tty.txbuf + tty.ptrbuf, tty.txlen - tty.ptrbuf); if (rc <= 0) { /* error - we can't continue... */ #ifdef LOG log(0, "tty: error in write() (%s)", strerror(errno)); #endif break; /* exiting... */ } #ifdef DEBUG log(7, "tty: written %d bytes", rc); #endif tty.ptrbuf += rc; if (tty.ptrbuf == tty.txlen) { /* request transmitting completed, switch to TTY_RESP */ #ifdef DEBUG log(7, "tty: request written (total %d bytes)", tty.txlen); #endif state_tty_set(&tty, TTY_RESP); } } if (FD_ISSET(tty.fd, &sdsetrd)) { if (tty.state == TTY_RESP) { rc = conn_read(tty.fd, tty.rxbuf + tty.ptrbuf, tty.rxlen - tty.ptrbuf); if (rc <= 0) { /* error - we can't continue... */ #ifdef LOG log(0, "tty: error in read() (%s)", strerror(errno)); #endif break; /* exiting... */ } #ifdef DEBUG log(7, "tty: readed %d bytes", rc); #endif tty.ptrbuf += rc; if (tty.ptrbuf == tty.rxlen) { /* XXX still generating error response */ modbus_ex_write(actconn->buf, MB_EX_CRC); /* switch connection to response state */ state_conn_set(actconn, CONN_RESP); /* make inter-request pause */ state_tty_set(&tty, TTY_PAUSE); } else /* reset timer */ tty.timer = cfg.respwait * 1000l; } else { /* drop unexpected tty data */ if ((rc = conn_read(tty.fd, tty.rxbuf, BUFSIZE)) <= 0) { /* error - we can't continue... */ #ifdef LOG log(0, "tty: error in read() (%s)", strerror(errno)); #endif break; /* exiting... */ } #ifdef DEBUG log(7, "tty: dropped %d bytes", rc); #endif } } /* processing data on the sockets */ len = queue.len; curconn = queue.beg; while (len--) { switch (curconn->state) { case CONN_HEADER: case CONN_RQST: if (FD_ISSET(curconn->sd, &sdsetrd)) { rc = conn_read(curconn->sd, curconn->buf + curconn->ctr, RQSTSIZE - curconn->ctr); if (rc <= 0) { /* error - drop this connection and go to next queue element */ curconn = conn_close(curconn); break; } curconn->ctr += rc; if (curconn->state == CONN_HEADER) if (curconn->ctr >= HDRSIZE) { /* header received completely */ if (modbus_check_header(curconn->buf) != RC_OK) { /* header is damaged, drop connection */ curconn = conn_close(curconn); break; } state_conn_set(curconn, CONN_RQST); } if (curconn->state == CONN_RQST) if (curconn->ctr >= HDRSIZE + MB_HDR(curconn->buf, MB_LENGTH_L)) { /* ### packet received completely ### */ state_conn_set(curconn, CONN_TTY); if (tty.state == TTY_READY) conn_tty_start(&tty, curconn); } } curconn = queue_next_elem(&queue, curconn); break; case CONN_RESP: if (FD_ISSET(curconn->sd, &sdsetwr)) { rc = conn_write(curconn->sd, curconn->buf + curconn->ctr, MB_HDR(curconn->buf, MB_LENGTH_L) + HDRSIZE - curconn->ctr); if (rc <= 0) { /* error - drop this connection and go to next queue element */ curconn = conn_close(curconn); break; } curconn->ctr += rc; if (curconn->ctr == (MB_HDR(curconn->buf, MB_LENGTH_L) + HDRSIZE)) state_conn_set(curconn, CONN_HEADER); } curconn = queue_next_elem(&queue, curconn); break; } /* switch (curconn->state) */ } /* while (len--) */ } /* while (TRUE) */ /* XXX some cleanup must be here */ }