/// This provides function similar to the pjsip_endpt_send_request method /// but includes setting the SAS trail. It does not support the timeout, token /// or callback options. pj_status_t PJUtils::send_request(pjsip_endpoint* endpt, pjsip_tx_data* tdata) { pjsip_transaction* tsx; pj_status_t status; status = pjsip_tsx_create_uac(&mod_sprout_util, tdata, &tsx); if (status != PJ_SUCCESS) { pjsip_tx_data_dec_ref(tdata); return status; } pjsip_tsx_set_transport(tsx, &tdata->tp_sel); // Set the trail ID in the transaction from the message. set_trail(tsx, get_trail(tdata)); status = pjsip_tsx_send_msg(tsx, NULL); if (status != PJ_SUCCESS) { pjsip_tx_data_dec_ref(tdata); } return status; }
pjsip_tx_data* PJUtils::clone_tdata(pjsip_tx_data* tdata) { pjsip_tx_data* cloned_tdata; pj_status_t status; status = pjsip_endpt_create_tdata(stack_data.endpt, &cloned_tdata); if (status != PJ_SUCCESS) { return NULL; } // Always increment ref counter to 1. pjsip_tx_data_add_ref(cloned_tdata); // Clone the message from the supplied tdata. cloned_tdata->msg = pjsip_msg_clone(cloned_tdata->pool, tdata->msg); if (cloned_tdata->msg == NULL) { pjsip_tx_data_dec_ref(cloned_tdata); cloned_tdata = NULL; } // Copy the trail identifier to the cloned message. set_trail(cloned_tdata, get_trail(tdata)); if (tdata->msg->type == PJSIP_REQUEST_MSG) { // Substitute the branch value in the top Via header with a unique // branch identifier. generate_new_branch_id(cloned_tdata); } // If the original message already had a specified transport set this // on the clone. (Must use pjsip_tx_data_set_transport to ensure // reference counts get updated.) if (tdata->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) { pjsip_tx_data_set_transport(cloned_tdata, &tdata->tp_sel); } // If the message has any addr in dest_info, copy that if (tdata->dest_info.addr.count != 0) { pj_memcpy(&cloned_tdata->dest_info, &tdata->dest_info, sizeof(cloned_tdata->dest_info)); } return cloned_tdata; }
pj_status_t PJUtils::create_response_fwd(pjsip_endpoint* endpt, pjsip_rx_data* rdata, unsigned options, pjsip_tx_data** p_tdata) { pj_status_t status = pjsip_endpt_create_response_fwd(endpt, rdata, options, p_tdata); if (status == PJ_SUCCESS) { // Copy the SAS trail across from the request. set_trail(*p_tdata, get_trail(rdata)); } return status; }
pj_status_t PJUtils::create_response(pjsip_endpoint* endpt, const pjsip_rx_data* rdata, int st_code, const pj_str_t* st_text, pjsip_tx_data** p_tdata) { pj_status_t status = pjsip_endpt_create_response(endpt, rdata, st_code, st_text, p_tdata); if (status == PJ_SUCCESS) { // Copy the SAS trail across from the request. set_trail(*p_tdata, get_trail(rdata)); } return status; }
static pj_bool_t on_rx_msg(pjsip_rx_data* rdata) { // Before we start, get a timestamp. This will track the time from // receiving a message to forwarding it on (or rejecting it). struct rx_msg_qe qe; if (clock_gettime(CLOCK_MONOTONIC, &qe.rx_time) != 0) { LOG_ERROR("Failed to get receive timestamp: %s", strerror(errno)); return PJ_TRUE; } // Do logging. local_log_rx_msg(rdata); sas_log_rx_msg(rdata); // Clone the message and queue it to a scheduler thread. pjsip_rx_data* clone_rdata; pj_status_t status = pjsip_rx_data_clone(rdata, 0, &clone_rdata); if (status != PJ_SUCCESS) { // Failed to clone the message, so drop it. LOG_ERROR("Failed to clone incoming message (%s)", PJUtils::pj_status_to_string(status).c_str()); return PJ_TRUE; } // Make sure the trail identifier is passed across. set_trail(clone_rdata, get_trail(rdata)); // @TODO - need to think about back-pressure mechanisms. For example, // should we have a maximum depth of queue and drop messages after that? // May be better to hold on to the message until the queue has space - this // will force back pressure on the particular TCP connection. Or should we // have a queue per transport and round-robin them? LOG_DEBUG("Queuing cloned received message %p for worker threads", clone_rdata); qe.rdata = clone_rdata; rx_msg_q.push(qe); // return TRUE to flag that we have absorbed the incoming message. return PJ_TRUE; }
void RegStore::send_notify(AoR::Subscription* s, int cseq, AoR::Binding* b, std::string b_id, SAS::TrailId trail) { pjsip_tx_data* tdata_notify = NULL; std::map<std::string, AoR::Binding> bindings; bindings.insert(std::pair<std::string, RegStore::AoR::Binding>(b_id, *b)); pj_status_t status = NotifyUtils::create_notify(&tdata_notify, s, "aor", cseq, bindings, NotifyUtils::DocState::PARTIAL, NotifyUtils::RegistrationState::ACTIVE, NotifyUtils::ContactState::TERMINATED, NotifyUtils::ContactEvent::EXPIRED, NotifyUtils::SubscriptionState::ACTIVE, (s->_expires - time(NULL))); if (status == PJ_SUCCESS) { set_trail(tdata_notify, trail); status = PJUtils::send_request(tdata_notify, 0, NULL, NULL, true); } }
pj_status_t PJUtils::create_response(pjsip_endpoint* endpt, const pjsip_rx_data* rdata, int st_code, const pj_str_t* st_text, pjsip_tx_data** p_tdata) { pj_status_t status = pjsip_endpt_create_response(endpt, rdata, st_code, st_text, p_tdata); if (status == PJ_SUCCESS) { // Copy the SAS trail across from the request. set_trail(*p_tdata, get_trail(rdata)); // Some headers should always be copied onto responses, like // charging headers PJUtils::clone_header(&STR_P_C_V, rdata->msg_info.msg, (*p_tdata)->msg, (*p_tdata)->pool); PJUtils::clone_header(&STR_P_C_F_A, rdata->msg_info.msg, (*p_tdata)->msg, (*p_tdata)->pool); } return status; }
pjsip_tx_data* PJUtils::clone_tdata(pjsip_tx_data* tdata) { pjsip_tx_data* cloned_tdata; pj_status_t status; status = pjsip_endpt_create_tdata(stack_data.endpt, &cloned_tdata); if (status != PJ_SUCCESS) { return NULL; } // Always increment ref counter to 1. pjsip_tx_data_add_ref(cloned_tdata); // Clone the message from the supplied tdata. cloned_tdata->msg = pjsip_msg_clone(cloned_tdata->pool, tdata->msg); if (cloned_tdata->msg == NULL) { pjsip_tx_data_dec_ref(cloned_tdata); cloned_tdata = NULL; } // Copy the trail identifier to the cloned message. set_trail(cloned_tdata, get_trail(tdata)); if (tdata->msg->type == PJSIP_REQUEST_MSG) { // Substitute the branch value in the top Via header with a unique // branch identifier. pjsip_via_hdr* via = (pjsip_via_hdr*) pjsip_msg_find_hdr(cloned_tdata->msg, PJSIP_H_VIA, NULL); via->branch_param.ptr = (char*) pj_pool_alloc(cloned_tdata->pool, PJSIP_MAX_BRANCH_LEN); via->branch_param.slen = PJSIP_RFC3261_BRANCH_LEN; pj_memcpy(via->branch_param.ptr, PJSIP_RFC3261_BRANCH_ID, PJSIP_RFC3261_BRANCH_LEN); pj_str_t tmp; tmp.ptr = via->branch_param.ptr + PJSIP_RFC3261_BRANCH_LEN + 2; // I have absolutely no idea what the following two lines do, but it // doesn't seem to work without them! *(tmp.ptr-2) = (pj_int8_t)(via->branch_param.slen+73); *(tmp.ptr-1) = (pj_int8_t)(via->branch_param.slen+99); pj_generate_unique_string( &tmp ); via->branch_param.slen = PJSIP_MAX_BRANCH_LEN; } // If the original message already had a specified transport set this // on the clone. (Must use pjsip_tx_data_set_transport to ensure // reference counts get updated.) if (tdata->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) { pjsip_tx_data_set_transport(cloned_tdata, &tdata->tp_sel); } // If the message has any addr in dest_info, copy that if (tdata->dest_info.addr.count != 0) { pj_memcpy(&cloned_tdata->dest_info, &tdata->dest_info, sizeof(cloned_tdata->dest_info)); } return cloned_tdata; }
static void sas_log_rx_msg(pjsip_rx_data* rdata) { SAS::TrailId trail = 0; if (rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG) { // Message is a response, so try to correlate to an existing UAC // transaction using the top-most Via header. pj_str_t key; pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAC, &rdata->msg_info.cseq->method, rdata); pjsip_transaction* tsx = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE); if (tsx) { // Found the UAC transaction, so get the trail if there is one. trail = get_trail(tsx); // Unlock tsx because it is locked in find_tsx() pj_grp_lock_release(tsx->grp_lock); } } else if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD) { // Message is an ACK, so try to correlate it to the existing UAS // transaction using the top-most Via header. pj_str_t key; pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_UAS_ROLE, &rdata->msg_info.cseq->method, rdata); pjsip_transaction* tsx = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE); if (tsx) { // Found the UAS transaction, so get the trail if there is one. trail = get_trail(tsx); // Unlock tsx because it is locked in find_tsx() pj_grp_lock_release(tsx->grp_lock); } } else if (rdata->msg_info.msg->line.req.method.id == PJSIP_CANCEL_METHOD) { // Message is a CANCEL request chasing an INVITE, so we want to try to // correlate it to the INVITE trail for the purposes of SAS tracing. pj_str_t key; pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_UAS_ROLE, pjsip_get_invite_method(), rdata); pjsip_transaction* tsx = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE); if (tsx) { // Found the INVITE UAS transaction, so get the trail if there is one. trail = get_trail(tsx); // Unlock tsx because it is locked in find_tsx() pj_grp_lock_release(tsx->grp_lock); } } if (trail == 0) { // The message doesn't correlate to an existing trail, so create a new // one. trail = SAS::new_trail(1u); } // Store the trail in the message as it gets passed up the stack. set_trail(rdata, trail); // Log the message event. SAS::Event event(trail, SASEvent::RX_SIP_MSG, 1u); event.add_static_param(pjsip_transport_get_type_from_flag(rdata->tp_info.transport->flag)); event.add_static_param(rdata->pkt_info.src_port); event.add_var_param(rdata->pkt_info.src_name); event.add_var_param(rdata->msg_info.len, rdata->msg_info.msg_buf); SAS::report_event(event); }
static pj_bool_t on_rx_msg(pjsip_rx_data* rdata) { // Do logging. local_log_rx_msg(rdata); sas_log_rx_msg(rdata); requests_counter->increment(); // Check whether the request should be processed if (!(load_monitor->admit_request()) && (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) && (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD)) { // Discard non-ACK requests if there are no available tokens. // Respond statelessly with a 503 Service Unavailable, including a // Retry-After header with a zero length timeout. LOG_DEBUG("Rejected request due to overload"); pjsip_cid_hdr* cid = (pjsip_cid_hdr*)rdata->msg_info.cid; SAS::TrailId trail = get_trail(rdata); SAS::Marker start_marker(trail, MARKER_ID_START, 1u); SAS::report_marker(start_marker); SAS::Event event(trail, SASEvent::SIP_OVERLOAD, 0); event.add_static_param(load_monitor->get_target_latency()); event.add_static_param(load_monitor->get_current_latency()); event.add_static_param(load_monitor->get_rate_limit()); SAS::report_event(event); PJUtils::report_sas_to_from_markers(trail, rdata->msg_info.msg); if ((rdata->msg_info.msg->line.req.method.id == PJSIP_REGISTER_METHOD) || ((pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, pjsip_get_subscribe_method())) == 0) || ((pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, pjsip_get_notify_method())) == 0)) { // Omit the Call-ID for these requests, as the same Call-ID can be // reused over a long period of time and produce huge SAS trails. PJUtils::mark_sas_call_branch_ids(trail, NULL, rdata->msg_info.msg); } else { PJUtils::mark_sas_call_branch_ids(trail, cid, rdata->msg_info.msg); } SAS::Marker end_marker(trail, MARKER_ID_END, 1u); SAS::report_marker(end_marker); pjsip_retry_after_hdr* retry_after = pjsip_retry_after_hdr_create(rdata->tp_info.pool, 0); PJUtils::respond_stateless(stack_data.endpt, rdata, PJSIP_SC_SERVICE_UNAVAILABLE, NULL, (pjsip_hdr*)retry_after, NULL); // We no longer terminate TCP connections on overload as the shutdown has // to wait for existing transactions to end and therefore it takes too // long to get feedback to the downstream node. We expect downstream nodes // to rebalance load if possible triggered by receipt of the 503 responses. overload_counter->increment(); return PJ_TRUE; } // Check that the worker threads are not all deadlocked. if (rx_msg_q.is_deadlocked()) { // The queue has not been serviced for sufficiently long to imply that // all the worker threads are deadlock, so exit the process so it will be // restarted. LOG_ERROR("Detected worker thread deadlock - exiting"); abort(); } // Before we start, get a timestamp. This will track the time from // receiving a message to forwarding it on (or rejecting it). struct rx_msg_qe qe; qe.stop_watch.start(); // Notify the connection tracker that the transport is active. connection_tracker->connection_active(rdata->tp_info.transport); // Clone the message and queue it to a scheduler thread. pjsip_rx_data* clone_rdata; pj_status_t status = pjsip_rx_data_clone(rdata, 0, &clone_rdata); if (status != PJ_SUCCESS) { // Failed to clone the message, so drop it. LOG_ERROR("Failed to clone incoming message (%s)", PJUtils::pj_status_to_string(status).c_str()); return PJ_TRUE; } // Make sure the trail identifier is passed across. set_trail(clone_rdata, get_trail(rdata)); // @TODO - need to think about back-pressure mechanisms. For example, // should we have a maximum depth of queue and drop messages after that? // May be better to hold on to the message until the queue has space - this // will force back pressure on the particular TCP connection. Or should we // have a queue per transport and round-robin them? LOG_DEBUG("Queuing cloned received message %p for worker threads", clone_rdata); qe.rdata = clone_rdata; // Track the current queue size queue_size_accumulator->accumulate(rx_msg_q.size()); rx_msg_q.push(qe); // return TRUE to flag that we have absorbed the incoming message. return PJ_TRUE; }
// LCOV_EXCL_START - can't meaningfully test SAS in UT static void sas_log_rx_msg(pjsip_rx_data* rdata) { bool first_message_in_trail = false; SAS::TrailId trail = 0; // Look for the SAS Trail ID for the corresponding transaction object. // // Note that we are NOT locking the transaction object before we fetch the // trail ID from it. This is deliberate - we cannot get a group lock from // this routine as we may already have obtained the IO lock (which is lower // in the locking hierarchy) higher up the stack. // (e.g. from ioqueue_common_abs::ioqueue_dispatch_read_event) and grabbing // the group lock here may cause us to deadlock with a thread using the locks // in the right order. // // This is safe for the following reasons // - The transaction objects are only ever invalidated by the current thread // (i.e. the transport thread), so we don't need to worry about the tsx // pointers being invalid. // - In principle, the trail IDs (which are 64 bit numbers stored as void*s // since thats the format of the generic PJSIP user data area) might be // being written to as we are reading them, thereby invalidating them. // However, the chances of this happening are exceedingly remote and, if it // ever happened, the worst that could happen is that the trail ID would be // invalid and the log we're about to make unreachable by SAS. This is // assumed to be sufficiently low impact as to be ignorable for practical // purposes. if (rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG) { // Message is a response, so try to correlate to an existing UAC // transaction using the top-most Via header. pj_str_t key; pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAC, &rdata->msg_info.cseq->method, rdata); pjsip_transaction* tsx = pjsip_tsx_layer_find_tsx(&key, PJ_FALSE); if (tsx) { // Found the UAC transaction, so get the trail if there is one. trail = get_trail(tsx); } } else if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD) { // Message is an ACK, so try to correlate it to the existing UAS // transaction using the top-most Via header. pj_str_t key; pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_UAS_ROLE, &rdata->msg_info.cseq->method, rdata); pjsip_transaction* tsx = pjsip_tsx_layer_find_tsx(&key, PJ_FALSE); if (tsx) { // Found the UAS transaction, so get the trail if there is one. trail = get_trail(tsx); } } else if (rdata->msg_info.msg->line.req.method.id == PJSIP_CANCEL_METHOD) { // Message is a CANCEL request chasing an INVITE, so we want to try to // correlate it to the INVITE trail for the purposes of SAS tracing. pj_str_t key; pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_UAS_ROLE, pjsip_get_invite_method(), rdata); pjsip_transaction* tsx = pjsip_tsx_layer_find_tsx(&key, PJ_FALSE); if (tsx) { // Found the INVITE UAS transaction, so get the trail if there is one. trail = get_trail(tsx); } } else if ((rdata->msg_info.msg->line.req.method.id == PJSIP_OPTIONS_METHOD) && (URIClassifier::classify_uri(rdata->msg_info.msg->line.req.uri) == NODE_LOCAL_SIP_URI)) { // This is an OPTIONS poll directed at this node. Don't log it to SAS, and set the trail ID to a sentinel value so we don't log the response either. TRC_DEBUG("Skipping SAS logging for OPTIONS request"); set_trail(rdata, DONT_LOG_TO_SAS); return; } if (trail == 0) { // The message doesn't correlate to an existing trail, so create a new // one. // If SAS::new_trail returns 0 or DONT_LOG_TO_SAS, keep going. while ((trail == 0) || (trail == DONT_LOG_TO_SAS)) { trail = SAS::new_trail(1u); } first_message_in_trail = true; } // Store the trail in the message as it gets passed up the stack. set_trail(rdata, trail); // Raise SAS markers on the first message in a trail only - subsequent // messages with the same trail ID don't need additional markers if (first_message_in_trail) { PJUtils::report_sas_to_from_markers(trail, rdata->msg_info.msg); pjsip_cid_hdr* cid = (pjsip_cid_hdr*)rdata->msg_info.cid; PJUtils::mark_sas_call_branch_ids(trail, cid, rdata->msg_info.msg); } // Log the message event. SAS::Event event(trail, SASEvent::RX_SIP_MSG, 0); event.add_static_param(pjsip_transport_get_type_from_flag(rdata->tp_info.transport->flag)); event.add_static_param(rdata->pkt_info.src_port); event.add_var_param(rdata->pkt_info.src_name); event.add_compressed_param(rdata->msg_info.len, rdata->msg_info.msg_buf, &SASEvent::PROFILE_SIP); SAS::report_event(event); }
/// This provides function similar to the pjsip_endpt_send_request method /// but includes setting the SAS trail. pj_status_t PJUtils::send_request(pjsip_tx_data* tdata, int retries, void* token, pjsip_endpt_send_callback cb) { pjsip_transaction* tsx; pj_status_t status = PJ_SUCCESS; LOG_DEBUG("Sending standalone request statefully"); // Allocate temporary storage for the request. StatefulSendState* sss = new StatefulSendState; // Store the user supplied callback and token. sss->user_token = token; sss->user_cb = cb; if (tdata->tp_sel.type != PJSIP_TPSELECTOR_TRANSPORT) { // No transport determined, so resolve the next hop for the message. resolve_next_hop(tdata, retries, sss->servers, get_trail(tdata)); if (!sss->servers.empty()) { // Set up the destination information for the first server. sss->current_server = 0; set_dest_info(tdata, sss->servers[sss->current_server]); } else { // No servers found. status = PJ_ENOTFOUND; } } if (status == PJ_SUCCESS) { // We have servers to send the request to, so allocate a transaction. status = pjsip_tsx_create_uac(&mod_sprout_util, tdata, &tsx); if (status == PJ_SUCCESS) { // Set the trail ID in the transaction from the message. set_trail(tsx, get_trail(tdata)); // Set up the module data for the new transaction to reference // the state information. tsx->mod_data[mod_sprout_util.id] = sss; if (tdata->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) { // Transport has already been determined, so copy it across to the // transaction. LOG_DEBUG("Transport already determined"); pjsip_tsx_set_transport(tsx, &tdata->tp_sel); } // Store the message and add a reference to prevent the transaction layer // freeing it. sss->tdata = tdata; pjsip_tx_data_add_ref(tdata); LOG_DEBUG("Sending request"); status = pjsip_tsx_send_msg(tsx, tdata); } } if (status != PJ_SUCCESS) { // The assumption here is that, if pjsip_tsx_send_msg returns an error // the on_tsx_state callback will not get called, so it is safe to free // off the state data and request here. Also, this is an unexpected // error rather than an indication that the destination server is down, // so we don't blacklist. LOG_ERROR("Failed to send request to %s", PJUtils::uri_to_string(PJSIP_URI_IN_ROUTING_HDR, PJUtils::next_hop(tdata->msg)).c_str()); // Only free the state data if there are no more references to it pj_status_t dec_status = pjsip_tx_data_dec_ref(tdata); if (dec_status == PJSIP_EBUFDESTROYED) { delete sss; } } return status; }
static void on_tsx_state(pjsip_transaction* tsx, pjsip_event* event) { StatefulSendState* sss; bool retrying = false; if ((mod_sprout_util.id < 0) || (event->type != PJSIP_EVENT_TSX_STATE)) { return; } sss = (StatefulSendState*)tsx->mod_data[mod_sprout_util.id]; if (sss == NULL) { return; } if (!sss->servers.empty()) { // The target for the request came from the resolver, so check to see // if the request failed. if ((tsx->state == PJSIP_TSX_STATE_COMPLETED) || (tsx->state == PJSIP_TSX_STATE_TERMINATED)) { // Transaction has completed or terminated. We need to look at both // states as // - timeouts and transport errors cause an immediate transition // to terminated state, bypassing completed state // - a 5xx response causes a transition to completed state, with a // possible delay until the transition to terminated state (5 seconds // for UDP transport), which would needlessly delay any retry. if ((event->body.tsx_state.type == PJSIP_EVENT_TIMER) || (event->body.tsx_state.type == PJSIP_EVENT_TRANSPORT_ERROR) || (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 500))) { // Either transaction failed on a timeout, transport error or received // 5xx error, so blacklist the failed target. LOG_DEBUG("Transaction failed with retriable error"); if ((event->body.tsx_state.type == PJSIP_EVENT_TIMER) || (event->body.tsx_state.type == PJSIP_EVENT_TRANSPORT_ERROR)) { // Either the connection failed, or the server didn't respond within // the timeout, so blacklist it. We don't blacklist servers that // return 5xx errors as this may indicate a transient overload. PJUtils::blacklist_server(sss->servers[sss->current_server]); } // Can we do a retry? ++sss->current_server; if (sss->current_server < (int)sss->servers.size()) { // More servers to try, so allocate a new branch ID and transaction. LOG_DEBUG("Attempt to resend request to next destination server"); pjsip_tx_data* tdata = sss->tdata; pjsip_transaction* retry_tsx; PJUtils::generate_new_branch_id(tdata); pj_status_t status = pjsip_tsx_create_uac(&mod_sprout_util, tdata, &retry_tsx); if (status == PJ_SUCCESS) { // The new transaction has been set up. // Set the trail ID in the transaction from the message. set_trail(retry_tsx, get_trail(tdata)); // Set up the module data for the new transaction to reference // the state information. retry_tsx->mod_data[mod_sprout_util.id] = sss; // Increment the reference count of the request as we are passing // it to a new transaction. pjsip_tx_data_add_ref(tdata); // Copy across the destination information for a retry and try to // resend the request. PJUtils::set_dest_info(tdata, sss->servers[sss->current_server]); status = pjsip_tsx_send_msg(retry_tsx, tdata); if (status == PJ_SUCCESS) { // Successfully sent a retry. Make sure this callback isn't // invoked again for the previous transaction. tsx->mod_data[mod_sprout_util.id] = NULL; retrying = true; } } } } } } if ((!retrying) && (tsx->status_code >= 200)) { // Call the user callback, if any, and prevent the callback to be called again // by clearing the transaction's module_data. LOG_DEBUG("Request transaction completed, status code = %d", tsx->status_code); tsx->mod_data[mod_sprout_util.id] = NULL; if (sss->user_cb != NULL) { (*sss->user_cb)(sss->user_token, event); } // The transaction has completed, so decrement our reference to the tx_data // and free the state data. pjsip_tx_data_dec_ref(sss->tdata); delete sss; } }
/// Write to the registration store. RegStore::AoR* write_to_store(RegStore* primary_store, ///<store to write to std::string aor, ///<address of record to write to pjsip_rx_data* rdata, ///<received message to read headers from int now, ///<time now int& expiry, ///<[out] longest expiry time bool& out_is_initial_registration, RegStore::AoR* backup_aor, ///<backup data if no entry in store RegStore* backup_store, ///<backup store to read from if no entry in store and no backup data bool send_notify, ///<whether to send notifies (only send when writing to the local store) std::string private_id, ///<private id that the binding was registered with SAS::TrailId trail) { // Get the call identifier and the cseq number from the respective headers. std::string cid = PJUtils::pj_str_to_string((const pj_str_t*)&rdata->msg_info.cid->id); int cseq = rdata->msg_info.cseq->cseq; NotifyUtils::ContactEvent contact_event = NotifyUtils::ContactEvent::CREATED; // Find the expire headers in the message. pjsip_msg *msg = rdata->msg_info.msg; pjsip_expires_hdr* expires = (pjsip_expires_hdr*)pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL); // The registration service uses optimistic locking to avoid concurrent // updates to the same AoR conflicting. This means we have to loop // reading, updating and writing the AoR until the write is successful. RegStore::AoR* aor_data = NULL; bool backup_aor_alloced = false; bool is_initial_registration = true; std::map<std::string, RegStore::AoR::Binding> bindings_for_notify; bool all_bindings_expired = false; do { // delete NULL is safe, so we can do this on every iteration. delete aor_data; // Find the current bindings for the AoR. aor_data = primary_store->get_aor_data(aor, trail); LOG_DEBUG("Retrieved AoR data %p", aor_data); if (aor_data == NULL) { // Failed to get data for the AoR because there is no connection // to the store. // LCOV_EXCL_START - local store (used in testing) never fails LOG_ERROR("Failed to get AoR binding for %s from store", aor.c_str()); break; // LCOV_EXCL_STOP } // If we don't have any bindings, try the backup AoR and/or store. if (aor_data->bindings().empty()) { if ((backup_aor == NULL) && (backup_store != NULL)) { backup_aor = backup_store->get_aor_data(aor, trail); backup_aor_alloced = (backup_aor != NULL); } if ((backup_aor != NULL) && (!backup_aor->bindings().empty())) { for (RegStore::AoR::Bindings::const_iterator i = backup_aor->bindings().begin(); i != backup_aor->bindings().end(); ++i) { RegStore::AoR::Binding* src = i->second; RegStore::AoR::Binding* dst = aor_data->get_binding(i->first); *dst = *src; } for (RegStore::AoR::Subscriptions::const_iterator i = backup_aor->subscriptions().begin(); i != backup_aor->subscriptions().end(); ++i) { RegStore::AoR::Subscription* src = i->second; RegStore::AoR::Subscription* dst = aor_data->get_subscription(i->first); *dst = *src; } } } is_initial_registration = is_initial_registration && aor_data->bindings().empty(); // Now loop through all the contacts. If there are multiple contacts in // the contact header in the SIP message, pjsip parses them to separate // contact header structures. pjsip_contact_hdr* contact = (pjsip_contact_hdr*)pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, NULL); while (contact != NULL) { // Calculate the expiry period for the updated binding. expiry = (contact->expires != -1) ? contact->expires : (expires != NULL) ? expires->ivalue : max_expires; if (expiry > max_expires) { // Expiry is too long, set it to the maximum. expiry = max_expires; } if (contact->star) { // Wildcard contact, which can only be used to clear all bindings for // the AoR (and only if the expiry is 0). It won't clear any emergency // bindings aor_data->clear(false); break; } pjsip_uri* uri = (contact->uri != NULL) ? (pjsip_uri*)pjsip_uri_get_uri(contact->uri) : NULL; if ((uri != NULL) && (PJSIP_URI_SCHEME_IS_SIP(uri))) { // The binding identifier is based on the +sip.instance parameter if // it is present. If not the contact URI is used instead. std::string contact_uri = PJUtils::uri_to_string(PJSIP_URI_IN_CONTACT_HDR, uri); std::string binding_id = get_binding_id(contact); if (binding_id == "") { binding_id = contact_uri; } LOG_DEBUG(". Binding identifier for contact = %s", binding_id.c_str()); // Find the appropriate binding in the bindings list for this AoR. RegStore::AoR::Binding* binding = aor_data->get_binding(binding_id); if ((cid != binding->_cid) || (cseq > binding->_cseq)) { // Either this is a new binding, has come from a restarted device, or // is an update to an existing binding. binding->_uri = contact_uri; if (cid != binding->_cid) { // New binding, set contact event to created contact_event = NotifyUtils::ContactEvent::CREATED; } else { // Updated binding, set contact event to refreshed contact_event = NotifyUtils::ContactEvent::REFRESHED; } // TODO Examine Via header to see if we're the first hop // TODO Only if we're not the first hop, check that the top path header has "ob" parameter // Get the Path headers, if present. RFC 3327 allows us the option of // rejecting a request with a Path header if there is no corresponding // "path" entry in the Supported header but we don't do so on the assumption // that the edge proxy knows what it's doing. binding->_path_headers.clear(); pjsip_routing_hdr* path_hdr = (pjsip_routing_hdr*) pjsip_msg_find_hdr_by_name(msg, &STR_PATH, NULL); while (path_hdr) { std::string path = PJUtils::uri_to_string(PJSIP_URI_IN_ROUTING_HDR, path_hdr->name_addr.uri); LOG_DEBUG("Path header %s", path.c_str()); // Extract all the paths from this header. Utils::split_string(path, ',', binding->_path_headers, 0, true); // Look for the next header. path_hdr = (pjsip_routing_hdr*) pjsip_msg_find_hdr_by_name(msg, &STR_PATH, path_hdr->next); } binding->_cid = cid; binding->_cseq = cseq; binding->_priority = contact->q1000; binding->_params.clear(); pjsip_param* p = contact->other_param.next; while ((p != NULL) && (p != &contact->other_param)) { std::string pname = PJUtils::pj_str_to_string(&p->name); std::string pvalue = PJUtils::pj_str_to_string(&p->value); // Skip parameters that must not be user-specified if (pname != "pub-gruu") { binding->_params[pname] = pvalue; } p = p->next; } binding->_private_id = private_id; binding->_emergency_registration = PJUtils::is_emergency_registration(contact); // If the new expiry is less than the current expiry, and it's an emergency registration, // don't update the expiry time if ((binding->_expires >= now + expiry) && (binding->_emergency_registration)) { LOG_DEBUG("Don't reduce expiry time for an emergency registration"); } else { binding->_expires = now + expiry; } // If this is a de-registration, don't send NOTIFYs, as this is covered in // expire_bindings which is called when the aor_data is saved. if ((expiry != 0) && (!binding->_emergency_registration)) { bindings_for_notify.insert(std::pair<std::string, RegStore::AoR::Binding>(binding_id, *binding)); } if (analytics != NULL) { // Generate an analytics log for this binding update. analytics->registration(aor, binding_id, contact_uri, expiry); } } } contact = (pjsip_contact_hdr*)pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, contact->next); } // Finally, update the cseq aor_data->_notify_cseq++; } while (!primary_store->set_aor_data(aor, aor_data, send_notify, trail, all_bindings_expired)); // If we allocated the backup AoR, tidy up. if (backup_aor_alloced) { delete backup_aor; } // Finally, send out SIP NOTIFYs for any subscriptions if (send_notify) { for (RegStore::AoR::Subscriptions::const_iterator i = aor_data->subscriptions().begin(); i != aor_data->subscriptions().end(); ++i) { RegStore::AoR::Subscription* subscription = i->second; if (subscription->_expires > now) { pjsip_tx_data* tdata_notify; pj_status_t status = NotifyUtils::create_notify(&tdata_notify, subscription, aor, aor_data->_notify_cseq, bindings_for_notify, NotifyUtils::DocState::PARTIAL, NotifyUtils::RegistrationState::ACTIVE, NotifyUtils::ContactState::ACTIVE, contact_event, NotifyUtils::SubscriptionState::ACTIVE, (subscription->_expires - now)); if (status == PJ_SUCCESS) { set_trail(tdata_notify, trail); status = PJUtils::send_request(tdata_notify, 0, NULL, NULL, true); } } } } if (all_bindings_expired) { LOG_DEBUG("All bindings have expired - triggering deregistration at the HSS"); hss->update_registration_state(aor, "", HSSConnection::DEREG_USER, 0); } out_is_initial_registration = is_initial_registration; return aor_data; }
void process_subscription_request(pjsip_rx_data* rdata) { pj_status_t status; int st_code = PJSIP_SC_OK; SAS::TrailId trail = get_trail(rdata); // Get the URI from the To header and check it is a SIP or SIPS URI. pjsip_uri* uri = (pjsip_uri*)pjsip_uri_get_uri(rdata->msg_info.to->uri); pjsip_msg *msg = rdata->msg_info.msg; pjsip_expires_hdr* expires = (pjsip_expires_hdr*)pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL); int expiry = (expires != NULL) ? expires->ivalue : DEFAULT_SUBSCRIPTION_EXPIRES; if (expiry > max_expires) { // Expiry is too long, set it to the maximum. expiry = max_expires; } if ((!PJSIP_URI_SCHEME_IS_SIP(uri)) && (!PJSIP_URI_SCHEME_IS_TEL(uri))) { // Reject a non-SIP/TEL URI with 404 Not Found (RFC3261 isn't clear // whether 404 is the right status code - it says 404 should be used if // the AoR isn't valid for the domain in the RequestURI). LOG_ERROR("Rejecting subscribe request using invalid URI scheme"); SAS::Event event(trail, SASEvent::SUBSCRIBE_FAILED_EARLY_URLSCHEME, 0); // Can't log the public ID as the subscribe has failed too early SAS::report_event(event); PJUtils::respond_stateless(stack_data.endpt, rdata, PJSIP_SC_NOT_FOUND, NULL, NULL, NULL); return; } bool emergency_subscription = false; pjsip_contact_hdr* contact_hdr = (pjsip_contact_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, NULL); while (contact_hdr != NULL) { emergency_subscription = PJUtils::is_emergency_registration(contact_hdr); if (!emergency_subscription) { break; } contact_hdr = (pjsip_contact_hdr*) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, contact_hdr->next); } if (emergency_subscription) { // Reject a subscription with a Contact header containing a contact address // that's been registered for emergency service. LOG_ERROR("Rejecting subscribe request from emergency registration"); SAS::Event event(trail, SASEvent::SUBSCRIBE_FAILED_EARLY_EMERGENCY, 0); // Can't log the public ID as the subscribe has failed too early SAS::report_event(event); // Allow-Events is a mandatory header on 489 responses. pjsip_generic_string_hdr* allow_events_hdr = pjsip_generic_string_hdr_create(rdata->tp_info.pool, &STR_ALLOW_EVENTS, &STR_REG); PJUtils::respond_stateless(stack_data.endpt, rdata, PJSIP_SC_BAD_EVENT, NULL, (pjsip_hdr*)allow_events_hdr, NULL); return; } ACR* acr = acr_factory->get_acr(get_trail(rdata), CALLING_PARTY, ACR::requested_node_role(rdata->msg_info.msg)); acr->rx_request(rdata->msg_info.msg, rdata->pkt_info.timestamp); // Canonicalize the public ID from the URI in the To header. std::string public_id = PJUtils::public_id_from_uri(uri); LOG_DEBUG("Process SUBSCRIBE for public ID %s", public_id.c_str()); // Get the call identifier from the headers. std::string cid = PJUtils::pj_str_to_string((const pj_str_t*)&rdata->msg_info.cid->id);; // Add SAS markers to the trail attached to the message so the trail // becomes searchable. SAS::Event event(trail, SASEvent::SUBSCRIBE_START, 0); event.add_var_param(public_id); SAS::report_event(event); LOG_DEBUG("Report SAS start marker - trail (%llx)", trail); SAS::Marker start_marker(trail, MARKER_ID_START, 1u); SAS::report_marker(start_marker); PJUtils::report_sas_to_from_markers(trail, rdata->msg_info.msg); PJUtils::mark_sas_call_branch_ids(trail, NULL, rdata->msg_info.msg); // Query the HSS for the associated URIs. std::vector<std::string> uris; std::map<std::string, Ifcs> ifc_map; // Subscriber must have already registered to be making a subscribe std::string state; HTTPCode http_code = hss->get_registration_data(public_id, state, ifc_map, uris, trail); if ((http_code != HTTP_OK) || (state != "REGISTERED")) { // We failed to get the list of associated URIs. This indicates that the // HSS is unavailable, the public identity doesn't exist or the public // identity doesn't belong to the private identity. // The client shouldn't retry when the subscriber isn't present in the // HSS; reject with a 403 in this case. // // The client should retry on timeout but no other Clearwater nodes should // (as Sprout will already have retried on timeout). Reject with a 504 // (503 is used for overload). st_code = PJSIP_SC_SERVER_TIMEOUT; if (http_code == HTTP_NOT_FOUND) { st_code = PJSIP_SC_FORBIDDEN; } LOG_ERROR("Rejecting SUBSCRIBE request"); PJUtils::respond_stateless(stack_data.endpt, rdata, st_code, NULL, NULL, NULL); delete acr; return; } // Determine the AOR from the first entry in the uris array. std::string aor = uris.front(); LOG_DEBUG("aor = %s", aor.c_str()); LOG_DEBUG("SUBSCRIBE for public ID %s uses AOR %s", public_id.c_str(), aor.c_str()); // Get the system time in seconds for calculating absolute expiry times. int now = time(NULL); // Write to the local store, checking the remote store if there is no entry locally. If the write to the local store succeeds, then write to the remote store. pjsip_tx_data* tdata_notify = NULL; RegStore::AoR* aor_data = NULL; std::string subscription_id; pj_status_t notify_status = write_subscriptions_to_store(store, aor, rdata, now, NULL, remote_store, &tdata_notify, &aor_data, true, subscription_id, trail); if (aor_data != NULL) { // Log the subscriptions. log_subscriptions(aor, aor_data); // If we have a remote store, try to store this there too. We don't worry // about failures in this case. if (remote_store != NULL) { RegStore::AoR* remote_aor_data = NULL; std::string ignore; write_subscriptions_to_store(remote_store, aor, rdata, now, aor_data, NULL, &tdata_notify, &remote_aor_data, false, ignore, trail); delete remote_aor_data; } } else { // Failed to connect to the local store. Reject the subscribe with a 500 // response. // LCOV_EXCL_START - the can't fail to connect to the store we use for UT st_code = PJSIP_SC_INTERNAL_SERVER_ERROR; // LCOV_EXCL_STOP } // Build and send the reply. pjsip_tx_data* tdata; status = PJUtils::create_response(stack_data.endpt, rdata, st_code, NULL, &tdata); if (status != PJ_SUCCESS) { // LCOV_EXCL_START - don't know how to get PJSIP to fail to create a response LOG_ERROR("Error building SUBSCRIBE %d response %s", st_code, PJUtils::pj_status_to_string(status).c_str()); SAS::Event event(trail, SASEvent::SUBSCRIBE_FAILED, 0); event.add_var_param(public_id); std::string error_msg = "Error building SUBSCRIBE (" + std::to_string(st_code) + ") " + PJUtils::pj_status_to_string(status); event.add_var_param(error_msg); SAS::report_event(event); PJUtils::respond_stateless(stack_data.endpt, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL, NULL); delete acr; delete aor_data; return; // LCOV_EXCL_STOP } // Add expires headers pjsip_expires_hdr* expires_hdr = pjsip_expires_hdr_create(tdata->pool, expiry); pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)expires_hdr); // Add the to tag to the response pjsip_to_hdr *to = (pjsip_to_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_TO, NULL); pj_strdup2(tdata->pool, &to->tag, subscription_id.c_str()); // Pass the response to the ACR. acr->tx_response(tdata->msg); // Send the response. status = pjsip_endpt_send_response2(stack_data.endpt, rdata, tdata, NULL, NULL); // Send the ACR and delete it. acr->send_message(); delete acr; // Send the Notify if (tdata_notify != NULL && notify_status == PJ_SUCCESS) { set_trail(tdata_notify, trail); status = PJUtils::send_request(tdata_notify, 0, NULL, NULL, true); if (status != PJ_SUCCESS) { // LCOV_EXCL_START SAS::Event event(trail, SASEvent::NOTIFICATION_FAILED, 0); std::string error_msg = "Failed to send NOTIFY - error code: " + std::to_string(status); event.add_var_param(error_msg); SAS::report_event(event); // LCOV_EXCL_STOP } } LOG_DEBUG("Report SAS end marker - trail (%llx)", trail); SAS::Marker end_marker(trail, MARKER_ID_END, 1u); SAS::report_marker(end_marker); delete aor_data; }
void send_register_to_as(pjsip_rx_data *received_register, pjsip_tx_data *ok_response, AsInvocation& as, int expires, const std::string& served_user, SAS::TrailId trail) { pj_status_t status; pjsip_tx_data *tdata; pjsip_method method; pjsip_method_set(&method, PJSIP_REGISTER_METHOD); pj_str_t user_uri; pj_cstr(&user_uri, served_user.c_str()); pj_str_t as_uri; pj_cstr(&as_uri, as.server_name.c_str()); status = pjsip_endpt_create_request(stack_data.endpt, &method, // Method &as_uri, // Target &stack_data.scscf_uri, // From &user_uri, // To &stack_data.scscf_uri, // Contact NULL, // Auto-generate Call-ID 1, // CSeq NULL, // No body &tdata); // OUT if (status != PJ_SUCCESS) { //LCOV_EXCL_START LOG_ERROR("Failed to build third-party REGISTER request for server %s", as.server_name.c_str()); return; //LCOV_EXCL_STOP } // Expires header based on 200 OK response pjsip_expires_hdr_create(tdata->pool, expires); pjsip_expires_hdr* expires_hdr = pjsip_expires_hdr_create(tdata->pool, expires); pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)expires_hdr); // TODO: modify orig-ioi of P-Charging-Vector and remove term-ioi if (received_register && ok_response) { // Copy P-Access-Network-Info, P-Visited-Network-Id and P-Charging-Vector // from original message PJUtils::clone_header(&STR_P_A_N_I, received_register->msg_info.msg, tdata->msg, tdata->pool); PJUtils::clone_header(&STR_P_V_N_I, received_register->msg_info.msg, tdata->msg, tdata->pool); PJUtils::clone_header(&STR_P_C_V, received_register->msg_info.msg, tdata->msg, tdata->pool); // Copy P-Charging-Function-Addresses from the OK response. PJUtils::clone_header(&STR_P_C_F_A, ok_response->msg, tdata->msg, tdata->pool); // Generate a message body based on Filter Criteria values char buf[MAX_SIP_MSG_SIZE]; pj_str_t sip_type = pj_str("message"); pj_str_t sip_subtype = pj_str("sip"); pj_str_t xml_type = pj_str("application"); pj_str_t xml_subtype = pj_str("3gpp-ims+xml"); // Build up this multipart body incrementally, based on the ServiceInfo, IncludeRegisterRequest and IncludeRegisterResponse fields pjsip_msg_body *final_body = pjsip_multipart_create(tdata->pool, NULL, NULL); // If we only have one part, we don't want a multipart MIME body - store the reference to each one here to use instead pjsip_msg_body *possible_final_body = NULL; int multipart_parts = 0; if (!as.service_info.empty()) { pjsip_multipart_part *xml_part = pjsip_multipart_create_part(tdata->pool); std::string xml_str = "<ims-3gpp><service-info>"+as.service_info+"</service-info></ims-3gpp>"; pj_str_t xml_pj_str; pj_cstr(&xml_pj_str, xml_str.c_str()); xml_part->body = pjsip_msg_body_create(tdata->pool, &xml_type, &xml_subtype, &xml_pj_str), possible_final_body = xml_part->body; multipart_parts++; pjsip_multipart_add_part(tdata->pool, final_body, xml_part); } if (as.include_register_request) { pjsip_multipart_part *request_part = pjsip_multipart_create_part(tdata->pool); pjsip_msg_print(received_register->msg_info.msg, buf, sizeof(buf)); pj_str_t request_str = pj_str(buf); request_part->body = pjsip_msg_body_create(tdata->pool, &sip_type, &sip_subtype, &request_str), possible_final_body = request_part->body; multipart_parts++; pjsip_multipart_add_part(tdata->pool, final_body, request_part); } if (as.include_register_response) { pjsip_multipart_part *response_part = pjsip_multipart_create_part(tdata->pool); pjsip_msg_print(ok_response->msg, buf, sizeof(buf)); pj_str_t response_str = pj_str(buf); response_part->body = pjsip_msg_body_create(tdata->pool, &sip_type, &sip_subtype, &response_str), possible_final_body = response_part->body; multipart_parts++; pjsip_multipart_add_part(tdata->pool, final_body, response_part); } if (multipart_parts == 0) { final_body = NULL; } else if (multipart_parts == 1) { final_body = possible_final_body; } else { // Just use the multipart MIME body you've built up } tdata->msg->body = final_body; } // Set the SAS trail on the request. set_trail(tdata, trail); // Allocate a temporary structure to record the default handling for this // REGISTER, and send it statefully. ThirdPartyRegData* tsxdata = new ThirdPartyRegData; tsxdata->default_handling = as.default_handling; tsxdata->trail = trail; tsxdata->public_id = served_user; pj_status_t resolv_status = PJUtils::send_request(tdata, 0, tsxdata, &send_register_cb); if (resolv_status != PJ_SUCCESS) { delete tsxdata; // LCOV_EXCL_LINE } }
pj_bool_t authenticate_rx_request(pjsip_rx_data* rdata) { TRC_DEBUG("Authentication module invoked"); pj_status_t status; bool is_register = (rdata->msg_info.msg->line.req.method.id == PJSIP_REGISTER_METHOD); SNMP::SuccessFailCountTable* auth_stats_table = NULL; std::string resync; SAS::TrailId trail = get_trail(rdata); if (!needs_authentication(rdata, trail)) { TRC_DEBUG("Request does not need authentication"); return PJ_FALSE; } TRC_DEBUG("Request needs authentication"); rapidjson::Document* av = NULL; const int unauth_sc = is_register ? PJSIP_SC_UNAUTHORIZED : PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED; int sc = unauth_sc; status = PJSIP_EAUTHNOAUTH; pjsip_digest_credential* credentials = get_credentials(rdata); if ((credentials != NULL) && (credentials->response.slen != 0)) { std::string impi = PJUtils::pj_str_to_string(&credentials->username); std::string nonce = PJUtils::pj_str_to_string(&credentials->nonce); uint64_t cas = 0; av = av_store->get_av(impi, nonce, cas, trail); if (!is_register) { // Challenged non-register requests must be SIP digest, so only one table // needed for this case. auth_stats_table = auth_stats_tables->non_register_auth_tbl; } else { if (!pj_strcmp2(&credentials->algorithm, "MD5")) { auth_stats_table = auth_stats_tables->sip_digest_auth_tbl; } else if (!pj_strcmp2(&credentials->algorithm, "AKAv1-MD5")) { auth_stats_table = auth_stats_tables->ims_aka_auth_tbl; } else { // Authorization header did not specify an algorithm, so check the av for // this information instead. if ((av != NULL) && (av->HasMember("aka"))) { auth_stats_table = auth_stats_tables->ims_aka_auth_tbl; } else { // Use the digest table if the AV specified digest, or as a fallback if there was no AV auth_stats_table = auth_stats_tables->sip_digest_auth_tbl; } } } if (auth_stats_table != NULL) { auth_stats_table->increment_attempts(); } // Request contains a response to a previous challenge, so pass it to // the authentication module to verify. TRC_DEBUG("Verify authentication information in request"); status = pjsip_auth_srv_verify2((is_register ? &auth_srv : &auth_srv_proxy), rdata, &sc, (void*)av); if (status == PJ_SUCCESS) { // The authentication information in the request was verified. TRC_DEBUG("Request authenticated successfully"); SAS::Event event(trail, SASEvent::AUTHENTICATION_SUCCESS, 0); SAS::report_event(event); if (auth_stats_table != NULL) { auth_stats_table->increment_successes(); } // Write a tombstone flag back to the AV store, handling contention. // We don't actually expect anything else to be writing to this row in // the AV store, but there is a window condition where we failed to read // from the primary, successfully read from the backup (with a different // CAS value) and then try to write back to the primary, which fails due // to "contention". Store::Status store_status; do { // Set the tomestone flag in the JSON authentication vector. rapidjson::Value tombstone_value; tombstone_value.SetBool(true); av->AddMember("tombstone", tombstone_value, (*av).GetAllocator()); // Store it. If this fails due to contention, read the updated JSON. store_status = av_store->set_av(impi, nonce, av, cas, trail); if (store_status == Store::DATA_CONTENTION) { // LCOV_EXCL_START - No support for contention in UT TRC_DEBUG("Data contention writing tombstone - retry"); delete av; av = av_store->get_av(impi, nonce, cas, trail); if (av == NULL) { store_status = Store::ERROR; } // LCOV_EXCL_STOP } } while (store_status == Store::DATA_CONTENTION); if (store_status != Store::OK) { // LCOV_EXCL_START TRC_ERROR("Tried to tombstone AV for %s/%s after processing an authentication, but failed", impi.c_str(), nonce.c_str()); // LCOV_EXCL_STOP } // If doing AKA authentication, check for an AUTS parameter. We only // check this if the request authenticated as actioning it otherwise // is a potential denial of service attack. if (!pj_strcmp(&credentials->algorithm, &STR_AKAV1_MD5)) { TRC_DEBUG("AKA authentication so check for client resync request"); pjsip_param* p = pjsip_param_find(&credentials->other_param, &STR_AUTS); if (p != NULL) { // Found AUTS parameter, so UE is requesting a resync. We need to // redo the authentication, passing an auts parameter to the HSS // comprising the first 16 octets of the nonce (RAND) and the 14 // octets of the auts parameter. (See TS 33.203 and table 6.3.3 of // TS 29.228 for details.) TRC_DEBUG("AKA SQN resync request from UE"); std::string auts = PJUtils::pj_str_to_string(&p->value); std::string nonce = PJUtils::pj_str_to_string(&credentials->nonce); // Convert the auts and nonce to binary for manipulation nonce = base64_decode(nonce); auts = base64_decode(auts); if ((auts.length() != 14) || (nonce.length() != 32)) { // AUTS and/or nonce are malformed, so reject the request. TRC_WARNING("Invalid auts/nonce on resync request from private identity %.*s", credentials->username.slen, credentials->username.ptr); status = PJSIP_EAUTHINAKACRED; sc = PJSIP_SC_FORBIDDEN; } else { // auts and nonce are as expected, so create the resync string // that needs to be passed to the HSS, and act as if no // authentication information was received. The resync string // should be RAND || AUTS. resync = base64_encode(nonce.substr(0, 16) + auts); status = PJSIP_EAUTHNOAUTH; sc = unauth_sc; } } } if (status == PJ_SUCCESS) { // Request authentication completed, so let the message through to other // modules. Remove any Proxy-Authorization headers first so they are not // passed to downstream devices. We can't do this for Authorization // headers, as these may need to be included in 3rd party REGISTER // messages. while (pjsip_msg_find_remove_hdr(rdata->msg_info.msg, PJSIP_H_PROXY_AUTHORIZATION, NULL) != NULL); delete av; return PJ_FALSE; } } } // The message either has insufficient authentication information, or // has failed authentication. In either case, the message will be // absorbed and responded to by the authentication module, so we need to // add SAS markers so the trail will become searchable. SAS::Marker start_marker(trail, MARKER_ID_START, 1u); SAS::report_marker(start_marker); // Add a SAS end marker SAS::Marker end_marker(trail, MARKER_ID_END, 1u); SAS::report_marker(end_marker); // Create an ACR for the message and pass the request to it. Role is always // considered originating for a REGISTER request. ACR* acr = acr_factory->get_acr(trail, CALLING_PARTY, NODE_ROLE_ORIGINATING); acr->rx_request(rdata->msg_info.msg, rdata->pkt_info.timestamp); pjsip_tx_data* tdata; if ((status == PJSIP_EAUTHNOAUTH) || (status == PJSIP_EAUTHACCNOTFOUND)) { // No authorization information in request, or no authentication vector // found in the store (so request is likely stale), so must issue // challenge. TRC_DEBUG("No authentication information in request or stale nonce, so reject with challenge"); pj_bool_t stale = (status == PJSIP_EAUTHACCNOTFOUND); sc = unauth_sc; if (stale && auth_stats_table != NULL) { auth_stats_table->increment_failures(); } status = PJUtils::create_response(stack_data.endpt, rdata, sc, NULL, &tdata); if (status != PJ_SUCCESS) { // Failed to create a response. This really shouldn't happen, but there // is nothing else we can do. // LCOV_EXCL_START delete acr; return PJ_TRUE; // LCOV_EXCL_STOP } create_challenge(credentials, stale, resync, rdata, tdata); } else { // Authentication failed. std::string error_msg = PJUtils::pj_status_to_string(status); TRC_ERROR("Authentication failed, %s", error_msg.c_str()); if (auth_stats_table != NULL) { auth_stats_table->increment_failures(); } SAS::Event event(trail, SASEvent::AUTHENTICATION_FAILED, 0); event.add_var_param(error_msg); SAS::report_event(event); if (sc != unauth_sc) { // Notify Homestead and the HSS that this authentication attempt // has definitively failed. std::string impi; std::string impu; PJUtils::get_impi_and_impu(rdata, impi, impu); hss->update_registration_state(impu, impi, HSSConnection::AUTH_FAIL, trail); } if (analytics != NULL) { analytics->auth_failure(PJUtils::pj_str_to_string(&credentials->username), PJUtils::public_id_from_uri((pjsip_uri*)pjsip_uri_get_uri(PJSIP_MSG_TO_HDR(rdata->msg_info.msg)->uri))); } status = PJUtils::create_response(stack_data.endpt, rdata, sc, NULL, &tdata); if (status != PJ_SUCCESS) { // Failed to create a response. This really shouldn't happen, but there // is nothing else we can do. // LCOV_EXCL_START delete acr; return PJ_TRUE; // LCOV_EXCL_STOP } } acr->tx_response(tdata->msg); // Issue the challenge response transaction-statefully. This is so that: // * if we challenge an INVITE, the UE can ACK the 407 // * if a challenged request gets retransmitted, we don't repeat the work pjsip_transaction* tsx = NULL; status = pjsip_tsx_create_uas2(NULL, rdata, NULL, &tsx); set_trail(tsx, trail); if (status != PJ_SUCCESS) { // LCOV_EXCL_START - defensive code not hit in UT TRC_WARNING("Couldn't create PJSIP transaction for authentication response: %d" " (sending statelessly instead)", status); // Send the response statelessly in this case - it's better than nothing pjsip_endpt_send_response2(stack_data.endpt, rdata, tdata, NULL, NULL); // LCOV_EXCL_STOP } else { // Let the tsx know about the original message pjsip_tsx_recv_msg(tsx, rdata); // Send our response in this transaction pjsip_tsx_send_msg(tsx, tdata); } // Send the ACR. acr->send(); delete acr; delete av; return PJ_TRUE; }