- TCP support

This commit is contained in:
Thomas Ries
2010-01-07 11:38:52 +00:00
parent 0d714ffd94
commit 137d82452d
5 changed files with 763 additions and 123 deletions

View File

@@ -95,6 +95,16 @@ int proxy_request (sip_ticket_t *ticket) {
request=ticket->sipmsg;
/*
* RFC&&&&
* add a received= parameter to the topmost Via header. Used for TCP
* connections - send answer within the existing TCP connection back
* to client.
*/
if (ticket->protocol == PROTO_TCP) {
sip_add_received_param(ticket);
}
/*
* RFC 3261, Section 16.4
* Proxy Behavior - Route Information Preprocessing
@@ -337,29 +347,6 @@ sts=sip_obscure_callid(ticket);
* 2) fixed outbound proxy
* 3) SIP URI
*/
/*
* fixed or domain outbound proxy defined ?
*/
// let's try with Route header first
#if 0
if ((type == REQTYP_OUTGOING) &&
(sip_find_outbound_proxy(ticket, &sendto_addr, &port) == STS_SUCCESS)) {
DEBUGC(DBCLASS_PROXY, "proxy_request: have outbound proxy %s:%i",
utils_inet_ntoa(sendto_addr), port);
/*
* Route present?
* If so, fetch address from topmost Route: header and remove it.
*/
} else if ((type == REQTYP_OUTGOING) &&
(!osip_list_eol(&(request->routes), 0))) {
sts=route_determine_nexthop(ticket, &sendto_addr, &port);
if (sts == STS_FAILURE) {
DEBUGC(DBCLASS_PROXY, "proxy_request: route_determine_nexthop failed");
return STS_FAILURE;
}
DEBUGC(DBCLASS_PROXY, "proxy_request: have Route header to %s:%i",
utils_inet_ntoa(sendto_addr), port);
#else
/*
* Route present?
* If so, fetch address from topmost Route: header and remove it.
@@ -373,11 +360,13 @@ sts=sip_obscure_callid(ticket);
}
DEBUGC(DBCLASS_PROXY, "proxy_request: have Route header to %s:%i",
utils_inet_ntoa(sendto_addr), port);
/*
* fixed or domain outbound proxy defined ?
*/
} else if ((type == REQTYP_OUTGOING) &&
(sip_find_outbound_proxy(ticket, &sendto_addr, &port) == STS_SUCCESS)) {
DEBUGC(DBCLASS_PROXY, "proxy_request: have outbound proxy %s:%i",
utils_inet_ntoa(sendto_addr), port);
#endif
/*
* destination from SIP URI
*/
@@ -670,18 +659,17 @@ sts=sip_obscure_callid(ticket);
* Determine Next-Hop Address
*/
/*&&&& priority probably should be:
* 0) rport=;received= header (TCP only for now)
* 1) Route header
* 2) fixed outbound proxy
* 3) Via header
*/
/*
* check if we need to send to an outbound proxy
* IF TCP, check for rport=x;received=y parameters in VIA
*/
// let's try with Route header first
#if 0
if ((type == RESTYP_OUTGOING) &&
(sip_find_outbound_proxy(ticket, &sendto_addr, &port) == STS_SUCCESS)) {
DEBUGC(DBCLASS_PROXY, "proxy_response: have outbound proxy %s:%i",
if ((ticket->protocol == PROTO_TCP) &&
(sip_get_received_param(ticket, &sendto_addr, &port) == STS_SUCCESS)) {
DEBUGC(DBCLASS_PROXY, "proxy_response: have received/rport to %s:%i",
utils_inet_ntoa(sendto_addr), port);
/*
* Route present?
@@ -696,25 +684,13 @@ sts=sip_obscure_callid(ticket);
}
DEBUGC(DBCLASS_PROXY, "proxy_response: have Route header to %s:%i",
utils_inet_ntoa(sendto_addr), port);
#else
/*
* Route present?
* If so, fetch address from topmost Route: header and remove it.
* check if we need to send to an outbound proxy
*/
if ((type == RESTYP_OUTGOING) &&
(!osip_list_eol(&(response->routes), 0))) {
sts=route_determine_nexthop(ticket, &sendto_addr, &port);
if (sts == STS_FAILURE) {
DEBUGC(DBCLASS_PROXY, "proxy_response: route_determine_nexthop failed");
return STS_FAILURE;
}
DEBUGC(DBCLASS_PROXY, "proxy_response: have Route header to %s:%i",
utils_inet_ntoa(sendto_addr), port);
} else if ((type == RESTYP_OUTGOING) &&
(sip_find_outbound_proxy(ticket, &sendto_addr, &port) == STS_SUCCESS)) {
DEBUGC(DBCLASS_PROXY, "proxy_response: have outbound proxy %s:%i",
utils_inet_ntoa(sendto_addr), port);
#endif
} else {
/* get target address and port from VIA header */
via = (osip_via_t *) osip_list_get (&(response->vias), 0);

View File

@@ -661,10 +661,10 @@ int rtp_relay_start_fwd (osip_call_id_t *callid, client_id_t client_id,
/* port is available, try to allocate */
if (j == RTPPROXY_SIZE) {
port=i;
sock=sockbind(local_ipaddr, port, 0); /* RTP */
sock=sockbind(local_ipaddr, port, PROTO_UDP, 0); /* RTP */
if (sock) {
sock_con=sockbind(local_ipaddr, port+1, 0); /* RTCP */
sock_con=sockbind(local_ipaddr, port+1, PROTO_UDP, 0); /* RTCP */
/* if success break, else try further on */
if (sock_con) break;
sts = close(sock);
@@ -908,6 +908,9 @@ int rtp_relay_stop_fwd (osip_call_id_t *callid,
rtp_proxytable[i].remote_ipaddr,
rtp_proxytable[i].remote_port + 1);
/* clean up */
if (rtp_proxytable[i].opposite_entry) {
rtp_proxytable[rtp_proxytable[i].opposite_entry-1].opposite_entry=0;
}
memset(&rtp_proxytable[i], 0, sizeof(rtp_proxytable[0]));
got_match=1;
}
@@ -1053,6 +1056,9 @@ static int match_socket (int rtp_proxytable_idx) {
strcpy(remip2, utils_inet_ntoa(rtp_proxytable[rtp_proxytable_idx].remote_ipaddr));
strcpy(lclip2, utils_inet_ntoa(rtp_proxytable[rtp_proxytable_idx].local_ipaddr));
rtp_proxytable[rtp_proxytable_idx].opposite_entry=j+1;
rtp_proxytable[j].opposite_entry=rtp_proxytable_idx+1;
DEBUGC(DBCLASS_RTP, "connected entry %i (fd=%i, %s:%i->%s:%i) <-> entry %i (fd=%i, %s:%i->%s:%i)",
j, rtp_proxytable[j].rtp_rx_sock,
lclip1, rtp_proxytable[j].local_port,

View File

@@ -578,10 +578,17 @@ int sip_add_myvia (sip_ticket_t *ticket, int interface) {
sts = sip_calculate_branch_id(ticket, branch_id);
myaddr=utils_inet_ntoa(addr);
sprintf(tmp, "SIP/2.0/UDP %s:%i;branch=%s%s",
myaddr, configuration.sip_listen_port,
branch_id,
(add_rport)? ";rport":"");
if (ticket->protocol == PROTO_UDP) {
sprintf(tmp, "SIP/2.0/UDP %s:%i;branch=%s%s",
myaddr, configuration.sip_listen_port,
branch_id,
(add_rport)? ";rport":"");
} else {
sprintf(tmp, "SIP/2.0/TCP %s:%i;branch=%s%s",
myaddr, configuration.sip_listen_port,
branch_id,
(add_rport)? ";rport":"");
}
DEBUGC(DBCLASS_BABBLE,"adding VIA:%s",tmp);
@@ -697,6 +704,11 @@ int sip_rewrite_contact (sip_ticket_t *ticket, int direction) {
osip_uri_clone(urlmap[i].true_url, &contact->url);
}
/* add transport=tcp parameter if TCP */
if (ticket->protocol == PROTO_TCP) {
osip_uri_set_transport_tcp(contact->url);
}
osip_list_add(&(sip_msg->contacts),contact,j);
replaced=1;
}
@@ -1144,7 +1156,7 @@ int sip_find_direction(sip_ticket_t *ticket, int *urlidx) {
* Also, my own outbound address is considered to be redirected traffic
* Example Scenario:
* Softphone(or PBX) running on the same host as siproxd is running.
* Using iptables, you do a REDIRECT of outgoping SIP traffix of the
* Using iptables, you do a REDIRECT of outgoing SIP traffic of the
* PBX to be passed to siproxd.
*/
if (type == DIRTYP_UNKNOWN) {
@@ -1376,3 +1388,83 @@ DEBUGC(DBCLASS_PROXY, "tmp+myidentlen=[%s], FromTag=[%s]",
}
/*
* SIP_ADD_RECEIVED_PARAM
*
* Add a received parameter to the topmost VIA header (IP and port)
*
* RETURNS
* STS_SUCCESS on success
*/
int sip_add_received_param(sip_ticket_t *ticket){
osip_via_t *via;
char tmp[6];
DEBUGC(DBCLASS_PROXY,"adding received= param to topmost via");
via = osip_list_get (&(ticket->sipmsg->vias), 0);
/* set rport=xxx;received=1.2.3.4 */
snprintf(tmp, sizeof(tmp), "%i", ntohs(ticket->from.sin_port));
osip_via_param_add(via,osip_strdup("rport"),osip_strdup(tmp));
osip_via_param_add(via,osip_strdup("received"),
osip_strdup(utils_inet_ntoa(ticket->from.sin_addr)));
return STS_SUCCESS;
}
/*
* SIP_GET_RECEIVED_PARAM
*
* Get a received parameter from the topmost VIA header (IP and port)
*
* RETURNS
* STS_SUCCESS on success
*/
int sip_get_received_param(sip_ticket_t *ticket,
struct in_addr *dest, int *port) {
osip_via_t *via;
osip_generic_param_t *received=NULL;
osip_generic_param_t *rport=NULL;
int sts;
DEBUGC(DBCLASS_PROXY,"searching received= param in topmost via");
via = osip_list_get (&(ticket->sipmsg->vias), 0);
osip_via_param_get_byname (via, "received", &received);
osip_via_param_get_byname (via, "rport", &rport);
if (received && rport && received->gvalue && rport->gvalue) {
/* fetch the IP */
sts = get_ip_by_host(received->gvalue, dest);
if (sts != STS_SUCCESS) return STS_FAILURE;
/* fetch the port number */
*port = atoi(rport->gvalue);
if ((*port <=0) || (*port >=65536)) return STS_FAILURE;
/* If TCP, then validate first if an existing connection is in the cache.
* If not, do not use this - a new conection must be established! */
if (ticket->protocol == PROTO_TCP) {
struct sockaddr_in addr;
addr.sin_family = AF_INET;
memcpy(&addr.sin_addr, dest, sizeof(struct in_addr));
addr.sin_port= htons(*port);
sts = tcp_find(addr);
if (sts < 0) {
DEBUGC(DBCLASS_BABBLE, "IP: %s, port: %i not found in cache",
utils_inet_ntoa(*dest), *port);
return STS_FAILURE;
}
}
/* found, return */
DEBUGC(DBCLASS_BABBLE, "IP:%s, port:%i is ok to be reused",
utils_inet_ntoa(*dest), *port);
return STS_SUCCESS;
}
/* not found */
return STS_FAILURE;
}

View File

@@ -100,21 +100,31 @@ struct siproxd_config {
char *plugin_dir;
stringa_t load_plugin;
int sip_dscp;
int tcp_timeout;
int tcp_connect_timeout;
int tcp_keepalive;
};
/*
* control structure for config file parser
*/
typedef struct {
int int4;
char *string;
} defval_t;
typedef struct {
char *keyword;
enum type {TYP_INT4, TYP_STRING, TYP_FLOAT, TYP_STRINGA} type;
void *dest;
defval_t defval;
} cfgopts_t;
/*
* SIP ticket
*/
typedef struct {
char *raw_buffer; /* raw UDP packet */
int raw_buffer_len; /* length of raw data */
osip_message_t *sipmsg; /* SIP */
struct sockaddr_in from; /* received from */
#define PROTO_UNKN -1
@@ -151,12 +161,13 @@ typedef struct {
/* sock.c */
int sipsock_listen(void); /*X*/
int sipsock_wait(void);
int sipsock_read(void *buf, size_t bufsize,
struct sockaddr_in *from, int *protocol);
//int sipsock_wait(void);
int sipsock_waitfordata(char *buf, size_t bufsize,
struct sockaddr_in *from, int *protocol);
int sipsock_send(struct in_addr addr, int port, int protocol, /*X*/
char *buffer, size_t size);
int sockbind(struct in_addr ipaddr, int localport, int errflg);
int sockbind(struct in_addr ipaddr, int localport, int protocol, int errflg);
int tcp_find(struct sockaddr_in dst_addr);
/* register.c */
void register_init(void);
@@ -173,7 +184,7 @@ int proxy_rewrite_invitation_body(sip_ticket_t *ticket, int direction); /*X*/
int proxy_rewrite_request_uri(osip_message_t *mymsg, int idx); /*X*/
int proxy_rewrite_useragent(sip_ticket_t *ticket); /*X*/
/* route_preprocessing.c */
/* route_processing.c */
int route_preprocess(sip_ticket_t *ticket); /*X*/
int route_add_recordroute(sip_ticket_t *ticket); /*X*/
int route_purge_recordroute(sip_ticket_t *ticket); /*X*/
@@ -209,10 +220,12 @@ int sip_find_outbound_proxy(sip_ticket_t *ticket, struct in_addr *addr,
int sip_find_direction(sip_ticket_t *ticket, int *urlidx); /*X*/
int sip_fixup_asterisk(char *buff, size_t *buflen); /*X*/
int sip_obscure_callid(sip_ticket_t *ticket); /*X*/
int sip_add_received_param(sip_ticket_t *ticket); /*X*/
int sip_get_received_param(sip_ticket_t *ticket,
struct in_addr *dest, int *port); /*X*/
/* readconf.c */
int read_config(char *name, int search, cfgopts_t cfgopts[], char *filter); /*X*/
int make_default_config(void); /*X*/
/* rtpproxy.c */
int rtpproxy_init( void ); /*X*/
@@ -262,12 +275,14 @@ int unload_plugins(void);
#define DEFAULT_MAXFWD 70 /* default Max-Forward count */
#define DEFAULT_EXPIRES 3600 /* default Expires timeout */
#define TCP_IDLE_TO 300 /* TCP connection idle timeout in seconds */
#define TCP_CONNECT_TO 500 /* TCP connect() timeout in msec */
#define URLMAP_SIZE 128 /* number of URL mapping table entries */
/* this limits the number of clients! */
#define SOURCECACHE_SIZE 256 /* number of return addresses */
#define DEJITTERLIMIT 1500000 /* max value for dejitter configuration */
#define DEFAULT_DEJITTER 100000 /* default value for dejitter configuration */
#define RTPPROXY_SIZE 256 /* number of rtp proxy entries */
/* this limits the number of calls! */
@@ -302,7 +317,7 @@ int unload_plugins(void);
/* constants for security testing */
#define SEC_MINLEN 16 /* minimum received length */
#define SEC_MAXLINELEN 1024 /* maximum acceptable length of one line
#define SEC_MAXLINELEN 2048 /* maximum acceptable length of one line
in the SIP telegram (security check)
Careful: Proxy-Authorization lines may
get quite long */

View File

@@ -1,5 +1,5 @@
/*
Copyright (C) 2002-2008 Thomas Ries <tries@gmx.net>
Copyright (C) 2002-2009 Thomas Ries <tries@gmx.net>
This file is part of Siproxd.
@@ -27,6 +27,7 @@
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
@@ -44,12 +45,35 @@ static char const ident[]="$Id$";
/* configuration storage */
extern struct siproxd_config configuration;
/* socket used for sending SIP datagrams */
/* 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 socket for listening to incoming packets
* binds to SIP UDP and TCP sockets for listening to incoming packets
*
* RETURNS
* STS_SUCCESS on success
@@ -58,9 +82,11 @@ int sip_udp_socket=0;
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, 1);
if (sip_udp_socket == 0) return STS_FAILURE; /* failure*/
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) {
@@ -92,75 +118,289 @@ int sipsock_listen (void) {
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 socket %i",sip_udp_socket);
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;
}
/*
* Wait for incoming SIP message. After a 2 sec timeout
* this function returns with sts=0
* read a message from SIP listen socket (UDP datagram)
*
* RETURNS >0 if data received, =0 if nothing received (T/O), -1 on error
* RETURNS number of bytes read (=0 if nothing read, <0 timeout)
* from is modified to return the sockaddr_in of the sender
*/
int sipsock_wait(void) {
int sts;
int sipsock_waitfordata(char *buf, size_t bufsize,
struct sockaddr_in *from, int *protocol) {
int i, fd;
fd_set fdset;
struct timeval timeout;
int highest_fd, num_fd_active;
static struct timeval timeout={0,0};
int length;
socklen_t fromlen;
timeout.tv_sec=2;
timeout.tv_usec=0;
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);
sts=select (sip_udp_socket+1, &fdset, NULL, NULL, &timeout);
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 (sts<0) {
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 != 4) {
if (errno != EINTR) {
WARN("select() returned error [%i:%s]",errno, strerror(errno));
} else {
DEBUGC(DBCLASS_NET,"select() returned error [%i:%s]",
errno, strerror(errno));
}
}
return sts;
}
/*
* read a message from SIP listen socket (UDP datagram)
*
* RETURNS number of bytes read
* from is modified to return the sockaddr_in of the sender
*/
int sipsock_read(void *buf, size_t bufsize,
struct sockaddr_in *from, int *protocol) {
int count;
socklen_t fromlen;
fromlen=sizeof(struct sockaddr_in);
*protocol = PROTO_UDP; /* up to now, only UDP */
count=recvfrom(sip_udp_socket, buf, bufsize, 0,
(struct sockaddr *)from, &fromlen);
if (count<0) {
WARN("recvfrom() returned error [%s]",strerror(errno));
*protocol = PROTO_UNKN;
/* nothing here = timeout condition */
if (num_fd_active <= 0) {
/* process the active TCP connection list - expire old entries */
tcp_expire();
return -1;
}
DEBUGC(DBCLASS_NET,"received UDP packet from %s, count=%i",
utils_inet_ntoa(from->sin_addr), count);
DUMP_BUFFER(DBCLASS_NETTRAF, buf, count);
return count;
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 UDP datagram to the specified destination
* sends an SIP datagram (UDP or TCP) to the specified destination
*
* RETURNS
* STS_SUCCESS on success
@@ -170,6 +410,7 @@ 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) {
@@ -182,31 +423,77 @@ int sipsock_send(struct in_addr addr, int port, int protocol,
return STS_FAILURE;
}
if (protocol != PROTO_UDP) {
ERROR("sipsock_send: only UDP supported by now");
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);
dst_addr.sin_family = AF_INET;
memcpy(&dst_addr.sin_addr.s_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);
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));
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",
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;
}
DEBUGC(DBCLASS_BABBLE,"sendto() [%s:%i] call failed: %s",
utils_inet_ntoa(addr), port, strerror(errno));
} else {
/*
* unknown/unsupported protocol
*/
ERROR("sipsock_send: only UDP and TCP supported by now");
return STS_FAILURE;
}
return STS_SUCCESS;
@@ -221,7 +508,7 @@ int sipsock_send(struct in_addr addr, int port, int protocol,
*
* RETURNS socket number on success, zero on failure
*/
int sockbind(struct in_addr ipaddr, int localport, int errflg) {
int sockbind(struct in_addr ipaddr, int localport, int protocol, int errflg) {
struct sockaddr_in my_addr;
int sts, on=1;
int sock;
@@ -230,10 +517,17 @@ int sockbind(struct in_addr ipaddr, int localport, int errflg) {
memset(&my_addr, 0, sizeof(my_addr));
my_addr.sin_family = AF_INET;
memcpy(&my_addr.sin_addr.s_addr, &ipaddr, sizeof(struct in_addr));
memcpy(&my_addr.sin_addr, &ipaddr, sizeof(struct in_addr));
my_addr.sin_port = htons(localport);
sock=socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP);
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;
@@ -277,3 +571,260 @@ int sockbind(struct in_addr ipaddr, int localport, int errflg) {
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, &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;
int optlen=sizeof(valopt);
/* get error status from delayed connect() */
if (getsockopt(sock, SOL_SOCKET, SO_ERROR,
(void*)(&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;
}