You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kamailio/modules/cdp/receiver.c

1170 lines
32 KiB

/*
* $Id$
*
* Copyright (C) 2012 Smile Communications, jason.penton@smilecoms.com
* Copyright (C) 2012 Smile Communications, richard.good@smilecoms.com
*
* The initial version of this code was written by Dragos Vingarzan
* (dragos(dot)vingarzan(at)fokus(dot)fraunhofer(dot)de and the
* Fruanhofer Institute. It was and still is maintained in a separate
* branch of the original SER. We are therefore migrating it to
* Kamailio/SR and look forward to maintaining it from here on out.
* 2011/2012 Smile Communications, Pty. Ltd.
* ported/maintained/improved by
* Jason Penton (jason(dot)penton(at)smilecoms.com and
* Richard Good (richard(dot)good(at)smilecoms.com) as part of an
* effort to add full IMS support to Kamailio/SR using a new and
* improved architecture
*
* NB: Alot of this code was originally part of OpenIMSCore,
* FhG Fokus.
* Copyright (C) 2004-2006 FhG Fokus
* Thanks for great work! This is an effort to
* break apart the various CSCF functions into logically separate
* components. We hope this will drive wider use. We also feel
* that in this way the architecture is more complete and thereby easier
* to manage in the Kamailio/SR environment
*
* This file is part of Kamailio, a free SIP server.
*
* Kamailio 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
*
* Kamailio 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "utils.h"
#include "globals.h"
#include "diameter_api.h"
#include "peerstatemachine.h"
#include "peermanager.h"
#include "config.h"
#include "receiver.h"
#include "../../cfg/cfg_struct.h"
extern dp_config *config; /**< Configuration for this diameter peer */
int dp_add_pid(pid_t pid);
void dp_del_pid(pid_t pid);
int receive_loop(peer *original_peer);
void receive_message(AAAMessage *msg,serviced_peer_t *sp);
/** prefix for the send FIFO pipes */
#define PIPE_PREFIX "/tmp/cdp_send_"
int local_id=0; /**< incrementing process local variable, to distinguish between different peer send pies */
int fd_exchange_pipe_unknown_local; /**< pipe to pass file descriptors towards the receiver process for unknown peers - local end to read from*/
int fd_exchange_pipe_unknown; /**< pipe to pass file descriptors towards the receiver process for unknown peers */
serviced_peer_t *serviced_peers=0; /**< pointer to the list of peers serviced by this process */
/**
* Print debug information about this receiver process
* @param level
*/
static void log_serviced_peers()
{
serviced_peer_t *sp;
LM_DBG("--- Receiver ["ANSI_BLUE"%s"ANSI_GREEN"] Serviced Peers: ---\n",
pt[process_no].desc
);
for(sp=serviced_peers;sp;sp=sp->next){
LM_DBG(ANSI_GREEN" Peer: ["ANSI_YELLOW"%.*s"ANSI_GREEN"] TCP Socket: ["ANSI_YELLOW"%d"ANSI_GREEN"] Recv.State: ["ANSI_YELLOW"%d"ANSI_GREEN"]\n",
sp->p?sp->p->fqdn.len:0,
sp->p?sp->p->fqdn.s:0,
sp->tcp_socket,
sp->state);
}
LM_DBG("--------------------------------------------------------\n");
}
/**
* Makes a send pipe for signaling messages to send-out and attaches it to where necessary.
* @params sp - the serviced peer to open it for
* @return 1 on success or 0 on failure
*/
static int make_send_pipe(serviced_peer_t *sp)
{
local_id++;
sp->send_pipe_name.s = shm_malloc(sizeof(PIPE_PREFIX)+64);
sprintf(sp->send_pipe_name.s,"%s%d_%d_%d",PIPE_PREFIX,getpid(),local_id,(unsigned int) time(0));
sp->send_pipe_name.len = strlen(sp->send_pipe_name.s);
if (mkfifo(sp->send_pipe_name.s, 0666)<0){
LM_ERR("make_send_pipe(): FIFO make failed > %s\n",strerror(errno));
return 0;
}
sp->send_pipe_fd = open(sp->send_pipe_name.s, O_RDONLY | O_NDELAY);
if (sp->send_pipe_fd<0){
LM_ERR("receiver_init(): FIFO open for read failed > %s\n",strerror(errno));
return 0;
}
// we open it for writing just to keep it alive - won't close when all other writers close it
sp->send_pipe_fd_out = open(sp->send_pipe_name.s, O_WRONLY);
if (sp->send_pipe_fd_out<0){
LM_ERR("receiver_init(): FIFO open for write (keep-alive) failed > %s\n",strerror(errno));
return 0;
}
if (sp->p)
sp->p->send_pipe_name=sp->send_pipe_name;
return 1;
}
/**
* Close a send pipe for signaling messages to send-out
* @param sp - the serviced peer to close it for
*/
static void close_send_pipe(serviced_peer_t *sp)
{
if (sp->send_pipe_name.s) {
close(sp->send_pipe_fd);
close(sp->send_pipe_fd_out);
remove(sp->send_pipe_name.s);
shm_free(sp->send_pipe_name.s);
sp->send_pipe_name.s=0;
sp->send_pipe_name.len=0;
sp->send_pipe_fd = -1;
sp->send_pipe_fd_out = -1;
}
}
/**
* Adds a peer in the list of serviced peers by this receiver process.
* \note This should only be called from the receiver process!!!
* @param p - the peer to add
* @returns 1 on success or 0 on error
*/
static serviced_peer_t* add_serviced_peer(peer *p)
{
serviced_peer_t *sp;
LM_INFO("add_serviced_peer(): Adding serviced_peer_t to receiver for peer [%.*s]\n",
p?p->fqdn.len:0,
p?p->fqdn.s:0);
sp = pkg_malloc(sizeof(serviced_peer_t));
if (!sp){
LM_INFO("add_serviced_peer(): error allocating pkg mem\n");
return 0;
}
memset(sp,0,sizeof(serviced_peer_t));
sp->p = p;
sp->tcp_socket = -1;
sp->prev = 0;
if (serviced_peers) {
serviced_peers->prev = sp;
sp->next = serviced_peers;
}
serviced_peers = sp;
if (!make_send_pipe(sp)){
pkg_free(sp);
return 0;
}
return sp;
}
/**
* Disconnects a serviced peer, but does not delete it from this receiver's list.
* Used from dedicated receivers, when the peers should be kept associated even if disconnected.
* @param sp - the serviced peer to operate on
* @param locked - if the sp->p has been previously locked
*/
static void disconnect_serviced_peer(serviced_peer_t *sp,int locked)
{
if (!sp) return;
LM_INFO("drop_serviced_peer(): [%.*s] Disconnecting from peer \n",
sp->p?sp->p->fqdn.len:0,
sp->p?sp->p->fqdn.s:0);
if (sp->p){
if (!locked) lock_get(sp->p->lock);
if (sp->p->I_sock == sp->tcp_socket) sm_process(sp->p,I_Peer_Disc,0,1,sp->tcp_socket);
if (sp->p->R_sock == sp->tcp_socket) sm_process(sp->p,R_Peer_Disc,0,1,sp->tcp_socket);
sp->p->send_pipe_name.s = 0;
sp->p->send_pipe_name.len = 0;
if (!locked) lock_release(sp->p->lock);
}
sp->tcp_socket = -1;
close_send_pipe(sp);
}
/**
* Drops a peer from the list of serviced peers by this receiver process
* \note This does not actually disconnect, but should be used for hand-overs of peers from one receiver to other
* \note This should only be called from the receiver process!!!
* @param p - the peer to drop
*/
static void drop_serviced_peer(serviced_peer_t *sp,int locked)
{
if (!sp) return;
LM_INFO("drop_serviced_peer(): Dropping serviced_peer_t from receiver for peer [%.*s]\n",
sp->p?sp->p->fqdn.len:0,
sp->p?sp->p->fqdn.s:0);
sp->p=0;
close_send_pipe(sp);
if (sp->next) sp->next->prev = sp->prev;
if (sp->prev) sp->prev->next = sp->next;
else serviced_peers = sp->next;
if (sp->msg) shm_free(sp->msg);
sp->msg = 0;
pkg_free(sp);
}
/**
* Sends a file descriptor to another process through a pipe, together with a pointer to the peer that it is
* associated with.
* @param pipe_fd - pipe to send through
* @param fd - descriptor to send
* @param p - peer associated with the descriptor or null if unknown peer
* @returns 1 on success, 0 on failure
*/
static int send_fd(int pipe_fd,int fd, peer *p)
{
struct msghdr msg;
struct iovec iov[1];
int ret;
#ifdef HAVE_MSGHDR_MSG_CONTROL
struct cmsghdr* cmsg;
/* make sure msg_control will point to properly aligned data */
union {
struct cmsghdr cm;
char control[CMSG_SPACE(sizeof(fd))];
}control_un;
msg.msg_control=control_un.control;
/* openbsd doesn't like "more space", msg_controllen must not
* include the end padding */
msg.msg_controllen=CMSG_LEN(sizeof(fd));
cmsg=CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
*(int*)CMSG_DATA(cmsg)=fd;
msg.msg_flags=0;
#else
msg.msg_accrights=(caddr_t) &fd;
msg.msg_accrightslen=sizeof(fd);
#endif
msg.msg_name=0;
msg.msg_namelen=0;
iov[0].iov_base=&p;
iov[0].iov_len=sizeof(peer*);
msg.msg_iov=iov;
msg.msg_iovlen=1;
again:
ret=sendmsg(pipe_fd, &msg, 0);
if (ret<0){
if (errno==EINTR) goto again;
if ((errno!=EAGAIN) && (errno!=EWOULDBLOCK)){
LM_CRIT( "send_fd: sendmsg failed on %d: %s\n",
pipe_fd, strerror(errno));
return 0;
}
}
return 1;
}
/**
* Receive a file descriptor from another process
* @param pipe_fd - pipe to read from
* @param fd - file descriptor to fill
* @param p - optional pipe to fill
* @returns 1 on success or 0 on failure
*/
static int receive_fd(int pipe_fd, int* fd,peer **p)
{
struct msghdr msg;
struct iovec iov[1];
int new_fd;
int ret;
#ifdef HAVE_MSGHDR_MSG_CONTROL
struct cmsghdr* cmsg;
union{
struct cmsghdr cm;
char control[CMSG_SPACE(sizeof(new_fd))];
}control_un;
msg.msg_control=control_un.control;
msg.msg_controllen=sizeof(control_un.control);
#else
msg.msg_accrights=(caddr_t) &new_fd;
msg.msg_accrightslen=sizeof(int);
#endif
msg.msg_name=0;
msg.msg_namelen=0;
iov[0].iov_base=p;
iov[0].iov_len=sizeof(peer*);
msg.msg_iov=iov;
msg.msg_iovlen=1;
again:
ret=recvmsg(pipe_fd, &msg, MSG_DONTWAIT|MSG_WAITALL);
if (ret<0){
if (errno==EINTR) goto again;
if ((errno==EAGAIN)||(errno==EWOULDBLOCK)) goto error;
LM_CRIT( "receive_fd: recvmsg on %d failed: %s\n",
pipe_fd, strerror(errno));
goto error;
}
if (ret==0){
/* EOF */
LM_CRIT( "receive_fd: EOF on %d\n", pipe_fd);
goto error;
}
if (ret!=sizeof(peer *)){
LM_WARN("receive_fd: different number of bytes received than expected (%d from %ld)"
"trying to fix...\n", ret, (long int)sizeof(peer*));
goto error;
}
#ifdef HAVE_MSGHDR_MSG_CONTROL
cmsg=CMSG_FIRSTHDR(&msg);
if ((cmsg!=0) && (cmsg->cmsg_len==CMSG_LEN(sizeof(new_fd)))){
if (cmsg->cmsg_type!= SCM_RIGHTS){
LM_ERR("receive_fd: msg control type != SCM_RIGHTS\n");
goto error;
}
if (cmsg->cmsg_level!= SOL_SOCKET){
LM_ERR("receive_fd: msg level != SOL_SOCKET\n");
goto error;
}
*fd=*((int*) CMSG_DATA(cmsg));
}else{
if(!cmsg)
LM_ERR("receive_fd: no descriptor passed, empty control message");
else
LM_ERR("receive_fd: no descriptor passed, cmsg=%p,"
"len=%d\n", cmsg, (unsigned)cmsg->cmsg_len);
*fd=-1;
*p=0;
/* it's not really an error */
}
#else
if (msg.msg_accrightslen==sizeof(int)){
*fd=new_fd;
}else{
LM_ERR("receive_fd: no descriptor passed,"
" accrightslen=%d\n", msg.msg_accrightslen);
*fd=-1;
}
#endif
return 1;
error:
return 0;
}
/**
* Initializes the receiver.
* @param p - the peer to initialize with, or NULL if to initialize as the receiver for unknown peers
* @return 1 on success or 0 on failure
*/
int receiver_init(peer *p)
{
int fd_exchange_pipe[2];/**< pipe to pass file descriptors towards this process */
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd_exchange_pipe)<0){
LM_ERR("receiver_init(): socketpair(fd_exchanged_pipe) failed > %s\n",strerror(errno));
return 0;
}
if (p) {
p->fd_exchange_pipe_local = fd_exchange_pipe[0];
p->fd_exchange_pipe = fd_exchange_pipe[1];
}
else {
fd_exchange_pipe_unknown_local = fd_exchange_pipe[0];
fd_exchange_pipe_unknown = fd_exchange_pipe[1];
}
return 1;
}
/**
* The Receiver Process - calls the receiv_loop and it never returns.
* @param p - the peer it is associated with or NULL for the unknown peers receiver
* @returns never, when disconnected it will exit
*/
void receiver_process(peer *p)
{
LM_INFO("receiver_process(): [%.*s] Receiver process doing init on new process...\n",
p?p->fqdn.len:0,p?p->fqdn.s:0);
if (p)
if (!add_serviced_peer(p)) goto done;
LM_INFO("receiver_process(): [%.*s] Receiver process starting up...\n",
p?p->fqdn.len:0,p?p->fqdn.s:0);
log_serviced_peers();
if (receive_loop(p)<0){
LM_INFO("receiver_process(): [%.*s] receive_loop() return -1 (error)!\n",
p?p->fqdn.len:0,p?p->fqdn.s:0);
}
done:
if (!*shutdownx){
LM_INFO("receiver_process(): [%.*s]... Receiver process cleaning-up - should not happen unless shuting down!\n",
p?p->fqdn.len:0,p?p->fqdn.s:0);
}
LM_INFO("receiver_process(): [%.*s]... Receiver process cleaning-up.\n",
p?p->fqdn.len:0,p?p->fqdn.s:0);
while(serviced_peers){
disconnect_serviced_peer(serviced_peers,0);
drop_serviced_peer(serviced_peers,0);
}
/* remove pid from list of running processes */
dp_del_pid(getpid());
#ifdef CDP_FOR_SER
#else
#ifdef PKG_MALLOC
#ifdef PKG_MALLOC
LM_DBG("Receiver[%.*s] Memory status (pkg):\n",
p?p->fqdn.len:0,p?p->fqdn.s:0);
//pkg_status();
#ifdef pkg_sums
pkg_sums();
#endif
#endif
#endif
#endif
LM_INFO("receiver_process(): [%.*s]... Receiver process finished.\n",
p?p->fqdn.len:0,p?p->fqdn.s:0);
exit(0);
}
/**
* Does the actual receive operations on the Diameter TCP socket, for retrieving incoming messages.
* The functions is to be called iteratively, each time there is something to be read from the TCP socket. It uses
* a simple state machine to read first the version, then the header and then the rest of the message. When an
* entire message is received, it is decoded and passed to the processing functions.
* @param sp - the serviced peer to operate on
* @return 1 on success, 0 on failure
*/
static inline int do_receive(serviced_peer_t *sp)
{
int cnt,n,version;
char *dst;
AAAMessage *dmsg;
switch (sp->state){
case Receiver_Waiting:
n = 1; /* wait for version */
dst = sp->buf;
break;
case Receiver_Header:
n = DIAMETER_HEADER_LEN - sp->buf_len; /* waiting for rest of header */
dst = sp->buf+sp->buf_len;
break;
case Receiver_Rest_of_Message:
n = sp->length - sp->msg_len; /* waiting for the rest of the message */
dst = sp->msg+sp->msg_len;
break;
default:
LM_ERR("do_receive(): [%.*s] Unknown state %d\n",
sp->p?sp->p->fqdn.len:0,
sp->p?sp->p->fqdn.s:0,
sp->state);
goto error_and_reset;
}
cnt = recv(sp->tcp_socket,dst,n,0);
if (cnt<=0)
goto error_and_reset;
switch (sp->state){
case Receiver_Waiting:
version = (unsigned char)(sp->buf[0]);
if (version!=1) {
LM_ERR("do_receive(): [%.*s] Received Unknown version [%d]\n",
sp->p->fqdn.len,
sp->p->fqdn.s,
(unsigned char)sp->buf[0]);
goto error_and_reset;
}else{
sp->state = Receiver_Header;
sp->buf_len = 1;
}
break;
case Receiver_Header:
sp->buf_len+=cnt;
if (sp->buf_len==DIAMETER_HEADER_LEN){
sp->length = get_3bytes(sp->buf+1);
if (sp->length>DP_MAX_MSG_LENGTH){
LM_ERR("do_receive(): [%.*s] Msg too big [%d] bytes\n",
sp->p?sp->p->fqdn.len:0,
sp->p?sp->p->fqdn.s:0,
sp->length);
goto error_and_reset;
}
LM_DBG("receive_loop(): [%.*s] Recv Version %d Length %d\n",
sp->p?sp->p->fqdn.len:0,
sp->p?sp->p->fqdn.s:0,
(unsigned char)(sp->buf[0]),
sp->length);
sp->msg = shm_malloc(sp->length);
if (!sp->msg) {
LOG_NO_MEM("shm",sp->length);
goto error_and_reset;
}
memcpy(sp->msg,sp->buf,sp->buf_len);
sp->msg_len=sp->buf_len;
sp->state = Receiver_Rest_of_Message;
}
break;
case Receiver_Rest_of_Message:
sp->msg_len+=cnt;
if (sp->msg_len==sp->length){
dmsg = AAATranslateMessage((unsigned char*)sp->msg,(unsigned int)sp->msg_len,1);
if (dmsg) {
sp->msg = 0;
receive_message(dmsg,sp);
}
else {
shm_free(sp->msg);
sp->msg = 0;
}
sp->msg_len = 0;
sp->buf_len = 0;
sp->state = Receiver_Waiting;
}
break;
default:
LM_ERR("do_receive(): [%.*s] Unknown state %d\n",
sp->p?sp->p->fqdn.len:0,
sp->p?sp->p->fqdn.s:0,
sp->state);
goto error_and_reset;
}
return 1;
error_and_reset:
if (sp->msg){
shm_free(sp->msg);
sp->msg = 0;
sp->msg_len = 0;
sp->buf_len = 0;
sp->state = Receiver_Waiting;
}
return 0;
}
/**
* Selects once on sockets for receiving and sending stuff.
* Monitors:
* - the fd exchange pipe, for receiving descriptors to be handled here
* - the tcp sockets of all serviced peers, triggering the incoming messages do_receive()
* - the send pipes of all serviced peers, triggering the sending of outgoing messages
* @returns 0 on normal exit or -1 on error
*/
int receive_loop(peer *original_peer)
{
fd_set rfds,efds;
struct timeval tv;
int n,max=0,cnt=0;
AAAMessage *msg=0;
serviced_peer_t *sp,*sp2;
peer *p;
int fd=-1;
int fd_exchange_pipe_local=0;
if (original_peer) fd_exchange_pipe_local = original_peer->fd_exchange_pipe_local;
else fd_exchange_pipe_local = fd_exchange_pipe_unknown_local;
// if (shutdownx) return -1;
while(shutdownx&&!*shutdownx){
n = 0;
while(!n){
if (shutdownx&&*shutdownx) break;
cfg_update();
log_serviced_peers();
max =-1;
FD_ZERO(&rfds);
FD_ZERO(&efds);
FD_SET(fd_exchange_pipe_local,&rfds);
if (fd_exchange_pipe_local>max) max = fd_exchange_pipe_local;
for(sp=serviced_peers;sp;sp=sp->next){
if (sp->tcp_socket>=0){
FD_SET(sp->tcp_socket,&rfds);
FD_SET(sp->tcp_socket,&efds);
if (sp->tcp_socket>max) max = sp->tcp_socket;
}
if (sp->send_pipe_fd>=0) {
FD_SET(sp->send_pipe_fd,&rfds);
if (sp->send_pipe_fd>max) max = sp->send_pipe_fd;
}
}
tv.tv_sec=1;
tv.tv_usec=0;
n = select(max+1,&rfds,0,&efds,&tv);
if (n==-1){
if (shutdownx&&*shutdownx) return 0;
LM_ERR("select_recv(): %s\n",strerror(errno));
for(sp=serviced_peers;sp;sp=sp2){
sp2 = sp->next;
disconnect_serviced_peer(sp,0);
if (sp->p && sp->p->is_dynamic)
drop_serviced_peer(sp,0);
}
sleep(1);
break;
}else
if (n){
if (FD_ISSET(fd_exchange_pipe_local,&rfds)){
/* fd exchange */
LM_DBG("select_recv(): There is something on the fd exchange pipe\n");
p = 0;
fd = -1;
if (!receive_fd(fd_exchange_pipe_local,&fd,&p)){
LM_ERR("select_recv(): Error reading from fd exchange pipe\n");
}else{
LM_DBG("select_recv(): fd exchange pipe says fd [%d] for peer %p:[%.*s]\n",fd,
p,
p?p->fqdn.len:0,
p?p->fqdn.s:0);
if (p){
sp2=0;
for(sp=serviced_peers;sp;sp=sp->next)
if (sp->p==p){
sp2 = sp;
break;
}
if (!sp2)
sp2 = add_serviced_peer(p);
else
make_send_pipe(sp2);
if (!sp2) {
LM_ERR("Error on add_serviced_peer()\n");
continue;
}
sp2->tcp_socket = fd;
if (p->state == Wait_Conn_Ack){
p->I_sock = fd;
sm_process(p,I_Rcv_Conn_Ack,0,0,fd);
}else{
p->R_sock = fd;
}
}else{
sp2 = add_serviced_peer(NULL);
if (!sp2) {
LM_ERR("Error on add_serviced_peer()\n");
continue;
}
sp2->tcp_socket = fd;
}
}
}
for(sp=serviced_peers;sp;){
if (sp->tcp_socket>=0 && FD_ISSET(sp->tcp_socket,&efds)) {
LM_INFO("select_recv(): [%.*s] Peer socket [%d] found on the exception list... dropping\n",
sp->p?sp->p->fqdn.len:0,
sp->p?sp->p->fqdn.s:0,
sp->tcp_socket);
goto drop_peer;
}
if (sp->send_pipe_fd>=0 && FD_ISSET(sp->send_pipe_fd,&rfds)) {
/* send */
LM_DBG("select_recv(): There is something on the send pipe\n");
cnt = read(sp->send_pipe_fd,&msg,sizeof(AAAMessage *));
if (cnt==0){
//This is very stupid and might not work well - droped messages... to be fixed
LM_INFO("select_recv(): ReOpening pipe for read. This should not happen...\n");
close(sp->send_pipe_fd);
sp->send_pipe_fd = open(sp->send_pipe_name.s, O_RDONLY | O_NDELAY);
goto receive;
}
if (cnt<sizeof(AAAMessage *)){
if (cnt<0) LM_ERR("select_recv(): Error reading from send pipe\n");
goto receive;
}
LM_DBG("select_recv(): Send pipe says [%p] %d\n",msg,cnt);
if (sp->tcp_socket<0){
LM_ERR("select_recv(): got a signal to send something, but the connection was not opened");
} else {
while( (cnt=write(sp->tcp_socket,msg->buf.s,msg->buf.len))==-1 ) {
if (errno==EINTR)
continue;
LM_ERR("select_recv(): [%.*s] write on socket [%d] returned error> %s... dropping\n",
sp->p?sp->p->fqdn.len:0,
sp->p?sp->p->fqdn.s:0,
sp->tcp_socket,
strerror(errno));
AAAFreeMessage(&msg);
close(sp->tcp_socket);
goto drop_peer;
}
if (cnt!=msg->buf.len){
LM_ERR("select_recv(): [%.*s] write on socket [%d] only wrote %d/%d bytes... dropping\n",
sp->p?sp->p->fqdn.len:0,
sp->p?sp->p->fqdn.s:0,
sp->tcp_socket,
cnt,
msg->buf.len);
AAAFreeMessage(&msg);
close(sp->tcp_socket);
goto drop_peer;
}
}
AAAFreeMessage(&msg);
//don't return, maybe there is something to read
}
receive:
/* receive */
if (sp->tcp_socket>=0 && FD_ISSET(sp->tcp_socket,&rfds)) {
errno=0;
cnt = do_receive(sp);
if (cnt<=0) {
LM_INFO("select_recv(): [%.*s] read on socket [%d] returned %d > %s... dropping\n",
sp->p?sp->p->fqdn.len:0,
sp->p?sp->p->fqdn.s:0,
sp->tcp_socket,
cnt,
errno?strerror(errno):"");
goto drop_peer;
}
}
//next_sp:
/* go to next serviced peer */
sp=sp->next;
continue;
drop_peer:
/* drop this serviced peer on error */
sp2 = sp->next;
disconnect_serviced_peer(sp,0);
if (sp->p && sp->p->is_dynamic)
drop_serviced_peer(sp,0);
sp = sp2;
}
}
}
}
return 0;
}
/**
* Initiate a connection to a peer.
* The obtained socket is then sent to the respective receiver.
* This is typically called from the timer, but otherwise it can be called from any other process.
* @param p - peer to connect to
* @returns socket if OK, -1 on error
*/
int peer_connect(peer *p)
{
int sock;
unsigned int option = 1;
struct addrinfo *ainfo=0,*res=0,*sainfo=0,hints;
char buf[256],host[256],serv[256];
int error;
memset (&hints, 0, sizeof(hints));
//hints.ai_protocol = IPPROTO_SCTP;
//hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_ADDRCONFIG;
hints.ai_socktype = SOCK_STREAM;
sprintf(buf,"%d",p->port);
error = getaddrinfo(p->fqdn.s, buf, &hints, &res);
if (error!=0){
LM_WARN("peer_connect(): Error opening connection to %.*s:%d >%s\n",
p->fqdn.len,p->fqdn.s,p->port,gai_strerror(error));
goto error;
}
for(ainfo = res;ainfo;ainfo = ainfo->ai_next)
{
if (getnameinfo(ainfo->ai_addr,ainfo->ai_addrlen,
host,256,serv,256,NI_NUMERICHOST|NI_NUMERICSERV)==0){
LM_WARN("peer_connect(): Trying to connect to %s port %s\n",
host,serv);
}
if ((sock = socket(ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol)) == -1) {
LM_ERR("peer_connect(): error creating client socket to %s port %s >"
" %s\n",host,serv,strerror(errno));
continue;
}
/* try to set the local socket used to connect to the peer */
if (p->src_addr.s && p->src_addr.len > 0) {
LM_DBG("peer_connect(): connetting to peer via src addr=%.*s",p->src_addr.len, p->src_addr.s);
memset (&hints, 0, sizeof(hints));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo(p->src_addr.s, NULL, &hints, &sainfo);
if (error!=0){
LM_WARN("peer_connect(): error getting client socket on %.*s:%s\n",
p->src_addr.len,p->src_addr.s,gai_strerror(error));
} else {
if (bind(sock, sainfo->ai_addr, sainfo->ai_addrlen )) {
LM_WARN("peer_connect(): error opening client socket on %.*s:%s\n",
p->src_addr.len,p->src_addr.s,strerror(errno));
}
}
}
{// Connect with timeout
int x;
x=fcntl(sock,F_GETFL,0);
fcntl(sock,F_SETFL,x | O_NONBLOCK);
int res = connect(sock,ainfo->ai_addr,ainfo->ai_addrlen);
if (res<0){
if (errno==EINPROGRESS){
struct timeval tv={
.tv_sec = config->connect_timeout,
.tv_usec = 0,
};
fd_set myset;
FD_ZERO(&myset);
FD_SET(sock, &myset);
if (select(sock+1, NULL, &myset, NULL, &tv) > 0) {
socklen_t lon = sizeof(int);
int valopt;
getsockopt(sock, SOL_SOCKET, SO_ERROR, (void*)(&valopt), &lon);
if (valopt) {
LM_WARN("peer_connect(): Error opening connection to to %s port %s >%s\n",host,serv,strerror(valopt));
close(sock);
continue;
}
}else{
LM_WARN("peer_connect(): Timeout or error opening connection to to %s port %s >%s\n",host,serv,strerror(errno));
close(sock);
continue;
}
}
}else{
LM_WARN("peer_connect(): Error opening connection to to %s port %s >%s\n",host,serv,strerror(errno));
close(sock);
continue;
}
x=fcntl(sock,F_GETFL,0);
fcntl(sock,F_SETFL,x & (~O_NONBLOCK));
}
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&option,sizeof(option));
LM_INFO("peer_connect(): Peer %.*s:%d connected\n",p->fqdn.len,p->fqdn.s,p->port);
if (!send_fd(p->fd_exchange_pipe,sock,p)){
LM_ERR("peer_connect(): [%.*s] Error sending fd to respective receiver\n",p->fqdn.len,p->fqdn.s);
close(sock);
goto error;
}
if (res) freeaddrinfo(res);
return sock;
}
error:
if (res) freeaddrinfo(res);
return -1;
}
/**
* Sends a newly connected socket to the receiver process
* @param sock
* @return
*/
int receiver_send_socket(int sock, peer *p)
{
int pipe_fd;
if (p)
pipe_fd = p->fd_exchange_pipe;
else
pipe_fd = fd_exchange_pipe_unknown;
return send_fd(pipe_fd,sock,p);
}
/**
* Sends a message to a peer (to be called from other processes).
* This just writes the pointer to the message in the send pipe. The specific
* peer process will pick that up and send the message, as only that specific
* process has the id of socket (we are forking the peers dynamically and as such,
* the sockets are not visible between processes).
* @param p - the peer to send to
* @param msg - the message to send
* @returns 1 on success, 0 on failure
*/
int peer_send_msg(peer *p,AAAMessage *msg)
{
int fd,n;
if (!AAABuildMsgBuffer(msg)) return 0;
if (!p->send_pipe_name.s) {
LM_ERR("peer_send_msg(): Peer %.*s has no attached send pipe\n",p->fqdn.len,p->fqdn.s);
return 0;
}
fd = open(p->send_pipe_name.s,O_WRONLY);
if (fd<0){
LM_ERR("peer_send_msg(): Peer %.*s error on pipe open > %s\n",p->fqdn.len,p->fqdn.s,strerror(errno));
return 0;
}
LM_DBG("peer_send_msg(): Pipe push [%p]\n",msg);
n = write(fd,&msg,sizeof(AAAMessage *));
if (n<0) {
LM_ERR("peer_send_msg(): Peer %.*s error on pipe write > %s\n",p->fqdn.len,p->fqdn.s,strerror(errno));
close(fd);
return 0;
}
if (n!=sizeof(AAAMessage *)) {
LM_ERR("peer_send_msg(): Peer %.*s error on pipe write > only %d bytes written\n",p->fqdn.len,p->fqdn.s,n);
close(fd);
return 0;
}
close(fd);
return 1;
}
/**
* Send a message to a peer (only to be called from the receiver process).
* This directly writes the message on the socket. It is used for transmission during
* the Capability Exchange procedure, when the send pipes are not opened yet.
* It also sends the file descriptor to the peer's dedicated receiver if one found.
* @param p - the peer to send to
* @param sock - the socket to send through
* @param msg - the message to send
* @param locked - whether the caller locked the peer already
* @returns 1 on success, 0 on error
*/
int peer_send(peer *p,int sock,AAAMessage *msg,int locked)
{
int n;
serviced_peer_t *sp;
if (!p||!msg||sock<0) return 0;
LM_DBG("peer_send(): [%.*s] sending direct message to peer\n",
p->fqdn.len,
p->fqdn.s);
if (!AAABuildMsgBuffer(msg)) return 0;
if (!locked) lock_get(p->lock);
while( (n=write(sock,msg->buf.s,msg->buf.len))==-1 ) {
if (errno==EINTR)
continue;
LM_ERR("peer_send(): write returned error: %s\n",
strerror(errno));
if (p->I_sock==sock) sm_process(p,I_Peer_Disc,0,1,p->I_sock);
if (p->R_sock==sock) sm_process(p,R_Peer_Disc,0,1,p->R_sock);
if (!locked) lock_release(p->lock);
AAAFreeMessage(&msg);
return 0;
}
if (n!=msg->buf.len){
LM_ERR("peer_send(): only wrote %d/%d bytes\n",n,msg->buf.len);
if (!locked) lock_release(p->lock);
AAAFreeMessage(&msg);
return 0;
}
if (!locked) lock_release(p->lock);
AAAFreeMessage(&msg);
/* now switch the peer processing to its dedicated process if this is not a dynamic peer */
if (!p->is_dynamic){
LM_DBG("peer_send(): [%.*s] switching peer to own and dedicated receiver\n",
p->fqdn.len,
p->fqdn.s);
send_fd(p->fd_exchange_pipe,sock,p);
for(sp=serviced_peers;sp;sp=sp->next)
if (sp->p==p){
drop_serviced_peer(sp,locked);
break;
}
}
return 1;
}
/**
* Receives a message and does basic processing or call the sm_process().
* This gets called from the do_receive() for every message that is received.
* Basic processing, before the state machine, is done here.
* @param msg - the message received
* @param sp - the serviced peer that it was receiver on
*/
void receive_message(AAAMessage *msg,serviced_peer_t *sp)
{
AAA_AVP *avp1,*avp2;
LM_DBG("receive_message(): [%.*s] Recv msg %d\n",
sp->p?sp->p->fqdn.len:0,
sp->p?sp->p->fqdn.s:0,
msg->commandCode);
if (!sp->p){
switch (msg->commandCode){
case Code_CE:
if (is_req(msg)){
avp1 = AAAFindMatchingAVP(msg,msg->avpList.head,AVP_Origin_Host,0,0);
avp2 = AAAFindMatchingAVP(msg,msg->avpList.head,AVP_Origin_Realm,0,0);
if (avp1&&avp2){
sp->p = get_peer_from_fqdn(avp1->data,avp2->data);
}
if (!sp->p) {
LM_ERR("receive_msg(): Received CER from unknown peer (accept unknown=%d) -ignored\n",
config->accept_unknown_peers);
AAAFreeMessage(&msg);
}else{
LM_DBG("receive_message(): [%.*s] This receiver has no peer associated\n",
sp->p?sp->p->fqdn.len:0,
sp->p?sp->p->fqdn.s:0 );
//set_peer_pipe();
make_send_pipe(sp);
sm_process(sp->p,R_Conn_CER,msg,0,sp->tcp_socket);
}
}
else{
LM_ERR("receive_msg(): Received CEA from an unknown peer -ignored\n");
AAAFreeMessage(&msg);
}
break;
default:
LM_ERR("receive_msg(): Received non-CE from an unknown peer -ignored\n");
AAAFreeMessage(&msg);
}
}else{
touch_peer(sp->p);
switch (sp->p->state){
case Wait_I_CEA:
if (msg->commandCode!=Code_CE||is_req(msg)){
sm_process(sp->p,I_Rcv_Non_CEA,msg,0,sp->tcp_socket);
}else
sm_process(sp->p,I_Rcv_CEA,msg,0,sp->tcp_socket);
break;
case I_Open:
switch (msg->commandCode){
case Code_CE:
if (is_req(msg)) sm_process(sp->p,I_Rcv_CER,msg,0,sp->tcp_socket);
else sm_process(sp->p,I_Rcv_CEA,msg,0,sp->tcp_socket);
break;
case Code_DW:
if (is_req(msg)) sm_process(sp->p,I_Rcv_DWR,msg,0,sp->tcp_socket);
else sm_process(sp->p,I_Rcv_DWA,msg,0,sp->tcp_socket);
break;
case Code_DP:
if (is_req(msg)) sm_process(sp->p,I_Rcv_DPR,msg,0,sp->tcp_socket);
else sm_process(sp->p,I_Rcv_DPA,msg,0,sp->tcp_socket);
break;
default:
sm_process(sp->p,I_Rcv_Message,msg,0,sp->tcp_socket);
}
break;
case R_Open:
switch (msg->commandCode){
case Code_CE:
if (is_req(msg)) sm_process(sp->p,R_Rcv_CER,msg,0,sp->tcp_socket);
else sm_process(sp->p,R_Rcv_CEA,msg,0,sp->tcp_socket);
break;
case Code_DW:
if (is_req(msg)) sm_process(sp->p,R_Rcv_DWR,msg,0,sp->tcp_socket);
else sm_process(sp->p,R_Rcv_DWA,msg,0,sp->tcp_socket);
break;
case Code_DP:
if (is_req(msg)) sm_process(sp->p,R_Rcv_DPR,msg,0,sp->tcp_socket);
else sm_process(sp->p,R_Rcv_DPA,msg,0,sp->tcp_socket);
break;
default:
sm_process(sp->p,R_Rcv_Message,msg,0,sp->tcp_socket);
}
break;
default:
LM_ERR("receive_msg(): [%.*s] Received msg while peer in state %d -ignored\n",
sp->p->fqdn.len,
sp->p->fqdn.s,
sp->p->state);
AAAFreeMessage(&msg);
}
}
}