/** * 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. * @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; // LOG(L_CRIT,"[%d]\n",sock); if (!p||!msg||sock<0) return 0; 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; LOG(L_ERR,"ERROR: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){ LOG(L_ERR,"ERROR: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); return 1; }
/** * Timer function for peer management. * This is registered as a timer by peer_manager_init() and gets called every * #PEER_MANAGER_TIMER seconds. Then it looks on what changed and triggers events. * @param now - time of call * @param ptr - generic pointer for timers - not used */ void peer_timer(time_t now,void *ptr) { peer *p,*n; LOG(L_DBG,"DBG:peer_timer(): taking care of peers...\n"); lock_get(peer_list_lock); p = peer_list->head; while(p){ lock_get(p->lock); n = p->next; if (p->activity+config->tc<=now){ LOG(L_INFO,"DBG:peer_timer(): Peer %.*s \tState %d \n",p->fqdn.len,p->fqdn.s,p->state); switch (p->state){ /* initiating connection */ case Closed: if (p->is_dynamic && config->drop_unknown_peers){ remove_peer(p); free_peer(p,1); break; } touch_peer(p); sm_process(p,Start,0,1,0); break; /* timeouts */ case Wait_Conn_Ack: case Wait_I_CEA: case Closing: case Wait_Returns: touch_peer(p); sm_process(p,Timeout,0,1,0); break; /* inactivity detected */ case I_Open: case R_Open: if (p->waitingDWA){ p->waitingDWA = 0; if (p->state==I_Open) sm_process(p,I_Peer_Disc,0,1,p->I_sock); if (p->state==R_Open) sm_process(p,R_Peer_Disc,0,1,p->R_sock); } else { p->waitingDWA = 1; Snd_DWR(p); touch_peer(p); } break; /* ignored states */ /* unknown states */ default: LOG(L_ERR,"ERROR:peer_timer(): Peer %.*s inactive in state %d\n", p->fqdn.len,p->fqdn.s,p->state); } } lock_release(p->lock); p = n; } lock_release(peer_list_lock); log_peer_list(L_INFO); }
/** * 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; }
/** * Send a AAAMessage asynchronously. * When the response is received, the callback_f(callback_param,...) is called. * @param message - the request to be sent * @param peer_id - FQDN of the peer to send * @param callback_f - callback to be called on transactional response or transaction timeout * @param callback_param - generic parameter to call the transactional callback function with * @returns 1 on success, 0 on failure * \todo remove peer_id and add Realm routing */ AAAReturnCode AAASendMessageToPeer( AAAMessage *message, str *peer_id, AAATransactionCallback_f *callback_f, void *callback_param) { peer *p; p = get_peer_by_fqdn(peer_id); if (!p) { LOG(L_ERR,"ERROR:AAASendMessageToPeer(): Peer unknown %.*s\n",peer_id->len,peer_id->s); goto error; } if (p->state!=I_Open && p->state!=R_Open){ LOG(L_ERR,"ERROR:AAASendMessageToPeer(): Peer not connected to %.*s\n",peer_id->len,peer_id->s); goto error; } /* only add transaction following when required */ if (callback_f){ if (is_req(message)) cdp_add_trans(message,callback_f,callback_param,config->transaction_timeout,1); else LOG(L_ERR,"ERROR:AAASendMessageToPeer(): can't add transaction callback for answer.\n"); } // if (!peer_send_msg(p,message)) if (!sm_process(p,Send_Message,message,0,0)) goto error; return 1; error: AAAFreeMessage(&message); return 0; }
/** * 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); }
/** * Send a AAAMessage synchronously. * This blocks until a response is received or a transactional time-out happens. * @param message - the request to be sent * @param peer_id - FQDN of the peer to send * @returns 1 on success, 0 on failure * \todo remove peer_id and add Realm routing * \todo replace the busy-waiting lock in here with one that does not consume CPU */ AAAMessage* AAASendRecvMessageToPeer(AAAMessage *message, str *peer_id) { peer *p; gen_sem_t *sem; cdp_trans_t *t; AAAMessage *ans; p = get_peer_by_fqdn(peer_id); if (!p) { LOG(L_ERR,"ERROR:AAASendRecvMessageToPeer(): Peer unknown %.*s\n",peer_id->len,peer_id->s); goto error; } if (p->state!=I_Open && p->state!=R_Open){ LOG(L_ERR,"ERROR:AAASendRecvMessageToPeer(): Peer not connected to %.*s\n",peer_id->len,peer_id->s); goto error; } if (is_req(message)){ sem_new(sem,0); t = cdp_add_trans(message,sendrecv_cb,(void*)sem,config->transaction_timeout,0); // if (!peer_send_msg(p,message)) { if (!sm_process(p,Send_Message,message,0,0)){ sem_free(sem); goto error; } /* block until callback is executed */ while(sem_get(sem)<0){ if (shutdownx&&(*shutdownx)) goto error; LOG(L_WARN,"WARN:AAASendRecvMessageToPeer(): interrupted by signal or something > %s\n",strerror(errno)); } sem_free(sem); ans = t->ans; cdp_free_trans(t); return ans; } else { LOG(L_ERR,"ERROR:AAASendRecvMessageToPeer(): can't add wait for answer to answer.\n"); goto error; } error: out_of_memory: AAAFreeMessage(&message); return 0; }
/** * Receives a mesasge and does basic processing or call the sm_process(). * This gets called from the receive_loop for every message that is received. * @param msg - the message received * @param sock - socket received on */ void receive_message(AAAMessage *msg,int sock) { AAA_AVP *avp1,*avp2; LOG(L_DBG,"DBG:receive_message(): [%d] Recv msg %d\n",sock,msg->commandCode); if (!this_peer) { this_peer = get_peer_from_sock(sock); set_peer_pipe(); } if (!this_peer){ 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){ this_peer = get_peer_from_fqdn(avp1->data,avp2->data); } if (!this_peer) { LOG(L_ERR,"ERROR:receive_msg(): Received CER from unknown peer (accept unknown=%d) -ignored\n", config->accept_unknown_peers); AAAFreeMessage(&msg); }else{ set_peer_pipe(); sm_process(this_peer,R_Conn_CER,msg,0,sock); } } else{ LOG(L_ERR,"ERROR:receive_msg(): Received CEA from an unknown peer -ignored\n"); AAAFreeMessage(&msg); } break; default: LOG(L_ERR,"ERROR:receive_msg(): Received non-CE from an unknown peer -ignored\n"); AAAFreeMessage(&msg); } }else{ touch_peer(this_peer); switch (this_peer->state){ case Wait_I_CEA: if (msg->commandCode!=Code_CE||is_req(msg)){ sm_process(this_peer,I_Rcv_Non_CEA,msg,0,sock); }else sm_process(this_peer,I_Rcv_CEA,msg,0,sock); break; case I_Open: switch (msg->commandCode){ case Code_CE: if (is_req(msg)) sm_process(this_peer,I_Rcv_CER,msg,0,sock); else sm_process(this_peer,I_Rcv_CEA,msg,0,sock); break; case Code_DW: if (is_req(msg)) sm_process(this_peer,I_Rcv_DWR,msg,0,sock); else sm_process(this_peer,I_Rcv_DWA,msg,0,sock); break; case Code_DP: if (is_req(msg)) sm_process(this_peer,I_Rcv_DPR,msg,0,sock); else sm_process(this_peer,I_Rcv_DPA,msg,0,sock); break; default: sm_process(this_peer,I_Rcv_Message,msg,0,sock); } break; case R_Open: switch (msg->commandCode){ case Code_CE: if (is_req(msg)) sm_process(this_peer,R_Rcv_CER,msg,0,sock); else sm_process(this_peer,R_Rcv_CEA,msg,0,sock); break; case Code_DW: if (is_req(msg)) sm_process(this_peer,R_Rcv_DWR,msg,0,sock); else sm_process(this_peer,R_Rcv_DWA,msg,0,sock); break; case Code_DP: if (is_req(msg)) sm_process(this_peer,R_Rcv_DPR,msg,0,sock); else sm_process(this_peer,R_Rcv_DPA,msg,0,sock); break; default: sm_process(this_peer,R_Rcv_Message,msg,0,sock); } break; default: LOG(L_ERR,"ERROR:receive_msg(): Received msg while peer in state %d -ignored\n",this_peer->state); AAAFreeMessage(&msg); } } }
/** * Receive Loop for Diameter messages. * Decodes the message and calls receive_message(). * @param sock - the socket to receive from * @returns when the socket is closed */ void receive_loop(int sock) { char buf[hdr_len],*msg; int buf_len,length,version,cnt,msg_len; AAAMessage *dmsg; while(!*shutdownx){ buf_len=0; while(buf_len<1){ cnt = select_recv(sock,buf+buf_len,1,0); if (cnt<0) goto error; buf_len+=cnt; } version = (unsigned char)buf[0]; if (version!=1) { LOG(L_ERR,"ERROR:receive_loop():[%d] Recv Unknown version [%d]\n",sock,(unsigned char)buf[0]); continue; } while(buf_len<hdr_len){ cnt = select_recv(sock,buf+buf_len,hdr_len-buf_len,0); if (cnt<0) goto error; buf_len+=cnt; } length = get_3bytes(buf+1); if (length>DP_MAX_MSG_LENGTH){ LOG(L_ERR,"ERROR:receive_loop():[%d] Msg too big [%d] bytes\n",sock,length); goto error; } LOG(L_DBG,"DBG:receive_loop():[%d] Recv Version %d Length %d\n",sock,version,length); msg = shm_malloc(length); if (!msg) { LOG_NO_MEM("shm",length); goto error; } memcpy(msg,buf,hdr_len); msg_len=hdr_len; while(msg_len<length){ cnt = select_recv(sock,msg+msg_len,length-msg_len,0); if (cnt<0) { shm_free(msg); goto error; } msg_len+=cnt; } LOG(L_DBG,"DBG:receive_loop():[%d] Recv message complete\n",sock); dmsg = AAATranslateMessage((unsigned char*)msg,(unsigned int)msg_len,1); /*shm_free(msg);*/ if (dmsg) receive_message(dmsg,sock); else{ shm_free(msg); } } error: if (this_peer) { if (this_peer->I_sock == sock) sm_process(this_peer,I_Peer_Disc,0,0,sock); if (this_peer->R_sock == sock) sm_process(this_peer,R_Peer_Disc,0,0,sock); } LOG(L_ERR,"INFO:receive_loop():[%d] Client closed connection or error... BYE\n",sock); }
/** * Diameter base protocol state-machine processing. * This function get's called for every event. It updates the states and can trigger * other events. * @param p - the peer for which the event happened * @param event - the event that happened * @param msg - if a Diameter message was received this is it, or NULL if not * @param peer_locked - if the peer lock is already aquired * @param sock - socket that this event happened on, or NULL if unrelated * @returns 1 on success, 0 on error. Also the peer states are updated */ int sm_process(peer *p,peer_event_t event,AAAMessage *msg,int peer_locked,int sock) { int result_code; peer_event_t next_event; int msg_received=0; if (!peer_locked) lock_get(p->lock); LM_DBG("sm_process(): Peer %.*s \tState %s \tEvent %s\n", p->fqdn.len,p->fqdn.s,dp_states[p->state],dp_events[event-101]); switch (p->state){ case Closed: switch (event){ case Start: p->state = Wait_Conn_Ack; next_event = I_Snd_Conn_Req(p); if (next_event==I_Rcv_Conn_NAck) sm_process(p,next_event,0,1,p->I_sock); else{ /* wait for fd to be transmitted to the respective receiver, in order to get a send pipe opened */ } break; case R_Conn_CER: R_Accept(p,sock); result_code = Process_CER(p,msg); Snd_CEA(p,msg,result_code,p->R_sock); msg=0; if (result_code>=2000 && result_code<3000) p->state = R_Open; else { R_Disc(p); p->state = Closed; } log_peer_list(L_INFO); break; case Stop: /* just ignore this state */ p->state = Closed; break; default: LM_ERR("sm_process(): In state %s invalid event %s\n", dp_states[p->state],dp_events[event-101]); goto error; } break; case Wait_Conn_Ack: switch(event){ case I_Rcv_Conn_Ack: I_Snd_CER(p); p->state = Wait_I_CEA; break; case I_Rcv_Conn_NAck: Cleanup(p,p->I_sock); p->state = Closed; break; case R_Conn_CER: if (p->r_cer) AAAFreeMessage(&(p->r_cer)); R_Accept(p,sock); result_code = Process_CER(p,msg); if (result_code>=2000 && result_code<3000){ p->state = Wait_Conn_Ack_Elect; p->r_cer = msg; } else { p->state = Closed; AAAFreeMessage(&msg); R_Disc(p); I_Disc(p); } break; case Timeout: Error(p,p->I_sock); p->state = Closed; default: LM_ERR("sm_process(): In state %s invalid event %s\n", dp_states[p->state],dp_events[event-101]); goto error; } break; case Wait_I_CEA: switch(event){ case I_Rcv_CEA: result_code = Process_CEA(p,msg); if (result_code>=2000 && result_code<3000) p->state = I_Open; else { Cleanup(p,p->I_sock); p->state = Closed; } log_peer_list(L_INFO); break; case R_Conn_CER: if (p->r_cer) AAAFreeMessage(&(p->r_cer)); R_Accept(p,sock); result_code = Process_CER(p,msg); if (result_code>=2000 && result_code<3000){ p->state = Wait_Returns; if (Elect(p,msg)){ // won the election = > I_Disc(), R_Send_CEA() LM_INFO("sm_process():Wait_I_CEA Win Elect \n"); sm_process(p,Win_Election,msg,1,sock); } else { // lost the election => wait for I_Recv_CEA, then R_Disc() LM_INFO("sm_process():Wait_I_CEA Lose Elect \n"); p->r_cer = msg; sm_process(p,I_Peer_Disc,0,1,p->I_sock); } } else{ Snd_CEA(p,msg,result_code,p->R_sock); R_Disc(p); I_Disc(p); p->state=Closed; break; } break; case I_Peer_Disc: I_Disc(p); p->state = Closed; break; case I_Rcv_Non_CEA: Error(p,p->I_sock); p->state = Closed; break; case Timeout: Error(p,p->I_sock); p->state = Closed; break; default: LM_ERR("sm_process(): In state %s invalid event %s\n", dp_states[p->state],dp_events[event-101]); goto error; } break; case Wait_Conn_Ack_Elect: switch(event){ case I_Rcv_Conn_Ack: I_Snd_CER(p); if (p->r_cer){ p->state = Wait_Returns; if (Elect(p,p->r_cer)){ // won the election = > I_Disc(), R_Send_CEA() LM_INFO("sm_process():Wait_Conn_Ack_Elect Win Elect \n"); sm_process(p,Win_Election,p->r_cer,1,sock); p->r_cer = 0; } else { // lost the election => wait for I_Recv_CEA, then R_Disc() LM_INFO("sm_process():Wait_Conn_Ack_Elect Lose Elect \n"); AAAFreeMessage(&p->r_cer); } } else { LM_ERR("sm_process():Wait_Conn_Ack_Elect, I_Rcv_Conn_Ack, No R-CER ! \n"); p->state = Wait_I_CEA; } break; case I_Rcv_Conn_NAck: Cleanup(p,p->I_sock); if (p->r_cer){ result_code = Process_CER(p,p->r_cer); Snd_CEA(p,p->r_cer,result_code,p->R_sock); p->r_cer=0; if (result_code>=2000 && result_code<3000) p->state = R_Open; else { R_Disc(p); p->state = Closed; // p->state = R_Open; /* Or maybe I should disconnect it?*/ } }else{ LM_ERR("sm_process():Wait_Conn_Ack_Elect, I_Rcv_Conn_NAck No R-CER ! \n"); } break; case R_Peer_Disc: R_Disc(p); p->state = Wait_Conn_Ack; break; case R_Conn_CER: R_Reject(p,sock); AAAFreeMessage(&msg); p->state = Wait_Conn_Ack_Elect; break; case Timeout: if (p->I_sock>=0) Error(p,p->I_sock); if (p->R_sock>=0) Error(p,p->R_sock); p->state = Closed; break; default: LM_ERR("sm_process(): In state %s invalid event %s\n", dp_states[p->state],dp_events[event-101]); goto error; } break; case Wait_Returns: switch(event){ case Win_Election: /* this is the Win Election -> I is dropped, R is kept */ LM_INFO("sm_process():Wait_Returns Win Elect \n"); I_Disc(p); result_code = Process_CER(p,msg); Snd_CEA(p,msg,result_code,p->R_sock); if (result_code>=2000 && result_code<3000){ p->state = R_Open; }else{ R_Disc(p); p->state = Closed; } break; case I_Peer_Disc: I_Disc(p); if (p->r_cer){ result_code = Process_CER(p,p->r_cer); Snd_CEA(p,p->r_cer,result_code,p->R_sock); p->r_cer=0; if (result_code>=2000 && result_code<3000){ p->state = R_Open; }else{ R_Disc(p); p->state = Closed; } }else { LM_ERR("sm_process():Wait_Returns, I_Peer_Disc No R-CER ! \n"); } break; case I_Rcv_CEA: /* this is the Lost Election -> I is kept, R dropped */ LM_INFO("sm_process():Wait_Returns Lost Elect \n"); R_Disc(p); result_code = Process_CEA(p,msg); if (result_code>=2000 && result_code<3000) p->state = I_Open; else { Cleanup(p,p->I_sock); p->state = Closed; } break; case R_Peer_Disc: R_Disc(p); p->state = Wait_I_CEA; break; case R_Conn_CER: R_Reject(p,p->R_sock); AAAFreeMessage(&msg); p->state = Wait_Returns; break; case Timeout: if (p->I_sock>=0) Error(p,p->I_sock); if (p->R_sock>=0) Error(p,p->R_sock); p->state = Closed; default: LM_ERR("sm_process(): In state %s invalid event %s\n", dp_states[p->state],dp_events[event-101]); goto error; } break; case R_Open: switch (event){ case Send_Message: Snd_Message(p,msg); p->state = R_Open; break; case R_Rcv_Message: // delayed processing until out of the critical zone //Rcv_Process(p,msg); msg_received = 1; p->state = R_Open; break; case R_Rcv_DWR: result_code = Process_DWR(p,msg); Snd_DWA(p,msg,result_code,p->R_sock); p->state = R_Open; break; case R_Rcv_DWA: Process_DWA(p,msg); p->state = R_Open; break; case R_Conn_CER: R_Reject(p,sock); AAAFreeMessage(&msg); p->state = R_Open; break; case Stop: Snd_DPR(p); p->state = Closing; break; case R_Rcv_DPR: Snd_DPA(p,msg,AAA_SUCCESS,p->R_sock); R_Disc(p); p->state = Closed; log_peer_list(L_INFO); break; case R_Peer_Disc: R_Disc(p); p->state = Closed; log_peer_list(L_INFO); break; case R_Rcv_CER: result_code = Process_CER(p,msg); Snd_CEA(p,msg,result_code,p->R_sock); if (result_code>=2000 && result_code<3000) p->state = R_Open; else { /*R_Disc(p);p.state = Closed;*/ p->state = R_Open; /* Or maybe I should disconnect it?*/ } break; case R_Rcv_CEA: result_code = Process_CEA(p,msg); if (result_code>=2000 && result_code<3000) p->state = R_Open; else { /*R_Disc(p);p.state = Closed;*/ p->state = R_Open; /* Or maybe I should disconnect it?*/ } log_peer_list(L_INFO); break; default: LM_ERR("sm_process(): In state %s invalid event %s\n", dp_states[p->state],dp_events[event-101]); goto error; } break; case I_Open: switch (event){ case Send_Message: Snd_Message(p,msg); p->state = I_Open; break; case I_Rcv_Message: // delayed processing until out of the critical zone //Rcv_Process(p,msg); msg_received = 1; p->state = I_Open; break; case I_Rcv_DWR: result_code = Process_DWR(p,msg); Snd_DWA(p,msg,result_code,p->I_sock); p->state =I_Open; break; case I_Rcv_DWA: Process_DWA(p,msg); p->state =I_Open; break; case R_Conn_CER: R_Reject(p,sock); AAAFreeMessage(&msg); p->state = I_Open; break; case Stop: Snd_DPR(p); p->state = Closing; break; case I_Rcv_DPR: Snd_DPA(p,msg,2001,p->I_sock); I_Disc(p); p->state = Closed; log_peer_list(L_INFO); break; case I_Peer_Disc: I_Disc(p); p->state = Closed; log_peer_list(L_INFO); break; case I_Rcv_CER: result_code = Process_CER(p,msg); Snd_CEA(p,msg,result_code,p->I_sock); if (result_code>=2000 && result_code<3000) p->state = I_Open; else { /*I_Disc(p);p.state = Closed;*/ p->state = I_Open; /* Or maybe I should disconnect it?*/ } break; case I_Rcv_CEA: result_code = Process_CEA(p,msg); if (result_code>=2000 && result_code<3000) p->state = I_Open; else { /*I_Disc(p);p.state = Closed;*/ p->state = I_Open; /* Or maybe I should disconnect it?*/ } break; default: LM_ERR("sm_process(): In state %s invalid event %s\n", dp_states[p->state],dp_events[event-101]); goto error; } break; case Closing: switch(event){ case I_Rcv_DPA: I_Disc(p); p->state = Closed; break; case R_Rcv_DPA: R_Disc(p); p->state = Closed; break; case Timeout: if (p->I_sock>=0) Error(p,p->I_sock); if (p->R_sock>=0) Error(p,p->R_sock); p->state = Closed; break; case I_Peer_Disc: I_Disc(p); p->state = Closed; break; case R_Peer_Disc: R_Disc(p); p->state = Closed; break; default: LM_ERR("sm_process(): In state %s invalid event %s\n", dp_states[p->state],dp_events[event-101]); goto error; } break; } if (!peer_locked) lock_release(p->lock); if (msg_received) Rcv_Process(p,msg); return 1; error: if (!peer_locked) lock_release(p->lock); return 0; }
/** * Timer function for peer management. * This is registered as a timer by peer_manager_init() and gets called every * #PEER_MANAGER_TIMER seconds. Then it looks on what changed and triggers events. * @param now - time of call * @param ptr - generic pointer for timers - not used */ int peer_timer(time_t now,void *ptr) { peer *p,*n; int i; LM_DBG("peer_timer(): taking care of peers...\n"); lock_get(peer_list_lock); p = peer_list->head; while(p){ lock_get(p->lock); n = p->next; if (p->disabled && (p->state != Closed || p->state != Closing)) { LM_DBG("Peer [%.*s] has been disabled - shutting down\n", p->fqdn.len, p->fqdn.s); if (p->state == I_Open) sm_process(p, Stop, 0, 1, p->I_sock); if (p->state == R_Open) sm_process(p, Stop, 0, 1, p->R_sock); lock_release(p->lock); p = n; continue; } if (p->activity+config->tc<=now){ LM_DBG("peer_timer(): Peer %.*s State %d \n",p->fqdn.len,p->fqdn.s,p->state); switch (p->state){ /* initiating connection */ case Closed: if (p->is_dynamic && config->drop_unknown_peers){ remove_peer(p); free_peer(p,1); break; } if (!p->disabled) { touch_peer(p); sm_process(p,Start,0,1,0); } break; /* timeouts */ case Wait_Conn_Ack: case Wait_I_CEA: case Closing: case Wait_Returns: case Wait_Conn_Ack_Elect: touch_peer(p); sm_process(p,Timeout,0,1,0); break; /* inactivity detected */ case I_Open: case R_Open: if (p->waitingDWA){ p->waitingDWA = 0; if (p->state==I_Open) sm_process(p,I_Peer_Disc,0,1,p->I_sock); if (p->state==R_Open) sm_process(p,R_Peer_Disc,0,1,p->R_sock); LM_WARN("Inactivity on peer [%.*s] and no DWA, Closing peer...\n", p->fqdn.len, p->fqdn.s); } else { p->waitingDWA = 1; Snd_DWR(p); touch_peer(p); if (debug_heavy) { LM_DBG("Inactivity on peer [%.*s], sending DWR... - if we don't get a reply, the peer will be closed\n", p->fqdn.len, p->fqdn.s); } } break; /* ignored states */ /* unknown states */ default: LM_ERR("peer_timer(): Peer %.*s inactive in state %d\n", p->fqdn.len,p->fqdn.s,p->state); } } lock_release(p->lock); p = n; } lock_release(peer_list_lock); log_peer_list(); i = config->tc/5; if (i<=0) i=1; return i; }
/** * Diameter base protocol state-machine processing. * This function get's called for every event. It updates the states and can trigger * other events. * @param p - the peer for which the event happened * @param event - the event that happened * @param msg - if a Diameter message was received this is it, or NULL if not * @param peer_locked - if the peer lock is already aquired * @param sock - socket that this event happened on, or NULL if unrelated * @returns 1 on success, 0 on error. Also the peer states are updated */ int sm_process(peer *p,peer_event_t event,AAAMessage *msg,int peer_locked,int sock) { int result_code; peer_event_t next_event; int msg_received=0; if (!peer_locked) lock_get(p->lock); LOG(L_INFO,"DBG:sm_process(): Peer %.*s \tState %s \tEvent %s\n", p->fqdn.len,p->fqdn.s,dp_states[p->state],dp_events[event-101]); switch (p->state){ case Closed: switch (event){ case Start: p->state = Wait_Conn_Ack; next_event = I_Snd_Conn_Req(p); sm_process(p,next_event,0,1,p->I_sock); break; case R_Conn_CER: R_Accept(p,sock); result_code = Process_CER(p,msg); Snd_CEA(p,msg,result_code,p->R_sock); if (result_code>=2000 && result_code<3000) p->state = R_Open; else { R_Disc(p); p->state = Closed; } log_peer_list(L_INFO); break; case Stop: /* just ignore this state */ p->state = Closed; break; default: LOG(L_DBG,"DBG:sm_process(): In state %s invalid event %s\n", dp_states[p->state],dp_events[event-101]); goto error; } break; case Wait_Conn_Ack: switch(event){ case I_Rcv_Conn_Ack: I_Snd_CER(p); p->state = Wait_I_CEA; break; case I_Rcv_Conn_NAck: Cleanup(p,p->I_sock); p->state = Closed; break; /* Commented as not reachable*/ case R_Conn_CER: R_Accept(p,sock); result_code = Process_CER(p,msg); if (result_code>=2000 && result_code<3000) p->state = Wait_Conn_Ack_Elect; else { p->state = Wait_Conn_Ack; close(sock); } break; case Timeout: Error(p,p->I_sock); p->state = Closed; default: LOG(L_DBG,"DBG:sm_process(): In state %s invalid event %s\n", dp_states[p->state],dp_events[event-101]); goto error; } break; case Wait_I_CEA: switch(event){ case I_Rcv_CEA: result_code = Process_CEA(p,msg); if (result_code>=2000 && result_code<3000) p->state = I_Open; else { Cleanup(p,p->I_sock); p->state = Closed; } log_peer_list(L_INFO); break; case R_Conn_CER: R_Accept(p,sock); result_code = Process_CER(p,msg); p->state = Wait_Returns; if (Elect(p,msg)) sm_process(p,Win_Election,msg,1,sock); break; case I_Peer_Disc: I_Disc(p); p->state = Closed; break; case I_Rcv_Non_CEA: Error(p,p->I_sock); p->state = Closed; break; case Timeout: Error(p,p->I_sock); p->state = Closed; break; default: LOG(L_DBG,"DBG:sm_process(): In state %s invalid event %s\n", dp_states[p->state],dp_events[event-101]); goto error; } break; /* commented as not reachable */ case Wait_Conn_Ack_Elect: switch(event){ default: LOG(L_DBG,"DBG:sm_process(): In state %s invalid event %s\n", dp_states[p->state],dp_events[event-101]); goto error; } break; case Wait_Returns: switch(event){ case Win_Election: I_Disc(p); result_code = Process_CER(p,msg); Snd_CEA(p,msg,result_code,p->R_sock); if (result_code>=2000 && result_code<3000){ p->state = R_Open; }else{ R_Disc(p); p->state = Closed; } break; case I_Peer_Disc: I_Disc(p); result_code = Process_CER(p,msg); Snd_CEA(p,msg,result_code,p->R_sock); if (result_code>=2000 && result_code<3000){ p->state = R_Open; }else{ R_Disc(p); p->state = Closed; } break; case I_Rcv_CEA: R_Disc(p); result_code = Process_CEA(p,msg); if (result_code>=2000 && result_code<3000) p->state = I_Open; else { Cleanup(p,p->I_sock); p->state = Closed; } break; case R_Peer_Disc: R_Disc(p); p->state = Wait_I_CEA; break; case R_Conn_CER: R_Reject(p,p->R_sock); p->state = Wait_Returns; break; case Timeout: if (p->I_sock>=0) Error(p,p->I_sock); if (p->R_sock>=0) Error(p,p->R_sock); p->state = Closed; default: LOG(L_DBG,"DBG:sm_process(): In state %s invalid event %s\n", dp_states[p->state],dp_events[event-101]); goto error; } break; case R_Open: switch (event){ case Send_Message: Snd_Message(p,msg); p->state = R_Open; break; case R_Rcv_Message: // delayed processing until out of the critical zone //Rcv_Process(p,msg); msg_received = 1; p->state = R_Open; break; case R_Rcv_DWR: result_code = Process_DWR(p,msg); Snd_DWA(p,msg,result_code,p->R_sock); p->state = R_Open; break; case R_Rcv_DWA: Process_DWA(p,msg); p->state = R_Open; break; case R_Conn_CER: R_Reject(p,sock); p->state = R_Open; break; case Stop: Snd_DPR(p); p->state = Closing; break; case R_Rcv_DPR: Snd_DPA(p,msg,AAA_SUCCESS,p->R_sock); R_Disc(p); p->state = Closed; log_peer_list(L_INFO); break; case R_Peer_Disc: R_Disc(p); p->state = Closed; log_peer_list(L_INFO); break; case R_Rcv_CER: result_code = Process_CER(p,msg); Snd_CEA(p,msg,result_code,p->R_sock); if (result_code>=2000 && result_code<3000) p->state = R_Open; else { /*R_Disc(p);p.state = Closed;*/ p->state = R_Open; /* Or maybe I should disconnect it?*/ } break; case R_Rcv_CEA: result_code = Process_CEA(p,msg); if (result_code>=2000 && result_code<3000) p->state = R_Open; else { /*R_Disc(p);p.state = Closed;*/ p->state = R_Open; /* Or maybe I should disconnect it?*/ } log_peer_list(L_INFO); break; default: LOG(L_DBG,"DBG:sm_process(): In state %s invalid event %s\n", dp_states[p->state],dp_events[event-101]); goto error; } break; case I_Open: switch (event){ case Send_Message: Snd_Message(p,msg); p->state = I_Open; break; case I_Rcv_Message: // delayed processing until out of the critical zone //Rcv_Process(p,msg); msg_received = 1; p->state = I_Open; break; case I_Rcv_DWR: result_code = Process_DWR(p,msg); Snd_DWA(p,msg,result_code,p->I_sock); p->state =I_Open; break; case I_Rcv_DWA: Process_DWA(p,msg); p->state =I_Open; break; case R_Conn_CER: R_Reject(p,sock); p->state = I_Open; break; case Stop: Snd_DPR(p); p->state = Closing; break; case I_Rcv_DPR: Snd_DPA(p,msg,2001,p->I_sock); R_Disc(p); p->state = Closed; log_peer_list(L_INFO); break; case I_Peer_Disc: I_Disc(p); p->state = Closed; log_peer_list(L_INFO); break; case I_Rcv_CER: result_code = Process_CER(p,msg); Snd_CEA(p,msg,result_code,p->I_sock); if (result_code>=2000 && result_code<3000) p->state = I_Open; else { /*I_Disc(p);p.state = Closed;*/ p->state = I_Open; /* Or maybe I should disconnect it?*/ } break; case I_Rcv_CEA: result_code = Process_CEA(p,msg); if (result_code>=2000 && result_code<3000) p->state = I_Open; else { /*I_Disc(p);p.state = Closed;*/ p->state = I_Open; /* Or maybe I should disconnect it?*/ } break; default: LOG(L_DBG,"DBG:sm_process(): In state %s invalid event %s\n", dp_states[p->state],dp_events[event-101]); goto error; } break; case Closing: switch(event){ case I_Rcv_DPA: I_Disc(p); p->state = Closed; break; case R_Rcv_DPA: R_Disc(p); p->state = Closed; break; case Timeout: if (p->I_sock>=0) Error(p,p->I_sock); if (p->R_sock>=0) Error(p,p->R_sock); p->state = Closed; break; case I_Peer_Disc: I_Disc(p); p->state = Closed; break; case R_Peer_Disc: R_Disc(p); p->state = Closed; break; default: LOG(L_DBG,"DBG:sm_process(): In state %s invalid event %s\n", dp_states[p->state],dp_events[event-101]); goto error; } break; } if (!peer_locked) lock_release(p->lock); if (msg_received) Rcv_Process(p,msg); return 1; error: if (!peer_locked) lock_release(p->lock); 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; }
/** * 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); } } }