/// <summary> /// Authenticates SIP request. This method also sends all needed replys to request sender. /// </summary> /// <param name="e">Request event arguments.</param> /// <param name="userName">If authentication sucessful, then authenticated user name is stored to this variable.</param> /// <returns>Returns true if request was authenticated.</returns> internal bool AuthenticateRequest(SIP_RequestReceivedEventArgs e, out string userName) { userName = null; SIP_t_Credentials credentials = SIP_Utils.GetCredentials(e.Request, m_pStack.Realm); // No credentials for our realm. if (credentials == null) { SIP_Response notAuthenticatedResponse = m_pStack.CreateResponse(SIP_ResponseCodes.x407_Proxy_Authentication_Required, e.Request); notAuthenticatedResponse.ProxyAuthenticate.Add( new Auth_HttpDigest(m_pStack.Realm, m_pStack.DigestNonceManager.CreateNonce(), m_Opaque). ToChallange()); e.ServerTransaction.SendResponse(notAuthenticatedResponse); return(false); } Auth_HttpDigest auth = new Auth_HttpDigest(credentials.AuthData, e.Request.RequestLine.Method); // Check opaque validity. if (auth.Opaque != m_Opaque) { SIP_Response notAuthenticatedResponse = m_pStack.CreateResponse( SIP_ResponseCodes.x407_Proxy_Authentication_Required + ": Opaque value won't match !", e.Request); notAuthenticatedResponse.ProxyAuthenticate.Add( new Auth_HttpDigest(m_pStack.Realm, m_pStack.DigestNonceManager.CreateNonce(), m_Opaque). ToChallange()); // Send response e.ServerTransaction.SendResponse(notAuthenticatedResponse); return(false); } // Check nonce validity. if (!m_pStack.DigestNonceManager.NonceExists(auth.Nonce)) { SIP_Response notAuthenticatedResponse = m_pStack.CreateResponse( SIP_ResponseCodes.x407_Proxy_Authentication_Required + ": Invalid nonce value !", e.Request); notAuthenticatedResponse.ProxyAuthenticate.Add( new Auth_HttpDigest(m_pStack.Realm, m_pStack.DigestNonceManager.CreateNonce(), m_Opaque). ToChallange()); // Send response e.ServerTransaction.SendResponse(notAuthenticatedResponse); return(false); } // Valid nonce, consume it so that nonce can't be used any more. else { m_pStack.DigestNonceManager.RemoveNonce(auth.Nonce); } SIP_AuthenticateEventArgs eArgs = OnAuthenticate(auth); // Authenticate failed. if (!eArgs.Authenticated) { SIP_Response notAuthenticatedResponse = m_pStack.CreateResponse( SIP_ResponseCodes.x407_Proxy_Authentication_Required + ": Authentication failed.", e.Request); notAuthenticatedResponse.ProxyAuthenticate.Add( new Auth_HttpDigest(m_pStack.Realm, m_pStack.DigestNonceManager.CreateNonce(), m_Opaque). ToChallange()); // Send response e.ServerTransaction.SendResponse(notAuthenticatedResponse); return(false); } userName = auth.UserName; return(true); }
/// <summary> /// This method is called when SIP stack receives new request. /// </summary> /// <param name="sender">Sender.</param> /// <param name="e">Event data.</param> private void m_pStack_RequestReceived(object sender, SIP_RequestReceivedEventArgs e) { OnRequestReceived(e); }
/// <summary> /// This method is called when new request is received. /// </summary> /// <param name="e">Request event arguments.</param> internal void OnRequestReceived(SIP_RequestReceivedEventArgs e) { SIP_Request request = e.Request; if (request.RequestLine.Method == SIP_Methods.CANCEL) { /* RFC 3261 9.2. If the UAS did not find a matching transaction for the CANCEL according to the procedure above, it SHOULD respond to the CANCEL with a 481 (Call Leg/Transaction Does Not Exist). Regardless of the method of the original request, as long as the CANCEL matched an existing transaction, the UAS answers the CANCEL request itself with a 200 (OK) response. */ SIP_ServerTransaction trToCancel = m_pProxy.Stack.TransactionLayer.MatchCancelToTransaction(e.Request); if (trToCancel != null) { trToCancel.Cancel(); //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x200_Ok)); } else { //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist)); } } // We never should ge BYE here, because transport layer must match it to dialog. else if (request.RequestLine.Method == SIP_Methods.BYE) { /* RFC 3261 15.1.2. If the BYE does not match an existing dialog, the UAS core SHOULD generate a 481 (Call/Transaction Does Not Exist) response and pass that to the server transaction. */ //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist)); } // We never should ge ACK here, because transport layer must match it to dialog. else if (request.RequestLine.Method == SIP_Methods.ACK) { // ACK is response less request, so we may not return error to it. } // B2BUA must respond to OPTIONS request, not to forward it. else if (request.RequestLine.Method == SIP_Methods.OPTIONS) { /* SIP_Response response = e.Request.CreateResponse(SIP_ResponseCodes.x200_Ok); // Add Allow to non ACK response. if(e.Request.RequestLine.Method != SIP_Methods.ACK){ response.Allow.Add("INVITE,ACK,OPTIONS,CANCEL,BYE,PRACK,MESSAGE,UPDATE"); } // Add Supported to 2xx non ACK response. if(response.StatusCodeType == SIP_StatusCodeType.Success && e.Request.RequestLine.Method != SIP_Methods.ACK){ response.Supported.Add("100rel,timer"); } e.ServerTransaction.SendResponse(response);*/ } // We never should get PRACK here, because transport layer must match it to dialog. else if (request.RequestLine.Method == SIP_Methods.PRACK) { //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist)); } // We never should get UPDATE here, because transport layer must match it to dialog. else if (request.RequestLine.Method == SIP_Methods.UPDATE) { //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist)); } else { /* draft-marjou-sipping-b2bua-00 4.1.3. When the UAS of the B2BUA receives an upstream SIP request, its associated UAC generates a new downstream SIP request with its new Via, Max-Forwards, Call-Id, CSeq, and Contact header fields. Route header fields of the upstream request are copied in the downstream request, except the first Route header if it is under the responsibility of the B2BUA. Record-Route header fields of the upstream request are not copied in the new downstream request, as Record-Route is only meaningful for the upstream dialog. The UAC SHOULD copy other header fields and body from the upstream request into this downstream request before sending it. */ SIP_Request b2buaRequest = e.Request.Copy(); b2buaRequest.Via.RemoveAll(); b2buaRequest.MaxForwards = 70; b2buaRequest.CallID = SIP_t_CallID.CreateCallID().CallID; b2buaRequest.CSeq.SequenceNumber = 1; b2buaRequest.Contact.RemoveAll(); // b2buaRequest.Contact.Add(m_pProxy.CreateContact(b2buaRequest.To.Address).ToStringValue()); if (b2buaRequest.Route.Count > 0 && m_pProxy.IsLocalRoute( SIP_Uri.Parse(b2buaRequest.Route.GetTopMostValue().Address.Uri.ToString()))) { b2buaRequest.Route.RemoveTopMostValue(); } b2buaRequest.RecordRoute.RemoveAll(); // Remove our Authorization header if it's there. foreach ( SIP_SingleValueHF<SIP_t_Credentials> header in b2buaRequest.ProxyAuthorization.HeaderFields) { try { Auth_HttpDigest digest = new Auth_HttpDigest(header.ValueX.AuthData, b2buaRequest.RequestLine.Method); if (m_pProxy.Stack.Realm == digest.Realm) { b2buaRequest.ProxyAuthorization.Remove(header); } } catch { // We don't care errors here. This can happen if remote server xxx auth method here and // we don't know how to parse it, so we leave it as is. } } //--- Add/replace default fields. ------------------------------------------ b2buaRequest.Allow.RemoveAll(); b2buaRequest.Supported.RemoveAll(); // Accept to non ACK,BYE request. if (request.RequestLine.Method != SIP_Methods.ACK && request.RequestLine.Method != SIP_Methods.BYE) { b2buaRequest.Allow.Add("INVITE,ACK,OPTIONS,CANCEL,BYE,PRACK"); } // Supported to non ACK request. if (request.RequestLine.Method != SIP_Methods.ACK) { b2buaRequest.Supported.Add("100rel,timer"); } // Remove Require: header. b2buaRequest.Require.RemoveAll(); // RFC 4028 7.4. For re-INVITE and UPDATE we need to add Session-Expires and Min-SE: headers. if (request.RequestLine.Method == SIP_Methods.INVITE || request.RequestLine.Method == SIP_Methods.UPDATE) { b2buaRequest.SessionExpires = new SIP_t_SessionExpires(m_pProxy.Stack.SessionExpries, "uac"); b2buaRequest.MinSE = new SIP_t_MinSE(m_pProxy.Stack.MinimumSessionExpries); } // Forward request. m_pProxy.ForwardRequest(true, e, b2buaRequest, false); } }
/// <summary> /// Raises <b>RequestReceived</b> event. /// </summary> /// <param name="request">SIP request.</param> protected void OnRequestReceived(SIP_RequestReceivedEventArgs request) { if (RequestReceived != null) { RequestReceived(this, request); } }
/// <summary> /// Handles SUBSCRIBE method. /// </summary> /// <param name="e">Request event arguments.</param> internal void Subscribe(SIP_RequestReceivedEventArgs e) { }
/// <summary> /// Is called when callee sends new request. /// </summary> /// <param name="e">Event data.</param> private void m_pCallee_RequestReceived(SIP_RequestReceivedEventArgs e) { /* SIP_Request request = m_pCaller.CreateRequest(e.Request.RequestLine.Method); CopyMessage(e.Request,request,new string[]{"Via:","Call-Id:","To:","From:","CSeq:","Contact:","Route:","Record-Route:","Max-Forwards:","Allow:","Require:","Supported:"}); // Remove our Authentication header if it's there. foreach(SIP_SingleValueHF<SIP_t_Credentials> header in request.ProxyAuthorization.HeaderFields){ try{ Auth_HttpDigest digest = new Auth_HttpDigest(header.ValueX.AuthData,request.RequestLine.Method); if(m_pOwner.Stack.Realm == digest.Realm){ request.ProxyAuthorization.Remove(header); } } catch{ // We don't care errors here. This can happen if remote server xxx auth method here and // we don't know how to parse it, so we leave it as is. } } SIP_ClientTransaction clientTransaction = m_pCaller.CreateTransaction(request); clientTransaction.ResponseReceived += new EventHandler<SIP_ResponseReceivedEventArgs>(m_pCaller_ResponseReceived); clientTransaction.Tag = e.ServerTransaction; clientTransaction.Start();*/ }
/// <summary> /// Forwards specified request to target recipient. /// </summary> /// <param name="statefull">Specifies if request is sent statefully or statelessly.</param> /// <param name="e">Request event arguments.</param> /// <param name="request">SIP request to forward.</param> /// <param name="addRecordRoute">Specifies if Record-Route header filed is added.</param> internal void ForwardRequest(bool statefull, SIP_RequestReceivedEventArgs e, SIP_Request request, bool addRecordRoute) { List <SIP_ProxyTarget> targetSet = new List <SIP_ProxyTarget>(); List <NetworkCredential> credentials = new List <NetworkCredential>(); SIP_Uri route = null; /* RFC 3261 16. * 1. Validate the request (Section 16.3) * 1. Reasonable Syntax * 2. URI scheme * 3. Max-Forwards * 4. (Optional) Loop Detection * 5. Proxy-Require * 6. Proxy-Authorization * 2. Preprocess routing information (Section 16.4) * 3. Determine target(s) for the request (Section 16.5) * 4. Forward the request (Section 16.6) */ #region 1. Validate the request (Section 16.3) // 1.1 Reasonable Syntax. // SIP_Message will do it. // 1.2 URI scheme check. if (!SIP_Utils.IsSipOrSipsUri(request.RequestLine.Uri.ToString())) { // TODO: SIP_GatewayEventArgs eArgs = OnGetGateways("uriScheme", "userName"); // No suitable gateway or authenticated user has no access. if (eArgs.Gateways.Count == 0) { e.ServerTransaction.SendResponse( m_pStack.CreateResponse(SIP_ResponseCodes.x416_Unsupported_URI_Scheme, e.Request)); return; } } // 1.3 Max-Forwards. if (request.MaxForwards <= 0) { e.ServerTransaction.SendResponse(m_pStack.CreateResponse( SIP_ResponseCodes.x483_Too_Many_Hops, request)); return; } // 1.4 (Optional) Loop Detection. // Skip. // 1.5 Proxy-Require. // TODO: // 1.6 Proxy-Authorization. // We need to auth all foreign calls. if (!SIP_Utils.IsSipOrSipsUri(request.RequestLine.Uri.ToString()) || !OnIsLocalUri(((SIP_Uri)request.RequestLine.Uri).Host)) { // We need to pass-through ACK. if (request.RequestLine.Method == SIP_Methods.ACK) { } else if (!AuthenticateRequest(e)) { return; } } #endregion #region 2. Preprocess routing information (Section 16.4). /* * The proxy MUST inspect the Request-URI of the request. If the * Request-URI of the request contains a value this proxy previously * placed into a Record-Route header field (see Section 16.6 item 4), * the proxy MUST replace the Request-URI in the request with the last * value from the Route header field, and remove that value from the * Route header field. The proxy MUST then proceed as if it received * this modified request. * * If the first value in the Route header field indicates this proxy, * the proxy MUST remove that value from the request. */ // Strict route. if (SIP_Utils.IsSipOrSipsUri(request.RequestLine.Uri.ToString()) && IsLocalRoute(((SIP_Uri)request.RequestLine.Uri))) { request.RequestLine.Uri = request.Route.GetAllValues()[request.Route.GetAllValues().Length - 1].Address.Uri; SIP_t_AddressParam[] routes = request.Route.GetAllValues(); route = (SIP_Uri)routes[routes.Length - 1].Address.Uri; request.Route.RemoveLastValue(); } // Loose route. else if (request.Route.GetAllValues().Length > 0 && IsLocalRoute(SIP_Uri.Parse(request.Route.GetTopMostValue().Address.Uri.ToString()))) { route = (SIP_Uri)request.Route.GetTopMostValue().Address.Uri; request.Route.RemoveTopMostValue(); } #endregion #region 3. Determine target(s) for the request (Section 16.5) /* 3. Determine target(s) for the request (Section 16.5) * Next, the proxy calculates the target(s) of the request. The set of * targets will either be predetermined by the contents of the request * or will be obtained from an abstract location service. Each target * in the set is represented as a URI. * * If the domain of the Request-URI indicates a domain this element is * not responsible for, the Request-URI MUST be placed into the target * set as the only target, and the element MUST proceed to the task of * Request Forwarding (Section 16.6). * * If the target set for the request has not been predetermined as * described above, this implies that the element is responsible for the * domain in the Request-URI, and the element MAY use whatever mechanism * it desires to determine where to send the request. Any of these * mechanisms can be modeled as accessing an abstract Location Service. * This may consist of obtaining information from a location service * created by a SIP Registrar, reading a database, consulting a presence * server, utilizing other protocols, or simply performing an * algorithmic substitution on the Request-URI. When accessing the * location service constructed by a registrar, the Request-URI MUST * first be canonicalized as described in Section 10.3 before being used * as an index. The output of these mechanisms is used to construct the * target set. */ // Non-SIP // Foreign SIP // Local SIP // FIX ME: we may have tel: here SIP_Uri requestUri = (SIP_Uri)e.Request.RequestLine.Uri; // Proxy is not responsible for the domain in the Request-URI. if (!OnIsLocalUri(requestUri.Host)) { /* 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 */ SIP_Flow targetFlow = null; string flowInfo = (route != null && route.Parameters["flowInfo"] != null) ? route.Parameters["flowInfo"].Value : null; if (flowInfo != null && request.To.Tag != null) { string flow1Tag = flowInfo.Substring(0, flowInfo.IndexOf(':')); string flow1ID = flowInfo.Substring(flowInfo.IndexOf(':') + 1, flowInfo.IndexOf('/') - flowInfo.IndexOf(':') - 1); string flow2ID = flowInfo.Substring(flowInfo.IndexOf('/') + 1); if (flow1Tag == request.To.Tag) { targetFlow = m_pStack.TransportLayer.GetFlow(flow1ID); } else { ; targetFlow = m_pStack.TransportLayer.GetFlow(flow2ID); } } targetSet.Add(new SIP_ProxyTarget(requestUri, targetFlow)); } // Proxy is responsible for the domain in the Request-URI. else { // TODO: tel: //SIP_Uri requestUri = SIP_Uri.Parse(e.Request.Uri); // Try to get AOR from registrar. SIP_Registration registration = m_pRegistrar.GetRegistration(requestUri.Address); // We have AOR specified in request-URI in registrar server. if (registration != null) { // Add all AOR SIP contacts to target set. foreach (SIP_RegistrationBinding binding in registration.Bindings) { if (binding.ContactURI is SIP_Uri && binding.TTL > 0) { targetSet.Add(new SIP_ProxyTarget((SIP_Uri)binding.ContactURI, binding.Flow)); } } } // We don't have AOR specified in request-URI in registrar server. else { // If the Request-URI indicates a resource at this proxy that does not // exist, the proxy MUST return a 404 (Not Found) response. if (!OnAddressExists(requestUri.Address)) { e.ServerTransaction.SendResponse( m_pStack.CreateResponse(SIP_ResponseCodes.x404_Not_Found, e.Request)); return; } } } // If the target set remains empty after applying all of the above, the proxy MUST return an error response, // which SHOULD be the 480 (Temporarily Unavailable) response. if (targetSet.Count == 0) { e.ServerTransaction.SendResponse( m_pStack.CreateResponse(SIP_ResponseCodes.x480_Temporarily_Unavailable, e.Request)); return; } #endregion #region 4. Forward the request (Section 16.6) #region Statefull if (statefull) { // Create proxy context that will be responsible for forwarding request. SIP_ProxyContext proxyContext = new SIP_ProxyContext(this, e.ServerTransaction, request, addRecordRoute, m_ForkingMode, (ProxyMode & SIP_ProxyMode.B2BUA) != 0, false, false, targetSet.ToArray(), credentials.ToArray()); m_pProxyContexts.Add(proxyContext); proxyContext.Start(); } #endregion #region Stateless else { /* RFC 3261 16.6 Request Forwarding. * For each target, the proxy forwards the request following these steps: * 1. Make a copy of the received request * 2. Update the Request-URI * 3. Update the Max-Forwards header field * 4. Optionally add a Record-route header field value * 5. Optionally add additional header fields * 6. Postprocess routing information * 7. Determine the next-hop address, port, and transport * 8. Add a Via header field value * 9. Add a Content-Length header field if necessary * 10. Forward the new request */ /* RFC 3261 16.11 Stateless Proxy. * o A stateless proxy MUST choose one and only one target from the target set. This choice * MUST only rely on fields in the message and time-invariant properties of the server. In * particular, a retransmitted request MUST be forwarded to the same destination each time * it is processed. Furthermore, CANCEL and non-Routed ACK requests MUST generate the same * choice as their associated INVITE. * * However, a stateless proxy cannot simply use a random number generator to compute * the first component of the branch ID, as described in Section 16.6 bullet 8. * This is because retransmissions of a request need to have the same value, and * a stateless proxy cannot tell a retransmission from the original request. * * We just use: "z9hG4bK-" + md5(topmost branch) */ bool isStrictRoute = false; SIP_Hop[] hops = null; #region 1. Make a copy of the received request SIP_Request forwardRequest = request.Copy(); #endregion #region 2. Update the Request-URI forwardRequest.RequestLine.Uri = targetSet[0].TargetUri; #endregion #region 3. Update the Max-Forwards header field forwardRequest.MaxForwards--; #endregion #region 4. Optionally add a Record-route header field value #endregion #region 5. Optionally add additional header fields #endregion #region 6. Postprocess routing information /* 6. Postprocess routing information. * * If the copy contains a Route header field, the proxy MUST inspect the URI in its first value. * If that URI does not contain an lr parameter, the proxy MUST modify the copy as follows: * - The proxy MUST place the Request-URI into the Route header * field as the last value. * * - The proxy MUST then place the first Route header field value * into the Request-URI and remove that value from the Route header field. */ if (forwardRequest.Route.GetAllValues().Length > 0 && !forwardRequest.Route.GetTopMostValue().Parameters.Contains("lr")) { forwardRequest.Route.Add(forwardRequest.RequestLine.Uri.ToString()); forwardRequest.RequestLine.Uri = SIP_Utils.UriToRequestUri(forwardRequest.Route.GetTopMostValue().Address.Uri); forwardRequest.Route.RemoveTopMostValue(); isStrictRoute = true; } #endregion #region 7. Determine the next-hop address, port, and transport /* 7. Determine the next-hop address, port, and transport. * The proxy MAY have a local policy to send the request to a * specific IP address, port, and transport, independent of the * values of the Route and Request-URI. Such a policy MUST NOT be * used if the proxy is not certain that the IP address, port, and * transport correspond to a server that is a loose router. * However, this mechanism for sending the request through a * specific next hop is NOT RECOMMENDED; instead a Route header * field should be used for that purpose as described above. * * In the absence of such an overriding mechanism, the proxy * applies the procedures listed in [4] as follows to determine * where to send the request. If the proxy has reformatted the * request to send to a strict-routing element as described in * step 6 above, the proxy MUST apply those procedures to the * Request-URI of the request. Otherwise, the proxy MUST apply * the procedures to the first value in the Route header field, if * present, else the Request-URI. The procedures will produce an * ordered set of (address, port, transport) tuples. * Independently of which URI is being used as input to the * procedures of [4], if the Request-URI specifies a SIPS * resource, the proxy MUST follow the procedures of [4] as if the * input URI were a SIPS URI. * * As described in [4], the proxy MUST attempt to deliver the * message to the first tuple in that set, and proceed through the * set in order until the delivery attempt succeeds. * * For each tuple attempted, the proxy MUST format the message as * appropriate for the tuple and send the request using a new * client transaction as detailed in steps 8 through 10. * * Since each attempt uses a new client transaction, it represents * a new branch. Thus, the branch parameter provided with the Via * header field inserted in step 8 MUST be different for each * attempt. * * If the client transaction reports failure to send the request * or a timeout from its state machine, the proxy continues to the * next address in that ordered set. If the ordered set is * exhausted, the request cannot be forwarded to this element in * the target set. The proxy does not need to place anything in * the response context, but otherwise acts as if this element of * the target set returned a 408 (Request Timeout) final response. */ SIP_Uri uri = null; if (isStrictRoute) { uri = (SIP_Uri)forwardRequest.RequestLine.Uri; } else if (forwardRequest.Route.GetTopMostValue() != null) { uri = (SIP_Uri)forwardRequest.Route.GetTopMostValue().Address.Uri; } else { uri = (SIP_Uri)forwardRequest.RequestLine.Uri; } hops = m_pStack.GetHops(uri, forwardRequest.ToByteData().Length, ((SIP_Uri)forwardRequest.RequestLine.Uri).IsSecure); if (hops.Length == 0) { if (forwardRequest.RequestLine.Method != SIP_Methods.ACK) { e.ServerTransaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x503_Service_Unavailable + ": No hop(s) for target.", forwardRequest)); } return; } #endregion #region 8. Add a Via header field value forwardRequest.Via.AddToTop( "SIP/2.0/transport-tl-addign sentBy-tl-assign-it;branch=z9hG4bK-" + Core.ComputeMd5(request.Via.GetTopMostValue().Branch, true)); // Add 'flowID' what received request, you should use the same flow to send response back. // For more info see RFC 3261 18.2.2. forwardRequest.Via.GetTopMostValue().Parameters.Add("flowID", request.Flow.ID); #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 try { try { if (targetSet[0].Flow != null) { m_pStack.TransportLayer.SendRequest(targetSet[0].Flow, request); return; } } catch { m_pStack.TransportLayer.SendRequest(request, null, hops[0]); } } catch (SIP_TransportException x) { string dummy = x.Message; if (forwardRequest.RequestLine.Method != SIP_Methods.ACK) { /* RFC 3261 16.9 Handling Transport Errors * If the transport layer notifies a proxy of an error when it tries to * forward a request (see Section 18.4), the proxy MUST behave as if the * forwarded request received a 503 (Service Unavailable) response. */ e.ServerTransaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x503_Service_Unavailable + ": Transport error.", forwardRequest)); } } #endregion } #endregion #endregion }
/// <summary> /// This method is called when new request is received. /// </summary> /// <param name="e">Request event arguments.</param> private void OnRequestReceived(SIP_RequestReceivedEventArgs e) { SIP_Request request = e.Request; try{ // Statefull if ((m_ProxyMode & SIP_ProxyMode.Statefull) != 0) { // Statefull proxy is transaction statefull proxy only, // what don't create dialogs and keep dialog state. /* RFC 3261 16.10. * StateFull proxy: * If a matching response context is found, the element MUST * immediately return a 200 (OK) response to the CANCEL request. * * If a response context is not found, the element does not have any * knowledge of the request to apply the CANCEL to. It MUST statelessly * forward the CANCEL request (it may have statelessly forwarded the * associated request previously). */ if (e.Request.RequestLine.Method == SIP_Methods.CANCEL) { // Don't do server transaction before we get CANCEL matching transaction. SIP_ServerTransaction trToCancel = m_pStack.TransactionLayer.MatchCancelToTransaction(e.Request); if (trToCancel != null) { trToCancel.Cancel(); e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok, request)); } else { ForwardRequest(false, e, true); } } // ACK never creates transaction, it's always passed directly to transport layer. else if (e.Request.RequestLine.Method == SIP_Methods.ACK) { ForwardRequest(false, e, true); } else { ForwardRequest(true, e, true); } } // B2BUA else if ((m_ProxyMode & SIP_ProxyMode.B2BUA) != 0) { m_pB2BUA.OnRequestReceived(e); } // Stateless else if ((m_ProxyMode & SIP_ProxyMode.Stateless) != 0) { // Stateless proxy don't do transaction, just forwards all. ForwardRequest(false, e, true); } else { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x501_Not_Implemented, request)); } } catch (Exception x) { try{ m_pStack.TransportLayer.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": " + x.Message, e.Request)); } catch { // Skip transport layer exception if send fails. } // Don't raise OnError for transport errors. if (!(x is SIP_TransportException)) { m_pStack.OnError(x); } } }
/// <summary> /// Authenticates SIP request. This method also sends all needed replys to request sender. /// </summary> /// <param name="e">Request event arguments.</param> /// <param name="userName">If authentication sucessful, then authenticated user name is stored to this variable.</param> /// <returns>Returns true if request was authenticated.</returns> internal bool AuthenticateRequest(SIP_RequestReceivedEventArgs e, out string userName) { userName = null; SIP_t_Credentials credentials = SIP_Utils.GetCredentials(e.Request, m_pStack.Realm); // No credentials for our realm. if (credentials == null) { SIP_Response notAuthenticatedResponse = m_pStack.CreateResponse(SIP_ResponseCodes.x407_Proxy_Authentication_Required, e.Request); notAuthenticatedResponse.ProxyAuthenticate.Add( new Auth_HttpDigest(m_pStack.Realm, m_pStack.DigestNonceManager.CreateNonce(), m_Opaque). ToChallange()); e.ServerTransaction.SendResponse(notAuthenticatedResponse); return false; } Auth_HttpDigest auth = new Auth_HttpDigest(credentials.AuthData, e.Request.RequestLine.Method); // Check opaque validity. if (auth.Opaque != m_Opaque) { SIP_Response notAuthenticatedResponse = m_pStack.CreateResponse( SIP_ResponseCodes.x407_Proxy_Authentication_Required + ": Opaque value won't match !", e.Request); notAuthenticatedResponse.ProxyAuthenticate.Add( new Auth_HttpDigest(m_pStack.Realm, m_pStack.DigestNonceManager.CreateNonce(), m_Opaque). ToChallange()); // Send response e.ServerTransaction.SendResponse(notAuthenticatedResponse); return false; } // Check nonce validity. if (!m_pStack.DigestNonceManager.NonceExists(auth.Nonce)) { SIP_Response notAuthenticatedResponse = m_pStack.CreateResponse( SIP_ResponseCodes.x407_Proxy_Authentication_Required + ": Invalid nonce value !", e.Request); notAuthenticatedResponse.ProxyAuthenticate.Add( new Auth_HttpDigest(m_pStack.Realm, m_pStack.DigestNonceManager.CreateNonce(), m_Opaque). ToChallange()); // Send response e.ServerTransaction.SendResponse(notAuthenticatedResponse); return false; } // Valid nonce, consume it so that nonce can't be used any more. else { m_pStack.DigestNonceManager.RemoveNonce(auth.Nonce); } SIP_AuthenticateEventArgs eArgs = OnAuthenticate(auth); // Authenticate failed. if (!eArgs.Authenticated) { SIP_Response notAuthenticatedResponse = m_pStack.CreateResponse( SIP_ResponseCodes.x407_Proxy_Authentication_Required + ": Authentication failed.", e.Request); notAuthenticatedResponse.ProxyAuthenticate.Add( new Auth_HttpDigest(m_pStack.Realm, m_pStack.DigestNonceManager.CreateNonce(), m_Opaque). ToChallange()); // Send response e.ServerTransaction.SendResponse(notAuthenticatedResponse); return false; } userName = auth.UserName; return true; }
/// <summary> /// Handles NOTIFY method. /// </summary> /// <param name="e">Request event arguments.</param> internal void Notify(SIP_RequestReceivedEventArgs e) {}
/// <summary> /// Handles SUBSCRIBE method. /// </summary> /// <param name="e">Request event arguments.</param> internal void Subscribe(SIP_RequestReceivedEventArgs e) {}
/// <summary> /// Handles REGISTER method. /// </summary> /// <param name="e">Request event arguments.</param> internal void Register(SIP_RequestReceivedEventArgs e) { /* RFC 3261 10.3 Processing REGISTER Requests. 1. The registrar inspects the Request-URI to determine whether it has access to bindings for the domain identified in the Request-URI. If not, and if the server also acts as a proxy server, the server SHOULD forward the request to the addressed domain, following the general behavior for proxying messages described in Section 16. 2. To guarantee that the registrar supports any necessary extensions, the registrar MUST process the Require header field. 3. A registrar SHOULD authenticate the UAC. 4. The registrar SHOULD determine if the authenticated user is authorized to modify registrations for this address-of-record. For example, a registrar might consult an authorization database that maps user names to a list of addresses-of-record for which that user has authorization to modify bindings. If the authenticated user is not authorized to modify bindings, the registrar MUST return a 403 (Forbidden) and skip the remaining steps. 5. The registrar extracts the address-of-record from the To header field of the request. If the address-of-record is not valid for the domain in the Request-URI, the registrar MUST send a 404 (Not Found) response and skip the remaining steps. The URI MUST then be converted to a canonical form. To do that, all URI parameters MUST be removed (including the user-param), and any escaped characters MUST be converted to their unescaped form. The result serves as an index into the list of bindings. 6. The registrar checks whether the request contains the Contact header field. If not, it skips to the last step. If the Contact header field is present, the registrar checks if there is one Contact field value that contains the special value "*" and an Expires field. If the request has additional Contact fields or an expiration time other than zero, the request is invalid, and the server MUST return a 400 (Invalid Request) and skip the remaining steps. If not, the registrar checks whether the Call-ID agrees with the value stored for each binding. If not, it MUST remove the binding. If it does agree, it MUST remove the binding only if the CSeq in the request is higher than the value stored for that binding. Otherwise, the update MUST be aborted and the request fails. 7. The registrar now processes each contact address in the Contact header field in turn. For each address, it determines the expiration interval as follows: - If the field value has an "expires" parameter, that value MUST be taken as the requested expiration. - If there is no such parameter, but the request has an Expires header field, that value MUST be taken as the requested expiration. - If there is neither, a locally-configured default value MUST be taken as the requested expiration. The registrar MAY choose an expiration less than the requested expiration interval. If and only if the requested expiration interval is greater than zero AND smaller than one hour AND less than a registrar-configured minimum, the registrar MAY reject the registration with a response of 423 (Interval Too Brief). This response MUST contain a Min-Expires header field that states the minimum expiration interval the registrar is willing to honor. It then skips the remaining steps. For each address, the registrar then searches the list of current bindings using the URI comparison rules. If the binding does not exist, it is tentatively added. If the binding does exist, the registrar checks the Call-ID value. If the Call-ID value in the existing binding differs from the Call-ID value in the request, the binding MUST be removed if the expiration time is zero and updated otherwise. If they are the same, the registrar compares the CSeq value. If the value is higher than that of the existing binding, it MUST update or remove the binding as above. If not, the update MUST be aborted and the request fails. This algorithm ensures that out-of-order requests from the same UA are ignored. Each binding record records the Call-ID and CSeq values from the request. The binding updates MUST be committed (that is, made visible to the proxy or redirect server) if and only if all binding updates and additions succeed. If any one of them fails (for example, because the back-end database commit failed), the request MUST fail with a 500 (Server Error) response and all tentative binding updates MUST be removed. 8. The registrar returns a 200 (OK) response. The response MUST contain Contact header field values enumerating all current bindings. Each Contact value MUST feature an "expires" parameter indicating its expiration interval chosen by the registrar. The response SHOULD include a Date header field. */ SIP_ServerTransaction transaction = e.ServerTransaction; SIP_Request request = e.Request; SIP_Uri to = null; string userName = ""; // Probably we need to do validate in SIP stack. #region Validate request if (SIP_Utils.IsSipOrSipsUri(request.To.Address.Uri.ToString())) { to = (SIP_Uri) request.To.Address.Uri; } else { transaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x400_Bad_Request + ": To: value must be SIP or SIPS URI.", request)); return; } #endregion #region 1. Check if we are responsible for Request-URI domain // if(m_pProxy.OnIsLocalUri(e.Request.Uri)){ // } // TODO: #endregion #region 2. Check that all required extentions supported #endregion #region 3. Authenticate request if (!m_pProxy.AuthenticateRequest(e, out userName)) { return; } #endregion #region 4. Check if user user is authorized to modify registrations // We do this in next step(5.). #endregion #region 5. Check if address of record exists if (!m_pProxy.OnAddressExists(to.Address)) { transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x404_Not_Found, request)); return; } else if (!OnCanRegister(userName, to.Address)) { transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x403_Forbidden, request)); return; } #endregion #region 6. Process * Contact if exists // Check if we have star contact. SIP_t_ContactParam starContact = null; foreach (SIP_t_ContactParam c in request.Contact.GetAllValues()) { if (c.IsStarContact) { starContact = c; break; } } // We have star contact. if (starContact != null) { if (request.Contact.GetAllValues().Length > 1) { transaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x400_Bad_Request + ": RFC 3261 10.3.6 -> If star(*) present, only 1 contact allowed.", request)); return; } else if (starContact.Expires != 0) { transaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x400_Bad_Request + ": RFC 3261 10.3.6 -> star(*) contact parameter 'expires' value must be always '0'.", request)); return; } // Remove bindings. SIP_Registration reg = m_pRegistrations[to.Address]; if (reg != null) { foreach (SIP_RegistrationBinding b in reg.Bindings) { if (request.CallID != b.CallID || request.CSeq.SequenceNumber > b.CSeqNo) { b.Remove(); } } } } #endregion #region 7. Process Contact values if (starContact == null) { SIP_Registration reg = m_pRegistrations[to.Address]; if (reg == null) { reg = new SIP_Registration(userName, to.Address); m_pRegistrations.Add(reg); } // We may do updates in batch only. // We just validate all values then do update(this ensures that update doesn't fail). // Check expires and CSeq. foreach (SIP_t_ContactParam c in request.Contact.GetAllValues()) { if (c.Expires == -1) { c.Expires = request.Expires; } if (c.Expires == -1) { c.Expires = m_pProxy.Stack.MinimumExpireTime; } // We must accept 0 values - means remove contact. if (c.Expires != 0 && c.Expires < m_pProxy.Stack.MinimumExpireTime) { SIP_Response resp = m_pStack.CreateResponse( SIP_ResponseCodes.x423_Interval_Too_Brief, request); resp.MinExpires = m_pProxy.Stack.MinimumExpireTime; transaction.SendResponse(resp); return; } SIP_RegistrationBinding currentBinding = reg.GetBinding(c.Address.Uri); if (currentBinding != null && currentBinding.CallID == request.CallID && request.CSeq.SequenceNumber < currentBinding.CSeqNo) { transaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x400_Bad_Request + ": CSeq value out of order.", request)); return; } } // Do binding updates. reg.AddOrUpdateBindings(e.ServerTransaction.Flow, request.CallID, request.CSeq.SequenceNumber, request.Contact.GetAllValues()); } #endregion #region 8. Create 200 OK response and return all current bindings SIP_Response response = m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok, request); response.Date = DateTime.Now; SIP_Registration registration = m_pRegistrations[to.Address]; if (registration != null) { foreach (SIP_RegistrationBinding b in registration.Bindings) { // Don't list expired bindings what wait to be disposed. if (b.TTL > 1) { response.Header.Add("Contact:", b.ToContactValue()); } } } // Add Authentication-Info:, then client knows next nonce. response.AuthenticationInfo.Add("qop=\"auth\",nextnonce=\"" + m_pStack.DigestNonceManager.CreateNonce() + "\""); transaction.SendResponse(response); #endregion }
/// <summary> /// This method is called when new request is received. /// </summary> /// <param name="e">Request event arguments.</param> private void OnRequestReceived(SIP_RequestReceivedEventArgs e) { /* RFC 3261 16.12. ????????? Forward does all thse steps. * 1. The proxy will inspect the Request-URI. If it indicates a * resource owned by this proxy, the proxy will replace it with * the results of running a location service. Otherwise, the * proxy will not change the Request-URI. * * 2. The proxy will inspect the URI in the topmost Route header * field value. If it indicates this proxy, the proxy removes it * from the Route header field (this route node has been reached). * * 3. The proxy will forward the request to the resource indicated * by the URI in the topmost Route header field value or in the * Request-URI if no Route header field is present. The proxy * determines the address, port and transport to use when * forwarding the request by applying the procedures in [4] to that URI. */ SIP_Request request = e.Request; try { #region Registrar // Registrar if ((m_ProxyMode & SIP_ProxyMode.Registrar) != 0 && request.RequestLine.Method == SIP_Methods.REGISTER) { m_pRegistrar.Register(e); } #endregion #region Presence /* * // Presence * else if((m_ProxyMode & SIP_ProxyMode.Presence) != 0 && (request.Method == "SUBSCRIBE" || request.Method == "NOTIFY")){ * * } */ #endregion #region Statefull // Statefull else if ((m_ProxyMode & SIP_ProxyMode.Statefull) != 0) { // Statefull proxy is transaction statefull proxy only, // what don't create dialogs and keep dialog state. /* RFC 3261 16.10. * StateFull proxy: * If a matching response context is found, the element MUST * immediately return a 200 (OK) response to the CANCEL request. * * If a response context is not found, the element does not have any * knowledge of the request to apply the CANCEL to. It MUST statelessly * forward the CANCEL request (it may have statelessly forwarded the * associated request previously). */ if (e.Request.RequestLine.Method == SIP_Methods.CANCEL) { // Don't do server transaction before we get CANCEL matching transaction. SIP_ServerTransaction trToCancel = m_pStack.TransactionLayer.MatchCancelToTransaction(e.Request); if (trToCancel != null) { trToCancel.Cancel(); e.ServerTransaction.SendResponse(m_pStack.CreateResponse( SIP_ResponseCodes.x200_Ok, request)); } else { ForwardRequest(false, e); } } // ACK never creates transaction, it's always passed directly to transport layer. else if (e.Request.RequestLine.Method == SIP_Methods.ACK) { ForwardRequest(false, e); } else { ForwardRequest(true, e); } } #endregion #region B2BUA // B2BUA else if ((m_ProxyMode & SIP_ProxyMode.B2BUA) != 0) { m_pB2BUA.OnRequestReceived(e); } #endregion #region Stateless // Stateless else if ((m_ProxyMode & SIP_ProxyMode.Stateless) != 0) { // Stateless proxy don't do transaction, just forwards all. ForwardRequest(false, e); } #endregion #region Proxy won't accept command else { e.ServerTransaction.SendResponse( m_pStack.CreateResponse(SIP_ResponseCodes.x501_Not_Implemented, request)); } #endregion } catch (Exception x) { try { m_pStack.TransportLayer.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x500_Server_Internal_Error + ": " + x.Message, e.Request)); } catch { // Skip transport layer exception if send fails. } // Don't raise OnError for transport errors. if (!(x is SIP_TransportException)) { m_pStack.OnError(x); } } }
/// <summary> /// This method is called when new request is received. /// </summary> /// <param name="e">Request event arguments.</param> private void OnRequestReceived(SIP_RequestReceivedEventArgs e) { /* RFC 3261 16.12. ????????? Forward does all thse steps. 1. The proxy will inspect the Request-URI. If it indicates a resource owned by this proxy, the proxy will replace it with the results of running a location service. Otherwise, the proxy will not change the Request-URI. 2. The proxy will inspect the URI in the topmost Route header field value. If it indicates this proxy, the proxy removes it from the Route header field (this route node has been reached). 3. The proxy will forward the request to the resource indicated by the URI in the topmost Route header field value or in the Request-URI if no Route header field is present. The proxy determines the address, port and transport to use when forwarding the request by applying the procedures in [4] to that URI. */ SIP_Request request = e.Request; try { #region Registrar // Registrar if ((m_ProxyMode & SIP_ProxyMode.Registrar) != 0 && request.RequestLine.Method == SIP_Methods.REGISTER) { m_pRegistrar.Register(e); } #endregion #region Presence /* // Presence else if((m_ProxyMode & SIP_ProxyMode.Presence) != 0 && (request.Method == "SUBSCRIBE" || request.Method == "NOTIFY")){ } */ #endregion #region Statefull // Statefull else if ((m_ProxyMode & SIP_ProxyMode.Statefull) != 0) { // Statefull proxy is transaction statefull proxy only, // what don't create dialogs and keep dialog state. /* RFC 3261 16.10. StateFull proxy: If a matching response context is found, the element MUST immediately return a 200 (OK) response to the CANCEL request. If a response context is not found, the element does not have any knowledge of the request to apply the CANCEL to. It MUST statelessly forward the CANCEL request (it may have statelessly forwarded the associated request previously). */ if (e.Request.RequestLine.Method == SIP_Methods.CANCEL) { // Don't do server transaction before we get CANCEL matching transaction. SIP_ServerTransaction trToCancel = m_pStack.TransactionLayer.MatchCancelToTransaction(e.Request); if (trToCancel != null) { trToCancel.Cancel(); e.ServerTransaction.SendResponse(m_pStack.CreateResponse( SIP_ResponseCodes.x200_Ok, request)); } else { ForwardRequest(false, e); } } // ACK never creates transaction, it's always passed directly to transport layer. else if (e.Request.RequestLine.Method == SIP_Methods.ACK) { ForwardRequest(false, e); } else { ForwardRequest(true, e); } } #endregion #region B2BUA // B2BUA else if ((m_ProxyMode & SIP_ProxyMode.B2BUA) != 0) { m_pB2BUA.OnRequestReceived(e); } #endregion #region Stateless // Stateless else if ((m_ProxyMode & SIP_ProxyMode.Stateless) != 0) { // Stateless proxy don't do transaction, just forwards all. ForwardRequest(false, e); } #endregion #region Proxy won't accept command else { e.ServerTransaction.SendResponse( m_pStack.CreateResponse(SIP_ResponseCodes.x501_Not_Implemented, request)); } #endregion } catch (Exception x) { try { m_pStack.TransportLayer.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x500_Server_Internal_Error + ": " + x.Message, e.Request)); } catch { // Skip transport layer exception if send fails. } // Don't raise OnError for transport errors. if (!(x is SIP_TransportException)) { m_pStack.OnError(x); } } }
/// <summary> /// Forwards specified request to destination recipient. /// </summary> /// <param name="statefull">Specifies if request is sent statefully or statelessly.</param> /// <param name="e">Request event arguments.</param> private void ForwardRequest(bool statefull, SIP_RequestReceivedEventArgs e) { ForwardRequest(statefull, e, e.Request, true); }
/// <summary> /// This method is called when SIP stack received new message. /// </summary> /// <param name="sender">Sender.</param> /// <param name="e">Event data.</param> private void m_pStack_RequestReceived(object sender, SIP_RequestReceivedEventArgs e) { // TODO: Performance: rise events on thread pool or see if this method called on pool aready, then we may not keep lock for events ? if (e.Request.RequestLine.Method == SIP_Methods.CANCEL) { /* RFC 3261 9.2. * If the UAS did not find a matching transaction for the CANCEL * according to the procedure above, it SHOULD respond to the CANCEL * with a 481 (Call Leg/Transaction Does Not Exist). * * Regardless of the method of the original request, as long as the * CANCEL matched an existing transaction, the UAS answers the CANCEL * request itself with a 200 (OK) response. */ SIP_ServerTransaction trToCancel = m_pStack.TransactionLayer.MatchCancelToTransaction(e.Request); if (trToCancel != null) { trToCancel.Cancel(); e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok, e.Request)); } else { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist, e.Request)); } } else if (e.Request.RequestLine.Method == SIP_Methods.BYE) { /* RFC 3261 15.1.2. * If the BYE does not match an existing dialog, the UAS core SHOULD generate a 481 * (Call/Transaction Does Not Exist) response and pass that to the server transaction. */ // TODO: SIP_Dialog dialog = m_pStack.TransactionLayer.MatchDialog(e.Request); if (dialog != null) { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok, e.Request)); dialog.Terminate(); } else { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist, e.Request)); } } else if (e.Request.RequestLine.Method == SIP_Methods.INVITE) { // Supress INVITE retransmissions. e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x100_Trying, e.Request)); // Create call. SIP_UA_Call call = new SIP_UA_Call(this, e.ServerTransaction); call.StateChanged += new EventHandler(Call_StateChanged); m_pCalls.Add(call); OnIncomingCall(call); } else { OnRequestReceived(e); } }
/// <summary> /// Forwards specified request to target recipient. /// </summary> /// <param name="statefull">Specifies if request is sent statefully or statelessly.</param> /// <param name="e">Request event arguments.</param> /// <param name="request">SIP request to forward.</param> /// <param name="addRecordRoute">Specifies if Record-Route header filed is added.</param> internal void ForwardRequest(bool statefull, SIP_RequestReceivedEventArgs e, SIP_Request request, bool addRecordRoute) { List<SIP_ProxyTarget> targetSet = new List<SIP_ProxyTarget>(); List<NetworkCredential> credentials = new List<NetworkCredential>(); SIP_Uri route = null; /* RFC 3261 16. 1. Validate the request (Section 16.3) 1. Reasonable Syntax 2. URI scheme 3. Max-Forwards 4. (Optional) Loop Detection 5. Proxy-Require 6. Proxy-Authorization 2. Preprocess routing information (Section 16.4) 3. Determine target(s) for the request (Section 16.5) 4. Forward the request (Section 16.6) */ #region 1. Validate the request (Section 16.3) // 1.1 Reasonable Syntax. // SIP_Message will do it. // 1.2 URI scheme check. if (!SIP_Utils.IsSipOrSipsUri(request.RequestLine.Uri.ToString())) { // TODO: SIP_GatewayEventArgs eArgs = OnGetGateways("uriScheme", "userName"); // No suitable gateway or authenticated user has no access. if (eArgs.Gateways.Count == 0) { e.ServerTransaction.SendResponse( m_pStack.CreateResponse(SIP_ResponseCodes.x416_Unsupported_URI_Scheme, e.Request)); return; } } // 1.3 Max-Forwards. if (request.MaxForwards <= 0) { e.ServerTransaction.SendResponse(m_pStack.CreateResponse( SIP_ResponseCodes.x483_Too_Many_Hops, request)); return; } // 1.4 (Optional) Loop Detection. // Skip. // 1.5 Proxy-Require. // TODO: // 1.6 Proxy-Authorization. // We need to auth all foreign calls. if (!SIP_Utils.IsSipOrSipsUri(request.RequestLine.Uri.ToString()) || !OnIsLocalUri(((SIP_Uri) request.RequestLine.Uri).Host)) { // We need to pass-through ACK. if (request.RequestLine.Method == SIP_Methods.ACK) {} else if (!AuthenticateRequest(e)) { return; } } #endregion #region 2. Preprocess routing information (Section 16.4). /* The proxy MUST inspect the Request-URI of the request. If the Request-URI of the request contains a value this proxy previously placed into a Record-Route header field (see Section 16.6 item 4), the proxy MUST replace the Request-URI in the request with the last value from the Route header field, and remove that value from the Route header field. The proxy MUST then proceed as if it received this modified request. If the first value in the Route header field indicates this proxy, the proxy MUST remove that value from the request. */ // Strict route. if (SIP_Utils.IsSipOrSipsUri(request.RequestLine.Uri.ToString()) && IsLocalRoute(((SIP_Uri) request.RequestLine.Uri))) { request.RequestLine.Uri = request.Route.GetAllValues()[request.Route.GetAllValues().Length - 1].Address.Uri; SIP_t_AddressParam[] routes = request.Route.GetAllValues(); route = (SIP_Uri) routes[routes.Length - 1].Address.Uri; request.Route.RemoveLastValue(); } // Loose route. else if (request.Route.GetAllValues().Length > 0 && IsLocalRoute(SIP_Uri.Parse(request.Route.GetTopMostValue().Address.Uri.ToString()))) { route = (SIP_Uri) request.Route.GetTopMostValue().Address.Uri; request.Route.RemoveTopMostValue(); } #endregion #region 3. Determine target(s) for the request (Section 16.5) /* 3. Determine target(s) for the request (Section 16.5) Next, the proxy calculates the target(s) of the request. The set of targets will either be predetermined by the contents of the request or will be obtained from an abstract location service. Each target in the set is represented as a URI. If the domain of the Request-URI indicates a domain this element is not responsible for, the Request-URI MUST be placed into the target set as the only target, and the element MUST proceed to the task of Request Forwarding (Section 16.6). If the target set for the request has not been predetermined as described above, this implies that the element is responsible for the domain in the Request-URI, and the element MAY use whatever mechanism it desires to determine where to send the request. Any of these mechanisms can be modeled as accessing an abstract Location Service. This may consist of obtaining information from a location service created by a SIP Registrar, reading a database, consulting a presence server, utilizing other protocols, or simply performing an algorithmic substitution on the Request-URI. When accessing the location service constructed by a registrar, the Request-URI MUST first be canonicalized as described in Section 10.3 before being used as an index. The output of these mechanisms is used to construct the target set. */ // Non-SIP // Foreign SIP // Local SIP // FIX ME: we may have tel: here SIP_Uri requestUri = (SIP_Uri) e.Request.RequestLine.Uri; // Proxy is not responsible for the domain in the Request-URI. if (!OnIsLocalUri(requestUri.Host)) { /* 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 */ SIP_Flow targetFlow = null; string flowInfo = (route != null && route.Parameters["flowInfo"] != null) ? route.Parameters["flowInfo"].Value : null; if (flowInfo != null && request.To.Tag != null) { string flow1Tag = flowInfo.Substring(0, flowInfo.IndexOf(':')); string flow1ID = flowInfo.Substring(flowInfo.IndexOf(':') + 1, flowInfo.IndexOf('/') - flowInfo.IndexOf(':') - 1); string flow2ID = flowInfo.Substring(flowInfo.IndexOf('/') + 1); if (flow1Tag == request.To.Tag) { targetFlow = m_pStack.TransportLayer.GetFlow(flow1ID); } else { ; targetFlow = m_pStack.TransportLayer.GetFlow(flow2ID); } } targetSet.Add(new SIP_ProxyTarget(requestUri, targetFlow)); } // Proxy is responsible for the domain in the Request-URI. else { // TODO: tel: //SIP_Uri requestUri = SIP_Uri.Parse(e.Request.Uri); // Try to get AOR from registrar. SIP_Registration registration = m_pRegistrar.GetRegistration(requestUri.Address); // We have AOR specified in request-URI in registrar server. if (registration != null) { // Add all AOR SIP contacts to target set. foreach (SIP_RegistrationBinding binding in registration.Bindings) { if (binding.ContactURI is SIP_Uri && binding.TTL > 0) { targetSet.Add(new SIP_ProxyTarget((SIP_Uri) binding.ContactURI, binding.Flow)); } } } // We don't have AOR specified in request-URI in registrar server. else { // If the Request-URI indicates a resource at this proxy that does not // exist, the proxy MUST return a 404 (Not Found) response. if (!OnAddressExists(requestUri.Address)) { e.ServerTransaction.SendResponse( m_pStack.CreateResponse(SIP_ResponseCodes.x404_Not_Found, e.Request)); return; } } } // If the target set remains empty after applying all of the above, the proxy MUST return an error response, // which SHOULD be the 480 (Temporarily Unavailable) response. if (targetSet.Count == 0) { e.ServerTransaction.SendResponse( m_pStack.CreateResponse(SIP_ResponseCodes.x480_Temporarily_Unavailable, e.Request)); return; } #endregion #region 4. Forward the request (Section 16.6) #region Statefull if (statefull) { // Create proxy context that will be responsible for forwarding request. SIP_ProxyContext proxyContext = new SIP_ProxyContext(this, e.ServerTransaction, request, addRecordRoute, m_ForkingMode, (ProxyMode & SIP_ProxyMode.B2BUA) != 0, false, false, targetSet.ToArray(), credentials.ToArray()); m_pProxyContexts.Add(proxyContext); proxyContext.Start(); } #endregion #region Stateless else { /* RFC 3261 16.6 Request Forwarding. For each target, the proxy forwards the request following these steps: 1. Make a copy of the received request 2. Update the Request-URI 3. Update the Max-Forwards header field 4. Optionally add a Record-route header field value 5. Optionally add additional header fields 6. Postprocess routing information 7. Determine the next-hop address, port, and transport 8. Add a Via header field value 9. Add a Content-Length header field if necessary 10. Forward the new request */ /* RFC 3261 16.11 Stateless Proxy. o A stateless proxy MUST choose one and only one target from the target set. This choice MUST only rely on fields in the message and time-invariant properties of the server. In particular, a retransmitted request MUST be forwarded to the same destination each time it is processed. Furthermore, CANCEL and non-Routed ACK requests MUST generate the same choice as their associated INVITE. However, a stateless proxy cannot simply use a random number generator to compute the first component of the branch ID, as described in Section 16.6 bullet 8. This is because retransmissions of a request need to have the same value, and a stateless proxy cannot tell a retransmission from the original request. We just use: "z9hG4bK-" + md5(topmost branch) */ bool isStrictRoute = false; SIP_Hop[] hops = null; #region 1. Make a copy of the received request SIP_Request forwardRequest = request.Copy(); #endregion #region 2. Update the Request-URI forwardRequest.RequestLine.Uri = targetSet[0].TargetUri; #endregion #region 3. Update the Max-Forwards header field forwardRequest.MaxForwards--; #endregion #region 4. Optionally add a Record-route header field value #endregion #region 5. Optionally add additional header fields #endregion #region 6. Postprocess routing information /* 6. Postprocess routing information. If the copy contains a Route header field, the proxy MUST inspect the URI in its first value. If that URI does not contain an lr parameter, the proxy MUST modify the copy as follows: - The proxy MUST place the Request-URI into the Route header field as the last value. - The proxy MUST then place the first Route header field value into the Request-URI and remove that value from the Route header field. */ if (forwardRequest.Route.GetAllValues().Length > 0 && !forwardRequest.Route.GetTopMostValue().Parameters.Contains("lr")) { forwardRequest.Route.Add(forwardRequest.RequestLine.Uri.ToString()); forwardRequest.RequestLine.Uri = SIP_Utils.UriToRequestUri(forwardRequest.Route.GetTopMostValue().Address.Uri); forwardRequest.Route.RemoveTopMostValue(); isStrictRoute = true; } #endregion #region 7. Determine the next-hop address, port, and transport /* 7. Determine the next-hop address, port, and transport. The proxy MAY have a local policy to send the request to a specific IP address, port, and transport, independent of the values of the Route and Request-URI. Such a policy MUST NOT be used if the proxy is not certain that the IP address, port, and transport correspond to a server that is a loose router. However, this mechanism for sending the request through a specific next hop is NOT RECOMMENDED; instead a Route header field should be used for that purpose as described above. In the absence of such an overriding mechanism, the proxy applies the procedures listed in [4] as follows to determine where to send the request. If the proxy has reformatted the request to send to a strict-routing element as described in step 6 above, the proxy MUST apply those procedures to the Request-URI of the request. Otherwise, the proxy MUST apply the procedures to the first value in the Route header field, if present, else the Request-URI. The procedures will produce an ordered set of (address, port, transport) tuples. Independently of which URI is being used as input to the procedures of [4], if the Request-URI specifies a SIPS resource, the proxy MUST follow the procedures of [4] as if the input URI were a SIPS URI. As described in [4], the proxy MUST attempt to deliver the message to the first tuple in that set, and proceed through the set in order until the delivery attempt succeeds. For each tuple attempted, the proxy MUST format the message as appropriate for the tuple and send the request using a new client transaction as detailed in steps 8 through 10. Since each attempt uses a new client transaction, it represents a new branch. Thus, the branch parameter provided with the Via header field inserted in step 8 MUST be different for each attempt. If the client transaction reports failure to send the request or a timeout from its state machine, the proxy continues to the next address in that ordered set. If the ordered set is exhausted, the request cannot be forwarded to this element in the target set. The proxy does not need to place anything in the response context, but otherwise acts as if this element of the target set returned a 408 (Request Timeout) final response. */ SIP_Uri uri = null; if (isStrictRoute) { uri = (SIP_Uri) forwardRequest.RequestLine.Uri; } else if (forwardRequest.Route.GetTopMostValue() != null) { uri = (SIP_Uri) forwardRequest.Route.GetTopMostValue().Address.Uri; } else { uri = (SIP_Uri) forwardRequest.RequestLine.Uri; } hops = m_pStack.GetHops(uri, forwardRequest.ToByteData().Length, ((SIP_Uri) forwardRequest.RequestLine.Uri).IsSecure); if (hops.Length == 0) { if (forwardRequest.RequestLine.Method != SIP_Methods.ACK) { e.ServerTransaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x503_Service_Unavailable + ": No hop(s) for target.", forwardRequest)); } return; } #endregion #region 8. Add a Via header field value forwardRequest.Via.AddToTop( "SIP/2.0/transport-tl-addign sentBy-tl-assign-it;branch=z9hG4bK-" + Core.ComputeMd5(request.Via.GetTopMostValue().Branch, true)); // Add 'flowID' what received request, you should use the same flow to send response back. // For more info see RFC 3261 18.2.2. forwardRequest.Via.GetTopMostValue().Parameters.Add("flowID", request.Flow.ID); #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 try { try { if (targetSet[0].Flow != null) { m_pStack.TransportLayer.SendRequest(targetSet[0].Flow, request); return; } } catch { m_pStack.TransportLayer.SendRequest(request, null, hops[0]); } } catch (SIP_TransportException x) { string dummy = x.Message; if (forwardRequest.RequestLine.Method != SIP_Methods.ACK) { /* RFC 3261 16.9 Handling Transport Errors If the transport layer notifies a proxy of an error when it tries to forward a request (see Section 18.4), the proxy MUST behave as if the forwarded request received a 503 (Service Unavailable) response. */ e.ServerTransaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x503_Service_Unavailable + ": Transport error.", forwardRequest)); } } #endregion } #endregion #endregion }
/// <summary> /// Handles NOTIFY method. /// </summary> /// <param name="e">Request event arguments.</param> internal void Notify(SIP_RequestReceivedEventArgs e) { }
/// <summary> /// Authenticates SIP request. This method also sends all needed replys to request sender. /// </summary> /// <param name="e">Request event arguments.</param> /// <returns>Returns true if request was authenticated.</returns> internal bool AuthenticateRequest(SIP_RequestReceivedEventArgs e) { string userName = null; return AuthenticateRequest(e, out userName); }
/// <summary> /// Handles REGISTER method. /// </summary> /// <param name="e">Request event arguments.</param> internal void Register(SIP_RequestReceivedEventArgs e) { /* RFC 3261 10.3 Processing REGISTER Requests. * 1. The registrar inspects the Request-URI to determine whether it * has access to bindings for the domain identified in the * Request-URI. If not, and if the server also acts as a proxy * server, the server SHOULD forward the request to the addressed * domain, following the general behavior for proxying messages * described in Section 16. * * 2. To guarantee that the registrar supports any necessary extensions, * the registrar MUST process the Require header field. * * 3. A registrar SHOULD authenticate the UAC. * * 4. The registrar SHOULD determine if the authenticated user is * authorized to modify registrations for this address-of-record. * For example, a registrar might consult an authorization * database that maps user names to a list of addresses-of-record * for which that user has authorization to modify bindings. If * the authenticated user is not authorized to modify bindings, * the registrar MUST return a 403 (Forbidden) and skip the * remaining steps. * * 5. The registrar extracts the address-of-record from the To header * field of the request. If the address-of-record is not valid * for the domain in the Request-URI, the registrar MUST send a * 404 (Not Found) response and skip the remaining steps. The URI * MUST then be converted to a canonical form. To do that, all * URI parameters MUST be removed (including the user-param), and * any escaped characters MUST be converted to their unescaped * form. The result serves as an index into the list of bindings. * * 6. The registrar checks whether the request contains the Contact * header field. If not, it skips to the last step. If the * Contact header field is present, the registrar checks if there * is one Contact field value that contains the special value "*" * and an Expires field. If the request has additional Contact * fields or an expiration time other than zero, the request is * invalid, and the server MUST return a 400 (Invalid Request) and * skip the remaining steps. If not, the registrar checks whether * the Call-ID agrees with the value stored for each binding. If * not, it MUST remove the binding. If it does agree, it MUST * remove the binding only if the CSeq in the request is higher * than the value stored for that binding. Otherwise, the update * MUST be aborted and the request fails. * * 7. The registrar now processes each contact address in the Contact * header field in turn. For each address, it determines the * expiration interval as follows: * * - If the field value has an "expires" parameter, that value * MUST be taken as the requested expiration. * * - If there is no such parameter, but the request has an * Expires header field, that value MUST be taken as the requested expiration. * * - If there is neither, a locally-configured default value MUST * be taken as the requested expiration. * * The registrar MAY choose an expiration less than the requested * expiration interval. If and only if the requested expiration * interval is greater than zero AND smaller than one hour AND * less than a registrar-configured minimum, the registrar MAY * reject the registration with a response of 423 (Interval Too * Brief). This response MUST contain a Min-Expires header field * that states the minimum expiration interval the registrar is * willing to honor. It then skips the remaining steps. * * For each address, the registrar then searches the list of * current bindings using the URI comparison rules. If the * binding does not exist, it is tentatively added. If the * binding does exist, the registrar checks the Call-ID value. If * the Call-ID value in the existing binding differs from the * Call-ID value in the request, the binding MUST be removed if * the expiration time is zero and updated otherwise. If they are * the same, the registrar compares the CSeq value. If the value * is higher than that of the existing binding, it MUST update or * remove the binding as above. If not, the update MUST be * aborted and the request fails. * * This algorithm ensures that out-of-order requests from the same * UA are ignored. * * Each binding record records the Call-ID and CSeq values from * the request. * * The binding updates MUST be committed (that is, made visible to * the proxy or redirect server) if and only if all binding * updates and additions succeed. If any one of them fails (for * example, because the back-end database commit failed), the * request MUST fail with a 500 (Server Error) response and all * tentative binding updates MUST be removed. * * 8. The registrar returns a 200 (OK) response. The response MUST * contain Contact header field values enumerating all current * bindings. Each Contact value MUST feature an "expires" * parameter indicating its expiration interval chosen by the * registrar. The response SHOULD include a Date header field. */ SIP_ServerTransaction transaction = e.ServerTransaction; SIP_Request request = e.Request; SIP_Uri to = null; string userName = ""; // Probably we need to do validate in SIP stack. #region Validate request if (SIP_Utils.IsSipOrSipsUri(request.To.Address.Uri.ToString())) { to = (SIP_Uri)request.To.Address.Uri; } else { transaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x400_Bad_Request + ": To: value must be SIP or SIPS URI.", request)); return; } #endregion #region 1. Check if we are responsible for Request-URI domain // if(m_pProxy.OnIsLocalUri(e.Request.Uri)){ // } // TODO: #endregion #region 2. Check that all required extentions supported #endregion #region 3. Authenticate request if (!m_pProxy.AuthenticateRequest(e, out userName)) { return; } #endregion #region 4. Check if user user is authorized to modify registrations // We do this in next step(5.). #endregion #region 5. Check if address of record exists if (!m_pProxy.OnAddressExists(to.Address)) { transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x404_Not_Found, request)); return; } else if (!OnCanRegister(userName, to.Address)) { transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x403_Forbidden, request)); return; } #endregion #region 6. Process * Contact if exists // Check if we have star contact. SIP_t_ContactParam starContact = null; foreach (SIP_t_ContactParam c in request.Contact.GetAllValues()) { if (c.IsStarContact) { starContact = c; break; } } // We have star contact. if (starContact != null) { if (request.Contact.GetAllValues().Length > 1) { transaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x400_Bad_Request + ": RFC 3261 10.3.6 -> If star(*) present, only 1 contact allowed.", request)); return; } else if (starContact.Expires != 0) { transaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x400_Bad_Request + ": RFC 3261 10.3.6 -> star(*) contact parameter 'expires' value must be always '0'.", request)); return; } // Remove bindings. SIP_Registration reg = m_pRegistrations[to.Address]; if (reg != null) { foreach (SIP_RegistrationBinding b in reg.Bindings) { if (request.CallID != b.CallID || request.CSeq.SequenceNumber > b.CSeqNo) { b.Remove(); } } } } #endregion #region 7. Process Contact values if (starContact == null) { SIP_Registration reg = m_pRegistrations[to.Address]; if (reg == null) { reg = new SIP_Registration(userName, to.Address); m_pRegistrations.Add(reg); } // We may do updates in batch only. // We just validate all values then do update(this ensures that update doesn't fail). // Check expires and CSeq. foreach (SIP_t_ContactParam c in request.Contact.GetAllValues()) { if (c.Expires == -1) { c.Expires = request.Expires; } if (c.Expires == -1) { c.Expires = m_pProxy.Stack.MinimumExpireTime; } // We must accept 0 values - means remove contact. if (c.Expires != 0 && c.Expires < m_pProxy.Stack.MinimumExpireTime) { SIP_Response resp = m_pStack.CreateResponse( SIP_ResponseCodes.x423_Interval_Too_Brief, request); resp.MinExpires = m_pProxy.Stack.MinimumExpireTime; transaction.SendResponse(resp); return; } SIP_RegistrationBinding currentBinding = reg.GetBinding(c.Address.Uri); if (currentBinding != null && currentBinding.CallID == request.CallID && request.CSeq.SequenceNumber < currentBinding.CSeqNo) { transaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x400_Bad_Request + ": CSeq value out of order.", request)); return; } } // Do binding updates. reg.AddOrUpdateBindings(e.ServerTransaction.Flow, request.CallID, request.CSeq.SequenceNumber, request.Contact.GetAllValues()); } #endregion #region 8. Create 200 OK response and return all current bindings SIP_Response response = m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok, request); response.Date = DateTime.Now; SIP_Registration registration = m_pRegistrations[to.Address]; if (registration != null) { foreach (SIP_RegistrationBinding b in registration.Bindings) { // Don't list expired bindings what wait to be disposed. if (b.TTL > 1) { response.Header.Add("Contact:", b.ToContactValue()); } } } // Add Authentication-Info:, then client knows next nonce. response.AuthenticationInfo.Add("qop=\"auth\",nextnonce=\"" + m_pStack.DigestNonceManager.CreateNonce() + "\""); transaction.SendResponse(response); #endregion }
/// <summary> /// Authenticates SIP request. This method also sends all needed replys to request sender. /// </summary> /// <param name="e">Request event arguments.</param> /// <returns>Returns true if request was authenticated.</returns> internal bool AuthenticateRequest(SIP_RequestReceivedEventArgs e) { string userName = null; return(AuthenticateRequest(e, out userName)); }
/// <summary> /// This method is called when new request is received. /// </summary> /// <param name="e">Request event arguments.</param> internal void OnRequestReceived(SIP_RequestReceivedEventArgs e) { SIP_Request request = e.Request; if (request.RequestLine.Method == SIP_Methods.CANCEL) { /* RFC 3261 9.2. * If the UAS did not find a matching transaction for the CANCEL * according to the procedure above, it SHOULD respond to the CANCEL * with a 481 (Call Leg/Transaction Does Not Exist). * * Regardless of the method of the original request, as long as the * CANCEL matched an existing transaction, the UAS answers the CANCEL * request itself with a 200 (OK) response. */ SIP_ServerTransaction trToCancel = m_pProxy.Stack.TransactionLayer.MatchCancelToTransaction(e.Request); if (trToCancel != null) { trToCancel.Cancel(); //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x200_Ok)); } else { //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist)); } } // We never should ge BYE here, because transport layer must match it to dialog. else if (request.RequestLine.Method == SIP_Methods.BYE) { /* RFC 3261 15.1.2. * If the BYE does not match an existing dialog, the UAS core SHOULD generate a 481 * (Call/Transaction Does Not Exist) response and pass that to the server transaction. */ //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist)); } // We never should ge ACK here, because transport layer must match it to dialog. else if (request.RequestLine.Method == SIP_Methods.ACK) { // ACK is response less request, so we may not return error to it. } // B2BUA must respond to OPTIONS request, not to forward it. else if (request.RequestLine.Method == SIP_Methods.OPTIONS) /* * SIP_Response response = e.Request.CreateResponse(SIP_ResponseCodes.x200_Ok); * // Add Allow to non ACK response. * if(e.Request.RequestLine.Method != SIP_Methods.ACK){ * response.Allow.Add("INVITE,ACK,OPTIONS,CANCEL,BYE,PRACK,MESSAGE,UPDATE"); * } * // Add Supported to 2xx non ACK response. * if(response.StatusCodeType == SIP_StatusCodeType.Success && e.Request.RequestLine.Method != SIP_Methods.ACK){ * response.Supported.Add("100rel,timer"); * } * e.ServerTransaction.SendResponse(response);*/ { } // We never should get PRACK here, because transport layer must match it to dialog. else if (request.RequestLine.Method == SIP_Methods.PRACK) { //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist)); } // We never should get UPDATE here, because transport layer must match it to dialog. else if (request.RequestLine.Method == SIP_Methods.UPDATE) { //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist)); } else { /* draft-marjou-sipping-b2bua-00 4.1.3. * When the UAS of the B2BUA receives an upstream SIP request, its * associated UAC generates a new downstream SIP request with its new * Via, Max-Forwards, Call-Id, CSeq, and Contact header fields. Route * header fields of the upstream request are copied in the downstream * request, except the first Route header if it is under the * responsibility of the B2BUA. Record-Route header fields of the * upstream request are not copied in the new downstream request, as * Record-Route is only meaningful for the upstream dialog. The UAC * SHOULD copy other header fields and body from the upstream request * into this downstream request before sending it. */ SIP_Request b2buaRequest = e.Request.Copy(); b2buaRequest.Via.RemoveAll(); b2buaRequest.MaxForwards = 70; b2buaRequest.CallID = SIP_t_CallID.CreateCallID().CallID; b2buaRequest.CSeq.SequenceNumber = 1; b2buaRequest.Contact.RemoveAll(); // b2buaRequest.Contact.Add(m_pProxy.CreateContact(b2buaRequest.To.Address).ToStringValue()); if (b2buaRequest.Route.Count > 0 && m_pProxy.IsLocalRoute(SIP_Uri.Parse(b2buaRequest.Route.GetTopMostValue().Address.Uri.ToString()))) { b2buaRequest.Route.RemoveTopMostValue(); } b2buaRequest.RecordRoute.RemoveAll(); // Remove our Authorization header if it's there. foreach (SIP_SingleValueHF <SIP_t_Credentials> header in b2buaRequest.ProxyAuthorization.HeaderFields) { try{ Auth_HttpDigest digest = new Auth_HttpDigest(header.ValueX.AuthData, b2buaRequest.RequestLine.Method); if (m_pProxy.Stack.Realm == digest.Realm) { b2buaRequest.ProxyAuthorization.Remove(header); } } catch { // We don't care errors here. This can happen if remote server xxx auth method here and // we don't know how to parse it, so we leave it as is. } } //--- Add/replace default fields. ------------------------------------------ b2buaRequest.Allow.RemoveAll(); b2buaRequest.Supported.RemoveAll(); // Accept to non ACK,BYE request. if (request.RequestLine.Method != SIP_Methods.ACK && request.RequestLine.Method != SIP_Methods.BYE) { b2buaRequest.Allow.Add("INVITE,ACK,OPTIONS,CANCEL,BYE,PRACK"); } // Supported to non ACK request. if (request.RequestLine.Method != SIP_Methods.ACK) { b2buaRequest.Supported.Add("100rel,timer"); } // Remove Require: header. b2buaRequest.Require.RemoveAll(); // RFC 4028 7.4. For re-INVITE and UPDATE we need to add Session-Expires and Min-SE: headers. if (request.RequestLine.Method == SIP_Methods.INVITE || request.RequestLine.Method == SIP_Methods.UPDATE) { b2buaRequest.SessionExpires = new SIP_t_SessionExpires(m_pProxy.Stack.SessionExpries, "uac"); b2buaRequest.MinSE = new SIP_t_MinSE(m_pProxy.Stack.MinimumSessionExpries); } // Forward request. //m_pProxy.ForwardRequest(true,e,b2buaRequest,false); } }
/// <summary> /// This method is called when SIP stack received new message. /// </summary> /// <param name="sender">Sender.</param> /// <param name="e">Event data.</param> private void m_pStack_RequestReceived(object sender, SIP_RequestReceivedEventArgs e) { // TODO: Performance: rise events on thread pool or see if this method called on pool aready, then we may not keep lock for events ? if (e.Request.RequestLine.Method == SIP_Methods.CANCEL) { /* RFC 3261 9.2. If the UAS did not find a matching transaction for the CANCEL according to the procedure above, it SHOULD respond to the CANCEL with a 481 (Call Leg/Transaction Does Not Exist). Regardless of the method of the original request, as long as the CANCEL matched an existing transaction, the UAS answers the CANCEL request itself with a 200 (OK) response. */ SIP_ServerTransaction trToCancel = m_pStack.TransactionLayer.MatchCancelToTransaction(e.Request); if (trToCancel != null) { trToCancel.Cancel(); e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok, e.Request)); } else { e.ServerTransaction.SendResponse( m_pStack.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist, e.Request)); } } else if (e.Request.RequestLine.Method == SIP_Methods.BYE) { /* RFC 3261 15.1.2. If the BYE does not match an existing dialog, the UAS core SHOULD generate a 481 (Call/Transaction Does Not Exist) response and pass that to the server transaction. */ // TODO: SIP_Dialog dialog = m_pStack.TransactionLayer.MatchDialog(e.Request); if (dialog != null) { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok, e.Request)); dialog.Terminate(); } else { e.ServerTransaction.SendResponse( m_pStack.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist, e.Request)); } } else if (e.Request.RequestLine.Method == SIP_Methods.INVITE) { SIP_ServerTransaction transaction = e.ServerTransaction; transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x180_Ringing, e.Request)); // TODO: See if we support SDP media. // Create call. SIP_UA_Call call = new SIP_UA_Call(this, transaction); call.StateChanged += Call_StateChanged; m_pCalls.Add(call); OnIncomingCall(call); } else { OnRequestReceived(e); } }