If you have ever tried to use direct media with Asterisk, you my have received this message before. It can happen when the User Agent connected to Asterisk tries to send a re-INVITE before handling the direct media re-INVITE from Asterisk. This scenario can happen any time two re-INVITE messages cross in the ether, but direct media scenarios invite this race condition.
This scenario is laid out in RFC 5407.
If your UA receives this in response to sending an re-INVITE then the remote UA is letting you know that it has already send you a re-INVITE that you haven’t responded to yet. If you receive the 491, you can handle the remote re-INVITE if you haven’t already sent your own 491. If that’s the case, then your only choice is to follow the guidance in RFC 5407.
In the Asterisk TestSuite, we run into this race condition mostly when doing attended transfers with direct-media. In general we want the tests to run as quickly as they can, so the time between initial call setup and the re-INVITE to put a leg on hold to start the transfer was small, essentially as fast as possible.
The old pjsua based solution could handle the direct media re-INVITE, but wouldn’t automatically re-send the on-hold re-INVITE. This meant that the TestSuite had to look for the 491 by packet sniffing in order to know if the re-INVITE had to be re-sent.
As this scenario now has to be recreated via sipp, race conditions become quite difficult. We can get around some of this with conditional branching and making a 491 response optional, but it gets complicated quickly. The re-INVITE from Asterisk setting up direct media can be very quick once sdp is negotiated on both sides. To handle this, the sipp scripts allow a little time for Asterisk to optionally send the re-INVITE along with being flexible in how it’s received. Sounds simple, but if you have ever written a sipp script before then the idea of adding the necessary 3pcc, direct media, and conditional re-INVITEs to a set of scripts has already given you a headache.
The two tests in particular we are looking at in this post are: caller_local_direct_media and callee_local_direct_media under tests channels pjsip transfers attended_transfer nominal.
The mechanism used in these test cases is through the use of labels triggered by optional or timed recv requests. The use of an optional or timed recv request is based on whether we are anticipating the re-INVITE while waiting to receive a message or waiting to send a message.
If we are waiting for a message, we would use the optional flag to tell sipp that we may get this message and the next flag to indicate which label to go to if we do. For instance if waiting for a BYE but anticipating a re-INVITE, then the end of our scenario could look like:
<label id="wait_reINVITE"/> <recv request="INVITE" optional="true" next="respond_reINVITE"/> <recv request="BYE" /> <send next="scenario_end"> <![CDATA[ SIP/2.0 200 OK ]]> </send> <label id="respond_reINVITE"/> <send> <![CDATA[ SIP/2.0 200 OK ]]> </send> <recv request="ACK" next="wait_reINVITE"/> <label id="scenario_end"/>
This essentially allows us to hide a little goto/function at the end of the scenario that handles any re-INVITEs and kicks back to waiting until a BYE is received. Once the 200 OK is sent in response to the BYE we jump to the scenario end and are done. Note that there has to be a mandatory recv after one or more optional ones for the script to be valid, ie:
<recv response="100" optional="true" /> <recv response="101" optional="true" /> <recv response="180" optional="true" /> <recv response="491" optional="true" next="respond_raceCondition"/> <recv response="200" rtd="true" crlf="true" />
If we are waiting to send a message, we can’t use the optional flag because this would just skip to the send and is invalid. So in that case we use a timed recv in place of the pause that we would typically use. This has some limitations but allows for receiving multiple re-INVITEs before sending a BYE:
<label id="waitagain"/> <recv request="INVITE" timeout="5000" ontimeout="sendBYE"/> <send> <![CDATA[ SIP/2.0 200 OK ]]> </send> <recv request="ACK" next="waitagain"/> <label id="sendBYE"/> <send retrans="500"> <![CDATA[ BYE sip:[$remote_contact] SIP/2.0 ]]> </send> <recv response="200"/> <label id="scenario_end"/>
By allowing Asterisk even a brief time to set up direct media and allowing for flexibility in receiving re-INVITE messages, we can create the desired scenarios in which to test Asterisk. Please check out the sipp xml files in caller_local_direct_media and callee_local_direct_media as they not only deal with this issue, but a few other tricky ones as well.