/* Revoke the suspension of the SIP request, i.e. * cancel the fr timer of the blind uac. * This function can be called when something fails * after t_suspend() has already been executed in the same * process, and it turns out that the transaction should * not have been suspended. * * Return value: * 0 - success * <0 - failure */ int t_cancel_suspend(unsigned int hash_index, unsigned int label) { struct cell *t; int branch; t = get_t(); if (!t || t == T_UNDEFINED) { LOG(L_ERR, "ERROR: t_revoke_suspend: " \ "no active transaction\n"); return -1; } /* Only to double-check the IDs */ if ((t->hash_index != hash_index) || (t->label != label) ) { LOG(L_ERR, "ERROR: t_revoke_suspend: " \ "transaction id mismatch\n"); return -1; } if(t->async_backup.backup_route != TM_ONREPLY_ROUTE){ /* The transaction does not need to be locked because this * function is either executed from the original route block * or from failure route which already locks */ reset_kr(); /* the blind UAC of t_suspend has set kr */ /* Try to find the blind UAC, and cancel its fr timer. * We assume that the last blind uac called this function. */ for ( branch = t->nr_of_outgoings-1; branch >= 0 && t->uac[branch].request.buffer; branch--); if (branch >= 0) { stop_rb_timers(&t->uac[branch].request); /* Set last_received to something >= 200, * the actual value does not matter, the branch * will never be picked up for response forwarding. * If last_received is lower than 200, * then the branch may tried to be cancelled later, * for example when t_reply() is called from * a failure rute => deadlock, because both * of them need the reply lock to be held. */ t->uac[branch].last_received=500; } else { /* Not a huge problem, fr timer will fire, but CANCEL will not be sent. last_received will be set to 408. */ return -1; } }else{ branch = t->async_backup.backup_branch; LOG(L_DBG,"DEBUG: t_cancel_suspend_reply: This is a cancel suspend for a response\n"); t->uac[branch].reply->msg_flags &= ~FL_RPL_SUSPENDED; if (t->uas.request) t->uas.request->msg_flags&= ~FL_RPL_SUSPENDED; } return 0; }
static int script_init( struct sip_msg *foo, void *bar) { /* we primarily reset all private memory here to make sure * private values left over from previous message will * not be used again */ set_t(T_UNDEFINED); reset_cancelled_t(); reset_e2eack_t(); /* reset the kr status */ reset_kr(); /* reset the static holders for T routes */ t_on_negative( 0 ); t_on_reply(0); t_on_branch(0); return 1; }
static int script_init( struct sip_msg *foo, void *bar) { /* we primarily reset all private memory here to make sure * private values left over from previous message will * not be used again */ set_t(T_UNDEFINED); reset_cancelled_t(); reset_e2eack_t(); fr_timeout = timer_id2timeout[FR_TIMER_LIST]; fr_inv_timeout = timer_id2timeout[FR_INV_TIMER_LIST]; /* reset the kill reason status */ reset_kr(); /* reset the static holders for T routes */ t_on_negative( 0 ); t_on_reply(0); t_on_branch(0); return SCB_RUN_ALL; }
/* This is the neurological point of reply processing -- called * from within a REPLY_LOCK, t_should_relay_response decides * how a reply shall be processed and how transaction state is * affected. * * Checks if the new reply (with new_code status) should be sent or not * based on the current * transaction status. * Returns - branch number (0,1,...) which should be relayed * -1 if nothing to be relayed */ static enum rps t_should_relay_response( struct cell *Trans , int new_code, int branch , int *should_store, int *should_relay, branch_bm_t *cancel_bitmap, struct sip_msg *reply ) { int branch_cnt; int picked_code; int inv_through; int do_cancel; /* note: this code never lets replies to CANCEL go through; we generate always a local 200 for CANCEL; 200s are not relayed because it's not an INVITE transaction; >= 300 are not relayed because 200 was already sent out */ LM_DBG("T_code=%d, new_code=%d\n", Trans->uas.status,new_code); inv_through=new_code>=200 && new_code<300 && is_invite(Trans); /* if final response sent out, allow only INVITE 2xx */ if ( Trans->uas.status >= 200 ) { if (inv_through) { LM_DBG("200 OK for INVITE after final sent\n"); *should_store=0; Trans->uac[branch].last_received=new_code; *should_relay=branch; return RPS_PUSHED_AFTER_COMPLETION; } if ( is_hopbyhop_cancel(Trans) && new_code>=200) { *should_store=0; *should_relay=-1; picked_branch=-1; return RPS_COMPLETED; } /* except the exception above, too late messages will be discarded */ goto discard; } /* if final response received at this branch, allow only INVITE 2xx */ if (Trans->uac[branch].last_received>=200 && !(inv_through && Trans->uac[branch].last_received<300)) { #ifdef EXTRA_DEBUG /* don't report on retransmissions */ if (Trans->uac[branch].last_received==new_code) { LM_DBG("final reply retransmission\n"); } else /* if you FR-timed-out, faked a local 408 and 487 came, don't * report on it either */ if (Trans->uac[branch].last_received==408 && new_code==487) { LM_DBG("487 reply came for a timed-out branch\n"); } else { /* this looks however how a very strange status rewrite attempt; * report on it */ LM_DBG("status rewrite by UAS: stored: %d, received: %d\n", Trans->uac[branch].last_received, new_code ); } #endif goto discard; } /* no final response sent yet */ /* negative replies subject to fork picking */ if (new_code >=300 ) { Trans->uac[branch].last_received=new_code; /* also append the current reply to the transaction to * make it available in failure routes - a kind of "fake" * save of the final reply per branch */ Trans->uac[branch].reply = reply; if (new_code>=600 && !disable_6xx_block) { /* this is a winner and close all branches */ which_cancel( Trans, cancel_bitmap ); picked_branch=branch; /* no more new branches should be added to this transaction */ Trans->flags |= T_NO_NEW_BRANCHES_FLAG; } else { /* if all_final return lowest */ picked_branch = t_pick_branch( Trans, &picked_code, &do_cancel); if (picked_branch==-2) { /* branches open yet */ *should_store=1; *should_relay=-1; picked_branch=-1; Trans->uac[branch].reply = 0; return RPS_STORE; } if (picked_branch==-1) { LM_CRIT("pick_branch failed (lowest==-1) for code %d\n",new_code); Trans->uac[branch].reply = 0; goto discard; } if (do_cancel) { branch_bm_t cb = 0; which_cancel( Trans, &cb ); cleanup_uac_timers(Trans); cancel_uacs( Trans, cb); } } /* no more pending branches -- try if that changes after * a callback; save branch count to be able to determine * later if new branches were initiated */ branch_cnt=Trans->nr_of_outgoings; reset_kr(); if ( !(Trans->flags&T_NO_DNS_FAILOVER_FLAG) && Trans->uac[picked_branch].proxy!=NULL ) { /* is is a DNS failover scenario, according to RFC 3263 ? */ if (is_3263_failure(Trans)) { LM_DBG("trying DNS-based failover\n"); /* do DNS failover -> add new branches */ if (do_dns_failover( Trans )!=0) { /* skip the failed added branches */ branch_cnt = Trans->nr_of_outgoings; } } } /* run ON_FAILURE handlers ( route and callbacks) */ if ( branch_cnt==Trans->nr_of_outgoings && (has_tran_tmcbs( Trans, TMCB_ON_FAILURE) || Trans->on_negative) ) { run_failure_handlers( Trans ); } /* now reset it; after the failure logic, the reply may * not be stored any more and we don't want to keep into * transaction some broken reference */ Trans->uac[branch].reply = 0; /* look if the callback perhaps replied transaction; it also covers the case in which a transaction is replied localy on CANCEL -- then it would make no sense to proceed to new branches bellow */ if (Trans->uas.status >= 200) { *should_store=0; *should_relay=-1; /* this might deserve an improvement -- if something was already replied, it was put on wait and then, returning RPS_COMPLETED will make t_on_reply put it on wait again; perhaps splitting put_on_wait from send_reply or a new RPS_ code would be healthy */ picked_branch=-1; return RPS_COMPLETED; } /* look if the callback/failure_route introduced new branches ... */ if (branch_cnt<Trans->nr_of_outgoings && get_kr()==REQ_FWDED) { /* await then result of new branches */ *should_store=1; *should_relay=-1; picked_branch=-1; return RPS_STORE; } /* really no more pending branches -- return selected code */ *should_store=0; *should_relay=picked_branch; picked_branch=-1; return RPS_COMPLETED; } /* not >=300 ... it must be 2xx or provisional 1xx */ if (new_code>=100) { /* 1xx and 2xx except 100 will be relayed */ Trans->uac[branch].last_received=new_code; *should_store=0; *should_relay= new_code==100? -1 : branch; if (new_code>=200 ) { which_cancel( Trans, cancel_bitmap ); return RPS_COMPLETED; } else return RPS_PROVISIONAL; } discard: *should_store=0; *should_relay=-1; return RPS_DISCARDED; }
/* function triggered from reactor in order to continue the processing */ int t_resume_async(int *fd, void *param) { static struct sip_msg faked_req; static struct ua_client uac; async_ctx *ctx = (async_ctx *)param; struct cell *backup_t; struct cell *backup_cancelled_t; struct cell *backup_e2eack_t; struct usr_avp **backup_list; struct socket_info* backup_si; struct cell *t= ctx->t; int route; LM_DBG("resuming on fd %d, transaction %p \n",*fd, t); if (current_processing_ctx) { LM_CRIT("BUG - a context already set!\n"); abort(); } /* prepare for resume route */ uac.br_flags = getb0flags( t->uas.request ) ; uac.uri = *GET_RURI( t->uas.request ); if (!fake_req( &faked_req /* the fake msg to be built*/, t->uas.request, /* the template msg saved in transaction */ &t->uas, /*the UAS side of the transaction*/ &uac, /* the fake UAC */ 1 /* copy dst_uri too */) ) { LM_ERR("fake_req failed\n"); return 0; } /* enviroment setting */ current_processing_ctx = ctx->msg_ctx; backup_t = get_t(); backup_e2eack_t = get_e2eack_t(); backup_cancelled_t = get_cancelled_t(); /* fake transaction */ set_t( t ); set_cancelled_t(ctx->cancelled_t); set_e2eack_t(ctx->e2eack_t); reset_kr(); set_kr(ctx->kr); /* make available the avp list from transaction */ backup_list = set_avp_list( &t->user_avps ); /* set default send address to the saved value */ backup_si = bind_address; bind_address = t->uac[0].request.dst.send_sock; async_status = ASYNC_DONE; /* assume default status as done */ /* call the resume function in order to read and handle data */ return_code = ctx->resume_f( *fd, &faked_req, ctx->resume_param ); if (async_status==ASYNC_CONTINUE) { /* do not run the resume route */ goto restore; } else if (async_status==ASYNC_CHANGE_FD) { if (return_code<0) { LM_ERR("ASYNC_CHANGE_FD: given file descriptor shall be positive!\n"); goto restore; } else if (return_code > 0 && return_code == *fd) { /*trying to add the same fd; shall continue*/ LM_CRIT("You are trying to replace the old fd with the same fd!" "Will act as in ASYNC_CONTINUE!\n"); goto restore; } /* remove the old fd from the reactor */ reactor_del_reader( *fd, -1, IO_FD_CLOSING); *fd=return_code; /* insert the new fd inside the reactor */ if (reactor_add_reader( *fd, F_SCRIPT_ASYNC, RCT_PRIO_ASYNC, (void*)ctx)<0 ) { LM_ERR("failed to add async FD to reactor -> act in sync mode\n"); do { return_code = ctx->resume_f( *fd, &faked_req, ctx->resume_param ); if (async_status == ASYNC_CHANGE_FD) *fd=return_code; } while(async_status==ASYNC_CONTINUE||async_status==ASYNC_CHANGE_FD); goto route; } /* changed fd; now restore old state */ goto restore; } /* remove from reactor, we are done */ reactor_del_reader( *fd, -1, IO_FD_CLOSING); route: if (async_status == ASYNC_DONE_CLOSE_FD) close(*fd); /* run the resume_route (some type as the original one) */ swap_route_type(route, ctx->route_type); run_resume_route( ctx->resume_route, &faked_req); set_route_type(route); /* no need for the context anymore */ shm_free(ctx); /* free also the processing ctx if still set * NOTE: it may become null if inside the run_resume_route * another async jump was made (and context attached again * to transaction) */ if (current_processing_ctx) { context_destroy(CONTEXT_GLOBAL, current_processing_ctx); pkg_free(current_processing_ctx); } restore: /* restore original environment */ set_t(backup_t); set_cancelled_t(backup_cancelled_t); set_e2eack_t(backup_e2eack_t); /* restore original avp list */ set_avp_list( backup_list ); bind_address = backup_si; free_faked_req( &faked_req, t); current_processing_ctx = NULL; return 0; }
/* Continues the SIP request processing previously saved by * t_suspend(). The script does not continue from the same * point, but a separate route block is executed instead. * * Return value: * 0 - success * <0 - failure */ int t_continue(unsigned int hash_index, unsigned int label, struct action *route) { struct cell *t; struct sip_msg faked_req; struct cancel_info cancel_data; int branch; struct ua_client *uac =NULL; int ret; int cb_type; int msg_status; int last_uac_status; int reply_status; int do_put_on_wait; struct hdr_field *hdr, *prev = 0, *tmp = 0; if (t_lookup_ident(&t, hash_index, label) < 0) { LM_ERR("transaction not found\n"); return -1; } if (t->flags & T_CANCELED) { /* The transaction has already been canceled, * needless to continue */ UNREF(t); /* t_unref would kill the transaction */ /* reset T as we have no working T anymore */ set_t(T_UNDEFINED, T_BR_UNDEFINED); return 1; } /* The transaction has to be locked to protect it * form calling t_continue() multiple times simultaneously */ LOCK_ASYNC_CONTINUE(t); t->flags |= T_ASYNC_CONTINUE; /* we can now know anywhere in kamailio * that we are executing post a suspend */ /* which route block type were we in when we were suspended */ cb_type = FAILURE_CB_TYPE;; switch (t->async_backup.backup_route) { case REQUEST_ROUTE: cb_type = FAILURE_CB_TYPE; break; case FAILURE_ROUTE: cb_type = FAILURE_CB_TYPE; break; case TM_ONREPLY_ROUTE: cb_type = ONREPLY_CB_TYPE; break; case BRANCH_ROUTE: cb_type = FAILURE_CB_TYPE; break; } if(t->async_backup.backup_route != TM_ONREPLY_ROUTE){ branch = t->async_backup.blind_uac; /* get the branch of the blind UAC setup * during suspend */ if (branch >= 0) { stop_rb_timers(&t->uac[branch].request); if (t->uac[branch].last_received != 0) { /* Either t_continue() has already been * called or the branch has already timed out. * Needless to continue. */ UNLOCK_ASYNC_CONTINUE(t); UNREF(t); /* t_unref would kill the transaction */ return 1; } /*we really don't need this next line anymore otherwise we will never be able to forward replies after a (t_relay) on this branch. We want to try and treat this branch as 'normal' (as if it were a normal req, not async)' */ //t->uac[branch].last_received=500; uac = &t->uac[branch]; } /* else Not a huge problem, fr timer will fire, but CANCEL will not be sent. last_received will be set to 408. */ reset_kr(); /* fake the request and the environment, like in failure_route */ if (!fake_req(&faked_req, t->uas.request, 0 /* extra flags */, uac)) { LM_ERR("building fake_req failed\n"); ret = -1; goto kill_trans; } faked_env( t, &faked_req, 1); /* execute the pre/post -script callbacks based on original route block */ if (exec_pre_script_cb(&faked_req, cb_type)>0) { if (run_top_route(route, &faked_req, 0)<0) LM_ERR("failure inside run_top_route\n"); exec_post_script_cb(&faked_req, cb_type); } /* TODO: save_msg_lumps should clone the lumps to shm mem */ /* restore original environment and free the fake msg */ faked_env( t, 0, 1); free_faked_req(&faked_req, t); /* update the flags */ t->uas.request->flags = faked_req.flags; if (t->uas.status < 200) { /* No final reply has been sent yet. * Check whether or not there is any pending branch. */ for ( branch = 0; branch < t->nr_of_outgoings; branch++ ) { if (t->uac[branch].last_received < 200) break; } if (branch == t->nr_of_outgoings) { /* There is not any open branch so there is * no chance that a final response will be received. */ ret = 0; goto kill_trans; } } } else { branch = t->async_backup.backup_branch; init_cancel_info(&cancel_data); LM_DBG("continuing from a suspended reply" " - resetting the suspend branch flag\n"); t->uac[branch].reply->msg_flags &= ~FL_RPL_SUSPENDED; if (t->uas.request) t->uas.request->msg_flags&= ~FL_RPL_SUSPENDED; faked_env( t, t->uac[branch].reply, 1); if (exec_pre_script_cb(t->uac[branch].reply, cb_type)>0) { if (run_top_route(route, t->uac[branch].reply, 0)<0){ LOG(L_ERR, "ERROR: t_continue_reply: Error in run_top_route\n"); } exec_post_script_cb(t->uac[branch].reply, cb_type); } LM_DBG("restoring previous environment"); faked_env( t, 0, 1); /*lock transaction replies - will be unlocked when reply is relayed*/ LOCK_REPLIES( t ); if ( is_local(t) ) { LM_DBG("t is local - sending reply with status code: [%d]\n", t->uac[branch].reply->first_line.u.reply.statuscode); reply_status = local_reply( t, t->uac[branch].reply, branch, t->uac[branch].reply->first_line.u.reply.statuscode, &cancel_data ); if (reply_status == RPS_COMPLETED) { /* no more UAC FR/RETR (if I received a 2xx, there may * be still pending branches ... */ cleanup_uac_timers( t ); if (is_invite(t)) cancel_uacs(t, &cancel_data, F_CANCEL_B_KILL); /* There is no need to call set_final_timer because we know * that the transaction is local */ put_on_wait(t); }else if (unlikely(cancel_data.cancel_bitmap)){ /* cancel everything, even non-INVITEs (e.g in case of 6xx), use * cancel_b_method for canceling unreplied branches */ cancel_uacs(t, &cancel_data, cfg_get(tm,tm_cfg, cancel_b_flags)); } } else { LM_DBG("t is not local - relaying reply with status code: [%d]\n", t->uac[branch].reply->first_line.u.reply.statuscode); do_put_on_wait = 0; if(t->uac[branch].reply->first_line.u.reply.statuscode>=200){ do_put_on_wait = 1; } reply_status=relay_reply( t, t->uac[branch].reply, branch, t->uac[branch].reply->first_line.u.reply.statuscode, &cancel_data, do_put_on_wait ); if (reply_status == RPS_COMPLETED) { /* no more UAC FR/RETR (if I received a 2xx, there may be still pending branches ... */ cleanup_uac_timers( t ); /* 2xx is a special case: we can have a COMPLETED request * with branches still open => we have to cancel them */ if (is_invite(t) && cancel_data.cancel_bitmap) cancel_uacs( t, &cancel_data, F_CANCEL_B_KILL); /* FR for negative INVITES, WAIT anything else */ /* Call to set_final_timer is embedded in relay_reply to avoid * race conditions when reply is sent out and an ACK to stop * retransmissions comes before retransmission timer is set.*/ }else if (unlikely(cancel_data.cancel_bitmap)){ /* cancel everything, even non-INVITEs (e.g in case of 6xx), use * cancel_b_method for canceling unreplied branches */ cancel_uacs(t, &cancel_data, cfg_get(tm,tm_cfg, cancel_b_flags)); } } t->uac[branch].request.flags|=F_RB_REPLIED; if (reply_status==RPS_ERROR){ goto done; } /* update FR/RETR timers on provisional replies */ msg_status=t->uac[branch].reply->REPLY_STATUS; last_uac_status=t->uac[branch].last_received; if (is_invite(t) && msg_status<200 && ( cfg_get(tm, tm_cfg, restart_fr_on_each_reply) || ( (last_uac_status<msg_status) && ((msg_status>=180) || (last_uac_status==0)) ) ) ) { /* provisional now */ restart_rb_fr(& t->uac[branch].request, t->fr_inv_timeout); t->uac[branch].request.flags|=F_RB_FR_INV; /* mark fr_inv */ } } done: UNLOCK_ASYNC_CONTINUE(t); if(t->async_backup.backup_route != TM_ONREPLY_ROUTE){ /* unref the transaction */ t_unref(t->uas.request); } else { tm_ctx_set_branch_index(T_BR_UNDEFINED); /* unref the transaction */ t_unref(t->uac[branch].reply); LOG(L_DBG,"DEBUG: t_continue_reply: Freeing earlier cloned reply\n"); /* free lumps that were added during reply processing */ del_nonshm_lump( &(t->uac[branch].reply->add_rm) ); del_nonshm_lump( &(t->uac[branch].reply->body_lumps) ); del_nonshm_lump_rpl( &(t->uac[branch].reply->reply_lump) ); /* free header's parsed structures that were added */ for( hdr=t->uac[branch].reply->headers ; hdr ; hdr=hdr->next ) { if ( hdr->parsed && hdr_allocs_parse(hdr) && (hdr->parsed<(void*)t->uac[branch].reply || hdr->parsed>=(void*)t->uac[branch].end_reply)) { clean_hdr_field(hdr); hdr->parsed = 0; } } /* now go through hdr_fields themselves and remove the pkg allocated space */ hdr = t->uac[branch].reply->headers; while (hdr) { if ( hdr && ((void*)hdr<(void*)t->uac[branch].reply || (void*)hdr>=(void*)t->uac[branch].end_reply)) { //this header needs to be freed and removed form the list. if (!prev) { t->uac[branch].reply->headers = hdr->next; } else { prev->next = hdr->next; } tmp = hdr; hdr = hdr->next; pkg_free(tmp); } else { prev = hdr; hdr = hdr->next; } } sip_msg_free(t->uac[branch].reply); t->uac[branch].reply = 0; } return 0; kill_trans: /* The script has hopefully set the error code. If not, * let us reply with a default error. */ if ((kill_transaction_unsafe(t, tm_error ? tm_error : E_UNSPEC)) <=0 ) { LOG(L_ERR, "ERROR: t_continue: " "reply generation failed\n"); /* The transaction must be explicitely released, * no more timer is running */ UNLOCK_ASYNC_CONTINUE(t); t_release_transaction(t); } else { UNLOCK_ASYNC_CONTINUE(t); } if(t->async_backup.backup_route != TM_ONREPLY_ROUTE){ t_unref(t->uas.request); } else { /* unref the transaction */ t_unref(t->uac[branch].reply); } return ret; }
/* function triggered from reactor in order to continue the processing */ int t_resume_async(int fd, void *param) { static struct sip_msg faked_req; static struct ua_client uac; async_ctx *ctx = (async_ctx *)param; struct cell *backup_t; struct usr_avp **backup_list; struct socket_info* backup_si; struct cell *t= ctx->t; int route; LM_DBG("resuming on fd %d, transaction %p \n",fd, t); if (current_processing_ctx) { LM_CRIT("BUG - a context already set!\n"); abort(); } /* prepare for resume route */ uac.br_flags = getb0flags( t->uas.request ) ; uac.uri = *GET_RURI( t->uas.request ); if (!fake_req( &faked_req /* the fake msg to be built*/, t->uas.request, /* the template msg saved in transaction */ &t->uas, /*the UAS side of the transaction*/ &uac, /* the fake UAC */ 1 /* copy dst_uri too */) ) { LM_ERR("fake_req failed\n"); return 0; } /* enviroment setting */ current_processing_ctx = ctx->msg_ctx; backup_t = get_t(); /* fake transaction */ set_t( t ); reset_kr(); set_kr(ctx->kr); /* make available the avp list from transaction */ backup_list = set_avp_list( &t->user_avps ); /* set default send address to the saved value */ backup_si = bind_address; bind_address = t->uac[0].request.dst.send_sock; async_status = ASYNC_DONE; /* assume default status as done */ /* call the resume function in order to read and handle data */ return_code = ctx->resume_f( fd, &faked_req, ctx->resume_param ); if (async_status==ASYNC_CONTINUE) { /* do not run the resume route */ goto restore; } /* remove from reactor, we are done */ reactor_del_reader( fd, -1, IO_FD_CLOSING); if (async_status == ASYNC_DONE_CLOSE_FD) close(fd); /* run the resume_route (some type as the original one) */ swap_route_type(route, ctx->route_type); run_resume_route( ctx->resume_route, &faked_req); set_route_type(route); /* no need for the context anymore */ shm_free(ctx); restore: /* restore original environment */ set_t(backup_t); /* restore original avp list */ set_avp_list( backup_list ); bind_address = backup_si; free_faked_req( &faked_req, t); current_processing_ctx = NULL; return 0; }