fis-gtm/sr_port/tcp_open.c

417 lines
12 KiB
C

/****************************************************************
* *
* Copyright 2001, 2011 Fidelity Information Services, Inc *
* *
* This source code contains the intellectual property *
* of its copyright holder(s), and is made available *
* under a license. If you do not know the terms of *
* the license, please stop and do not read further. *
* *
****************************************************************/
/*
* tcp_open.
*
* parameters:
* host - name like beowulf.sanchez.com or NULL
* port - numeric port number
* timeout - numeric seconds
* passive - boolean 0 is sender and must have a host, 1 may
* have a host and if so, it is checked to see that
* incomming connections are from that host.
* return:
* socket descriptor for the connection or
* -1 and output the error.
*
* Note that on error return errno may not be at the same value as the error condition.
*
*/
#include "mdef.h"
#include <errno.h>
#include "gtm_time.h"
#include "gtm_socket.h"
#include "gtm_inet.h"
#include "gtm_string.h"
#include "gtm_ctype.h"
#include "gtm_stdio.h"
#include "gtm_netdb.h"
#include "copy.h"
#include "gt_timer.h"
#include "io.h"
#include "iotimer.h"
#include "iotcp_select.h"
#include "iotcpdef.h"
#include "iotcproutine.h"
#include "io_params.h"
#include "util.h"
#include "gtmmsg.h"
#define MAX_CONN_PENDING 5
#define NAP_LENGTH 300
#ifdef __osf__
/* Tru64 does not have the prototype for "hstrerror" even though the function is available in the library.
* Until we revamp the TCP communications setup stuff to use the new(er) POSIX definitions, we cannot move
* away from "hstrerror". Declare prototype for this function in Tru64 manually until then.
*/
const char *hstrerror(int err);
#endif
GBLREF tcp_library_struct tcp_routines;
error_def(ERR_INVADDRSPEC);
error_def(ERR_IPADDRREQ);
error_def(ERR_SETSOCKOPTERR);
error_def(ERR_SOCKINIT);
error_def(ERR_SYSCALL);
error_def(ERR_SOCKACPT);
error_def(ERR_SOCKLISTEN);
error_def(ERR_TEXT);
int tcp_open(char *host, unsigned short port, int4 timeout, boolean_t passive) /* host needs to be NULL terminated */
{
boolean_t no_time_left = FALSE, error_given = FALSE;
char temp_addr[SA_MAXLEN + 1], addr[SA_MAXLEN + 1];
char *from, *to, *errptr, *temp_ch, *adptr;
int match, sock, sendbufsize, ii, on = 1, temp_1 = -2;
GTM_SOCKLEN_TYPE size;
int4 rv, msec_timeout;
struct sockaddr_in sin;
in_addr_t temp_sin_addr;
char msg_buffer[1024];
mstr msg_string;
ABS_TIME cur_time, end_time;
fd_set tcp_fd;
struct sockaddr_in peer;
short retry_num;
int save_errno, errlen;
const char *terrptr;
temp_sin_addr = 0;
msg_string.len = SIZEOF(msg_buffer);
msg_string.addr = msg_buffer;
memset((char *)&sin, 0, SIZEOF(struct sockaddr_in));
/* ============================= initialize structures ============================== */
if (NULL != host)
{
temp_ch = host;
while(ISDIGIT_ASCII(*temp_ch) || ('.' == *temp_ch))
temp_ch++;
if ('\0' != *temp_ch)
{
adptr = iotcp_name2ip(host);
if (NULL == adptr)
{
# if !defined(__hpux) && !defined(__MVS__)
terrptr = HSTRERROR(h_errno);
rts_error(VARLSTCNT(6) ERR_INVADDRSPEC, 0, ERR_TEXT, 2, LEN_AND_STR(terrptr));
# else
/* Grumble grumble HPUX and z/OS don't have hstrerror() */
rts_error(VARLSTCNT(1) ERR_INVADDRSPEC);
# endif
return -1;
}
SPRINTF(addr, "%s", adptr);
} else
SPRINTF(addr, "%s", host);
if ((unsigned int)-1 == (temp_sin_addr = tcp_routines.aa_inet_addr(addr)))
{
gtm_putmsg(VARLSTCNT(1) ERR_INVADDRSPEC);
assert(FALSE);
return -1;
}
}
if (passive)
/* We can only listen on our own system */
sin.sin_addr.s_addr = INADDR_ANY;
else
{
if (0 == temp_sin_addr)
{ /* If no address was specified */
gtm_putmsg(VARLSTCNT(1) ERR_IPADDRREQ);
assert(FALSE);
return -1;
}
/* Set where to send the connection attempt */
sin.sin_addr.s_addr = temp_sin_addr;
}
sin.sin_port = GTM_HTONS(port);
sin.sin_family = AF_INET;
/* ============================== do the connection ============================== */
if (passive)
{
struct timeval utimeout, save_utimeout;
int lsock;
lsock = tcp_routines.aa_socket(AF_INET, SOCK_STREAM, 0);
if (-1 == lsock)
{
save_errno = errno;
errptr = (char *)STRERROR(save_errno);
errlen = STRLEN(errptr);
gtm_putmsg(VARLSTCNT(5) ERR_SOCKINIT, 3, save_errno, errlen, errptr);
assert(FALSE);
return -1;
}
/* allow multiple connections to the same IP address */
if (-1 == tcp_routines.aa_setsockopt(lsock, SOL_SOCKET, SO_REUSEADDR, &on, SIZEOF(on)))
{
save_errno = errno;
(void)tcp_routines.aa_close(lsock);
errptr = (char *)STRERROR(save_errno);
errlen = STRLEN(errptr);
gtm_putmsg(VARLSTCNT(7) ERR_SETSOCKOPTERR, 5,
RTS_ERROR_LITERAL("SO_REUSEADDR"), save_errno, errlen, errptr);
assert(FALSE);
return -1;
}
if (-1 == tcp_routines.aa_bind(lsock, (struct sockaddr *)&sin, SIZEOF(struct sockaddr)))
{
save_errno = errno;
(void)tcp_routines.aa_close(lsock);
gtm_putmsg(VARLSTCNT(8) ERR_SYSCALL, 5,
RTS_ERROR_LITERAL("bind()"), CALLFROM, save_errno);
return -1;
}
/* establish a queue of length MAX_CONN_PENDING for incoming connections */
if (-1 == tcp_routines.aa_listen(lsock, MAX_CONN_PENDING))
{
save_errno = errno;
(void)tcp_routines.aa_close(lsock);
errptr = (char *)STRERROR(save_errno);
errlen = STRLEN(errptr);
gtm_putmsg(VARLSTCNT(6) ERR_SOCKLISTEN, 0, ERR_TEXT, 2, errlen, errptr);
assert(FALSE);
return -1;
}
if (NO_M_TIMEOUT != timeout)
{
msec_timeout = timeout2msec(timeout);
sys_get_curr_time(&cur_time);
add_int_to_abs_time(&cur_time, msec_timeout, &end_time);
utimeout.tv_sec = timeout;
utimeout.tv_usec = 0;
}
while(1)
{
while(1)
{
/*
* the check for EINTR below is valid and should not be converted to an EINTR
* wrapper macro, since it might be a timeout.
*/
FD_ZERO(&tcp_fd);
FD_SET(lsock, &tcp_fd);
save_utimeout = utimeout;
rv = tcp_routines.aa_select(lsock + 1, (void *)&tcp_fd, (void *)0, (void *)0,
(NO_M_TIMEOUT == timeout) ? (struct timeval *)0 : &utimeout);
save_errno = errno;
utimeout = save_utimeout;
if ((0 <= rv) || (EINTR != save_errno))
break;
if (NO_M_TIMEOUT != timeout)
{
sys_get_curr_time(&cur_time);
cur_time = sub_abs_time(&end_time, &cur_time);
if (0 >= (utimeout.tv_sec = cur_time.at_sec))
{
rv = 0;
break;
}
}
}
if (0 == rv)
{
(void)tcp_routines.aa_close(lsock);
util_out_print("Listening timed out.\n", TRUE);
assert(FALSE);
return -1;
} else if (0 > rv)
{
(void)tcp_routines.aa_close(lsock);
gtm_putmsg(VARLSTCNT(8) ERR_SYSCALL, 5,
RTS_ERROR_LITERAL("select()"), CALLFROM, save_errno);
assert(FALSE);
return -1;
}
size = SIZEOF(struct sockaddr_in);
sock = tcp_routines.aa_accept(lsock, &peer, &size);
save_errno = errno;
if (-1 == sock)
{
# ifdef __hpux
/* ENOBUFS in HP-UX is either because of a memory problem or when we have received a RST just
* after a SYN before an accept call. Normally this is not fatal and is just a transient state.
* Hence exiting just after a single error of this kind should not be done. So retry in case
* of HP-UX and ENOBUFS error.
*/
if (ENOBUFS == save_errno)
{
retry_num = 0;
while (HPUX_MAX_RETRIES > retry_num)
{ /* In case of succeeding with select in first go, accept will still get 5ms time
* difference.
*/
SHORT_SLEEP(5);
for ( ; HPUX_MAX_RETRIES > retry_num; retry_num++)
{
utimeout.tv_sec = 0;
utimeout.tv_usec = HPUX_SEL_TIMEOUT;
FD_ZERO(&tcp_fd);
FD_SET(lsock, &tcp_fd);
rv = tcp_routines.aa_select(lsock + 1, (void *)&tcp_fd, (void *)0,
(void *)0, &utimeout);
save_errno = errno;
if (0 < rv)
break;
else
SHORT_SLEEP(5);
}
if (0 > rv)
{
(void)tcp_routines.aa_close(lsock);
gtm_putmsg(VARLSTCNT(8) ERR_SYSCALL, 5,
RTS_ERROR_LITERAL("select()"),
CALLFROM, save_errno);
assert(FALSE);
return -1;
}
if (0 == rv)
{
(void)tcp_routines.aa_close(lsock);
util_out_print("Select timed out.\n", TRUE);
assert(FALSE);
return -1;
}
sock = tcp_routines.aa_accept(lsock, &peer, &size);
save_errno = errno;
if ((-1 == sock) && (ENOBUFS == save_errno))
retry_num++;
else
break;
}
}
if (-1 == sock)
# endif
{
(void)tcp_routines.aa_close(lsock);
errptr = (char *)STRERROR(save_errno);
errlen = STRLEN(errptr);
gtm_putmsg(VARLSTCNT(6) ERR_SOCKACPT, 0, ERR_TEXT, 2, errlen, errptr);
assert(FALSE);
return -1;
}
}
SPRINTF(&temp_addr[0], "%s", tcp_routines.aa_inet_ntoa(peer.sin_addr));
# ifdef DEBUG_ONLINE
PRINTF("Connection is from : %s\n", &temp_addr[0]);
# endif
/* Check if connection is from whom we want it to be from. Note that this is not a robust check
(potential for multiple IP addrs for a resolved name but workarounds for this exist so not a lot
of effort has been expended here at this time. Especially since the check is easily spoofed with
raw sockets anyway. It is more for the accidental "oops" type check than serious security..
*/
if ((0 == temp_sin_addr) || (0 == memcmp(&addr[0], &temp_addr[0], strlen(addr))))
break;
else
{ /* Connection not from expected host */
(void)tcp_routines.aa_close(sock);
if (NO_M_TIMEOUT != timeout)
{
sys_get_curr_time(&cur_time);
cur_time = sub_abs_time(&end_time, &cur_time);
utimeout.tv_sec = ((cur_time.at_sec > 0) ? cur_time.at_sec : 0);
}
if (!error_given)
{
util_out_print("Connection from !AD rejected and ignored (expected from !AD)", TRUE,
LEN_AND_STR(&temp_addr[0]), LEN_AND_STR(&addr[0]));
error_given = TRUE;
}
}
}
(void)tcp_routines.aa_close(lsock);
} else
{
if (NO_M_TIMEOUT != timeout)
{
msec_timeout = timeout2msec(timeout);
sys_get_curr_time(&cur_time);
add_int_to_abs_time(&cur_time, msec_timeout, &end_time);
}
no_time_left = FALSE;
temp_1 = 1;
do
{
if (1 != temp_1)
tcp_routines.aa_close(sock);
sock = tcp_routines.aa_socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sock)
{
save_errno = errno;
errptr = (char *)STRERROR(save_errno);
errlen = STRLEN(errptr);
gtm_putmsg(VARLSTCNT(5) ERR_SOCKINIT, 3, save_errno, errlen, errptr);
assert(FALSE);
return -1;
}
/* Allow multiple connections to the same IP address */
if (-1 == tcp_routines.aa_setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, SIZEOF(on)))
{
save_errno = errno;
(void)tcp_routines.aa_close(sock);
errptr = (char *)STRERROR(save_errno);
errlen = STRLEN(errptr);
gtm_putmsg(VARLSTCNT(7) ERR_SETSOCKOPTERR, 5,
RTS_ERROR_LITERAL("SO_REUSEADDR"), save_errno, errlen, errptr);
assert(FALSE);
return -1;
}
temp_1 = tcp_routines.aa_connect(sock, (struct sockaddr *)(&sin), SIZEOF(sin));
save_errno = errno;
/*
* the check for EINTR below is valid and should not be converted to an EINTR
* wrapper macro, because other error conditions are checked, and a retry is not
* immediately performed.
*/
if ((0 > temp_1) && (ECONNREFUSED != save_errno) && (EINTR != save_errno))
{
(void)tcp_routines.aa_close(sock);
gtm_putmsg(VARLSTCNT(8) ERR_SYSCALL, 5,
RTS_ERROR_LITERAL("connect()"), CALLFROM, save_errno);
assert(FALSE);
return -1;
}
if ((0 > temp_1) && (EINTR == save_errno))
{
(void)tcp_routines.aa_close(sock);
util_out_print("Interrupted.", TRUE);
assert(FALSE);
return -1;
}
if ((temp_1 < 0) && (NO_M_TIMEOUT != timeout))
{
sys_get_curr_time(&cur_time);
cur_time = sub_abs_time(&end_time, &cur_time);
if (cur_time.at_sec <= 0)
no_time_left = TRUE;
}
SHORT_SLEEP(NAP_LENGTH); /* Sleep for NAP_LENGTH ms */
} while ((FALSE == no_time_left) && (0 > temp_1));
if (0 > temp_1) /* out of time */
{
tcp_routines.aa_close(sock);
util_out_print("Connection timed out.", TRUE);
assert(FALSE);
return -1;
}
}
return sock;
}