siproxd/src/sock.c
Thomas Ries 1d5a42e061 siproxd bug 3086371
sock.c:727: warning: passing argument 2 of 'connect' from incompatible
pointer type
sock.c:757: warning: pointer targets in passing argument 5 of 'getsockopt'
differ in signedness
2010-10-13 22:46:37 +00:00

831 lines
26 KiB
C

/*
Copyright (C) 2002-2009 Thomas Ries <tries@gmx.net>
This file is part of Siproxd.
Siproxd is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Siproxd is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Siproxd; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "config.h"
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <osipparser2/osip_parser.h>
#include "siproxd.h"
#include "log.h"
static char const ident[]="$Id$";
/* configuration storage */
extern struct siproxd_config configuration;
/* static functions */
static void tcp_expire(void);
static int tcp_add(struct sockaddr_in addr, int fd);
static int tcp_connect(struct sockaddr_in dst_addr);
static int tcp_remove(int idx);
/* module local variables */
/* UDP socket used for SIP datagrams */
int sip_udp_socket=0;
/* TCP listen socket used for SIP */
int sip_tcp_socket=0;
/* TCP sockets used for SIP connections (twice the max number of clients) */
struct {
int fd; /* file descriptor, 0=unused */
struct sockaddr_in dst_addr; /* remote target of TCP connection */
time_t traffic_ts; /* last 'alive' TS (real SIP traffic) */
time_t keepalive_ts; /* last 'alive' TS */
int rxbuf_size;
int rxbuf_len;
char *rx_buffer;
} sip_tcp_cache[2*URLMAP_SIZE];
/*
* binds to SIP UDP and TCP sockets for listening to incoming packets
*
* RETURNS
* STS_SUCCESS on success
* STS_FAILURE on error
*/
int sipsock_listen (void) {
struct in_addr ipaddr;
/* listen on UDP port */
memset(&ipaddr, 0, sizeof(ipaddr));
sip_udp_socket=sockbind(ipaddr, configuration.sip_listen_port,
PROTO_UDP, 1);
if (sip_udp_socket == 0) return STS_FAILURE; /* failure */
/* set DSCP value, need to be ROOT */
if (configuration.sip_dscp) {
int tos;
int uid,euid;
uid=getuid();
euid=geteuid();
DEBUGC(DBCLASS_SIP,"uid=%i, euid=%i", uid, euid);
if (uid != euid) seteuid(0);
if (geteuid()==0) {
/* now I'm root */
if (!(configuration.sip_dscp & ~0x3f)) {
tos = (configuration.sip_dscp << 2) & 0xff;
if(setsockopt(sip_udp_socket, SOL_IP, IP_TOS, &tos, sizeof(tos))) {
ERROR("sipsock_listen: setsockopt() failed while "
"setting DSCP value: %s", strerror(errno));
}
} else {
ERROR("sipsock_listen: Invalid DSCP value %d",
configuration.sip_dscp);
configuration.sip_dscp = 0; /* inhibit further attempts */
}
} else {
/* could not get root */
WARN("siproxd not started as root - cannot set DSCP value");
configuration.rtp_dscp = 0; /* inhibit further attempts */
}
/* drop privileges */
if (uid != euid) seteuid(euid);
}
/* listen on TCP port */
memset(&ipaddr, 0, sizeof(ipaddr));
sip_tcp_socket=sockbind(ipaddr, configuration.sip_listen_port,
PROTO_TCP, 1);
if (sip_tcp_socket == 0) return STS_FAILURE; /* failure */
if (listen(sip_tcp_socket, 10)) {
ERROR("TCP listen() failed: %s", strerror(errno));
return STS_FAILURE;
}
INFO("bound to port %i", configuration.sip_listen_port);
DEBUGC(DBCLASS_NET,"bound UDP socket=%i, TCP socket=%i",
sip_udp_socket, sip_tcp_socket);
/* initialize the TCP connection cache array */
memset(&sip_tcp_cache, 0, sizeof(sip_tcp_cache));
return STS_SUCCESS;
}
/*
* read a message from SIP listen socket (UDP datagram)
*
* RETURNS number of bytes read (=0 if nothing read, <0 timeout)
* from is modified to return the sockaddr_in of the sender
*/
int sipsock_waitfordata(char *buf, size_t bufsize,
struct sockaddr_in *from, int *protocol) {
int i, fd;
fd_set fdset;
int highest_fd, num_fd_active;
static struct timeval timeout={0,0};
int length;
socklen_t fromlen;
DEBUGC(DBCLASS_BABBLE,"entered sipsock_waitfordata");
/* we keep the select() timeout running acrosse multiple calls to
* select(). This avoids missing select() timeouts if the system
* is busy with a lot of SIP traffic, causing NOT doing some
* cyclic tasks. Like this we ensure that every 'N' (N=5) seconds
* sipsock_waitfordata will return a timeout condition.
* Note: there is still the remote possibility that SIP packet
* arrive so fast that select() always return data available -
* but in this case YOU have some seroious other issues...
*/
if ((timeout.tv_sec== 0) && (timeout.tv_usec == 0)) {
DEBUGC(DBCLASS_BABBLE,"winding up select() timeout");
timeout.tv_sec=5;
timeout.tv_usec=0;
}
/* prepare FD set: UDP, TCP listen */
FD_ZERO(&fdset);
FD_SET (sip_udp_socket, &fdset);
FD_SET (sip_tcp_socket, &fdset);
if (sip_udp_socket > sip_tcp_socket) {
highest_fd = sip_udp_socket;
} else {
highest_fd = sip_tcp_socket;
}
/* prepare FD set: TCP connections */
for (i=0; i<(sizeof(sip_tcp_cache)/sizeof(sip_tcp_cache[0])); i++) {
/* active TCP conenction? */
if (sip_tcp_cache[i].fd) {
/* add to FD set */
FD_SET(sip_tcp_cache[i].fd, &fdset);
if (sip_tcp_cache[i].fd > highest_fd) {
highest_fd = sip_tcp_cache[i].fd;
}
} /* if fd > 0 */
}
/* select() on all FD's with timeout */
num_fd_active=select (highest_fd+1, &fdset, NULL, NULL, &timeout);
/* WARN on failures */
if (num_fd_active < 0) {
/* WARN on failure, except if it is an "interrupted system call"
as it will result by SIGINT, SIGTERM */
if (errno != EINTR) {
WARN("select() returned error [%i:%s]",errno, strerror(errno));
} else {
DEBUGC(DBCLASS_NET,"select() returned error [%i:%s]",
errno, strerror(errno));
}
}
/* nothing here = timeout condition */
if (num_fd_active <= 0) {
/* process the active TCP connection list - expire old entries */
tcp_expire();
return -1;
}
for (i=0; i< highest_fd; i++) {
if (FD_ISSET(i, &fdset)) DEBUGC(DBCLASS_BABBLE, "FD %i = active", i);
}
/*
* Some FD's have signalled that data is available (fdset)
* Process them:
* - UDP socket: read data and return
* - TCP listen socket: Accept connection, update TCP cache and return
* - TCP connection socket: read data, update alive timestamp & return
* In case of disconnected socket (recv error [all but EAGAIN, EINTR])
* close connection
*/
/* Strategy to get get data from the FD's:
* 1) check TCP listen socket, if connection pending ACCEPT
* 2) check UDP socket. If data available, process that & return
* 3) check TCP sockets, take first in table with data & return
*/
/*
* Check TCP listen socket
*/
if (FD_ISSET(sip_tcp_socket, &fdset)) {
fromlen=sizeof(struct sockaddr_in);
fd = accept(sip_tcp_socket, (struct sockaddr *)from, &fromlen);
if (fd < 0) {
WARN("accept() returned error [%i:%s]",errno, strerror(errno));
return 0;
}
i=tcp_add(*from, fd);
if (i < 0) {
ERROR("out of space in TCP connection cache - rejecting");
close(fd);
return 0;
}
DEBUGC(DBCLASS_NET, "accepted TCP connection from [%s] fd=%i",
utils_inet_ntoa(from->sin_addr), fd);
num_fd_active--;
if (num_fd_active <=0) return 0;
}
/*
* Check UDP socket
*/
if (FD_ISSET(sip_udp_socket, &fdset)) {
*protocol = PROTO_UDP;
fromlen=sizeof(struct sockaddr_in);
length=recvfrom(sip_udp_socket, buf, bufsize, 0,
(struct sockaddr *)from, &fromlen);
if (length < 0) {
WARN("recvfrom() returned error [%s]",strerror(errno));
length=0;
}
DEBUGC(DBCLASS_NET,"received UDP packet from [%s:%i] count=%i",
utils_inet_ntoa(from->sin_addr), ntohs(from->sin_port), length);
DUMP_BUFFER(DBCLASS_NETTRAF, buf, length);
return length;
}
/*
* Check active TCP sockets
*/
for (i=0; i<(sizeof(sip_tcp_cache)/sizeof(sip_tcp_cache[0])); i++) {
if (sip_tcp_cache[i].fd == 0) continue;
/* no more active FD's to be expected, exit the loop */
if (num_fd_active <= 0) break;
if (FD_ISSET(sip_tcp_cache[i].fd, &fdset)) {
/* found a match */
DEBUGC(DBCLASS_BABBLE,"matched active TCP fd=%i idx=%i",
sip_tcp_cache[i].fd, i);
num_fd_active--;
*protocol = PROTO_TCP;
memcpy(from, &sip_tcp_cache[i].dst_addr, sizeof(struct sockaddr_in));
length = recv(sip_tcp_cache[i].fd, buf, bufsize, 0);
if (length < 0) {
WARN("recv() returned error [%s], disconnecting TCP [%s] fd=%i",
strerror(errno), utils_inet_ntoa(from->sin_addr),
sip_tcp_cache[i].fd);
length=0;
tcp_remove(i);
}
if (length == 0) {
/* length=0 indicates a disconnect from remote side */
DEBUGC(DBCLASS_NET, "received TCP disconnect [%s:%i] fd=%i",
utils_inet_ntoa(from->sin_addr), ntohs(from->sin_port),
sip_tcp_cache[i].fd);
tcp_remove(i);
continue;
}
/* prematurely check for <CR><LF> keepalives, no need to do any
work on them... Set length = 0 and done. */
if (length == 2 && (memcmp(buf, "\x0d\x0a", 2) == 0)) {
DEBUGC(DBCLASS_NET, "got a SIP TCP keepalive from [%s:%i] fd=%i",
utils_inet_ntoa(from->sin_addr), ntohs(from->sin_port),
sip_tcp_cache[i].fd);
return 0;
}
DEBUGC(DBCLASS_NET,"received TCP packet from [%s:%i] count=%i fd=%i",
utils_inet_ntoa(from->sin_addr), ntohs(from->sin_port),
length, sip_tcp_cache[i].fd);
DUMP_BUFFER(DBCLASS_NETTRAF, buf, length);
/* check for <CR><LF> termination of TCP RX buffer */
if ((length > 2) &&
(memcmp(&buf[length-2], "\x0d\x0a", 2) != 0)) {
/* not terminated */
DEBUGC(DBCLASS_NET, "received incomplete fragment, buffering...");
/* append to RX buffer of his connection */
if (sip_tcp_cache[i].rxbuf_len+length < sip_tcp_cache[i].rxbuf_size) {
memcpy(&sip_tcp_cache[i].rx_buffer[sip_tcp_cache[i].rxbuf_len],
buf, length);
sip_tcp_cache[i].rxbuf_len+=length;
} else {
/* out of RX buffer space, discard this SIP frame */
DEBUGC(DBCLASS_NET, "RX buffer too small, discarding this frame");
sip_tcp_cache[i].rxbuf_len=0;
}
return 0;
} else {
/* terminated by <CR><LF> */
if (sip_tcp_cache[i].rxbuf_len != 0) {
/* have already buffered data waiting. Copy new fragment to end
* of RX buffer and then copy all back... */
if (sip_tcp_cache[i].rxbuf_len+length < sip_tcp_cache[i].rxbuf_size) {
DEBUGC(DBCLASS_NET, "received last fragment, assembling...");
memcpy(&sip_tcp_cache[i].rx_buffer[sip_tcp_cache[i].rxbuf_len],
buf, length);
sip_tcp_cache[i].rxbuf_len+=length;
} else {
/* out of RX buffer space, discard this SIP frame */
DEBUGC(DBCLASS_NET, "RX buffer too small, discarding this frame");
sip_tcp_cache[i].rxbuf_len=0;
return 0;
}
/* copy whole RX buffer to the callers buffer */
if (sip_tcp_cache[i].rxbuf_len <= bufsize) {
memcpy (buf, sip_tcp_cache[i].rx_buffer, sip_tcp_cache[i].rxbuf_len);
length = sip_tcp_cache[i].rxbuf_len;
} else {
/* TCP RX buffer bigger than callers buffer... */
DEBUGC(DBCLASS_NET, "buffer passed to sipsock_waitfordata is too small");
sip_tcp_cache[i].rxbuf_len=0;
length =0;
}
}
/* update activity timestamp */
if (length > 0) {
time(&sip_tcp_cache[i].traffic_ts);
sip_tcp_cache[i].keepalive_ts=sip_tcp_cache[i].traffic_ts;
}
return length;
}
} /* FD_ISSET(sip_tcp_cache[i].fd, &fdset */
} /* for i */
/* no data found to be processed */
return 0;
}
/*
* sends an SIP datagram (UDP or TCP) to the specified destination
*
* RETURNS
* STS_SUCCESS on success
* STS_FAILURE on error
*/
int sipsock_send(struct in_addr addr, int port, int protocol,
char *buffer, size_t size) {
struct sockaddr_in dst_addr;
int sts;
int i;
/* first time: allocate a socket for sending */
if (sip_udp_socket == 0) {
ERROR("SIP socket not allocated");
return STS_FAILURE;
}
if (buffer == NULL) {
ERROR("sipsock_send got NULL buffer");
return STS_FAILURE;
}
if (protocol == PROTO_UDP) {
/*
* UDP target
*/
dst_addr.sin_family = AF_INET;
memcpy(&dst_addr.sin_addr, &addr, sizeof(struct in_addr));
dst_addr.sin_port= htons(port);
DEBUGC(DBCLASS_NET,"send UDP packet to %s: %i", utils_inet_ntoa(addr),port);
DUMP_BUFFER(DBCLASS_NETTRAF, buffer, size);
sts = sendto(sip_udp_socket, buffer, size, 0,
(const struct sockaddr *)&dst_addr,
(socklen_t)sizeof(dst_addr));
if (sts == -1) {
if (errno != ECONNREFUSED) {
ERROR("sendto() [%s:%i size=%ld] call failed: %s",
utils_inet_ntoa(addr),
port, (long)size, strerror(errno));
return STS_FAILURE;
}
DEBUGC(DBCLASS_BABBLE,"sendto() [%s:%i] call failed: %s",
utils_inet_ntoa(addr), port, strerror(errno));
}
} else if (protocol == PROTO_TCP) {
/*
* TCP target
*/
dst_addr.sin_family = AF_INET;
memcpy(&dst_addr.sin_addr, &addr, sizeof(struct in_addr));
dst_addr.sin_port= htons(port);
/* check connection cache for an existing TCP connection */
i=tcp_find(dst_addr);
/* if no TCP connection found, do a connect (non blocking) and add to list */
if (i < 0) {
DEBUGC(DBCLASS_NET,"no TCP connection found to %s:%i - connecting",
utils_inet_ntoa(addr), port);
i=tcp_connect(dst_addr);
if (i < 0) {
ERROR("tcp_connect() failed");
return STS_FAILURE;
}
} /* if i */
/* send data and update alive timestamp */
DEBUGC(DBCLASS_NET,"send TCP packet to %s:%i", utils_inet_ntoa(addr), port);
DUMP_BUFFER(DBCLASS_NETTRAF, buffer, size);
time(&sip_tcp_cache[i].traffic_ts);
sip_tcp_cache[i].keepalive_ts=sip_tcp_cache[i].traffic_ts;
sts = send(sip_tcp_cache[i].fd, buffer, size, 0);
if (sts == -1) {
ERROR("send() [%s:%i size=%ld] call failed: %s",
utils_inet_ntoa(addr),
port, (long)size, strerror(errno));
return STS_FAILURE;
}
} else {
/*
* unknown/unsupported protocol
*/
ERROR("sipsock_send: only UDP and TCP supported by now");
return STS_FAILURE;
}
return STS_SUCCESS;
}
/*
* generic routine to allocate and bind a socket to a specified
* local address and port (UDP)
* errflg !=0 log errors, ==0 don't
*
* RETURNS socket number on success, zero on failure
*/
int sockbind(struct in_addr ipaddr, int localport, int protocol, int errflg) {
struct sockaddr_in my_addr;
int sts, on=1;
int sock;
int flags;
memset(&my_addr, 0, sizeof(my_addr));
my_addr.sin_family = AF_INET;
memcpy(&my_addr.sin_addr, &ipaddr, sizeof(struct in_addr));
my_addr.sin_port = htons(localport);
if (protocol == PROTO_UDP) {
sock=socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP);
} else if (protocol == PROTO_TCP) {
sock=socket (PF_INET, SOCK_STREAM, IPPROTO_TCP);
} else {
if (errflg) ERROR("invalig protocol: %i", protocol);
return 0;
}
if (sock < 0) {
ERROR("socket call failed: %s",strerror(errno));
return 0;
}
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on , sizeof(on)) < 0) {
ERROR("setsockopt returned error [%i:%s]",errno, strerror(errno));
return 0;
}
sts=bind(sock, (struct sockaddr *)&my_addr, sizeof(my_addr));
if (sts != 0) {
if (errflg) ERROR("bind failed: %s",strerror(errno));
close(sock);
return 0;
}
/*
* It has been seen on linux 2.2.x systems that for some
* reason (bug?) inside the RTP relay, select()
* claims that a certain file descriptor has data available to
* read, a subsequent call to read() or recv() then does block!!
* So lets make the FD's we are going to use non-blocking, so
* we will at least survive and not run into a deadlock.
*
* There is a way to (more or less) reproduce this effect:
* Make a local UA to local UA call and then very quickly do
* HOLD/unHOLD, several times.
*/
flags = fcntl(sock, F_GETFL);
if (flags < 0) {
ERROR("fcntl(F_SETFL) failed: %s",strerror(errno));
close(sock);
return 0;
}
if (fcntl(sock, F_SETFL, (long) flags | O_NONBLOCK) < 0) {
ERROR("fcntl(F_SETFL) failed: %s",strerror(errno));
close(sock);
return 0;
}
return sock;
}
/*
* age and expire TCP connections
*
* RETURNS: -
*/
static void tcp_expire(void) {
time_t now;
time_t to_limit;
int i;
int sts;
time(&now);
to_limit = now - configuration.tcp_timeout;
for (i=0; i<(sizeof(sip_tcp_cache)/sizeof(sip_tcp_cache[0])); i++) {
if (sip_tcp_cache[i].fd == 0) continue;
if (sip_tcp_cache[i].traffic_ts < to_limit) {
/* TCP has expired, close & cleanup */
DEBUGC(DBCLASS_NET, "TCP inactivity T/O, disconnecting: [%s] fd=%i",
utils_inet_ntoa((&sip_tcp_cache[i].dst_addr)->sin_addr),
sip_tcp_cache[i].fd);
tcp_remove(i);
} else
/* TCP keepalive handling */
if ((sip_tcp_cache[i].keepalive_ts + configuration.tcp_keepalive) <= now) {
DEBUGC(DBCLASS_NET, "sending TCP keepalive [%s:%i] fd=%i idx=%i",
utils_inet_ntoa(sip_tcp_cache[i].dst_addr.sin_addr),
ntohs(sip_tcp_cache[i].dst_addr.sin_port), sip_tcp_cache[i].fd, i);
sip_tcp_cache[i].keepalive_ts = now;
sts = send(sip_tcp_cache[i].fd, "\x0d\x0a", 2, 0);
if (sts == -1) {
WARN("keepalive send() failed: %s", strerror(errno));
}
}
} /* for */
}
/*
* find a TCP connection in cache
*
* RETURNS: index into TCP cache or -1 on not found
*/
int tcp_find(struct sockaddr_in dst_addr) {
int i;
/* check connection cache for an existing TCP connection */
for (i=0; i<(sizeof(sip_tcp_cache)/sizeof(sip_tcp_cache[0])); i++) {
/* occupied entry? */
if (sip_tcp_cache[i].fd == 0) continue;
/* address & port match */
if ((memcmp(&dst_addr.sin_addr, &sip_tcp_cache[i].dst_addr.sin_addr,
sizeof(struct in_addr)) ==0) &&
(dst_addr.sin_port==sip_tcp_cache[i].dst_addr.sin_port)) break;
} /* for */
/* if no TCP connection found return -1 */
if (i >= (sizeof(sip_tcp_cache)/sizeof(sip_tcp_cache[0]))) return -1;
return i;
}
/*
* add a TCP connection into cache
*
* RETURNS: index into TCP cache or -1 on failure (out of space)
*/
static int tcp_add(struct sockaddr_in addr, int fd) {
int i;
/* find free entry in TCP cache */
for (i=0; i<(sizeof(sip_tcp_cache)/sizeof(sip_tcp_cache[0])); i++) {
if (sip_tcp_cache[i].fd == 0) break;
}
if (i >= (sizeof(sip_tcp_cache)/sizeof(sip_tcp_cache[0]))) {
DEBUGC(DBCLASS_NET, "out of space in TCP cache [%s] fd=%i",
utils_inet_ntoa(addr.sin_addr), fd);
return -1;
}
/* store connection data in TCP cache */
sip_tcp_cache[i].fd = fd;
memcpy(&sip_tcp_cache[i].dst_addr, &addr, sizeof(struct sockaddr_in));
time(&sip_tcp_cache[i].traffic_ts);
sip_tcp_cache[i].keepalive_ts=sip_tcp_cache[i].traffic_ts;
/* sanity check: must be unallocated */
if (sip_tcp_cache[i].rx_buffer != NULL) {
WARN("sip_tcp_cache[%i].rx_buffer was not freed! Potential memleak.", i);
free(sip_tcp_cache[i].rx_buffer);
sip_tcp_cache[i].rx_buffer = NULL;
}
/* allocate RX buffer */
sip_tcp_cache[i].rx_buffer=malloc(BUFFER_SIZE);
if (sip_tcp_cache[i].rx_buffer == NULL) {
DEBUGC(DBCLASS_NET, "malloc() of %i bytes failed", BUFFER_SIZE);
return -1;
}
sip_tcp_cache[i].rxbuf_size=BUFFER_SIZE;
sip_tcp_cache[i].rxbuf_len=0;
DEBUGC(DBCLASS_NET, "added TCP connection [%s] fd=%i to cache idx=%i",
utils_inet_ntoa(addr.sin_addr), fd, i);
return i;
}
/*
* connect to a remote TCP target
*
* RETURNS: index into TCP cache or -1 on failure
*/
static int tcp_connect(struct sockaddr_in dst_addr) {
int sock;
int flags;
int sts;
int i;
struct timeval timeout={0,0};
fd_set fdset;
/* get socket and connect to remote site */
sock=socket (PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock < 0) {
ERROR("socket() call failed: %s",strerror(errno));
return -1;
}
/* non blocking */
flags = fcntl(sock, F_GETFL);
if (flags < 0) {
ERROR("fcntl(F_SETFL) failed: %s",strerror(errno));
close(sock);
return -1;
}
if (fcntl(sock, F_SETFL, (long) flags | O_NONBLOCK) < 0) {
ERROR("fcntl(F_SETFL) failed: %s",strerror(errno));
close(sock);
return -1;
}
sts=connect(sock, (struct sockaddr *)&dst_addr, sizeof(struct sockaddr_in));
if ((sts == -1 ) && (errno == EINPROGRESS)) {
/* if non-blocking connect(), wait until connection
successful, discarded or timeout */
DEBUGC(DBCLASS_NET, "connection in progress, waiting %i msec to succeed",
configuration.tcp_connect_timeout);
/* timeout for connect */
timeout.tv_sec = (configuration.tcp_connect_timeout/1000);
timeout.tv_usec = (configuration.tcp_connect_timeout%1000)*1000;
do {
/* prepare fd set */
FD_ZERO(&fdset);
FD_SET(sock, &fdset);
sts = select(sock+1, NULL, &fdset, NULL, &timeout);
if ((sts < 0) && (errno == EINTR)) {
/* select() has been interrupted, do it again */
continue;
} else if (sts < 0) {
ERROR("waiting for TCP connect failed: %s",strerror(errno));
close(sock);
return -1;
} else if (sts > 0) {
/* fd available for write */
int valopt;
socklen_t optlen=sizeof(valopt);
/* get error status from delayed connect() */
if (getsockopt(sock, SOL_SOCKET, SO_ERROR,
&valopt, &optlen) < 0) {
ERROR("getsockopt(SO_ERROR) failed: %s",strerror(errno));
close(sock);
return -1;
}
if (valopt == EINPROGRESS) {
ERROR("connect() returned: %s",strerror(valopt));
continue;
}
DEBUGC(DBCLASS_NET, "connect() completed after %i msec",
(int)(configuration.tcp_connect_timeout
- (timeout.tv_sec*1000)
- (timeout.tv_usec/1000)));
/* check the returned error value from connect() */
if (valopt) {
ERROR("delayed TCP connect() failed : %s",strerror(errno));
close(sock);
return -1;
}
/* all went fine, continue */
break;
} else {
DEBUGC(DBCLASS_NET, "tcp_connect() timeout");
close(sock);
return -1;
}
} while (1);
} else if (sts == -1 ) {
if ((errno != ECONNREFUSED) && (errno != ETIMEDOUT)) {
ERROR("connect() [%s:%i] call failed: %s",
utils_inet_ntoa(dst_addr.sin_addr),
ntohs(dst_addr.sin_port), strerror(errno));
close(sock);
return -1;
}
DEBUGC(DBCLASS_BABBLE,"connect() [%s:%i] call failed: %s",
utils_inet_ntoa(dst_addr.sin_addr),
ntohs(dst_addr.sin_port), strerror(errno));
}
i=tcp_add(dst_addr, sock);
if (i < 0) {
ERROR("out of space in TCP connection cache - rejecting");
close(sock);
return -1;
}
DEBUGC(DBCLASS_NET, "connected TCP connection to [%s:%i] fd=%i",
utils_inet_ntoa(dst_addr.sin_addr),
ntohs(dst_addr.sin_port), sock);
return i;
}
/*
* clean up resources occupied by a TCP entry
*
* RETURNS: 0
*/
static int tcp_remove(int idx) {
close(sip_tcp_cache[idx].fd);
sip_tcp_cache[idx].fd=0;
free(sip_tcp_cache[idx].rx_buffer);
sip_tcp_cache[idx].rx_buffer=NULL;
sip_tcp_cache[idx].rxbuf_size=0;
sip_tcp_cache[idx].rxbuf_len=0;
return 0;
}