Example #1
0
        /// <summary>
        /// Sends specified request to the specified data flow.
        /// </summary>
        /// <param name="flow">SIP data flow.</param>
        /// <param name="request">SIP request to send.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception>
        private void SendToFlow(SIP_Flow flow, SIP_Request request)
        {
            if (flow == null)
            {
                throw new ArgumentNullException("flow");
            }
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            #region Contact (RFC 3261 8.1.1.8)

            /*
             *  The Contact header field provides a SIP or SIPS URI that can be used
             *  to contact that specific instance of the UA for subsequent requests.
             *  The Contact header field MUST be present and contain exactly one SIP
             *  or SIPS URI in any request that can result in the establishment of a
             *  dialog.  For the methods defined in this specification, that includes
             *  only the INVITE request.  For these requests, the scope of the
             *  Contact is global.  That is, the Contact header field value contains
             *  the URI at which the UA would like to receive requests, and this URI
             *  MUST be valid even if used in subsequent requests outside of any
             *  dialogs.
             *
             *  If the Request-URI or top Route header field value contains a SIPS
             *  URI, the Contact header field MUST contain a SIPS URI as well.
             */

            SIP_t_ContactParam contact = request.Contact.GetTopMostValue();

            // Add contact header If request-Method can establish dialog and contact header not present.
            if (SIP_Utils.MethodCanEstablishDialog(request.RequestLine.Method) && contact == null)
            {
                SIP_Uri from = (SIP_Uri)request.From.Address.Uri;

                request.Contact.Add((flow.IsSecure ? "sips:" : "sip:") + from.User + "@" + flow.LocalPublicEP.ToString());

                // REMOVE ME: 22.10.2010
                //request.Contact.Add((flow.IsSecure ? "sips:" : "sip:" ) + from.User + "@" + m_pStack.TransportLayer.GetContactHost(flow).ToString());
            }
            // If contact SIP URI and host = auto-allocate, allocate it as needed.
            else if (contact != null && contact.Address.Uri is SIP_Uri && ((SIP_Uri)contact.Address.Uri).Host == "auto-allocate")
            {
                ((SIP_Uri)contact.Address.Uri).Host = flow.LocalPublicEP.ToString();

                // REMOVE ME: 22.10.2010
                //((SIP_Uri)contact.Address.Uri).Host =  m_pStack.TransportLayer.GetContactHost(flow).ToString();
            }

            #endregion

            m_pTransaction = m_pStack.TransactionLayer.CreateClientTransaction(flow, request, true);
            m_pTransaction.ResponseReceived += new EventHandler <SIP_ResponseReceivedEventArgs>(ClientTransaction_ResponseReceived);
            m_pTransaction.TimedOut         += new EventHandler(ClientTransaction_TimedOut);
            m_pTransaction.TransportError   += new EventHandler <ExceptionEventArgs>(ClientTransaction_TransportError);

            // Start transaction processing.
            m_pTransaction.Start();
        }
        /// <summary>
        /// Creates new SIP client transaction for specified request.
        /// </summary>
        /// <param name="request">SIP request.</param>
        /// <param name="destination">Remote destination info.</param>
        /// <param name="addVia">Specified if transaction adds new Via: header. If this value is false,
        /// then its user responsibility to add vlid Via: header to <b>request</b> argument.</param>
        /// <returns>Returns ncreated SIP client transaction.</returns>
        public SIP_ClientTransaction CreateClientTransaction(SIP_Request request,SIP_Destination destination,bool addVia)
        {
            SIP_ClientTransaction transaction = new SIP_ClientTransaction(m_pSipStack,request,destination,addVia);
            m_pClientTransactions.Add(transaction);

            return transaction;
        }
        /// <summary>
        /// Cleans up active transaction.
        /// </summary>
        private void CleanUpActiveTransaction()
        {
            if (m_pTransaction != null)
            {
                // Don't dispose transaction, transaction will dispose itself when done.
                // Otherwise for example failed INVITE won't linger in "Completed" state as it must be.
                // We just release Events processing, because you don't care about them any more.
                m_pTransaction.ResponseReceived -= new EventHandler <SIP_ResponseReceivedEventArgs>(ClientTransaction_ResponseReceived);
                m_pTransaction.TimedOut         -= new EventHandler(ClientTransaction_TimedOut);
                m_pTransaction.TransportError   -= new EventHandler <ExceptionEventArgs>(ClientTransaction_TransportError);

                m_pTransaction = null;
            }
        }
Example #4
0
        /// <summary>
        /// Creates new client transaction.
        /// </summary>
        /// <param name="flow">SIP data flow which is used to send request.</param>
        /// <param name="request">SIP request that transaction will handle.</param>
        /// <param name="addVia">If true, transaction will add <b>Via:</b> header, otherwise it's user responsibility.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception>
        /// <returns>Returns created transaction.</returns>
        public SIP_ClientTransaction CreateClientTransaction(SIP_Flow flow, SIP_Request request, bool addVia)
        {
            if (m_IsDisposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if (flow == null)
            {
                throw new ArgumentNullException("flow");
            }
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            // Add Via:
            if (addVia)
            {
                SIP_t_ViaParm via = new SIP_t_ViaParm();
                via.ProtocolName      = "SIP";
                via.ProtocolVersion   = "2.0";
                via.ProtocolTransport = flow.Transport;
                via.SentBy            = new HostEndPoint("transport_layer_will_replace_it", -1);
                via.Branch            = SIP_t_ViaParm.CreateBranch();
                via.RPort             = 0;
                request.Via.AddToTop(via.ToStringValue());
            }

            lock (m_pClientTransactions){
                SIP_ClientTransaction transaction = new SIP_ClientTransaction(m_pStack, flow, request);
                m_pClientTransactions.Add(transaction.Key, transaction);
                transaction.StateChanged += new EventHandler(delegate(object s, EventArgs e){
                    if (transaction.State == SIP_TransactionState.Terminated)
                    {
                        lock (m_pClientTransactions){
                            m_pClientTransactions.Remove(transaction.Key);
                        }
                    }
                });

                SIP_Dialog dialog = MatchDialog(request);
                if (dialog != null)
                {
                    dialog.AddTransaction(transaction);
                }

                return(transaction);
            }
        }
        /// <summary>
        /// Creates and send CANCEL request to remote target.
        /// </summary>
        private void SendCancel()
        {
            /* RFC 3261 9.1.
             *  The following procedures are used to construct a CANCEL request.  The
             *  Request-URI, Call-ID, To, the numeric part of CSeq, and From header
             *  fields in the CANCEL request MUST be identical to those in the
             *  request being cancelled, including tags.  A CANCEL constructed by a
             *  client MUST have only a single Via header field value matching the
             *  top Via value in the request being cancelled.  Using the same values
             *  for these header fields allows the CANCEL to be matched with the
             *  request it cancels (Section 9.2 indicates how such matching occurs).
             *  However, the method part of the CSeq header field MUST have a value
             *  of CANCEL.  This allows it to be identified and processed as a
             *  transaction in its own right (See Section 17).
             *
             *  If the request being cancelled contains a Route header field, the
             *  CANCEL request MUST include that Route header field's values.
             *
             *      This is needed so that stateless proxies are able to route CANCEL
             *      requests properly.
             */

            SIP_Request cancelRequest = new SIP_Request(SIP_Methods.CANCEL);

            cancelRequest.RequestLine.Uri = this.Request.RequestLine.Uri;
            cancelRequest.Via.Add(this.Request.Via.GetTopMostValue().ToStringValue());
            cancelRequest.CallID = this.Request.CallID;
            cancelRequest.From   = this.Request.From;
            cancelRequest.To     = this.Request.To;
            cancelRequest.CSeq   = new SIP_t_CSeq(this.Request.CSeq.SequenceNumber, SIP_Methods.CANCEL);
            foreach (SIP_t_AddressParam route in this.Request.Route.GetAllValues())
            {
                cancelRequest.Route.Add(route.ToStringValue());
            }
            cancelRequest.MaxForwards = 70;

            // We must use same data flow to send CANCEL what sent initial request.
            SIP_ClientTransaction transaction = this.Stack.TransactionLayer.CreateClientTransaction(this.Flow, cancelRequest, false);

            transaction.Start();
        }
        /// <summary>
        /// Cleans up any resources being used.
        /// </summary>
        public void Dispose()
        {
            lock (m_pLock){
                if (m_State == SIP_RequestSenderState.Disposed)
                {
                    return;
                }
                m_State = SIP_RequestSenderState.Disposed;

                OnDisposed();

                this.ResponseReceived = null;
                this.Completed        = null;
                this.Disposed         = null;

                m_pStack       = null;
                m_pRequest     = null;
                m_pCredentials = null;
                m_pHops        = null;
                m_pTransaction = null;
                m_pLock        = null;
            }
        }
        /// <summary>
        /// Matches SIP response to client transaction. If not matching transaction found, returns null.
        /// </summary>
        /// <param name="response">SIP response to match.</param>
        internal SIP_ClientTransaction MatchClientTransaction(SIP_Response response)
        {
            /* RFC 3261 17.1.3 Matching Responses to Client Transactions.
             *  1.  If the response has the same value of the branch parameter in
             *      the top Via header field as the branch parameter in the top
             *      Via header field of the request that created the transaction.
             *
             *  2.  If the method parameter in the CSeq header field matches the
             *      method of the request that created the transaction.  The
             *      method is needed since a CANCEL request constitutes a
             *      different transaction, but shares the same value of the branch
             *      parameter.
             */

            SIP_ClientTransaction retVal = null;

            string transactionID = response.Via.GetTopMostValue().Branch + "-" + response.CSeq.RequestMethod;

            lock (m_pClientTransactions){
                m_pClientTransactions.TryGetValue(transactionID, out retVal);
            }

            return(retVal);
        }
        /// <summary>
        /// Sends request to the specified flow.
        /// </summary>
        /// <param name="flow">Data flow.</param>
        /// <param name="request">SIP request.</param>
        /// <param name="transaction">Owner client transaction or null if stateless sending.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception>
        /// <exception cref="ArgumentException">Is raised when any of the arguments contains invalid value.</exception>
        internal void SendRequest(SIP_Flow flow,SIP_Request request,SIP_ClientTransaction transaction)
        {
            if(m_IsDisposed){
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if(flow == null){
                throw new ArgumentNullException("flow");
            }
            if(request == null){
                throw new ArgumentNullException("request");
            }
            if(request.Via.GetTopMostValue() == null){
                throw new ArgumentException("Argument 'request' doesn't contain required Via: header field.");
            }

            // Set sent-by
            SIP_t_ViaParm via = request.Via.GetTopMostValue();
            via.ProtocolTransport = flow.Transport;
            // Via sent-by is used only to send responses when request maker data flow is not active.
            // Normally this never used, so just report first local listening point as sent-by.
            HostEndPoint sentBy = null;
            foreach(IPBindInfo bind in this.BindInfo){
                if(flow.Transport == SIP_Transport.UDP && bind.Protocol == BindInfoProtocol.UDP){
                    if(!string.IsNullOrEmpty(bind.HostName)){
                        sentBy = new HostEndPoint(bind.HostName,bind.Port);
                    }
                    else{
                        sentBy = new HostEndPoint(flow.LocalEP.Address.ToString(),bind.Port);
                    }
                    break;
                }
                else if(flow.Transport == SIP_Transport.TLS && bind.Protocol == BindInfoProtocol.TCP && bind.SslMode == SslMode.SSL){
                    if(!string.IsNullOrEmpty(bind.HostName)){
                        sentBy = new HostEndPoint(bind.HostName,bind.Port);
                    }
                    else{
                        sentBy = new HostEndPoint(flow.LocalEP.Address.ToString(),bind.Port);
                    }
                    break;
                }
                else if(flow.Transport == SIP_Transport.TCP && bind.Protocol == BindInfoProtocol.TCP){
                    if(!string.IsNullOrEmpty(bind.HostName)){
                        sentBy = new HostEndPoint(bind.HostName,bind.Port);
                    }
                    else{
                        sentBy = new HostEndPoint(flow.LocalEP.Address.ToString(),bind.Port);
                    }
                    break;
                }
            }
            // No local end point for sent-by, just use flow local end point for it.
            if(sentBy == null){
                via.SentBy = new HostEndPoint(flow.LocalEP);
            }
            else{
                via.SentBy = sentBy;
            }

            // Send request.
            flow.Send(request);

            // Log.
            if(m_pStack.Logger != null){
                byte[] requestData = request.ToByteData();

                m_pStack.Logger.AddWrite(
                    Guid.NewGuid().ToString(),
                    null,
                    0,
                    "Request [" + (transaction == null ? "" : "transactionID='" + transaction.ID + "';") + "method='" + request.RequestLine.Method + "'; cseq='" + request.CSeq.SequenceNumber + "'; " +
                    "transport='" + flow.Transport + "'; size='" + requestData.Length + "'; sent '" + flow.LocalEP + "' -> '" + flow.RemoteEP + "'.",
                    flow.LocalEP,
                    flow.RemoteEP,
                    requestData
                );
            }
        }
        /// <summary>
        /// Creates new client transaction.
        /// </summary>
        /// <param name="flow">SIP data flow which is used to send request.</param>
        /// <param name="request">SIP request that transaction will handle.</param>
        /// <param name="addVia">If true, transaction will add <b>Via:</b> header, otherwise it's user responsibility.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception>
        /// <returns>Returns created transaction.</returns>
        public SIP_ClientTransaction CreateClientTransaction(SIP_Flow flow,SIP_Request request,bool addVia)
        {
            if(m_IsDisposed){
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if(flow == null){
                throw new ArgumentNullException("flow");
            }
            if(request == null){
                throw new ArgumentNullException("request");
            }

            // Add Via:
            if(addVia){
                SIP_t_ViaParm via = new SIP_t_ViaParm();
                via.ProtocolName = "SIP";
                via.ProtocolVersion = "2.0";
                via.ProtocolTransport = flow.Transport;
                via.SentBy = new HostEndPoint("transport_layer_will_replace_it",-1);
                via.Branch = SIP_t_ViaParm.CreateBranch();
                via.RPort = 0;
                request.Via.AddToTop(via.ToStringValue());
            }

            lock(m_pClientTransactions){
                SIP_ClientTransaction transaction = new SIP_ClientTransaction(m_pStack,flow,request);
                m_pClientTransactions.Add(transaction.Key,transaction);
                transaction.StateChanged += new EventHandler(delegate(object s,EventArgs e){
                    if(transaction.State == SIP_TransactionState.Terminated){
                        lock(m_pClientTransactions){
                            m_pClientTransactions.Remove(transaction.Key);
                        }
                    }
                });

                return transaction;
            }
        }
 /// <summary>
 /// Default constructor.
 /// </summary>
 /// <param name="stack">Reference to SIP stack.</param>
 /// <param name="transaction">Client transaction what response it is. This value can be null if no matching client response.</param>
 /// <param name="response">Received response.</param>
 internal SIP_ResponseReceivedEventArgs(SIP_Stack stack,SIP_ClientTransaction transaction,SIP_Response response)
 {
     m_pStack       = stack;
     m_pResponse    = response;
     m_pTransaction = transaction;
 }
            /// <summary>
            /// Cleans up any resources being used.
            /// </summary>
            public void Dispose()
            {
                lock(m_pLock){
                    if(m_IsDisposed){
                        return;
                    }
                    m_IsDisposed = true;

                    m_pOwner.TargetHandler_Disposed(this);

                    m_pOwner = null;
                    m_pRequest = null;
                    m_pTargetUri = null;
                    m_pHops = null;
                    if(m_pTransaction != null){
                        m_pTransaction.Dispose();
                        m_pTransaction = null;
                    }
                    if(m_pTimerC != null){
                        m_pTimerC.Dispose();
                        m_pTimerC = null;
                    }
                }
            }
 /// <summary>
 /// Removes specified transaction from SIP clinet transactions collection.
 /// </summary>
 /// <param name="transaction">Transaction to remove.</param>
 internal void RemoveClientTransaction(SIP_ClientTransaction transaction)
 {
     m_pClientTransactions.Remove(transaction);
 }
        /// <summary>
        /// Cleans up active transaction.
        /// </summary>
        private void CleanUpActiveTransaction()
        {
            if(m_pTransaction != null){
                // Don't dispose transaction, transaction will dispose itself when done.
                // Otherwise for example failed INVITE won't linger in "Completed" state as it must be.
                // We just release Events processing, because you don't care about them any more.
                m_pTransaction.ResponseReceived -= new EventHandler<SIP_ResponseReceivedEventArgs>(ClientTransaction_ResponseReceived);
                m_pTransaction.TimedOut -= new EventHandler(ClientTransaction_TimedOut);
                m_pTransaction.TransportError -= new EventHandler<ExceptionEventArgs>(ClientTransaction_TransportError);

                m_pTransaction = null;
            }
        }
        /// <summary>
        /// Sends SIP response to caller. If proxy context is in B2BUA mode, new response is generated 
        /// as needed.
        /// </summary>
        /// <param name="transaction">Client transaction what response it is.</param>
        /// <param name="response">Response to send.</param>
        private void SendResponse(SIP_ClientTransaction transaction,SIP_Response response)
        {
            if(m_IsB2BUA){
                /* draft-marjou-sipping-b2bua-00 4.1.3.
                    When the UAC side of the B2BUA receives the downstream SIP response
                    of a forwarded request, its associated UAS creates an upstream
                    response (except for 100 responses).  The creation of the Via, Max-
                    Forwards, Call-Id, CSeq, Record-Route and Contact header fields
                    follows the rules of [2].  The Record-Route header fields of the
                    downstream response are not copied in the new upstream response, as
                    Record-Route is meaningful for the downstream dialog.  The UAS SHOULD
                    copy other header fields and body from the downstream response into
                    this upstream response before sending it.
                */

                SIP_Request originalRequest = m_pServerTransaction.Request;

                // We need to use caller original request to construct response from proxied response.
                SIP_Response b2buaResponse = response.Copy();
                b2buaResponse.Via.RemoveAll();
                b2buaResponse.Via.AddToTop(originalRequest.Via.GetTopMostValue().ToStringValue());
                b2buaResponse.CallID = originalRequest.CallID;
                b2buaResponse.CSeq = originalRequest.CSeq;
                b2buaResponse.Contact.RemoveAll();
                //b2buaResponse.Contact.Add(m_pProxy.CreateContact(originalRequest.From.Address).ToStringValue());
                b2buaResponse.RecordRoute.RemoveAll();

                b2buaResponse.Allow.RemoveAll();
                b2buaResponse.Supported.RemoveAll();
                // Accept to non ACK,BYE request.
                if(originalRequest.RequestLine.Method != SIP_Methods.ACK && originalRequest.RequestLine.Method != SIP_Methods.BYE){
                    b2buaResponse.Allow.Add("INVITE,ACK,OPTIONS,CANCEL,BYE,PRACK");
                }
                // Supported to non ACK request.
                if(originalRequest.RequestLine.Method != SIP_Methods.ACK){
                    b2buaResponse.Supported.Add("100rel,timer");
                }
                // Remove Require: header.
                b2buaResponse.Require.RemoveAll();

                m_pServerTransaction.SendResponse(b2buaResponse);

                // If INVITE 2xx response do call here.
                if(response.CSeq.RequestMethod.ToUpper() == SIP_Methods.INVITE && response.StatusCodeType == SIP_StatusCodeType.Success){
                    m_pProxy.B2BUA.AddCall(m_pServerTransaction.Dialog,transaction.Dialog);
                }
            }
            else{
                m_pServerTransaction.SendResponse(response);
            }
        }
        /// <summary>
        /// Processes received response.
        /// </summary>
        /// <param name="handler">Target handler what received response.</param>
        /// <param name="transaction">Client transaction what response it is.</param>
        /// <param name="response">Response received.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>handler</b>,<b>transaction</b> or <b>response</b> is null reference.</exception>
        private void ProcessResponse(TargetHandler handler,SIP_ClientTransaction transaction,SIP_Response response)
        {
            if(handler == null){
                throw new ArgumentNullException("handler");
            }
            if(transaction == null){
                throw new ArgumentNullException("transaction");
            }
            if(response == null){
                throw new ArgumentNullException("response");
            }

            /* RFC 3261 16.7 Response Processing.
                Steps 1 - 2 handled in TargetHandler.

                3.  Remove the topmost Via
                4.  Add the response to the response context
                5.  Check to see if this response should be forwarded immediately
                6.  When necessary, choose the best final response from the response context.
                    If no final response has been forwarded after every client
                    transaction associated with the response context has been terminated,
                    the proxy must choose and forward the "best" response from those it
                    has seen so far.

                The following processing MUST be performed on each response that is
                forwarded.  It is likely that more than one response to each request
                will be forwarded: at least each provisional and one final response.

                7.  Aggregate authorization header field values if necessary
                8.  Optionally rewrite Record-Route header field values
                9.  Forward the response
                10. Generate any necessary CANCEL requests
            */

            bool forwardResponse = false;

            lock(m_pLock){

                #region 3.  Remove the topmost Via

                /*
                    The proxy removes the topmost Via header field value from the
                    response.

                    If no Via header field values remain in the response, the
                    response was meant for this element and MUST NOT be forwarded.
                    The remainder of the processing described in this section is
                    not performed on this message, the UAC processing rules
                    described in Section 8.1.3 are followed instead (transport
                    layer processing has already occurred).

                    This will happen, for instance, when the element generates
                    CANCEL requests as described in Section 10.

                    NOTE: We MAY NOT do it for B2BUA, skip it for B2BUA
                */
                if(!m_IsB2BUA){
                    response.Via.RemoveTopMostValue();
                    if(response.Via.GetAllValues().Length == 0){
                        return;
                    }
                }

                #endregion

                #region 4.  Add the response to the response context

                /*
                    Final responses received are stored in the response context
                    until a final response is generated on the server transaction
                    associated with this context.  The response may be a candidate
                    for the best final response to be returned on that server
                    transaction.  Information from this response may be needed in
                    forming the best response, even if this response is not chosen.

                    If the proxy chooses to recurse on any contacts in a 3xx
                    response by adding them to the target set, it MUST remove them
                    from the response before adding the response to the response
                    context.  However, a proxy SHOULD NOT recurse to a non-SIPS URI
                    if the Request-URI of the original request was a SIPS URI.  If
                    the proxy recurses on all of the contacts in a 3xx response,
                    the proxy SHOULD NOT add the resulting contactless response to
                    the response context.

                    Removing the contact before adding the response to the response
                    context prevents the next element upstream from retrying a
                    location this proxy has already attempted.

                    3xx responses may contain a mixture of SIP, SIPS, and non-SIP
                    URIs.  A proxy may choose to recurse on the SIP and SIPS URIs
                    and place the remainder into the response context to be
                    returned, potentially in the final response.
                */

                if(response.StatusCodeType == SIP_StatusCodeType.Redirection && !m_NoRecurse && !handler.IsRecursed){
                    // Get SIP contacts and remove them from response.
                    SIP_t_ContactParam[] contacts = response.Contact.GetAllValues();
                    // Remove all contacts from response, we add non-SIP URIs back.
                    response.Contact.RemoveAll();
                    foreach(SIP_t_ContactParam contact in contacts){
                        // SIP URI add it to fork list.
                        if(contact.Address.IsSipOrSipsUri){
                            m_pTargets.Enqueue(new TargetHandler(this,null,(SIP_Uri)contact.Address.Uri,m_AddRecordRoute,true));
                        }
                        // Add specified URI back to response.
                        else{
                            response.Contact.Add(contact.ToStringValue());
                        }
                    }

                    // There are remaining non-SIP contacts, so we need to add the response to reponses collection.
                    if(response.Contact.GetAllValues().Length > 0){
                        m_pResponses.Add(response);
                    }

                    // Handle forking
                    if(m_pTargets.Count > 0){
                        if(m_ForkingMode == SIP_ForkingMode.Parallel){
                            while(m_pTargets.Count > 0){
                                TargetHandler h = m_pTargets.Dequeue();
                                m_pTargetsHandlers.Add(handler);
                                h.Start();
                            }
                        }
                        // Just fork next.
                        else{
                            TargetHandler h = m_pTargets.Dequeue();
                            m_pTargetsHandlers.Add(handler);
                            h.Start();
                        }

                        // Because we forked request to new target(s), we don't need to do steps 5 - 10.
                        return;
                    }
                }
                // Not 3xx response or recursing disabled.
                else{
                    m_pResponses.Add(response);
                }

                #endregion

                #region 5.  Check to see if this response should be forwarded immediately

                /*
                    Until a final response has been sent on the server transaction,
                    the following responses MUST be forwarded immediately:

                    -  Any provisional response other than 100 (Trying)

                    -  Any 2xx response

                    If a 6xx response is received, it is not immediately forwarded,
                    but the stateful proxy SHOULD cancel all client pending
                    transactions as described in Section 10, and it MUST NOT create
                    any new branches in this context.

                    After a final response has been sent on the server transaction,
                    the following responses MUST be forwarded immediately:

                    -  Any 2xx response to an INVITE request
                */

                if(!m_IsFinalResponseSent){
                    if(response.StatusCodeType == SIP_StatusCodeType.Provisional && response.StatusCode != 100){
                        forwardResponse = true;
                    }
                    else if(response.StatusCodeType == SIP_StatusCodeType.Success){
                        forwardResponse = true;
                    }
                    else if(response.StatusCodeType == SIP_StatusCodeType.GlobalFailure){
                        CancelAllTargets();
                    }
                }
                else{
                    if(response.StatusCodeType == SIP_StatusCodeType.Success && m_pServerTransaction.Request.RequestLine.Method == SIP_Methods.INVITE){
                        forwardResponse = true;
                    }
                }

                #endregion

                #region x.  Handle sequential forking

                /*
                    Sequential Search: In a sequential search, a proxy server attempts
                    each contact address in sequence, proceeding to the next one
                    only after the previous has generated a final response.  A 2xx
                    or 6xx class final response always terminates a sequential
                    search.
                */
                if(m_ForkingMode == SIP_ForkingMode.Sequential && response.StatusCodeType != SIP_StatusCodeType.Provisional){
                    if(response.StatusCodeType == SIP_StatusCodeType.Success){
                        // Do nothing, 2xx will be always forwarded and step 10. Cancels all targets.
                    }
                    else if(response.StatusCodeType == SIP_StatusCodeType.GlobalFailure){
                        // Do nothing, 6xx is already handled in setp 5.
                    }
                    else if(m_pTargets.Count > 0){
                        TargetHandler h = m_pTargets.Dequeue();
                        m_pTargetsHandlers.Add(handler);
                        h.Start();

                        // Skip all next steps, we will get new responses from new target.
                        return;
                    }
                }

                #endregion

                #region 6.  When necessary, choose the best final response from the response context

                /*
                    A stateful proxy MUST send a final response to a response
                    context's server transaction if no final responses have been
                    immediately forwarded by the above rules and all client
                    transactions in this response context have been terminated.

                    The stateful proxy MUST choose the "best" final response among
                    those received and stored in the response context.

                    If there are no final responses in the context, the proxy MUST
                    send a 408 (Request Timeout) response to the server
                    transaction.

                */

                if(!m_IsFinalResponseSent && !forwardResponse && m_pTargets.Count == 0){
                    bool mustChooseBestFinalResponse = true;
                    // Check if all transactions terminated.
                    foreach(TargetHandler h in m_pTargetsHandlers){
                        if(!h.IsCompleted){
                            mustChooseBestFinalResponse = false;
                            break;
                        }
                    }

                    if(mustChooseBestFinalResponse){
                        response = GetBestFinalResponse();
                        if(response == null){
                            response = this.Proxy.Stack.CreateResponse(SIP_ResponseCodes.x408_Request_Timeout,m_pServerTransaction.Request);
                        }

                        forwardResponse = true;
                    }
                }

                #endregion

                if(forwardResponse){

                    #region 7.  Aggregate authorization header field values if necessary

                    /*
                        If the selected response is a 401 (Unauthorized) or 407 (Proxy Authentication Required),
                        the proxy MUST collect any WWW-Authenticate and Proxy-Authenticate header field values
                        from all other 401 (Unauthorized) and 407 (Proxy Authentication Required) responses
                        received so far in this response context and add them to this response without
                        modification before forwarding. The resulting 401 (Unauthorized) or 407 (Proxy
                        Authentication Required) response could have several WWW-Authenticate AND
                        Proxy-Authenticate header field values.

                        This is necessary because any or all of the destinations the request was forwarded to
                        may have requested credentials.  The client needs to receive all of those challenges and
                        supply credentials for each of them when it retries the request.
                    */
                    if(response.StatusCode == 401 || response.StatusCode == 407){
                        foreach(SIP_Response resp in m_pResponses.ToArray()){
                            if(response != resp && (resp.StatusCode == 401 || resp.StatusCode == 407)){
                                // WWW-Authenticate
                                foreach(SIP_HeaderField hf in resp.WWWAuthenticate.HeaderFields){
                                    resp.WWWAuthenticate.Add(hf.Value);
                                }
                                // Proxy-Authenticate
                                foreach(SIP_HeaderField hf in resp.ProxyAuthenticate.HeaderFields){
                                    resp.ProxyAuthenticate.Add(hf.Value);
                                }
                            }
                        }
                    }

                    #endregion

                    #region 8.  Optionally rewrite Record-Route header field values

                    // This is optional so we currently won't do that.

                    #endregion

                    #region 9.  Forward the response

                    SendResponse(transaction,response);
                    if(response.StatusCodeType != SIP_StatusCodeType.Provisional){
                        m_IsFinalResponseSent = true;
                    }

                    #endregion

                    #region 10. Generate any necessary CANCEL requests

                    /*
                        If the forwarded response was a final response, the proxy MUST
                        generate a CANCEL request for all pending client transactions
                        associated with this response context.
                    */
                    if(response.StatusCodeType != SIP_StatusCodeType.Provisional){
                        CancelAllTargets();
                    }

                    #endregion
                }
            }
        }
            /// <summary>
            /// Sends specified request to the specified data flow.
            /// </summary>
            /// <param name="flow">SIP data flow.</param>
            /// <param name="request">SIP request to send.</param>
            /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception>
            private void SendToFlow(SIP_Flow flow,SIP_Request request)
            {
                if(flow == null){
                    throw new ArgumentNullException("flow");
                }
                if(request == null){
                    throw new ArgumentNullException("request");
                }

                /* NAT traversal.
                    When we do record routing, store request sender flow info and request target flow info.
                    Now the tricky part, how proxy later which flow is target (because both sides can send requests).
                      Sender-flow will store from-tag to flow and target-flow will store flowID only (Because we don't know to-tag).
                      Later if request to-tag matches(incoming request), use that flow, otherwise(outgoing request) other flow.

                    flowInfo: sender-flow "/" target-flow
                              sender-flow = from-tag ":" flowID
                              target-flow = flowID
                */
                if(m_AddRecordRoute && request.From.Tag != null){
                    string flowInfo = request.From.Tag + ":" + m_pOwner.ServerTransaction.Flow.ID + "/" + flow.ID;
                    ((SIP_Uri)request.RecordRoute.GetTopMostValue().Address.Uri).Parameters.Add("flowInfo",flowInfo);
                }

                /* RFC 3261 16.6 Request Forwarding.
                        Common Steps 1 - 7 are done in target Init().

                        8.  Add a Via header field value
                        9.  Add a Content-Length header field if necessary
                        10. Forward the new request
                        11. Set timer C
                */

                #region 8.  Add a Via header field value

                // Skip, Client transaction will add it.

                #endregion

                #region 9.  Add a Content-Length header field if necessary

                // Skip, our SIP_Message class is smart and do it when ever it's needed.

                #endregion

                #region 10. Forward the new request

                m_pTransaction = m_pOwner.Proxy.Stack.TransactionLayer.CreateClientTransaction(flow,request,true);
                m_pTransaction.ResponseReceived += new EventHandler<SIP_ResponseReceivedEventArgs>(ClientTransaction_ResponseReceived);
                m_pTransaction.TimedOut += new EventHandler(ClientTransaction_TimedOut);
                m_pTransaction.TransportError += new EventHandler<ExceptionEventArgs>(ClientTransaction_TransportError);
                m_pTransaction.Disposed += new EventHandler(m_pTransaction_Disposed);

                // Start transaction processing.
                m_pTransaction.Start();

                #endregion

                #region 11. Set timer C

                /* 11. Set timer C
                    In order to handle the case where an INVITE request never
                    generates a final response, the TU uses a timer which is called
                    timer C.  Timer C MUST be set for each client transaction when
                    an INVITE request is proxied.  The timer MUST be larger than 3
                    minutes.  Section 16.7 bullet 2 discusses how this timer is
                    updated with provisional responses, and Section 16.8 discusses
                    processing when it fires.
                */
                if(request.RequestLine.Method == SIP_Methods.INVITE){
                    m_pTimerC = new TimerEx();
                    m_pTimerC.AutoReset = false;
                    m_pTimerC.Interval = 3 * 60 * 1000;
                    m_pTimerC.Elapsed += new ElapsedEventHandler(m_pTimerC_Elapsed);
                }

                #endregion
            }
 /// <summary>
 /// Cleans up acitve hop resources.
 /// </summary>
 private void CleanUpActiveHop()
 {
     if(m_pTimerC != null){
         m_pTimerC.Dispose();
         m_pTimerC = null;
     }
     if(m_pTransaction != null){
         m_pTransaction.Dispose();
         m_pTransaction = null;
     }
 }
Example #18
0
        private void HandleAck(SIP_Dialog dialog, SIP_ClientTransaction transaction)
        {
            if (dialog == null)
            {
                throw new ArgumentNullException("dialog");
            }
            if (transaction == null)
            {
                throw new ArgumentNullException("transaction");
            }

            /* RFC 3261 6.
                The ACK for a 2xx response to an INVITE request is a separate transaction.
              
               RFC 3261 13.2.2.4.
                The UAC core MUST generate an ACK request for each 2xx received from
                the transaction layer.  The header fields of the ACK are constructed
                in the same way as for any request sent within a dialog (see Section
                12) with the exception of the CSeq and the header fields related to
                authentication.  The sequence number of the CSeq header field MUST be
                the same as the INVITE being acknowledged, but the CSeq method MUST
                be ACK.  The ACK MUST contain the same credentials as the INVITE.  If
                the 2xx contains an offer (based on the rules above), the ACK MUST
                carry an answer in its body.
            */

            SIP_t_ViaParm via = new SIP_t_ViaParm();
            via.ProtocolName = "SIP";
            via.ProtocolVersion = "2.0";
            via.ProtocolTransport = transaction.Flow.Transport;
            via.SentBy = new HostEndPoint(transaction.Flow.LocalEP);
            via.Branch = SIP_t_ViaParm.CreateBranch();
            via.RPort = 0;

            SIP_Request ackRequest = dialog.CreateRequest(SIP_Methods.ACK);
            ackRequest.Via.AddToTop(via.ToStringValue());
            ackRequest.CSeq = new SIP_t_CSeq(transaction.Request.CSeq.SequenceNumber, SIP_Methods.ACK);
            // Authorization
            foreach (SIP_HeaderField h in transaction.Request.Authorization.HeaderFields)
            {
                ackRequest.Authorization.Add(h.Value);
            }
            // Proxy-Authorization 
            foreach (SIP_HeaderField h in transaction.Request.ProxyAuthorization.HeaderFields)
            {
                ackRequest.Authorization.Add(h.Value);
            }

            // Send ACK.
            SendAck(dialog, ackRequest);

            // Start receive 2xx retransmissions.
            transaction.ResponseReceived += delegate(object sender, SIP_ResponseReceivedEventArgs e)
            {
                if (dialog.State == SIP_DialogState.Disposed || dialog.State == SIP_DialogState.Terminated)
                {
                    return;
                }

                // Don't send ACK for forked 2xx, our sent BYE(to all early dialogs) or their early timer will kill these dialogs.
                // Send ACK only to our accepted dialog 2xx response retransmission.
                if (e.Response.From.Tag == ackRequest.From.Tag && e.Response.To.Tag == ackRequest.To.Tag)
                {
                    SendAck(dialog, ackRequest);
                }
            };
        }
        /// <summary>
        /// Creates new SIP UAC dialog.
        /// </summary>
        /// <param name="transaction">Owner transaction what forces to create dialog.</param>
        /// <param name="response">SIP response what causes dialog creation.</param>
        /// <returns>Returns new SIP dialog.</returns>
        internal SIP_Dialog CreateDialog(SIP_ClientTransaction transaction,SIP_Response response)
        {
            // TODO: ren EnsureDialog

            SIP_Dialog dialog = new SIP_Dialog(m_pSipStack,response);
            m_pDialogs.Add(dialog);

            return dialog;
        }
        /// <summary>
        /// Sends specified request to the specified data flow.
        /// </summary>
        /// <param name="flow">SIP data flow.</param>
        /// <param name="request">SIP request to send.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception>
        private void SendToFlow(SIP_Flow flow,SIP_Request request)
        {
            if(flow == null){
                throw new ArgumentNullException("flow");
            }
            if(request == null){
                throw new ArgumentNullException("request");
            }

            #region Contact (RFC 3261 8.1.1.8)

            /*
                The Contact header field provides a SIP or SIPS URI that can be used
                to contact that specific instance of the UA for subsequent requests.
                The Contact header field MUST be present and contain exactly one SIP
                or SIPS URI in any request that can result in the establishment of a
                dialog.  For the methods defined in this specification, that includes
                only the INVITE request.  For these requests, the scope of the
                Contact is global.  That is, the Contact header field value contains
                the URI at which the UA would like to receive requests, and this URI
                MUST be valid even if used in subsequent requests outside of any
                dialogs.

                If the Request-URI or top Route header field value contains a SIPS
                URI, the Contact header field MUST contain a SIPS URI as well.
            */

            SIP_t_ContactParam contact = request.Contact.GetTopMostValue();

            // Add contact header If request-Method can establish dialog and contact header not present.            
            if(SIP_Utils.MethodCanEstablishDialog(request.RequestLine.Method) && contact == null){    
                SIP_Uri from = (SIP_Uri)request.From.Address.Uri;

                request.Contact.Add((flow.IsSecure ? "sips:" : "sip:" ) + from.User + "@" + flow.LocalPublicEP.ToString());

                // REMOVE ME: 22.10.2010
                //request.Contact.Add((flow.IsSecure ? "sips:" : "sip:" ) + from.User + "@" + m_pStack.TransportLayer.GetContactHost(flow).ToString());
            }
            // If contact SIP URI and host = auto-allocate, allocate it as needed.
            else if(contact != null && contact.Address.Uri is SIP_Uri && ((SIP_Uri)contact.Address.Uri).Host == "auto-allocate"){
                ((SIP_Uri)contact.Address.Uri).Host =  flow.LocalPublicEP.ToString();

                // REMOVE ME: 22.10.2010
                //((SIP_Uri)contact.Address.Uri).Host =  m_pStack.TransportLayer.GetContactHost(flow).ToString();
            }

            #endregion
         
            m_pTransaction = m_pStack.TransactionLayer.CreateClientTransaction(flow,request,true);  
            m_pTransaction.ResponseReceived += new EventHandler<SIP_ResponseReceivedEventArgs>(ClientTransaction_ResponseReceived);
            m_pTransaction.TimedOut += new EventHandler(ClientTransaction_TimedOut);
            m_pTransaction.TransportError += new EventHandler<ExceptionEventArgs>(ClientTransaction_TransportError);
 
            // Start transaction processing.
            m_pTransaction.Start();
        }
 /// <summary>
 /// Default constructor.
 /// </summary>
 /// <param name="stack">Reference to SIP stack.</param>
 /// <param name="transaction">Client transaction what response it is. This value can be null if no matching client response.</param>
 /// <param name="response">Received response.</param>
 internal SIP_ResponseReceivedEventArgs(SIP_Stack stack, SIP_ClientTransaction transaction, SIP_Response response)
 {
     m_pStack       = stack;
     m_pResponse    = response;
     m_pTransaction = transaction;
 }
        /// <summary>
        /// Cleans up any resources being used.
        /// </summary>
        public void Dispose()
        {
            lock(m_pLock){
                if(m_State == SIP_RequestSenderState.Disposed){
                    return;
                }
                m_State = SIP_RequestSenderState.Disposed;

                OnDisposed();

                this.ResponseReceived = null;
                this.Completed = null;
                this.Disposed = null;
                
                m_pStack       = null;
                m_pRequest     = null;
                m_pCredentials = null;
                m_pHops        = null;
                m_pTransaction = null;
                m_pLock        = null;
            }
        }