/* * For notifier, create NOTIFY request to subscriber, and set the state * of the subscription. */ PJ_DEF(pj_status_t) pjsip_xfer_notify( pjsip_evsub *sub, pjsip_evsub_state state, int xfer_st_code, const pj_str_t *xfer_st_text, pjsip_tx_data **p_tdata) { pjsip_tx_data *tdata; pjsip_xfer *xfer; pjsip_param *param; const pj_str_t reason = { "noresource", 10 }; char *body; int bodylen; pjsip_msg_body *msg_body; pj_status_t status; /* Check arguments. */ PJ_ASSERT_RETURN(sub, PJ_EINVAL); /* Get the xfer object. */ xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id); PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION); /* Lock object. */ pjsip_dlg_inc_lock(xfer->dlg); /* Create the NOTIFY request. * Note that reason is only used when state is TERMINATED, and * the defined termination reason for REFER is "noresource". */ status = pjsip_evsub_notify( sub, state, NULL, &reason, &tdata); if (status != PJ_SUCCESS) goto on_return; /* Check status text */ if (xfer_st_text==NULL || xfer_st_text->slen==0) xfer_st_text = pjsip_get_status_text(xfer_st_code); /* Save st_code and st_text, for current_notify() */ xfer->last_st_code = xfer_st_code; pj_strdup(xfer->dlg->pool, &xfer->last_st_text, xfer_st_text); /* Create sipfrag content. */ body = (char*) pj_pool_alloc(tdata->pool, 128); bodylen = pj_ansi_snprintf(body, 128, "SIP/2.0 %u %.*s\r\n", xfer_st_code, (int)xfer_st_text->slen, xfer_st_text->ptr); PJ_ASSERT_ON_FAIL(bodylen > 0 && bodylen < 128, {status=PJ_EBUG; pjsip_tx_data_dec_ref(tdata); goto on_return; });
static void call_on_state_changed( pjsip_inv_session *inv, pjsip_event *e) { call_t *call = (call_t*)inv->mod_data[mod_sipecho.id]; if (!call) return; PJ_UNUSED_ARG(e); if (inv->state == PJSIP_INV_STATE_DISCONNECTED) { PJ_LOG(3,(THIS_FILE, "Call %d: DISCONNECTED [reason=%d (%s)]", call - app.call, inv->cause, pjsip_get_status_text(inv->cause)->ptr)); destroy_call(call); } else { PJ_LOG(3,(THIS_FILE, "Call %d: state changed to %s", call - app.call, pjsip_inv_state_name(inv->state))); } }
/* * Callback when INVITE session state has changed. * This callback is registered when the invite session module is initialized. * We mostly want to know when the invite session has been disconnected, * so that we can quit the application. */ static void call_on_state_changed( pjsip_inv_session *inv, pjsip_event *e) { PJ_UNUSED_ARG(e); if (inv->state == PJSIP_INV_STATE_DISCONNECTED) { PJ_LOG(3,(THIS_FILE, "Call DISCONNECTED [reason=%d (%s)]", inv->cause, pjsip_get_status_text(inv->cause)->ptr)); PJ_LOG(3,(THIS_FILE, "One call completed, application quitting...")); g_complete = 1; } else { PJ_LOG(3,(THIS_FILE, "Call state changed to %s", pjsip_inv_state_name(inv->state))); } }
/* * pjsip_strerror() */ PJ_DEF(pj_str_t) pjsip_strerror( pj_status_t statcode, char *buf, pj_size_t bufsize ) { pj_str_t errstr; #if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) if (statcode >= PJSIP_ERRNO_START && statcode < PJSIP_ERRNO_START+800) { /* Status code. */ const pj_str_t *status_text = pjsip_get_status_text(PJSIP_ERRNO_TO_SIP_STATUS(statcode)); errstr.ptr = buf; pj_strncpy_with_null(&errstr, status_text, bufsize); return errstr; } else if (statcode >= PJSIP_ERRNO_START_PJSIP && statcode < PJSIP_ERRNO_START_PJSIP + 1000) { /* Find the error in the table. * Use binary search! */ int first = 0; int n = PJ_ARRAY_SIZE(err_str); while (n > 0) { int half = n/2; int mid = first + half; if (err_str[mid].code < statcode) { first = mid+1; n -= (half+1); } else if (err_str[mid].code > statcode) { n = half; } else { first = mid; break; } } if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) { pj_str_t msg; msg.ptr = (char*)err_str[first].msg; msg.slen = pj_ansi_strlen(err_str[first].msg); errstr.ptr = buf; pj_strncpy_with_null(&errstr, &msg, bufsize); return errstr; } } #endif /* PJ_HAS_ERROR_STRING */ /* Error not found. */ errstr.ptr = buf; errstr.slen = pj_ansi_snprintf(buf, bufsize, "Unknown pjsip error %d", statcode); return errstr; }
static void transfer_client_cb(pjsip_evsub *sub, pjsip_event *event) { auto link = getSIPVoIPLink(); if (not link) { RING_ERR("no more VoIP link"); return; } auto mod_ua_id = link->getModId(); switch (pjsip_evsub_get_state(sub)) { case PJSIP_EVSUB_STATE_ACCEPTED: if (!event) return; pj_assert(event->type == PJSIP_EVENT_TSX_STATE && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG); break; case PJSIP_EVSUB_STATE_TERMINATED: pjsip_evsub_set_mod_data(sub, mod_ua_id, NULL); break; case PJSIP_EVSUB_STATE_ACTIVE: { if (!event) return; pjsip_rx_data* r_data = event->body.rx_msg.rdata; if (!r_data) return; std::string request(pjsip_rx_data_get_info(r_data)); pjsip_status_line status_line = { 500, *pjsip_get_status_text(500) }; if (!r_data->msg_info.msg) return; if (r_data->msg_info.msg->line.req.method.id == PJSIP_OTHER_METHOD and request.find("NOTIFY") != std::string::npos) { pjsip_msg_body *body = r_data->msg_info.msg->body; if (!body) return; if (pj_stricmp2(&body->content_type.type, "message") or pj_stricmp2(&body->content_type.subtype, "sipfrag")) return; if (pjsip_parse_status_line((char*) body->data, body->len, &status_line) != PJ_SUCCESS) return; } if (!r_data->msg_info.cid) return; auto call = static_cast<SIPCall *>(pjsip_evsub_get_mod_data(sub, mod_ua_id)); if (!call) return; if (status_line.code / 100 == 2) { if (call->inv) call->terminateSipSession(PJSIP_SC_GONE); Manager::instance().hangupCall(call->getCallId()); pjsip_evsub_set_mod_data(sub, mod_ua_id, NULL); } break; } case PJSIP_EVSUB_STATE_NULL: case PJSIP_EVSUB_STATE_SENT: case PJSIP_EVSUB_STATE_PENDING: case PJSIP_EVSUB_STATE_UNKNOWN: default: break; } }
static void tsx_callback(void *token, pjsip_event *event) { pj_status_t status; pjsip_publishc *pubc = (pjsip_publishc*) token; pjsip_transaction *tsx = event->body.tsx_state.tsx; /* Decrement pending transaction counter. */ pj_assert(pubc->pending_tsx > 0); --pubc->pending_tsx; /* If publication data has been deleted by user then remove publication * data from transaction's callback, and don't call callback. */ if (pubc->_delete_flag) { /* Nothing to do */ ; } else if (tsx->status_code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED || tsx->status_code == PJSIP_SC_UNAUTHORIZED) { pjsip_rx_data *rdata = event->body.tsx_state.src.rdata; pjsip_tx_data *tdata; status = pjsip_auth_clt_reinit_req( &pubc->auth_sess, rdata, tsx->last_tx, &tdata); if (status != PJ_SUCCESS) { call_callback(pubc, status, tsx->status_code, &rdata->msg_info.msg->line.status.reason, rdata, -1); } else { status = pjsip_publishc_send(pubc, tdata); } } else { pjsip_rx_data *rdata; pj_int32_t expiration = 0xFFFF; if (tsx->status_code/100 == 2) { pjsip_msg *msg; pjsip_expires_hdr *expires; pjsip_generic_string_hdr *etag_hdr; const pj_str_t STR_ETAG = { "SIP-ETag", 8 }; rdata = event->body.tsx_state.src.rdata; msg = rdata->msg_info.msg; /* Save ETag value */ etag_hdr = (pjsip_generic_string_hdr*) pjsip_msg_find_hdr_by_name(msg, &STR_ETAG, NULL); if (etag_hdr) { pj_strdup(pubc->pool, &pubc->etag, &etag_hdr->hvalue); } else { pubc->etag.slen = 0; } /* Update expires value */ expires = (pjsip_expires_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL); if (expires) expiration = expires->ivalue; if (pubc->auto_refresh && expiration!=0 && expiration!=0xFFFF) { pj_time_val delay = { 0, 0}; delay.sec = expiration - DELAY_BEFORE_REFRESH; if (pubc->expires != PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED && delay.sec > (pj_int32_t)pubc->expires) { delay.sec = pubc->expires; } if (delay.sec < DELAY_BEFORE_REFRESH) delay.sec = DELAY_BEFORE_REFRESH; pubc->timer.cb = &pubc_refresh_timer_cb; pubc->timer.id = REFRESH_TIMER; pubc->timer.user_data = pubc; pjsip_endpt_schedule_timer( pubc->endpt, &pubc->timer, &delay); pj_gettimeofday(&pubc->last_refresh); pubc->next_refresh = pubc->last_refresh; pubc->next_refresh.sec += delay.sec; } } else { rdata = (event->body.tsx_state.type==PJSIP_EVENT_RX_MSG) ? event->body.tsx_state.src.rdata : NULL; } /* Call callback. */ if (expiration == 0xFFFF) expiration = -1; /* Temporarily increment pending_tsx to prevent callback from * destroying pubc. */ ++pubc->pending_tsx; call_callback(pubc, PJ_SUCCESS, tsx->status_code, (rdata ? &rdata->msg_info.msg->line.status.reason : pjsip_get_status_text(tsx->status_code)), rdata, expiration); --pubc->pending_tsx; } /* Delete the record if user destroy pubc during the callback. */ if (pubc->_delete_flag && pubc->pending_tsx==0) { pjsip_publishc_destroy(pubc); } }
void ICSCFSproutletRegTsx::on_rx_response(pjsip_msg* rsp, int fork_id) { if (_acr != NULL) { // Pass the received response to the ACR. // @TODO - timestamp from response??? _acr->rx_response(rsp); } // Check if this response is one that we are allowed to retry the HSS lookup // for. See TS 24.229 - section 5.3.1.3. // // Note we support service restoration, so integrity-protected settings in // Authorization header are immaterial). pjsip_status_code rsp_status = (pjsip_status_code)rsp->line.status.code; const ForkState& fork_status = fork_state(fork_id); TRC_DEBUG("Check retry conditions for REGISTER, status = %d, S-CSCF %sresponsive", rsp_status, (fork_status.error_state != NONE) ? "not " : ""); if ((PJSIP_IS_STATUS_IN_CLASS(rsp_status, 300)) || (fork_status.error_state != NONE) || (rsp_status == PJSIP_SC_TEMPORARILY_UNAVAILABLE)) { // Indeed it is, first log to SAS. TRC_DEBUG("Attempt retry to alternate S-CSCF for REGISTER request"); std::string st_code = std::to_string(rsp_status); SAS::Event event(trail(), SASEvent::SCSCF_RETRY, 0); std::string method = "REGISTER"; event.add_var_param(method); event.add_var_param(st_code); SAS::report_event(event); // Now we can simply reuse the UA router we made on the initial request. pjsip_sip_uri* scscf_sip_uri = NULL; pjsip_msg* req = original_request(); std::string wildcard; int status_code = _router->get_scscf(get_pool(req), scscf_sip_uri, wildcard); if (status_code == PJSIP_SC_OK) { TRC_DEBUG("Found SCSCF for REGISTER"); req->line.req.uri = (pjsip_uri*)scscf_sip_uri; send_request(req); // We're not forwarding this response upstream. free_msg(rsp); } else { // In the register case the spec's are quite particular about how // failures are reported. if (status_code == PJSIP_SC_FORBIDDEN) { // The HSS has returned a negative response to the user registration // request - I-CSCF should respond with 403. rsp->line.status.code = PJSIP_SC_FORBIDDEN; rsp->line.status.reason = *pjsip_get_status_text(rsp->line.status.code); } else { // The I-CSCF can't select an S-CSCF for the REGISTER request (either // because there are no more S-CSCFs that meet the mandatory // capabilitires, or the HSS is temporarily unavailable). There was at // least one valid S-CSCF (as this is retry processing). The I-CSCF // must return 504 (TS 24.229, 5.3.1.3) in this case. rsp->line.status.code = PJSIP_SC_SERVER_TIMEOUT; rsp->line.status.reason = *pjsip_get_status_text(rsp->line.status.code); } // We're done, no more retries. free_msg(req); send_response(rsp); } } else { // Provisional, successful or non-retryable response, simply forward on // upstream. If this is a final response, there will be no more retries. send_response(rsp); } }
/// Retry the request to an alternate S-CSCF if possible. bool ICSCFProxy::UASTsx::retry_to_alternate_scscf(int rsp_status) { bool retry = false; if (_case == SessionCase::REGISTER) { // Check whether conditions are satisfied for retrying a REGISTER (see // 5.3.1.3/TS24.229). LOG_DEBUG("Check retry conditions for REGISTER request, status code = %d", rsp_status); if (((rsp_status >= 300) && (rsp_status <= 399)) || (rsp_status == PJSIP_SC_REQUEST_TIMEOUT) || (rsp_status == PJSIP_SC_TEMPORARILY_UNAVAILABLE)) { // Can do a retry (we support service restoration, so integrity-protected // settings in Authorization header are immaterial). LOG_DEBUG("Attempt retry to alternate S-CSCF for REGISTER request"); retry = true; std::string st_code = std::to_string(rsp_status); SAS::Event event(trail(), SASEvent::SCSCF_RETRY, 0); std::string method = "REGISTER"; event.add_var_param(method); event.add_var_param(st_code); SAS::report_event(event); } } else { // Check whether conditions are satisfied for retrying a Non-REGISTER. LOG_DEBUG("Check retry conditions for Non-REGISTER request, status code = %d", rsp_status); if (rsp_status == PJSIP_SC_REQUEST_TIMEOUT) { LOG_DEBUG("Attempt retry to alternate S-CSCF for non-REGISTER request"); retry = true; std::string st_code = std::to_string(rsp_status); SAS::Event event(trail(), SASEvent::SCSCF_RETRY, 0); std::string method = "NON-REGISTER"; event.add_var_param(method); event.add_var_param(st_code); SAS::report_event(event); } } if (retry) { // Retry conditions are satisfied, so try to calculate a new target. int status_code = calculate_targets(); if (status_code == PJSIP_SC_OK) { // We found a suitable alternate S-CSCF and have programmed it as a // target, so action the retry. forward_request(); } else { // Failed to find another S-CSCF for the request. LOG_DEBUG("Failed to find alternate S-CSCF for retry"); retry = false; if (_case == SessionCase::REGISTER) { // In the register case the spec's are quite particular about how // failures are reported. if (status_code == PJSIP_SC_FORBIDDEN) { // The HSS has returned a negative response to the user registration // request - I-CSCF should respond with 403. _best_rsp->msg->line.status.code = PJSIP_SC_FORBIDDEN; _best_rsp->msg->line.status.reason = *pjsip_get_status_text(_best_rsp->msg->line.status.code); } else { // The I-CSCF can't select an S-CSCF for the REGISTER request (either // because there are no more S-CSCFs that meet the mandatory // capabilitires, or the HSS is temporarily unavailable). There was at // least one valid S-CSCF (as this is retry processing). The I-CSCF // must return 504 (TS 24.229, 5.3.1.3) in this case. _best_rsp->msg->line.status.code = PJSIP_SC_SERVER_TIMEOUT; _best_rsp->msg->line.status.reason = *pjsip_get_status_text(_best_rsp->msg->line.status.code); } pjsip_tx_data_invalidate_msg(_best_rsp); } } } return retry; }
static void tsx_callback(void *token, pjsip_event *event) { pj_status_t status; pjsip_publishc *pubc = (pjsip_publishc*) token; pjsip_transaction *tsx = event->body.tsx_state.tsx; /* Decrement pending transaction counter. */ pj_assert(pubc->pending_tsx > 0); --pubc->pending_tsx; /* Mark that we're in callback to prevent deletion (#1164) */ ++pubc->in_callback; /* If publication data has been deleted by user then remove publication * data from transaction's callback, and don't call callback. */ if (pubc->_delete_flag) { /* Nothing to do */ ; } else if (tsx->status_code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED || tsx->status_code == PJSIP_SC_UNAUTHORIZED) { pjsip_rx_data *rdata = event->body.tsx_state.src.rdata; pjsip_tx_data *tdata; status = pjsip_auth_clt_reinit_req( &pubc->auth_sess, rdata, tsx->last_tx, &tdata); if (status != PJ_SUCCESS) { call_callback(pubc, status, tsx->status_code, &rdata->msg_info.msg->line.status.reason, rdata, -1); } else { status = pjsip_publishc_send(pubc, tdata); } } else { pjsip_rx_data *rdata; pj_int32_t expiration = 0xFFFF; if (tsx->status_code/100 == 2) { pjsip_msg *msg; pjsip_expires_hdr *expires; pjsip_generic_string_hdr *etag_hdr; const pj_str_t STR_ETAG = { "SIP-ETag", 8 }; rdata = event->body.tsx_state.src.rdata; msg = rdata->msg_info.msg; /* Save ETag value */ etag_hdr = (pjsip_generic_string_hdr*) pjsip_msg_find_hdr_by_name(msg, &STR_ETAG, NULL); if (etag_hdr) { pj_strdup(pubc->pool, &pubc->etag, &etag_hdr->hvalue); } else { pubc->etag.slen = 0; } /* Update expires value */ expires = (pjsip_expires_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL); if (pubc->auto_refresh && expires) expiration = expires->ivalue; if (pubc->auto_refresh && expiration!=0 && expiration!=0xFFFF) { pj_time_val delay = { 0, 0}; /* Cancel existing timer, if any */ if (pubc->timer.id != 0) { pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer); pubc->timer.id = 0; } delay.sec = expiration - DELAY_BEFORE_REFRESH; if (pubc->expires != PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED && delay.sec > (pj_int32_t)pubc->expires) { delay.sec = pubc->expires; } if (delay.sec < DELAY_BEFORE_REFRESH) delay.sec = DELAY_BEFORE_REFRESH; pubc->timer.cb = &pubc_refresh_timer_cb; pubc->timer.id = REFRESH_TIMER; pubc->timer.user_data = pubc; pjsip_endpt_schedule_timer( pubc->endpt, &pubc->timer, &delay); pj_gettimeofday(&pubc->last_refresh); pubc->next_refresh = pubc->last_refresh; pubc->next_refresh.sec += delay.sec; } } else { rdata = (event->body.tsx_state.type==PJSIP_EVENT_RX_MSG) ? event->body.tsx_state.src.rdata : NULL; } /* Call callback. */ if (expiration == 0xFFFF) expiration = -1; /* Temporarily increment pending_tsx to prevent callback from * destroying pubc. */ ++pubc->pending_tsx; call_callback(pubc, PJ_SUCCESS, tsx->status_code, (rdata ? &rdata->msg_info.msg->line.status.reason : pjsip_get_status_text(tsx->status_code)), rdata, expiration); --pubc->pending_tsx; /* If we have pending request(s), send them now */ pj_mutex_lock(pubc->mutex); while (!pj_list_empty(&pubc->pending_reqs)) { pjsip_tx_data *tdata = pubc->pending_reqs.next; pj_list_erase(tdata); /* Add SIP-If-Match if we have etag and the request doesn't have * one (http://trac.pjsip.org/repos/ticket/996) */ if (pubc->etag.slen) { const pj_str_t STR_HNAME = { "SIP-If-Match", 12 }; pjsip_generic_string_hdr *sim_hdr; sim_hdr = (pjsip_generic_string_hdr*) pjsip_msg_find_hdr_by_name(tdata->msg, &STR_HNAME, NULL); if (!sim_hdr) { /* Create the header */ sim_hdr = pjsip_generic_string_hdr_create(tdata->pool, &STR_HNAME, &pubc->etag); pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)sim_hdr); } else { /* Update */ if (pj_strcmp(&pubc->etag, &sim_hdr->hvalue)) pj_strdup(tdata->pool, &sim_hdr->hvalue, &pubc->etag); } } status = pjsip_publishc_send(pubc, tdata); if (status == PJ_EPENDING) { pj_assert(!"Not expected"); pj_list_erase(tdata); pjsip_tx_data_dec_ref(tdata); } else if (status == PJ_SUCCESS) { break; } } pj_mutex_unlock(pubc->mutex); } /* No longer in callback. */ --pubc->in_callback; /* Delete the record if user destroy pubc during the callback. */ if (pubc->_delete_flag && pubc->pending_tsx==0) { pjsip_publishc_destroy(pubc); } }
int main(int argc, char *argv[]) { static char report[1024]; printf("PJSIP Performance Measurement Tool v%s\n" "(c)2006 pjsip.org\n\n", PJ_VERSION); if (create_app() != 0) return 1; if (init_options(argc, argv) != 0) return 1; if (init_sip() != 0) return 1; if (init_media() != 0) return 1; pj_log_set_level(app.log_level); if (app.log_level > 4) { pjsip_endpt_register_module(app.sip_endpt, &msg_logger); } /* Misc infos */ if (app.client.dst_uri.slen != 0) { if (app.client.method.id == PJSIP_INVITE_METHOD) { if (app.client.stateless) { PJ_LOG(3,(THIS_FILE, "Info: --stateless option makes no sense for INVITE," " ignored.")); } } } if (app.client.dst_uri.slen) { /* Client mode */ pj_status_t status; char test_type[64]; unsigned msec_req, msec_res; unsigned i; /* Get the job name */ if (app.client.method.id == PJSIP_INVITE_METHOD) { pj_ansi_strcpy(test_type, "INVITE calls"); } else if (app.client.stateless) { pj_ansi_sprintf(test_type, "stateless %.*s requests", (int)app.client.method.name.slen, app.client.method.name.ptr); } else { pj_ansi_sprintf(test_type, "stateful %.*s requests", (int)app.client.method.name.slen, app.client.method.name.ptr); } printf("Sending %d %s to '%.*s' with %d maximum outstanding jobs, please wait..\n", app.client.job_count, test_type, (int)app.client.dst_uri.slen, app.client.dst_uri.ptr, app.client.job_window); for (i=0; i<app.thread_count; ++i) { status = pj_thread_create(app.pool, NULL, &client_thread, (void*)(long)i, 0, 0, &app.thread[i]); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to create thread", status); return 1; } } for (i=0; i<app.thread_count; ++i) { pj_thread_join(app.thread[i]); app.thread[i] = NULL; } if (app.client.last_completion.sec) { pj_time_val duration; duration = app.client.last_completion; PJ_TIME_VAL_SUB(duration, app.client.first_request); msec_res = PJ_TIME_VAL_MSEC(duration); } else { msec_res = app.client.timeout * 1000; } if (msec_res == 0) msec_res = 1; if (app.client.requests_sent.sec) { pj_time_val duration; duration = app.client.requests_sent; PJ_TIME_VAL_SUB(duration, app.client.first_request); msec_req = PJ_TIME_VAL_MSEC(duration); } else { msec_req = app.client.timeout * 1000; } if (msec_req == 0) msec_req = 1; if (app.client.job_submitted < app.client.job_count) puts("\ntimed-out!\n"); else puts("\ndone.\n"); pj_ansi_snprintf( report, sizeof(report), "Total %d %s sent in %d ms at rate of %d/sec\n" "Total %d responses receieved in %d ms at rate of %d/sec:", app.client.job_submitted, test_type, msec_req, app.client.job_submitted * 1000 / msec_req, app.client.total_responses, msec_res, app.client.total_responses*1000/msec_res); write_report(report); /* Print detailed response code received */ pj_ansi_sprintf(report, "\nDetailed responses received:"); write_report(report); for (i=0; i<PJ_ARRAY_SIZE(app.client.response_codes); ++i) { const pj_str_t *reason; if (app.client.response_codes[i] == 0) continue; reason = pjsip_get_status_text(i); pj_ansi_snprintf( report, sizeof(report), " - %d responses: %7d (%.*s)", i, app.client.response_codes[i], (int)reason->slen, reason->ptr); write_report(report); } /* Total responses and rate */ pj_ansi_snprintf( report, sizeof(report), " ------\n" " TOTAL responses: %7d (rate=%d/sec)\n", app.client.total_responses, app.client.total_responses*1000/msec_res); write_report(report); pj_ansi_sprintf(report, "Maximum outstanding job: %d", app.client.stat_max_window); write_report(report); } else { /* Server mode */ char s[10], *unused; pj_status_t status; unsigned i; puts("pjsip-perf started in server-mode"); printf("Receiving requests on the following URIs:\n" " sip:0@%.*s:%d%s for stateless handling\n" " sip:1@%.*s:%d%s for stateful handling\n" " sip:2@%.*s:%d%s for call handling\n", (int)app.local_addr.slen, app.local_addr.ptr, app.local_port, (app.use_tcp ? ";transport=tcp" : ""), (int)app.local_addr.slen, app.local_addr.ptr, app.local_port, (app.use_tcp ? ";transport=tcp" : ""), (int)app.local_addr.slen, app.local_addr.ptr, app.local_port, (app.use_tcp ? ";transport=tcp" : "")); printf("INVITE with non-matching user part will be handled call-statefully\n"); for (i=0; i<app.thread_count; ++i) { status = pj_thread_create(app.pool, NULL, &server_thread, (void*)(long)i, 0, 0, &app.thread[i]); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Unable to create thread", status); return 1; } } puts("\nPress <ENTER> to quit\n"); fflush(stdout); unused = fgets(s, sizeof(s), stdin); PJ_UNUSED_ARG(unused); app.thread_quit = PJ_TRUE; for (i=0; i<app.thread_count; ++i) { pj_thread_join(app.thread[i]); app.thread[i] = NULL; } puts(""); } destroy_app(); return 0; }
static void tsx_callback(void *token, pjsip_event *event) { pj_status_t status; pjsip_regc *regc = (pjsip_regc*) token; pjsip_transaction *tsx = event->body.tsx_state.tsx; pj_atomic_inc(regc->busy_ctr); pj_lock_acquire(regc->lock); /* Decrement pending transaction counter. */ pj_assert(regc->has_tsx); regc->has_tsx = PJ_FALSE; /* Add reference to the transport */ if (tsx->transport != regc->last_transport) { if (regc->last_transport) { pjsip_transport_dec_ref(regc->last_transport); regc->last_transport = NULL; } if (tsx->transport) { regc->last_transport = tsx->transport; pjsip_transport_add_ref(regc->last_transport); } } /* Handle 401/407 challenge (even when _delete_flag is set) */ if (tsx->status_code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED || tsx->status_code == PJSIP_SC_UNAUTHORIZED) { pjsip_rx_data *rdata = event->body.tsx_state.src.rdata; pjsip_tx_data *tdata; /* reset current op */ regc->current_op = REGC_IDLE; status = pjsip_auth_clt_reinit_req( ®c->auth_sess, rdata, tsx->last_tx, &tdata); if (status == PJ_SUCCESS) { status = pjsip_regc_send(regc, tdata); } if (status != PJ_SUCCESS) { /* Only call callback if application is still interested * in it. */ if (regc->_delete_flag == 0) { /* Should be safe to release the lock temporarily. * We do this to avoid deadlock. */ pj_lock_release(regc->lock); call_callback(regc, status, tsx->status_code, &rdata->msg_info.msg->line.status.reason, rdata, -1, 0, NULL); pj_lock_acquire(regc->lock); } } } else if (regc->_delete_flag) { /* User has called pjsip_regc_destroy(), so don't call callback. * This regc will be destroyed later in this function. */ /* Just reset current op */ regc->current_op = REGC_IDLE; } else { pjsip_rx_data *rdata; pj_int32_t expiration = NOEXP; unsigned contact_cnt = 0; pjsip_contact_hdr *contact[PJSIP_REGC_MAX_CONTACT]; if (tsx->status_code/100 == 2) { rdata = event->body.tsx_state.src.rdata; /* Calculate expiration */ expiration = calculate_response_expiration(regc, rdata, &contact_cnt, PJSIP_REGC_MAX_CONTACT, contact); /* Mark operation as complete */ regc->current_op = REGC_IDLE; /* Schedule next registration */ if (regc->auto_reg && expiration > 0) { pj_time_val delay = { 0, 0}; delay.sec = expiration - DELAY_BEFORE_REFRESH; if (regc->expires != PJSIP_REGC_EXPIRATION_NOT_SPECIFIED && delay.sec > (pj_int32_t)regc->expires) { delay.sec = regc->expires; } if (delay.sec < DELAY_BEFORE_REFRESH) delay.sec = DELAY_BEFORE_REFRESH; regc->timer.cb = ®c_refresh_timer_cb; regc->timer.id = REFRESH_TIMER; regc->timer.user_data = regc; pjsip_endpt_schedule_timer( regc->endpt, ®c->timer, &delay); pj_gettimeofday(®c->last_reg); regc->next_reg = regc->last_reg; regc->next_reg.sec += delay.sec; } } else { rdata = (event->body.tsx_state.type==PJSIP_EVENT_RX_MSG) ? event->body.tsx_state.src.rdata : NULL; } /* Update registration */ if (expiration==NOEXP) expiration=-1; regc->expires = expiration; /* Call callback. */ /* Should be safe to release the lock temporarily. * We do this to avoid deadlock. */ pj_lock_release(regc->lock); call_callback(regc, PJ_SUCCESS, tsx->status_code, (rdata ? &rdata->msg_info.msg->line.status.reason : pjsip_get_status_text(tsx->status_code)), rdata, expiration, contact_cnt, contact); pj_lock_acquire(regc->lock); } pj_lock_release(regc->lock); /* Delete the record if user destroy regc during the callback. */ if (pj_atomic_dec_and_get(regc->busy_ctr)==0 && regc->_delete_flag) { pjsip_regc_destroy(regc); } }
void create_challenge(pjsip_authorization_hdr* auth_hdr, std::string resync, pjsip_rx_data* rdata, pjsip_tx_data* tdata) { // Get the public and private identities from the request. std::string impi; std::string impu; std::string nonce; PJUtils::get_impi_and_impu(rdata, impi, impu); // Set up the authorization type, following Annex P.4 of TS 33.203. Currently // only support AKA and SIP Digest, so only implement the subset of steps // required to distinguish between the two. std::string auth_type; if (auth_hdr != NULL) { pjsip_param* integrity = pjsip_param_find(&auth_hdr->credential.digest.other_param, &STR_INTEGRITY_PROTECTED); if ((integrity != NULL) && ((pj_stricmp(&integrity->value, &STR_YES) == 0) || (pj_stricmp(&integrity->value, &STR_NO) == 0))) { // Authentication scheme is AKA. auth_type = "aka"; } } // Get the Authentication Vector from the HSS. Json::Value* av = NULL; HTTPCode http_code = hss->get_auth_vector(impi, impu, auth_type, resync, av, get_trail(rdata)); if ((av != NULL) && (!verify_auth_vector(av, impi, get_trail(rdata)))) { // Authentication Vector is badly formed. delete av; av = NULL; } if (av != NULL) { // Retrieved a valid authentication vector, so generate the challenge. LOG_DEBUG("Valid AV - generate challenge"); char buf[16]; pj_str_t random; random.ptr = buf; random.slen = sizeof(buf); LOG_DEBUG("Create WWW-Authenticate header"); pjsip_www_authenticate_hdr* hdr = pjsip_www_authenticate_hdr_create(tdata->pool); // Set up common fields for Digest and AKA cases (both are considered // Digest authentication). hdr->scheme = STR_DIGEST; if (av->isMember("aka")) { // AKA authentication. LOG_DEBUG("Add AKA information"); SAS::Event event(get_trail(rdata), SASEvent::AUTHENTICATION_CHALLENGE, 0); std::string AKA = "AKA"; event.add_var_param(AKA); SAS::report_event(event); Json::Value& aka = (*av)["aka"]; // Use default realm for AKA as not specified in the AV. pj_strdup(tdata->pool, &hdr->challenge.digest.realm, &aka_realm); hdr->challenge.digest.algorithm = STR_AKAV1_MD5; nonce = aka["challenge"].asString(); pj_strdup2(tdata->pool, &hdr->challenge.digest.nonce, nonce.c_str()); pj_create_random_string(buf, sizeof(buf)); pj_strdup(tdata->pool, &hdr->challenge.digest.opaque, &random); hdr->challenge.digest.qop = STR_AUTH; hdr->challenge.digest.stale = PJ_FALSE; // Add the cryptography key parameter. pjsip_param* ck_param = (pjsip_param*)pj_pool_alloc(tdata->pool, sizeof(pjsip_param)); ck_param->name = STR_CK; std::string ck = "\"" + aka["cryptkey"].asString() + "\""; pj_strdup2(tdata->pool, &ck_param->value, ck.c_str()); pj_list_insert_before(&hdr->challenge.digest.other_param, ck_param); // Add the integrity key parameter. pjsip_param* ik_param = (pjsip_param*)pj_pool_alloc(tdata->pool, sizeof(pjsip_param)); ik_param->name = STR_IK; std::string ik = "\"" + aka["integritykey"].asString() + "\""; pj_strdup2(tdata->pool, &ik_param->value, ik.c_str()); pj_list_insert_before(&hdr->challenge.digest.other_param, ik_param); } else { // Digest authentication. LOG_DEBUG("Add Digest information"); SAS::Event event(get_trail(rdata), SASEvent::AUTHENTICATION_CHALLENGE, 0); std::string DIGEST = "DIGEST"; event.add_var_param(DIGEST); SAS::report_event(event); Json::Value& digest = (*av)["digest"]; pj_strdup2(tdata->pool, &hdr->challenge.digest.realm, digest["realm"].asCString()); hdr->challenge.digest.algorithm = STR_MD5; pj_create_random_string(buf, sizeof(buf)); nonce.assign(buf, sizeof(buf)); pj_strdup(tdata->pool, &hdr->challenge.digest.nonce, &random); pj_create_random_string(buf, sizeof(buf)); pj_strdup(tdata->pool, &hdr->challenge.digest.opaque, &random); pj_strdup2(tdata->pool, &hdr->challenge.digest.qop, digest["qop"].asCString()); hdr->challenge.digest.stale = PJ_FALSE; } // Add the header to the message. pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr); // Store the branch parameter in memcached for correlation purposes pjsip_via_hdr* via_hdr = (pjsip_via_hdr*)pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_VIA, NULL); std::string branch = (via_hdr != NULL) ? PJUtils::pj_str_to_string(&via_hdr->branch_param) : ""; (*av)["branch"] = branch; // Write the authentication vector (as a JSON string) into the AV store. LOG_DEBUG("Write AV to store"); uint64_t cas = 0; bool success = av_store->set_av(impi, nonce, av, cas, get_trail(rdata)); if (success) { // We've written the AV into the store, so need to set a Chronos // timer so that an AUTHENTICATION_TIMEOUT SAR is sent to the // HSS when it expires. std::string timer_id; std::string chronos_body = "{\"impi\": \"" + impi + "\", \"impu\": \"" + impu +"\", \"nonce\": \"" + nonce +"\"}"; LOG_DEBUG("Sending %s to Chronos to set AV timer", chronos_body.c_str()); chronos->send_post(timer_id, 30, "/authentication-timeout", chronos_body, 0); } delete av; } else { std::string error_msg; // If we couldn't get the AV because a downstream node is overloaded then don't return // a 4xx error to the client. if ((http_code == HTTP_SERVER_UNAVAILABLE) || (http_code == HTTP_GATEWAY_TIMEOUT)) { error_msg = "Downstream node is overloaded or unresponsive, unable to get Authentication vector"; LOG_DEBUG(error_msg.c_str()); tdata->msg->line.status.code = PJSIP_SC_SERVER_TIMEOUT; tdata->msg->line.status.reason = *pjsip_get_status_text(PJSIP_SC_SERVER_TIMEOUT); } else { error_msg = "Failed to get Authentication vector"; LOG_DEBUG(error_msg.c_str()); tdata->msg->line.status.code = PJSIP_SC_FORBIDDEN; tdata->msg->line.status.reason = *pjsip_get_status_text(PJSIP_SC_FORBIDDEN); } SAS::Event event(get_trail(rdata), SASEvent::AUTHENTICATION_FAILED, 0); event.add_var_param(error_msg); SAS::report_event(event); pjsip_tx_data_invalidate_msg(tdata); } }
/* This function is called when we receive SUBSCRIBE request message * to refresh existing subscription. */ static void on_received_sub_refresh( pjsip_event_sub *sub, pjsip_transaction *tsx, pjsip_rx_data *rdata) { pjsip_event_hdr *e; pjsip_expires_hdr *expires; pj_str_t hname; int status = 200; pj_str_t reason_phrase = { NULL, 0 }; int new_state = sub->state; int old_state = sub->state; int new_interval = 0; pjsip_tx_data *tdata; PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): received target refresh", sub, state[sub->state].ptr)); /* Check that the event matches. */ hname = pj_str("Event"); e = pjsip_msg_find_hdr_by_name( rdata->msg, &hname, NULL); if (!e) { status = 400; reason_phrase = pj_str("Missing Event header"); goto send_response; } if (pj_stricmp(&e->event_type, &sub->event->event_type) != 0 || pj_stricmp(&e->id_param, &sub->event->id_param) != 0) { status = 481; reason_phrase = pj_str("Subscription does not exist"); goto send_response; } /* Check server state. */ if (sub->state == PJSIP_EVENT_SUB_STATE_TERMINATED) { status = 481; reason_phrase = pj_str("Subscription does not exist"); goto send_response; } /* Check expires header. */ expires = pjsip_msg_find_hdr(rdata->msg, PJSIP_H_EXPIRES, NULL); if (!expires) { /* status = 400; reason_phrase = pj_str("Missing Expires header"); goto send_response; */ new_interval = sub->default_interval; } else { /* Check that interval is not too short. * Note that expires time may be zero (for unsubscription). */ new_interval = expires->ivalue; if (new_interval != 0 && new_interval < SECONDS_BEFORE_EXPIRY) { status = PJSIP_SC_INTERVAL_TOO_BRIEF; goto send_response; } } /* Update interval. */ sub->default_interval = new_interval; pj_gettimeofday(&sub->expiry_time); sub->expiry_time.sec += new_interval; /* Update timer only if this is not unsubscription. */ if (new_interval > 0) { sub->default_interval = new_interval; sub_schedule_uas_expire( sub, new_interval ); /* Call callback. */ if (sub->cb.on_received_refresh) { sub->pending_tsx++; (*sub->cb.on_received_refresh)(sub, rdata); sub->pending_tsx--; } } send_response: tdata = pjsip_endpt_create_response( sub->endpt, rdata, status); if (tdata) { if (reason_phrase.slen) tdata->msg->line.status.reason = reason_phrase; /* Add Expires header. */ expires = pjsip_expires_hdr_create(tdata->pool); expires->ivalue = sub->default_interval; pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)expires); if (PJSIP_IS_STATUS_IN_CLASS(status,200)) { pjsip_msg_add_hdr(tdata->msg, pjsip_hdr_shallow_clone(tdata->pool, mgr.allow_events)); } /* Send down to transaction. */ pjsip_tsx_on_tx_msg(tsx, tdata); } if (sub->default_interval==0 || !PJSIP_IS_STATUS_IN_CLASS(status,200)) { /* Notify application if sub is terminated. */ new_state = PJSIP_EVENT_SUB_STATE_TERMINATED; sub_set_state(sub, new_state); if (new_state!=old_state && sub->cb.on_sub_terminated) { pj_str_t reason = {"", 0}; if (reason_phrase.slen) reason = reason_phrase; else reason = *pjsip_get_status_text(status); sub->pending_tsx++; (*sub->cb.on_sub_terminated)(sub, &reason); sub->pending_tsx--; } } pj_mutex_unlock(sub->mutex); /* Prefer to call log when we're not holding the mutex. */ PJ_LOG(4,(THIS_FILE, "event_sub%p (%s): sent refresh response %s, status=%d", sub, state[sub->state].ptr, (tdata ? tdata->obj_name : "null"), status)); /* Check if application has requested deletion. */ if (sub->delete_flag && sub->pending_tsx <= 0) { pjsip_event_sub_destroy(sub); } }
/* This callback is called when response to SUBSCRIBE is received. */ static void on_subscribe_response(void *token, pjsip_event *event) { pjsip_event_sub *sub = token; pjsip_transaction *tsx = event->obj.tsx; int new_state, old_state = sub->state; pj_assert(tsx->status_code >= 200); if (tsx->status_code < 200) return; pj_assert(sub->role == PJSIP_ROLE_UAC); /* Lock mutex. */ pj_mutex_lock(sub->mutex); /* If request failed with 401/407 error, silently retry the request. */ if (tsx->status_code==401 || tsx->status_code==407) { pjsip_tx_data *tdata; tdata = pjsip_auth_reinit_req(sub->endpt, sub->pool, &sub->auth_sess, sub->cred_cnt, sub->cred_info, tsx->last_tx, event->src.rdata ); if (tdata) { int status; pjsip_cseq_hdr *cseq; cseq = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL); cseq->cseq = sub->cseq++; status = pjsip_endpt_send_request( sub->endpt, tdata, -1, sub, &on_subscribe_response); if (status == 0) { pj_mutex_unlock(sub->mutex); return; } } } if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code,200)) { /* Update To tag. */ if (sub->to->tag.slen == 0) pj_strdup(sub->pool, &sub->to->tag, &event->src.rdata->to_tag); new_state = sub->state; } else if (tsx->status_code == 481) { new_state = PJSIP_EVENT_SUB_STATE_TERMINATED; } else if (tsx->status_code >= 300) { /* RFC 3265 Section 3.1.4.2: * If a SUBSCRIBE request to refresh a subscription fails * with a non-481 response, the original subscription is still * considered valid for the duration of original exires. * * Note: * Since we normally send SUBSCRIBE for refreshing the subscription, * it means the subscription already expired anyway. So we terminate * the subscription now. */ if (sub->state != PJSIP_EVENT_SUB_STATE_ACTIVE) { new_state = PJSIP_EVENT_SUB_STATE_TERMINATED; } else { /* Use this to be compliant with Section 3.1.4.2 new_state = sub->state; */ new_state = PJSIP_EVENT_SUB_STATE_TERMINATED; } } else { pj_assert(0); new_state = sub->state; } if (new_state != sub->state && sub->state != PJSIP_EVENT_SUB_STATE_TERMINATED) { sub_set_state(sub, new_state); } if (sub->state == PJSIP_EVENT_SUB_STATE_ACTIVE || sub->state == PJSIP_EVENT_SUB_STATE_PENDING) { /* * Register timer for next subscription refresh, but only when * we're not unsubscribing. Also update default_interval and Expires * header. */ if (sub->default_interval > 0 && !sub->delete_flag) { pjsip_expires_hdr *exp = NULL; /* Could be transaction timeout. */ if (event->src_type == PJSIP_EVENT_RX_MSG) { exp = pjsip_msg_find_hdr(event->src.rdata->msg, PJSIP_H_EXPIRES, NULL); } if (exp) { int delay = exp->ivalue; if (delay > 0) { pj_time_val new_expiry; pj_gettimeofday(&new_expiry); new_expiry.sec += delay; if (sub->timer.id==0 || new_expiry.sec < sub->expiry_time.sec-SECONDS_BEFORE_EXPIRY/2) { //if (delay > 0 && delay < sub->default_interval) { sub->default_interval = delay; sub->uac_expires->ivalue = delay; update_next_refresh(sub, delay); } } } } } /* Call callback. */ if (!sub->delete_flag) { if (sub->cb.on_received_sub_response) { (*sub->cb.on_received_sub_response)(sub, event); } } /* Notify application if we're terminated. */ if (new_state!=old_state && new_state==PJSIP_EVENT_SUB_STATE_TERMINATED) { if (sub->cb.on_sub_terminated) { pj_str_t reason; if (event->src_type == PJSIP_EVENT_RX_MSG) reason = event->src.rdata->msg->line.status.reason; else reason = *pjsip_get_status_text(tsx->status_code); (*sub->cb.on_sub_terminated)(sub, &reason); } } /* Decrement pending tsx count. */ --sub->pending_tsx; pj_assert(sub->pending_tsx >= 0); if (sub->delete_flag && sub->pending_tsx <= 0) { pjsip_event_sub_destroy(sub); } else { pj_mutex_unlock(sub->mutex); } /* DO NOT ACCESS sub FROM NOW ON! IT MIGHT HAVE BEEN DELETED */ }