/** * Authorization Client State-Machine - Stateless * \Note - should be called with a lock on the session and will unlock it - do not use it after! * @param s * @param event * @param msg */ inline void auth_client_stateless_sm_process(cdp_session_t* s, int event, AAAMessage *msg) { cdp_auth_session_t *x; int rc; if (!s) return; x = &(s->u.auth); switch(x->state){ case AUTH_ST_IDLE: switch(event){ case AUTH_EV_SEND_REQ: x->state = AUTH_ST_PENDING; break; default: LOG(L_ERR,"ERR:auth_client_stateless_sm_process(): Received invalid event %d while in state %s!\n", event,auth_states[x->state]); } break; case AUTH_ST_PENDING: if (!is_req(msg)){ rc = get_result_code(msg); if (rc>=2000 && rc<3000 && get_auth_session_state(msg)==NO_STATE_MAINTAINED) event = AUTH_EV_RECV_ANS_SUCCESS; else event = AUTH_EV_RECV_ANS_UNSUCCESS; } switch(event){ case AUTH_EV_RECV_ANS_SUCCESS: x->state = AUTH_ST_OPEN; break; case AUTH_EV_RECV_ANS_UNSUCCESS: x->state = AUTH_ST_IDLE; break; default: LOG(L_ERR,"ERR:auth_client_stateless_sm_process(): Received invalid event %d while in state %s!\n", event,auth_states[x->state]); } break; case AUTH_ST_OPEN: switch(event){ case AUTH_EV_SESSION_TIMEOUT: x->state = AUTH_ST_IDLE; break; case AUTH_EV_SERVICE_TERMINATED: x->state = AUTH_ST_IDLE; break; default: LOG(L_ERR,"ERR:auth_client_stateless_sm_process(): Received invalid event %d while in state %s!\n", event,auth_states[x->state]); } break; default: LOG(L_ERR,"ERR:auth_client_stateless_sm_process(): Received event %d while in invalid state %d!\n", event,x->state); } if (s) AAASessionsUnlock(s->hash); }
/** * Looks for a CC Acc dession with a given id and returns it if found * \note Returns with a lock on AAASession->hash. Unlock when done working with the result * @returns the new AAASession or null on error */ AAASession* AAAGetCCAccSession(str id) { AAASession *x=cdp_get_session(id); if (x){ switch (x->type){ case ACCT_CC_CLIENT: return x; default: AAASessionsUnlock(x->hash); return 0; } } return 0; }
/** * Looks for an Auth session with a given id and returns it if found * \note Returns with a lock on AAASession->hash. Unlock when done working with the result * @returns the new AAASession or null on error */ AAASession* AAAGetAuthSession(str id) { AAASession *x=cdp_get_session(id); if (x){ switch (x->type){ case AUTH_CLIENT_STATEFULL: case AUTH_CLIENT_STATELESS: case AUTH_SERVER_STATEFULL: case AUTH_SERVER_STATELESS: return x; default: AAASessionsUnlock(x->hash); return 0; } } return 0; }
/** * Finds a session in the session hash table. * \note Returns with a lock on the sessions[x->hash].lock!!! * @param id - the id of the session * @returns the session if found or 0 if not */ cdp_session_t* cdp_get_session(str id) { unsigned int hash; cdp_session_t *x; if (!id.len) return 0; hash = get_str_hash(id,sessions_hash_size); LM_DBG("called get session with id %.*s and hash %u\n",id.len,id.s,hash); AAASessionsLock(hash); for(x = sessions[hash].head;x;x=x->next){ LM_DBG("looking for |%.*s| in |%.*s|\n",id.len,id.s,x->id.len,x->id.s); if (x->id.len == id.len && strncasecmp(x->id.s,id.s,id.len)==0) return x; } AAASessionsUnlock(hash); LM_DBG("no session found\n"); return 0; }
void cdp_sessions_log() { int hash; cdp_session_t *x; LM_DBG(ANSI_MAGENTA"------- CDP Sessions ----------------\n"ANSI_GREEN); for(hash=0;hash<sessions_hash_size;hash++){ AAASessionsLock(hash); for(x = sessions[hash].head;x;x=x->next) { LM_DBG(ANSI_GRAY" %3u. [%.*s] AppId [%d] Type [%d]\n", hash, x->id.len,x->id.s, x->application_id, x->type); switch (x->type){ case AUTH_CLIENT_STATEFULL: case AUTH_SERVER_STATEFULL: LM_DBG(ANSI_GRAY"\tAuth State [%d] Timeout [%d] Lifetime [%d] Grace [%d] Generic [%p]\n", x->u.auth.state, (int)(x->u.auth.timeout-time(0)), x->u.auth.lifetime?(int)(x->u.auth.lifetime-time(0)):-1, (int)(x->u.auth.grace_period), x->u.auth.generic_data); break; case ACCT_CC_CLIENT: LM_DBG(ANSI_GRAY"\tCCAcct State [%d] Charging Active [%c (%d)s] Reserved Units(valid=%ds) [%d] Generic [%p]\n", x->u.cc_acc.state, (x->u.cc_acc.charging_start_time&&x->u.cc_acc.state!=ACC_CC_ST_DISCON)?'Y':'N', x->u.cc_acc.charging_start_time?(int)((int)time(0) - (int)x->u.cc_acc.charging_start_time):-1, x->u.cc_acc.reserved_units?(int)((int)x->u.cc_acc.last_reservation_request_time + x->u.cc_acc.reserved_units_validity_time) - (int)time(0):-1, x->u.cc_acc.reserved_units, x->u.cc_acc.generic_data); break; default: break; } } AAASessionsUnlock(hash); } LM_DBG(ANSI_MAGENTA"-------------------------------------\n"ANSI_GREEN); }
/** * Removes and frees a session. * \note must be called with a lock on the x->hash and it will unlock on exit. Do not use x after calling this * * @param x - the session to remove */ void del_session(cdp_session_t *x) { unsigned int hash; if (!x) return; hash = x->hash; if (hash < 0 || hash >= sessions_hash_size) { LM_ERR("del_session: x->hash :%d out of range of sessions_hash_size: %d !\n",hash, sessions_hash_size); return; } if (sessions[x->hash].head == x) sessions[x->hash].head = x->next; else if (x->prev) x->prev->next = x->next; if (sessions[x->hash].tail == x) sessions[x->hash].tail = x->prev; else if (x->next) x->next->prev = x->prev; AAASessionsUnlock(hash); free_session(x); }
/** * Authorization Server State-Machine - Stateless * \Note - should be called with a lock on the session and will unlock it - do not use it after! * * @param auth * @param event * @param msg */ inline void auth_server_stateless_sm_process(cdp_session_t* s, int event, AAAMessage* msg) { /* empty - no state change, anyway */ /* cdp_auth_session_t *x; int rc; if (!s) return; x = &(s->u.auth); switch(x->state){ case AUTH_ST_IDLE: switch(event){ default: LM_ERR("auth_server_stateless_sm_process(): Received invalid event %d while in state %s!\n", event,auth_state[x->state]); } break; default: LM_ERR("auth_server_stateless_sm_process(): Received event %d while in invalid state %d!\n", event,x->state); } */ if (s) AAASessionsUnlock(s->hash); }
/** * Get the first peer that is connected from the list of routing entries. * @param r - the list of routing entries to look into * @returns - the peer or null if none connected */ peer* get_first_connected_route(cdp_session_t* cdp_session, routing_entry *r, int app_id, int vendor_id) { peer * peers[LB_MAX_PEERS]; int peer_count = 0; int prev_metric = 0; routing_entry *i; peer *p; int j; time_t least_recent_time; struct timespec time_spec; if (cdp_session) { /*try and find an already used peer for this session - sticky*/ if ((cdp_session->sticky_peer_fqdn.len > 0) && cdp_session->sticky_peer_fqdn.s) { //we have an old sticky peer. let's make sure it's up and connected before we use it. AAASessionsUnlock(cdp_session->hash); /*V1.1 - Don't attempt to hold two locks at same time */ p = get_peer_by_fqdn(&cdp_session->sticky_peer_fqdn); AAASessionsLock(cdp_session->hash); /*V1.1 - As we were...no call seems to pass cdp_session unlocked */ if (p && !p->disabled && (p->state == I_Open || p->state == R_Open) && peer_handles_application(p, app_id, vendor_id)) { p->last_selected = time(NULL); LM_DBG("Found a sticky peer [%.*s] for this session - re-using\n", p->fqdn.len, p->fqdn.s); return p; } } } for (i = r; i; i = i->next) { if (peer_count >= LB_MAX_PEERS) break; p = get_peer_by_fqdn(&(i->fqdn)); if (!p) LM_DBG("The peer %.*s does not seem to be connected or configured\n", i->fqdn.len, i->fqdn.s); else LM_DBG("The peer %.*s state is %s\n", i->fqdn.len, i->fqdn.s, (p->state == I_Open || p->state == R_Open) ? "opened" : "closed"); if (p && !p->disabled && (p->state == I_Open || p->state == R_Open) && peer_handles_application(p, app_id, vendor_id)) { LM_DBG("The peer %.*s matches - will forward there\n", i->fqdn.len, i->fqdn.s); if (peer_count != 0) {//check the metric if (i->metric != prev_metric) break; //metric must be the same peers[peer_count++] = p; } else {//we're first prev_metric = i->metric; peers[peer_count++] = p; } } } if (peer_count == 0) { return 0; } least_recent_time = peers[0]->last_selected; LM_DBG("peer [%.*s] was last used @ %ld\n", peers[0]->fqdn.len, peers[0]->fqdn.s, peers[0]->last_selected); p = peers[0]; for (j = 1; j < peer_count; j++) { LM_DBG("Peer [%.*s] was last used at [%ld]\n", peers[j]->fqdn.len, peers[j]->fqdn.s, peers[j]->last_selected); if (peers[j]->last_selected < least_recent_time) { least_recent_time = peers[j]->last_selected; p = peers[j]; } } ser_clock_gettime(&time_spec); p->last_selected = (time_spec.tv_sec*1000000) + round(time_spec.tv_nsec / 1.0e3); // Convert nanoseconds to microseconds LM_DBG("chosen peer [%.*s]\n", p->fqdn.len, p->fqdn.s); if (cdp_session) { if (cdp_session->sticky_peer_fqdn_buflen <= p->fqdn.len) { LM_DBG("not enough storage for sticky peer - allocating more\n"); if (cdp_session->sticky_peer_fqdn.s) shm_free(cdp_session->sticky_peer_fqdn.s); cdp_session->sticky_peer_fqdn.s = (char*) shm_malloc(p->fqdn.len + 1); if (!cdp_session->sticky_peer_fqdn.s) { LM_ERR("no more shm memory\n"); return 0; } cdp_session->sticky_peer_fqdn_buflen = p->fqdn.len + 1; memset(cdp_session->sticky_peer_fqdn.s, 0, p->fqdn.len + 1); } cdp_session->sticky_peer_fqdn.len = p->fqdn.len; memcpy(cdp_session->sticky_peer_fqdn.s, p->fqdn.s, p->fqdn.len); } return p; }
/** * Authorization Server State-Machine - Statefull * \Note - should be called with a lock on the session and will unlock it - do not use it after! * @param s * @param event * @param msg */ inline void auth_server_statefull_sm_process(cdp_session_t* s, int event, AAAMessage* msg) { cdp_auth_session_t *x; if (!s) return; x = &(s->u.auth); if (s->cb) (s->cb)(event, s); LM_DBG("after callback for event %i\n", event); switch (x->state) { case AUTH_ST_IDLE: switch (event) { case AUTH_EV_RECV_STR: break; case AUTH_EV_RECV_REQ: // The RequestHandler will generate a Send event for the answer // and we will only then now if the user is authorised or not // if its not authorised it will move back to idle and cleanup the session // so no big deal // but this is not the Diameter RFC... x->state = AUTH_ST_OPEN; // execute the cb here because we won't have a chance later if (s->cb) (s->cb)(AUTH_EV_SESSION_MODIFIED, s); // Don't unlock the session hash table because the session is returned to the user // This can only be called from the AAACreateServerAuthSession()! s = 0; break; case AUTH_EV_SEND_STA: x->state = AUTH_ST_IDLE; cdp_session_cleanup(s, msg); s = 0; break; /* Just in case we have some lost sessions */ case AUTH_EV_SESSION_TIMEOUT: case AUTH_EV_SESSION_GRACE_TIMEOUT: cdp_session_cleanup(s, msg); s = 0; break; default: LM_ERR("auth_client_statefull_sm_process(): Received invalid event %d while in state %s!\n", event, auth_states[x->state]); } break; case AUTH_ST_OPEN: if (event == AUTH_EV_SEND_ANS && msg && !is_req(msg)) { int rc = get_result_code(msg); if (rc >= 2000 && rc < 3000) event = AUTH_EV_SEND_ANS_SUCCESS; else event = AUTH_EV_SEND_ANS_UNSUCCESS; } switch (event) { case AUTH_EV_RECV_STR: break; case AUTH_EV_SEND_ANS_SUCCESS: x->state = AUTH_ST_OPEN; update_auth_session_timers(x, msg); add_auth_session_timers(x, msg); break; case AUTH_EV_SEND_ANS_UNSUCCESS: x->state = AUTH_ST_IDLE; cdp_session_cleanup(s, msg); s = 0; break; case AUTH_EV_SEND_ASR: x->state = AUTH_ST_DISCON; break; case AUTH_EV_SESSION_TIMEOUT: case AUTH_EV_SESSION_GRACE_TIMEOUT: x->state = AUTH_ST_IDLE; LM_DBG("before session cleanup\n"); cdp_session_cleanup(s, msg); s = 0; break; case AUTH_EV_SEND_STA: LM_ERR("SENDING STA!!!\n"); x->state = AUTH_ST_IDLE; cdp_session_cleanup(s, msg); s = 0; break; default: LM_ERR("auth_client_statefull_sm_process(): Received invalid event %d while in state %s!\n", event, auth_states[x->state]); } break; case AUTH_ST_DISCON: switch (event) { case AUTH_EV_RECV_STR: break; case AUTH_EV_RECV_ASA: case AUTH_EV_RECV_ASA_SUCCESS: x->state = AUTH_ST_IDLE; //cdp_session_cleanup(s,msg); break; case AUTH_EV_RECV_ASA_UNSUCCESS: Send_ASR(s, msg); // how many times will this be done? x->state = AUTH_ST_DISCON; break; case AUTH_EV_SEND_STA: x->state = AUTH_ST_IDLE; cdp_session_cleanup(s, msg); s = 0; break; default: LM_ERR("auth_client_statefull_sm_process(): Received invalid event %d while in state %s!\n", event, auth_states[x->state]); } break; default: LM_ERR("auth_client_statefull_sm_process(): Received event %d while in invalid state %d!\n", event, x->state); } if (s) { if (s->cb) (s->cb)(AUTH_EV_SESSION_MODIFIED, s); AAASessionsUnlock(s->hash); } }
/** * stateful client state machine * \Note - should be called with a lock on the session and will unlock it - do not use it after! * @param auth - AAAAuthSession which uses this state machine * @param ev - Event * @param msg - AAAMessage * @returns 0 if msg should be given to the upper layer 1 if not */ inline int auth_client_statefull_sm_process(cdp_session_t* s, int event, AAAMessage* msg) { cdp_auth_session_t *x; int rc; int rv = 0; //return value if (!s) { switch (event) { case AUTH_EV_RECV_ASR: Send_ASA(0, msg); break; default: LM_ERR("auth_client_statefull_sm_process(): Received invalid event %d with no session!\n", event); } return rv; } x = &(s->u.auth); if (s->cb) (s->cb)(event, s); LM_INFO("after callback of event %i\n", event); //if (x && x->state && msg) LM_ERR("auth_client_statefull_sm_process [event %i] [state %i] endtoend %u hopbyhop %u\n",event,x->state,msg->endtoendId,msg->hopbyhopId); switch (x->state) { case AUTH_ST_IDLE: switch (event) { case AUTH_EV_SEND_REQ: s->application_id = msg->applicationId; s->u.auth.state = AUTH_ST_PENDING; update_auth_session_timers(x, msg); add_auth_session_timers(x, msg); //Richard add this add: add destination realm to cdp session //use msg origin realm as the destination realm //we do this here as this is were the state changes to open //Where must we free this? s->dest_realm.s = (char*) shm_malloc(msg->dest_realm->data.len); memcpy(s->dest_realm.s, msg->dest_realm->data.s, msg->dest_realm->data.len); s->dest_realm.len = msg->dest_realm->data.len; //LM_INFO("state machine: i was in idle and i am going to pending\n"); break; default: LM_ERR("auth_client_statefull_sm_process(): Received invalid event %d while in state %s!(data %p)\n", event, auth_states[x->state], x->generic_data); } break; case AUTH_ST_PENDING: if (event == AUTH_EV_RECV_ANS && msg && !is_req(msg)) { rc = get_result_code(msg); if (rc >= 2000 && rc < 3000 && get_auth_session_state(msg) == STATE_MAINTAINED) event = AUTH_EV_RECV_ANS_SUCCESS; else event = AUTH_EV_RECV_ANS_UNSUCCESS; } switch (event) { case AUTH_EV_RECV_ANS_SUCCESS: x->state = AUTH_ST_OPEN; update_auth_session_timers(x, msg); //LM_INFO("state machine: i was in pending and i am going to open\n"); break; case AUTH_EV_RECV_ANS_UNSUCCESS: LM_DBG("In state AUTH_ST_PENDING and received AUTH_EV_RECV_ANS_UNSUCCESS - nothing to do but clean up session\n"); case AUTH_EV_SESSION_TIMEOUT: case AUTH_EV_SERVICE_TERMINATED: case AUTH_EV_SESSION_GRACE_TIMEOUT: cdp_session_cleanup(s, NULL); s = 0; break; default: LM_ERR("auth_client_stateless_sm_process(): Received invalid event %d while in state %s!\n", event, auth_states[x->state]); } break; case AUTH_ST_OPEN: if (event == AUTH_EV_RECV_ANS && msg && !is_req(msg)) { rc = get_result_code(msg); if (rc >= 2000 && rc < 3000 && get_auth_session_state(msg) == STATE_MAINTAINED) event = AUTH_EV_RECV_ANS_SUCCESS; else event = AUTH_EV_RECV_ANS_UNSUCCESS; } switch (event) { case AUTH_EV_SEND_REQ: // if the request is STR i should move to Discon .. // this is not in the state machine but I (Alberto Diez) need it if (msg->commandCode == IMS_STR) s->u.auth.state = AUTH_ST_DISCON; else { s->u.auth.state = AUTH_ST_OPEN; add_auth_session_timers(x, msg); } break; case AUTH_EV_RECV_ANS_SUCCESS: x->state = AUTH_ST_OPEN; update_auth_session_timers(x, msg); //LM_INFO("state machine: i was in open and i am going to open\n"); break; case AUTH_EV_RECV_ANS_UNSUCCESS: x->state = AUTH_ST_DISCON; //LM_INFO("state machine: i was in open and i am going to discon\n"); break; case AUTH_EV_SESSION_TIMEOUT: case AUTH_EV_SERVICE_TERMINATED: case AUTH_EV_SESSION_GRACE_TIMEOUT: x->state = AUTH_ST_DISCON; //LM_INFO("state machine: i was in open and i am going to discon\n"); Send_STR(s, msg); break; case AUTH_EV_SEND_ASA_SUCCESS: x->state = AUTH_ST_DISCON; //LM_INFO("state machine: i was in open and i am going to discon\n"); Send_STR(s, msg); break; case AUTH_EV_SEND_ASA_UNSUCCESS: x->state = AUTH_ST_OPEN; update_auth_session_timers(x, msg); //LM_INFO("state machine: i was in open and i am going to open\n"); break; case AUTH_EV_RECV_ASR: // two cases , client will comply or will not // our client is very nice and always complys.. because // our brain is in the PCRF... if he says to do this , we do it // Alberto Diez , (again this is not Diameter RFC) x->state = AUTH_ST_DISCON; Send_ASA(s, msg); Send_STR(s, msg); break; default: LM_ERR("auth_client_statefull_sm_process(): Received invalid event %d while in state %s!\n", event, auth_states[x->state]); } break; case AUTH_ST_DISCON: switch (event) { case AUTH_EV_RECV_ASR: x->state = AUTH_ST_DISCON; //LM_INFO("state machine: i was in discon and i am going to discon\n"); Send_ASA(s, msg); break; // Just added this because it might happen if the other peer doesnt // send a valid STA, then the session stays open forever... // We dont accept that... we have lifetime+grace_period for that // This is not in the Diameter RFC ... case AUTH_EV_SESSION_TIMEOUT: case AUTH_EV_SESSION_GRACE_TIMEOUT: // thats the addition case AUTH_EV_RECV_STA: x->state = AUTH_ST_IDLE; LM_INFO("state machine: AUTH_EV_RECV_STA about to clean up\n"); if (msg) AAAFreeMessage(&msg); // if might be needed in frequency // If I register a ResponseHandler then i Free the STA there not here.. // but i dont have interest in that now.. cdp_session_cleanup(s, NULL); s = 0; rv = 1; break; default: LM_ERR("auth_client_statefull_sm_process(): Received invalid event %d while in state %s!\n", event, auth_states[x->state]); } break; default: LM_ERR("auth_client_statefull_sm_process(): Received event %d while in invalid state %d!\n", event, x->state); } if (s) { if (s->cb) (s->cb)(AUTH_EV_SESSION_MODIFIED, s); AAASessionsUnlock(s->hash); } return rv; }
/** * Processes an incoming message. * This actually just puts the message into a message queue. One worker will pick-it-up * and do the actual processing. * \note Must be called with a lock on the peer. * @param p - peer received from * @param msg - the message received */ void Rcv_Process(peer *p, AAAMessage *msg) { AAASession *session=0; int nput=0; if (msg->sessionId) session = cdp_get_session(msg->sessionId->data); if (session){ switch (session->type){ case AUTH_CLIENT_STATEFULL: if (is_req(msg)){ if (msg->commandCode==IMS_ASR) auth_client_statefull_sm_process(session,AUTH_EV_RECV_ASR,msg); else auth_client_statefull_sm_process(session,AUTH_EV_RECV_REQ,msg); session = 0; }else { if (msg->commandCode==IMS_STA) nput=auth_client_statefull_sm_process(session,AUTH_EV_RECV_STA,msg); else auth_client_statefull_sm_process(session,AUTH_EV_RECV_ANS,msg); session = 0; } break; case AUTH_SERVER_STATEFULL: if (is_req(msg)) { if (msg->commandCode==IMS_STR) { auth_server_statefull_sm_process(session,AUTH_EV_RECV_STR,msg); } else { auth_server_statefull_sm_process(session,AUTH_EV_RECV_REQ,msg); } session = 0; }else{ if (msg->commandCode==IMS_ASA) auth_server_statefull_sm_process(session,AUTH_EV_RECV_ASA,msg); else auth_server_statefull_sm_process(session,AUTH_EV_RECV_ANS,msg); session = 0; } break; default: AAASessionsUnlock(session->hash); session =0; break; } }else{ if (msg->sessionId){ if (msg->commandCode == IMS_ASR) auth_client_statefull_sm_process(0,AUTH_EV_RECV_ASR,msg); } } if (!nput && !put_task(p,msg)){ LM_ERR("Rcv_Process(): Queue refused task\n"); if (msg) AAAFreeMessage(&msg); } //if (msg) LM_ERR("Rcv_Process(): task added to queue command %d, flags %#1x endtoend %u hopbyhop %u\n",msg->commandCode,msg->flags,msg->endtoendId,msg->hopbyhopId); // AAAPrintMessage(msg); }
/** * Sends a message to the peer. * \note Must be called with a lock on the peer. * @param p - peer to send to * @param msg - message to send */ void Snd_Message(peer *p, AAAMessage *msg) { AAASession *session=0; int rcode; int send_message_before_session_sm=0; LM_DBG("Snd_Message called to peer [%.*s] for %s with code %d \n", p->fqdn.len,p->fqdn.s,is_req(msg)?"request":"response",msg->commandCode); touch_peer(p); if (msg->sessionId) session = cdp_get_session(msg->sessionId->data); if (session){ LM_DBG("There is a session of type %d\n",session->type); switch (session->type){ case AUTH_CLIENT_STATEFULL: if (is_req(msg)) { auth_client_statefull_sm_process(session,AUTH_EV_SEND_REQ,msg); session = 0; } else { if (msg->commandCode == IMS_ASA){ if (!msg->res_code){ msg->res_code = AAAFindMatchingAVP(msg,0,AVP_Result_Code,0,0); } if (!msg->res_code) { auth_client_statefull_sm_process(session,AUTH_EV_SEND_ASA_UNSUCCESS,msg); session = 0; } else { rcode = get_4bytes(msg->res_code->data.s); if (rcode>=2000 && rcode<3000) { peer_send_msg(p,msg); send_message_before_session_sm=1; auth_client_statefull_sm_process(session,AUTH_EV_SEND_ASA_SUCCESS,msg); session = 0; } else { auth_client_statefull_sm_process(session,AUTH_EV_SEND_ASA_UNSUCCESS,msg); session = 0; } } }else { auth_client_statefull_sm_process(session,AUTH_EV_SEND_ANS,msg); session = 0; } } break; case AUTH_SERVER_STATEFULL: LM_DBG("this message is matched here to see what request or reply it is\n"); if (is_req(msg)) { if (msg->commandCode== IMS_ASR) { LM_DBG("ASR\n"); auth_server_statefull_sm_process(session,AUTH_EV_SEND_ASR,msg); session = 0; } else { //would be a RAR but ok! LM_DBG("other request\n"); auth_server_statefull_sm_process(session,AUTH_EV_SEND_REQ,msg); session = 0; } } else { if (msg->commandCode == IMS_STR) { LM_DBG("STA\n"); auth_server_statefull_sm_process(session,AUTH_EV_SEND_STA,msg); session = 0; } else { LM_DBG("other reply\n"); auth_server_statefull_sm_process(session,AUTH_EV_SEND_ANS,msg); session = 0; } } break; default: break; } if (session) AAASessionsUnlock(session->hash); } if (!send_message_before_session_sm) peer_send_msg(p,msg); }
/** * Processes an incoming message. * This actually just puts the message into a message queue. One worker will pick-it-up * and do the actual processing. * \note Must be called with a lock on the peer. * @param p - peer received from * @param msg - the message received */ void Rcv_Process(peer *p, AAAMessage *msg) { AAASession *session=0; int nput=0; if (msg->sessionId) session = cdp_get_session(msg->sessionId->data); if (session){ switch (session->type){ case ACCT_CC_CLIENT: if (is_req(msg)){ LM_WARN("unhandled receive request on Credit Control Acct session\n"); AAASessionsUnlock(session->hash); //must be called because we dont call state machine here session = 0; //we dont call SM here so we mustnt set to 0 } else { cc_acc_client_stateful_sm_process(session, ACC_CC_EV_RECV_ANS, msg); session = 0; } break; case AUTH_CLIENT_STATEFULL: if (is_req(msg)){ if (msg->commandCode==IMS_ASR) auth_client_statefull_sm_process(session,AUTH_EV_RECV_ASR,msg); else auth_client_statefull_sm_process(session,AUTH_EV_RECV_REQ,msg); session = 0; }else { if (msg->commandCode==IMS_STA) nput=auth_client_statefull_sm_process(session,AUTH_EV_RECV_STA,msg); else auth_client_statefull_sm_process(session,AUTH_EV_RECV_ANS,msg); session = 0; } break; case AUTH_SERVER_STATEFULL: if (is_req(msg)) { if (msg->commandCode==IMS_STR) { auth_server_statefull_sm_process(session,AUTH_EV_RECV_STR,msg); } else { auth_server_statefull_sm_process(session,AUTH_EV_RECV_REQ,msg); } session = 0; }else{ if (msg->commandCode==IMS_ASA) auth_server_statefull_sm_process(session,AUTH_EV_RECV_ASA,msg); else auth_server_statefull_sm_process(session,AUTH_EV_RECV_ANS,msg); session = 0; } break; default: AAASessionsUnlock(session->hash); session =0; break; } }else{ if (msg->sessionId){ if (msg->commandCode == IMS_ASR) auth_client_statefull_sm_process(0,AUTH_EV_RECV_ASR,msg); } } if (!nput && !put_task(p,msg)){ LM_ERR("Rcv_Process(): Queue refused task\n"); if (msg) AAAFreeMessage(&msg); } //if (msg) LM_ERR("Rcv_Process(): task added to queue command %d, flags %#1x endtoend %u hopbyhop %u\n",msg->commandCode,msg->flags,msg->endtoendId,msg->hopbyhopId); // AAAPrintMessage(msg); }
/** * stateful client state machine * \Note - should be called with a lock on the session and will unlock it - do not use it after! * @param cc_acc - AAACCAccSession which uses this state machine * @param ev - Event * @param msg - AAAMessage * @returns 0 if msg should be given to the upper layer 1 if not */ inline int cc_acc_client_stateful_sm_process(cdp_session_t* s, int event, AAAMessage* msg) { cdp_cc_acc_session_t* x; int ret = 0; int rc; //return code for responses int record_type; x = &(s->u.cc_acc); LM_DBG("cc_acc_client_stateful_sm_process: processing CC App in state [%d] and event [%d]\n", x->state, event); //first run session callbacks if (s->cb) (s->cb)(event, s); LM_DBG("finished callback of event %i\n", event); switch (x->state) { case ACC_CC_ST_IDLE: switch (event) { case ACC_CC_EV_SEND_REQ: //were sending a message - CCR //assert this is an initial request. we can't move from IDLE with anything else record_type = get_accounting_record_type(msg); switch (record_type) { case 2 /*START RECORD*/: LM_DBG("sending CCR START record on session\n"); s->application_id = msg->applicationId; s->u.cc_acc.state = ACC_CC_ST_PENDING_I; //update our reservation and its timers... if they exist in CCR update_gsu_request_timers(x, msg); break; default: LM_ERR("Sending CCR with no/incorrect accounting record type AVP. In state IDLE\n"); break; } break; default: LM_ERR("Recevied unknown event [%d] in state [%d]\n", event, x->state); break; } break; case ACC_CC_ST_OPEN: switch (event) { case ACC_CC_EV_SEND_REQ: //were sending a message - CCR //make sure it is either an update or a termination. record_type = get_accounting_record_type(msg); switch (record_type) { case 3 /*UPDATE RECORD*/: LM_DBG("sending CCR UPDATE record on session\n"); s->u.cc_acc.state = ACC_CC_ST_PENDING_U; //update our reservation and its timers... update_gsu_request_timers(x, msg); break; case 4: /*TERMINATE RECORD*/ LM_DBG("sending CCR TERMINATE record on session\n"); s->u.cc_acc.state = ACC_CC_ST_PENDING_T; //update our reservation and its timers... update_gsu_request_timers(x, msg); break; default: LM_ERR("asked to send CCR with no/incorrect accounting record type AVP. In state IDLE\n"); break; } break; case ACC_CC_EV_RSVN_WARNING: //nothing we can do here, we have sent callback, client needs to send CCR Update LM_DBG("Reservation close to expiring\n"); break; default: LM_ERR("Received unknown event [%d] in state [%d]\n", event, x->state); break; } break; case ACC_CC_ST_PENDING_I: if (event == ACC_CC_EV_RECV_ANS && msg && !is_req(msg)) { rc = get_result_code(msg); if (rc >= 2000 && rc < 3000) { event = ACC_CC_EV_RECV_ANS_SUCCESS; } else { event = ACC_CC_EV_RECV_ANS_UNSUCCESS; } } switch (event) { case ACC_CC_EV_RECV_ANS_SUCCESS: x->state = ACC_CC_ST_OPEN; LM_DBG("received success response for CCR START\n"); update_gsu_response_timers(x, msg); break; case ACC_CC_EV_RECV_ANS_UNSUCCESS: //TODO: grant/terminate service callbacks to callback clients LM_ERR("failed answer on CCR START\n"); x->state = ACC_CC_ST_DISCON; break; default: LM_ERR("Received unknown event [%d] in state [%d]\n", event, x->state); break; } break; case ACC_CC_ST_PENDING_T: if (event == ACC_CC_EV_RECV_ANS && msg && !is_req(msg)) { rc = get_result_code(msg); if (rc >= 2000 && rc < 3000) { event = ACC_CC_EV_RECV_ANS_SUCCESS; } else { event = ACC_CC_EV_RECV_ANS_UNSUCCESS; } } switch (event) { case ACC_CC_EV_RECV_ANS_SUCCESS: x->state = ACC_CC_ST_DISCON; // update_gsu_response_timers(x, msg); case ACC_CC_EV_RECV_ANS_UNSUCCESS: x->state = ACC_CC_ST_DISCON; default: LM_DBG("Received event [%d] in state [%d] - cleaning up session regardless\n", event, x->state); //have to leave session alone because our client app still has to be given this msg x->discon_time = time(0); // if (msg) AAAFreeMessage(&msg); // cdp_session_cleanup(s, NULL); // s = 0; } break; case ACC_CC_ST_PENDING_U: if (event == ACC_CC_EV_RECV_ANS && msg && !is_req(msg)) { rc = get_result_code(msg); if (rc >= 2000 && rc < 3000) { event = ACC_CC_EV_RECV_ANS_SUCCESS; } else { event = ACC_CC_EV_RECV_ANS_UNSUCCESS; } } switch (event) { case ACC_CC_EV_RECV_ANS_SUCCESS: x->state = ACC_CC_ST_OPEN; LM_DBG("success CCA for UPDATE\n"); update_gsu_response_timers(x, msg); break; case ACC_CC_EV_RECV_ANS_UNSUCCESS: //TODO: check whether we grant or terminate service to callback clients x->state = ACC_CC_ST_DISCON; LM_ERR("update failed... going back to IDLE/DISCON\n"); break; default: LM_ERR("Received unknown event [%d] in state [%d]\n", event, x->state); break; } break; case ACC_CC_ST_DISCON: switch (event) { case ACC_CC_EV_SESSION_STALE: LM_DBG("stale session about to be cleared\n"); cdp_session_cleanup(s, msg); s = 0; break; default: LM_ERR("Received unknown event [%d] in state [%d]\n", event, x->state); break; } break; } if (s) { AAASessionsUnlock(s->hash); } return ret; }
int cdp_sessions_timer(time_t now, void* ptr) { int hash; cdp_session_t *x,*n; for(hash=0;hash<sessions_hash_size;hash++){ AAASessionsLock(hash); for(x = sessions[hash].head;x;x=n) { n = x->next; switch (x->type){ case ACCT_CC_CLIENT: if (x->u.cc_acc.type == ACC_CC_TYPE_SESSION) { //check for old, stale sessions, we need to do something more elegant //here to ensure that if a CCR start record is sent and the client never sends anything //else that we catch it and clean up the session from within CDP, calling all callbacks, etc if ((time(0) > (x->u.cc_acc.discon_time + GRACE_DISCON_TIMEOUT)) && (x->u.cc_acc.state==ACC_CC_ST_DISCON)) { cc_acc_client_stateful_sm_process(x, ACC_CC_EV_SESSION_STALE, 0); } //check reservation timers - again here we are assuming CC-Time applications int last_res_timestamp = x->u.cc_acc.last_reservation_request_time; int res_valid_for = x->u.cc_acc.reserved_units_validity_time; int last_reservation = x->u.cc_acc.reserved_units; int buffer_time = 15; //15 seconds - TODO: add as config parameter //we should check for reservation expiries if the state is open if(x->u.cc_acc.state==ACC_CC_ST_OPEN){ if (last_res_timestamp) { //we have obv already started reservations if ((last_res_timestamp + res_valid_for) < (time(0) + last_reservation + buffer_time)) { LM_DBG("reservation about to expire, sending callback\n"); cc_acc_client_stateful_sm_process(x, ACC_CC_EV_RSVN_WARNING, 0); } } } /* TODO: if reservation has expired we need to tear down the session. Ideally * the client application (module) should do this but for completeness we should * put a failsafe here too. */ } break; case AUTH_CLIENT_STATEFULL: if (x->u.auth.timeout>=0 && x->u.auth.timeout<=now){ //Session timeout LM_CRIT("session TIMEOUT\n"); auth_client_statefull_sm_process(x,AUTH_EV_SESSION_TIMEOUT,0); } else if (x->u.auth.lifetime>0 && x->u.auth.lifetime+x->u.auth.grace_period<=now){ //lifetime + grace timeout LM_CRIT("lifetime+grace TIMEOUT\n"); auth_client_statefull_sm_process(x,AUTH_EV_SESSION_GRACE_TIMEOUT,0); }else if (x->u.auth.lifetime>0 && x->u.auth.lifetime<=now){ //lifetime timeout LM_CRIT("lifetime+grace TIMEOUT\n"); auth_client_statefull_sm_process(x,AUTH_EV_SESSION_LIFETIME_TIMEOUT,0); } break; case AUTH_SERVER_STATEFULL: if (x->u.auth.timeout>=0 && x->u.auth.timeout<=now){ //Session timeout LM_CRIT("session TIMEOUT\n"); auth_server_statefull_sm_process(x,AUTH_EV_SESSION_TIMEOUT,0); }else if (x->u.auth.lifetime>0 && x->u.auth.lifetime+x->u.auth.grace_period<=now){ //lifetime + grace timeout LM_CRIT("lifetime+grace TIMEOUT\n"); auth_server_statefull_sm_process(x,AUTH_EV_SESSION_GRACE_TIMEOUT,0); }else if (x->u.auth.lifetime>0 && x->u.auth.lifetime<=now){ //lifetime timeout LM_CRIT("lifetime+grace TIMEOUT\n"); auth_server_statefull_sm_process(x,AUTH_EV_SESSION_LIFETIME_TIMEOUT,0); } break; default: break; } } AAASessionsUnlock(hash); } if (now%5==0)cdp_sessions_log(); return 1; }