When the PJSIP work for Asterisk began one of the primary concerns kept in mind was that it be extensible. One of the APIs derived from this concern was session supplements. Session supplements are a way for modules to add themselves in to the handling of SIP messages for sessions (or calls as you may know them). They are invoked on incoming and outgoing messages and allow modules to do anything to the session or channel involved. In the case of outgoing messages they can also modify the message.
A prime example of this is PJSIP caller ID support. The caller ID module (res_pjsip_caller_id) uses a session supplement to handle both incoming and outgoing messages. For incoming messages it examines the SIP message and extracts the caller ID information from it. For outgoing messages it modifies the message to contain the caller ID information. No changes to res_pjsip or res_pjsip_session were required to support this.
You can see what the interface for session supplements looks like by examining the implementation for caller ID.
static struct ast_sip_session_supplement caller_id_supplement = { .method = "INVITE,UPDATE", .priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL - 1000, .incoming_request = caller_id_incoming_request, .incoming_response = caller_id_incoming_response, .outgoing_request = caller_id_outgoing_request, .outgoing_response = caller_id_outgoing_response, };
For this blog post we will focus on the callback for outgoing_request. This callback is invoked when a SIP request is sent as a result of the ast_sip_session_send_request function being called and, until recently, as a result of a callback from PJSIP informing us there is an outgoing message. This PJSIP callback no longer invokes outgoing_request as the original meaning of the PJSIP callback was misunderstood.
Let’s take a gander at some of our original PJSIP callback implementation.
static void session_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e) { struct ast_sip_session *session = inv->mod_data[session_module.id]; pjsip_event_id_e type; if (e) { print_debug_details(inv, NULL, e); type = e->type; } else { type = PJSIP_EVENT_UNKNOWN; } if (!session) { return; } switch(type) { case PJSIP_EVENT_TX_MSG: handle_outgoing(session, e->body.tx_msg.tdata); break; case PJSIP_EVENT_RX_MSG: handle_incoming_before_media(inv, session, e->body.rx_msg.rdata); break; case PJSIP_EVENT_TSX_STATE: ast_debug(3, "Source of transaction state change is %s\n", pjsip_event_str(e->body.tsx_state.type)); /* Transaction state changes are prompted by some other underlying event. */ switch(e->body.tsx_state.type) { case PJSIP_EVENT_TX_MSG: handle_outgoing(session, e->body.tsx_state.src.tdata);
This function is called by PJSIP when a state change occurs and it provides information about why the state changed in the event. The event type that used to invoke outgoing_request is PJSIP_EVENT_TX_MSG. On an initial read you might think that this is called when an outgoing message is about to be sent. You would be incorrect. As found when doing testing of Asterisk internally depending on timing this could be called when an outgoing message is about to be sent, in the process of being sent or has been sent.
Why is this bad?
The outgoing_request callback is supposed to provide a contract that the message is safe to modify before it is sent. Invocation as a result of PJSIP_EVENT_TX_MSG could not provide this guarantee. What is even worse is that the PJSIP process of sending modifies the message. If this occurred while a session supplement was doing the same they would both clash. This is not safe and could cause memory corruption resulting in a crash as seen during our testing.
This problem has been resolved by simply removing the code that invokes the outgoing_request on session supplements as a result of PJSIP_EVENT_TX_MSG. Since we already do it before passing the message to PJSIP we guarantee that it is safe to do so and that no clash will occur between PJSIP and the session supplement.