void SessionExpiresHelper::process_request(pjsip_msg* req, pj_pool_t* pool, SAS::TrailId trail) { // Session expires is only allowed on INVITE and UPDATE methods. pjsip_method* method = &req->line.req.method; if ((pjsip_method_cmp(method, pjsip_get_invite_method()) != 0) && (pjsip_method_cmp(method, &METHOD_UPDATE) != 0)) { return; } // Store if this is the initial transaction on a dialog, and if the UAC // supports session timers. We need both of these when processing the // response. _initial_request = (PJSIP_MSG_TO_HDR(req)->tag.slen == 0); _uac_supports_timer = timer_supported(req); // Find the session-expires header (if present) and the minimum // session-expires. Note that the latter has a default value. pjsip_session_expires_hdr* se_hdr = (pjsip_session_expires_hdr*) pjsip_msg_find_hdr_by_name(req, &STR_SESSION_EXPIRES, NULL); pjsip_min_se_hdr* min_se_hdr = (pjsip_min_se_hdr*) pjsip_msg_find_hdr_by_name(req, &STR_MIN_SE, NULL); SessionInterval min_se = (min_se_hdr != NULL) ? min_se_hdr->expires : DEFAULT_MIN_SE; if ((se_hdr != NULL) && (se_hdr->expires < _target_se)) { // The request already has a session expires that is below our target. We // don't need to change the value. TRC_DEBUG("Session expires already set to %d", se_hdr->expires); } else { // No pre-existing session expires, or the current value is greater than // our target. Set it to as close to our target as possible, but don't set // it below the min-SE. if (se_hdr == NULL) { se_hdr = pjsip_session_expires_hdr_create(pool); pjsip_msg_add_hdr(req, (pjsip_hdr*)se_hdr); } se_hdr->expires = std::max(_target_se, min_se); TRC_DEBUG("Set session expires to %d", se_hdr->expires); } // Make a note of the session expires (we may need it when processing the // response) _se_on_req = se_hdr->expires; }
void SessionExpiresHelper::process_response(pjsip_msg* rsp, pj_pool_t* pool, SAS::TrailId trail) { // Session expires is only allowed on INVITE and UPDATE methods. pjsip_method* method = &PJSIP_MSG_CSEQ_HDR(rsp)->method; if ((pjsip_method_cmp(method, pjsip_get_invite_method()) != 0) && (pjsip_method_cmp(method, &METHOD_UPDATE) != 0)) { return; } // We only need to process successful final responses. if (!PJSIP_IS_STATUS_IN_CLASS(rsp->line.status.code, 200)) { return; } pjsip_session_expires_hdr* se_hdr = (pjsip_session_expires_hdr*) pjsip_msg_find_hdr_by_name(rsp, &STR_SESSION_EXPIRES, NULL); if (se_hdr == NULL) { // There is no session-expires header. This means we are most downstream // device that supports session timers, and in particular the UAS does not // support them. // // If the UAC does not support session timers, there's nothing more we can // do - session timers will not be used for this dialog. // // If the UAC *does* support session timers, re-add a session-expires header // that instructs the UAC to be the refresher. if (_uac_supports_timer) { se_hdr = pjsip_session_expires_hdr_create(pool); pjsip_msg_add_hdr(rsp, (pjsip_hdr*)se_hdr); se_hdr->expires = _se_on_req; se_hdr->refresher = SESSION_REFRESHER_UAC; // Also update (or add) the require header to force the UAC to do session // refreshes. pjsip_require_hdr* require_hdr = (pjsip_require_hdr*) pjsip_msg_find_hdr(rsp, PJSIP_H_REQUIRE, NULL); if (require_hdr == NULL) { require_hdr = (pjsip_require_hdr*)pjsip_require_hdr_create(pool); pjsip_msg_add_hdr(rsp, (pjsip_hdr*)require_hdr); } pj_strdup(pool, &require_hdr->values[require_hdr->count], &STR_TIMER); require_hdr->count++; } } if (_initial_request) { if (se_hdr == NULL) { SAS::Event event(trail, SASEvent::SESS_TIMER_NO_UA_SUPPORT, 0); SAS::report_event(event); } else if (se_hdr->expires > _target_se) { SAS::Event event(trail, SASEvent::SESS_TIMER_INTERVAL_TOO_LONG, 0); event.add_static_param(_target_se); event.add_static_param(se_hdr->expires); SAS::report_event(event); } } }
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); }
/* Callback to be called to handle new incoming requests. */ static pj_bool_t proxy_on_rx_request( pjsip_rx_data *rdata ) { pjsip_transaction *uas_tsx, *uac_tsx; struct uac_data *uac_data; struct uas_data *uas_data; pjsip_tx_data *tdata; pj_status_t status; if (rdata->msg_info.msg->line.req.method.id != PJSIP_CANCEL_METHOD) { /* Verify incoming request */ status = proxy_verify_request(rdata); if (status != PJ_SUCCESS) { app_perror("RX invalid request", status); return PJ_TRUE; } /* * Request looks sane, next clone the request to create transmit data. */ status = pjsip_endpt_create_request_fwd(global.endpt, rdata, NULL, NULL, 0, &tdata); if (status != PJ_SUCCESS) { pjsip_endpt_respond_stateless(global.endpt, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL, NULL); return PJ_TRUE; } /* Process routing */ status = proxy_process_routing(tdata); if (status != PJ_SUCCESS) { app_perror("Error processing route", status); return PJ_TRUE; } /* Calculate target */ status = proxy_calculate_target(rdata, tdata); if (status != PJ_SUCCESS) { app_perror("Error calculating target", status); return PJ_TRUE; } /* Everything is set to forward the request. */ /* If this is an ACK request, forward statelessly. * This happens if the proxy records route and this ACK * is sent for 2xx response. An ACK that is sent for non-2xx * final response will be absorbed by transaction layer, and * it will not be received by on_rx_request() callback. */ if (tdata->msg->line.req.method.id == PJSIP_ACK_METHOD) { status = pjsip_endpt_send_request_stateless(global.endpt, tdata, NULL, NULL); if (status != PJ_SUCCESS) { app_perror("Error forwarding request", status); return PJ_TRUE; } return PJ_TRUE; } /* Create UAC transaction for forwarding the request. * Set our module as the transaction user to receive further * events from this transaction. */ status = pjsip_tsx_create_uac(&mod_tu, tdata, &uac_tsx); if (status != PJ_SUCCESS) { pjsip_tx_data_dec_ref(tdata); pjsip_endpt_respond_stateless(global.endpt, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL, NULL); return PJ_TRUE; } /* Create UAS transaction to handle incoming request */ status = pjsip_tsx_create_uas(&mod_tu, rdata, &uas_tsx); if (status != PJ_SUCCESS) { pjsip_tx_data_dec_ref(tdata); pjsip_endpt_respond_stateless(global.endpt, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL, NULL); pjsip_tsx_terminate(uac_tsx, PJSIP_SC_INTERNAL_SERVER_ERROR); return PJ_TRUE; } /* Feed the request to the UAS transaction to drive it's state * out of NULL state. */ pjsip_tsx_recv_msg(uas_tsx, rdata); /* Attach a data to the UAC transaction, to be used to find the * UAS transaction when we receive response in the UAC side. */ uac_data = (struct uac_data*) pj_pool_alloc(uac_tsx->pool, sizeof(struct uac_data)); uac_data->uas_tsx = uas_tsx; uac_tsx->mod_data[mod_tu.id] = (void*)uac_data; /* Attach data to the UAS transaction, to find the UAC transaction * when cancelling INVITE request. */ uas_data = (struct uas_data*) pj_pool_alloc(uas_tsx->pool, sizeof(struct uas_data)); uas_data->uac_tsx = uac_tsx; uas_tsx->mod_data[mod_tu.id] = (void*)uas_data; /* Everything is setup, forward the request */ status = pjsip_tsx_send_msg(uac_tsx, tdata); if (status != PJ_SUCCESS) { pjsip_tx_data *err_res; /* Fail to send request, for some reason */ /* Destroy transmit data */ pjsip_tx_data_dec_ref(tdata); /* I think UAC transaction should have been destroyed when * it fails to send request, so no need to destroy it. pjsip_tsx_terminate(uac_tsx, PJSIP_SC_INTERNAL_SERVER_ERROR); */ /* Send 500/Internal Server Error to UAS transaction */ pjsip_endpt_create_response(global.endpt, rdata, 500, NULL, &err_res); pjsip_tsx_send_msg(uas_tsx, err_res); return PJ_TRUE; } /* Send 100/Trying if this is an INVITE */ if (rdata->msg_info.msg->line.req.method.id == PJSIP_INVITE_METHOD) { pjsip_tx_data *res100; pjsip_endpt_create_response(global.endpt, rdata, 100, NULL, &res100); pjsip_tsx_send_msg(uas_tsx, res100); } } else { /* This is CANCEL request */ pjsip_transaction *invite_uas; struct uas_data *uas_data; pj_str_t key; /* Find the UAS INVITE transaction */ pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_UAS_ROLE, pjsip_get_invite_method(), rdata); invite_uas = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE); if (!invite_uas) { /* Invite transaction not found, respond CANCEL with 481 */ pjsip_endpt_respond_stateless(global.endpt, rdata, 481, NULL, NULL, NULL); return PJ_TRUE; } /* Respond 200 OK to CANCEL */ pjsip_endpt_respond(global.endpt, NULL, rdata, 200, NULL, NULL, NULL, NULL); /* Send CANCEL to cancel the UAC transaction. * The UAS INVITE transaction will get final response when * we receive final response from the UAC INVITE transaction. */ uas_data = (struct uas_data*) invite_uas->mod_data[mod_tu.id]; if (uas_data->uac_tsx && uas_data->uac_tsx->status_code < 200) { pjsip_tx_data *cancel; pj_mutex_lock(uas_data->uac_tsx->mutex); pjsip_endpt_create_cancel(global.endpt, uas_data->uac_tsx->last_tx, &cancel); pjsip_endpt_send_request(global.endpt, cancel, -1, NULL, NULL); pj_mutex_unlock(uas_data->uac_tsx->mutex); } /* Unlock UAS tsx because it is locked in find_tsx() */ pj_mutex_unlock(invite_uas->mutex); } 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); }
static pjsip_dialog *find_dialog(pjsip_rx_data *rdata) { pj_str_t tsx_key; pjsip_transaction *tsx; pjsip_dialog *dlg; pj_str_t *local_tag; pj_str_t *remote_tag; if (!rdata->msg_info.msg) { return NULL; } if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) { local_tag = &rdata->msg_info.to->tag; remote_tag = &rdata->msg_info.from->tag; } else { local_tag = &rdata->msg_info.from->tag; remote_tag = &rdata->msg_info.to->tag; } /* We can only call the convenient method for * 1) responses * 2) non-CANCEL requests * 3) CANCEL requests with a to-tag */ if (rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG || pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_cancel_method) || rdata->msg_info.to->tag.slen != 0) { dlg = pjsip_ua_find_dialog(&rdata->msg_info.cid->id, local_tag, remote_tag, PJ_FALSE); if (dlg) { return dlg; } } /* * There may still be a matching dialog if this is * 1) an incoming CANCEL request without a to-tag * 2) an incoming response to a dialog-creating request. */ if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) { /* CANCEL requests will need to match the INVITE we initially received. Any * other request type will either have been matched already or is not in * dialog */ pjsip_tsx_create_key(rdata->tp_info.pool, &tsx_key, PJSIP_ROLE_UAS, pjsip_get_invite_method(), rdata); } else { pjsip_tsx_create_key(rdata->tp_info.pool, &tsx_key, PJSIP_ROLE_UAC, &rdata->msg_info.cseq->method, rdata); } tsx = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_TRUE); if (!tsx) { ast_debug(3, "Could not find matching transaction for %s\n", pjsip_rx_data_get_info(rdata)); return NULL; } dlg = pjsip_tsx_get_dlg(tsx); #ifdef HAVE_PJ_TRANSACTION_GRP_LOCK pj_grp_lock_release(tsx->grp_lock); #else pj_mutex_unlock(tsx->mutex); #endif return dlg; }