Skip to content

Instantly share code, notes, and snippets.

@shivanshtalwar
Created July 1, 2024 08:40
investigating bug
/*! \file janus_sip.c
* \author Lorenzo Miniero <lorenzo@meetecho.com>
* \copyright GNU General Public License v3
* \brief Janus SIP plugin
* \details Check the \ref sip for more details.
*
* \ingroup plugins
* \ref plugins
*
* \page sip SIP plugin documentation
* This is a simple SIP plugin for Janus, allowing WebRTC peers
* to register at a SIP server (e.g., Asterisk) and call SIP user agents
* through a Janus instance. Specifically, when attaching to the plugin peers
* are requested to provide their SIP server credentials, i.e., the address
* of the SIP server and their username/secret. This results in the plugin
* registering at the SIP server and acting as a SIP client on behalf of
* the web peer. Most of the SIP states and lifetime are masked by the plugin,
* and only the relevant events (e.g., INVITEs and BYEs) and functionality
* (call, hangup) are made available to the web peer: peers can call
* extensions at the SIP server or wait for incoming INVITEs, and during
* a call they can send DTMF tones. Calls can do plain RTP or SDES-SRTP.
*
* The concept behind this plugin is to allow different web pages associated
* to the same peer, and hence the same SIP user, to attach to the plugin
* at the same time and yet just do a SIP REGISTER once. The same should
* apply for calls: while an incoming call would be notified to all the
* web UIs associated to the peer, only one would be able to pick up and
* answer, in pretty much the same way as SIP forking works but without the
* need to fork in the same place. This specific functionality, though, has
* not been implemented as of yet.
*
* \section sipapi SIP Plugin API
*
* All requests you can send in the SIP Plugin API are asynchronous,
* which means all responses (successes and errors) will be delivered
* as events with the same transaction.
*
* The supported requests are \c register , \c unregister , \c call ,
* \c accept, \c decline , \c info , \c message , \c dtmf_info ,
* \c subscribe , \c unsubscribe , \c transfer , \c recording ,
* \c hold , \c unhold , \c update and \c hangup . \c register can be used,
* as the name suggests, to register a username at a SIP registrar to
* call and be called, while \c unregister unregisters it; \c call is used
* to send an INVITE to a different SIP URI through the plugin, while
* \c accept and \c decline are used to accept or reject the call in
* case one is invited instead of inviting; \c transfer takes care of
* attended and blind transfers (see \ref siptr for more details);
* \c hold and \c unhold can be used respectively to put a
* call on-hold and to resume it; \c info allows you to send a generic
* SIP INFO request, while \c dtmf_info is focused on using INFO for DTMF
* instead; \c message is the method you use to send a SIP message
* to the other peer; \c subscribe and \c unsubscribe are used to deal
* with SIP events, i.e., to send SUBSCRIBE requests that will result in
* NOTIFY asynchronous events; \c recording is used, instead, to record the
* conversation to one or more .mjr files (depending on the direction you
* want to record); \c update allows you to update an existing session
* (e.g., to do a renegotiation or force an ICE restart); finally, \c hangup
* can be used to terminate the communication at any time, either to
* hangup (BYE) an ongoing call or to cancel/decline (CANCEL/BYE) a call
* that hasn't started yet.
*
* No matter the request, an error response or event is always formatted
* like this:
*
\verbatim
{
"sip" : "event",
"error_code" : <numeric ID, check Macros below>,
"error" : "<error description as a string>"
}
\endverbatim
*
* Notice that the error syntax above refers to the plugin API messaging,
* and not SIP error codes obtained in response to SIP requests, which
* are notified using a different syntax:
*
\verbatim
{
"sip" : "event",
"result" : {
"event" : "<name of the error event>",
"code" : <SIP error code>,
"reason" : "<SIP error reason>",
"reason_header" : "<Reason header text; optional>",
"reason_header_protocol" : "<Reason header protocol; optional>",
"reason_header_cause" : "<Reason header cause code; optional>"
}
}
\endverbatim
*
* Coming to the available requests, you send a SIP REGISTER using the
* \c register request. To be more precise, a \c register request MAY result
* in a SIP REGISTER, as this method actually provides ways to start using
* a SIP account with no need for a registration. It is the case, for instance,
* of the so-called \c guest registrations: if you register as a \c guest ,
* it means you'll use the provided SIP URI in your \c From headers for calls,
* but you will actually not send a SIP REGISTER; this is especially useful
* for outgoing calls to services that don't require registration (e.g., IVR
* systems, or conference bridges), but also means you won't be able to
* receive calls unless peers know what your private SIP address is. A SIP
* REGISTER isn't sent also when registering as a \c helper : as we'll
* explain later, \c helper sessions are sessions only meant to facilitate
* the setup of \ref sipmc.
*
* That said, a \c register request has to be formatted as follows:
*
\verbatim
{
"request" : "register",
"type" : "<if guest or helper, no SIP REGISTER is actually sent; optional>",
"send_register" : <true|false; if false, no SIP REGISTER is actually sent; optional>,
"force_udp" : <true|false; if true, forces UDP for the SIP messaging; optional>,
"force_tcp" : <true|false; if true, forces TCP for the SIP messaging; optional>,
"sips" : <true|false; if true, configures a SIPS URI too when registering; optional>,
"rfc2543_cancel" : <true|false; if true, configures sip client to CANCEL pending INVITEs without having received a provisional response first; optional>,
"username" : "<SIP URI to register; mandatory>",
"secret" : "<password to use to register; optional>",
"ha1_secret" : "<prehashed password to use to register; optional>",
"authuser" : "<username to use to authenticate (overrides the one in the SIP URI); optional>",
"display_name" : "<display name to use when sending SIP REGISTER; optional>",
"user_agent" : "<user agent to use when sending SIP REGISTER; optional>",
"proxy" : "<server to register at; optional, as won't be needed in case the REGISTER is not goint to be sent (e.g., guests)>",
"outbound_proxy" : "<outbound proxy to use, if any; optional>",
"headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP REGISTER; optional>",
"contact_params" : "<array of key/value objects, to specify custom Contact URI params to add to the SIP REGISTER; optional>",
"incoming_header_prefixes" : "<array of strings, to specify custom (non-standard) headers to read on incoming SIP events; optional>",
"refresh" : "<true|false; if true, only uses the SIP REGISTER as an update and not a new registration; optional>",
"master_id" : "<ID of an already registered account, if this is an helper for multiple calls (more on that later); optional>",
"register_ttl" : "<integer; number of seconds after which the registration should expire; optional>"
}
\endverbatim
*
* A \c registering event will be sent back, as this is an asynchronous request.
*
* In case it is required to, this request will originate a SIP REGISTER to the
* specified server with the right credentials. 401 and 407 responses will be
* handled automatically, and so errors will not be notified back to the caller
* unless they're definitive (e.g., wrong credentials). A failure to register
* will return an error with name \c registration_failed. A successful registration,
* instead, is notified in a \c registered event formatted like this:
*
\verbatim
{
"sip" : "event",
"result" : {
"event" : "registered",
"username" : <SIP URI username>,
"register_sent" : <true|false, depending on whether a REGISTER was sent or not>,
"master_id" : <unique ID of this registered session in the plugin, if a potential master>
}
}
\endverbatim
*
* To unregister, just send an \c unregister request with no other arguments:
*
\verbatim
{
"request" : "unregister"
}
\endverbatim
*
* As before, an \c unregistering event will be sent back. Just as before,
* this will also send a SIP REGISTER in case it had been sent originally.
* A successful unregistration is notified in an \c unregistered event:
*
\verbatim
{
"sip" : "event",
"result" : {
"event" : "unregistered",
"username" : <SIP URI username>,
"register_sent" : <true|false, depending on whether a REGISTER was sent or not>
}
}
\endverbatim
*
* Once registered, you can call or wait to be called: notice that you won't
* be able to get incoming calls if you chose never to send a REGISTER at
* all, though.
*
* To send a SIP INVITE, you can use the \c call request, which has to
* be formatted like this:
*
\verbatim
{
"request" : "call",
"call_id" : "<user-defined value of Call-ID SIP header used in all SIP requests throughout the call; optional>",
"uri" : "<SIP URI to call; mandatory>",
"refer_id" : <in case this is the result of a REFER, the unique identifier that addresses it; optional>,
"headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP INVITE; optional>",
"srtp" : "<whether to mandate (sdes_mandatory) or offer (sdes_optional) SRTP support; optional>",
"srtp_profile" : "<SRTP profile to negotiate, in case SRTP is offered; optional>",
"secret" : "<password to use to call, only needed in case authentication is needed and no REGISTER was sent; optional>",
"ha1_secret" : "<prehashed password to use to call, only needed in case authentication is needed and no REGISTER was sent; optional>",
"authuser" : "<username to use to authenticate as to call, only needed in case authentication is needed and no REGISTER was sent; optional>",
"autoaccept_reinvites" : <true|false, whether we should blindly accept re-INVITEs with a 200 OK instead of relaying the SDP to the application; optional, TRUE by default>
}
\endverbatim
*
* A \c calling event will be sent back, as this is an asynchronous request.
*
* Notice that this request MUST be associated to a JSEP offer: there's no
* way to send an offerless INVITE via the SIP plugin. This will generate
* a SIP INVITE and send it according to the instructions. While a
* <code>100 Trying</code> will not be notified back to the user, a
* <code>180 Ringing</code> will, in a \c ringing event:
*
\verbatim
{
"sip" : "event",
"call_id" : "<value of SIP Call-ID header for related call>",
"result" : {
"event" : "ringing",
"headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
}
}
\endverbatim
*
* If the call is declined, or any other error occurs, a \c hangup error
* event will be sent back. If the call is accepted, instead, an \c accepted
* event will be sent back to the user, along with the JSEP answer originated
* by the callee:
*
\verbatim
{
"sip" : "event",
"call_id" : "<value of SIP Call-ID header for related call>",
"result" : {
"event" : "accepted",
"username" : "<SIP URI of the callee>",
"headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
}
}
\endverbatim
*
* At this point, PeerConnection-related considerations aside, the call
* can be considered established. A SIP ACK is sent automatically by the
* SIP plugin, so there's no action required of the application to do
* that manually.
*
* Notice that the SIP plugin supports early-media via \c 183 responses
* responses. In case a \c 183 response is received, it's sent back to
* the user, along with the JSEP answer originated by the callee, in
* a \c progress event:
*
\verbatim
{
"sip" : "event",
"call_id" : "<value of SIP Call-ID header for related call>",
"result" : {
"event" : "progress",
"username" : "<SIP URI of the callee>",
"headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
}
}
\endverbatim
*
* In case the caller received a \c progress event, the following
* \c accepted event will NOT contain a JSEP answer, as the one received
* in the "Session Progress" event will act as the SDP answer for the session.
*
* Notice that you only use \c call to start a conversation, that is for
* the first INVITE. To update a session via a re-INVITE, e.g., to renegotiate
* a session to add/remove streams or force an ICE restart, you do NOT
* use \c call, but another request called \c update instead. This request
* needs no arguments, as the whole context is derived from the current
* state of the session. It does need the new JSEP offer to provide, though,
* as part of the renegotiation.
*
\verbatim
{
"request" : "update"
}
\endverbatim
*
* An \c updating event will be sent back, as this is an asynchronous request.
*
* While the \c call request allows you to send a SIP INVITE (and the
* \c update request allows you to update an existing session), there is
* a way to react to SIP INVITEs as well, that is to handle incoming calls.
* Incoming calls are notified to the application via \c incomingcall
* events:
*
\verbatim
{
"sip" : "event",
"call_id" : "<value of SIP Call-ID header for related call>",
"result" : {
"event" : "incomingcall",
"username" : "<SIP URI of the caller>",
"displayname" : "<display name of the caller, if available; optional>",
"callee" : "<SIP URI that was called (in case the user is associated with multiple public URIs)>",
"referred_by" : "<SIP URI header conveying the identity of the transferor, if this is a transfer; optional>",
"replaces" : "<call-ID of the call that this is supposed to replace, if this is an attended transfer; optional>",
"srtp" : "<whether the caller mandates (sdes_mandatory) or offers (sdes_optional) SRTP support; optional>",
"headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
}
}
\endverbatim
*
* The \c incomingcall may or may not be accompanied by a JSEP offer, depending
* on whether the caller sent an offerless INVITE or a regular one. Either
* way, you can accept the incoming call with the \c accept request:
*
\verbatim
{
"request" : "accept",
"srtp" : "<whether to mandate (sdes_mandatory) or offer (sdes_optional) SRTP support; optional>",
"headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP OK; optional>"
"autoaccept_reinvites" : <true|false, whether we should blindly accept re-INVITEs with a 200 OK instead of relaying the SDP to the browser; optional, TRUE by default>
}
\endverbatim
*
* An \c accepting event will be sent back, as this is an asynchronous request.
*
* This will result in a <code>200 OK</code> to be sent back to the caller.
* An \c accept request must always be accompanied by a JSEP answer (if the
* \c incomingcall event contained an offer) or offer (in case it was an
* offerless INVITE). In the former case, an \c accepted event will be
* sent back just to confirm the call can be considered established;
* in the latter case, instead, an \c accepting event will be sent back
* instead, and an \c accepted event will only follow later, as soon as
* a JSEP answer is available in the SIP ACK the caller sent back.
*
* Notice that in case you get an incoming call while you're in another
* call, you will NOT get an \c incomingcall event, but a \c missed_call
* event instead, and just as a notification as there's no way to have
* two calls at the same time on the same handle in the SIP plugin:
*
\verbatim
{
"sip" : "event",
"call_id" : "<value of SIP Call-ID header for related call>",
"result" : {
"event" : "missed_call",
"caller" : "<SIP URI of the caller>",
"displayname" : "<display name of the caller, if available; optional>",
"callee" : "<SIP URI that was called (in case the user is associated with multiple public URIs)>"
}
}
\endverbatim
*
* Besides, you only use \c accept to answer the first INVITE. To accept a
* re-INVITE instead, which would be notified via an \c updatingcall event,
* you do NOT use \c accept, but the previously introduced \c update instead.
* This request needs no arguments, as the whole context is derived from the current
* state of the session. It does need the new JSEP answer to provide, though,
* as part of the renegotiation. As before, an \c updated event will be
* sent back, as this is an asynchronous request.
*
* Closing a session depends on the call state. If you have an incoming
* call that you don't want to accept, use the \c decline request; in all
* other cases, use the \c hangup request instead. Both requests need no
* additional arguments, as the whole context can be extracted from the
* current state of the session in the plugin:
*
\verbatim
{
"request" : "decline",
"code" : <SIP code to be sent, if not set, 486 is used; optional>",
"headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP request; optional>"
}
\endverbatim
*
\verbatim
{
"request" : "hangup",
"headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP BYE; optional>"
}
\endverbatim
*
* Since these are asynchronous requests, you'll get an event in response:
* \c declining if you used \c decline and \c hangingup if you used \c hangup.
*
* As anticipated before, when a call is declined or being hung up, a
* \c hangup event is sent instead, which is basically a SIP error event
* notification as it includes the \c code and \c reason . A regular BYE,
* for instance, would be notified with \c 200 and <code>SIP BYE</code>,
* although a more verbose description may be provided as well.
*
* When a session has been established, there are different requests that
* you can use to interact with the session.
*
* First of all, you can put a call on-hold with the \c hold request.
* By default, this request will send a new INVITE to the peer with a
* \c sendonly direction for media, but in case you want to set a
* different direction (\c recvonly or \c inactive ) you can do that by
* passing a \c direction attribute as well:
*
\verbatim
{
"request" : "hold",
"direction" : "<sendonly, recvonly or inactive>"
}
\endverbatim
*
* No WebRTC renegotiation will be involved here on the holder side, as
* this will only trigger a re-INVITE on the SIP side. To remove the
* call from on-hold, just send a \c unhold request to the plugin,
* which requires no additional attributes:
*
\verbatim
{
"request" : "unhold"
}
\endverbatim
*
* and will restore the media direction that was set in the SDP before
* putting the call on-hold.
*
* The \c message request allows you to send a SIP MESSAGE to the peer.
* By default, it is sent in dialog, during active call.
* But, if the user is registered, it might be sent out of dialog also. In that case the uri parameter is required.
*
\verbatim
{
"request" : "message",
"call_id" : "<user-defined value of Call-ID SIP header used to send the message; optional>",
"content_type" : "<content type; optional>"
"content" : "<text to send>",
"uri" : "<SIP URI of the peer; optional; if set, the message will be sent out of dialog>",
"headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP MESSAGE; optional>"
}
\endverbatim
*
* A \c messagesent event will be sent back. Incoming SIP MESSAGEs, instead,
* are notified in \c message events:
*
\verbatim
{
"sip" : "event",
"result" : {
"event" : "message",
"sender" : "<SIP URI of the message sender>",
"displayname" : "<display name of the sender, if available; optional>",
"content_type" : "<content type of the message>",
"content" : "<content of the message>",
"headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
}
}
\endverbatim
*
* After delivery a \c messagedelivery event will be sent back with the SIP server response.
* Used to track the delivery status of the message.
*
\verbatim
{
"sip" : "event",
"call_id" : "<value of SIP Call-ID header for related message>",
"result" : {
"event" : "messagedelivery",
"code" : "<SIP error code>",
"reason" : "<SIP error reason>",
}
}
\endverbatim
*
* SIP INFO works pretty much the same way, except that you use an \c info
* request to one to the peer:
*
\verbatim
{
"request" : "info",
"type" : "<content type>"
"content" : "<message to send>",
"headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP INFO; optional>"
}
\endverbatim
*
* A \c infosent event will be sent back. Incoming SIP INFOs, instead,
* are notified in \c info events:
*
\verbatim
{
"sip" : "event",
"result" : {
"event" : "info",
"sender" : "<SIP URI of the message sender>",
"displayname" : "<display name of the sender, if available; optional>",
"type" : "<content type of the message>",
"content" : "<content of the message>",
"headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
}
}
\endverbatim
*
* As anticipated, SIP events are supported as well, using the SUBSCRIBE
* and NOTIFY mechanism. To do that, you need to use the \c subscribe
* request, which has to be formatted like this:
*
\verbatim
{
"request" : "subscribe",
"call_id" : "<user-defined value of Call-ID SIP header used in all SIP requests throughout the subscription; optional>",
"event" : "<the event to subscribe to, e.g., 'message-summary'; mandatory>",
"accept" : "<what should be put in the Accept header; optional>",
"to" : "<who should be the SUBSCRIBE addressed to; optional, will use the user's identity if missing>",
"subscribe_ttl" : "<integer; number of seconds after which the subscription should expire; optional>",
"headers" : "<array of key/value objects, to specify custom headers to add to the SIP SUBSCRIBE; optional>"
}
\endverbatim
*
* A \c subscribing event will be sent back, followed by a \c subscribe_succeeded if
* the SUBSCRIBE request was accepted, and a \c subscribe_failed if the transaction
* failed instead. Incoming SIP NOTIFY events, instead, are notified in \c notify events:
*
\verbatim
{
"sip" : "event",
"call_id" : "<value of SIP Call-ID header for related subscription>",
"result" : {
"event" : "notify",
"notify" : "<name of the event that the user is subscribed to, e.g., 'message-summary'>",
"substate" : "<substate of the subscription, e.g., 'active'>",
"content-type" : "<content-type of the message>"
"content" : "<content of the message>",
"headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
}
}
\endverbatim
*
* You can also record a SIP call, and it works pretty much the same the
* VideoCall plugin does. Specifically, you make use of the \c recording
* request to either start or stop a recording, using the following syntax:
*
\verbatim
{
"request" : "recording",
"action" : "<start|stop, depending on whether you want to start or stop recording something>"
"audio" : <true|false; whether or not our audio should be recorded>,
"video" : <true|false; whether or not our video should be recorded>,
"peer_audio" : <true|false; whether or not our peer's audio should be recorded>,
"peer_video" : <true|false; whether or not our peer's video should be recorded>,
"send_peer_pli" : <true|false; whether or not send PLI to request keyframe from peer>,
"filename" : "<base path/filename to use for all the recordings>"
}
\endverbatim
*
* As you can see, this means that the two sides of conversation are recorded
* separately, and so are the audio and video streams if available. You can
* choose which ones to record, in case you're interested in just a subset.
* The \c filename part is just a prefix, and dictates the actual filenames
* that will be used for the up-to-four recordings that may need to be enabled.
*
* A \c recordingupdated event is sent back in case the request is successful.
*
* \section sipmc Simultaneous SIP calls using the same account
*
* As anticipated in the previous sections, attaching to the SIP plugin
* with a Janus handle means creating a SIP stack on behalf of a user
* or application: this typically means registering an account, and being
* able to start or receive calls, handle subscriptions, and so on. This
* also means that, since in Janus each core handle can only be associated
* with a single PeerConnection, each SIP account is limited to a single
* call per time: if a user is in a SIP session already, and another call
* comes in, it's automatically rejected with a \c 486 \c Busy .
*
* While usually not a big deal, there are use cases where it might make
* sense to be able to support multiple concurrent calls, and maybe switch
* from one to the other seamlessly. This is possible in the SIP plugin
* using the so-called \c helper sessions. Specifically, \c helper sessions
* work under the assumption that there's a \c master session that is
* registered normally (the "regular" SIP plugin handle, that is), and
* that these \c helper sessions can simply be associated to that: any time
* another concurrent call is needed, if the \c master session is busy
* one of the \c helpers can be used; the more \c helper sessions are
* available, the more simultaneous calls can be established.
*
* The way this works is simple:
*
* 1. you create a SIP session the usual way, and send a regular \c register
* there; this will be the \c master session, and will return a \c master_id
* when successfully registered;
* 2. for each \c helper you want to add, you attach a new Janus handle
* to the SIP plugin, and send a \c register with \c type: \c "helper" and
* providing the same \c username as the master, plus a \c master_id attribute
* referencing the main session;
* 3. at this point, the new \c helper is associated to the \c master ,
* meaning it can be used to start new calls or receive calls exactly
* as the main session, and using the same account information, credentials,
* etc.
*
* Notice that, as soon as the \c master unregisters, or the Janus handle
* it's on is detached, all the \c helper sessions associated to it are
* automatically torn down as well. Specifically, the plugin will forcibly
* detach the related handles. Should you need to register again, and want
* some helpers there too, you'll have to add them again.
*
* If you want to see this in practice, the SIP plugin demo has a "hidden"
* function you can invoke from the JavaScript console to play with helpers:
* calling the \c addHelper() function will add a new helper, and show additional
* controls. You can add as many helpers as you want.
*
* \section siptr Attended and blind transfers
*
* The Janus SIP plugin supports both attended and blind transfers, and to
* do so mostly relies on the multiple calls functionality: as such, make
* sure you've read and are familiar with the section on \ref sipmc .
*
* Most of the transfer-related functionality are based on existing messages
* and events already documented in the previous section, but there are a
* few aspects you need to be aware of. First of all, if you're the transferor,
* you need to use a new request called \c transfer , that allows you to
* send a SIP REFER to the transferee so to reach a different target. The
* \c transfer request must be formatted like this:
*
\verbatim
{
"request" : "transfer",
"uri" : "<SIP URI to send the transferee too>",
"replace" : "<call-ID of the call this attended transfer is supposed to replace; default is none, which means blind/unattended transfer>"
}
\endverbatim
*
* Whether this is a blind (no call to replace) or attended transfer,
* a \c transferring event will be sent back, as this is an asynchronous
* request. Further updates will come in the form of NOTIFY-related events,
* as a REFER implicitly creates a subscription.
*
* The recipient of a REFER, instead, will receive an asynchronous event
* called \c transfer as well, with info it needs to be aware of. In fact,
* the SIP plugin doesn't do anything automatically: an incoming REFER is
* notified to the application, so that it can decide whether to follow
* up on the transfer or not. The syntax of the event is the following:
*
\verbatim
{
"sip" : "event",
"result" : {
"event" : "transfer",
"refer_id" : <unique ID, internal to Janus, of this referral>,
"refer_to" : "<SIP URI to call>",
"referred_by" : "<SIP URI SIP URI header conveying the identity of the transferor; optional>",
"replaces" : "<call-ID of the call this transfer is supposed to replace; optional, and only present for attended transfers>",
"headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
}
}
\endverbatim
*
* The most important property in that list is \c refer_id as that value
* must be included in the \c call request to call the target, if the
* transfer is accepted: in fact, that's the only way the SIP plugin has
* to correlate the new outgoing call to the previous transfer request,
* and thus be able to notify the transferor about how the call is
* proceeding by means of NOTIFY events. Notice that, if the transferee
* decides to follow up on the transfer request, and they're already in
* a call (e.g., with the transferor), then they must use a different
* handle for the purpose, e.g., via a helper as described in the
* \ref sipmc section.
*
* The transfer target will receive the call exactly as previously discussed,
* with the difference that it may or may not include a \c referred_by
* property for information purposes. Just as the transferee, if they're
* already in a call, it's up to the application to create a helper to
* setup a new Janus handle to accept the transfer.
*
* Notice that the plugin will NOT put the involved calls on-hold, or
* automatically close calls that are meant to be replaced by a transfer.
* All this is the application responsibility, and as such it's up to
* the developer to react to events accordingly.
*
*/
#include "plugin.h"
#include <arpa/inet.h>
#include <net/if.h>
#include <jansson.h>
#include <sofia-sip/msg_header.h>
#include <sofia-sip/nua.h>
#include <sofia-sip/nua_tag.h>
#include <sofia-sip/sdp.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-prototypes"
#include <sofia-sip/sip_header.h>
#pragma GCC diagnostic pop
#include <sofia-sip/sip_status.h>
#include <sofia-sip/url.h>
#include <sofia-sip/tport_tag.h>
#include <sofia-sip/su_log.h>
#include <sofia-sip/sofia_features.h>
#include "../debug.h"
#include "../apierror.h"
#include "../config.h"
#include "../mutex.h"
#include "../record.h"
#include "../rtp.h"
#include "../rtpsrtp.h"
#include "../rtcp.h"
#include "../sdp-utils.h"
#include "../utils.h"
#include "../ip-utils.h"
#include <curl/curl.h>
/* Plugin information */
#define JANUS_SIP_VERSION 9
#define JANUS_SIP_VERSION_STRING "0.0.9"
#define JANUS_SIP_DESCRIPTION "This is a simple SIP plugin for Janus, allowing WebRTC peers to register at a SIP server and call SIP user agents through a Janus instance."
#define JANUS_SIP_NAME "JANUS SIP plugin"
#define JANUS_SIP_AUTHOR "Meetecho s.r.l."
#define JANUS_SIP_PACKAGE "janus.plugin.sip"
_Bool startsWith(const char *a, const char *b)
{
if (strncmp(a, b, strlen(b)) == 0)
{
return 1;
}
else
{
return 0;
}
}
typedef struct string_buffer_s
{
char *ptr;
size_t len;
} string_buffer_t;
static char *concat(const char *s1, const char *s2)
{
char *result = malloc(strlen(s1) + strlen(s2) + 1);
strcpy(result, s1);
strcat(result, s2);
return result;
}
static void string_buffer_initialize(string_buffer_t *sb)
{
sb->len = 0;
sb->ptr = malloc(sb->len + 1);
sb->ptr[0] = '\0';
}
static void string_buffer_finish(string_buffer_t *sb)
{
free(sb->ptr);
sb->len = 0;
sb->ptr = NULL;
}
static size_t string_buffer_callback(void *buf, size_t size, size_t nmemb, void *data)
{
string_buffer_t *sb = data;
size_t new_len = sb->len + size * nmemb;
sb->ptr = realloc(sb->ptr, new_len + 1);
memcpy(sb->ptr + sb->len, buf, size * nmemb);
sb->ptr[new_len] = '\0';
sb->len = new_len;
return size * nmemb;
}
static size_t write_callback(void *buf, size_t size, size_t nmemb, void *data)
{
return string_buffer_callback(buf, size, nmemb, data);
}
static char *convert_uri(const char *orig_uri)
{
if (!startsWith(orig_uri, "encryptedUri-"))
{
return orig_uri;
}
CURL *curl;
CURLcode res;
string_buffer_t strbuf;
char *uri;
JANUS_LOG(LOG_INFO, "uri before conversion: %s\n", orig_uri);
const char *base_url_endpoint = getenv("URI_CONV_ENDPOINT");
JANUS_LOG(LOG_INFO, "endpoint: %s\n", base_url_endpoint);
char *encoded_orig_uri = curl_easy_escape(curl, orig_uri, strlen(orig_uri));
const char *url_endpoint = concat(base_url_endpoint, encoded_orig_uri);
JANUS_LOG(LOG_INFO, "url endpoint: %s\n", url_endpoint);
string_buffer_initialize(&strbuf);
curl = curl_easy_init();
struct curl_slist *headers_list = NULL;
const char *auth_token = getenv("URI_CONV_AUTH_TOKEN");
const char *auth_header = concat("Authorization: Bearer ", auth_token);
headers_list = curl_slist_append(headers_list, auth_header);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers_list);
curl_easy_setopt(curl, CURLOPT_URL, url_endpoint);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &strbuf);
res = curl_easy_perform(curl);
if (res != CURLE_OK)
{
JANUS_LOG(LOG_ERR, "uri conversion failed. curl_easy_perform() err: %s\n", curl_easy_strerror(res));
curl_easy_cleanup(curl);
string_buffer_finish(&strbuf);
uri = "";
return uri;
}
uri = strbuf.ptr;
JANUS_LOG(LOG_INFO, "uri after conversion: %s\n", uri);
curl_easy_cleanup(curl);
free(encoded_orig_uri);
return uri;
}
/* Plugin methods */
janus_plugin *create(void);
int janus_sip_init(janus_callbacks *callback, const char *config_path);
void janus_sip_destroy(void);
int janus_sip_get_api_compatibility(void);
int janus_sip_get_version(void);
const char *janus_sip_get_version_string(void);
const char *janus_sip_get_description(void);
const char *janus_sip_get_name(void);
const char *janus_sip_get_author(void);
const char *janus_sip_get_package(void);
void janus_sip_create_session(janus_plugin_session *handle, int *error);
struct janus_plugin_result *janus_sip_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep);
void janus_sip_setup_media(janus_plugin_session *handle);
void janus_sip_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp *packet);
void janus_sip_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet);
void janus_sip_hangup_media(janus_plugin_session *handle);
void janus_sip_destroy_session(janus_plugin_session *handle, int *error);
json_t *janus_sip_query_session(janus_plugin_session *handle);
/* Plugin setup */
static janus_plugin janus_sip_plugin =
JANUS_PLUGIN_INIT (
.init = janus_sip_init,
.destroy = janus_sip_destroy,
.get_api_compatibility = janus_sip_get_api_compatibility,
.get_version = janus_sip_get_version,
.get_version_string = janus_sip_get_version_string,
.get_description = janus_sip_get_description,
.get_name = janus_sip_get_name,
.get_author = janus_sip_get_author,
.get_package = janus_sip_get_package,
.create_session = janus_sip_create_session,
.handle_message = janus_sip_handle_message,
.setup_media = janus_sip_setup_media,
.incoming_rtp = janus_sip_incoming_rtp,
.incoming_rtcp = janus_sip_incoming_rtcp,
.hangup_media = janus_sip_hangup_media,
.destroy_session = janus_sip_destroy_session,
.query_session = janus_sip_query_session,
);
/* Plugin creator */
janus_plugin *create(void) {
JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_SIP_NAME);
return &janus_sip_plugin;
}
/* Parameter validation */
static struct janus_json_parameter request_parameters[] = {
{"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
};
static struct janus_json_parameter register_parameters[] = {
{"type", JSON_STRING, 0},
{"send_register", JANUS_JSON_BOOL, 0},
{"force_udp", JANUS_JSON_BOOL, 0},
{"force_tcp", JANUS_JSON_BOOL, 0},
{"sips", JANUS_JSON_BOOL, 0},
{"rfc2543_cancel", JANUS_JSON_BOOL, 0},
{"username", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
{"secret", JSON_STRING, 0},
{"ha1_secret", JSON_STRING, 0},
{"authuser", JSON_STRING, 0},
{"display_name", JSON_STRING, 0},
{"user_agent", JSON_STRING, 0},
{"headers", JSON_OBJECT, 0},
{"contact_params", JSON_OBJECT, 0},
{"master_id", JANUS_JSON_INTEGER, 0},
{"refresh", JANUS_JSON_BOOL, 0},
{"incoming_header_prefixes", JSON_ARRAY, 0},
{"register_ttl", JANUS_JSON_INTEGER, 0}
};
static struct janus_json_parameter subscribe_parameters[] = {
{"to", JSON_STRING, 0},
{"event", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
{"accept", JSON_STRING, 0},
{"subscribe_ttl", JANUS_JSON_INTEGER, 0},
{"call_id", JANUS_JSON_STRING, 0},
{"headers", JSON_OBJECT, 0}
};
static struct janus_json_parameter proxy_parameters[] = {
{"proxy", JSON_STRING, 0},
{"outbound_proxy", JSON_STRING, 0}
};
static struct janus_json_parameter call_parameters[] = {
{"uri", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
{"headers", JSON_OBJECT, 0},
{"call_id", JANUS_JSON_STRING, 0},
{"srtp", JSON_STRING, 0},
{"srtp_profile", JSON_STRING, 0},
{"autoaccept_reinvites", JANUS_JSON_BOOL, 0},
{"refer_id", JANUS_JSON_INTEGER, 0},
/* The following are only needed in case "guest" registrations
* still need an authenticated INVITE for some reason */
{"secret", JSON_STRING, 0},
{"ha1_secret", JSON_STRING, 0},
{"authuser", JSON_STRING, 0}
};
static struct janus_json_parameter accept_parameters[] = {
{"srtp", JSON_STRING, 0},
{"headers", JSON_OBJECT, 0},
{"autoaccept_reinvites", JANUS_JSON_BOOL, 0}
};
static struct janus_json_parameter decline_parameters[] = {
{"code", JANUS_JSON_INTEGER, 0},
{"headers", JSON_OBJECT, 0},
{"refer_id", JANUS_JSON_INTEGER, 0}
};
static struct janus_json_parameter transfer_parameters[] = {
{"uri", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
{"replace", JANUS_JSON_STRING, 0}
};
static struct janus_json_parameter hold_parameters[] = {
{"direction", JSON_STRING, 0}
};
static struct janus_json_parameter recording_parameters[] = {
{"action", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
{"audio", JANUS_JSON_BOOL, 0},
{"video", JANUS_JSON_BOOL, 0},
{"peer_audio", JANUS_JSON_BOOL, 0},
{"peer_video", JANUS_JSON_BOOL, 0},
{"send_peer_pli", JANUS_JSON_BOOL, 0},
{"filename", JSON_STRING, 0}
};
static struct janus_json_parameter dtmf_info_parameters[] = {
{"digit", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
{"duration", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
{"headers", JSON_OBJECT, 0}
};
static struct janus_json_parameter info_parameters[] = {
{"type", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
{"content", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
{"headers", JSON_OBJECT, 0}
};
static struct janus_json_parameter sipmessage_parameters[] = {
{"content_type", JSON_STRING, 0},
{"content", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
{"uri", JSON_STRING, 0},
{"headers", JSON_OBJECT, 0},
{"call_id", JANUS_JSON_STRING, 0}
};
/* Useful stuff */
static volatile gint initialized = 0, stopping = 0;
static gboolean notify_events = TRUE;
static gboolean ipv6_disabled = FALSE;
static janus_callbacks *gateway = NULL;
static char *local_ip = NULL, *sdp_ip = NULL, *local_media_ip = NULL;
static janus_network_address janus_network_local_media_ip = { 0 };
static int keepalive_interval = 120;
static gboolean behind_nat = FALSE;
static char *user_agent;
#define JANUS_DEFAULT_REGISTER_TTL 3600
static int register_ttl = JANUS_DEFAULT_REGISTER_TTL;
#define JANUS_DEFAULT_SUBSCRIBE_TTL 3600
static int subscribe_ttl = JANUS_DEFAULT_SUBSCRIBE_TTL;
static uint16_t rtp_range_min = 10000;
static uint16_t rtp_range_max = 60000;
static int dscp_audio_rtp = 0;
static int dscp_video_rtp = 0;
static char *sips_certs_dir = NULL;
#define JANUS_DEFAULT_SIP_TIMER_T1X64 32000
static int sip_timer_t1x64 = JANUS_DEFAULT_SIP_TIMER_T1X64;
static uint16_t dtmf_keys[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '#', 'A', 'B', 'C', 'D'};
static gboolean query_contact_header = FALSE;
static GThread *handler_thread;
static void *janus_sip_handler(void *data);
static void janus_sip_hangup_media_internal(janus_plugin_session *handle);
typedef struct janus_sip_message {
janus_plugin_session *handle;
char *transaction;
json_t *message;
json_t *jsep;
} janus_sip_message;
static GAsyncQueue *messages = NULL;
static janus_sip_message exit_message;
typedef enum {
janus_sip_registration_status_disabled = -2,
janus_sip_registration_status_failed = -1,
janus_sip_registration_status_unregistered = 0,
janus_sip_registration_status_registering,
janus_sip_registration_status_registered,
janus_sip_registration_status_unregistering,
} janus_sip_registration_status;
static const char *janus_sip_registration_status_string(janus_sip_registration_status status) {
switch(status) {
case janus_sip_registration_status_disabled:
return "disabled";
case janus_sip_registration_status_failed:
return "failed";
case janus_sip_registration_status_unregistered:
return "unregistered";
case janus_sip_registration_status_registering:
return "registering";
case janus_sip_registration_status_registered:
return "registered";
case janus_sip_registration_status_unregistering:
return "unregistering";
default:
return "unknown";
}
}
typedef enum {
janus_sip_call_status_idle = 0,
janus_sip_call_status_inviting,
janus_sip_call_status_invited,
janus_sip_call_status_incall,
janus_sip_call_status_incall_reinviting,
janus_sip_call_status_incall_reinvited,
janus_sip_call_status_closing,
} janus_sip_call_status;
static const char *janus_sip_call_status_string(janus_sip_call_status status) {
switch(status) {
case janus_sip_call_status_idle:
return "idle";
case janus_sip_call_status_inviting:
return "inviting";
case janus_sip_call_status_invited:
return "invited";
case janus_sip_call_status_incall:
return "incall";
case janus_sip_call_status_incall_reinviting:
return "incall_reinviting";
case janus_sip_call_status_incall_reinvited:
return "incall_reinvited";
case janus_sip_call_status_closing:
return "closing";
default:
return "unknown";
}
}
/* Sofia stuff */
typedef struct ssip_s ssip_t;
typedef struct ssip_oper_s ssip_oper_t;
#undef SU_ROOT_MAGIC_T
#define SU_ROOT_MAGIC_T ssip_t
#undef NUA_MAGIC_T
#define NUA_MAGIC_T ssip_t
#undef NUA_HMAGIC_T
#define NUA_HMAGIC_T ssip_oper_t
struct ssip_s {
su_home_t s_home[1];
su_root_t *s_root;
nua_t *s_nua;
nua_handle_t *s_nh_r, *s_nh_i, *s_nh_m;
char *contact_header; /* Only needed for Sofia SIP >= 1.13 */
GHashTable *subscriptions;
janus_mutex smutex;
struct janus_sip_session *session;
};
typedef struct janus_sip_transfer {
struct janus_sip_session *session;
char *referred_by;
char *custom_headers;
nua_handle_t *nh_s;
nua_saved_event_t saved[1];
} janus_sip_transfer;
typedef enum {
janus_sip_secret_type_plaintext = 1,
janus_sip_secret_type_hashed = 2,
janus_sip_secret_type_unknown
} janus_sip_secret_type;
typedef struct janus_sip_account {
char *identity;
char *user_agent; /* Used to override the general UA string */
gboolean force_udp;
gboolean force_tcp;
gboolean sips;
gboolean rfc2543_cancel;
char *username;
char *display_name; /* Used for outgoing calls in the From header */
char *authuser; /**< username to use for authentication */
char *secret;
janus_sip_secret_type secret_type;
int sip_port;
char *proxy;
char *outbound_proxy;
janus_sip_registration_status registration_status;
} janus_sip_account;
typedef struct janus_sip_media {
char *remote_audio_ip; /* Peer audio media IP address */
char *remote_video_ip; /* Peer video media IP address */
gboolean earlymedia;
gboolean update;
gboolean autoaccept_reinvites;
gboolean ready;
gboolean require_srtp,
has_srtp_local_audio, has_srtp_local_video,
has_srtp_remote_audio, has_srtp_remote_video;
janus_srtp_profile srtp_profile;
gboolean on_hold;
gboolean has_audio;
int audio_rtp_fd, audio_rtcp_fd;
int local_audio_rtp_port, remote_audio_rtp_port;
int local_audio_rtcp_port, remote_audio_rtcp_port;
guint32 audio_ssrc, audio_ssrc_peer;
int audio_pt, opusred_pt;
const char *audio_pt_name;
gint32 audio_srtp_tag;
srtp_t audio_srtp_in, audio_srtp_out;
srtp_policy_t audio_remote_policy, audio_local_policy;
char *audio_srtp_local_profile, *audio_srtp_local_crypto;
gboolean audio_send, audio_recv;
janus_sdp_mdirection hold_audio_dir, pre_hold_audio_dir;
gboolean has_video;
int video_rtp_fd, video_rtcp_fd;
int local_video_rtp_port, remote_video_rtp_port;
int local_video_rtcp_port, remote_video_rtcp_port;
guint32 video_ssrc, video_ssrc_peer;
guint32 simulcast_ssrc;
int video_pt;
const char *video_pt_name;
gint32 video_srtp_tag;
srtp_t video_srtp_in, video_srtp_out;
srtp_policy_t video_remote_policy, video_local_policy;
char *video_srtp_local_profile, *video_srtp_local_crypto;
gboolean video_send, video_recv;
gboolean video_pli_supported;
janus_sdp_mdirection hold_video_dir, pre_hold_video_dir;
janus_rtp_switching_context acontext, vcontext;
int pipefd[2];
gboolean updated;
int video_orientation_extension_id;
int audio_level_extension_id;
int dtmf_pt;
} janus_sip_media;
typedef struct janus_sip_dtmf {
uint16_t dtmf_event_id;
uint32_t timestamp;
} janus_sip_dtmf;
typedef struct janus_sip_session {
janus_plugin_session *handle;
ssip_t *stack;
janus_sip_account account;
janus_sip_call_status status;
janus_sip_media media;
char *transaction;
char *callee;
char *callid;
guint32 refer_id; /* In case we were asked to transfer, keep track of the ID */
janus_sdp *sdp; /* The SDP this user sent */
janus_recorder *arc; /* The Janus recorder instance for this user's audio, if enabled */
janus_recorder *arc_peer; /* The Janus recorder instance for the peer's audio, if enabled */
janus_recorder *vrc; /* The Janus recorder instance for this user's video, if enabled */
janus_recorder *vrc_peer; /* The Janus recorder instance for the peer's video, if enabled */
janus_mutex rec_mutex; /* Mutex to protect the recorders from race conditions */
GThread *relayer_thread;
volatile gint establishing, established;
volatile gint hangingup;
volatile gint destroyed;
/* Sessions may be helpers under a "master" (e.g., for multiple calls from/to the same account) */
guint32 master_id; /* Master ID the helpers refer to */
struct janus_sip_session *master;
gboolean helper; /* Whether this session is a helper or not */
GList *helpers; /* The helper sessions, if this is the "master" */
janus_mutex mutex;
char *hangup_reason_header;
char *hangup_reason_header_protocol;
char *hangup_reason_header_cause;
GList *incoming_header_prefixes;
GList *active_calls;
janus_refcount ref;
janus_sip_dtmf latest_dtmf;
} janus_sip_session;
static GHashTable *sessions;
static GHashTable *identities;
static GHashTable *callids;
static GHashTable *messageids;
static GHashTable *masters;
static GHashTable *transfers;
static janus_mutex sessions_mutex = JANUS_MUTEX_INITIALIZER;
static void janus_sip_srtp_cleanup(janus_sip_session *session);
static void janus_sip_media_reset(janus_sip_session *session);
static void janus_sip_rtcp_pli_send(janus_sip_session *session);
static void janus_sip_call_update_status(janus_sip_session *session, janus_sip_call_status new_status) {
if(session->status != new_status) {
JANUS_LOG(LOG_VERB, "[%s] Call status change: [%s]-->[%s]\n", session->account.username == NULL ? "null" : session->account.username, janus_sip_call_status_string(session->status), janus_sip_call_status_string(new_status));
session->status = new_status;
}
}
static gboolean janus_sip_call_is_established(janus_sip_session *session) {
return (session->status == janus_sip_call_status_incall ||
session->status == janus_sip_call_status_incall_reinviting ||
session->status == janus_sip_call_status_incall_reinvited) ? TRUE : FALSE;
}
static void janus_sip_media_reset(janus_sip_session *session);
static void janus_sip_session_destroy(janus_sip_session *session) {
if(session && g_atomic_int_compare_and_exchange(&session->destroyed, 0, 1)) {
if(session->master == NULL && session->account.identity)
g_hash_table_remove(identities, session->account.identity);
if(session->callid)
g_hash_table_remove(callids, session->callid);
janus_refcount_decrease(&session->ref);
}
}
static void janus_sip_session_dereference(janus_sip_session *session) {
/* This is used to decrease the reference when removing to the messageids hashtable. janus_refcount_increase(&session->ref) must be called before inserting into messageids hashtable */
janus_refcount_decrease(&session->ref);
}
static char *janus_sip_session_contact_header_retrieve(janus_sip_session *session) {
if(session->helper && session->master)
return session->master->stack->contact_header;
else
return session->stack->contact_header;
}
static void janus_sip_session_free(const janus_refcount *session_ref) {
janus_sip_session *session = janus_refcount_containerof(session_ref, janus_sip_session, ref);
/* Remove the reference to the core plugin session */
janus_refcount_decrease(&session->handle->ref);
/* This session can be destroyed, free all the resources */
if(session->master == NULL && session->account.identity) {
g_free(session->account.identity);
session->account.identity = NULL;
}
if(session->stack != NULL) {
su_home_deinit(session->stack->s_home);
su_home_unref(session->stack->s_home);
g_free(session->stack->contact_header);
g_free(session->stack);
session->stack = NULL;
}
if(session->account.proxy) {
g_free(session->account.proxy);
session->account.proxy = NULL;
}
if(session->account.outbound_proxy) {
g_free(session->account.outbound_proxy);
session->account.outbound_proxy = NULL;
}
if(session->account.secret) {
g_free(session->account.secret);
session->account.secret = NULL;
}
if(session->account.username) {
g_free(session->account.username);
session->account.username = NULL;
}
if(session->account.display_name) {
g_free(session->account.display_name);
session->account.display_name = NULL;
}
if(session->account.user_agent) {
g_free(session->account.user_agent);
session->account.user_agent = NULL;
}
if(session->account.authuser) {
g_free(session->account.authuser);
session->account.authuser = NULL;
}
if(session->callee) {
g_free(session->callee);
session->callee = NULL;
}
if(session->callid) {
g_free(session->callid);
session->callid = NULL;
}
if(session->sdp) {
janus_sdp_destroy(session->sdp);
session->sdp = NULL;
}
if(session->transaction) {
g_free(session->transaction);
session->transaction = NULL;
}
if(session->media.remote_audio_ip) {
g_free(session->media.remote_audio_ip);
session->media.remote_audio_ip = NULL;
}
if(session->media.remote_video_ip) {
g_free(session->media.remote_video_ip);
session->media.remote_video_ip = NULL;
}
if(session->hangup_reason_header) {
g_free(session->hangup_reason_header);
session->hangup_reason_header = NULL;
}
if(session->hangup_reason_header_protocol) {
g_free(session->hangup_reason_header_protocol);
session->hangup_reason_header_protocol = NULL;
}
if(session->hangup_reason_header_cause) {
g_free(session->hangup_reason_header_cause);
session->hangup_reason_header_cause = NULL;
}
if(session->incoming_header_prefixes) {
g_list_free_full(session->incoming_header_prefixes, g_free);
session->incoming_header_prefixes = NULL;
}
janus_sip_srtp_cleanup(session);
g_free(session);
}
static void janus_sip_message_free(janus_sip_message *msg) {
if(!msg || msg == &exit_message)
return;
if(msg->handle && msg->handle->plugin_handle) {
janus_sip_session *session = (janus_sip_session *)msg->handle->plugin_handle;
janus_refcount_decrease(&session->ref);
}
msg->handle = NULL;
g_free(msg->transaction);
msg->transaction = NULL;
if(msg->message)
json_decref(msg->message);
msg->message = NULL;
if(msg->jsep)
json_decref(msg->jsep);
msg->jsep = NULL;
g_free(msg);
}
static void janus_sip_transfer_destroy(janus_sip_transfer *t) {
if(t == NULL)
return;
g_free(t->referred_by);
g_free(t->custom_headers);
if(t->session != NULL)
janus_refcount_decrease(&t->session->ref);
g_free(t);
}
/* SRTP stuff (in case we need SDES) */
static int janus_sip_srtp_set_local(janus_sip_session *session, gboolean video, char **profile, char **crypto) {
if(session == NULL)
return -1;
/* Which SRTP profile are we going to negotiate? */
int key_length = 0, salt_length = 0, master_length = 0;
if(session->media.srtp_profile == JANUS_SRTP_AES128_CM_SHA1_32) {
key_length = SRTP_MASTER_KEY_LENGTH;
salt_length = SRTP_MASTER_SALT_LENGTH;
master_length = SRTP_MASTER_LENGTH;
*profile = g_strdup("AES_CM_128_HMAC_SHA1_32");
} else if(session->media.srtp_profile == JANUS_SRTP_AES128_CM_SHA1_80) {
key_length = SRTP_MASTER_KEY_LENGTH;
salt_length = SRTP_MASTER_SALT_LENGTH;
master_length = SRTP_MASTER_LENGTH;
*profile = g_strdup("AES_CM_128_HMAC_SHA1_80");
#ifdef HAVE_SRTP_AESGCM
} else if(session->media.srtp_profile == JANUS_SRTP_AEAD_AES_128_GCM) {
key_length = SRTP_AESGCM128_MASTER_KEY_LENGTH;
salt_length = SRTP_AESGCM128_MASTER_SALT_LENGTH;
master_length = SRTP_AESGCM128_MASTER_LENGTH;
*profile = g_strdup("AEAD_AES_128_GCM");
} else if(session->media.srtp_profile == JANUS_SRTP_AEAD_AES_256_GCM) {
key_length = SRTP_AESGCM256_MASTER_KEY_LENGTH;
salt_length = SRTP_AESGCM256_MASTER_SALT_LENGTH;
master_length = SRTP_AESGCM256_MASTER_LENGTH;
*profile = g_strdup("AEAD_AES_256_GCM");
#endif
} else {
JANUS_LOG(LOG_ERR, "[SIP-%s] Unsupported SRTP profile\n", session->account.username);
return -2;
}
JANUS_LOG(LOG_VERB, "[SIP-%s] %s\n", session->account.username, *profile);
JANUS_LOG(LOG_VERB, "[SIP-%s] Key/Salt/Master: %d/%d/%d\n",
session->account.username, master_length, key_length, salt_length);
/* Generate key/salt */
uint8_t *key = g_malloc0(master_length);
srtp_crypto_get_random(key, master_length);
/* Set SRTP policies */
srtp_policy_t *policy = video ? &session->media.video_local_policy : &session->media.audio_local_policy;
switch(session->media.srtp_profile) {
case JANUS_SRTP_AES128_CM_SHA1_32:
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(&(policy->rtp));
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&(policy->rtcp));
break;
case JANUS_SRTP_AES128_CM_SHA1_80:
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&(policy->rtp));
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&(policy->rtcp));
break;
#ifdef HAVE_SRTP_AESGCM
case JANUS_SRTP_AEAD_AES_128_GCM:
srtp_crypto_policy_set_aes_gcm_128_16_auth(&(policy->rtp));
srtp_crypto_policy_set_aes_gcm_128_16_auth(&(policy->rtcp));
break;
case JANUS_SRTP_AEAD_AES_256_GCM:
srtp_crypto_policy_set_aes_gcm_256_16_auth(&(policy->rtp));
srtp_crypto_policy_set_aes_gcm_256_16_auth(&(policy->rtcp));
break;
#endif
default:
/* Will never happen? */
JANUS_LOG(LOG_WARN, "[SIP-%s] Unsupported SRTP profile\n", session->account.username);
break;
}
policy->ssrc.type = ssrc_any_inbound;
policy->key = key;
policy->next = NULL;
/* Create SRTP context */
srtp_err_status_t res = srtp_create(video ? &session->media.video_srtp_out : &session->media.audio_srtp_out, policy);
if(res != srtp_err_status_ok) {
/* Something went wrong... */
JANUS_LOG(LOG_ERR, "Oops, error creating outbound SRTP session: %d (%s)\n", res, janus_srtp_error_str(res));
g_free(*profile);
*profile = NULL;
g_free(key);
policy->key = NULL;
return -2;
}
/* Base64 encode the salt */
*crypto = g_base64_encode(key, master_length);
if((video && session->media.video_srtp_out) || (!video && session->media.audio_srtp_out)) {
JANUS_LOG(LOG_VERB, "%s outbound SRTP session created\n", video ? "Video" : "Audio");
}
return 0;
}
static int janus_sip_srtp_set_remote(janus_sip_session *session, gboolean video, const char *profile, const char *crypto) {
if(session == NULL || profile == NULL || crypto == NULL)
return -1;
/* Which SRTP profile is being negotiated? */
JANUS_LOG(LOG_VERB, "[SIP-%s] %s\n", session->account.username, profile);
gsize key_length = 0, salt_length = 0, master_length = 0;
if(!strcasecmp(profile, "AES_CM_128_HMAC_SHA1_32")) {
session->media.srtp_profile = JANUS_SRTP_AES128_CM_SHA1_32;
key_length = SRTP_MASTER_KEY_LENGTH;
salt_length = SRTP_MASTER_SALT_LENGTH;
master_length = SRTP_MASTER_LENGTH;
} else if(!strcasecmp(profile, "AES_CM_128_HMAC_SHA1_80")) {
session->media.srtp_profile = JANUS_SRTP_AES128_CM_SHA1_80;
key_length = SRTP_MASTER_KEY_LENGTH;
salt_length = SRTP_MASTER_SALT_LENGTH;
master_length = SRTP_MASTER_LENGTH;
#ifdef HAVE_SRTP_AESGCM
} else if(!strcasecmp(profile, "AEAD_AES_128_GCM")) {
session->media.srtp_profile = JANUS_SRTP_AEAD_AES_128_GCM;
key_length = SRTP_AESGCM128_MASTER_KEY_LENGTH;
salt_length = SRTP_AESGCM128_MASTER_SALT_LENGTH;
master_length = SRTP_AESGCM128_MASTER_LENGTH;
} else if(!strcasecmp(profile, "AEAD_AES_256_GCM")) {
session->media.srtp_profile = JANUS_SRTP_AEAD_AES_256_GCM;
key_length = SRTP_AESGCM256_MASTER_KEY_LENGTH;
salt_length = SRTP_AESGCM256_MASTER_SALT_LENGTH;
master_length = SRTP_AESGCM256_MASTER_LENGTH;
#endif
} else {
JANUS_LOG(LOG_WARN, "[SIP-%s] Unsupported SRTP profile %s\n", session->account.username, profile);
return -2;
}
JANUS_LOG(LOG_VERB, "[SIP-%s] Key/Salt/Master: %zu/%zu/%zu\n",
session->account.username, master_length, key_length, salt_length);
/* Base64 decode the crypto string and set it as the remote SRTP context */
gsize len = 0;
guchar *decoded = g_base64_decode(crypto, &len);
if(len < master_length) {
/* FIXME Can this happen? */
g_free(decoded);
return -3;
}
/* Set SRTP policies */
srtp_policy_t *policy = video ? &session->media.video_remote_policy : &session->media.audio_remote_policy;
switch(session->media.srtp_profile) {
case JANUS_SRTP_AES128_CM_SHA1_32:
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(&(policy->rtp));
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&(policy->rtcp));
break;
case JANUS_SRTP_AES128_CM_SHA1_80:
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&(policy->rtp));
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&(policy->rtcp));
break;
#ifdef HAVE_SRTP_AESGCM
case JANUS_SRTP_AEAD_AES_128_GCM:
srtp_crypto_policy_set_aes_gcm_128_16_auth(&(policy->rtp));
srtp_crypto_policy_set_aes_gcm_128_16_auth(&(policy->rtcp));
break;
case JANUS_SRTP_AEAD_AES_256_GCM:
srtp_crypto_policy_set_aes_gcm_256_16_auth(&(policy->rtp));
srtp_crypto_policy_set_aes_gcm_256_16_auth(&(policy->rtcp));
break;
#endif
default:
/* Will never happen? */
JANUS_LOG(LOG_WARN, "[SIP-%s] Unsupported SRTP profile\n", session->account.username);
break;
}
policy->ssrc.type = ssrc_any_inbound;
policy->key = decoded;
policy->next = NULL;
/* Create SRTP context */
srtp_err_status_t res = srtp_create(video ? &session->media.video_srtp_in : &session->media.audio_srtp_in, policy);
if(res != srtp_err_status_ok) {
/* Something went wrong... */
JANUS_LOG(LOG_ERR, "Oops, error creating inbound SRTP session: %d (%s)\n", res, janus_srtp_error_str(res));
g_free(decoded);
policy->key = NULL;
return -2;
}
if((video && session->media.video_srtp_in) || (!video && session->media.audio_srtp_in)) {
JANUS_LOG(LOG_VERB, "%s inbound SRTP session created\n", video ? "Video" : "Audio");
}
return 0;
}
static void janus_sip_srtp_cleanup(janus_sip_session *session) {
if(session == NULL)
return;
session->media.require_srtp = FALSE;
session->media.has_srtp_local_audio = FALSE;
session->media.has_srtp_local_video = FALSE;
session->media.has_srtp_remote_audio = FALSE;
session->media.has_srtp_remote_video = FALSE;
session->media.srtp_profile = 0;
/* Audio */
session->media.audio_srtp_tag = 0;
if(session->media.audio_srtp_out)
srtp_dealloc(session->media.audio_srtp_out);
session->media.audio_srtp_out = NULL;
g_free(session->media.audio_local_policy.key);
session->media.audio_local_policy.key = NULL;
if(session->media.audio_srtp_in)
srtp_dealloc(session->media.audio_srtp_in);
session->media.audio_srtp_in = NULL;
g_free(session->media.audio_remote_policy.key);
session->media.audio_remote_policy.key = NULL;
if(session->media.audio_srtp_local_profile) {
g_free(session->media.audio_srtp_local_profile);
session->media.audio_srtp_local_profile = NULL;
}
if(session->media.audio_srtp_local_crypto) {
g_free(session->media.audio_srtp_local_crypto);
session->media.audio_srtp_local_crypto = NULL;
}
/* Video */
session->media.video_srtp_tag = 0;
if(session->media.video_srtp_out)
srtp_dealloc(session->media.video_srtp_out);
session->media.video_srtp_out = NULL;
g_free(session->media.video_local_policy.key);
session->media.video_local_policy.key = NULL;
if(session->media.video_srtp_in)
srtp_dealloc(session->media.video_srtp_in);
session->media.video_srtp_in = NULL;
g_free(session->media.video_remote_policy.key);
session->media.video_remote_policy.key = NULL;
if(session->media.video_srtp_local_profile) {
g_free(session->media.video_srtp_local_profile);
session->media.video_srtp_local_profile = NULL;
}
if(session->media.video_srtp_local_crypto) {
g_free(session->media.video_srtp_local_crypto);
session->media.video_srtp_local_crypto = NULL;
}
}
static void janus_sip_media_reset(janus_sip_session *session) {
if(session == NULL)
return;
g_free(session->media.remote_audio_ip);
session->media.remote_audio_ip = NULL;
g_free(session->media.remote_video_ip);
session->media.remote_video_ip = NULL;
session->media.earlymedia = FALSE;
session->media.update = FALSE;
session->media.updated = FALSE;
session->media.autoaccept_reinvites = TRUE;
session->media.ready = FALSE;
session->media.require_srtp = FALSE;
session->media.on_hold = FALSE;
session->media.has_audio = FALSE;
session->media.audio_pt = -1;
session->media.opusred_pt = -1;
session->media.audio_pt_name = NULL; /* Immutable string, no need to free*/
session->media.audio_send = TRUE;
session->media.audio_recv = TRUE;
session->media.hold_audio_dir = JANUS_SDP_SENDONLY;
session->media.pre_hold_audio_dir = JANUS_SDP_DEFAULT;
session->media.has_video = FALSE;
session->media.video_pt = -1;
session->media.video_pt_name = NULL; /* Immutable string, no need to free*/
session->media.video_send = TRUE;
session->media.video_recv = TRUE;
session->media.video_pli_supported = FALSE;
session->media.hold_video_dir = JANUS_SDP_SENDONLY;
session->media.pre_hold_video_dir = JANUS_SDP_DEFAULT;
session->media.video_orientation_extension_id = -1;
session->media.audio_level_extension_id = -1;
session->media.dtmf_pt = -1;
janus_rtp_switching_context_reset(&session->media.acontext);
janus_rtp_switching_context_reset(&session->media.vcontext);
}
/* Sofia Event thread */
gpointer janus_sip_sofia_thread(gpointer user_data);
/* Sofia callbacks */
void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, nua_t *nua, nua_magic_t *magic, nua_handle_t *nh, nua_hmagic_t *hmagic, sip_t const *sip, tagi_t tags[]);
void janus_sip_save_reason(sip_t const *sip, janus_sip_session *session);
/* SDP parsing and manipulation */
void janus_sip_sdp_process(janus_sip_session *session, janus_sdp *sdp, gboolean answer, gboolean update, gboolean *changed);
char *janus_sip_sdp_manipulate(janus_sip_session *session, janus_sdp *sdp, gboolean answer);
/* Media */
static int janus_sip_allocate_local_ports(janus_sip_session *session, gboolean update);
static void *janus_sip_relay_thread(void *data);
static void janus_sip_media_cleanup(janus_sip_session *session);
static void janus_sip_check_rfc2833(janus_sip_session *session, char *buffer, int len);
/* URI parsing utilities */
#define JANUS_SIP_URI_MAXLEN 1024
typedef struct {
char data[JANUS_SIP_URI_MAXLEN];
url_t url[1];
} janus_sip_uri_t;
/* Parses a SIP URI (SIPS is not supported), returns 0 on success, -1 otherwise */
static int janus_sip_parse_uri(janus_sip_uri_t *sip_uri, const char *data) {
g_strlcpy(sip_uri->data, data, JANUS_SIP_URI_MAXLEN);
if(url_d(sip_uri->url, sip_uri->data) < 0 || sip_uri->url->url_type != url_sip)
return -1;
return 0;
}
/* Similar to the above function, but it also accepts SIPS URIs */
static int janus_sip_parse_proxy_uri(janus_sip_uri_t *sip_uri, const char *data) {
g_strlcpy(sip_uri->data, data, JANUS_SIP_URI_MAXLEN);
if(url_d(sip_uri->url, sip_uri->data) < 0 || (sip_uri->url->url_type != url_sip && sip_uri->url->url_type != url_sips))
return -1;
return 0;
}
/* Helper to strip quotes from a SIP Reason Header */
static void janus_sip_remove_quotes(char *str) {
size_t len = strlen(str);
if(len > 2 && str[0] == '"' && str[len-1] == '"') {
memmove(str, str+1, len-2);
str[len-2] = 0;
}
}
static json_t *janus_sip_get_incoming_headers(const sip_t *sip, const janus_sip_session *session) {
json_t *headers = json_object();
if(!sip)
return headers;
sip_unknown_t *unknown_header = sip->sip_unknown;
while(unknown_header != NULL) {
GList *temp = session->incoming_header_prefixes;
while(temp != NULL) {
char *header_prefix = (char *)temp->data;
if(header_prefix != NULL && unknown_header->un_name != NULL) {
if(strncasecmp(unknown_header->un_name, header_prefix, strlen(header_prefix)) == 0) {
const char *header_name = g_strdup(unknown_header->un_name);
json_object_set(headers, header_name, json_string(unknown_header->un_value));
break;
}
}
temp = temp->next;
}
unknown_header = unknown_header->un_next;
}
return headers;
}
/* Error codes */
#define JANUS_SIP_ERROR_UNKNOWN_ERROR 499
#define JANUS_SIP_ERROR_NO_MESSAGE 440
#define JANUS_SIP_ERROR_INVALID_JSON 441
#define JANUS_SIP_ERROR_INVALID_REQUEST 442
#define JANUS_SIP_ERROR_MISSING_ELEMENT 443
#define JANUS_SIP_ERROR_INVALID_ELEMENT 444
#define JANUS_SIP_ERROR_ALREADY_REGISTERED 445
#define JANUS_SIP_ERROR_INVALID_ADDRESS 446
#define JANUS_SIP_ERROR_WRONG_STATE 447
#define JANUS_SIP_ERROR_MISSING_SDP 448
#define JANUS_SIP_ERROR_LIBSOFIA_ERROR 449
#define JANUS_SIP_ERROR_IO_ERROR 450
#define JANUS_SIP_ERROR_RECORDING_ERROR 451
#define JANUS_SIP_ERROR_TOO_STRICT 452
#define JANUS_SIP_ERROR_HELPER_ERROR 453
#define JANUS_SIP_ERROR_NO_SUCH_CALLID 454
#define JANUS_SIP_ERROR_URI_CONV 455
/* Random string helper (for call-ids) */
static char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
static void janus_sip_random_string(int length, char *buffer) {
if(length > 0 && buffer) {
int l = (int)(sizeof(charset)-1);
int i=0;
for(i=0; i<length; i++) {
int key = janus_random_uint32() % l;
buffer[i] = charset[key];
}
buffer[length-1] = '\0';
}
}
static void janus_sip_parse_custom_headers(json_t *root, char *custom_headers, size_t size) {
custom_headers[0] = '\0';
json_t *headers = json_object_get(root, "headers");
if(headers) {
if(json_object_size(headers) > 0) {
/* Parse custom headers */
const char *key = NULL;
json_t *value = NULL;
void *iter = json_object_iter(headers);
while(iter != NULL) {
key = json_object_iter_key(iter);
value = json_object_get(headers, key);
if(value == NULL || !json_is_string(value)) {
JANUS_LOG(LOG_WARN, "Skipping header '%s': value is not a string\n", key);
iter = json_object_iter_next(headers, iter);
continue;
}
char h[255];
g_snprintf(h, 255, "%s: %s", key, json_string_value(value));
JANUS_LOG(LOG_VERB, "Adding custom header, %s\n", h);
janus_strlcat(custom_headers, h, size - 2);
janus_strlcat(custom_headers, "\r\n", size);
iter = json_object_iter_next(headers, iter);
}
}
}
}
static void janus_sip_parse_custom_contact_params(json_t *root, char *custom_params, size_t size) {
custom_params[0] = '\0';
json_t *params = json_object_get(root, "contact_params");
gboolean first = TRUE;
if(params) {
if(json_object_size(params) > 0) {
/* Parse custom Contact URI params */
const char *key = NULL;
json_t *value = NULL;
void *iter = json_object_iter(params);
while(iter != NULL) {
key = json_object_iter_key(iter);
value = json_object_get(params, key);
if(value == NULL || !json_is_string(value)) {
JANUS_LOG(LOG_WARN, "Skipping param '%s': value is not a string\n", key);
iter = json_object_iter_next(params, iter);
continue;
}
char h[255];
if(first) {
first = FALSE;
g_snprintf(h, 255, "%s=%s", key, json_string_value(value));
} else {
g_snprintf(h, 255, ";%s=%s", key, json_string_value(value));
}
JANUS_LOG(LOG_VERB, "Adding custom param, %s\n", h);
janus_strlcat(custom_params, h, size);
iter = json_object_iter_next(params, iter);
}
}
}
}
/* Sofia SIP logger function: when the Event Handlers mechanism is enabled,
* we use this to intercept SIP messages sent by the stack (received
* messages are more easily recoverable in janus_sip_sofia_callback) */
char sofia_log[3072];
char call_id[255];
gboolean skip = FALSE, started = FALSE, append = FALSE;
static void janus_sip_sofia_logger(void *stream, char const *fmt, va_list ap) {
if(!fmt)
return;
char line[255];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#ifndef __clang__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
#endif
g_vsnprintf(line, sizeof(line), fmt, ap);
#ifndef __clang__
#pragma GCC diagnostic pop
#endif
#pragma GCC diagnostic pop
if(skip) {
/* This is a message we're not interested in: just check when it ends */
if(line[3] == '-') {
skip = FALSE;
append = FALSE;
}
return;
}
if(append) {
/* We're copying a message in our buffer: check if this is the end */
if(line[3] == '-') {
if(!started) {
/* Ok, start appending from now on */
started = TRUE;
sofia_log[0] = '\0';
call_id[0] = '\0';
} else {
/* Message ended, handle it */
skip = FALSE;
append = FALSE;
/* Look for the session this message belongs to */
janus_sip_session *session = NULL;
janus_mutex_lock(&sessions_mutex);
if(strlen(call_id))
session = g_hash_table_lookup(callids, call_id);
if(!session) {
/* Couldn't find any SIP session with that Call-ID, check the request */
if(strstr(sofia_log, "REGISTER") == sofia_log || strstr(sofia_log, "SIP/2.0 ") == sofia_log) {
/* FIXME This is a REGISTER or a response code:
* check the To header and get the identity from there */
char *from = strstr(sofia_log, "To: ");
if(from) {
from = from+4;
char *start = strstr(from, "<");
if(start) {
start++;
char *end = strstr(from, ">");
if(end) {
*end = '\0';
g_snprintf(call_id, sizeof(call_id), "%s", start);
*end = '>';
session = g_hash_table_lookup(identities, call_id);
}
}
}
}
}
if(session)
janus_refcount_increase(&session->ref);
janus_mutex_unlock(&sessions_mutex);
if(session) {
/* Notify event handlers about the content of the whole outgoing SIP message */
json_t *info = json_object();
json_object_set_new(info, "event", json_string("sip-out"));
json_object_set_new(info, "sip", json_string(sofia_log));
gateway->notify_event(&janus_sip_plugin, session->handle, info);
janus_refcount_decrease(&session->ref);
} else {
JANUS_LOG(LOG_WARN, "Couldn't find a session associated to this message, dropping it...\n%s", sofia_log);
}
/* Done, reset the buffers */
sofia_log[0] = '\0';
call_id[0] = '\0';
}
return;
}
if(strlen(line) == 1) {
/* Append a carriage and return */
janus_strlcat(sofia_log, "\r\n", sizeof(sofia_log));
} else {
/* If this is an OPTIONS, we don't care: drop it */
char *header = &line[3];
if(strstr(header, "OPTIONS") == header) {
skip = TRUE;
return;
}
/* Is this a Call-ID header? Keep note of it */
if(strstr(header, "Call-ID") == header) {
g_snprintf(call_id, sizeof(call_id), "%s", header+9);
}
/* Append the line to our buffer, skipping the indent */
janus_strlcat(sofia_log, &line[3], sizeof(sofia_log));
}
return;
}
/* Still waiting to decide if this is a message we need */
if(line[0] == 's' && line[1] == 'e' && line[2] == 'n' && line[3] == 'd' && line[4] == ' ') {
/* An outgoing message is going to be logged, prepare for that */
skip = FALSE;
started = FALSE;
append = TRUE;
int length = atoi(&line[5]);
JANUS_LOG(LOG_HUGE, "Intercepting message (%d bytes)\n", length);
if(strstr(line, "-----"))
started = TRUE;
}
}
/* Helpers to ref/unref sessions with active calls */
static void janus_sip_ref_active_call(janus_sip_session *session) {
if(session == NULL)
return;
janus_sip_session *master = session->master;
if(master) {
janus_mutex_lock(&master->mutex);
master->active_calls = g_list_append(master->active_calls, session);
janus_refcount_increase(&session->ref);
janus_mutex_unlock(&master->mutex);
} else {
janus_mutex_lock(&session->mutex);
session->active_calls = g_list_append(session->active_calls, session);
janus_refcount_increase(&session->ref);
janus_mutex_unlock(&session->mutex);
}
}
static void janus_sip_unref_active_call(janus_sip_session *session) {
if(session == NULL)
return;
janus_sip_session *master = session->master;
if(master) {
janus_mutex_lock(&master->mutex);
if(g_list_find(master->active_calls, session) != NULL) {
master->active_calls = g_list_remove(master->active_calls, session);
janus_refcount_decrease(&session->ref);
}
janus_mutex_unlock(&master->mutex);
} else {
janus_mutex_lock(&session->mutex);
if(g_list_find(session->active_calls, session) != NULL) {
session->active_calls = g_list_remove(session->active_calls, session);
janus_refcount_decrease(&session->ref);
}
janus_mutex_unlock(&session->mutex);
}
}
/* Plugin implementation */
int janus_sip_init(janus_callbacks *callback, const char *config_path) {
if(g_atomic_int_get(&stopping)) {
/* Still stopping from before */
return -1;
}
if(callback == NULL || config_path == NULL) {
/* Invalid arguments */
return -1;
}
/* First of all, let's check what version of Sofia SIP is available */
int sofia_major = 0, sofia_minor = 0, sofia_patch = 0;
if(sscanf(SOFIA_SIP_VERSION, "%d.%d.%d", &sofia_major, &sofia_minor, &sofia_patch) == 3) {
if(sofia_major > 2 || (sofia_major >= 1 && sofia_minor >= 13)) {
/* Versions of Sofia SIP >= 1.13 apparently don't add a Contact header in
* dialogs, so we'll query it ourselves using nua_get_params (see #2597) */
query_contact_header = TRUE;
}
}
/* Read configuration */
char filename[255];
g_snprintf(filename, 255, "%s/%s.jcfg", config_path, JANUS_SIP_PACKAGE);
JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
janus_config *config = janus_config_parse(filename);
if(config == NULL) {
JANUS_LOG(LOG_WARN, "Couldn't find .jcfg configuration file (%s), trying .cfg\n", JANUS_SIP_PACKAGE);
g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_SIP_PACKAGE);
JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename);
config = janus_config_parse(filename);
}
if(config != NULL) {
janus_config_print(config);
janus_config_category *config_general = janus_config_get_create(config, NULL, janus_config_type_category, "general");
janus_config_item *item = janus_config_get(config, config_general, janus_config_type_item, "local_ip");
if(item && item->value) {
/* Verify that the address is valid */
struct ifaddrs *ifas = NULL;
janus_network_address iface;
janus_network_address_string_buffer ibuf;
if(getifaddrs(&ifas) == -1) {
JANUS_LOG(LOG_ERR, "Unable to acquire list of network devices/interfaces; some configurations may not work as expected... %d (%s)\n",
errno, g_strerror(errno));
} else {
if(janus_network_lookup_interface(ifas, item->value, &iface) != 0) {
JANUS_LOG(LOG_WARN, "Error setting local IP address to %s, falling back to detecting IP address...\n", item->value);
} else {
if(janus_network_address_to_string_buffer(&iface, &ibuf) != 0 || janus_network_address_string_buffer_is_null(&ibuf)) {
JANUS_LOG(LOG_WARN, "Error getting local IP address from %s, falling back to detecting IP address...\n", item->value);
} else {
local_ip = g_strdup(janus_network_address_string_from_buffer(&ibuf));
}
}
freeifaddrs(ifas);
}
}
item = janus_config_get(config, config_general, janus_config_type_item, "local_media_ip");
if(item && item->value && strlen(item->value) > 0)
local_media_ip = g_strdup(item->value);
item = janus_config_get(config, config_general, janus_config_type_item, "sdp_ip");
if(item && item->value && strlen(item->value) > 0) {
sdp_ip = g_strdup(item->value);
JANUS_LOG(LOG_VERB, "IP to advertise in SDP: %s\n", sdp_ip);
}
item = janus_config_get(config, config_general, janus_config_type_item, "keepalive_interval");
if(item && item->value)
keepalive_interval = atoi(item->value);
if(keepalive_interval < 0) {
JANUS_LOG(LOG_ERR, "Invalid SIP keep-alive interval: %d (falling back to default)\n", keepalive_interval);
keepalive_interval = 120;
} else {
JANUS_LOG(LOG_VERB, "SIP keep-alive interval set to %d seconds\n", keepalive_interval);
}
item = janus_config_get(config, config_general, janus_config_type_item, "sip_timer_t1x64");
if(item && item->value)
sip_timer_t1x64 = atoi(item->value);
if(sip_timer_t1x64 < 1) {
JANUS_LOG(LOG_ERR, "Invalid SIP Timer T1X64 value: %d (falling back to default)\n", sip_timer_t1x64);
sip_timer_t1x64 = JANUS_DEFAULT_SIP_TIMER_T1X64;
} else {
JANUS_LOG(LOG_VERB, "SIP Timer T1X64 set to %d milliseconds\n", sip_timer_t1x64);
}
item = janus_config_get(config, config_general, janus_config_type_item, "register_ttl");
if(item && item->value)
register_ttl = atoi(item->value);
if(register_ttl < 0) {
JANUS_LOG(LOG_ERR, "Invalid SIP registration TTL: %d (falling back to default)\n", register_ttl);
register_ttl = JANUS_DEFAULT_REGISTER_TTL;
} else {
JANUS_LOG(LOG_VERB, "SIP registration TTL set to %d seconds\n", register_ttl);
}
item = janus_config_get(config, config_general, janus_config_type_item, "behind_nat");
if(item && item->value)
behind_nat = janus_is_true(item->value);
item = janus_config_get(config, config_general, janus_config_type_item, "user_agent");
if(item && item->value)
user_agent = g_strdup(item->value);
else
user_agent = g_strdup("Janus WebRTC Server SIP Plugin "JANUS_SIP_VERSION_STRING);
JANUS_LOG(LOG_VERB, "SIP User-Agent set to %s\n", user_agent);
item = janus_config_get(config, config_general, janus_config_type_item, "rtp_port_range");
if(item && item->value) {
/* Split in min and max port */
char *maxport = strrchr(item->value, '-');
if(maxport != NULL) {
*maxport = '\0';
maxport++;
if(janus_string_to_uint16(item->value, &rtp_range_min) < 0)
JANUS_LOG(LOG_WARN, "Invalid RTP min port value: %s (assuming 0)\n", item->value);
if(janus_string_to_uint16(maxport, &rtp_range_max) < 0)
JANUS_LOG(LOG_WARN, "Invalid RTP max port value: %s (assuming 0)\n", maxport);
maxport--;
*maxport = '-';
}
if(rtp_range_min > rtp_range_max) {
uint16_t temp_port = rtp_range_min;
rtp_range_min = rtp_range_max;
rtp_range_max = temp_port;
}
if(rtp_range_max == 0)
rtp_range_max = 65535;
JANUS_LOG(LOG_VERB, "SIP RTP/RTCP port range: %u -- %u\n", rtp_range_min, rtp_range_max);
}
item = janus_config_get(config, config_general, janus_config_type_item, "events");
if(item != NULL && item->value != NULL)
notify_events = janus_is_true(item->value);
if(!notify_events && callback->events_is_enabled()) {
JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_SIP_NAME);
}
/* Is there any DSCP TOS to apply? */
item = janus_config_get(config, config_general, janus_config_type_item, "dscp_audio_rtp");
if(item && item->value) {
int val = atoi(item->value);
if(val < 0) {
JANUS_LOG(LOG_WARN, "Ignoring dscp_audio_rtp value as it's not a positive integer\n");
} else {
dscp_audio_rtp = val;
}
}
item = janus_config_get(config, config_general, janus_config_type_item, "dscp_video_rtp");
if(item && item->value) {
int val = atoi(item->value);
if(val < 0) {
JANUS_LOG(LOG_WARN, "Ignoring dscp_video_rtp value as it's not a positive integer\n");
} else {
dscp_video_rtp = val;
}
}
/* Check if Sofia should find certificates in a custom folder */
item = janus_config_get(config, config_general, janus_config_type_item, "sips_certs_dir");
if(item && item->value) {
sips_certs_dir = g_strdup(item->value);
JANUS_LOG(LOG_VERB, "Sofia SIP certificates folder: %s\n", sips_certs_dir);
}
janus_config_destroy(config);
}
config = NULL;
if(local_ip == NULL) {
local_ip = janus_network_detect_local_ip_as_string(janus_network_query_options_any_ip);
if(local_ip == NULL) {
JANUS_LOG(LOG_WARN, "Couldn't find any address! using 127.0.0.1 as the local IP... (which is NOT going to work out of your machine)\n");
local_ip = g_strdup("127.0.0.1");
}
}
JANUS_LOG(LOG_VERB, "Local IP set to %s\n", local_ip);
/* Since we might have to derive SDP connection address from local_media_ip make sure it has a meaningful value
* for the purpose of using it in the SDP c= header */
janus_network_address_nullify(&janus_network_local_media_ip);
if(local_media_ip) {
if(janus_network_string_to_address(janus_network_query_options_any_ip, local_media_ip, &janus_network_local_media_ip) != 0) {
JANUS_LOG(LOG_ERR, "Invalid local media IP address [%s]...\n", local_media_ip);
return -1;
}
if((janus_network_local_media_ip.family == AF_INET && janus_network_local_media_ip.ipv4.s_addr == INADDR_ANY) ||
(janus_network_local_media_ip.family == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED(&janus_network_local_media_ip.ipv6))) {
janus_network_address_nullify(&janus_network_local_media_ip);
}
}
JANUS_LOG(LOG_VERB, "Binding media address set to [%s]...\n", janus_network_address_is_null(&janus_network_local_media_ip) ? "any" : local_media_ip);
if(!sdp_ip) {
char *ip = janus_network_address_is_null(&janus_network_local_media_ip) ? local_ip : local_media_ip;
if(ip) {
sdp_ip = g_strdup(ip);
JANUS_LOG(LOG_VERB, "IP to advertise in SDP: %s\n", sdp_ip);
}
}
/* Setup sofia */
su_init();
if(notify_events && callback->events_is_enabled()) {
JANUS_LOG(LOG_WARN, "sofia-sip logs are going to be redirected and they will not be shown in the process output\n");
/* Enable the transport logging, as we want to have access to the SIP messages */
setenv("TPORT_LOG", "1", 1);
su_log_redirect(NULL, janus_sip_sofia_logger, NULL);
}
sessions = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)janus_sip_session_destroy);
identities = g_hash_table_new(g_str_hash, g_str_equal);
callids = g_hash_table_new(g_str_hash, g_str_equal);
messageids = g_hash_table_new_full(NULL, NULL, (GDestroyNotify)g_free, (GDestroyNotify)janus_sip_session_dereference);
masters = g_hash_table_new(NULL, NULL);
transfers = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)janus_sip_transfer_destroy);
messages = g_async_queue_new_full((GDestroyNotify) janus_sip_message_free);
/* This is the callback we'll need to invoke to contact the Janus core */
gateway = callback;
if(janus_network_address_is_null(&janus_network_local_media_ip) ||
janus_network_local_media_ip.family == AF_INET6) {
/* Finally, let's check if IPv6 is disabled, as we may need to know for RTP/RTCP sockets */
int fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
if(fd <= 0) {
ipv6_disabled = TRUE;
} else {
int v6only = 0;
if(setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)) != 0)
ipv6_disabled = TRUE;
}
if(fd > 0)
close(fd);
if(ipv6_disabled) {
if(!janus_network_address_is_null(&janus_network_local_media_ip)) {
JANUS_LOG(LOG_ERR, "IPv6 disabled and local media address is IPv6...\n");
return -1;
}
JANUS_LOG(LOG_WARN, "IPv6 disabled, will only use IPv4 for RTP/RTCP sockets (SIP)\n");
}
} else if(janus_network_local_media_ip.family == AF_INET) {
/* Disable if we have a specified IPv4 address for RTP/RTCP sockets */
ipv6_disabled = TRUE;
}
g_atomic_int_set(&initialized, 1);
/* Launch the thread that will handle incoming messages */
GError *error = NULL;
handler_thread = g_thread_try_new("sip handler", janus_sip_handler, NULL, &error);
if(error != NULL) {
g_atomic_int_set(&initialized, 0);
JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the SIP handler thread...\n",
error->code, error->message ? error->message : "??");
g_error_free(error);
return -1;
}
JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_SIP_NAME);
return 0;
}
void janus_sip_destroy(void) {
if(!g_atomic_int_get(&initialized))
return;
g_atomic_int_set(&stopping, 1);
g_async_queue_push(messages, &exit_message);
if(handler_thread != NULL) {
g_thread_join(handler_thread);
handler_thread = NULL;
}
/* FIXME We should destroy the sessions cleanly */
janus_mutex_lock(&sessions_mutex);
g_hash_table_destroy(sessions);
g_hash_table_destroy(callids);
g_hash_table_destroy(messageids);
g_hash_table_destroy(identities);
g_hash_table_destroy(masters);
g_hash_table_destroy(transfers);
sessions = NULL;
callids = NULL;
messageids = NULL;
identities = NULL;
masters = NULL;
transfers = NULL;
janus_mutex_unlock(&sessions_mutex);
g_async_queue_unref(messages);
messages = NULL;
g_atomic_int_set(&initialized, 0);
g_atomic_int_set(&stopping, 0);
/* Deinitialize sofia */
su_deinit();
g_free(local_ip);
g_free(local_media_ip);
g_free(sdp_ip);
JANUS_LOG(LOG_INFO, "%s destroyed!\n", JANUS_SIP_NAME);
}
int janus_sip_get_api_compatibility(void) {
/* Important! This is what your plugin MUST always return: don't lie here or bad things will happen */
return JANUS_PLUGIN_API_VERSION;
}
int janus_sip_get_version(void) {
return JANUS_SIP_VERSION;
}
const char *janus_sip_get_version_string(void) {
return JANUS_SIP_VERSION_STRING;
}
const char *janus_sip_get_description(void) {
return JANUS_SIP_DESCRIPTION;
}
const char *janus_sip_get_name(void) {
return JANUS_SIP_NAME;
}
const char *janus_sip_get_author(void) {
return JANUS_SIP_AUTHOR;
}
const char *janus_sip_get_package(void) {
return JANUS_SIP_PACKAGE;
}
static janus_sip_session *janus_sip_lookup_session(janus_plugin_session *handle) {
janus_sip_session *session = NULL;
if(g_hash_table_contains(sessions, handle)) {
session = (janus_sip_session *)handle->plugin_handle;
}
return session;
}
void janus_sip_create_session(janus_plugin_session *handle, int *error) {
if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
*error = -1;
return;
}
janus_sip_session *session = g_malloc0(sizeof(janus_sip_session));
session->handle = handle;
session->account.identity = NULL;
session->account.force_udp = FALSE;
session->account.force_tcp = FALSE;
session->account.sips = FALSE;
session->account.rfc2543_cancel = FALSE;
session->account.username = NULL;
session->account.display_name = NULL;
session->account.user_agent = NULL;
session->account.authuser = NULL;
session->account.secret = NULL;
session->account.secret_type = janus_sip_secret_type_unknown;
session->account.sip_port = 0;
session->account.proxy = NULL;
session->account.outbound_proxy = NULL;
session->account.registration_status = janus_sip_registration_status_unregistered;
session->status = janus_sip_call_status_idle;
session->stack = NULL;
session->transaction = NULL;
session->callee = NULL;
session->callid = NULL;
session->sdp = NULL;
session->hangup_reason_header = NULL;
session->hangup_reason_header_protocol = NULL;
session->hangup_reason_header_cause = NULL;
session->media.remote_audio_ip = NULL;
session->media.remote_video_ip = NULL;
session->media.earlymedia = FALSE;
session->media.update = FALSE;
session->media.autoaccept_reinvites = TRUE;
session->media.ready = FALSE;
session->media.require_srtp = FALSE;
session->media.has_srtp_local_audio = FALSE;
session->media.has_srtp_local_video = FALSE;
session->media.has_srtp_remote_audio = FALSE;
session->media.has_srtp_remote_video = FALSE;
session->media.srtp_profile = 0;
session->media.audio_srtp_local_profile = NULL;
session->media.audio_srtp_local_crypto = NULL;
session->media.video_srtp_local_profile = NULL;
session->media.video_srtp_local_crypto = NULL;
session->media.on_hold = FALSE;
session->media.has_audio = FALSE;
session->media.audio_rtp_fd = -1;
session->media.audio_rtcp_fd= -1;
session->media.local_audio_rtp_port = 0;
session->media.remote_audio_rtp_port = 0;
session->media.local_audio_rtcp_port = 0;
session->media.remote_audio_rtcp_port = 0;
session->media.audio_ssrc = 0;
session->media.audio_ssrc_peer = 0;
session->media.audio_pt = -1;
session->media.opusred_pt = -1;
session->media.audio_pt_name = NULL;
session->media.audio_send = TRUE;
session->media.audio_recv = TRUE;
session->media.hold_audio_dir = JANUS_SDP_SENDONLY;
session->media.pre_hold_audio_dir = JANUS_SDP_DEFAULT;
session->media.has_video = FALSE;
session->media.video_rtp_fd = -1;
session->media.video_rtcp_fd= -1;
session->media.local_video_rtp_port = 0;
session->media.remote_video_rtp_port = 0;
session->media.local_video_rtcp_port = 0;
session->media.remote_video_rtcp_port = 0;
session->media.video_ssrc = 0;
session->media.video_ssrc_peer = 0;
session->media.simulcast_ssrc = 0;
session->media.video_pt = -1;
session->media.video_pt_name = NULL;
session->media.video_recv = TRUE;
session->media.video_pli_supported = FALSE;
session->media.hold_video_dir = JANUS_SDP_SENDONLY;
session->media.pre_hold_video_dir = JANUS_SDP_DEFAULT;
session->media.video_orientation_extension_id = -1;
session->media.audio_level_extension_id = -1;
/* Initialize the RTP context */
janus_rtp_switching_context_reset(&session->media.acontext);
janus_rtp_switching_context_reset(&session->media.vcontext);
session->media.pipefd[0] = -1;
session->media.pipefd[1] = -1;
session->media.updated = FALSE;
session->media.audio_remote_policy.ssrc.type = ssrc_any_inbound;
session->media.audio_local_policy.ssrc.type = ssrc_any_inbound;
session->media.video_remote_policy.ssrc.type = ssrc_any_inbound;
session->media.video_local_policy.ssrc.type = ssrc_any_inbound;
janus_mutex_init(&session->rec_mutex);
g_atomic_int_set(&session->establishing, 0);
g_atomic_int_set(&session->established, 0);
g_atomic_int_set(&session->hangingup, 0);
g_atomic_int_set(&session->destroyed, 0);
janus_mutex_init(&session->mutex);
handle->plugin_handle = session;
janus_refcount_init(&session->ref, janus_sip_session_free);
janus_mutex_lock(&sessions_mutex);
g_hash_table_insert(sessions, handle, session);
janus_mutex_unlock(&sessions_mutex);
return;
}
void janus_sip_destroy_session(janus_plugin_session *handle, int *error) {
if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
*error = -1;
return;
}
janus_mutex_lock(&sessions_mutex);
janus_sip_session *session = janus_sip_lookup_session(handle);
if(!session) {
janus_mutex_unlock(&sessions_mutex);
JANUS_LOG(LOG_ERR, "No SIP session associated with this handle...\n");
*error = -2;
return;
}
JANUS_LOG(LOG_VERB, "Destroying SIP session (%s)...\n", session->account.username ? session->account.username : "unregistered user");
janus_sip_hangup_media_internal(handle);
/* If this is a master or helper session, update the related sessions */
if(session->master_id != 0) {
if(session->master == NULL) {
/* This is the master, remove it from the list */
g_hash_table_remove(masters, GUINT_TO_POINTER(session->master_id));
/* Remove the helper sessions */
janus_mutex_lock(&session->mutex);
GList *temp = NULL;
while(session->helpers != NULL) {
temp = session->helpers;
session->helpers = g_list_remove_link(session->helpers, temp);
janus_sip_session *helper = (janus_sip_session *)temp->data;
if(helper != NULL && helper->handle != NULL) {
/* Get rid of this helper */
janus_refcount_decrease(&session->ref);
janus_refcount_decrease(&helper->ref);
gateway->end_session(helper->handle);
}
g_list_free(temp);
}
janus_mutex_unlock(&session->mutex);
} else {
/* This is a helper session, remove it from the list and remove the references */
janus_sip_session *master = session->master;
janus_mutex_lock(&master->mutex);
gboolean found = (g_list_find(master->helpers, session) != NULL);
if(found) {
master->helpers = g_list_remove(master->helpers, session);
janus_refcount_decrease(&session->ref);
janus_refcount_decrease(&master->ref);
}
janus_mutex_unlock(&master->mutex);
}
}
/* If this session was involved in a transfer, get rid of the reference */
if(session->refer_id) {
g_hash_table_remove(transfers, GUINT_TO_POINTER(session->refer_id));
session->refer_id = 0;
}
/* Shutdown the NUA */
if(session->stack) {
janus_mutex_lock(&session->stack->smutex);
if(session->stack->s_nua)
nua_shutdown(session->stack->s_nua);
janus_mutex_unlock(&session->stack->smutex);
}
g_hash_table_remove(sessions, handle);
janus_mutex_unlock(&sessions_mutex);
return;
}
json_t *janus_sip_query_session(janus_plugin_session *handle) {
if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) {
return NULL;
}
janus_mutex_lock(&sessions_mutex);
janus_sip_session *session = janus_sip_lookup_session(handle);
if(!session) {
janus_mutex_unlock(&sessions_mutex);
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
return NULL;
}
janus_refcount_increase(&session->ref);
janus_mutex_unlock(&sessions_mutex);
/* Provide some generic info, e.g., if we're in a call and with whom */
json_t *info = json_object();
if(session->master != NULL) {
/* This is an helper session, provide the details for the master session */
json_object_set_new(info, "helper", json_true());
json_t *master = json_object();
json_object_set_new(master, "username", session->master->account.username ? json_string(session->master->account.username) : NULL);
json_object_set_new(master, "authuser", session->master->account.authuser ? json_string(session->master->account.authuser) : NULL);
json_object_set_new(master, "secret", session->master->account.secret ? json_string("(hidden)") : NULL);
json_object_set_new(master, "display_name", session->master->account.display_name ? json_string(session->master->account.display_name) : NULL);
json_object_set_new(master, "user_agent", session->master->account.user_agent ? json_string(session->master->account.user_agent) : NULL);
json_object_set_new(master, "identity", session->master->account.identity ? json_string(session->master->account.identity) : NULL);
json_object_set_new(master, "registration_status", json_string(janus_sip_registration_status_string(session->master->account.registration_status)));
json_object_set_new(info, "master", master);
}
json_object_set_new(info, "username", session->account.username ? json_string(session->account.username) : NULL);
json_object_set_new(info, "authuser", session->account.authuser ? json_string(session->account.authuser) : NULL);
json_object_set_new(info, "secret", session->account.secret ? json_string("(hidden)") : NULL);
json_object_set_new(info, "display_name", session->account.display_name ? json_string(session->account.display_name) : NULL);
json_object_set_new(info, "user_agent", session->account.user_agent ? json_string(session->account.user_agent) : NULL);
json_object_set_new(info, "identity", session->account.identity ? json_string(session->account.identity) : NULL);
json_object_set_new(info, "registration_status", json_string(janus_sip_registration_status_string(session->account.registration_status)));
json_object_set_new(info, "call_status", json_string(janus_sip_call_status_string(session->status)));
janus_mutex_lock(&session->mutex);
if(session->helpers != NULL)
json_object_set_new(info, "helpers", json_integer(g_list_length(session->helpers)));
if(session->callee) {
json_object_set_new(info, "callee", json_string(session->callee));
json_object_set_new(info, "srtp-required", json_string(session->media.require_srtp ? "yes" : "no"));
json_object_set_new(info, "sdes-local-audio", json_string(session->media.has_srtp_local_audio ? "yes" : "no"));
json_object_set_new(info, "sdes-local-video", json_string(session->media.has_srtp_local_video ? "yes" : "no"));
json_object_set_new(info, "sdes-remote-audio", json_string(session->media.has_srtp_remote_audio ? "yes" : "no"));
json_object_set_new(info, "sdes-remote-video", json_string(session->media.has_srtp_remote_video ? "yes" : "no"));
}
janus_mutex_unlock(&session->mutex);
if(session->arc || session->vrc || session->arc_peer || session->vrc_peer) {
json_t *recording = json_object();
if(session->arc && session->arc->filename)
json_object_set_new(recording, "audio", json_string(session->arc->filename));
if(session->vrc && session->vrc->filename)
json_object_set_new(recording, "video", json_string(session->vrc->filename));
if(session->arc_peer && session->arc_peer->filename)
json_object_set_new(recording, "audio-peer", json_string(session->arc_peer->filename));
if(session->vrc_peer && session->vrc_peer->filename)
json_object_set_new(recording, "video-peer", json_string(session->vrc_peer->filename));
json_object_set_new(info, "recording", recording);
}
json_object_set_new(info, "establishing", json_integer(g_atomic_int_get(&session->establishing)));
json_object_set_new(info, "established", json_integer(g_atomic_int_get(&session->established)));
json_object_set_new(info, "hangingup", json_integer(g_atomic_int_get(&session->hangingup)));
json_object_set_new(info, "destroyed", json_integer(g_atomic_int_get(&session->destroyed)));
janus_refcount_decrease(&session->ref);
return info;
}
struct janus_plugin_result *janus_sip_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep) {
if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
return janus_plugin_result_new(JANUS_PLUGIN_ERROR, g_atomic_int_get(&stopping) ? "Shutting down" : "Plugin not initialized", NULL);
janus_mutex_lock(&sessions_mutex);
janus_sip_session *session = janus_sip_lookup_session(handle);
if(!session) {
janus_mutex_unlock(&sessions_mutex);
return janus_plugin_result_new(JANUS_PLUGIN_ERROR, "No session associated with this handle", NULL);
}
/* Increase the reference counter for this session: we'll decrease it after we handle the message */
janus_refcount_increase(&session->ref);
janus_mutex_unlock(&sessions_mutex);
janus_sip_message *msg = g_malloc(sizeof(janus_sip_message));
msg->handle = handle;
msg->transaction = transaction;
msg->message = message;
msg->jsep = jsep;
g_async_queue_push(messages, msg);
/* All the requests to this plugin are handled asynchronously */
return janus_plugin_result_new(JANUS_PLUGIN_OK_WAIT, NULL, NULL);
}
void janus_sip_setup_media(janus_plugin_session *handle) {
JANUS_LOG(LOG_INFO, "[%s-%p] WebRTC media is now available\n", JANUS_SIP_PACKAGE, handle);
if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
return;
janus_mutex_lock(&sessions_mutex);
janus_sip_session *session = janus_sip_lookup_session(handle);
if(!session) {
janus_mutex_unlock(&sessions_mutex);
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
return;
}
if(g_atomic_int_get(&session->destroyed)) {
janus_mutex_unlock(&sessions_mutex);
return;
}
g_atomic_int_set(&session->established, 1);
g_atomic_int_set(&session->establishing, 0);
g_atomic_int_set(&session->hangingup, 0);
janus_mutex_unlock(&sessions_mutex);
/* Only relay RTP/RTCP when we get this event */
}
void janus_sip_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp *packet) {
if(handle == NULL || g_atomic_int_get(&handle->stopped) || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
return;
if(gateway) {
/* Honour the audio/video active flags */
janus_sip_session *session = (janus_sip_session *)handle->plugin_handle;
if(!session || g_atomic_int_get(&session->destroyed)) {
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
return;
}
if(!janus_sip_call_is_established(session))
return;
gboolean video = packet->video;
char *buf = packet->buffer;
uint16_t len = packet->length;
/* Forward to our SIP peer */
if(video) {
if(!session->media.video_send) {
/* Dropping video packet, peer doesn't want to receive it */
return;
}
if(session->media.on_hold && session->media.hold_video_dir != JANUS_SDP_SENDONLY) {
/* Dropping video packet, the call is on hold and we're not sending anything */
return;
}
if(session->media.simulcast_ssrc) {
/* The user is simulcasting: drop everything except the base layer */
janus_rtp_header *header = (janus_rtp_header *)buf;
uint32_t ssrc = ntohl(header->ssrc);
if(ssrc != session->media.simulcast_ssrc) {
JANUS_LOG(LOG_DBG, "Dropping packet (not base simulcast substream)\n");
return;
}
}
if(session->media.video_ssrc == 0) {
janus_rtp_header *header = (janus_rtp_header *)buf;
session->media.video_ssrc = ntohl(header->ssrc);
JANUS_LOG(LOG_VERB, "Got SIP video SSRC: %"SCNu32"\n", session->media.video_ssrc);
}
if(session->media.has_video && session->media.video_rtp_fd != -1) {
/* Save the frame if we're recording */
janus_recorder_save_frame(session->vrc, buf, len);
/* Is SRTP involved? */
if(session->media.has_srtp_local_video) {
char sbuf[2048];
memcpy(&sbuf, buf, len);
int protected = len;
int res = srtp_protect(session->media.video_srtp_out, &sbuf, &protected);
if(res != srtp_err_status_ok) {
janus_rtp_header *header = (janus_rtp_header *)&sbuf;
guint32 timestamp = ntohl(header->timestamp);
guint16 seq = ntohs(header->seq_number);
JANUS_LOG(LOG_ERR, "[SIP-%s] Video SRTP protect error... %s (len=%d-->%d, ts=%"SCNu32", seq=%"SCNu16")...\n",
session->account.username, janus_srtp_error_str(res), len, protected, timestamp, seq);
} else {
/* Forward the frame to the peer */
if(send(session->media.video_rtp_fd, sbuf, protected, 0) < 0) {
janus_rtp_header *header = (janus_rtp_header *)&sbuf;
guint32 timestamp = ntohl(header->timestamp);
guint16 seq = ntohs(header->seq_number);
JANUS_LOG(LOG_HUGE, "[SIP-%s] Error sending SRTP video packet... %s (len=%d, ts=%"SCNu32", seq=%"SCNu16")...\n",
session->account.username, g_strerror(errno), protected, timestamp, seq);
}
}
} else {
/* Forward the frame to the peer */
if(send(session->media.video_rtp_fd, buf, len, 0) < 0) {
janus_rtp_header *header = (janus_rtp_header *)&buf;
guint32 timestamp = ntohl(header->timestamp);
guint16 seq = ntohs(header->seq_number);
JANUS_LOG(LOG_HUGE, "[SIP-%s] Error sending RTP video packet... %s (len=%d, ts=%"SCNu32", seq=%"SCNu16")...\n",
session->account.username, g_strerror(errno), len, timestamp, seq);
}
}
}
} else {
if(!session->media.audio_send) {
/* Dropping audio packet, peer doesn't want to receive it */
return;
}
if(session->media.on_hold && session->media.hold_audio_dir != JANUS_SDP_SENDONLY) {
/* Dropping audio packet, the call is on hold and we're not sending anything */
return;
}
if(session->media.audio_ssrc == 0) {
janus_rtp_header *header = (janus_rtp_header *)buf;
session->media.audio_ssrc = ntohl(header->ssrc);
JANUS_LOG(LOG_VERB, "Got SIP audio SSRC: %"SCNu32"\n", session->media.audio_ssrc);
}
if(session->media.has_audio && session->media.audio_rtp_fd != -1) {
/* Save the frame if we're recording */
janus_recorder_save_frame(session->arc, buf, len);
/* Is SRTP involved? */
if(session->media.has_srtp_local_audio) {
char sbuf[2048];
memcpy(&sbuf, buf, len);
int protected = len;
int res = srtp_protect(session->media.audio_srtp_out, &sbuf, &protected);
if(res != srtp_err_status_ok) {
janus_rtp_header *header = (janus_rtp_header *)&sbuf;
guint32 timestamp = ntohl(header->timestamp);
guint16 seq = ntohs(header->seq_number);
JANUS_LOG(LOG_ERR, "[SIP-%s] Audio SRTP protect error... %s (len=%d-->%d, ts=%"SCNu32", seq=%"SCNu16")...\n",
session->account.username, janus_srtp_error_str(res), len, protected, timestamp, seq);
} else {
/* Forward the frame to the peer */
if(send(session->media.audio_rtp_fd, sbuf, protected, 0) < 0) {
janus_rtp_header *header = (janus_rtp_header *)&sbuf;
guint32 timestamp = ntohl(header->timestamp);
guint16 seq = ntohs(header->seq_number);
JANUS_LOG(LOG_HUGE, "[SIP-%s] Error sending SRTP audio packet... %s (len=%d, ts=%"SCNu32", seq=%"SCNu16")...\n",
session->account.username, g_strerror(errno), protected, timestamp, seq);
}
}
} else {
/* Forward the frame to the peer */
if(send(session->media.audio_rtp_fd, buf, len, 0) < 0) {
janus_rtp_header *header = (janus_rtp_header *)&buf;
guint32 timestamp = ntohl(header->timestamp);
guint16 seq = ntohs(header->seq_number);
JANUS_LOG(LOG_HUGE, "[SIP-%s] Error sending RTP audio packet... %s (len=%d, ts=%"SCNu32", seq=%"SCNu16")...\n",
session->account.username, g_strerror(errno), len, timestamp, seq);
}
}
}
}
}
}
void janus_sip_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet) {
if(handle == NULL || g_atomic_int_get(&handle->stopped) || g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
return;
if(gateway) {
janus_sip_session *session = (janus_sip_session *)handle->plugin_handle;
if(!session || g_atomic_int_get(&session->destroyed)) {
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
return;
}
if(!janus_sip_call_is_established(session))
return;
gboolean video = packet->video;
char *buf = packet->buffer;
uint16_t len = packet->length;
/* Forward to our SIP peer */
if(video) {
if(session->media.has_video && session->media.video_rtcp_fd != -1) {
/* Fix SSRCs as the Janus core does */
JANUS_LOG(LOG_HUGE, "[SIP] Fixing SSRCs (local %u, peer %u)\n",
session->media.video_ssrc, session->media.video_ssrc_peer);
janus_rtcp_fix_ssrc(NULL, (char *)buf, len, 1, session->media.video_ssrc, session->media.video_ssrc_peer);
/* Is SRTP involved? */
if(session->media.has_srtp_local_video) {
char sbuf[2048];
memcpy(&sbuf, buf, len);
int protected = len;
int res = srtp_protect_rtcp(session->media.video_srtp_out, &sbuf, &protected);
if(res != srtp_err_status_ok) {
JANUS_LOG(LOG_ERR, "[SIP-%s] Video SRTCP protect error... %s (len=%d-->%d)...\n",
session->account.username, janus_srtp_error_str(res), len, protected);
} else {
/* Forward the message to the peer */
if(send(session->media.video_rtcp_fd, sbuf, protected, 0) < 0) {
JANUS_LOG(LOG_HUGE, "[SIP-%s] Error sending SRTCP video packet... %s (len=%d)...\n",
session->account.username, g_strerror(errno), protected);
}
}
} else {
/* Forward the message to the peer */
if(send(session->media.video_rtcp_fd, buf, len, 0) < 0) {
JANUS_LOG(LOG_HUGE, "[SIP-%s] Error sending RTCP video packet... %s (len=%d)...\n",
session->account.username, g_strerror(errno), len);
}
}
}
} else {
if(session->media.has_audio && session->media.audio_rtcp_fd != -1) {
/* Fix SSRCs as the Janus core does */
JANUS_LOG(LOG_HUGE, "[SIP] Fixing SSRCs (local %u, peer %u)\n",
session->media.audio_ssrc, session->media.audio_ssrc_peer);
janus_rtcp_fix_ssrc(NULL, (char *)buf, len, 1, session->media.audio_ssrc, session->media.audio_ssrc_peer);
/* Is SRTP involved? */
if(session->media.has_srtp_local_audio) {
char sbuf[2048];
memcpy(&sbuf, buf, len);
int protected = len;
int res = srtp_protect_rtcp(session->media.audio_srtp_out, &sbuf, &protected);
if(res != srtp_err_status_ok) {
JANUS_LOG(LOG_ERR, "[SIP-%s] Audio SRTCP protect error... %s (len=%d-->%d)...\n",
session->account.username, janus_srtp_error_str(res), len, protected);
} else {
/* Forward the message to the peer */
if(send(session->media.audio_rtcp_fd, sbuf, protected, 0) < 0) {
JANUS_LOG(LOG_HUGE, "[SIP-%s] Error sending SRTCP audio packet... %s (len=%d)...\n",
session->account.username, g_strerror(errno), protected);
}
}
} else {
/* Forward the message to the peer */
if(send(session->media.audio_rtcp_fd, buf, len, 0) < 0) {
JANUS_LOG(LOG_HUGE, "[SIP-%s] Error sending RTCP audio packet... %s (len=%d)...\n",
session->account.username, g_strerror(errno), len);
}
}
}
}
}
}
static void janus_sip_recorder_close(janus_sip_session *session,
gboolean stop_audio, gboolean stop_audio_peer, gboolean stop_video, gboolean stop_video_peer) {
if(session->arc && stop_audio) {
janus_recorder *rc = session->arc;
session->arc = NULL;
janus_recorder_close(rc);
JANUS_LOG(LOG_INFO, "Closed user's audio recording %s\n", rc->filename ? rc->filename : "??");
janus_recorder_destroy(rc);
}
if(session->arc_peer && stop_audio_peer) {
janus_recorder *rc = session->arc_peer;
session->arc_peer = NULL;
janus_recorder_close(rc);
JANUS_LOG(LOG_INFO, "Closed peer's audio recording %s\n", rc->filename ? rc->filename : "??");
janus_recorder_destroy(rc);
}
if(session->vrc && stop_video) {
janus_recorder *rc = session->vrc;
session->vrc = NULL;
janus_recorder_close(rc);
JANUS_LOG(LOG_INFO, "Closed user's video recording %s\n", rc->filename ? rc->filename : "??");
janus_recorder_destroy(rc);
}
if(session->vrc_peer && stop_video_peer) {
janus_recorder *rc = session->vrc_peer;
session->vrc_peer = NULL;
janus_recorder_close(rc);
JANUS_LOG(LOG_INFO, "Closed peer's video recording %s\n", rc->filename ? rc->filename : "??");
janus_recorder_destroy(rc);
}
}
void janus_sip_hangup_media(janus_plugin_session *handle) {
JANUS_LOG(LOG_INFO, "[%s-%p] No WebRTC media anymore\n", JANUS_SIP_PACKAGE, handle);
janus_mutex_lock(&sessions_mutex);
janus_sip_hangup_media_internal(handle);
janus_mutex_unlock(&sessions_mutex);
}
static void janus_sip_hangup_media_internal(janus_plugin_session *handle) {
if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized))
return;
janus_sip_session *session = janus_sip_lookup_session(handle);
if(!session) {
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
return;
}
if(g_atomic_int_get(&session->destroyed))
return;
if(!g_atomic_int_compare_and_exchange(&session->hangingup, 0, 1))
return;
session->media.simulcast_ssrc = 0;
/* Do cleanup if media thread has not been created */
if(!session->media.ready && !session->relayer_thread) {
janus_mutex_lock(&session->mutex);
janus_sip_media_cleanup(session);
janus_mutex_unlock(&session->mutex);
}
/* Get rid of the recorders, if available */
janus_mutex_lock(&session->rec_mutex);
janus_sip_recorder_close(session, TRUE, TRUE, TRUE, TRUE);
janus_mutex_unlock(&session->rec_mutex);
if(!(session->status == janus_sip_call_status_inviting ||
session->status == janus_sip_call_status_invited ||
janus_sip_call_is_established(session))) {
g_atomic_int_set(&session->establishing, 0);
g_atomic_int_set(&session->established, 0);
g_atomic_int_set(&session->hangingup, 0);
return;
}
/* Involve SIP if needed */
janus_mutex_lock(&session->mutex);
if(session->stack->s_nh_i != NULL && session->callee != NULL) {
g_free(session->callee);
session->callee = NULL;
janus_mutex_unlock(&session->mutex);
/* Send a BYE */
session->media.earlymedia = FALSE;
session->media.update = FALSE;
session->media.autoaccept_reinvites = TRUE;
session->media.ready = FALSE;
session->media.on_hold = FALSE;
/* Send a BYE or respond with 480 */
if(janus_sip_call_is_established(session) || session->status == janus_sip_call_status_inviting)
nua_bye(session->stack->s_nh_i, TAG_END());
else
nua_respond(session->stack->s_nh_i, 480, sip_status_phrase(480), TAG_END());
janus_sip_call_update_status(session, janus_sip_call_status_closing);
/* Notify the operation */
json_t *event = json_object();
json_object_set_new(event, "sip", json_string("event"));
json_t *result = json_object();
json_object_set_new(result, "event", json_string("hangingup"));
json_object_set_new(event, "result", result);
json_object_set_new(event, "call_id", json_string(session->callid));
int ret = gateway->push_event(session->handle, &janus_sip_plugin, NULL, event, NULL);
JANUS_LOG(LOG_VERB, " >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(event);
} else {
janus_mutex_unlock(&session->mutex);
}
g_atomic_int_set(&session->establishing, 0);
g_atomic_int_set(&session->established, 0);
g_atomic_int_set(&session->hangingup, 0);
}
/* Thread to handle incoming messages */
static void *janus_sip_handler(void *data) {
JANUS_LOG(LOG_VERB, "Joining SIP handler thread\n");
janus_sip_message *msg = NULL;
int error_code = 0;
char error_cause[512];
json_t *root = NULL;
while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) {
msg = g_async_queue_pop(messages);
if(msg == &exit_message)
break;
if(msg->handle == NULL) {
janus_sip_message_free(msg);
continue;
}
janus_mutex_lock(&sessions_mutex);
janus_sip_session *session = janus_sip_lookup_session(msg->handle);
if(!session) {
janus_mutex_unlock(&sessions_mutex);
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
janus_sip_message_free(msg);
continue;
}
if(g_atomic_int_get(&session->destroyed)) {
janus_mutex_unlock(&sessions_mutex);
janus_sip_message_free(msg);
continue;
}
janus_mutex_unlock(&sessions_mutex);
/* Handle request */
error_code = 0;
root = msg->message;
if(msg->message == NULL) {
JANUS_LOG(LOG_ERR, "No message??\n");
error_code = JANUS_SIP_ERROR_NO_MESSAGE;
g_snprintf(error_cause, 512, "%s", "No message??");
goto error;
}
if(!json_is_object(root)) {
JANUS_LOG(LOG_ERR, "JSON error: not an object\n");
error_code = JANUS_SIP_ERROR_INVALID_JSON;
g_snprintf(error_cause, 512, "JSON error: not an object");
goto error;
}
JANUS_VALIDATE_JSON_OBJECT(root, request_parameters,
error_code, error_cause, TRUE,
JANUS_SIP_ERROR_MISSING_ELEMENT, JANUS_SIP_ERROR_INVALID_ELEMENT);
if(error_code != 0)
goto error;
json_t *request = json_object_get(root, "request");
const char *request_text = json_string_value(request);
json_t *result = NULL;
if(!strcasecmp(request_text, "register")) {
/* Send a REGISTER */
JANUS_VALIDATE_JSON_OBJECT(root, register_parameters,
error_code, error_cause, TRUE,
JANUS_SIP_ERROR_MISSING_ELEMENT, JANUS_SIP_ERROR_INVALID_ELEMENT);
if(error_code != 0)
goto error;
gboolean refresh = json_is_true(json_object_get(root, "refresh"));
if(session->account.registration_status > janus_sip_registration_status_unregistered && !refresh) {
JANUS_LOG(LOG_ERR, "Already registered (%s)\n", session->account.username);
error_code = JANUS_SIP_ERROR_ALREADY_REGISTERED;
g_snprintf(error_cause, 512, "Already registered (%s)", session->account.username);
goto error;
}
/* Parse the request */
gboolean guest = FALSE, helper = FALSE;
json_t *type = json_object_get(root, "type");
if(type != NULL) {
const char *type_text = json_string_value(type);
if(!strcmp(type_text, "guest")) {
JANUS_LOG(LOG_INFO, "Registering as a guest\n");
guest = TRUE;
} else if(!strcmp(type_text, "helper")) {
JANUS_LOG(LOG_INFO, "Registering as a helper\n");
helper = TRUE;
} else {
JANUS_LOG(LOG_WARN, "Unknown type '%s', ignoring...\n", type_text);
}
}
if(helper) {
/* This is actually an helper session, for an already registered one */
json_t *master = json_object_get(root, "master_id");
if(master == NULL) {
JANUS_LOG(LOG_ERR, "Missing mandatory element for helper (master_id)\n");
error_code = JANUS_SIP_ERROR_MISSING_ELEMENT;
g_snprintf(error_cause, 512, "Missing mandatory element for helper (master_id)");
goto error;
}
guint32 master_id = json_integer_value(master);
janus_mutex_lock(&sessions_mutex);
if(session->master != NULL) {
janus_mutex_unlock(&sessions_mutex);
JANUS_LOG(LOG_ERR, "Session already a helper (%"SCNu32")\n", session->master_id);
error_code = JANUS_SIP_ERROR_HELPER_ERROR;
g_snprintf(error_cause, 512, "Session already a helper (%"SCNu32")", master_id);
goto error;
}
janus_sip_session *ms = g_hash_table_lookup(masters, GUINT_TO_POINTER(master_id));
if(ms == NULL) {
janus_mutex_unlock(&sessions_mutex);
JANUS_LOG(LOG_ERR, "No such master session (%"SCNu32")\n", master_id);
error_code = JANUS_SIP_ERROR_HELPER_ERROR;
g_snprintf(error_cause, 512, "No such master session (%"SCNu32")", master_id);
goto error;
}
/* Add this session as an helper for the master */
janus_refcount_increase(&session->ref);
janus_refcount_increase(&ms->ref);
session->helper = TRUE;
session->master = ms;
session->master_id = master_id;
janus_mutex_lock(&ms->mutex);
ms->helpers = g_list_append(ms->helpers, session);
janus_mutex_unlock(&ms->mutex);
session->account.registration_status = janus_sip_registration_status_disabled;
g_free(session->account.username);
session->account.username = ms->account.username ? g_strdup(ms->account.username) : NULL;
if(session->stack == NULL) {
session->stack = g_malloc0(sizeof(ssip_t));
su_home_init(session->stack->s_home);
if(session->master->stack->contact_header != NULL)
session->stack->contact_header = g_strdup(session->master->stack->contact_header);
}
/* Check if custom headers need to be intercepted */
json_t *header_prefixes_json = json_object_get(root, "incoming_header_prefixes");
if(header_prefixes_json) {
size_t index = 0;
json_t *value = NULL;
json_array_foreach(header_prefixes_json, index, value) {
const char *header_prefix = json_string_value(value);
if(header_prefix)
session->incoming_header_prefixes = g_list_append(session->incoming_header_prefixes, g_strdup(header_prefix));
}
} else {
/* No custom headers, inherit the parent's if any */
if(ms->incoming_header_prefixes != NULL) {
GList *temp = ms->incoming_header_prefixes;
while(temp != NULL) {
char *header_prefix = (char *)temp->data;
if(header_prefix != NULL)
session->incoming_header_prefixes = g_list_append(session->incoming_header_prefixes, g_strdup(header_prefix));
temp = temp->next;
}
}
}
session->stack->session = session;
janus_mutex_unlock(&sessions_mutex);
/* Send an event back */
result = json_object();
json_object_set_new(result, "event", json_string("registered"));
json_object_set_new(result, "username", json_string(ms->account.username));
json_object_set_new(result, "register_sent", json_false());
json_object_set_new(result, "helper", json_true());
json_object_set_new(result, "master_id", json_integer(session->master_id));
/* Also notify event handlers */
if(notify_events && gateway->events_is_enabled()) {
json_t *info = json_object();
json_object_set_new(info, "event", json_string("registered"));
json_object_set_new(info, "identity", json_string(ms->account.identity));
json_object_set_new(info, "type", json_string("guest"));
json_object_set_new(info, "helper", json_true());
json_object_set_new(info, "master_id", json_integer(session->master_id));
gateway->notify_event(&janus_sip_plugin, session->handle, info);
}
goto done;
}
if(session->master != NULL) {
JANUS_LOG(LOG_ERR, "Can't register on a helper session\n");
error_code = JANUS_SIP_ERROR_HELPER_ERROR;
g_snprintf(error_cause, 512, "Can't register on a helper session");
goto error;
}
gboolean send_register = TRUE;
json_t *do_register = json_object_get(root, "send_register");
if(do_register != NULL) {
send_register = json_is_true(do_register);
if(guest && send_register) {
JANUS_LOG(LOG_ERR, "Conflicting elements: send_register cannot be true if guest is true\n");
error_code = JANUS_SIP_ERROR_INVALID_ELEMENT;
g_snprintf(error_cause, 512, "Conflicting elements: send_register cannot be true if guest is true");
goto error;
}
}
gboolean sips = FALSE;
json_t *do_sips = json_object_get(root, "sips");
if(do_sips != NULL) {
sips = json_is_true(do_sips);
}
gboolean force_udp = FALSE;
json_t *do_udp = json_object_get(root, "force_udp");
if(do_udp != NULL) {
force_udp = json_is_true(do_udp);
}
gboolean force_tcp = FALSE;
json_t *do_tcp = json_object_get(root, "force_tcp");
if(do_tcp != NULL) {
force_tcp = json_is_true(do_tcp);
}
if(force_udp && force_tcp) {
JANUS_LOG(LOG_ERR, "Conflicting elements: force_udp and force_tcp cannot both be true\n");
error_code = JANUS_SIP_ERROR_INVALID_ELEMENT;
g_snprintf(error_cause, 512, "Conflicting elements: force_udp and force_tcp cannot both be true");
goto error;
}
if(!force_udp && !force_tcp)
force_udp = TRUE;
gboolean rfc2543_cancel = FALSE;
json_t *do_rfc2543_cancel = json_object_get(root, "rfc2543_cancel");
if(do_rfc2543_cancel != NULL) {
rfc2543_cancel = json_is_true(do_rfc2543_cancel);
}
/* Parse addresses */
json_t *proxy = json_object_get(root, "proxy");
const char *proxy_text = NULL;
if(proxy && !json_is_null(proxy)) {
/* Has to be validated separately because it could be null */
JANUS_VALIDATE_JSON_OBJECT(root, proxy_parameters,
error_code, error_cause, TRUE,
JANUS_SIP_ERROR_MISSING_ELEMENT, JANUS_SIP_ERROR_INVALID_ELEMENT);
if(error_code != 0)
goto error;
proxy_text = json_string_value(proxy);
janus_sip_uri_t proxy_uri;
if(janus_sip_parse_proxy_uri(&proxy_uri, proxy_text) < 0) {
JANUS_LOG(LOG_ERR, "Invalid proxy address %s\n", proxy_text);
error_code = JANUS_SIP_ERROR_INVALID_ADDRESS;
g_snprintf(error_cause, 512, "Invalid proxy address %s\n", proxy_text);
goto error;
}
}
json_t *outbound_proxy = json_object_get(root, "outbound_proxy");
const char *obproxy_text = NULL;
if(outbound_proxy && !json_is_null(outbound_proxy)) {
/* Has to be validated separately because it could be null */
JANUS_VALIDATE_JSON_OBJECT(root, proxy_parameters,
error_code, error_cause, TRUE,
JANUS_SIP_ERROR_MISSING_ELEMENT, JANUS_SIP_ERROR_INVALID_ELEMENT);
if(error_code != 0)
goto error;
obproxy_text = json_string_value(outbound_proxy);
janus_sip_uri_t outbound_proxy_uri;
if(janus_sip_parse_proxy_uri(&outbound_proxy_uri, obproxy_text) < 0) {
JANUS_LOG(LOG_ERR, "Invalid outbound_proxy address %s\n", obproxy_text);
error_code = JANUS_SIP_ERROR_INVALID_ADDRESS;
g_snprintf(error_cause, 512, "Invalid outbound_proxy address %s\n", obproxy_text);
goto error;
}
}
/* Parse register TTL */
int ttl = register_ttl;
json_t *reg_ttl = json_object_get(root, "register_ttl");
if(reg_ttl && json_is_integer(reg_ttl))
ttl = json_integer_value(reg_ttl);
if(ttl <= 0)
ttl = JANUS_DEFAULT_REGISTER_TTL;
/* Parse display name */
const char *display_name_text = NULL;
json_t *display_name = json_object_get(root, "display_name");
if(display_name && json_is_string(display_name))
display_name_text = json_string_value(display_name);
/* Parse user agent */
const char *user_agent_text = NULL;
json_t *user_agent = json_object_get(root, "user_agent");
if(user_agent && json_is_string(user_agent))
user_agent_text = json_string_value(user_agent);
/* Now the user part (always needed, even for the guest case) */
json_t *username = json_object_get(root, "username");
if(!username) {
/* The username is mandatory even when registering as guests */
JANUS_LOG(LOG_ERR, "Missing element (username)\n");
error_code = JANUS_SIP_ERROR_MISSING_ELEMENT;
g_snprintf(error_cause, 512, "Missing element (username)");
goto error;
}
const char *username_text = NULL;
const char *secret_text = NULL;
const char *authuser_text = NULL;
janus_sip_secret_type secret_type = janus_sip_secret_type_plaintext;
janus_sip_uri_t username_uri;
char user_id[256];
/* Parse address */
username_text = json_string_value(username);
if(janus_sip_parse_uri(&username_uri, username_text) < 0) {
JANUS_LOG(LOG_ERR, "Invalid user address %s\n", username_text);
error_code = JANUS_SIP_ERROR_INVALID_ADDRESS;
g_snprintf(error_cause, 512, "Invalid user address %s\n", username_text);
goto error;
}
g_strlcpy(user_id, username_uri.url->url_user, sizeof(user_id));
if(guest) {
/* Not needed, we can stop here: just say we're registered */
JANUS_LOG(LOG_INFO, "Guest will have username %s\n", user_id);
send_register = FALSE;
} else {
json_t *secret = json_object_get(root, "secret");
json_t *ha1_secret = json_object_get(root, "ha1_secret");
json_t *authuser = json_object_get(root, "authuser");
if(!secret && !ha1_secret) {
JANUS_LOG(LOG_ERR, "Missing element (secret or ha1_secret)\n");
error_code = JANUS_SIP_ERROR_MISSING_ELEMENT;
g_snprintf(error_cause, 512, "Missing element (secret or ha1_secret)");
goto error;
}
if(secret && ha1_secret) {
JANUS_LOG(LOG_ERR, "Conflicting elements specified (secret and ha1_secret)\n");
error_code = JANUS_SIP_ERROR_INVALID_ELEMENT;
g_snprintf(error_cause, 512, "Conflicting elements specified (secret and ha1_secret)");
goto error;
}
if(secret) {
secret_text = json_string_value(secret);
secret_type = janus_sip_secret_type_plaintext;
} else {
secret_text = json_string_value(ha1_secret);
secret_type = janus_sip_secret_type_hashed;
}
if(authuser) {
authuser_text = json_string_value(authuser);
}
/* Got the values, try registering now */
JANUS_LOG(LOG_VERB, "Registering user %s (auth=%s, secret %s) @ %s through %s (outbound proxy: %s)\n",
username_text, secret_text, username_uri.url->url_host,
authuser_text != NULL ? authuser_text : username_text,
proxy_text != NULL ? proxy_text : "(null)",
obproxy_text != NULL ? obproxy_text : "none");
}
/* Create a master ID if we don't have one yet */
if(session->master_id == 0) {
janus_mutex_lock(&sessions_mutex);
while(session->master_id == 0) {
session->master_id = janus_random_uint32();
if(g_hash_table_lookup(masters, GUINT_TO_POINTER(session->master_id)) != NULL)
session->master_id = 0;
}
g_hash_table_insert(masters, GUINT_TO_POINTER(session->master_id), session);
janus_mutex_unlock(&sessions_mutex);
}
json_t *header_prefixes_json = json_object_get(root, "incoming_header_prefixes");
if(header_prefixes_json) {
size_t index = 0;
json_t *value = NULL;
json_array_foreach(header_prefixes_json, index, value) {
const char *header_prefix = json_string_value(value);
if(header_prefix)
session->incoming_header_prefixes = g_list_append(session->incoming_header_prefixes, g_strdup(header_prefix));
}
}
/* If this is a refresh, get rid of the old values */
if(refresh) {
/* Cleanup old values */
if(session->account.identity != NULL) {
janus_mutex_lock(&sessions_mutex);
g_hash_table_remove(identities, session->account.identity);
janus_mutex_unlock(&sessions_mutex);
g_free(session->account.identity);
}
session->account.identity = NULL;
session->account.force_udp = FALSE;
session->account.force_tcp = FALSE;
session->account.sips = FALSE;
session->account.rfc2543_cancel = FALSE;
if(session->account.username != NULL)
g_free(session->account.username);
session->account.username = NULL;
if(session->account.display_name != NULL)
g_free(session->account.display_name);
session->account.display_name = NULL;
if(session->account.authuser != NULL)
g_free(session->account.authuser);
session->account.authuser = NULL;
if(session->account.secret != NULL)
g_free(session->account.secret);
session->account.secret = NULL;
session->account.secret_type = janus_sip_secret_type_unknown;
if(session->account.proxy != NULL)
g_free(session->account.proxy);
session->account.proxy = NULL;
if(session->account.outbound_proxy != NULL)
g_free(session->account.outbound_proxy);
session->account.outbound_proxy = NULL;
if(session->account.user_agent != NULL)
g_free(session->account.user_agent);
session->account.user_agent = NULL;
session->account.registration_status = janus_sip_registration_status_unregistered;
}
session->account.identity = g_strdup(username_text);
janus_mutex_lock(&sessions_mutex);
g_hash_table_insert(identities, session->account.identity, session);
janus_mutex_unlock(&sessions_mutex);
session->account.force_udp = force_udp;
session->account.force_tcp = force_tcp;
session->account.sips = sips;
session->account.rfc2543_cancel = rfc2543_cancel;
session->account.username = g_strdup(user_id);
session->account.authuser = g_strdup(authuser_text ? authuser_text : user_id);
session->account.secret = secret_text ? g_strdup(secret_text) : NULL;
session->account.secret_type = secret_type;
if(display_name_text) {
session->account.display_name = g_strdup(display_name_text);
}
if(user_agent_text) {
session->account.user_agent = g_strdup(user_agent_text);
}
if(proxy_text) {
session->account.proxy = g_strdup(proxy_text);
}
if(obproxy_text) {
session->account.outbound_proxy = g_strdup(obproxy_text);
}
session->account.registration_status = janus_sip_registration_status_registering;
if(!refresh && session->stack == NULL) {
/* Start the thread first */
GError *error = NULL;
char tname[16];
g_snprintf(tname, sizeof(tname), "sip %s", session->account.username);
janus_refcount_increase(&session->ref);
g_thread_try_new(tname, janus_sip_sofia_thread, session, &error);
if(error != NULL) {
janus_refcount_decrease(&session->ref);
JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the SIP Sofia thread...\n",
error->code, error->message ? error->message : "??");
error_code = JANUS_SIP_ERROR_UNKNOWN_ERROR;
g_snprintf(error_cause, 512, "Got error %d (%s) trying to launch the SIP Sofia thread",
error->code, error->message ? error->message : "??");
g_error_free(error);
goto error;
}
long int timeout = 0;
while(session->stack == NULL || session->stack->s_nua == NULL) {
g_usleep(100000);
timeout += 100000;
if(timeout >= 2000000) {
break;
}
}
if(timeout >= 2000000) {
JANUS_LOG(LOG_ERR, "Two seconds passed and still no NUA, problems with the thread?\n");
error_code = JANUS_SIP_ERROR_UNKNOWN_ERROR;
g_snprintf(error_cause, 512, "Two seconds passed and still no NUA, problems with the thread?");
goto error;
}
}
if(session == NULL || session->stack == NULL) {
JANUS_LOG(LOG_ERR, "Missing session or Sofia stack\n");
error_code = JANUS_SIP_ERROR_UNKNOWN_ERROR;
g_snprintf(error_cause, 512, "Missing session or Sofia stack");
goto error;
}
if(session->stack->s_nh_r != NULL) {
nua_handle_destroy(session->stack->s_nh_r);
session->stack->s_nh_r = NULL;
}
if(send_register) {
/* Check if the REGISTER needs to be enriched with custom headers */
char custom_headers[2048];
janus_sip_parse_custom_headers(root, (char *)&custom_headers, sizeof(custom_headers));
/* Do the same in case there are custom Contact URI params */
char custom_params[2048];
janus_sip_parse_custom_contact_params(root, (char *)&custom_params, sizeof(custom_params));
/* Create a new NUA handle */
janus_mutex_lock(&session->stack->smutex);
if(session->stack->s_nua == NULL) {
janus_mutex_unlock(&session->stack->smutex);
JANUS_LOG(LOG_ERR, "NUA destroyed while registering?\n");
error_code = JANUS_SIP_ERROR_LIBSOFIA_ERROR;
g_snprintf(error_cause, 512, "Invalid NUA");
goto error;
}
session->stack->s_nh_r = nua_handle(session->stack->s_nua, session, TAG_END());
janus_mutex_unlock(&session->stack->smutex);
if(session->stack->s_nh_r == NULL) {
JANUS_LOG(LOG_ERR, "NUA Handle for REGISTER still null??\n");
error_code = JANUS_SIP_ERROR_LIBSOFIA_ERROR;
g_snprintf(error_cause, 512, "Invalid NUA Handle");
goto error;
}
/* TTL */
char ttl_text[20];
g_snprintf(ttl_text, sizeof(ttl_text), "%d", ttl);
/* Send the REGISTER */
nua_register(session->stack->s_nh_r,
NUTAG_M_USERNAME(session->account.authuser),
NUTAG_M_DISPLAY(session->account.display_name),
SIPTAG_FROM_STR(username_text),
SIPTAG_TO_STR(username_text),
TAG_IF(strlen(custom_headers) > 0, SIPTAG_HEADER_STR(custom_headers)),
TAG_IF(strlen(custom_params) > 0, NUTAG_M_PARAMS(custom_params)),
SIPTAG_EXPIRES_STR(ttl_text),
NUTAG_REGISTRAR(proxy_text),
NUTAG_PROXY(obproxy_text),
TAG_END());
result = json_object();
json_object_set_new(result, "event", json_string("registering"));
} else {
JANUS_LOG(LOG_VERB, "Not sending a SIP REGISTER: either send_register was set to false or guest mode was enabled\n");
session->account.registration_status = janus_sip_registration_status_disabled;
result = json_object();
json_object_set_new(result, "event", json_string("registered"));
json_object_set_new(result, "username", json_string(session->account.username));
json_object_set_new(result, "register_sent", json_false());
json_object_set_new(result, "master_id", json_integer(session->master_id));
/* Also notify event handlers */
if(notify_events && gateway->events_is_enabled()) {
json_t *info = json_object();
json_object_set_new(info, "event", json_string("registered"));
json_object_set_new(info, "identity", json_string(session->account.identity));
json_object_set_new(info, "type", json_string("guest"));
json_object_set_new(info, "master_id", json_integer(session->master_id));
gateway->notify_event(&janus_sip_plugin, session->handle, info);
}
}
} else if(!strcasecmp(request_text, "unregister")) {
if(session->stack == NULL) {
JANUS_LOG(LOG_ERR, "Wrong state (register first)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (register first)");
goto error;
}
if(session->helper) {
/* Not really "unregistering", we're just removing the association to the "master" */
janus_sip_session *master = session->master;
janus_mutex_lock(&master->mutex);
gboolean found = (g_list_find(master->helpers, session) != NULL);
if(found) {
master->helpers = g_list_remove(master->helpers, session);
janus_refcount_decrease(&session->ref);
janus_refcount_decrease(&master->ref);
}
janus_mutex_unlock(&master->mutex);
session->helper = FALSE;
session->master = NULL;
session->master_id = FALSE;
/* Done */
session->account.registration_status = janus_sip_registration_status_unregistered;
result = json_object();
json_object_set_new(result, "event", json_string("unregistering"));
goto done;
}
if(session->account.registration_status < janus_sip_registration_status_registered) {
JANUS_LOG(LOG_ERR, "Wrong state (not registered)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (not registered)");
goto error;
}
if(session->stack->s_nh_r == NULL) {
JANUS_LOG(LOG_ERR, "NUA Handle for REGISTER still null??\n");
error_code = JANUS_SIP_ERROR_LIBSOFIA_ERROR;
g_snprintf(error_cause, 512, "Invalid NUA Handle");
goto error;
}
/* Unregister now */
session->account.registration_status = janus_sip_registration_status_unregistering;
nua_unregister(session->stack->s_nh_r, TAG_END());
result = json_object();
json_object_set_new(result, "event", json_string("unregistering"));
} else if(!strcasecmp(request_text, "subscribe")) {
/* Subscribe to some SIP events */
JANUS_VALIDATE_JSON_OBJECT(root, subscribe_parameters,
error_code, error_cause, TRUE,
JANUS_SIP_ERROR_MISSING_ELEMENT, JANUS_SIP_ERROR_INVALID_ELEMENT);
if(error_code != 0)
goto error;
if(session->account.registration_status != janus_sip_registration_status_registered &&
session->account.registration_status != janus_sip_registration_status_disabled) {
JANUS_LOG(LOG_ERR, "Wrong state (not registered)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (not registered)");
goto error;
}
const char *to = json_string_value(json_object_get(root, "to"));
if(to == NULL)
to = session->account.identity;
const char *event_type = json_string_value(json_object_get(root, "event"));
const char *accept = json_string_value(json_object_get(root, "accept"));
/* TTL */
int ttl = subscribe_ttl;
json_t *sub_ttl = json_object_get(root, "subscribe_ttl");
if(sub_ttl && json_is_integer(sub_ttl))
ttl = json_integer_value(sub_ttl);
if(ttl <= 0)
ttl = JANUS_DEFAULT_SUBSCRIBE_TTL;
char ttl_text[20];
g_snprintf(ttl_text, sizeof(ttl_text), "%d", ttl);
/* Take call-id from request, if it exists */
const char *callid = NULL;
json_t *request_callid = json_object_get(root, "call_id");
if(request_callid)
callid = json_string_value(request_callid);
/* Do we have a handle for this subscription already? */
janus_mutex_lock(&session->stack->smutex);
nua_handle_t *nh = NULL;
if(session->stack->subscriptions != NULL)
nh = g_hash_table_lookup(session->stack->subscriptions, (char *)event_type);
if(nh == NULL) {
/* We don't, create one now */
if(!session->helper) {
if(session->stack->s_nua == NULL) {
janus_mutex_unlock(&session->stack->smutex);
JANUS_LOG(LOG_ERR, "NUA destroyed while subscribing?\n");
error_code = JANUS_SIP_ERROR_LIBSOFIA_ERROR;
g_snprintf(error_cause, 512, "Invalid NUA");
goto error;
}
nh = nua_handle(session->stack->s_nua, session, TAG_END());
} else {
/* This is a helper, we need to use the master's SIP stack */
if(session->master == NULL || session->master->stack == NULL) {
error_code = JANUS_SIP_ERROR_HELPER_ERROR;
g_snprintf(error_cause, 512, "Invalid master SIP stack");
goto error;
}
janus_mutex_lock(&session->master->stack->smutex);
if(session->master->stack->s_nua == NULL) {
janus_mutex_unlock(&session->master->stack->smutex);
JANUS_LOG(LOG_ERR, "NUA destroyed while subscribing?\n");
error_code = JANUS_SIP_ERROR_LIBSOFIA_ERROR;
g_snprintf(error_cause, 512, "Invalid NUA");
goto error;
}
nh = nua_handle(session->master->stack->s_nua, session, TAG_END());
janus_mutex_unlock(&session->master->stack->smutex);
}
if(session->stack->subscriptions == NULL) {
/* We still need a table for mapping these subscriptions as well */
session->stack->subscriptions = g_hash_table_new_full(g_int64_hash, g_int64_equal,
(GDestroyNotify)g_free, (GDestroyNotify)nua_handle_destroy);
}
g_hash_table_insert(session->stack->subscriptions, g_strdup(event_type), nh);
}
janus_mutex_unlock(&session->stack->smutex);
char custom_headers[2048];
janus_sip_parse_custom_headers(root, (char *)&custom_headers, sizeof(custom_headers));
/* Retrieve the Contact header for manually adding if not NULL */
char *contact_header = janus_sip_session_contact_header_retrieve(session);
/* Send the SUBSCRIBE */
nua_subscribe(nh,
SIPTAG_TO_STR(to),
SIPTAG_EVENT_STR(event_type),
SIPTAG_CALL_ID_STR(callid),
TAG_IF(contact_header != NULL, SIPTAG_CONTACT_STR(contact_header)),
SIPTAG_ACCEPT_STR(accept),
SIPTAG_EXPIRES_STR(ttl_text),
NUTAG_PROXY(session->helper && session->master ?
session->master->account.outbound_proxy : session->account.outbound_proxy),
TAG_IF(strlen(custom_headers) > 0, SIPTAG_HEADER_STR(custom_headers)),
TAG_END());
result = json_object();
json_object_set_new(result, "event", json_string("subscribing"));
if (callid)
json_object_set_new(result, "call_id", json_string(callid));
} else if(!strcasecmp(request_text, "unsubscribe")) {
/* Unsubscribe from some SIP events */
JANUS_VALIDATE_JSON_OBJECT(root, subscribe_parameters,
error_code, error_cause, TRUE,
JANUS_SIP_ERROR_MISSING_ELEMENT, JANUS_SIP_ERROR_INVALID_ELEMENT);
if(error_code != 0)
goto error;
if(session->account.registration_status != janus_sip_registration_status_registered &&
session->account.registration_status != janus_sip_registration_status_disabled) {
JANUS_LOG(LOG_ERR, "Wrong state (not registered)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (not registered)");
goto error;
}
const char *to = json_string_value(json_object_get(root, "to"));
if(to == NULL)
to = session->account.identity;
const char *event_type = json_string_value(json_object_get(root, "event"));
/* Get the handle we used for this subscription */
janus_mutex_lock(&session->stack->smutex);
nua_handle_t *nh = NULL;
if(session->stack->subscriptions != NULL)
nh = g_hash_table_lookup(session->stack->subscriptions, (char *)event_type);
janus_mutex_unlock(&session->stack->smutex);
if(nh == NULL) {
JANUS_LOG(LOG_ERR, "Wrong state (not subscribed to this event)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (not subscribed to this event)");
goto error;
}
/* Send the SUBSCRIBE with Expires set to 0 */
nua_subscribe(nh, SIPTAG_TO_STR(to), SIPTAG_EVENT_STR(event_type),
SIPTAG_EXPIRES_STR("0"), TAG_END());
result = json_object();
json_object_set_new(result, "event", json_string("unsubscribing"));
} else if(!strcasecmp(request_text, "call")) {
/* Call another peer */
if(session->stack == NULL) {
JANUS_LOG(LOG_ERR, "Wrong state (register first)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (register first)");
goto error;
}
if(session->account.registration_status != janus_sip_registration_status_registered &&
session->account.registration_status != janus_sip_registration_status_disabled) {
JANUS_LOG(LOG_ERR, "Wrong state (not registered)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (not registered)");
goto error;
}
if(session->status >= janus_sip_call_status_inviting) {
JANUS_LOG(LOG_ERR, "Wrong state (already in a call? status=%s)\n", janus_sip_call_status_string(session->status));
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (already in a call? status=%s)", janus_sip_call_status_string(session->status));
goto error;
}
JANUS_VALIDATE_JSON_OBJECT(root, call_parameters,
error_code, error_cause, TRUE,
JANUS_SIP_ERROR_MISSING_ELEMENT, JANUS_SIP_ERROR_INVALID_ELEMENT);
if(error_code != 0)
goto error;
json_t *secret = json_object_get(root, "secret");
json_t *ha1_secret = json_object_get(root, "ha1_secret");
json_t *authuser = json_object_get(root, "authuser");
if(secret && ha1_secret) {
JANUS_LOG(LOG_ERR, "Conflicting elements specified (secret and ha1_secret)\n");
error_code = JANUS_SIP_ERROR_INVALID_ELEMENT;
g_snprintf(error_cause, 512, "Conflicting elements specified (secret and ha1_secret)");
goto error;
}
json_t *uri = json_object_get(root, "uri");
/* Check if the INVITE needs to be enriched with custom headers */
char custom_headers[2048];
janus_sip_parse_custom_headers(root, (char *)&custom_headers, sizeof(custom_headers));
/* SDES-SRTP is disabled by default, let's see if we need to enable it */
gboolean offer_srtp = FALSE, require_srtp = FALSE;
janus_srtp_profile srtp_profile = JANUS_SRTP_AES128_CM_SHA1_80;
json_t *srtp = json_object_get(root, "srtp");
if(srtp) {
const char *srtp_text = json_string_value(srtp);
if(!strcasecmp(srtp_text, "sdes_optional")) {
/* Negotiate SDES, but make it optional */
offer_srtp = TRUE;
} else if(!strcasecmp(srtp_text, "sdes_mandatory")) {
/* Negotiate SDES, and require it */
offer_srtp = TRUE;
require_srtp = TRUE;
} else {
JANUS_LOG(LOG_ERR, "Invalid element (srtp can only be sdes_optional or sdes_mandatory)\n");
error_code = JANUS_SIP_ERROR_INVALID_ELEMENT;
g_snprintf(error_cause, 512, "Invalid element (srtp can only be sdes_optional or sdes_mandatory)");
goto error;
}
if(offer_srtp) {
/* Any SRTP profile different from the default? */
srtp_profile = JANUS_SRTP_AES128_CM_SHA1_80;
const char *profile = json_string_value(json_object_get(root, "srtp_profile"));
if(profile) {
if(!strcmp(profile, "AES_CM_128_HMAC_SHA1_32")) {
srtp_profile = JANUS_SRTP_AES128_CM_SHA1_32;
} else if(!strcmp(profile, "AES_CM_128_HMAC_SHA1_80")) {
srtp_profile = JANUS_SRTP_AES128_CM_SHA1_80;
#ifdef HAVE_SRTP_AESGCM
} else if(!strcmp(profile, "AEAD_AES_128_GCM")) {
srtp_profile = JANUS_SRTP_AEAD_AES_128_GCM;
} else if(!strcmp(profile, "AEAD_AES_256_GCM")) {
srtp_profile = JANUS_SRTP_AEAD_AES_256_GCM;
#endif
} else {
JANUS_LOG(LOG_ERR, "Invalid element (unsupported SRTP profile)\n");
error_code = JANUS_SIP_ERROR_INVALID_ELEMENT;
g_snprintf(error_cause, 512, "Invalid element (unsupported SRTP profile)");
goto error;
}
}
}
}
json_t *aar = json_object_get(root, "autoaccept_reinvites");
session->media.autoaccept_reinvites = aar ? json_is_true(aar) : TRUE;
/* Parse address */
const char *uri_text = convert_uri(json_string_value(uri));
if (uri_text == "")
{
JANUS_LOG(LOG_ERR, "Invalid user address %s\n", uri_text);
error_code = JANUS_SIP_ERROR_URI_CONV;
g_snprintf(error_cause, 512, "Failed to convert uri");
goto error;
}
janus_sip_uri_t target_uri;
if (janus_sip_parse_uri(&target_uri, uri_text) < 0)
{
JANUS_LOG(LOG_ERR, "Invalid user address %s\n", uri_text);
error_code = JANUS_SIP_ERROR_INVALID_ADDRESS;
g_snprintf(error_cause, 512, "Invalid user address %s\n", uri_text);
goto error;
}
/* Any SDP to handle? if not, something's wrong */
const char *msg_sdp_type = json_string_value(json_object_get(msg->jsep, "type"));
const char *msg_sdp = json_string_value(json_object_get(msg->jsep, "sdp"));
if(!msg_sdp) {
JANUS_LOG(LOG_ERR, "Missing SDP\n");
error_code = JANUS_SIP_ERROR_MISSING_SDP;
g_snprintf(error_cause, 512, "Missing SDP");
goto error;
}
if(json_is_true(json_object_get(msg->jsep, "e2ee"))) {
/* Media is encrypted, but SIP endpoints will need unencrypted media frames */
JANUS_LOG(LOG_ERR, "Media encryption unsupported by this plugin\n");
error_code = JANUS_SIP_ERROR_INVALID_ELEMENT;
g_snprintf(error_cause, 512, "Media encryption unsupported by this plugin");
goto error;
}
if(strstr(msg_sdp, "m=application")) {
JANUS_LOG(LOG_ERR, "The SIP plugin does not support DataChannels\n");
error_code = JANUS_SIP_ERROR_MISSING_SDP;
g_snprintf(error_cause, 512, "The SIP plugin does not support DataChannels");
goto error;
}
JANUS_LOG(LOG_VERB, "%s is calling %s\n", session->account.username, uri_text);
JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg_sdp_type, msg_sdp);
/* Clean up SRTP stuff from before first, in case it's still needed */
janus_sip_srtp_cleanup(session);
session->media.require_srtp = require_srtp;
session->media.has_srtp_local_audio = offer_srtp;
session->media.has_srtp_local_video = offer_srtp;
session->media.srtp_profile = srtp_profile;
if(offer_srtp) {
JANUS_LOG(LOG_VERB, "Going to negotiate SDES-SRTP (%s)...\n", require_srtp ? "mandatory" : "optional");
}
/* Get video-orientation extension id from SDP we got */
session->media.video_orientation_extension_id = janus_rtp_header_extension_get_id(msg_sdp, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION);
/* Get audio-level extension id from SDP we got */
session->media.audio_level_extension_id = janus_rtp_header_extension_get_id(msg_sdp, JANUS_RTP_EXTMAP_AUDIO_LEVEL);
/* Parse the SDP we got, manipulate some things, and generate a new one */
char sdperror[100];
janus_sdp *parsed_sdp = janus_sdp_parse(msg_sdp, sdperror, sizeof(sdperror));
if(!parsed_sdp) {
JANUS_LOG(LOG_ERR, "Error parsing SDP: %s\n", sdperror);
error_code = JANUS_SIP_ERROR_MISSING_SDP;
g_snprintf(error_cause, 512, "Error parsing SDP: %s", sdperror);
goto error;
}
/* Allocate RTP ports and merge them with the anonymized SDP */
if(strstr(msg_sdp, "m=audio") && !strstr(msg_sdp, "m=audio 0")) {
JANUS_LOG(LOG_VERB, "Going to negotiate audio...\n");
session->media.has_audio = TRUE; /* FIXME Maybe we need a better way to signal this */
}
if(strstr(msg_sdp, "m=video") && !strstr(msg_sdp, "m=video 0")) {
JANUS_LOG(LOG_VERB, "Going to negotiate video...\n");
session->media.has_video = TRUE; /* FIXME Maybe we need a better way to signal this */
}
janus_mutex_lock(&session->mutex);
if(janus_sip_allocate_local_ports(session, FALSE) < 0) {
janus_mutex_unlock(&session->mutex);
JANUS_LOG(LOG_ERR, "Could not allocate RTP/RTCP ports\n");
janus_sdp_destroy(parsed_sdp);
error_code = JANUS_SIP_ERROR_IO_ERROR;
g_snprintf(error_cause, 512, "Could not allocate RTP/RTCP ports");
goto error;
}
janus_mutex_unlock(&session->mutex);
char *sdp = janus_sip_sdp_manipulate(session, parsed_sdp, FALSE);
if(sdp == NULL) {
JANUS_LOG(LOG_ERR, "Error manipulating SDP\n");
janus_sdp_destroy(parsed_sdp);
error_code = JANUS_SIP_ERROR_IO_ERROR;
g_snprintf(error_cause, 512, "Error manipulating SDP");
goto error;
}
/* Take note of the SDP (may be useful for UPDATEs or re-INVITEs) */
janus_sdp_destroy(session->sdp);
session->sdp = parsed_sdp;
JANUS_LOG(LOG_VERB, "Prepared SDP for INVITE:\n%s", sdp);
/* Prepare the From header */
char from_hdr[1024];
/* Prepare the stack */
if(session->stack->s_nh_i != NULL)
nua_handle_destroy(session->stack->s_nh_i);
if(!session->helper) {
janus_mutex_lock(&session->stack->smutex);
if(session->stack->s_nua == NULL) {
janus_mutex_unlock(&session->stack->smutex);
JANUS_LOG(LOG_ERR, "NUA destroyed while calling?\n");
error_code = JANUS_SIP_ERROR_LIBSOFIA_ERROR;
g_snprintf(error_cause, 512, "Invalid NUA");
goto error;
}
session->stack->s_nh_i = nua_handle(session->stack->s_nua, session, TAG_END());
janus_mutex_unlock(&session->stack->smutex);
if(session->account.display_name) {
g_snprintf(from_hdr, sizeof(from_hdr), "\"%s\" <%s>", session->account.display_name, session->account.identity);
} else {
g_snprintf(from_hdr, sizeof(from_hdr), "%s", session->account.identity);
}
} else {
/* This is a helper, we need to use the master's SIP stack */
if(session->master == NULL || session->master->stack == NULL) {
g_free(sdp);
session->sdp = NULL;
janus_sdp_destroy(parsed_sdp);
error_code = JANUS_SIP_ERROR_HELPER_ERROR;
g_snprintf(error_cause, 512, "Invalid master SIP stack");
goto error;
}
janus_mutex_lock(&session->master->stack->smutex);
if(session->master->stack->s_nua == NULL) {
janus_mutex_unlock(&session->master->stack->smutex);
g_free(sdp);
session->sdp = NULL;
janus_sdp_destroy(parsed_sdp);
error_code = JANUS_SIP_ERROR_LIBSOFIA_ERROR;
g_snprintf(error_cause, 512, "Invalid NUA");
goto error;
}
session->stack->s_nh_i = nua_handle(session->master->stack->s_nua, session, TAG_END());
janus_mutex_unlock(&session->master->stack->smutex);
if(session->master->account.display_name) {
g_snprintf(from_hdr, sizeof(from_hdr), "\"%s\" <%s>", session->master->account.display_name, session->master->account.identity);
} else {
g_snprintf(from_hdr, sizeof(from_hdr), "%s", session->master->account.identity);
}
}
if(session->stack->s_nh_i == NULL) {
JANUS_LOG(LOG_WARN, "NUA Handle for INVITE still null??\n");
g_free(sdp);
session->sdp = NULL;
janus_sdp_destroy(parsed_sdp);
error_code = JANUS_SIP_ERROR_LIBSOFIA_ERROR;
g_snprintf(error_cause, 512, "Invalid NUA Handle");
goto error;
}
g_atomic_int_set(&session->hangingup, 0);
janus_sip_call_update_status(session, janus_sip_call_status_inviting);
char *callid = NULL;
json_t *request_callid = json_object_get(root, "call_id");
/* Take call-id from request, if it exists */
if(request_callid) {
callid = g_strdup(json_string_value(request_callid));
if(callid == NULL) {
JANUS_LOG(LOG_WARN, "Invalid call_id provided, generating a random one\n");
}
}
if(callid == NULL) {
/* If call-id does not exist in request, create a random one */
callid = g_malloc0(24);
janus_sip_random_string(24, callid);
}
/* Also notify event handlers */
if(notify_events && gateway->events_is_enabled()) {
json_t *info = json_object();
json_object_set_new(info, "event", json_string("calling"));
json_object_set_new(info, "callee", json_string(uri_text));
json_object_set_new(info, "call-id", json_string(callid));
json_object_set_new(info, "sdp", json_string(sdp));
gateway->notify_event(&janus_sip_plugin, session->handle, info);
}
/* If we're here because of a REFER, tell the transferer the request was accepted */
guint32 refer_id = json_integer_value(json_object_get(root, "refer_id"));
char *referred_by = NULL;
if(refer_id > 0) {
JANUS_LOG(LOG_VERB, "Call is after a refer (%"SCNu32")\n", refer_id);
janus_mutex_lock(&sessions_mutex);
janus_sip_transfer *transfer = g_hash_table_lookup(transfers, GUINT_TO_POINTER(refer_id));
janus_mutex_unlock(&sessions_mutex);
if(transfer != NULL) {
if(session->refer_id > 0 && session->refer_id != refer_id) {
janus_mutex_lock(&sessions_mutex);
g_hash_table_remove(transfers, GUINT_TO_POINTER(session->refer_id));
janus_mutex_unlock(&sessions_mutex);
}
session->refer_id = refer_id;
referred_by = transfer->referred_by ? g_strdup(transfer->referred_by) : NULL;
/* Any custom headers we should include? (e.g., Replaces) */
janus_strlcat(custom_headers, transfer->custom_headers, sizeof(custom_headers));
}
}
/* If the user negotiated simulcasting, just stick with the base substream */
json_t *msg_simulcast = json_object_get(msg->jsep, "simulcast");
if(msg_simulcast && json_array_size(msg_simulcast) > 0) {
JANUS_LOG(LOG_WARN, "Client negotiated simulcasting which we don't do here, falling back to base substream...\n");
size_t i = 0;
for(i=0; i<json_array_size(msg_simulcast); i++) {
json_t *sobj = json_array_get(msg_simulcast, i);
json_t *s = json_object_get(sobj, "ssrcs");
if(s && json_array_size(s) > 0)
session->media.simulcast_ssrc = json_integer_value(json_array_get(s, 0));
session->media.simulcast_ssrc = json_integer_value(json_object_get(s, "ssrc-0"));
/* FIXME We're stopping at the first item, there may be more */
break;
}
}
/* Check if there are new credentials to authenticate the INVITE */
if(authuser) {
JANUS_LOG(LOG_VERB, "Updating credentials (authuser) for authenticating the INVITE\n");
if(!session->helper) {
g_free(session->account.authuser);
session->account.authuser = g_strdup(json_string_value(authuser));
} else if(session->master != NULL) {
g_free(session->master->account.authuser);
session->master->account.authuser = g_strdup(json_string_value(authuser));
}
}
if(secret) {
JANUS_LOG(LOG_VERB, "Updating credentials (secret) for authenticating the INVITE\n");
if(!session->helper) {
g_free(session->account.secret);
session->account.secret = g_strdup(json_string_value(secret));
session->account.secret_type = janus_sip_secret_type_plaintext;
} else if(session->master != NULL) {
g_free(session->master->account.secret);
session->master->account.secret = g_strdup(json_string_value(secret));
session->master->account.secret_type = janus_sip_secret_type_plaintext;
}
} else if(ha1_secret) {
JANUS_LOG(LOG_VERB, "Updating credentials (ha1_secret) for authenticating the INVITE\n");
if(!session->helper) {
g_free(session->account.secret);
session->account.secret = g_strdup(json_string_value(ha1_secret));
session->account.secret_type = janus_sip_secret_type_hashed;
} else if(session->master != NULL) {
g_free(session->master->account.secret);
session->master->account.secret = g_strdup(json_string_value(ha1_secret));
session->master->account.secret_type = janus_sip_secret_type_hashed;
}
}
/* Send INVITE */
janus_mutex_lock(&session->mutex);
g_free(session->callee);
session->callee = g_strdup(uri_text);
janus_mutex_unlock(&session->mutex);
janus_mutex_lock(&sessions_mutex);
g_free(session->callid);
session->callid = callid;
g_hash_table_insert(callids, session->callid, session);
janus_mutex_unlock(&sessions_mutex);
g_atomic_int_set(&session->establishing, 1);
/* Add a reference for this call */
janus_sip_ref_active_call(session);
/* Retrieve the Contact header for manually adding if not NULL */
char *contact_header = janus_sip_session_contact_header_retrieve(session);
/* Send the INVITE */
nua_invite(session->stack->s_nh_i,
SIPTAG_FROM_STR(from_hdr),
SIPTAG_TO_STR(uri_text),
SIPTAG_CALL_ID_STR(callid),
TAG_IF(contact_header != NULL, SIPTAG_CONTACT_STR(contact_header)),
SOATAG_USER_SDP_STR(sdp),
NUTAG_PROXY(session->helper && session->master ?
session->master->account.outbound_proxy : session->account.outbound_proxy),
TAG_IF(referred_by != NULL, SIPTAG_REFERRED_BY_STR(referred_by)),
TAG_IF(strlen(custom_headers) > 0, SIPTAG_HEADER_STR(custom_headers)),
NUTAG_AUTOANSWER(0),
NUTAG_AUTOACK(FALSE),
TAG_END());
g_free(sdp);
g_free(session->transaction);
g_free(referred_by);
session->transaction = msg->transaction ? g_strdup(msg->transaction) : NULL;
/* Send an ack back */
result = json_object();
json_object_set_new(result, "event", json_string("calling"));
json_object_set_new(result, "call_id", json_string(session->callid));
} else if(!strcasecmp(request_text, "accept")) {
if(session->status != janus_sip_call_status_invited) {
JANUS_LOG(LOG_ERR, "Wrong state (not invited? status=%s)\n", janus_sip_call_status_string(session->status));
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (not invited? status=%s)", janus_sip_call_status_string(session->status));
goto error;
}
janus_mutex_lock(&session->mutex);
if(session->callee == NULL) {
janus_mutex_unlock(&session->mutex);
JANUS_LOG(LOG_ERR, "Wrong state (no caller?)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (no caller?)");
goto error;
}
janus_mutex_unlock(&session->mutex);
JANUS_VALIDATE_JSON_OBJECT(root, accept_parameters,
error_code, error_cause, TRUE,
JANUS_SIP_ERROR_MISSING_ELEMENT, JANUS_SIP_ERROR_INVALID_ELEMENT);
if(error_code != 0)
goto error;
json_t *srtp = json_object_get(root, "srtp");
gboolean answer_srtp = FALSE;
if(srtp) {
const char *srtp_text = json_string_value(srtp);
if(!strcasecmp(srtp_text, "sdes_optional")) {
/* Negotiate SDES, but make it optional */
answer_srtp = TRUE;
} else if(!strcasecmp(srtp_text, "sdes_mandatory")) {
/* Negotiate SDES, and require it */
answer_srtp = TRUE;
session->media.require_srtp = TRUE;
} else {
JANUS_LOG(LOG_ERR, "Invalid element (srtp can only be sdes_optional or sdes_mandatory)\n");
error_code = JANUS_SIP_ERROR_INVALID_ELEMENT;
g_snprintf(error_cause, 512, "Invalid element (srtp can only be sdes_optional or sdes_mandatory)");
goto error;
}
}
gboolean has_srtp = TRUE;
if(session->media.has_audio)
has_srtp = (has_srtp && session->media.has_srtp_remote_audio);
if(session->media.has_video)
has_srtp = (has_srtp && session->media.has_srtp_remote_video);
if(session->media.require_srtp && !has_srtp) {
JANUS_LOG(LOG_ERR, "Can't accept the call: SDES-SRTP required, but caller didn't offer it\n");
error_code = JANUS_SIP_ERROR_TOO_STRICT;
g_snprintf(error_cause, 512, "Can't accept the call: SDES-SRTP required, but caller didn't offer it");
goto error;
}
answer_srtp = answer_srtp || session->media.has_srtp_remote_audio || session->media.has_srtp_remote_video;
json_t *aar = json_object_get(root, "autoaccept_reinvites");
session->media.autoaccept_reinvites = aar ? json_is_true(aar) : TRUE;
/* Any SDP to handle? if not, something's wrong */
const char *msg_sdp_type = json_string_value(json_object_get(msg->jsep, "type"));
const char *msg_sdp = json_string_value(json_object_get(msg->jsep, "sdp"));
if(!msg_sdp) {
JANUS_LOG(LOG_ERR, "Missing SDP\n");
error_code = JANUS_SIP_ERROR_MISSING_SDP;
g_snprintf(error_cause, 512, "Missing SDP");
goto error;
}
if(json_is_true(json_object_get(msg->jsep, "e2ee"))) {
/* Media is encrypted, but SIP endpoints will need unencrypted media frames */
JANUS_LOG(LOG_ERR, "Media encryption unsupported by this plugin\n");
error_code = JANUS_SIP_ERROR_INVALID_ELEMENT;
g_snprintf(error_cause, 512, "Media encryption unsupported by this plugin");
goto error;
}
/* Accept a call from another peer */
JANUS_LOG(LOG_VERB, "We're accepting the call from %s\n", session->callee);
gboolean answer = !strcasecmp(msg_sdp_type, "answer");
if(!answer) {
JANUS_LOG(LOG_VERB, "This is a response to an offerless INVITE\n");
}
JANUS_LOG(LOG_VERB, "This is involving a negotiation (%s) as well:\n%s\n", msg_sdp_type, msg_sdp);
session->media.has_srtp_local_audio = answer_srtp && session->media.has_srtp_remote_audio;
session->media.has_srtp_local_video = answer_srtp && session->media.has_srtp_remote_video;
if(answer_srtp) {
JANUS_LOG(LOG_VERB, "Going to negotiate SDES-SRTP (%s)...\n", session->media.require_srtp ? "mandatory" : "optional");
}
/* Get video-orientation extension id from SDP we got */
session->media.video_orientation_extension_id = janus_rtp_header_extension_get_id(msg_sdp, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION);
/* Get audio-level extension id from SDP we got */
session->media.audio_level_extension_id = janus_rtp_header_extension_get_id(msg_sdp, JANUS_RTP_EXTMAP_AUDIO_LEVEL);
/* Parse the SDP we got, manipulate some things, and generate a new one */
char sdperror[100];
janus_sdp *parsed_sdp = janus_sdp_parse(msg_sdp, sdperror, sizeof(sdperror));
if(!parsed_sdp) {
JANUS_LOG(LOG_ERR, "Error parsing SDP: %s\n", sdperror);
error_code = JANUS_SIP_ERROR_MISSING_SDP;
g_snprintf(error_cause, 512, "Error parsing SDP: %s", sdperror);
goto error;
}
/* Allocate RTP ports and merge them with the anonymized SDP */
if(strstr(msg_sdp, "m=audio") && !strstr(msg_sdp, "m=audio 0")) {
JANUS_LOG(LOG_VERB, "Going to negotiate audio...\n");
session->media.has_audio = TRUE; /* FIXME Maybe we need a better way to signal this */
}
if(strstr(msg_sdp, "m=video") && !strstr(msg_sdp, "m=video 0")) {
JANUS_LOG(LOG_VERB, "Going to negotiate video...\n");
session->media.has_video = TRUE; /* FIXME Maybe we need a better way to signal this */
}
janus_mutex_lock(&session->mutex);
if(janus_sip_allocate_local_ports(session, FALSE) < 0) {
janus_mutex_unlock(&session->mutex);
JANUS_LOG(LOG_ERR, "Could not allocate RTP/RTCP ports\n");
janus_sdp_destroy(parsed_sdp);
error_code = JANUS_SIP_ERROR_IO_ERROR;
g_snprintf(error_cause, 512, "Could not allocate RTP/RTCP ports");
goto error;
}
janus_mutex_unlock(&session->mutex);
char *sdp = janus_sip_sdp_manipulate(session, parsed_sdp, TRUE);
if(sdp == NULL) {
JANUS_LOG(LOG_ERR, "Could not allocate RTP/RTCP ports\n");
janus_sdp_destroy(parsed_sdp);
error_code = JANUS_SIP_ERROR_IO_ERROR;
g_snprintf(error_cause, 512, "Could not allocate RTP/RTCP ports");
goto error;
}
if(session->media.audio_pt > -1) {
session->media.audio_pt_name = janus_get_codec_from_pt(sdp, session->media.audio_pt);
JANUS_LOG(LOG_VERB, "Detected audio codec: %d (%s)\n", session->media.audio_pt, session->media.audio_pt_name);
}
if(session->media.video_pt > -1) {
session->media.video_pt_name = janus_get_codec_from_pt(sdp, session->media.video_pt);
JANUS_LOG(LOG_VERB, "Detected video codec: %d (%s)\n", session->media.video_pt, session->media.video_pt_name);
}
/* Take note of the SDP (may be useful for UPDATEs or re-INVITEs) */
janus_sdp_destroy(session->sdp);
session->sdp = parsed_sdp;
JANUS_LOG(LOG_VERB, "Prepared SDP for 200 OK:\n%s", sdp);
/* If the user negotiated simulcasting, just stick with the base substream */
json_t *msg_simulcast = json_object_get(msg->jsep, "simulcast");
if(msg_simulcast) {
JANUS_LOG(LOG_WARN, "Client negotiated simulcasting which we don't do here, falling back to base substream...\n");
json_t *s = json_object_get(msg_simulcast, "ssrcs");
if(s && json_array_size(s) > 0)
session->media.simulcast_ssrc = json_integer_value(json_array_get(s, 0));
}
/* Also notify event handlers */
if(notify_events && gateway->events_is_enabled()) {
json_t *info = json_object();
json_object_set_new(info, "event", json_string(answer ? "accepted" : "accepting"));
if(session->callid)
json_object_set_new(info, "call-id", json_string(session->callid));
gateway->notify_event(&janus_sip_plugin, session->handle, info);
}
/* Check if the OK needs to be enriched with custom headers */
char custom_headers[2048];
janus_sip_parse_custom_headers(root, (char *)&custom_headers, sizeof(custom_headers));
/* Send 200 OK */
if(!answer) {
if(session->transaction)
g_free(session->transaction);
session->transaction = msg->transaction ? g_strdup(msg->transaction) : NULL;
}
g_atomic_int_set(&session->hangingup, 0);
janus_sip_call_update_status(session, janus_sip_call_status_incall);
if(session->stack->s_nh_i == NULL) {
JANUS_LOG(LOG_WARN, "NUA Handle for 200 OK still null??\n");
}
nua_respond(session->stack->s_nh_i,
200, sip_status_phrase(200),
SOATAG_USER_SDP_STR(sdp),
SOATAG_RTP_SELECT(SOA_RTP_SELECT_COMMON),
NUTAG_AUTOANSWER(0),
NUTAG_AUTOACK(FALSE),
TAG_IF(strlen(custom_headers) > 0, SIPTAG_HEADER_STR(custom_headers)),
TAG_END());
g_free(sdp);
/* Send an ack back */
result = json_object();
json_object_set_new(result, "event", json_string(answer ? "accepted" : "accepting"));
if(answer) {
/* Start the media */
session->media.ready = TRUE; /* FIXME Maybe we need a better way to signal this */
GError *error = NULL;
char tname[16];
g_snprintf(tname, sizeof(tname), "siprtp %s", session->account.username);
janus_refcount_increase(&session->ref);
session->relayer_thread = g_thread_try_new(tname, janus_sip_relay_thread, session, &error);
if(error != NULL) {
session->relayer_thread = NULL;
session->media.ready = FALSE;
janus_refcount_decrease(&session->ref);
JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the RTP/RTCP thread...\n",
error->code, error->message ? error->message : "??");
g_error_free(error);
}
}
} else if(!strcasecmp(request_text, "update")) {
/* Update an existing call */
if(!(session->status == janus_sip_call_status_incall_reinvited || session->status == janus_sip_call_status_incall)) {
JANUS_LOG(LOG_ERR, "Wrong state (not in a call? status=%s)\n", janus_sip_call_status_string(session->status));
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (not in a call?)");
goto error;
}
janus_mutex_lock(&session->mutex);
if(session->callee == NULL) {
janus_mutex_unlock(&session->mutex);
JANUS_LOG(LOG_ERR, "Wrong state (no callee?)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (no callee?)");
goto error;
}
janus_mutex_unlock(&session->mutex);
if(session->sdp == NULL) {
JANUS_LOG(LOG_ERR, "Wrong state (no local SDP?)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (no local SDP?)");
goto error;
}
/* Any SDP to handle? if not, something's wrong */
const char *msg_sdp = json_string_value(json_object_get(msg->jsep, "sdp"));
if(!msg_sdp) {
JANUS_LOG(LOG_ERR, "Missing SDP update\n");
error_code = JANUS_SIP_ERROR_MISSING_SDP;
g_snprintf(error_cause, 512, "Missing SDP update");
goto error;
}
if(!json_is_true(json_object_get(msg->jsep, "update"))) {
JANUS_LOG(LOG_ERR, "Missing SDP update\n");
error_code = JANUS_SIP_ERROR_MISSING_SDP;
g_snprintf(error_cause, 512, "Missing SDP update");
goto error;
}
if(json_is_true(json_object_get(msg->jsep, "e2ee"))) {
/* Media is encrypted, but SIP endpoints will need unencrypted media frames */
JANUS_LOG(LOG_ERR, "Media encryption unsupported by this plugin\n");
error_code = JANUS_SIP_ERROR_INVALID_ELEMENT;
g_snprintf(error_cause, 512, "Media encryption unsupported by this plugin");
goto error;
}
const char *msg_sdp_type = json_string_value(json_object_get(msg->jsep, "type"));
gboolean offer = !strcasecmp(msg_sdp_type, "offer");
if(!offer && session->status == janus_sip_call_status_incall) {
JANUS_LOG(LOG_ERR, "[SIP-%s] SDP type %s is incompatible with session status %s\n", session->account.username, msg_sdp_type, janus_sip_call_status_string(session->status));
error_code = JANUS_SIP_ERROR_MISSING_SDP;
g_snprintf(error_cause, 512, "[SIP-%s] SDP type %s is incompatible with session status %s\n", session->account.username, msg_sdp_type, janus_sip_call_status_string(session->status));
goto error;
}
/* Get video-orientation extension id from SDP we got */
session->media.video_orientation_extension_id = janus_rtp_header_extension_get_id(msg_sdp, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION);
/* Get audio-level extension id from SDP we got */
session->media.audio_level_extension_id = janus_rtp_header_extension_get_id(msg_sdp, JANUS_RTP_EXTMAP_AUDIO_LEVEL);
/* Parse the SDP we got, manipulate some things, and generate a new one */
char sdperror[100];
janus_sdp *parsed_sdp = janus_sdp_parse(msg_sdp, sdperror, sizeof(sdperror));
if(!parsed_sdp) {
JANUS_LOG(LOG_ERR, "Error parsing SDP: %s\n", sdperror);
error_code = JANUS_SIP_ERROR_MISSING_SDP;
g_snprintf(error_cause, 512, "Error parsing SDP: %s", sdperror);
goto error;
}
if(session->status == janus_sip_call_status_incall_reinvited && offer) {
/* We have re-INVITE in progress */
JANUS_LOG(LOG_VERB, "[SIP-%s] We have incoming offereless re-INVITE in progress\n", session->account.username);
}
if(offer)
session->sdp->o_version++;
gboolean audio_added = strstr(msg_sdp, "m=audio") && !strstr(msg_sdp, "m=audio 0") && session->media.local_audio_rtp_port == 0;
gboolean video_added = strstr(msg_sdp, "m=video") && !strstr(msg_sdp, "m=video 0") && session->media.local_video_rtp_port == 0;
if(audio_added)
session->media.has_audio = TRUE; /* FIXME Maybe we need a better way to signal this */
if(video_added)
session->media.has_video = TRUE; /* FIXME Maybe we need a better way to signal this */
if(offer) {
gboolean offer_srtp = session->media.require_srtp || session->media.has_srtp_local_audio || session->media.has_srtp_local_video;
session->media.has_srtp_local_audio = offer_srtp;
session->media.has_srtp_local_video = offer_srtp;
} else {
gboolean has_srtp = TRUE;
if (session->media.has_audio)
has_srtp = (has_srtp && session->media.has_srtp_remote_audio);
if (session->media.has_video)
has_srtp = (has_srtp && session->media.has_srtp_remote_video);
if (session->media.require_srtp && !has_srtp) {
JANUS_LOG(LOG_ERR,
"Can't update the call: SDES-SRTP required, but caller didn't offer it\n");
error_code = JANUS_SIP_ERROR_TOO_STRICT;
g_snprintf(error_cause, 512,
"Can't update the call: SDES-SRTP required, but caller didn't offer it");
goto error;
}
session->media.has_srtp_local_audio = session->media.has_srtp_remote_audio;
session->media.has_srtp_local_video = session->media.has_srtp_remote_video;
}
if(audio_added || video_added) {
janus_mutex_lock(&session->mutex);
if(janus_sip_allocate_local_ports(session, TRUE) < 0) {
janus_mutex_unlock(&session->mutex);
JANUS_LOG(LOG_ERR, "Could not allocate RTP/RTCP ports\n");
janus_sdp_destroy(parsed_sdp);
error_code = JANUS_SIP_ERROR_IO_ERROR;
g_snprintf(error_cause, 512, "Could not allocate RTP/RTCP ports");
goto error;
}
janus_mutex_unlock(&session->mutex);
if(!offer)
session->media.updated = TRUE;
}
char *sdp = janus_sip_sdp_manipulate(session, parsed_sdp, !offer);
if(sdp == NULL) {
JANUS_LOG(LOG_ERR, "Error manipulating SDP\n");
janus_sdp_destroy(parsed_sdp);
error_code = JANUS_SIP_ERROR_IO_ERROR;
g_snprintf(error_cause, 512, "Error manipulating SDP");
goto error;
}
if(!offer) {
if(session->media.audio_pt_name == NULL && session->media.audio_pt > -1) {
session->media.audio_pt_name = janus_get_codec_from_pt(sdp, session->media.audio_pt);
JANUS_LOG(LOG_VERB, "Detected audio codec: %d (%s)\n", session->media.audio_pt, session->media.audio_pt_name);
}
if(session->media.video_pt_name == NULL && session->media.video_pt > -1) {
session->media.video_pt_name = janus_get_codec_from_pt(sdp, session->media.video_pt);
JANUS_LOG(LOG_VERB, "Detected video codec: %d (%s)\n", session->media.video_pt, session->media.video_pt_name);
}
}
/* Take note of the new SDP */
janus_sdp_destroy(session->sdp);
session->sdp = parsed_sdp;
session->media.update = offer;
JANUS_LOG(LOG_VERB, "Prepared SDP for update:\n%s", sdp);
if(session->status == janus_sip_call_status_incall) {
/* Retrieve the Contact header for manually adding if not NULL */
char *contact_header = janus_sip_session_contact_header_retrieve(session);
/* We're sending a re-INVITE ourselves */
nua_invite(session->stack->s_nh_i,
TAG_IF(contact_header != NULL, SIPTAG_CONTACT_STR(contact_header)),
SOATAG_USER_SDP_STR(sdp),
TAG_END());
} else {
/* We're answering to a re-INVITE we received */
nua_respond(session->stack->s_nh_i,
200, sip_status_phrase(200),
SOATAG_USER_SDP_STR(sdp),
SOATAG_RTP_SELECT(SOA_RTP_SELECT_COMMON),
NUTAG_AUTOANSWER(0),
TAG_END());
}
g_free(sdp);
/* Send an ack back */
result = json_object();
json_object_set_new(result, "event", json_string(offer ? "updating" : "updated"));
} else if(!strcasecmp(request_text, "decline")) {
JANUS_VALIDATE_JSON_OBJECT(root, decline_parameters,
error_code, error_cause, TRUE,
JANUS_SIP_ERROR_MISSING_ELEMENT, JANUS_SIP_ERROR_INVALID_ELEMENT);
if(error_code != 0)
goto error;
/* Wheck if we're declining a call transfer, rather than an incoming call */
guint32 refer_id = json_integer_value(json_object_get(root, "refer_id"));
if(refer_id > 0) {
janus_mutex_lock(&sessions_mutex);
janus_sip_transfer *transfer = g_hash_table_lookup(transfers, GUINT_TO_POINTER(refer_id));
janus_mutex_unlock(&sessions_mutex);
if(transfer != NULL && transfer->nh_s != NULL) {
/* Send a NOTIFY with the error code */
int response_code = 603;
json_t *code_json = json_object_get(root, "code");
if(code_json)
response_code = json_integer_value(code_json);
if(response_code <= 399) {
JANUS_LOG(LOG_WARN, "Invalid SIP response code specified, using 603 to decline transfer\n");
response_code = 603;
}
char content[100];
g_snprintf(content, sizeof(content), "SIP/2.0 %d %s", response_code, sip_status_phrase(response_code));
nua_notify(transfer->nh_s,
NUTAG_SUBSTATE(nua_substate_terminated),
SIPTAG_CONTENT_TYPE_STR("message/sipfrag"),
SIPTAG_PAYLOAD_STR(content),
NUTAG_WITH_SAVED(transfer->saved),
TAG_END());
/* Also notify event handlers */
if(notify_events && gateway->events_is_enabled()) {
json_t *info = json_object();
json_object_set_new(info, "event", json_string("declined"));
json_object_set_new(info, "refer_id", json_integer(refer_id));
json_object_set_new(info, "code", json_integer(response_code));
gateway->notify_event(&janus_sip_plugin, session->handle, info);
}
/* Notify the operation */
result = json_object();
json_object_set_new(result, "event", json_string("declining"));
json_object_set_new(result, "refer_id", json_integer(refer_id));
json_object_set_new(result, "code", json_integer(response_code));
janus_mutex_lock(&sessions_mutex);
g_hash_table_remove(transfers, GUINT_TO_POINTER(refer_id));
janus_mutex_unlock(&sessions_mutex);
goto done;
} else {
janus_mutex_lock(&sessions_mutex);
g_hash_table_remove(transfers, GUINT_TO_POINTER(refer_id));
janus_mutex_unlock(&sessions_mutex);
JANUS_LOG(LOG_ERR, "Wrong state (no transfer?)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (no transfer?)");
goto error;
}
}
/* Reject an incoming call */
if(session->status != janus_sip_call_status_invited) {
JANUS_LOG(LOG_ERR, "Wrong state (not invited? status=%s)\n", janus_sip_call_status_string(session->status));
/* Ignore */
janus_sip_message_free(msg);
continue;
//~ g_snprintf(error_cause, 512, "Wrong state (not in a call?)");
//~ goto error;
}
janus_mutex_lock(&session->mutex);
if(session->callee == NULL) {
janus_mutex_unlock(&session->mutex);
JANUS_LOG(LOG_ERR, "Wrong state (no callee?)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (no callee?)");
goto error;
}
janus_mutex_unlock(&session->mutex);
session->media.earlymedia = FALSE;
session->media.update = FALSE;
session->media.autoaccept_reinvites = TRUE;
session->media.ready = FALSE;
session->media.on_hold = FALSE;
janus_sip_call_update_status(session, janus_sip_call_status_closing);
if(session->stack->s_nh_i == NULL) {
JANUS_LOG(LOG_WARN, "NUA Handle for 200 OK still null??\n");
}
int response_code = 603;
json_t *code_json = json_object_get(root, "code");
if(code_json)
response_code = json_integer_value(code_json);
if(response_code <= 399) {
JANUS_LOG(LOG_WARN, "Invalid SIP response code specified, using 486 to decline call\n");
response_code = 486;
}
/* Check if the response needs to be enriched with custom headers */
char custom_headers[2048];
janus_sip_parse_custom_headers(root, (char *)&custom_headers, sizeof(custom_headers));
nua_respond(session->stack->s_nh_i, response_code, sip_status_phrase(response_code),
TAG_IF(strlen(custom_headers) > 0, SIPTAG_HEADER_STR(custom_headers)),
TAG_END());
janus_mutex_lock(&session->mutex);
/* Also notify event handlers */
if(notify_events && gateway->events_is_enabled()) {
json_t *info = json_object();
json_object_set_new(info, "event", json_string("declined"));
json_object_set_new(info, "callee", json_string(session->callee));
if(session->callid)
json_object_set_new(info, "call-id", json_string(session->callid));
json_object_set_new(info, "code", json_integer(response_code));
gateway->notify_event(&janus_sip_plugin, session->handle, info);
}
g_free(session->callee);
session->callee = NULL;
janus_mutex_unlock(&session->mutex);
/* Notify the operation */
result = json_object();
json_object_set_new(result, "event", json_string("declining"));
json_object_set_new(result, "code", json_integer(response_code));
if(session->callid)
json_object_set_new(result, "call_id", json_string(session->callid));
} else if(!strcasecmp(request_text, "transfer")) {
/* Transfer an existing call */
JANUS_VALIDATE_JSON_OBJECT(root, transfer_parameters,
error_code, error_cause, TRUE,
JANUS_SIP_ERROR_MISSING_ELEMENT, JANUS_SIP_ERROR_INVALID_ELEMENT);
if(error_code != 0)
goto error;
if(!janus_sip_call_is_established(session)) {
JANUS_LOG(LOG_ERR, "Wrong state (not in a call? status=%s)\n", janus_sip_call_status_string(session->status));
g_snprintf(error_cause, 512, "Wrong state (not in a call?)");
goto error;
}
janus_mutex_lock(&session->mutex);
if(session->callee == NULL) {
janus_mutex_unlock(&session->mutex);
JANUS_LOG(LOG_ERR, "Wrong state (no callee?)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (no callee?)");
goto error;
}
janus_mutex_unlock(&session->mutex);
if(session->sdp == NULL) {
JANUS_LOG(LOG_ERR, "Wrong state (no local SDP?)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (no local SDP?)");
goto error;
}
/* Transfer to the following URI */
json_t *uri = json_object_get(root, "uri");
const char *uri_text = json_string_value(uri);
janus_sip_uri_t target_uri;
if(janus_sip_parse_uri(&target_uri, uri_text) < 0) {
JANUS_LOG(LOG_ERR, "Invalid user address %s\n", uri_text);
error_code = JANUS_SIP_ERROR_INVALID_ADDRESS;
g_snprintf(error_cause, 512, "Invalid user address %s\n", uri_text);
goto error;
}
/* Is this a blind (unattended) or warm (attended) transfer? (default=blind) */
const char *callid = json_string_value(json_object_get(root, "replace"));
sip_refer_to_t *refer_to = NULL;
if(callid != NULL) {
/* This is an attended transfer, make sure this call exists */
janus_mutex_lock(&sessions_mutex);
janus_sip_session *replaced = g_hash_table_lookup(callids, callid);
janus_mutex_unlock(&sessions_mutex);
if(replaced == NULL || replaced->stack == NULL || replaced->stack->s_nh_i == NULL) {
JANUS_LOG(LOG_ERR, "No such call-ID %s\n", callid);
error_code = JANUS_SIP_ERROR_NO_SUCH_CALLID;
g_snprintf(error_cause, 512, "No such call-ID %s", callid);
goto error;
}
/* Craft the Replaces header field */
sip_replaces_t *r = nua_handle_make_replaces(replaced->stack->s_nh_i, session->stack->s_home, 0);
char *replaces = sip_headers_as_url_query(session->stack->s_home, SIPTAG_REPLACES(r), TAG_END());
refer_to = sip_refer_to_format(session->stack->s_home, "<%s?%s>", uri_text, replaces);
JANUS_LOG(LOG_VERB, "Attended transfer: <%s?%s>\n", uri_text, replaces);
su_free(session->stack->s_home, r);
su_free(session->stack->s_home, replaces);
}
if(refer_to == NULL)
refer_to = sip_refer_to_format(session->stack->s_home, "<%s>", uri_text);
/* Send the REFER */
nua_refer(session->stack->s_nh_i,
SIPTAG_REFER_TO(refer_to),
TAG_END());
/* Notify the operation */
result = json_object();
json_object_set_new(result, "event", json_string("transferring"));
} else if(!strcasecmp(request_text, "hold") || !strcasecmp(request_text, "unhold")) {
/* We either need to put the call on-hold, or resume it */
if(session->status != janus_sip_call_status_incall) {
JANUS_LOG(LOG_ERR, "Wrong state (not in a call? status=%s)\n", janus_sip_call_status_string(session->status));
/* Ignore */
janus_sip_message_free(msg);
continue;
//~ g_snprintf(error_cause, 512, "Wrong state (not in a call?)");
//~ goto error;
}
janus_mutex_lock(&session->mutex);
if(session->callee == NULL) {
janus_mutex_unlock(&session->mutex);
JANUS_LOG(LOG_ERR, "Wrong state (no callee?)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (no callee?)");
goto error;
}
janus_mutex_unlock(&session->mutex);
if(session->sdp == NULL) {
JANUS_LOG(LOG_ERR, "Wrong state (no SDP?)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (no SDP?)");
goto error;
}
gboolean hold = !strcasecmp(request_text, "hold");
if(hold != session->media.on_hold) {
/* To put the call on-hold, we need to change the media direction:
* resuming it means resuming the direction we had before */
janus_sdp_mdirection hold_dir = JANUS_SDP_SENDONLY;
if(hold) {
/* By default when holding we use recvonly, but the
* actual direction to set can be passed via API too */
JANUS_VALIDATE_JSON_OBJECT(root, hold_parameters,
error_code, error_cause, TRUE,
JANUS_SIP_ERROR_MISSING_ELEMENT, JANUS_SIP_ERROR_INVALID_ELEMENT);
if(error_code != 0)
goto error;
json_t *hdir = json_object_get(root, "direction");
if(hdir != NULL) {
const char *dir = json_string_value(hdir);
hold_dir = janus_sdp_parse_mdirection(dir);
if(hold_dir != JANUS_SDP_SENDONLY && hold_dir != JANUS_SDP_RECVONLY &&
hold_dir != JANUS_SDP_INACTIVE) {
/* Invalid direction */
JANUS_LOG(LOG_ERR, "Invalid direction (can only be sendonly, recvonly or inactive)\n");
error_code = JANUS_SIP_ERROR_INVALID_ELEMENT;
g_snprintf(error_cause, 512, "Invalid direction (can only be sendonly, recvonly or inactive)");
goto error;
}
}
}
session->media.on_hold = hold;
janus_sdp_mline *m = janus_sdp_mline_find(session->sdp, JANUS_SDP_AUDIO);
if(m) {
if(hold) {
/* Take note of the original media direction */
session->media.hold_audio_dir = hold_dir;
session->media.pre_hold_audio_dir = m->direction;
if(m->direction != hold_dir) {
/* Update the media direction */
switch(m->direction) {
case JANUS_SDP_DEFAULT:
case JANUS_SDP_SENDRECV:
m->direction = hold_dir;
break;
default:
m->direction = JANUS_SDP_INACTIVE;
break;
}
}
} else {
m->direction = session->media.pre_hold_audio_dir;
}
}
m = janus_sdp_mline_find(session->sdp, JANUS_SDP_VIDEO);
if(m) {
if(hold) {
/* Take note of the original media direction */
session->media.hold_video_dir = hold_dir;
session->media.pre_hold_video_dir = m->direction;
if(m->direction != hold_dir) {
/* Update the media direction */
switch(m->direction) {
case JANUS_SDP_DEFAULT:
case JANUS_SDP_SENDRECV:
m->direction = hold_dir;
break;
default:
m->direction = JANUS_SDP_INACTIVE;
break;
}
}
} else {
m->direction = session->media.pre_hold_video_dir;
}
}
/* Check if the INVITE needs to be enriched with custom headers */
char custom_headers[2048];
janus_sip_parse_custom_headers(root, (char *)&custom_headers, sizeof(custom_headers));
/* Retrieve the Contact header for manually adding if not NULL */
char *contact_header = janus_sip_session_contact_header_retrieve(session);
/* Send the re-INVITE */
char *sdp = janus_sdp_write(session->sdp);
nua_invite(session->stack->s_nh_i,
TAG_IF(contact_header != NULL, SIPTAG_CONTACT_STR(contact_header)),
SOATAG_USER_SDP_STR(sdp),
TAG_IF(strlen(custom_headers) > 0, SIPTAG_HEADER_STR(custom_headers)),
TAG_END());
g_free(sdp);
}
/* Send an ack back */
result = json_object();
json_object_set_new(result, "event", json_string(hold ? "holding" : "resuming"));
} else if(!strcasecmp(request_text, "hangup")) {
/* Hangup an ongoing call */
if(!janus_sip_call_is_established(session) && session->status != janus_sip_call_status_inviting) {
JANUS_LOG(LOG_ERR, "Wrong state (not established/inviting? status=%s)\n",
janus_sip_call_status_string(session->status));
/* Ignore */
janus_sip_message_free(msg);
continue;
}
janus_mutex_lock(&session->mutex);
if(session->callee == NULL) {
janus_mutex_unlock(&session->mutex);
JANUS_LOG(LOG_ERR, "Wrong state (no callee?)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (no callee?)");
goto error;
}
janus_mutex_unlock(&session->mutex);
session->media.earlymedia = FALSE;
session->media.update = FALSE;
session->media.autoaccept_reinvites = TRUE;
session->media.ready = FALSE;
session->media.on_hold = FALSE;
janus_sip_call_update_status(session, janus_sip_call_status_closing);
char custom_headers[2048];
janus_sip_parse_custom_headers(root, (char *)&custom_headers, sizeof(custom_headers));
nua_bye(session->stack->s_nh_i,
TAG_IF(strlen(custom_headers) > 0, SIPTAG_HEADER_STR(custom_headers)),
TAG_END());
janus_mutex_lock(&session->mutex);
g_free(session->callee);
session->callee = NULL;
janus_mutex_unlock(&session->mutex);
/* Notify the operation */
result = json_object();
json_object_set_new(result, "event", json_string("hangingup"));
} else if(!strcasecmp(request_text, "recording")) {
/* Start or stop recording */
if(!(session->status == janus_sip_call_status_inviting || /* Presume it makes sense to start recording with early media? */
janus_sip_call_is_established(session))) {
JANUS_LOG(LOG_ERR, "Wrong state (not in a call? status=%s)\n", janus_sip_call_status_string(session->status));
g_snprintf(error_cause, 512, "Wrong state (not in a call?)");
goto error;
}
janus_mutex_lock(&session->mutex);
if(session->callee == NULL) {
janus_mutex_unlock(&session->mutex);
JANUS_LOG(LOG_ERR, "Wrong state (no callee?)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (no callee?)");
goto error;
}
janus_mutex_unlock(&session->mutex);
JANUS_VALIDATE_JSON_OBJECT(root, recording_parameters,
error_code, error_cause, TRUE,
JANUS_SIP_ERROR_MISSING_ELEMENT, JANUS_SIP_ERROR_INVALID_ELEMENT);
if(error_code != 0)
goto error;
json_t *action = json_object_get(root, "action");
const char *action_text = json_string_value(action);
if(strcasecmp(action_text, "start") && strcasecmp(action_text, "stop") &&
strcasecmp(action_text, "pause") && strcasecmp(action_text, "resume")) {
JANUS_LOG(LOG_ERR, "Invalid action (should be start|stop|pause|resume)\n");
error_code = JANUS_SIP_ERROR_INVALID_ELEMENT;
g_snprintf(error_cause, 512, "Invalid action (should be start|stop|pause|resume)");
goto error;
}
gboolean record_audio = FALSE, record_video = FALSE, /* No media is recorded by default */
record_peer_audio = FALSE, record_peer_video = FALSE, send_peer_pli = FALSE;
json_t *audio = json_object_get(root, "audio");
record_audio = audio ? json_is_true(audio) : FALSE;
json_t *video = json_object_get(root, "video");
record_video = video ? json_is_true(video) : FALSE;
json_t *peer_audio = json_object_get(root, "peer_audio");
record_peer_audio = peer_audio ? json_is_true(peer_audio) : FALSE;
json_t *peer_video = json_object_get(root, "peer_video");
record_peer_video = peer_video ? json_is_true(peer_video) : FALSE;
json_t *peer_pli = json_object_get(root, "send_peer_pli");
send_peer_pli = peer_pli ? json_is_true(peer_pli) : FALSE;
if(!record_audio && !record_video && !record_peer_audio && !record_peer_video) {
JANUS_LOG(LOG_ERR, "Invalid request (at least one of audio, video, peer_audio and peer_video should be true)\n");
error_code = JANUS_SIP_ERROR_RECORDING_ERROR;
g_snprintf(error_cause, 512, "Invalid request (at least one of audio, video, peer_audio and peer_video should be true)");
goto error;
}
json_t *recfile = json_object_get(root, "filename");
const char *recording_base = json_string_value(recfile);
janus_mutex_lock(&session->rec_mutex);
if(!strcasecmp(action_text, "start")) {
/* Start recording something */
janus_recorder *rc = NULL;
char filename[255];
gint64 now = janus_get_real_time();
if(record_peer_audio || record_peer_video) {
JANUS_LOG(LOG_INFO, "Starting recording of peer's %s (user %s, call %s)\n",
(record_peer_audio && record_peer_video ? "audio and video" : (record_peer_audio ? "audio" : "video")),
session->account.username, session->transaction);
/* Start recording this peer's audio and/or video */
if(record_peer_audio) {
memset(filename, 0, 255);
if(recording_base) {
/* Use the filename and path we have been provided */
g_snprintf(filename, 255, "%s-peer-audio", recording_base);
/* FIXME This only works if offer/answer happened */
rc = janus_recorder_create(NULL, session->media.audio_pt_name, filename);
} else {
/* Build a filename */
g_snprintf(filename, 255, "sip-%s-%s-%"SCNi64"-peer-audio",
session->account.username ? session->account.username : "unknown",
session->transaction ? session->transaction : "unknown",
now);
/* FIXME This only works if offer/answer happened */
rc = janus_recorder_create(NULL, session->media.audio_pt_name, filename);
}
if(rc == NULL) {
/* FIXME We should notify the fact the recorder could not be created */
JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this peer!\n");
} else {
/* If RED is in use, take note of it */
if(session->media.opusred_pt > 0)
janus_recorder_opusred(rc, session->media.opusred_pt);
session->arc_peer = rc;
}
}
if(record_peer_video) {
memset(filename, 0, 255);
if(recording_base) {
/* Use the filename and path we have been provided */
g_snprintf(filename, 255, "%s-peer-video", recording_base);
/* FIXME This only works if offer/answer happened */
rc = janus_recorder_create(NULL, session->media.video_pt_name, filename);
} else {
/* Build a filename */
g_snprintf(filename, 255, "sip-%s-%s-%"SCNi64"-peer-video",
session->account.username ? session->account.username : "unknown",
session->transaction ? session->transaction : "unknown",
now);
/* FIXME This only works if offer/answer happened */
rc = janus_recorder_create(NULL, session->media.video_pt_name, filename);
}
/* If the video-orientation extension has been negotiated, mark it in the recording */
if(session->media.video_orientation_extension_id > 0)
janus_recorder_add_extmap(session->vrc_peer, session->media.video_orientation_extension_id, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION);
/* If we detected PLI support in the remote SDP, craft and send a PLI to the peer */
if(session->media.video_pli_supported || send_peer_pli)
janus_sip_rtcp_pli_send(session);
if(rc == NULL) {
/* FIXME We should notify the fact the recorder could not be created */
JANUS_LOG(LOG_ERR, "Couldn't open an video recording file for this peer!\n");
} else {
session->vrc_peer = rc;
}
}
}
if(record_audio || record_video) {
/* Start recording the user's audio and/or video */
JANUS_LOG(LOG_INFO, "Starting recording of user's %s (user %s, call %s)\n",
(record_audio && record_video ? "audio and video" : (record_audio ? "audio" : "video")),
session->account.username, session->transaction);
if(record_audio) {
memset(filename, 0, 255);
if(recording_base) {
/* Use the filename and path we have been provided */
g_snprintf(filename, 255, "%s-user-audio", recording_base);
/* FIXME This only works if offer/answer happened */
rc = janus_recorder_create(NULL, session->media.audio_pt_name, filename);
} else {
/* Build a filename */
g_snprintf(filename, 255, "sip-%s-%s-%"SCNi64"-own-audio",
session->account.username ? session->account.username : "unknown",
session->transaction ? session->transaction : "unknown",
now);
/* FIXME This only works if offer/answer happened */
rc = janus_recorder_create(NULL, session->media.audio_pt_name, filename);
}
if(rc == NULL) {
/* FIXME We should notify the fact the recorder could not be created */
JANUS_LOG(LOG_ERR, "Couldn't open an audio recording file for this user!\n");
} else {
/* If RED is in use, take note of it */
if(session->media.opusred_pt > 0)
janus_recorder_opusred(rc, session->media.opusred_pt);
session->arc = rc;
}
}
if(record_video) {
memset(filename, 0, 255);
if(recording_base) {
/* Use the filename and path we have been provided */
g_snprintf(filename, 255, "%s-user-video", recording_base);
/* FIXME This only works if offer/answer happened */
rc = janus_recorder_create(NULL, session->media.video_pt_name, filename);
} else {
/* Build a filename */
g_snprintf(filename, 255, "sip-%s-%s-%"SCNi64"-own-video",
session->account.username ? session->account.username : "unknown",
session->transaction ? session->transaction : "unknown",
now);
/* FIXME This only works if offer/answer happened */
rc = janus_recorder_create(NULL, session->media.video_pt_name, filename);
}
if(rc == NULL) {
/* FIXME We should notify the fact the recorder could not be created */
JANUS_LOG(LOG_ERR, "Couldn't open a video recording file for this user!\n");
} else {
session->vrc = rc;
}
/* If the video-orientation extension has been negotiated, mark it in the recording */
if(session->media.video_orientation_extension_id > 0)
janus_recorder_add_extmap(session->vrc, session->media.video_orientation_extension_id, JANUS_RTP_EXTMAP_VIDEO_ORIENTATION);
/* Send a PLI */
JANUS_LOG(LOG_VERB, "Recording video, sending a PLI to kickstart it\n");
gateway->send_pli(session->handle);
}
}
} else if(!strcasecmp(action_text, "pause")) {
if(record_audio)
janus_recorder_pause(session->arc);
if(record_video)
janus_recorder_pause(session->vrc);
if(record_peer_audio)
janus_recorder_pause(session->arc_peer);
if(record_peer_video)
janus_recorder_pause(session->vrc_peer);
} else if(!strcasecmp(action_text, "resume")) {
if(record_audio)
janus_recorder_resume(session->arc);
if(record_video && !janus_recorder_resume(session->vrc))
gateway->send_pli(session->handle);
if(record_peer_audio)
janus_recorder_resume(session->arc_peer);
if(record_peer_video)
janus_recorder_resume(session->vrc_peer);
} else {
/* Stop recording something: notice that this never returns an error, even when we were not recording anything */
janus_sip_recorder_close(session, record_audio, record_peer_audio, record_video, record_peer_video);
}
janus_mutex_unlock(&session->rec_mutex);
/* Notify the result */
result = json_object();
json_object_set_new(result, "event", json_string("recordingupdated"));
} else if(!strcasecmp(request_text, "info")) {
/* Send a SIP INFO request: we'll need the payload type and content */
if(!janus_sip_call_is_established(session)) {
JANUS_LOG(LOG_ERR, "Wrong state (not established? status=%s)\n", janus_sip_call_status_string(session->status));
g_snprintf(error_cause, 512, "Wrong state (not in a call?)");
goto error;
}
janus_mutex_lock(&session->mutex);
if(session->callee == NULL) {
janus_mutex_unlock(&session->mutex);
JANUS_LOG(LOG_ERR, "Wrong state (no callee?)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (no callee?)");
goto error;
}
janus_mutex_unlock(&session->mutex);
JANUS_VALIDATE_JSON_OBJECT(root, info_parameters,
error_code, error_cause, TRUE,
JANUS_SIP_ERROR_MISSING_ELEMENT, JANUS_SIP_ERROR_INVALID_ELEMENT);
if(error_code != 0)
goto error;
const char *info_type = json_string_value(json_object_get(root, "type"));
const char *info_content = json_string_value(json_object_get(root, "content"));
char custom_headers[2048];
janus_sip_parse_custom_headers(root, (char *)&custom_headers, sizeof(custom_headers));
nua_info(session->stack->s_nh_i,
SIPTAG_CONTENT_TYPE_STR(info_type),
SIPTAG_PAYLOAD_STR(info_content),
TAG_IF(strlen(custom_headers) > 0, SIPTAG_HEADER_STR(custom_headers)),
TAG_END());
/* Notify the operation */
result = json_object();
json_object_set_new(result, "event", json_string("infosent"));
} else if(!strcasecmp(request_text, "message")) {
/* Send a SIP MESSAGE request: we'll only need the content and optional payload type */
JANUS_VALIDATE_JSON_OBJECT(root, sipmessage_parameters,
error_code, error_cause, TRUE,
JANUS_SIP_ERROR_MISSING_ELEMENT, JANUS_SIP_ERROR_INVALID_ELEMENT);
if(error_code != 0) {
goto error;
}
gboolean in_dialog_message = TRUE;
json_t *uri = json_object_get(root, "uri");
const char *uri_text = json_string_value(uri);
if(uri != NULL)
in_dialog_message = FALSE;
if(in_dialog_message) {
if(!(session->status == janus_sip_call_status_inviting || janus_sip_call_is_established(session))) {
JANUS_LOG(LOG_ERR, "Wrong state (not established? status=%s)\n", janus_sip_call_status_string(session->status));
g_snprintf(error_cause, 512, "Wrong state (not in a call?)");
goto error;
}
janus_mutex_lock(&session->mutex);
if(session->callee == NULL) {
janus_mutex_unlock(&session->mutex);
JANUS_LOG(LOG_ERR, "Wrong state (no callee?)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (no callee?)");
goto error;
}
janus_mutex_unlock(&session->mutex);
} else {
if(session->account.registration_status != janus_sip_registration_status_registered &&
session->account.registration_status != janus_sip_registration_status_disabled) {
JANUS_LOG(LOG_ERR, "Wrong state (not registered)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (not registered)");
goto error;
}
janus_sip_uri_t target_uri;
if(janus_sip_parse_uri(&target_uri, uri_text) < 0) {
JANUS_LOG(LOG_ERR, "Invalid user address %s\n", uri_text);
error_code = JANUS_SIP_ERROR_INVALID_ADDRESS;
g_snprintf(error_cause, 512, "Invalid user address %s\n", uri_text);
goto error;
}
}
const char *content_type = "text/plain";
json_t *content_type_text = json_object_get(root, "content_type");
if(content_type_text && json_is_string(content_type_text))
content_type = json_string_value(content_type_text);
const char *msg_content = json_string_value(json_object_get(root, "content"));
char custom_headers[2048];
janus_sip_parse_custom_headers(root, (char *)&custom_headers, sizeof(custom_headers));
char *message_callid = NULL;
if(in_dialog_message) {
/* Take Call-ID, later used to report delivery status */
message_callid = g_strdup(session->callid) ;
nua_message(session->stack->s_nh_i,
SIPTAG_CONTENT_TYPE_STR(content_type),
SIPTAG_PAYLOAD_STR(msg_content),
TAG_IF(strlen(custom_headers) > 0, SIPTAG_HEADER_STR(custom_headers)),
TAG_END());
} else {
/* Get appropriate handle */
nua_handle_t *nh = NULL;
if(!session->helper) {
janus_mutex_lock(&session->stack->smutex);
if(session->stack->s_nua == NULL) {
janus_mutex_unlock(&session->stack->smutex);
JANUS_LOG(LOG_ERR, "NUA destroyed while sending message?\n");
error_code = JANUS_SIP_ERROR_LIBSOFIA_ERROR;
g_snprintf(error_cause, 512, "Invalid NUA");
goto error;
}
nh = nua_handle(session->stack->s_nua, session, TAG_END());
janus_mutex_unlock(&session->stack->smutex);
} else {
/* This is a helper, we need to use the master's SIP stack */
if(session->master == NULL || session->master->stack == NULL) {
error_code = JANUS_SIP_ERROR_HELPER_ERROR;
g_snprintf(error_cause, 512, "Invalid master SIP stack");
goto error;
}
janus_mutex_lock(&session->master->stack->smutex);
if(session->master->stack->s_nua == NULL) {
janus_mutex_unlock(&session->master->stack->smutex);
JANUS_LOG(LOG_ERR, "NUA destroyed while sending message?\n");
error_code = JANUS_SIP_ERROR_LIBSOFIA_ERROR;
g_snprintf(error_cause, 512, "Invalid NUA");
goto error;
}
nh = nua_handle(session->master->stack->s_nua, session, TAG_END());
janus_mutex_unlock(&session->master->stack->smutex);
}
json_t *request_callid = json_object_get(root, "call_id");
/* Use call-id from the request, if it exists */
if(request_callid) {
message_callid = g_strdup(json_string_value(request_callid));
if(message_callid == NULL) {
JANUS_LOG(LOG_WARN, "Invalid call_id provided, generating a random one\n");
}
}
if(message_callid == NULL) {
/* If call-id does not exist in request, create a random one */
message_callid = g_malloc0(24);
janus_sip_random_string(24, message_callid);
}
nua_message(nh,
SIPTAG_TO_STR(uri_text),
SIPTAG_CONTENT_TYPE_STR(content_type),
SIPTAG_PAYLOAD_STR(msg_content),
NUTAG_PROXY(session->helper && session->master ?
session->master->account.outbound_proxy : session->account.outbound_proxy),
TAG_IF(strlen(custom_headers) > 0, SIPTAG_HEADER_STR(custom_headers)),
SIPTAG_CALL_ID_STR(message_callid),
TAG_END());
}
/* Notify the application */
result = json_object();
json_object_set_new(result, "event", json_string("messagesent"));
json_object_set_new(result, "call_id", json_string(message_callid));
/* Store message id and session */
janus_mutex_lock(&sessions_mutex);
janus_refcount_increase(&session->ref);
g_hash_table_insert(messageids, g_strdup(message_callid), session);
janus_mutex_unlock(&sessions_mutex);
g_free(message_callid);
} else if(!strcasecmp(request_text, "dtmf_info")) {
/* Send DMTF tones using SIP INFO
* (https://tools.ietf.org/html/draft-kaplan-dispatch-info-dtmf-package-00)
*/
if(!janus_sip_call_is_established(session)) {
JANUS_LOG(LOG_ERR, "Wrong state (not established? status=%s)\n", janus_sip_call_status_string(session->status));
g_snprintf(error_cause, 512, "Wrong state (not in a call?)");
goto error;
}
janus_mutex_lock(&session->mutex);
if(session->callee == NULL) {
janus_mutex_unlock(&session->mutex);
JANUS_LOG(LOG_ERR, "Wrong state (no callee?)\n");
error_code = JANUS_SIP_ERROR_WRONG_STATE;
g_snprintf(error_cause, 512, "Wrong state (no callee?)");
goto error;
}
janus_mutex_unlock(&session->mutex);
JANUS_VALIDATE_JSON_OBJECT(root, dtmf_info_parameters,
error_code, error_cause, TRUE,
JANUS_SIP_ERROR_MISSING_ELEMENT, JANUS_SIP_ERROR_INVALID_ELEMENT);
if(error_code != 0)
goto error;
json_t *digit = json_object_get(root, "digit");
const char *digit_text = json_string_value(digit);
if(strlen(digit_text) != 1) {
JANUS_LOG(LOG_ERR, "Invalid element (digit should be one character))\n");
error_code = JANUS_SIP_ERROR_INVALID_ELEMENT;
g_snprintf(error_cause, 512, "Invalid element (digit should be one character)");
goto error;
}
int duration_ms = 0;
json_t *duration = json_object_get(root, "duration");
duration_ms = duration ? json_integer_value(duration) : 0;
if(duration_ms <= 0 || duration_ms > 5000) {
duration_ms = 160; /* default value */
}
char payload[64];
g_snprintf(payload, sizeof(payload), "Signal=%s\r\nDuration=%d", digit_text, duration_ms);
char custom_headers[2048];
janus_sip_parse_custom_headers(root, (char *)&custom_headers, sizeof(custom_headers));
nua_info(session->stack->s_nh_i,
SIPTAG_CONTENT_TYPE_STR("application/dtmf-relay"),
SIPTAG_PAYLOAD_STR(payload),
TAG_IF(strlen(custom_headers) > 0, SIPTAG_HEADER_STR(custom_headers)),
TAG_END());
/* Notify the result */
result = json_object();
json_object_set_new(result, "event", json_string("dtmfsent"));
} else if(!strcasecmp(request_text, "reset")) {
/* Apparently, under some particular circumstances that we haven't
* managed to replicate ourselves yet, it can sometimes happen that
* a janus_sip_session remains with the establishing atomic set to
* 1, even though the last call has been correctly closed. This
* prevents further incoming calls to be established, as the
* plugin automatically answers with a 486 busy thinking another
* call is currently in progress. This "reset" request is here
* to reset the establishing flag back to 0, in case the call
* state is idle but the flag is still set to 1 instead */
if(session->status == janus_sip_call_status_idle) {
g_atomic_int_set(&session->established, 0);
g_atomic_int_set(&session->establishing, 0);
}
/* Notify the result */
result = json_object();
json_object_set_new(result, "event", json_string("reset"));
} else {
JANUS_LOG(LOG_ERR, "Unknown request (%s)\n", request_text);
error_code = JANUS_SIP_ERROR_INVALID_REQUEST;
g_snprintf(error_cause, 512, "Unknown request (%s)", request_text);
goto error;
}
done:
{
/* Prepare JSON event */
json_t *event = json_object();
json_object_set_new(event, "sip", json_string("event"));
if(result != NULL)
json_object_set_new(event, "result", result);
json_object_set_new(event, "call_id", json_string(session->callid));
int ret = gateway->push_event(msg->handle, &janus_sip_plugin, msg->transaction, event, NULL);
JANUS_LOG(LOG_VERB, " >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(event);
janus_sip_message_free(msg);
continue;
}
error:
{
/* Prepare JSON error event */
json_t *event = json_object();
json_object_set_new(event, "sip", json_string("event"));
json_object_set_new(event, "error_code", json_integer(error_code));
json_object_set_new(event, "error", json_string(error_cause));
json_object_set_new(event, "call_id", json_string(session->callid));
int ret = gateway->push_event(msg->handle, &janus_sip_plugin, msg->transaction, event, NULL);
JANUS_LOG(LOG_VERB, " >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(event);
janus_sip_message_free(msg);
}
}
JANUS_LOG(LOG_VERB, "Leaving SIP handler thread\n");
return NULL;
}
/* Sofia callbacks */
void janus_sip_sofia_callback(nua_event_t event, int status, char const *phrase, nua_t *nua, nua_magic_t *magic, nua_handle_t *nh, nua_hmagic_t *hmagic, sip_t const *sip, tagi_t tags[])
{
janus_sip_session *session = (janus_sip_session *)(hmagic ? hmagic : magic);
ssip_t *ssip = session->stack;
/* Notify event handlers about the content of the whole incoming SIP message, if any */
if(notify_events && gateway->events_is_enabled() && ssip) {
/* Print the incoming message */
size_t msg_size = 0;
msg_t *msg = nua_current_request(nua);
if(msg) {
char *msg_str = msg_as_string(ssip->s_home, msg, NULL, 0, &msg_size);
json_t *info = json_object();
json_object_set_new(info, "event", json_string("sip-in"));
json_object_set_new(info, "sip", json_string(msg_str));
gateway->notify_event(&janus_sip_plugin, session->handle, info);
su_free(ssip->s_home, msg_str);
}
}
switch (event) {
/* Status or Error Indications */
case nua_i_active:
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
break;
case nua_i_error:
JANUS_LOG(LOG_WARN, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
break;
case nua_i_fork:
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
break;
case nua_i_media_error:
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
break;
case nua_i_subscription:
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
break;
case nua_i_state:;
tagi_t const *ti = tl_find(tags, nutag_callstate);
enum nua_callstate callstate = ti ? ti->t_value : -1;
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s, call state [%s]\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??", nua_callstate_name(callstate));
/* There are several call states, but we care about the terminated state in order to send the 'hangup' event
* and the proceeding state in order to send the 'proceeding' event so the client can play a ringback tone for
* the user since we don't send early media. (assuming this is the right session, of course).
* http://sofia-sip.sourceforge.net/refdocs/nua/nua__tag_8h.html#a516dc237722dc8ca4f4aa3524b2b444b
*/
if(callstate == nua_callstate_proceeding &&
(session->stack->s_nh_i == nh || session->stack->s_nh_i == NULL)) {
json_t *call = json_object();
json_object_set_new(call, "sip", json_string("event"));
json_t *calling = json_object();
json_object_set_new(calling, "event", json_string("proceeding"));
json_object_set_new(calling, "code", json_integer(status));
json_object_set_new(call, "result", calling);
json_object_set_new(call, "call_id", json_string(session->callid));
int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, call, NULL);
JANUS_LOG(LOG_VERB, " >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(call);
/* Also notify event handlers */
if(notify_events && gateway->events_is_enabled()) {
json_t *info = json_object();
json_object_set_new(info, "event", json_string("proceeding"));
if(session->callid)
json_object_set_new(info, "call-id", json_string(session->callid));
json_object_set_new(info, "code", json_integer(status));
gateway->notify_event(&janus_sip_plugin, session->handle, info);
}
} else if(callstate == nua_callstate_terminated &&
(session->stack->s_nh_i == nh || session->stack->s_nh_i == NULL)) {
session->media.earlymedia = FALSE;
session->media.update = FALSE;
session->media.autoaccept_reinvites = TRUE;
session->media.ready = FALSE;
session->media.on_hold = FALSE;
janus_sip_call_update_status(session, janus_sip_call_status_idle);
session->stack->s_nh_i = NULL;
json_t *call = json_object();
json_object_set_new(call, "sip", json_string("event"));
json_t *calling = json_object();
json_object_set_new(calling, "event", json_string("hangup"));
json_object_set_new(calling, "code", json_integer(status));
json_object_set_new(calling, "reason", json_string(phrase ? phrase : ""));
if(session->hangup_reason_header)
json_object_set_new(calling, "reason_header", json_string(session->hangup_reason_header));
if(session->hangup_reason_header_protocol)
json_object_set_new(calling, "reason_header_protocol", json_string(session->hangup_reason_header_protocol));
if(session->hangup_reason_header_cause)
json_object_set_new(calling, "reason_header_cause", json_string(session->hangup_reason_header_cause));
json_object_set_new(call, "result", calling);
json_object_set_new(call, "call_id", json_string(session->callid));
int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, call, NULL);
JANUS_LOG(LOG_VERB, " >> Pushing event: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(call);
/* Also notify event handlers */
if(notify_events && gateway->events_is_enabled()) {
json_t *info = json_object();
json_object_set_new(info, "event", json_string("hangup"));
if(session->callid)
json_object_set_new(info, "call-id", json_string(session->callid));
json_object_set_new(info, "code", json_integer(status));
if(phrase)
json_object_set_new(info, "reason", json_string(phrase));
if(session->hangup_reason_header)
json_object_set_new(info, "reason_header", json_string(session->hangup_reason_header));
if(session->hangup_reason_header_protocol)
json_object_set_new(info, "reason_header_protocol", json_string(session->hangup_reason_header_protocol));
if(session->hangup_reason_header_cause)
json_object_set_new(info, "reason_header_cause", json_string(session->hangup_reason_header_cause));
gateway->notify_event(&janus_sip_plugin, session->handle, info);
}
/* Get rid of any PeerConnection that may have been set up */
janus_mutex_lock(&sessions_mutex);
if(session->callid) {
g_hash_table_remove(callids, session->callid);
g_free(session->callid);
session->callid = NULL;
}
janus_mutex_unlock(&sessions_mutex);
g_free(session->transaction);
session->transaction = NULL;
g_free(session->hangup_reason_header);
g_free(session->hangup_reason_header_protocol);
g_free(session->hangup_reason_header_cause);
session->hangup_reason_header = NULL;
session->hangup_reason_header_protocol = NULL;
session->hangup_reason_header_cause = NULL;
if(g_atomic_int_get(&session->establishing) || g_atomic_int_get(&session->established)) {
if(session->media.has_audio || session->media.has_video) {
/* Get rid of the PeerConnection in the core */
gateway->close_pc(session->handle);
} else {
/* No SDP was exchanged, just clean up locally */
janus_sip_hangup_media_internal(session->handle);
}
}
} else if(session->stack->s_nh_i == nh && callstate == nua_callstate_calling && session->status == janus_sip_call_status_incall) {
/* Have just sent re-INVITE */
janus_sip_call_update_status(session, janus_sip_call_status_incall_reinviting);
} else if(session->stack->s_nh_i == nh && callstate == nua_callstate_ready &&
(session->status == janus_sip_call_status_incall_reinviting || session->status == janus_sip_call_status_incall_reinvited)) {
/* Clear re-INVITE progress status */
janus_sip_call_update_status(session, janus_sip_call_status_incall);
}
break;
case nua_i_terminated: {
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
/* We had a reference to this session for this call, get rid of it */
janus_sip_unref_active_call(session);
break;
}
/* SIP requests */
case nua_i_ack: {
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
/* We're only interested in this when there's been an offerless INVITE, as here's where we'd get our answer */
if(sip->sip_payload && sip->sip_payload->pl_data) {
JANUS_LOG(LOG_VERB, "This ACK contains a payload, probably as a result of an offerless INVITE: simulating 200 OK...\n");
janus_sip_sofia_callback(nua_r_invite, 700, "ACK", nua, magic, nh, hmagic, sip, tags);
}
break;
}
case nua_i_outbound:
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
break;
case nua_i_bye: {
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
janus_sip_save_reason(sip, session);
break;
}
case nua_i_cancel: {
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
janus_sip_save_reason(sip, session);
break;
}
case nua_i_invite: {
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
/* Add a reference for this call */
janus_sip_ref_active_call(session);
if(ssip == NULL) {
JANUS_LOG(LOG_ERR, "\tInvalid SIP stack\n");
nua_respond(nh, 500, sip_status_phrase(500), TAG_END());
break;
}
if(sip->sip_from == NULL || sip->sip_to == NULL) {
JANUS_LOG(LOG_ERR, "\tInvalid request (missing From or To)\n");
nua_respond(nh, 400, sip_status_phrase(400), TAG_END());
break;
}
gboolean reinvite = FALSE, busy = FALSE;
if(session->stack->s_nh_i == NULL) {
if(g_atomic_int_get(&session->establishing) || g_atomic_int_get(&session->established) || session->relayer_thread != NULL) {
/* Still busy establishing another call (or maybe still cleaning up the previous call) */
busy = TRUE;
}
} else {
if(session->stack->s_nh_i == nh) {
/* re-INVITE, we'll check what changed later */
reinvite = TRUE;
JANUS_LOG(LOG_VERB, "Got a re-INVITE...\n");
} else if(session->status >= janus_sip_call_status_inviting) {
/* Busy with another call */
busy = TRUE;
}
}
if(busy) {
/* This session is busy, any helper that can take it? */
JANUS_LOG(LOG_VERB, "Busy... maybe a helper can help?\n");
janus_sip_session *helper = NULL;
janus_mutex_lock(&session->mutex);
/* Find a free helper */
GList *temp = session->helpers;
while(temp != NULL) {
helper = (janus_sip_session *)temp->data;
if(helper->stack->s_nh_i == NULL && !g_atomic_int_get(&helper->establishing) &&
!g_atomic_int_get(&helper->established) && helper->relayer_thread == NULL) {
/* Found! */
break;
}
JANUS_LOG(LOG_VERB, " -- Helper %p is busy too...\n", helper);
helper = NULL;
temp = temp->next;
}
janus_mutex_unlock(&session->mutex);
if(helper != NULL) {
/* Bind the call to the helper and handle it there */
JANUS_LOG(LOG_VERB, "Passing INVITE to helper %p\n", helper);
nua_handle_bind(nh, helper);
/* This session won't need the reference anymore, the helper will */
janus_sip_unref_active_call(session);
janus_sip_sofia_callback(event, status, phrase, nua, magic, nh, helper, sip, tags);
break;
}
JANUS_LOG(LOG_VERB, "\tAlready in a call (busy, status=%s)\n", janus_sip_call_status_string(session->status));
nua_respond(nh, 486, sip_status_phrase(486), TAG_END());
/* Notify the web app about the missed invite */
json_t *missed = json_object();
json_object_set_new(missed, "sip", json_string("event"));
json_t *result = json_object();
json_object_set_new(result, "event", json_string("missed_call"));
char *caller_text = url_as_string(session->stack->s_home, sip->sip_from->a_url);
json_object_set_new(result, "caller", json_string(caller_text));
if(sip->sip_from->a_display) {
json_object_set_new(result, "displayname", json_string(sip->sip_from->a_display));
}
char *callee_text = url_as_string(session->stack->s_home, sip->sip_to->a_url);
json_object_set_new(result, "callee", json_string(callee_text));
json_object_set_new(missed, "result", result);
json_object_set_new(missed, "call_id", json_string(sip->sip_call_id->i_id));
int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, missed, NULL);
JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(missed);
/* Also notify event handlers */
if(notify_events && gateway->events_is_enabled()) {
json_t *info = json_object();
json_object_set_new(info, "event", json_string("missed_call"));
json_object_set_new(info, "caller", json_string(caller_text));
json_object_set_new(info, "callee", json_string(callee_text));
gateway->notify_event(&janus_sip_plugin, session->handle, info);
}
su_free(session->stack->s_home, caller_text);
su_free(session->stack->s_home, callee_text);
break;
}
if(!reinvite) {
g_atomic_int_set(&session->establishing, 1);
} else {
/* This is a re-INVITE, we have a reference already */
janus_sip_unref_active_call(session);
}
/* Check if there's an SDP to process */
janus_sdp *sdp = NULL;
if(!sip->sip_payload) {
JANUS_LOG(LOG_VERB,"Received offerless %s\n", reinvite ? "re-INVITE" : "INVITE");
} else {
char sdperror[100];
sdp = janus_sdp_parse(sip->sip_payload->pl_data, sdperror, sizeof(sdperror));
if(!sdp) {
JANUS_LOG(LOG_ERR, "\tError parsing SDP! %s\n", sdperror);
g_atomic_int_set(&session->establishing, 0);
nua_respond(nh, 488, sip_status_phrase(488), TAG_END());
break;
}
}
if(!reinvite) {
janus_mutex_lock(&session->mutex);
/* New incoming call */
g_free(session->callee);
char *caller_text = url_as_string(session->stack->s_home, sip->sip_from->a_url);
session->callee = g_strdup(caller_text);
janus_mutex_unlock(&session->mutex);
su_free(session->stack->s_home, caller_text);
janus_mutex_lock(&sessions_mutex);
g_free(session->callid);
session->callid = sip && sip->sip_call_id ? g_strdup(sip->sip_call_id->i_id) : NULL;
if(session->callid)
g_hash_table_insert(callids, session->callid, session);
janus_mutex_unlock(&sessions_mutex);
janus_sip_call_update_status(session, janus_sip_call_status_invited);
/* Clean up SRTP stuff from before first, in case it's still needed */
janus_sip_srtp_cleanup(session);
}
/* Parse SDP */
JANUS_LOG(LOG_VERB, "Someone is %s a call:\n%s",
reinvite ? "updating" : "inviting us in",
sip->sip_payload ? sip->sip_payload->pl_data : "(no SDP)");
gboolean changed = FALSE;
if(sdp) {
janus_sip_sdp_process(session, sdp, FALSE, reinvite, &changed);
/* Check if offer has neither audio nor video, fail with 488 */
if(!session->media.has_audio && !session->media.has_video) {
g_atomic_int_set(&session->establishing, 0);
nua_respond(nh, 488, sip_status_phrase(488), TAG_END());
janus_sdp_destroy(sdp);
break;
}
/* Also fail with 488 if there's no remote IP addresses that can be used for RTP */
if(!session->media.remote_audio_ip && !session->media.remote_video_ip) {
g_atomic_int_set(&session->establishing, 0);
nua_respond(nh, 488, sip_status_phrase(488), TAG_END());
janus_sdp_destroy(sdp);
break;
}
}
if(reinvite && session->media.autoaccept_reinvites) {
/* No need to involve the application: we reply ourselves */
nua_respond(nh, 200, sip_status_phrase(200), TAG_END());
janus_sdp_destroy(sdp);
break;
}
/* Check if there's an isfocus feature parameter in the Contact header */
gboolean is_focus = FALSE;
if(sip->sip_contact && sip->sip_contact->m_params) {
int i=0;
for(i=0; sip->sip_contact->m_params[i]; i++) {
if(!strcasecmp(sip->sip_contact->m_params[i], "isfocus")) {
/* The peer is a conference bridge */
is_focus = TRUE;
break;
}
}
}
/* If this is a re-INVITE, take note of that */
if(reinvite) {
session->media.update = TRUE;
/* Mark status as janus_sip_call_status_incall_reinvited only when handling reinvites ourselves*/
janus_sip_call_update_status(session, janus_sip_call_status_incall_reinvited);
}
/* Notify the application about the new incoming call or re-INVITE */
json_t *jsep = NULL;
if(sdp)
jsep = json_pack("{ssss}", "type", "offer", "sdp", sip->sip_payload->pl_data);
json_t *call = json_object();
json_object_set_new(call, "sip", json_string("event"));
json_t *calling = json_object();
json_object_set_new(calling, "event", json_string(reinvite ? "updatingcall" : "incomingcall"));
json_object_set_new(calling, "username", json_string(session->callee));
if(session->callid)
json_object_set_new(calling, "call_id", json_string(session->callid));
if(sip->sip_from->a_display) {
json_object_set_new(calling, "displayname", json_string(sip->sip_from->a_display));
}
char *callee_text = url_as_string(session->stack->s_home, sip->sip_to->a_url);
json_object_set_new(calling, "callee", json_string(callee_text));
if(session->incoming_header_prefixes) {
json_t *headers = janus_sip_get_incoming_headers(sip, session);
json_object_set_new(calling, "headers", headers);
}
char *referred_by = NULL;
if(sip->sip_referred_by) {
char *rby_text = sip_header_as_string(session->stack->s_home, (const sip_header_t *)sip->sip_referred_by);
referred_by = g_strdup(rby_text);
su_free(session->stack->s_home, rby_text);
json_object_set_new(calling, "referred_by", json_string(referred_by));
}
if(sip->sip_replaces && sip->sip_replaces->rp_call_id) {
json_object_set_new(calling, "replaces", json_string(sip->sip_replaces->rp_call_id));
}
if(is_focus)
json_object_set_new(calling, "isfocus", json_true());
if(sdp && (session->media.has_srtp_remote_audio || session->media.has_srtp_remote_video)) {
/* FIXME Maybe a true/false instead? */
json_object_set_new(calling, "srtp", json_string(session->media.require_srtp ? "sdes_mandatory" : "sdes_optional"));
}
json_object_set_new(call, "result", calling);
json_object_set_new(call, "call_id", json_string(session->callid));
int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, call, jsep);
JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(call);
if(jsep)
json_decref(jsep);
janus_sdp_destroy(sdp);
/* Also notify event handlers */
if(notify_events && gateway->events_is_enabled()) {
json_t *info = json_object();
json_object_set_new(info, "event", json_string(reinvite ? "updatingcall" : "incomingcall"));
if(session->callid)
json_object_set_new(info, "call-id", json_string(session->callid));
json_object_set_new(info, "username", json_string(session->callee));
if(sip->sip_from->a_display)
json_object_set_new(info, "displayname", json_string(sip->sip_from->a_display));
json_object_set_new(info, "callee", json_string(callee_text));
if(referred_by)
json_object_set_new(info, "referred_by", json_string(referred_by));
gateway->notify_event(&janus_sip_plugin, session->handle, info);
}
su_free(session->stack->s_home, callee_text);
g_free(referred_by);
if(!reinvite) {
/* Send a Ringing back */
nua_respond(nh, 180, sip_status_phrase(180), TAG_END());
session->stack->s_nh_i = nh;
}
break;
}
case nua_i_refer: {
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
/* We're being asked to transfer a call */
if(sip == NULL || sip->sip_refer_to == NULL) {
JANUS_LOG(LOG_ERR, "Missing Refer-To header\n");
nua_respond(nh, 400, sip_status_phrase(400), TAG_END());
break;
}
/* Access the headers we need */
char *refer_to = NULL, *referred_by = NULL, *custom_headers = NULL, *replaces = NULL;
const char *url_headers = sip->sip_refer_to->r_url->url_headers;
if(url_headers != NULL) {
/* Convert to SIP headers */
sip->sip_refer_to->r_url->url_headers = NULL;
custom_headers = url_query_as_header_string(session->stack->s_home, url_headers);
/* FIXME Look for the "replaces" part, to extract the call-id */
char *start = strstr(custom_headers, "replaces:");
if(start != NULL) {
start += strlen("replaces:");
char *end = strchr(start, ';');
if(end != NULL) {
/* Found */
*end = '\0';
replaces = g_strdup(start);
*end = ';';
}
}
}
refer_to = url_as_string(session->stack->s_home, sip->sip_refer_to->r_url);
sip->sip_refer_to->r_url->url_headers = url_headers;
if(sip->sip_referred_by != NULL)
referred_by = sip_header_as_string(session->stack->s_home, (const sip_header_t *)sip->sip_referred_by);
else if(sip->sip_from != NULL)
referred_by = url_as_string(session->stack->s_home, sip->sip_from->a_url);
JANUS_LOG(LOG_VERB, "Incoming REFER: %s (by %s, headers: %s)\n",
refer_to, referred_by ? referred_by : "unknown", custom_headers ? custom_headers : "unknown");
/* Send a 202 back */
nua_respond(nh, 202, sip_status_phrase(202), NUTAG_WITH_CURRENT(nua), TAG_END());
JANUS_LOG(LOG_VERB, "[%p] 202\n", nh);
/* Take note of the session and NUA handle we got the REFER from (for NOTIFY) */
janus_mutex_lock(&sessions_mutex);
guint32 refer_id = 0;
while(refer_id == 0) {
refer_id = janus_random_uint32();
if(g_hash_table_lookup(transfers, GUINT_TO_POINTER(refer_id)) != NULL) {
refer_id = 0;
continue;
}
janus_sip_transfer *t = g_malloc(sizeof(janus_sip_transfer));
janus_refcount_increase(&session->ref);
t->session = session;
t->referred_by = referred_by ? g_strdup(referred_by) : NULL;
t->custom_headers = custom_headers ? g_strdup(custom_headers) : NULL;
t->nh_s = nh;
nua_save_event(nua, t->saved);
g_hash_table_insert(transfers, GUINT_TO_POINTER(refer_id), t);
}
janus_mutex_unlock(&sessions_mutex);
/* Notify the application */
json_t *info = json_object();
json_object_set_new(info, "sip", json_string("event"));
json_t *result = json_object();
json_object_set_new(result, "event", json_string("transfer"));
json_object_set_new(result, "refer_id", json_integer(refer_id));
json_object_set_new(result, "refer_to", json_string(refer_to));
if(referred_by != NULL) {
json_object_set_new(result, "referred_by", json_string(referred_by));
su_free(session->stack->s_home, referred_by);
}
if(replaces != NULL) {
json_object_set_new(result, "replaces", json_string(replaces));
g_free(replaces);
}
if(session->incoming_header_prefixes) {
json_t *headers = janus_sip_get_incoming_headers(sip, session);
json_object_set_new(result, "headers", headers);
}
su_free(session->stack->s_home, refer_to);
if(custom_headers != NULL)
su_free(session->stack->s_home, custom_headers);
json_object_set_new(info, "result", result);
int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, info, NULL);
JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(info);
break;
}
case nua_i_info: {
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
/* We expect a payload */
if(!sip->sip_content_type || !sip->sip_content_type->c_type || !sip->sip_payload || !sip->sip_payload->pl_data) {
return;
}
const char *type = sip->sip_content_type->c_type;
char *payload = sip->sip_payload->pl_data;
/* Notify the application */
json_t *info = json_object();
json_object_set_new(info, "sip", json_string("event"));
json_t *result = json_object();
json_object_set_new(result, "event", json_string("info"));
char *caller_text = url_as_string(session->stack->s_home, sip->sip_from->a_url);
json_object_set_new(result, "sender", json_string(caller_text));
su_free(session->stack->s_home, caller_text);
if(sip->sip_from && sip->sip_from->a_display && strlen(sip->sip_from->a_display) > 0) {
json_object_set_new(result, "displayname", json_string(sip->sip_from->a_display));
}
json_object_set_new(result, "type", json_string(type));
json_object_set_new(result, "content", json_string(payload));
if(session->incoming_header_prefixes) {
json_t *headers = janus_sip_get_incoming_headers(sip, session);
json_object_set_new(result, "headers", headers);
}
if(session->callid)
json_object_set_new(info, "call_id", json_string(session->callid));
json_object_set_new(info, "result", result);
int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, info, NULL);
JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(info);
break;
}
case nua_i_message: {
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
/* We expect a payload */
if(!sip->sip_content_type || !sip->sip_content_type->c_type || !sip->sip_payload || !sip->sip_payload->pl_data) {
return;
}
const char *content_type = sip->sip_content_type->c_type;
char *payload = sip->sip_payload->pl_data;
/* Notify the application */
json_t *message = json_object();
json_object_set_new(message, "sip", json_string("event"));
json_t *result = json_object();
json_object_set_new(result, "event", json_string("message"));
char *caller_text = url_as_string(session->stack->s_home, sip->sip_from->a_url);
json_object_set_new(result, "sender", json_string(caller_text));
su_free(session->stack->s_home, caller_text);
if(sip->sip_from && sip->sip_from->a_display && strlen(sip->sip_from->a_display) > 0) {
json_object_set_new(result, "displayname", json_string(sip->sip_from->a_display));
}
json_object_set_new(result, "content", json_string(payload));
if(session->incoming_header_prefixes) {
json_t *headers = janus_sip_get_incoming_headers(sip, session);
json_object_set_new(result, "headers", headers);
}
if(session->callid)
json_object_set_new(message, "call_id", json_string(session->callid));
json_object_set_new(result, "content_type", json_string(content_type));
json_object_set_new(message, "result", result);
int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, message, NULL);
JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(message);
break;
}
case nua_i_notify: {
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
/* We expect a payload */
if(!sip) {
/* No SIP message? Maybe an internal message? */
return;
}
if(!sip->sip_payload || !sip->sip_payload->pl_data) {
/* Send a 200 back and ignore the message */
nua_respond(nh, 200, sip_status_phrase(200), TAG_END());
return;
}
/* Notify the application */
json_t *notify = json_object();
json_object_set_new(notify, "sip", json_string("event"));
json_object_set_new(notify, "call_id", json_string(sip->sip_call_id->i_id));
json_t *result = json_object();
json_object_set_new(result, "event", json_string("notify"));
if(sip->sip_event != NULL)
json_object_set_new(result, "notify", json_string(sip->sip_event->o_type));
const tagi_t *t = tl_find(tags, nutag_substate);
if(t != NULL) {
enum nua_substate substate = (enum nua_substate)(t->t_value);
json_object_set_new(result, "substate", json_string(nua_substate_name(substate)));
}
if(sip->sip_content_type != NULL)
json_object_set_new(result, "content-type", json_string(sip->sip_content_type->c_type));
json_object_set_new(result, "content", json_string(sip->sip_payload->pl_data));
if(session->incoming_header_prefixes) {
json_t *headers = janus_sip_get_incoming_headers(sip, session);
json_object_set_new(result, "headers", headers);
}
json_object_set_new(notify, "result", result);
int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, notify, NULL);
JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(notify);
break;
}
case nua_i_options:
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
/* Stack responds automatically to OPTIONS request unless OPTIONS is
* included in the set of application methods, set by NUTAG_APPL_METHOD(). */
break;
/* Responses */
case nua_r_get_params:
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
const tagi_t* from = NULL;
if((status != 200) || ((from = tl_find(tags, siptag_from_str)) == NULL)) {
JANUS_LOG(LOG_WARN, "Unable to find 'siptag_from_str' among all the tags\n");
break;
}
const char *from_value = (const char *)from->t_value;
if(from_value == NULL || strlen(from_value) < 2) {
JANUS_LOG(LOG_WARN, "Invalid 'siptag_from_str' value '%s'\n", from_value);
break;
}
JANUS_LOG(LOG_VERB, "'siptag_from_str': %s\n", from_value);
g_free(ssip->contact_header);
ssip->contact_header = g_strdup(from_value);
break;
case nua_r_set_params:
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
break;
case nua_r_notifier:
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
break;
case nua_r_shutdown:
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
if(status < 200 && !g_atomic_int_get(&stopping)) {
/* shutdown in progress -> return */
break;
}
if(status >= 200 && ssip != NULL) {
/* Check if this session (and/or its helpers) had dangling
* references for ongoing calls: we won't receive other events
* after this, so it's up to us to clean up after ourselves */
janus_mutex_lock(&session->mutex);
while(session->active_calls) {
janus_sip_session *s = (janus_sip_session *)session->active_calls->data;
if(s != NULL) {
JANUS_LOG(LOG_VERB, "[%p] Removing reference\n", s);
janus_refcount_decrease(&s->ref);
}
session->active_calls = g_list_remove(session->active_calls, s);
}
janus_mutex_unlock(&session->mutex);
/* End the event loop: su_root_run() will return */
su_root_break(ssip->s_root);
}
break;
case nua_r_terminate:
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
break;
/* SIP responses */
case nua_r_bye:
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
break;
case nua_r_cancel:
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
break;
case nua_r_info:
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
/* FIXME Should we notify the user, in case the SIP INFO returned an error? */
break;
case nua_r_message:
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
/* Handle authentication for SIP MESSAGE - eg. SippySoft Softswitch requires 401 authentication even if SIP user is registered */
if(status == 401 || status == 407) {
const char *scheme = NULL;
const char *realm = NULL;
if(status == 401) {
/* Get scheme/realm from 401 error */
sip_www_authenticate_t const* www_auth = sip->sip_www_authenticate;
if(www_auth == NULL)
break;
scheme = www_auth->au_scheme;
realm = msg_params_find(www_auth->au_params, "realm=");
} else {
/* Get scheme/realm from 407 error, proxy-auth */
sip_proxy_authenticate_t const* proxy_auth = sip->sip_proxy_authenticate;
if(proxy_auth == NULL)
break;
scheme = proxy_auth->au_scheme;
realm = msg_params_find(proxy_auth->au_params, "realm=");
}
char authuser[100], secret[100];
memset(authuser, 0, sizeof(authuser));
memset(secret, 0, sizeof(secret));
if(session->helper) {
/* This is an helper session, we'll need the credentials from the master */
if(session->master == NULL) {
JANUS_LOG(LOG_WARN, "No master session for this helper, authentication will fail...\n");
} else {
session = session->master;
}
}
if(session->account.authuser && strchr(session->account.authuser, ':')) {
/* The authuser contains a colon: wrap it in quotes */
g_snprintf(authuser, sizeof(authuser), "\"%s\"", session->account.authuser);
} else {
g_snprintf(authuser, sizeof(authuser), "%s", session->account.authuser);
}
if(session->account.secret && strchr(session->account.secret, ':')) {
/* The secret contains a colon: wrap it in quotes */
g_snprintf(secret, sizeof(secret), "\"%s\"", session->account.secret);
} else {
g_snprintf(secret, sizeof(secret), "%s", session->account.secret);
}
char auth[256];
memset(auth, 0, sizeof(auth));
g_snprintf(auth, sizeof(auth), "%s%s:%s:%s:%s%s",
session->account.secret_type == janus_sip_secret_type_hashed ? "HA1+" : "",
scheme,
realm,
authuser,
session->account.secret_type == janus_sip_secret_type_hashed ? "HA1+" : "",
secret);
JANUS_LOG(LOG_VERB, "\t%s\n", auth);
/* Authenticate */
nua_authenticate(nh,
NUTAG_AUTH(auth),
TAG_END());
} else {
char *messageid = g_strdup(sip->sip_call_id->i_id);
/* Find session associated with the message */
janus_mutex_lock(&sessions_mutex);
janus_sip_session *message_session = g_hash_table_lookup(messageids, messageid);
if (!message_session) {
message_session = session;
JANUS_LOG(LOG_VERB, "Message (%s) not associated with any session, event will be reported to master\n", messageid);
}
janus_mutex_unlock(&sessions_mutex);
/* MESSAGE response, notify the application */
json_t *result = json_object();
/* SIP code and reason */
json_object_set_new(result, "event", json_string("messagedelivery"));
json_object_set_new(result, "code", json_integer(status));
json_object_set_new(result, "reason", json_string(phrase));
/* Build the delivery receipt */
json_t *dr = json_object();
json_object_set_new(dr, "sip", json_string("event"));
json_object_set_new(dr, "result", result);
json_object_set_new(dr, "call_id", json_string(messageid));
/* Report delivery */
int ret = gateway->push_event(message_session->handle, &janus_sip_plugin, message_session->transaction, dr, NULL);
JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(dr);
janus_mutex_lock(&sessions_mutex);
g_hash_table_remove(messageids, messageid);
janus_mutex_unlock(&sessions_mutex);
g_free(messageid);
}
break;
case nua_r_refer: {
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
/* We got a response to our REFER */
JANUS_LOG(LOG_VERB, "Response to REFER received\n");
break;
}
case nua_r_invite: {
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
/* If this INVITE was triggered by a REFER, notify the transferer */
if(session->refer_id > 0) {
janus_mutex_lock(&sessions_mutex);
janus_sip_transfer *transfer = g_hash_table_lookup(transfers, GUINT_TO_POINTER(session->refer_id));
janus_mutex_unlock(&sessions_mutex);
if(transfer != NULL && transfer->nh_s != NULL) {
/* Send a NOTIFY */
char content[100];
g_snprintf(content, sizeof(content), "SIP/2.0 %d %s", status, phrase);
nua_notify(transfer->nh_s,
NUTAG_SUBSTATE(nua_substate_active),
SIPTAG_CONTENT_TYPE_STR("message/sipfrag"),
SIPTAG_PAYLOAD_STR(content),
TAG_END());
}
}
gboolean in_progress = FALSE;
if(status < 200) {
/* Not ready yet, either notify the user (e.g., "ringing") or handle early media */
if(status == 180 || status == 183) {
/* If's a Session Progress: check if there's an SDP, and if so, treat it like a 200 */
if(sip->sip_payload && sip->sip_payload->pl_data) {
in_progress = TRUE;
} else {
/* Ringing, notify the application */
json_t *ringing = json_object();
json_object_set_new(ringing, "sip", json_string("event"));
json_t *result = json_object();
json_object_set_new(result, "event", json_string("ringing"));
if(session->incoming_header_prefixes) {
json_t *headers = janus_sip_get_incoming_headers(sip, session);
json_object_set_new(result, "headers", headers);
}
json_object_set_new(ringing, "result", result);
json_object_set_new(ringing, "call_id", json_string(session->callid));
int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, ringing, NULL);
JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(ringing);
break;
}
} else {
/* Nothing to do, let's wait for a 200 OK */
break;
}
} else if(status == 401 || status == 407) {
janus_sip_save_reason(sip, session);
const char *scheme = NULL;
const char *realm = NULL;
if(status == 401) {
/* Get scheme/realm from 401 error */
sip_www_authenticate_t const* www_auth = sip->sip_www_authenticate;
if(www_auth == NULL)
break;
scheme = www_auth->au_scheme;
realm = msg_params_find(www_auth->au_params, "realm=");
} else {
/* Get scheme/realm from 407 error, proxy-auth */
sip_proxy_authenticate_t const* proxy_auth = sip->sip_proxy_authenticate;
if(proxy_auth == NULL)
break;
scheme = proxy_auth->au_scheme;
realm = msg_params_find(proxy_auth->au_params, "realm=");
}
char authuser[100], secret[100];
memset(authuser, 0, sizeof(authuser));
memset(secret, 0, sizeof(secret));
if(session->helper) {
/* This is an helper session, we'll need the credentials from the master */
if(session->master == NULL) {
JANUS_LOG(LOG_WARN, "No master session for this helper, authentication will fail...\n");
} else {
session = session->master;
}
}
if(session->account.authuser && strchr(session->account.authuser, ':')) {
/* The authuser contains a colon: wrap it in quotes */
g_snprintf(authuser, sizeof(authuser), "\"%s\"", session->account.authuser);
} else {
g_snprintf(authuser, sizeof(authuser), "%s", session->account.authuser);
}
if(session->account.secret && strchr(session->account.secret, ':')) {
/* The secret contains a colon: wrap it in quotes */
g_snprintf(secret, sizeof(secret), "\"%s\"", session->account.secret);
} else {
g_snprintf(secret, sizeof(secret), "%s", session->account.secret);
}
char auth[256];
memset(auth, 0, sizeof(auth));
g_snprintf(auth, sizeof(auth), "%s%s:%s:%s:%s%s",
session->account.secret_type == janus_sip_secret_type_hashed ? "HA1+" : "",
scheme,
realm,
authuser,
session->account.secret_type == janus_sip_secret_type_hashed ? "HA1+" : "",
secret);
JANUS_LOG(LOG_VERB, "\t%s\n", auth);
/* Authenticate */
nua_authenticate(nh,
NUTAG_AUTH(auth),
TAG_END());
break;
} else if(status == 700) {
JANUS_LOG(LOG_VERB, "Handling SDP answer in ACK\n");
} else if(status >= 400 && status != 700) {
janus_sip_save_reason(sip, session);
break;
}
if(ssip == NULL) {
JANUS_LOG(LOG_ERR, "\tInvalid SIP stack\n");
nua_respond(nh, 500, sip_status_phrase(500), TAG_END());
break;
}
if(sip->sip_payload == NULL) {
JANUS_LOG(LOG_ERR, "\tMissing SDP\n");
nua_respond(nh, 488, sip_status_phrase(488), TAG_END());
break;
}
char sdperror[100];
janus_sdp *sdp = janus_sdp_parse(sip->sip_payload->pl_data, sdperror, sizeof(sdperror));
if(!sdp) {
JANUS_LOG(LOG_ERR, "\tError parsing SDP! %s\n", sdperror);
nua_respond(nh, 488, sip_status_phrase(488), TAG_END());
break;
}
/* Send an ACK, if needed */
if(!in_progress) {
char *route = NULL;
sip_record_route_t *srr = sip->sip_record_route;
if(srr != NULL) {
while(srr->r_next != NULL)
srr = srr->r_next;
route = srr ? url_as_string(session->stack->s_home, srr->r_url) : NULL;
}
JANUS_LOG(LOG_VERB, "Sending ACK (route=%s)\n", route ? route : "none");
nua_ack(nh,
TAG_IF(route, NTATAG_DEFAULT_PROXY(route)),
TAG_END());
if(route != NULL)
su_free(session->stack->s_home, route);
}
/* Parse SDP */
JANUS_LOG(LOG_VERB, "Peer accepted our call:\n%s", sip->sip_payload->pl_data);
janus_sip_call_update_status(session, janus_sip_call_status_incall);
char *fixed_sdp = sip->sip_payload->pl_data;
gboolean changed = FALSE;
gboolean update = session->media.ready;
janus_sip_sdp_process(session, sdp, TRUE, update, &changed);
/* If we asked for SRTP and are not getting it, fail */
gboolean has_srtp = TRUE;
if(session->media.has_audio)
has_srtp = (has_srtp && session->media.has_srtp_remote_audio);
if(session->media.has_video)
has_srtp = (has_srtp && session->media.has_srtp_remote_video);
if(session->media.require_srtp && !has_srtp) {
JANUS_LOG(LOG_ERR, "We asked for mandatory SRTP but didn't get any in the reply!\n");
janus_sdp_destroy(sdp);
/* Hangup immediately */
session->media.earlymedia = FALSE;
session->media.update = FALSE;
session->media.autoaccept_reinvites = TRUE;
session->media.ready = FALSE;
session->media.on_hold = FALSE;
janus_sip_call_update_status(session, janus_sip_call_status_closing);
nua_bye(nh, TAG_END());
janus_mutex_lock(&session->mutex);
g_free(session->callee);
session->callee = NULL;
janus_mutex_unlock(&session->mutex);
break;
}
if(!session->media.remote_audio_ip && !session->media.remote_video_ip) {
/* No remote address parsed? Give up */
JANUS_LOG(LOG_ERR, "\tNo remote IP address found for RTP, something's wrong with the SDP!\n");
janus_sdp_destroy(sdp);
/* Hangup immediately */
session->media.earlymedia = FALSE;
session->media.update = FALSE;
session->media.autoaccept_reinvites = TRUE;
session->media.ready = FALSE;
session->media.on_hold = FALSE;
janus_sip_call_update_status(session, janus_sip_call_status_closing);
nua_bye(nh, TAG_END());
janus_mutex_lock(&session->mutex);
g_free(session->callee);
session->callee = NULL;
janus_mutex_unlock(&session->mutex);
break;
}
if(session->media.audio_pt > -1) {
session->media.audio_pt_name = janus_get_codec_from_pt(fixed_sdp, session->media.audio_pt);
JANUS_LOG(LOG_VERB, "Detected audio codec: %d (%s)\n", session->media.audio_pt, session->media.audio_pt_name);
}
if(session->media.video_pt > -1) {
session->media.video_pt_name = janus_get_codec_from_pt(fixed_sdp, session->media.video_pt);
JANUS_LOG(LOG_VERB, "Detected video codec: %d (%s)\n", session->media.video_pt, session->media.video_pt_name);
}
session->media.ready = TRUE; /* FIXME Maybe we need a better way to signal this */
if(update && !session->media.earlymedia && !session->media.update) {
/* Don't push to the application if this is in response to a hold/unhold we sent ourselves */
JANUS_LOG(LOG_VERB, "This is an update to an existing call (possibly in response to hold/unhold)\n");
janus_sdp_destroy(sdp);
break;
}
if(!session->media.earlymedia && !session->media.update) {
GError *error = NULL;
char tname[16];
g_snprintf(tname, sizeof(tname), "siprtp %s", session->account.username);
janus_refcount_increase(&session->ref);
session->relayer_thread = g_thread_try_new(tname, janus_sip_relay_thread, session, &error);
if(error != NULL) {
session->relayer_thread = NULL;
session->media.ready = FALSE;
janus_refcount_decrease(&session->ref);
JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the RTP/RTCP thread...\n",
error->code, error->message ? error->message : "??");
g_error_free(error);
}
}
/* Check if there's an isfocus feature parameter in the Contact header */
gboolean is_focus = FALSE;
if(sip->sip_contact && sip->sip_contact->m_params) {
int i=0;
for(i=0; sip->sip_contact->m_params[i]; i++) {
if(!strcasecmp(sip->sip_contact->m_params[i], "isfocus")) {
/* The peer is a conference bridge */
is_focus = TRUE;
break;
}
}
}
/* Send event back to the application */
json_t *jsep = NULL;
if(!session->media.earlymedia) {
jsep = json_pack("{ssss}", "type", "answer", "sdp", fixed_sdp);
} else {
/* We've received the 200 OK after the 183, we can remove the flag now */
session->media.earlymedia = FALSE;
}
if(in_progress) {
/* If we just received the 183, set the flag instead so that we can handle the 200 OK differently */
session->media.earlymedia = TRUE;
}
json_t *call = json_object();
json_object_set_new(call, "sip", json_string("event"));
json_t *calling = json_object();
json_object_set_new(calling, "event", json_string(in_progress ? "progress" : "accepted"));
json_object_set_new(calling, "username", json_string(session->callee));
if(is_focus)
json_object_set_new(calling, "isfocus", json_true());
if(session->incoming_header_prefixes) {
json_t *headers = janus_sip_get_incoming_headers(sip, session);
json_object_set_new(calling, "headers", headers);
}
json_object_set_new(call, "result", calling);
json_object_set_new(call, "call_id", json_string(session->callid));
int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, call, jsep);
JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(call);
json_decref(jsep);
janus_sdp_destroy(sdp);
/* Also notify event handlers */
if(!session->media.update && notify_events && gateway->events_is_enabled()) {
json_t *info = json_object();
json_object_set_new(info, "event", json_string(in_progress ? "progress" : "accepted"));
if(session->callid)
json_object_set_new(info, "call-id", json_string(session->callid));
json_object_set_new(info, "username", json_string(session->callee));
gateway->notify_event(&janus_sip_plugin, session->handle, info);
}
if(session->media.update) {
/* We just received a 200 OK to an update we sent */
session->media.update = FALSE;
}
break;
}
case nua_r_register:
case nua_r_unregister: {
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
if(status == 200) {
if(event == nua_r_register) {
if(session->account.registration_status < janus_sip_registration_status_registered)
session->account.registration_status = janus_sip_registration_status_registered;
} else {
session->account.registration_status = janus_sip_registration_status_unregistered;
}
const char *event_name = (event == nua_r_register ? "registered" : "unregistered");
JANUS_LOG(LOG_VERB, "Successfully %s\n", event_name);
/* Notify the application */
json_t *reg = json_object();
json_object_set_new(reg, "sip", json_string("event"));
json_t *reging = json_object();
json_object_set_new(reging, "event", json_string(event_name));
json_object_set_new(reging, "username", json_string(session->account.username));
if(event == nua_r_register) {
json_object_set_new(reging, "register_sent", json_true());
json_object_set_new(reging, "master_id", json_integer(session->master_id));
}
if(session->incoming_header_prefixes) {
json_t *headers = janus_sip_get_incoming_headers(sip, session);
json_object_set_new(reging, "headers", headers);
}
json_object_set_new(reg, "result", reging);
int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, reg, NULL);
JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(reg);
/* If we unregistered and this session had helpers, get rid of them */
if(event == nua_r_unregister) {
janus_mutex_lock(&session->mutex);
GList *temp = NULL;
while(session->helpers != NULL) {
temp = session->helpers;
session->helpers = g_list_remove_link(session->helpers, temp);
janus_sip_session *helper = (janus_sip_session *)temp->data;
if(helper != NULL && helper->handle != NULL) {
/* Get rid of this helper */
janus_refcount_decrease(&session->ref);
janus_refcount_decrease(&helper->ref);
gateway->end_session(helper->handle);
}
g_list_free(temp);
}
janus_mutex_unlock(&session->mutex);
}
/* Also notify event handlers */
if(notify_events && gateway->events_is_enabled()) {
json_t *info = json_object();
json_object_set_new(info, "event", json_string(event_name));
json_object_set_new(info, "identity", json_string(session->account.identity));
if(session->account.proxy)
json_object_set_new(info, "proxy", json_string(session->account.proxy));
gateway->notify_event(&janus_sip_plugin, session->handle, info);
}
} else if(status == 401 || status == 407) {
const char *scheme = NULL;
const char *realm = NULL;
if(status == 401) {
/* Get scheme/realm from 401 error */
sip_www_authenticate_t const* www_auth = sip->sip_www_authenticate;
if(www_auth == NULL) {
/* No WWW-Authenticate header, give up */
goto auth_failed;
}
scheme = www_auth->au_scheme;
realm = msg_params_find(www_auth->au_params, "realm=");
} else {
/* Get scheme/realm from 407 error, proxy-auth */
sip_proxy_authenticate_t const* proxy_auth = sip->sip_proxy_authenticate;
if(proxy_auth == NULL) {
/* No Proxy-Authenticate header, give up */
goto auth_failed;
}
scheme = proxy_auth->au_scheme;
realm = msg_params_find(proxy_auth->au_params, "realm=");
}
char authuser[100], secret[100];
memset(authuser, 0, sizeof(authuser));
memset(secret, 0, sizeof(secret));
if(session->account.authuser && strchr(session->account.authuser, ':')) {
/* The authuser contains a colon: wrap it in quotes */
g_snprintf(authuser, sizeof(authuser), "\"%s\"", session->account.authuser);
} else {
g_snprintf(authuser, sizeof(authuser), "%s", session->account.authuser);
}
if(session->account.secret && strchr(session->account.secret, ':')) {
/* The secret contains a colon: wrap it in quotes */
g_snprintf(secret, sizeof(secret), "\"%s\"", session->account.secret);
} else {
g_snprintf(secret, sizeof(secret), "%s", session->account.secret);
}
char auth[256];
memset(auth, 0, sizeof(auth));
g_snprintf(auth, sizeof(auth), "%s%s:%s:%s:%s%s",
session->account.secret_type == janus_sip_secret_type_hashed ? "HA1+" : "",
scheme,
realm,
authuser,
session->account.secret_type == janus_sip_secret_type_hashed ? "HA1+" : "",
secret);
JANUS_LOG(LOG_VERB, "\t%s\n", auth);
/* Authenticate */
nua_authenticate(nh,
NUTAG_AUTH(auth),
TAG_END());
} else if(status >= 400) {
auth_failed:
/* Authentication failed? */
session->account.registration_status = janus_sip_registration_status_failed;
/* Cleanup registration values */
if(session->account.identity != NULL) {
janus_mutex_lock(&sessions_mutex);
g_hash_table_remove(identities, session->account.identity);
janus_mutex_unlock(&sessions_mutex);
g_free(session->account.identity);
}
session->account.identity = NULL;
session->account.force_udp = FALSE;
session->account.force_tcp = FALSE;
session->account.sips = FALSE;
session->account.rfc2543_cancel = FALSE;
if(session->account.username != NULL)
g_free(session->account.username);
session->account.username = NULL;
if(session->account.display_name != NULL)
g_free(session->account.display_name);
session->account.display_name = NULL;
if(session->account.authuser != NULL)
g_free(session->account.authuser);
session->account.authuser = NULL;
if(session->account.secret != NULL)
g_free(session->account.secret);
session->account.secret = NULL;
session->account.secret_type = janus_sip_secret_type_unknown;
if(session->account.proxy != NULL)
g_free(session->account.proxy);
session->account.proxy = NULL;
if(session->account.outbound_proxy != NULL)
g_free(session->account.outbound_proxy);
session->account.outbound_proxy = NULL;
if(session->account.user_agent != NULL)
g_free(session->account.user_agent);
session->account.user_agent = NULL;
session->account.registration_status = janus_sip_registration_status_unregistered;
/* Tell the application... */
json_t *event = json_object();
json_object_set_new(event, "sip", json_string("event"));
json_t *result = json_object();
json_object_set_new(result, "event", json_string("registration_failed"));
json_object_set_new(result, "code", json_integer(status));
json_object_set_new(result, "reason", json_string(phrase ? phrase : ""));
if(session->incoming_header_prefixes) {
json_t *headers = janus_sip_get_incoming_headers(sip, session);
json_object_set_new(result, "headers", headers);
}
json_object_set_new(event, "result", result);
int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, event, NULL);
JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(event);
/* Also notify event handlers */
if(notify_events && gateway->events_is_enabled()) {
json_t *info = json_object();
json_object_set_new(info, "event", json_string("registration_failed"));
json_object_set_new(info, "code", json_integer(status));
json_object_set_new(info, "reason", json_string(phrase ? phrase : ""));
gateway->notify_event(&janus_sip_plugin, session->handle, info);
}
}
break;
}
case nua_r_subscribe: {
JANUS_LOG(LOG_VERB, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
if(status == 200 || status == 202) {
/* Success */
json_t *event = json_object();
json_object_set_new(event, "sip", json_string("event"));
json_object_set_new(event, "call_id", json_string(sip->sip_call_id->i_id));
json_t *result = json_object();
json_object_set_new(result, "event", json_string("subscribe_succeeded"));
json_object_set_new(result, "code", json_integer(status));
if(session->incoming_header_prefixes) {
json_t *headers = janus_sip_get_incoming_headers(sip, session);
json_object_set_new(result, "headers", headers);
}
if (sip->sip_expires)
json_object_set_new(result, "expires", json_integer(sip->sip_expires->ex_delta));
json_object_set_new(result, "reason", json_string(phrase ? phrase : ""));
json_object_set_new(event, "result", result);
int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, event, NULL);
JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(event);
} else if(status == 401 || status == 407) {
const char *scheme = NULL;
const char *realm = NULL;
if(status == 401) {
/* Get scheme/realm from 401 error */
sip_www_authenticate_t const* www_auth = sip->sip_www_authenticate;
if(www_auth == NULL)
break;
scheme = www_auth->au_scheme;
realm = msg_params_find(www_auth->au_params, "realm=");
} else {
/* Get scheme/realm from 407 error, proxy-auth */
sip_proxy_authenticate_t const* proxy_auth = sip->sip_proxy_authenticate;
if(proxy_auth == NULL)
break;
scheme = proxy_auth->au_scheme;
realm = msg_params_find(proxy_auth->au_params, "realm=");
}
char authuser[100], secret[100];
memset(authuser, 0, sizeof(authuser));
memset(secret, 0, sizeof(secret));
if(session->helper) {
/* This is an helper session, we'll need the credentials from the master */
if(session->master == NULL) {
JANUS_LOG(LOG_WARN, "No master session for this helper, authentication will fail...\n");
} else {
session = session->master;
}
}
if(session->account.authuser && strchr(session->account.authuser, ':')) {
/* The authuser contains a colon: wrap it in quotes */
g_snprintf(authuser, sizeof(authuser), "\"%s\"", session->account.authuser);
} else {
g_snprintf(authuser, sizeof(authuser), "%s", session->account.authuser);
}
if(session->account.secret && strchr(session->account.secret, ':')) {
/* The secret contains a colon: wrap it in quotes */
g_snprintf(secret, sizeof(secret), "\"%s\"", session->account.secret);
} else {
g_snprintf(secret, sizeof(secret), "%s", session->account.secret);
}
char auth[256];
memset(auth, 0, sizeof(auth));
g_snprintf(auth, sizeof(auth), "%s%s:%s:%s:%s%s",
session->account.secret_type == janus_sip_secret_type_hashed ? "HA1+" : "",
scheme,
realm,
authuser,
session->account.secret_type == janus_sip_secret_type_hashed ? "HA1+" : "",
secret);
JANUS_LOG(LOG_VERB, "\t%s\n", auth);
/* Authenticate */
nua_authenticate(nh,
NUTAG_AUTH(auth),
TAG_END());
break;
} else if(status >= 400) {
/* Something went wrong */
JANUS_LOG(LOG_WARN, "[%s] SUBSCRIBE failed: %d %s\n", session->account.username, status, phrase ? phrase : "");
json_t *event = json_object();
json_object_set_new(event, "sip", json_string("event"));
if(sip && sip->sip_call_id)
json_object_set_new(event, "call_id", json_string(sip->sip_call_id->i_id));
json_t *result = json_object();
json_object_set_new(result, "event", json_string("subscribe_failed"));
json_object_set_new(result, "code", json_integer(status));
json_object_set_new(result, "reason", json_string(phrase ? phrase : ""));
if(session->incoming_header_prefixes) {
json_t *headers = janus_sip_get_incoming_headers(sip, session);
json_object_set_new(result, "headers", headers);
}
json_object_set_new(event, "result", result);
int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, event, NULL);
JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(event);
}
break;
}
case nua_r_notify: {
JANUS_LOG(LOG_WARN, "[%s][%s]: %d %s\n", session->account.username, nua_event_name(event), status, phrase ? phrase : "??");
/* We got a response to a NOTIFY we sent, but we really don't care */
break;
}
default:
/* unknown event -> print out error message */
JANUS_LOG(LOG_ERR, "Unknown event %d (%s)\n", event, nua_event_name(event));
break;
}
}
void janus_sip_save_reason(sip_t const *sip, janus_sip_session *session) {
if(!sip || !session)
return;
if(sip->sip_reason && sip->sip_reason->re_text) {
g_free(session->hangup_reason_header);
session->hangup_reason_header = g_strdup(sip->sip_reason->re_text);
janus_sip_remove_quotes(session->hangup_reason_header);
}
if(sip->sip_reason && sip->sip_reason->re_protocol) {
g_free(session->hangup_reason_header_protocol);
session->hangup_reason_header_protocol = g_strdup(sip->sip_reason->re_protocol);
}
if(sip->sip_reason && sip->sip_reason->re_cause) {
g_free(session->hangup_reason_header_cause);
session->hangup_reason_header_cause = g_strdup(sip->sip_reason->re_cause);
}
}
void janus_sip_sdp_process(janus_sip_session *session, janus_sdp *sdp, gboolean answer, gboolean update, gboolean *changed) {
if(!session || !sdp)
return;
/* c= */
int opusred_pt = answer ? janus_sdp_get_opusred_pt(sdp, -1) : -1;
if(sdp->c_addr) {
if(update) {
if(changed && (!session->media.remote_audio_ip || strcmp(sdp->c_addr, session->media.remote_audio_ip))) {
/* This is an update and an address changed */
*changed = TRUE;
}
if(changed && (!session->media.remote_video_ip || strcmp(sdp->c_addr, session->media.remote_video_ip))) {
/* This is an update and an address changed */
*changed = TRUE;
}
}
/* Regardless if we audio and video are being negotiated we set their connection addresses
* from session level c= header by default. If media level connection addresses are available
* they will be set when processing appropriate media description.*/
g_free(session->media.remote_audio_ip);
session->media.remote_audio_ip = g_strdup(sdp->c_addr);
g_free(session->media.remote_video_ip);
session->media.remote_video_ip = g_strdup(sdp->c_addr);
}
GList *temp = sdp->m_lines;
while(temp) {
janus_sdp_mline *m = (janus_sdp_mline *)temp->data;
session->media.require_srtp = session->media.require_srtp || (m->proto && !strcasecmp(m->proto, "RTP/SAVP"));
if(m->type == JANUS_SDP_AUDIO) {
if(m->port) {
if(m->port != session->media.remote_audio_rtp_port) {
/* This is an update and an address changed */
if(changed)
*changed = TRUE;
}
session->media.has_audio = TRUE;
session->media.remote_audio_rtp_port = m->port;
session->media.remote_audio_rtcp_port = m->port+1; /* FIXME We're assuming RTCP is on the next port */
session->media.dtmf_pt = janus_sdp_get_codec_pt(sdp, -1, "dtmf");
if(m->direction == JANUS_SDP_SENDONLY || m->direction == JANUS_SDP_INACTIVE)
session->media.audio_send = FALSE;
else
session->media.audio_send = TRUE;
if(m->direction == JANUS_SDP_RECVONLY || m->direction == JANUS_SDP_INACTIVE)
session->media.audio_recv = FALSE;
else
session->media.audio_recv = TRUE;
} else {
session->media.audio_send = FALSE;
session->media.audio_recv = FALSE;
}
} else if(m->type == JANUS_SDP_VIDEO) {
if(m->port) {
if(m->port != session->media.remote_video_rtp_port) {
/* This is an update and an address changed */
if(changed)
*changed = TRUE;
}
session->media.has_video = TRUE;
session->media.remote_video_rtp_port = m->port;
session->media.remote_video_rtcp_port = m->port+1; /* FIXME We're assuming RTCP is on the next port */
if(m->direction == JANUS_SDP_SENDONLY || m->direction == JANUS_SDP_INACTIVE)
session->media.video_send = FALSE;
else
session->media.video_send = TRUE;
if(m->direction == JANUS_SDP_RECVONLY || m->direction == JANUS_SDP_INACTIVE)
session->media.video_recv = FALSE;
else
session->media.video_recv = TRUE;
} else {
session->media.video_send = FALSE;
session->media.video_recv = FALSE;
}
} else {
JANUS_LOG(LOG_WARN, "Unsupported media line (not audio/video)\n");
temp = temp->next;
continue;
}
if(m->c_addr && m->type == JANUS_SDP_AUDIO) {
if(update && (!session->media.remote_audio_ip || strcmp(m->c_addr, session->media.remote_audio_ip))) {
/* This is an update and an address changed */
if(changed)
*changed = TRUE;
}
g_free(session->media.remote_audio_ip);
session->media.remote_audio_ip = g_strdup(m->c_addr);
}
else if(m->c_addr && m->type == JANUS_SDP_VIDEO) {
if(update && (!session->media.remote_video_ip || strcmp(m->c_addr, session->media.remote_video_ip))) {
/* This is an update and an address changed */
if(changed)
*changed = TRUE;
}
g_free(session->media.remote_video_ip);
session->media.remote_video_ip = g_strdup(m->c_addr);
}
GList *tempA = m->attributes;
while(tempA) {
janus_sdp_attribute *a = (janus_sdp_attribute *)tempA->data;
if(a->name) {
if(!strcasecmp(a->name, "crypto")) {
if(m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO) {
if((m->type == JANUS_SDP_AUDIO && session->media.audio_srtp_in != NULL) || (m->type == JANUS_SDP_VIDEO && session->media.video_srtp_in != NULL)) {
/* Remote SRTP is already set */
tempA = tempA->next;
continue;
}
gint32 tag = 0;
char profile[101], crypto[101];
int res = a->value ? (sscanf(a->value, "%"SCNi32" %100s inline:%100s",
&tag, profile, crypto)) : 0;
if(res != 3) {
JANUS_LOG(LOG_WARN, "Failed to parse crypto line, ignoring... %s\n", a->value);
} else {
gboolean video = (m->type == JANUS_SDP_VIDEO);
if(answer && ((!video && tag != session->media.audio_srtp_tag) || (video && tag != session->media.video_srtp_tag))) {
/* Not the tag for the crypto line we offered */
tempA = tempA->next;
continue;
}
if(janus_sip_srtp_set_remote(session, video, profile, crypto) < 0) {
/* Unsupported profile? */
tempA = tempA->next;
continue;
}
if(!video) {
session->media.audio_srtp_tag = tag;
session->media.has_srtp_remote_audio = TRUE;
} else {
session->media.video_srtp_tag = tag;
session->media.has_srtp_remote_video = TRUE;
}
}
}
} else if(m->type == JANUS_SDP_VIDEO && !strcasecmp(a->name, "rtcp-fb") && a->value) {
if(strstr(a->value, " pli"))
session->media.video_pli_supported = TRUE;
}
}
tempA = tempA->next;
}
if(answer && (m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO)) {
/* Check which codec was negotiated eventually */
int pt = -1;
if(m->ptypes)
pt = GPOINTER_TO_INT(m->ptypes->data);
if(pt > -1) {
if(m->type == JANUS_SDP_AUDIO) {
if(pt == opusred_pt) {
session->media.opusred_pt = pt;
session->media.audio_pt = m->ptypes->next ? GPOINTER_TO_INT(m->ptypes->next->data) : -1;
} else {
session->media.audio_pt = pt;
}
} else {
session->media.video_pt = pt;
}
}
}
temp = temp->next;
}
if(update && changed && *changed) {
/* Something changed: mark this on the session, so that the thread can update the sockets */
session->media.updated = TRUE;
if(session->media.pipefd[1] > 0) {
int code = 1;
ssize_t res = 0;
do {
res = write(session->media.pipefd[1], &code, sizeof(int));
} while(res == -1 && errno == EINTR);
}
}
}
char *janus_sip_sdp_manipulate(janus_sip_session *session, janus_sdp *sdp, gboolean answer) {
if(!session || !session->stack || !sdp)
return NULL;
GHashTable *codecs = NULL;
GList *pts_to_remove = NULL;
/* Start replacing stuff */
JANUS_LOG(LOG_VERB, "Setting protocol to %s\n", session->media.require_srtp ? "RTP/SAVP" : "RTP/AVP");
if(sdp->c_addr) {
g_free(sdp->c_addr);
sdp->c_addr = g_strdup(sdp_ip);
}
int opusred_pt = answer ? janus_sdp_get_opusred_pt(sdp, -1) : -1;
GList *temp = sdp->m_lines;
while(temp) {
janus_sdp_mline *m = (janus_sdp_mline *)temp->data;
g_free(m->proto);
m->proto = g_strdup(session->media.require_srtp ? "RTP/SAVP" : "RTP/AVP");
if(m->type == JANUS_SDP_AUDIO) {
m->port = session->media.local_audio_rtp_port;
if(session->media.has_srtp_local_audio) {
if(!session->media.audio_srtp_local_profile || !session->media.audio_srtp_local_crypto) {
janus_sip_srtp_set_local(session, FALSE, &session->media.audio_srtp_local_profile, &session->media.audio_srtp_local_crypto);
}
if(session->media.audio_srtp_tag == 0)
session->media.audio_srtp_tag = 1;
janus_sdp_attribute *a = janus_sdp_attribute_create("crypto", "%"SCNi32" %s inline:%s",
session->media.audio_srtp_tag, session->media.audio_srtp_local_profile, session->media.audio_srtp_local_crypto);
m->attributes = g_list_append(m->attributes, a);
}
} else if(m->type == JANUS_SDP_VIDEO) {
m->port = session->media.local_video_rtp_port;
if(session->media.has_srtp_local_video) {
if(!session->media.video_srtp_local_profile || !session->media.video_srtp_local_crypto) {
janus_sip_srtp_set_local(session, TRUE, &session->media.video_srtp_local_profile, &session->media.video_srtp_local_crypto);
}
if(session->media.video_srtp_tag == 0)
session->media.video_srtp_tag = 1;
janus_sdp_attribute *a = janus_sdp_attribute_create("crypto", "%"SCNi32" %s inline:%s",
session->media.video_srtp_tag, session->media.video_srtp_local_profile, session->media.video_srtp_local_crypto);
m->attributes = g_list_append(m->attributes, a);
}
}
g_free(m->c_addr);
m->c_addr = g_strdup(sdp_ip);
if(answer && (m->type == JANUS_SDP_AUDIO || m->type == JANUS_SDP_VIDEO)) {
/* Check which codec was negotiated eventually */
int pt = -1;
if(m->ptypes)
pt = GPOINTER_TO_INT(m->ptypes->data);
if(pt > -1) {
if(m->type == JANUS_SDP_AUDIO) {
if(pt == opusred_pt) {
session->media.opusred_pt = pt;
session->media.audio_pt = m->ptypes->next ? GPOINTER_TO_INT(m->ptypes->next->data) : -1;
} else {
session->media.audio_pt = pt;
}
} else {
session->media.video_pt = pt;
}
}
}
/* If this is an answer, get rid of multiple versions of the same
* codec as well (e.g., video profiles), as that confuses the hell
* out of SOATAG_RTP_SELECT(SOA_RTP_SELECT_COMMON) in nua_respond() */
if(answer) {
if(codecs == NULL)
codecs = g_hash_table_new_full(g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL);
/* Check all rtpmap attributes */
int pt = -1;
char codec[50];
GList *ma = m->attributes;
while(ma) {
janus_sdp_attribute *a = (janus_sdp_attribute *)ma->data;
if(a->name != NULL && a->value != NULL && !strcasecmp(a->name, "rtpmap")) {
if(sscanf(a->value, "%3d %49s", &pt, codec) == 2) {
if(g_hash_table_lookup(codecs, codec) != NULL) {
/* We already have a version of this codec, remove the payload type */
pts_to_remove = g_list_append(pts_to_remove, GINT_TO_POINTER(pt));
JANUS_LOG(LOG_HUGE, "Removing %d (%s)\n", pt, codec);
} else {
/* Keep track of this codec */
g_hash_table_insert(codecs, g_strdup(codec), GINT_TO_POINTER(pt));
}
}
}
ma = ma->next;
}
/* If we need to remove some payload types from this m-line, do it now */
if(pts_to_remove != NULL) {
GList *temp = pts_to_remove;
while(temp) {
int pt = GPOINTER_TO_INT(temp->data);
janus_sdp_remove_payload_type(sdp, m->index, pt);
temp = temp->next;
}
g_list_free(pts_to_remove);
pts_to_remove = NULL;
}
}
temp = temp->next;
}
if(codecs != NULL)
g_hash_table_destroy(codecs);
/* Generate a SDP string out of our changes */
return janus_sdp_write(sdp);
}
/* Bind local RTP/RTCP sockets */
static int janus_sip_allocate_local_ports(janus_sip_session *session, gboolean update) {
if(session == NULL) {
JANUS_LOG(LOG_ERR, "Invalid session\n");
return -1;
}
if(!update) {
/* Reset status */
if(session->media.audio_rtp_fd != -1) {
close(session->media.audio_rtp_fd);
session->media.audio_rtp_fd = -1;
}
if(session->media.audio_rtcp_fd != -1) {
close(session->media.audio_rtcp_fd);
session->media.audio_rtcp_fd = -1;
}
session->media.local_audio_rtp_port = 0;
session->media.local_audio_rtcp_port = 0;
session->media.audio_ssrc = 0;
if(session->media.video_rtp_fd != -1) {
close(session->media.video_rtp_fd);
session->media.video_rtp_fd = -1;
}
if(session->media.video_rtcp_fd != -1) {
close(session->media.video_rtcp_fd);
session->media.video_rtcp_fd = -1;
}
session->media.local_video_rtp_port = 0;
session->media.local_video_rtcp_port = 0;
session->media.video_ssrc = 0;
if(session->media.pipefd[0] > 0) {
close(session->media.pipefd[0]);
session->media.pipefd[0] = -1;
}
if(session->media.pipefd[1] > 0) {
close(session->media.pipefd[1]);
session->media.pipefd[1] = -1;
}
}
gboolean use_ipv6_address_family = !ipv6_disabled &&
(janus_network_address_is_null(&janus_network_local_media_ip) || janus_network_local_media_ip.family == AF_INET6);
socklen_t addrlen = use_ipv6_address_family? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in);
/* Start */
int attempts = 100; /* FIXME Don't retry forever */
if(session->media.has_audio) {
JANUS_LOG(LOG_VERB, "Allocating audio ports using address [%s]\n",
janus_network_address_is_null(&janus_network_local_media_ip) ? "any" : local_media_ip);
struct sockaddr_storage audio_rtp_address, audio_rtcp_address;
while(session->media.local_audio_rtp_port == 0 || session->media.local_audio_rtcp_port == 0) {
if(attempts == 0) /* Too many failures */
return -1;
memset(&audio_rtp_address, 0, sizeof(audio_rtp_address));
memset(&audio_rtcp_address, 0, sizeof(audio_rtcp_address));
if(session->media.audio_rtp_fd == -1) {
session->media.audio_rtp_fd = socket(use_ipv6_address_family ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);
int v6only = 0;
if(use_ipv6_address_family && session->media.audio_rtp_fd != -1 &&
setsockopt(session->media.audio_rtp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)) != 0) {
JANUS_LOG(LOG_WARN, "Error setting v6only to false on audio RTP socket (error=%s)\n",
g_strerror(errno));
}
/* Set the DSCP value if set in the config file */
if(session->media.audio_rtp_fd != -1 && dscp_audio_rtp > 0) {
int optval = dscp_audio_rtp << 2;
int ret = setsockopt(session->media.audio_rtp_fd, IPPROTO_IP, IP_TOS, &optval, sizeof(optval));
if(ret < 0) {
JANUS_LOG(LOG_WARN, "Error setting IP_TOS %d on audio RTP socket (error=%s)\n",
optval, g_strerror(errno));
}
}
}
if(session->media.audio_rtcp_fd == -1) {
session->media.audio_rtcp_fd = socket(use_ipv6_address_family ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);
int v6only = 0;
if(use_ipv6_address_family && session->media.audio_rtcp_fd != -1 &&
setsockopt(session->media.audio_rtcp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)) != 0) {
JANUS_LOG(LOG_WARN, "Error setting v6only to false on audio RTCP socket (error=%s)\n",
g_strerror(errno));
}
}
if(session->media.audio_rtp_fd == -1 || session->media.audio_rtcp_fd == -1) {
JANUS_LOG(LOG_ERR, "Error creating audio sockets...\n");
return -1;
}
int rtp_port = g_random_int_range(rtp_range_min, rtp_range_max);
if(rtp_port % 2)
rtp_port++; /* Pick an even port for RTP */
if(use_ipv6_address_family) {
struct sockaddr_in6 *addr = (struct sockaddr_in6 *)&audio_rtp_address;
addr->sin6_family = AF_INET6;
addr->sin6_port = htons(rtp_port);
addr->sin6_addr = janus_network_address_is_null(&janus_network_local_media_ip) ? in6addr_any : janus_network_local_media_ip.ipv6;
} else {
struct sockaddr_in *addr = (struct sockaddr_in *)&audio_rtp_address;
addr->sin_family = AF_INET;
addr->sin_port = htons(rtp_port);
addr->sin_addr.s_addr = janus_network_address_is_null(&janus_network_local_media_ip) ? INADDR_ANY : janus_network_local_media_ip.ipv4.s_addr;
}
if(bind(session->media.audio_rtp_fd, (struct sockaddr *)(&audio_rtp_address), addrlen) < 0) {
JANUS_LOG(LOG_ERR, "Bind failed for audio RTP (port %d), error (%s), trying a different one...\n", rtp_port, g_strerror(errno));
close(session->media.audio_rtp_fd);
session->media.audio_rtp_fd = -1;
attempts--;
continue;
}
JANUS_LOG(LOG_VERB, "Audio RTP listener bound to [%s]:%d(%d)\n",
janus_network_address_is_null(&janus_network_local_media_ip) ? "any" : local_media_ip, rtp_port, session->media.audio_rtp_fd);
int rtcp_port = rtp_port+1;
if(use_ipv6_address_family) {
struct sockaddr_in6 *addr = (struct sockaddr_in6 *)&audio_rtcp_address;
addr->sin6_family = AF_INET6;
addr->sin6_port = htons(rtcp_port);
addr->sin6_addr = janus_network_address_is_null(&janus_network_local_media_ip) ? in6addr_any : janus_network_local_media_ip.ipv6;
} else {
struct sockaddr_in *addr = (struct sockaddr_in *)&audio_rtcp_address;
addr->sin_family = AF_INET;
addr->sin_port = htons(rtcp_port);
addr->sin_addr.s_addr = janus_network_address_is_null(&janus_network_local_media_ip) ? INADDR_ANY : janus_network_local_media_ip.ipv4.s_addr;
}
if(bind(session->media.audio_rtcp_fd, (struct sockaddr *)(&audio_rtcp_address), addrlen) < 0) {
JANUS_LOG(LOG_ERR, "Bind failed for audio RTCP (port %d), error (%s), trying a different one...\n", rtcp_port, g_strerror(errno));
/* RTP socket is not valid anymore, reset it */
close(session->media.audio_rtp_fd);
session->media.audio_rtp_fd = -1;
close(session->media.audio_rtcp_fd);
session->media.audio_rtcp_fd = -1;
attempts--;
continue;
}
JANUS_LOG(LOG_VERB, "Audio RTCP listener bound to [%s]:%d(%d)\n",
janus_network_address_is_null(&janus_network_local_media_ip) ? "any" : local_media_ip, rtcp_port, session->media.audio_rtcp_fd);
session->media.local_audio_rtp_port = rtp_port;
session->media.local_audio_rtcp_port = rtcp_port;
}
}
if(session->media.has_video) {
JANUS_LOG(LOG_VERB, "Allocating video ports using address [%s]\n",
janus_network_address_is_null(&janus_network_local_media_ip ) ?"any" : local_media_ip);
struct sockaddr_storage video_rtp_address, video_rtcp_address;
while(session->media.local_video_rtp_port == 0 || session->media.local_video_rtcp_port == 0) {
if(attempts == 0) /* Too many failures */
return -1;
memset(&video_rtp_address, 0, sizeof(video_rtp_address));
memset(&video_rtcp_address, 0, sizeof(video_rtcp_address));
if(session->media.video_rtp_fd == -1) {
session->media.video_rtp_fd = socket(use_ipv6_address_family ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);
int v6only = 0;
if(use_ipv6_address_family && session->media.video_rtp_fd != -1 &&
setsockopt(session->media.video_rtp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)) != 0) {
JANUS_LOG(LOG_WARN, "Error setting v6only to false on video RTP socket (error=%s)\n",
g_strerror(errno));
}
/* Set the DSCP value if set in the config file */
if(session->media.video_rtp_fd != -1 && dscp_video_rtp > 0) {
int optval = dscp_video_rtp << 2;
int ret = setsockopt(session->media.video_rtp_fd, IPPROTO_IP, IP_TOS, &optval, sizeof(optval));
if(ret < 0) {
JANUS_LOG(LOG_WARN, "Error setting IP_TOS %d on video RTP socket (error=%s)\n",
optval, g_strerror(errno));
}
}
}
if(session->media.video_rtcp_fd == -1) {
session->media.video_rtcp_fd = socket(use_ipv6_address_family ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);
int v6only = 0;
if(use_ipv6_address_family && session->media.video_rtcp_fd != -1 &&
setsockopt(session->media.video_rtcp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)) != 0) {
JANUS_LOG(LOG_WARN, "Error setting v6only to false on video RTCP socket (error=%s)\n",
g_strerror(errno));
}
}
if(session->media.video_rtp_fd == -1 || session->media.video_rtcp_fd == -1) {
JANUS_LOG(LOG_ERR, "Error creating video sockets...\n");
return -1;
}
int rtp_port = g_random_int_range(rtp_range_min, rtp_range_max);
if(rtp_port % 2)
rtp_port++; /* Pick an even port for RTP */
if(use_ipv6_address_family) {
struct sockaddr_in6 *addr = (struct sockaddr_in6 *)&video_rtp_address;
addr->sin6_family = AF_INET6;
addr->sin6_port = htons(rtp_port);
addr->sin6_addr = janus_network_address_is_null(&janus_network_local_media_ip) ? in6addr_any : janus_network_local_media_ip.ipv6;
} else {
struct sockaddr_in *addr = (struct sockaddr_in *)&video_rtp_address;
addr->sin_family = AF_INET;
addr->sin_port = htons(rtp_port);
addr->sin_addr.s_addr = janus_network_address_is_null(&janus_network_local_media_ip) ? INADDR_ANY : janus_network_local_media_ip.ipv4.s_addr;
}
if(bind(session->media.video_rtp_fd, (struct sockaddr *)(&video_rtp_address), addrlen) < 0) {
JANUS_LOG(LOG_ERR, "Bind failed for video RTP (port %d), error (%s), trying a different one...\n", rtp_port, g_strerror(errno));
close(session->media.video_rtp_fd);
session->media.video_rtp_fd = -1;
attempts--;
continue;
}
JANUS_LOG(LOG_VERB, "Video RTP listener bound to [%s]:%d(%d)\n",
janus_network_address_is_null(&janus_network_local_media_ip) ? "any" : local_media_ip, rtp_port, session->media.video_rtp_fd);
int rtcp_port = rtp_port+1;
if(use_ipv6_address_family) {
struct sockaddr_in6 *addr = (struct sockaddr_in6 *)&video_rtcp_address;
addr->sin6_family = AF_INET6;
addr->sin6_port = htons(rtcp_port);
addr->sin6_addr = janus_network_address_is_null(&janus_network_local_media_ip) ? in6addr_any : janus_network_local_media_ip.ipv6;
} else {
struct sockaddr_in *addr = (struct sockaddr_in *)&video_rtcp_address;
addr->sin_family = AF_INET;
addr->sin_port = htons(rtcp_port);
addr->sin_addr.s_addr = janus_network_address_is_null(&janus_network_local_media_ip) ? INADDR_ANY : janus_network_local_media_ip.ipv4.s_addr;
}
if(bind(session->media.video_rtcp_fd, (struct sockaddr *)(&video_rtcp_address), addrlen) < 0) {
JANUS_LOG(LOG_ERR, "Bind failed for video RTCP (port %d), error (%s), trying a different one...\n", rtcp_port, g_strerror(errno));
/* RTP socket is not valid anymore, reset it */
close(session->media.video_rtp_fd);
session->media.video_rtp_fd = -1;
close(session->media.video_rtcp_fd);
session->media.video_rtcp_fd = -1;
attempts--;
continue;
}
JANUS_LOG(LOG_VERB, "Video RTCP listener bound to [%s]:%d(%d)\n",
janus_network_address_is_null(&janus_network_local_media_ip) ? "any" : local_media_ip, rtcp_port, session->media.video_rtcp_fd);
session->media.local_video_rtp_port = rtp_port;
session->media.local_video_rtcp_port = rtcp_port;
}
}
if(!update) {
/* We need this to quickly interrupt the poll when it's time to update a session or wrap up */
pipe(session->media.pipefd);
}
return 0;
}
/* Helper method to (re)connect RTP/RTCP sockets */
static void janus_sip_connect_sockets(janus_sip_session *session, struct sockaddr_storage *audio_server_addr, struct sockaddr_storage *video_server_addr) {
if(!session || (!audio_server_addr && !video_server_addr))
return;
/* Connect peers (FIXME This pretty much sucks right now) */
if(session->media.remote_audio_rtp_port && audio_server_addr && session->media.audio_rtp_fd != -1) {
if(audio_server_addr->ss_family == AF_INET6) {
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)audio_server_addr;
addr6->sin6_port = htons(session->media.remote_audio_rtp_port);
} else if(audio_server_addr->ss_family == AF_INET) {
struct sockaddr_in *addr = (struct sockaddr_in *)audio_server_addr;
addr->sin_port = htons(session->media.remote_audio_rtp_port);
}
if(connect(session->media.audio_rtp_fd, (struct sockaddr *)audio_server_addr, sizeof(struct sockaddr_storage)) == -1) {
JANUS_LOG(LOG_ERR, "[SIP-%s] Couldn't connect audio RTP? (%s:%d)\n", session->account.username,
session->media.remote_audio_ip, session->media.remote_audio_rtp_port);
JANUS_LOG(LOG_ERR, "[SIP-%s] -- %d (%s)\n", session->account.username, errno, g_strerror(errno));
}
}
if(session->media.remote_audio_rtcp_port && audio_server_addr && session->media.audio_rtcp_fd != -1) {
if(audio_server_addr->ss_family == AF_INET6) {
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)audio_server_addr;
addr6->sin6_port = htons(session->media.remote_audio_rtcp_port);
} else if(audio_server_addr->ss_family == AF_INET) {
struct sockaddr_in *addr = (struct sockaddr_in *)audio_server_addr;
addr->sin_port = htons(session->media.remote_audio_rtcp_port);
}
if(connect(session->media.audio_rtcp_fd, (struct sockaddr *)audio_server_addr, sizeof(struct sockaddr_storage)) == -1) {
JANUS_LOG(LOG_ERR, "[SIP-%s] Couldn't connect audio RTCP? (%s:%d)\n", session->account.username,
session->media.remote_audio_ip, session->media.remote_audio_rtcp_port);
JANUS_LOG(LOG_ERR, "[SIP-%s] -- %d (%s)\n", session->account.username, errno, g_strerror(errno));
}
}
if(session->media.remote_video_rtp_port && video_server_addr && session->media.video_rtp_fd != -1) {
if(video_server_addr->ss_family == AF_INET6) {
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)video_server_addr;
addr6->sin6_port = htons(session->media.remote_video_rtp_port);
} else if(video_server_addr->ss_family == AF_INET) {
struct sockaddr_in *addr = (struct sockaddr_in *)video_server_addr;
addr->sin_port = htons(session->media.remote_video_rtp_port);
}
if(connect(session->media.video_rtp_fd, (struct sockaddr *)video_server_addr, sizeof(struct sockaddr_storage)) == -1) {
JANUS_LOG(LOG_ERR, "[SIP-%s] Couldn't connect video RTP? (%s:%d)\n", session->account.username,
session->media.remote_video_ip, session->media.remote_video_rtp_port);
JANUS_LOG(LOG_ERR, "[SIP-%s] -- %d (%s)\n", session->account.username, errno, g_strerror(errno));
}
}
if(session->media.remote_video_rtcp_port && video_server_addr && session->media.video_rtcp_fd != -1) {
if(video_server_addr->ss_family == AF_INET6) {
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)video_server_addr;
addr6->sin6_port = htons(session->media.remote_video_rtcp_port);
} else if(video_server_addr->ss_family == AF_INET) {
struct sockaddr_in *addr = (struct sockaddr_in *)video_server_addr;
addr->sin_port = htons(session->media.remote_video_rtcp_port);
}
if(connect(session->media.video_rtcp_fd, (struct sockaddr *)video_server_addr, sizeof(struct sockaddr_storage)) == -1) {
JANUS_LOG(LOG_ERR, "[SIP-%s] Couldn't connect video RTCP? (%s:%d)\n", session->account.username,
session->media.remote_video_ip, session->media.remote_video_rtcp_port);
JANUS_LOG(LOG_ERR, "[SIP-%s] -- %d (%s)\n", session->account.username, errno, g_strerror(errno));
}
}
}
static void janus_sip_media_cleanup(janus_sip_session *session) {
if(session->media.audio_rtp_fd != -1) {
close(session->media.audio_rtp_fd);
session->media.audio_rtp_fd = -1;
}
if(session->media.audio_rtcp_fd != -1) {
close(session->media.audio_rtcp_fd);
session->media.audio_rtcp_fd = -1;
}
session->media.local_audio_rtp_port = 0;
session->media.local_audio_rtcp_port = 0;
session->media.remote_audio_rtp_port = 0;
session->media.remote_audio_rtcp_port = 0;
session->media.audio_ssrc = 0;
session->media.audio_ssrc_peer = 0;
if(session->media.video_rtp_fd != -1) {
close(session->media.video_rtp_fd);
session->media.video_rtp_fd = -1;
}
if(session->media.video_rtcp_fd != -1) {
close(session->media.video_rtcp_fd);
session->media.video_rtcp_fd = -1;
}
session->media.local_video_rtp_port = 0;
session->media.local_video_rtcp_port = 0;
session->media.remote_video_rtp_port = 0;
session->media.remote_video_rtcp_port = 0;
session->media.video_ssrc = 0;
session->media.video_ssrc_peer = 0;
session->media.simulcast_ssrc = 0;
if(session->media.pipefd[0] > 0) {
close(session->media.pipefd[0]);
session->media.pipefd[0] = -1;
}
if(session->media.pipefd[1] > 0) {
close(session->media.pipefd[1]);
session->media.pipefd[1] = -1;
}
/* Clean up SRTP stuff, if needed */
janus_sip_srtp_cleanup(session);
/* Media fields not cleaned up elsewhere */
janus_sip_media_reset(session);
}
/* Thread to relay RTP/RTCP frames coming from the SIP peer */
static void *janus_sip_relay_thread(void *data) {
janus_sip_session *session = (janus_sip_session *)data;
if(!session) {
g_thread_unref(g_thread_self());
return NULL;
}
if(!session->account.username || !session->callee) {
janus_refcount_decrease(&session->ref);
g_thread_unref(g_thread_self());
return NULL;
}
JANUS_LOG(LOG_VERB, "Starting relay thread (%s <--> %s)\n", session->account.username, session->callee);
if(!session->callee) {
JANUS_LOG(LOG_WARN, "[SIP-%s] Leaving thread, no callee...\n", session->account.username);
janus_refcount_decrease(&session->ref);
g_thread_unref(g_thread_self());
return NULL;
}
/* File descriptors */
socklen_t addrlen;
struct sockaddr_in remote;
int resfd = 0, bytes = 0, pollerrs = 0;
struct pollfd fds[5];
int pipe_fd = session->media.pipefd[0];
char buffer[1500];
memset(buffer, 0, 1500);
if(pipe_fd == -1) {
/* If the pipe file descriptor doesn't exist, it means we're done already,
* and/or we may never be notified about sessions being closed, so give up */
JANUS_LOG(LOG_WARN, "[SIP-%s] Leaving thread, no pipe file descriptor...\n", session->account.username);
janus_refcount_decrease(&session->ref);
g_thread_unref(g_thread_self());
return NULL;
}
/* Loop */
int num = 0;
gboolean goon = TRUE;
session->media.updated = TRUE; /* Connect UDP sockets upon loop entry */
gboolean have_audio_server_ip = TRUE;
gboolean have_video_server_ip = TRUE;
while(goon && session != NULL && !g_atomic_int_get(&session->destroyed) &&
session->status > janus_sip_call_status_idle &&
session->status < janus_sip_call_status_closing) { /* FIXME We need a per-call watchdog as well */
if(session->media.updated) {
/* Apparently there was a session update, or the loop has just been entered */
session->media.updated = FALSE;
/* Resolve the addresses, if needed */
have_audio_server_ip = FALSE;
have_video_server_ip = FALSE;
struct sockaddr_storage audio_server_addr = { 0 }, video_server_addr = { 0 };
if(session->media.remote_audio_ip && strcmp(session->media.remote_audio_ip, "0.0.0.0")) {
if(janus_network_resolve_address(session->media.remote_audio_ip, &audio_server_addr) < 0) {
JANUS_LOG(LOG_ERR, "[SIP-%s] Couldn't resolve audio address '%s'\n",
session->account.username, session->media.remote_audio_ip);
} else {
/* Address resolved */
have_audio_server_ip = TRUE;
}
}
if(session->media.remote_video_ip && strcmp(session->media.remote_video_ip, "0.0.0.0")) {
if(janus_network_resolve_address(session->media.remote_video_ip, &video_server_addr) < 0) {
JANUS_LOG(LOG_ERR, "[SIP-%s] Couldn't resolve video address '%s'\n",
session->account.username, session->media.remote_video_ip);
} else {
/* Address resolved */
have_video_server_ip = TRUE;
}
}
if(have_audio_server_ip || have_video_server_ip) {
janus_sip_connect_sockets(session, have_audio_server_ip ? &audio_server_addr : NULL,
have_video_server_ip ? &video_server_addr : NULL);
} else if(session->media.remote_audio_ip == NULL && session->media.remote_video_ip == NULL) {
JANUS_LOG(LOG_ERR, "[SIP-%p] Couldn't update session details: both audio and video remote IP addresses are NULL\n",
session->account.username);
} else {
if(session->media.remote_audio_ip)
JANUS_LOG(LOG_ERR, "[SIP-%p] Couldn't update session details: audio remote IP address (%s) is invalid\n",
session->account.username, session->media.remote_audio_ip);
if(session->media.remote_video_ip)
JANUS_LOG(LOG_ERR, "[SIP-%p] Couldn't update session details: video remote IP address (%s) is invalid\n",
session->account.username, session->media.remote_video_ip);
}
/* In case we're on hold (remote address is 0.0.0.0) set the send properties to FALSE */
if(have_audio_server_ip && !strcmp(session->media.remote_audio_ip, "0.0.0.0")) {
session->media.audio_send = FALSE;
session->media.audio_recv = FALSE;
}
if(have_video_server_ip && !strcmp(session->media.remote_video_ip, "0.0.0.0")) {
session->media.video_send = FALSE;
session->media.video_recv = FALSE;
}
}
/* Prepare poll */
num = 0;
if(session->media.audio_rtp_fd != -1) {
fds[num].fd = session->media.audio_rtp_fd;
fds[num].events = POLLIN;
fds[num].revents = 0;
num++;
}
if(session->media.audio_rtcp_fd != -1) {
fds[num].fd = session->media.audio_rtcp_fd;
fds[num].events = POLLIN;
fds[num].revents = 0;
num++;
}
if(session->media.video_rtp_fd != -1) {
fds[num].fd = session->media.video_rtp_fd;
fds[num].events = POLLIN;
fds[num].revents = 0;
num++;
}
if(session->media.video_rtcp_fd != -1) {
fds[num].fd = session->media.video_rtcp_fd;
fds[num].events = POLLIN;
fds[num].revents = 0;
num++;
}
/* Finally, let's add the pipe */
pipe_fd = session->media.pipefd[0];
if(pipe_fd == -1) {
/* Pipe was closed? Means the call is over */
break;
}
fds[num].fd = pipe_fd;
fds[num].events = POLLIN;
fds[num].revents = 0;
num++;
/* Wait for some data */
resfd = poll(fds, num, 1000);
if(resfd < 0) {
if(errno == EINTR) {
JANUS_LOG(LOG_HUGE, "[SIP-%s] Got an EINTR (%s), ignoring...\n", session->account.username, g_strerror(errno));
continue;
}
JANUS_LOG(LOG_ERR, "[SIP-%s] Error polling...\n", session->account.username);
JANUS_LOG(LOG_ERR, "[SIP-%s] -- %d (%s)\n", session->account.username, errno, g_strerror(errno));
break;
} else if(resfd == 0) {
/* No data, keep going */
continue;
}
if(session == NULL || g_atomic_int_get(&session->destroyed) ||
session->status <= janus_sip_call_status_idle ||
session->status >= janus_sip_call_status_closing)
break;
int i = 0;
for(i=0; i<num; i++) {
if(fds[i].revents & (POLLERR | POLLHUP)) {
/* If we just updated the session, let's wait until things have calmed down */
if(session->media.updated)
break;
/* Check the socket error */
int error = 0;
socklen_t errlen = sizeof(error);
getsockopt(fds[i].fd, SOL_SOCKET, SO_ERROR, (void *)&error, &errlen);
if(error == 0) {
/* Maybe not a breaking error after all? */
continue;
} else if(error == 111) {
/* ICMP error? If it's related to RTCP, let's just close the RTCP socket and move on */
if(fds[i].fd == session->media.audio_rtcp_fd) {
JANUS_LOG(LOG_WARN, "[SIP-%s] Got a '%s' on the audio RTCP socket, closing it\n",
session->account.username, g_strerror(error));
janus_mutex_lock(&session->mutex);
close(session->media.audio_rtcp_fd);
session->media.audio_rtcp_fd = -1;
janus_mutex_unlock(&session->mutex);
continue;
} else if(fds[i].fd == session->media.video_rtcp_fd) {
JANUS_LOG(LOG_WARN, "[SIP-%s] Got a '%s' on the video RTCP socket, closing it\n",
session->account.username, g_strerror(error));
janus_mutex_lock(&session->mutex);
close(session->media.video_rtcp_fd);
session->media.video_rtcp_fd = -1;
janus_mutex_unlock(&session->mutex);
continue;
}
}
/* FIXME Should we be more tolerant of ICMP errors on RTP sockets as well? */
pollerrs++;
if(pollerrs < 100)
continue;
JANUS_LOG(LOG_ERR, "[SIP-%s] Too many errors polling %d (socket #%d): %s...\n", session->account.username,
fds[i].fd, i, fds[i].revents & POLLERR ? "POLLERR" : "POLLHUP");
JANUS_LOG(LOG_ERR, "[SIP-%s] -- %d (%s)\n", session->account.username, error, g_strerror(error));
goon = FALSE; /* Can we assume it's pretty much over, after a POLLERR? */
/* FIXME Simulate a "hangup" coming from the application */
janus_sip_hangup_media(session->handle);
break;
} else if(fds[i].revents & POLLIN) {
if(pipe_fd != -1 && fds[i].fd == pipe_fd) {
/* Poll interrupted for a reason, go on */
int code = 0;
(void)read(pipe_fd, &code, sizeof(int));
break;
}
/* Got an RTP/RTCP packet */
if(session->media.audio_rtp_fd != -1 && fds[i].fd == session->media.audio_rtp_fd) {
/* Got something audio (RTP) */
addrlen = sizeof(remote);
bytes = recvfrom(session->media.audio_rtp_fd, buffer, 1500, 0, (struct sockaddr*)&remote, &addrlen);
if(bytes < 0 || !janus_is_rtp(buffer, bytes)) {
/* Failed to read or not an RTP packet? */
continue;
}
pollerrs = 0;
if(!session->media.audio_recv) {
/* Dropping audio packet, we weren't expecting anything */
continue;
}
if(session->media.on_hold && session->media.hold_audio_dir != JANUS_SDP_RECVONLY) {
/* Dropping video packet, the call is on hold and we're not receiving anything */
continue;
}
janus_rtp_header *header = (janus_rtp_header *)buffer;
janus_sip_check_rfc2833(session, buffer, bytes);
if(session->media.audio_ssrc_peer == 0) {
session->media.audio_ssrc_peer = ntohl(header->ssrc);
JANUS_LOG(LOG_VERB, "Got SIP peer audio SSRC: %"SCNu32"\n", session->media.audio_ssrc_peer);
}
/* Is this SRTP? */
if(session->media.has_srtp_remote_audio) {
int buflen = bytes;
srtp_err_status_t res = srtp_unprotect(session->media.audio_srtp_in, buffer, &buflen);
if(res != srtp_err_status_ok && res != srtp_err_status_replay_fail && res != srtp_err_status_replay_old) {
guint32 timestamp = ntohl(header->timestamp);
guint16 seq = ntohs(header->seq_number);
JANUS_LOG(LOG_ERR, "[SIP-%s] Audio SRTP unprotect error: %s (len=%d-->%d, ts=%"SCNu32", seq=%"SCNu16")\n",
session->account.username, janus_srtp_error_str(res), bytes, buflen, timestamp, seq);
continue;
}
bytes = buflen;
}
/* Check if the SSRC changed (e.g., after a re-INVITE or UPDATE) */
janus_rtp_header_update(header, &session->media.acontext, FALSE, 0);
/* Save the frame if we're recording */
header->ssrc = htonl(session->media.audio_ssrc_peer);
janus_recorder_save_frame(session->arc_peer, buffer, bytes);
/* Relay to application */
janus_plugin_rtp rtp = { .mindex = -1, .video = FALSE, .buffer = buffer, .length = bytes };
janus_plugin_rtp_extensions_reset(&rtp.extensions);
/* Add audio-level extension, if present */
if(session->media.audio_level_extension_id != -1) {
gboolean vad = FALSE;
int level = -1;
if(janus_rtp_header_extension_parse_audio_level(buffer, bytes,
session->media.audio_level_extension_id, &vad, &level) == 0) {
rtp.extensions.audio_level = level;
rtp.extensions.audio_level_vad = vad;
}
}
gateway->relay_rtp(session->handle, &rtp);
continue;
} else if(session->media.audio_rtcp_fd != -1 && fds[i].fd == session->media.audio_rtcp_fd) {
/* Got something audio (RTCP) */
addrlen = sizeof(remote);
bytes = recvfrom(session->media.audio_rtcp_fd, buffer, 1500, 0, (struct sockaddr*)&remote, &addrlen);
if(bytes < 0 || !janus_is_rtcp(buffer, bytes)) {
/* Failed to read or not an RTCP packet? */
continue;
}
pollerrs = 0;
if(!session->media.video_recv) {
/* Dropping video packet, we weren't expecting anything */
continue;
}
if(session->media.on_hold && session->media.hold_video_dir != JANUS_SDP_RECVONLY) {
/* Dropping video packet, the call is on hold and we're not receiving anything */
continue;
}
/* Is this SRTCP? */
if(session->media.has_srtp_remote_audio) {
int buflen = bytes;
srtp_err_status_t res = srtp_unprotect_rtcp(session->media.audio_srtp_in, buffer, &buflen);
if(res != srtp_err_status_ok && res != srtp_err_status_replay_fail && res != srtp_err_status_replay_old) {
JANUS_LOG(LOG_ERR, "[SIP-%s] Audio SRTCP unprotect error: %s (len=%d-->%d)\n",
session->account.username, janus_srtp_error_str(res), bytes, buflen);
continue;
}
bytes = buflen;
}
/* Relay to application */
janus_plugin_rtcp rtcp = { .mindex = -1, .video = FALSE, .buffer = buffer, bytes };
gateway->relay_rtcp(session->handle, &rtcp);
continue;
} else if(session->media.video_rtp_fd != -1 && fds[i].fd == session->media.video_rtp_fd) {
/* Got something video (RTP) */
addrlen = sizeof(remote);
bytes = recvfrom(session->media.video_rtp_fd, buffer, 1500, 0, (struct sockaddr*)&remote, &addrlen);
if(bytes < 0 || !janus_is_rtp(buffer, bytes)) {
/* Failed to read or not an RTP packet? */
continue;
}
pollerrs = 0;
janus_rtp_header *header = (janus_rtp_header *)buffer;
if(session->media.video_ssrc_peer == 0) {
session->media.video_ssrc_peer = ntohl(header->ssrc);
JANUS_LOG(LOG_VERB, "Got SIP peer video SSRC: %"SCNu32"\n", session->media.video_ssrc_peer);
}
/* Is this SRTP? */
if(session->media.has_srtp_remote_video) {
int buflen = bytes;
srtp_err_status_t res = srtp_unprotect(session->media.video_srtp_in, buffer, &buflen);
if(res != srtp_err_status_ok && res != srtp_err_status_replay_fail && res != srtp_err_status_replay_old) {
guint32 timestamp = ntohl(header->timestamp);
guint16 seq = ntohs(header->seq_number);
JANUS_LOG(LOG_ERR, "[SIP-%s] Video SRTP unprotect error: %s (len=%d-->%d, ts=%"SCNu32", seq=%"SCNu16")\n",
session->account.username, janus_srtp_error_str(res), bytes, buflen, timestamp, seq);
continue;
}
bytes = buflen;
}
/* Check if the SSRC changed (e.g., after a re-INVITE or UPDATE) */
janus_rtp_header_update(header, &session->media.vcontext, TRUE, 0);
/* Save the frame if we're recording */
header->ssrc = htonl(session->media.video_ssrc_peer);
janus_recorder_save_frame(session->vrc_peer, buffer, bytes);
/* Relay to application */
janus_plugin_rtp rtp = { .mindex = -1, .video = TRUE, .buffer = buffer, .length = bytes };
janus_plugin_rtp_extensions_reset(&rtp.extensions);
/* Add video-orientation extension, if present */
if(session->media.video_orientation_extension_id > 0) {
gboolean c = FALSE, f = FALSE, r1 = FALSE, r0 = FALSE;
if(janus_rtp_header_extension_parse_video_orientation(buffer, bytes,
session->media.video_orientation_extension_id, &c, &f, &r1, &r0) == 0) {
rtp.extensions.video_rotation = 0;
if(r1 && r0)
rtp.extensions.video_rotation = 270;
else if(r1)
rtp.extensions.video_rotation = 180;
else if(r0)
rtp.extensions.video_rotation = 90;
rtp.extensions.video_back_camera = c;
rtp.extensions.video_flipped = f;
}
}
gateway->relay_rtp(session->handle, &rtp);
continue;
} else if(session->media.video_rtcp_fd != -1 && fds[i].fd == session->media.video_rtcp_fd) {
/* Got something video (RTCP) */
addrlen = sizeof(remote);
bytes = recvfrom(session->media.video_rtcp_fd, buffer, 1500, 0, (struct sockaddr*)&remote, &addrlen);
if(bytes < 0 || !janus_is_rtcp(buffer, bytes)) {
/* Failed to read or not an RTCP packet? */
continue;
}
pollerrs = 0;
/* Is this SRTCP? */
if(session->media.has_srtp_remote_video) {
int buflen = bytes;
srtp_err_status_t res = srtp_unprotect_rtcp(session->media.video_srtp_in, buffer, &buflen);
if(res != srtp_err_status_ok && res != srtp_err_status_replay_fail && res != srtp_err_status_replay_old) {
JANUS_LOG(LOG_ERR, "[SIP-%s] Video SRTP unprotect error: %s (len=%d-->%d)\n",
session->account.username, janus_srtp_error_str(res), bytes, buflen);
continue;
}
bytes = buflen;
}
/* Relay to application */
janus_plugin_rtcp rtcp = { .mindex = -1, .video = TRUE, .buffer = buffer, bytes };
gateway->relay_rtcp(session->handle, &rtcp);
continue;
}
}
}
}
/* Cleanup the media session */
janus_mutex_lock(&session->mutex);
janus_sip_media_cleanup(session);
janus_mutex_unlock(&session->mutex);
/* Done */
JANUS_LOG(LOG_VERB, "Leaving SIP relay thread\n");
session->relayer_thread = NULL;
janus_refcount_decrease(&session->ref);
g_thread_unref(g_thread_self());
return NULL;
}
/* Sofia Event thread */
gpointer janus_sip_sofia_thread(gpointer user_data) {
janus_sip_session *session = (janus_sip_session *)user_data;
if(session == NULL) {
g_thread_unref(g_thread_self());
return NULL;
}
if(session->account.username == NULL) {
janus_refcount_decrease(&session->ref);
g_thread_unref(g_thread_self());
return NULL;
}
JANUS_LOG(LOG_VERB, "Joining sofia loop thread (%s)...\n", session->account.username);
session->stack = g_malloc0(sizeof(ssip_t));
su_home_init(session->stack->s_home);
session->stack->session = session;
session->stack->s_nua = NULL;
session->stack->s_nh_r = NULL;
session->stack->s_nh_i = NULL;
session->stack->s_nh_m = NULL;
session->stack->s_root = su_root_create(session->stack);
session->stack->subscriptions = NULL;
janus_mutex_init(&session->stack->smutex);
JANUS_LOG(LOG_VERB, "Setting up sofia stack (sip:%s@%s)\n", session->account.username, local_ip);
char sip_url[128];
char sips_url[128];
char *ipv6;
ipv6 = strstr(local_ip, ":");
if(session->account.force_tcp)
g_snprintf(sip_url, sizeof(sip_url), "sip:%s%s%s:*;transport=tcp", ipv6 ? "[" : "", local_ip, ipv6 ? "]" : "");
else
g_snprintf(sip_url, sizeof(sip_url), "sip:%s%s%s:*;transport=udp", ipv6 ? "[" : "", local_ip, ipv6 ? "]" : "");
g_snprintf(sips_url, sizeof(sips_url), "sips:%s%s%s:*;transport=tls", ipv6 ? "[" : "", local_ip, ipv6 ? "]" : "");
char outbound_options[256] = "use-rport no-validate";
if(keepalive_interval > 0)
janus_strlcat(outbound_options, " options-keepalive", sizeof(outbound_options));
if(!behind_nat)
janus_strlcat(outbound_options, " no-natify", sizeof(outbound_options));
session->stack->s_nua = nua_create(session->stack->s_root,
janus_sip_sofia_callback,
session,
SIPTAG_ALLOW_STR("INVITE, ACK, BYE, CANCEL, OPTIONS, REFER, MESSAGE, INFO, NOTIFY"),
NUTAG_M_USERNAME(session->account.username),
NUTAG_URL(sip_url),
TAG_IF(session->account.sips, NUTAG_SIPS_URL(sips_url)),
TAG_IF(session->account.sips && sips_certs_dir, NUTAG_CERTIFICATE_DIR(sips_certs_dir)),
SIPTAG_USER_AGENT_STR(session->account.user_agent ? session->account.user_agent : user_agent),
NUTAG_KEEPALIVE(keepalive_interval * 1000), /* Sofia expects it in milliseconds */
NUTAG_OUTBOUND(outbound_options),
NUTAG_APPL_METHOD("REFER"), /* We'll respond to incoming REFER messages ourselves */
SIPTAG_SUPPORTED_STR("replaces"), /* Advertise that we support the Replaces header */
SIPTAG_SUPPORTED(NULL),
NTATAG_CANCEL_2543(session->account.rfc2543_cancel),
NTATAG_SIP_T1X64(sip_timer_t1x64),
TAG_NULL());
if(query_contact_header)
nua_get_params(session->stack->s_nua, SIPTAG_FROM_STR(""), TAG_END());
su_root_run(session->stack->s_root);
/* When we get here, we're done */
janus_mutex_lock(&session->stack->smutex);
nua_t *s_nua = session->stack->s_nua;
session->stack->s_nua = NULL;
janus_mutex_unlock(&session->stack->smutex);
if(session->stack->s_nh_r != NULL) {
nua_handle_destroy(session->stack->s_nh_r);
session->stack->s_nh_r = NULL;
}
if(session->stack->s_nh_i != NULL) {
nua_handle_destroy(session->stack->s_nh_i);
session->stack->s_nh_i = NULL;
}
if(session->stack->s_nh_m != NULL) {
nua_handle_destroy(session->stack->s_nh_m);
session->stack->s_nh_m = NULL;
}
janus_mutex_lock(&session->stack->smutex);
if(session->stack->subscriptions != NULL)
g_hash_table_unref(session->stack->subscriptions);
session->stack->subscriptions = NULL;
janus_mutex_unlock(&session->stack->smutex);
nua_destroy(s_nua);
su_root_destroy(session->stack->s_root);
session->stack->s_root = NULL;
janus_refcount_decrease(&session->ref);
JANUS_LOG(LOG_VERB, "Leaving sofia loop thread...\n");
g_thread_unref(g_thread_self());
return NULL;
}
/* Check peer RTP has RFC2833 and push event */
static void janus_sip_check_rfc2833(janus_sip_session *session, char *buffer, int len) {
if(session->media.dtmf_pt <= 0)
return;
janus_rtp_header *rtp_header = (janus_rtp_header *)buffer;
if(rtp_header->type != session->media.dtmf_pt)
return;
int plen = 0;
char *payload_buffer = janus_rtp_payload(buffer, len, &plen);
if(plen < 0 || (size_t)plen < sizeof(janus_rtp_rfc2833_payload))
return;
janus_rtp_rfc2833_payload *rfc2833_payload = (janus_rtp_rfc2833_payload *)payload_buffer;
uint16_t duration = ntohs(rfc2833_payload->duration);
if(rfc2833_payload->end == 0)
return;
/* Set up last dtmf to avoid duplication */
if(session->latest_dtmf.dtmf_event_id == rfc2833_payload->event && session->latest_dtmf.timestamp == rtp_header->timestamp)
return;
session->latest_dtmf.dtmf_event_id = rfc2833_payload->event;
session->latest_dtmf.timestamp = rtp_header->timestamp;
/* Parse dtmf key */
uint16_t dtmf_key;
if(rfc2833_payload->event > 15)
return;
dtmf_key = dtmf_keys[rfc2833_payload->event];
char dtmf_key_str[2];
dtmf_key_str[0] = dtmf_key;
dtmf_key_str[1] = '\0';
/* Notify the application */
json_t *info = json_object();
json_object_set_new(info, "sip", json_string("event"));
json_t *result = json_object();
json_object_set_new(result, "event", json_string("dtmf"));
json_object_set_new(result, "sender", json_string(session->callee));
json_object_set_new(result, "signal", json_string(dtmf_key_str));
json_object_set_new(result, "duration", json_integer(duration));
if(session->callid)
json_object_set_new(info, "call_id", json_string(session->callid));
json_object_set_new(info, "result", result);
int ret = gateway->push_event(session->handle, &janus_sip_plugin, session->transaction, info, NULL);
JANUS_LOG(LOG_VERB, " >> Pushing event to peer: %d (%s)\n", ret, janus_get_api_error(ret));
json_decref(info);
return;
}
/* Helper method to send an RTCP PLI to the SIP peer */
static void janus_sip_rtcp_pli_send(janus_sip_session *session) {
if(!session || g_atomic_int_get(&session->destroyed)) {
JANUS_LOG(LOG_ERR, "No session associated with this handle...\n");
return;
}
if(!janus_sip_call_is_established(session))
return;
if(!session->media.has_video || session->media.video_rtcp_fd == -1)
return;
/* Generate a PLI */
char rtcp_buf[12];
int rtcp_len = 12;
janus_rtcp_pli((char *)&rtcp_buf, rtcp_len);
/* Fix SSRCs as the Janus core does */
JANUS_LOG(LOG_HUGE, "[SIP] Fixing SSRCs (local %u, peer %u)\n",
session->media.video_ssrc, session->media.video_ssrc_peer);
janus_rtcp_fix_ssrc(NULL, (char *)rtcp_buf, rtcp_len, 1, session->media.video_ssrc, session->media.video_ssrc_peer);
/* Is SRTP involved? */
if(session->media.has_srtp_local_video) {
char sbuf[50];
memcpy(&sbuf, rtcp_buf, rtcp_len);
int protected = rtcp_len;
int res = srtp_protect_rtcp(session->media.video_srtp_out, &sbuf, &protected);
if(res != srtp_err_status_ok) {
JANUS_LOG(LOG_ERR, "[SIP-%s] Video SRTCP protect error... %s (len=%d-->%d)...\n",
session->account.username, janus_srtp_error_str(res), rtcp_len, protected);
} else {
/* Forward the message to the peer */
if(send(session->media.video_rtcp_fd, sbuf, protected, 0) < 0) {
JANUS_LOG(LOG_HUGE, "[SIP-%s] Error sending SRTCP video packet... %s (len=%d)...\n",
session->account.username, g_strerror(errno), protected);
}
}
} else {
/* Forward the message to the peer */
if(send(session->media.video_rtcp_fd, rtcp_buf, rtcp_len, 0) < 0) {
JANUS_LOG(LOG_HUGE, "[SIP-%s] Error sending RTCP video packet... %s (len=%d)...\n",
session->account.username, g_strerror(errno), rtcp_len);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment