/// <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="method">SIP method.</param> /// <param name="uri">Request URI.</param> /// <exception cref="ArgumentNullException">Is raised when <b>method</b> or <b>uri</b> is null reference.</exception> /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception> public SIP_RequestLine(string method, AbsoluteUri uri) { if (method == null) { throw new ArgumentNullException("method"); } if (!SIP_Utils.IsToken(method)) { throw new ArgumentException("Argument 'method' value must be token."); } if (uri == null) { throw new ArgumentNullException("uri"); } m_Method = method.ToUpper(); m_pUri = uri; m_Version = "SIP/2.0"; }
/// <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).ToChallenge()); 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).ToChallenge()); // 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).ToChallenge()); // 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 = this.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).ToChallenge()); // Send response e.ServerTransaction.SendResponse(notAuthenticatedResponse); return(false); } userName = auth.UserName; return(true); }
/// <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="addRecordRoute">If true Record-Route header field is added.</param> internal void ForwardRequest(bool statefull, SIP_RequestReceivedEventArgs e, bool addRecordRoute) { SIP_RequestContext requestContext = new SIP_RequestContext(this, e.Request, e.Flow); SIP_Request request = e.Request; SIP_Uri route = null; /* RFC 3261 16. * 1. Validate the request (Section 16.3) * 1. Reasonable Syntax * 2. URI scheme (NOTE: We do it later) * 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) * x. Custom handling (non-RFC) * 1. Process custom request handlers. * 2. URI scheme * 4. Forward the request (Section 16.6) */ #region 1. Validate the request (Section 16.3) // 1.1 Reasonable Syntax. // SIP_Message parsing have done it. // 1.2 URI scheme check. // We do it later. // 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. /* If an element requires credentials before forwarding a request, * the request MUST be inspected as described in Section 22.3. That * section also defines what the element must do if the inspection * fails. */ // We need to auth all foreign calls. if (!SIP_Utils.IsSipOrSipsUri(request.RequestLine.Uri.ToString()) || !this.OnIsLocalUri(((SIP_Uri)request.RequestLine.Uri).Host)) { // If To: field is registrar AOR and request-URI is local registration contact, skip authentication. bool skipAuth = false; if (request.To.Address.IsSipOrSipsUri) { SIP_Registration registration = m_pRegistrar.GetRegistration(((SIP_Uri)request.To.Address.Uri).Address); if (registration != null) { if (registration.GetBinding(request.RequestLine.Uri) != null) { skipAuth = true; } } } if (!skipAuth) { string userName = null; // We need to pass-through ACK. if (request.RequestLine.Method == SIP_Methods.ACK) { } else if (!AuthenticateRequest(e, out userName)) { return; } requestContext.SetUser(userName); } } #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. * * This will only happen when the element sending the request to the * proxy (which may have been an endpoint) is a strict router. This * rewrite on receive is necessary to enable backwards compatibility * with those elements. It also allows elements following this * specification to preserve the Request-URI through strict-routing * proxies (see Section 12.2.1.1). * * This requirement does not obligate a proxy to keep state in order * to detect URIs it previously placed in Record-Route header fields. * Instead, a proxy need only place enough information in those URIs * to recognize them as values it provided when they later appear. * * If the Request-URI contains a maddr parameter, the proxy MUST check * to see if its value is in the set of addresses or domains the proxy * is configured to be responsible for. If the Request-URI has a maddr * parameter with a value the proxy is responsible for, and the request * was received using the port and transport indicated (explicitly or by * default) in the Request-URI, the proxy MUST strip the maddr and any * non-default port or transport parameter and continue processing as if * those values had not been present in the request. * * A request may arrive with a maddr matching the proxy, but on a * port or transport different from that indicated in the URI. Such * a request needs to be forwarded to the proxy using the indicated * port and transport. * * If the first value in the Route header field indicates this proxy, * the proxy MUST remove that value from the request. */ // Strict route - handle it. if ((request.RequestLine.Uri is SIP_Uri) && IsRecordRoute(((SIP_Uri)request.RequestLine.Uri)) && request.Route.GetAllValues().Length > 0) { 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(); } // Check if Route header field indicates this proxy. if (request.Route.GetAllValues().Length > 0) { route = (SIP_Uri)request.Route.GetTopMostValue().Address.Uri; // We consider loose-route always ours, because otherwise this message never reach here. if (route.Param_Lr) { request.Route.RemoveTopMostValue(); } // It's our route, remove it. else if (IsLocalRoute(route)) { request.Route.RemoveTopMostValue(); } } #endregion #region REGISTER request processing if (e.Request.RequestLine.Method == SIP_Methods.REGISTER) { SIP_Uri requestUri = (SIP_Uri)e.Request.RequestLine.Uri; // REGISTER is meant for us. if (this.OnIsLocalUri(requestUri.Host)) { if ((m_ProxyMode & SIP_ProxyMode.Registrar) != 0) { m_pRegistrar.Register(e); return; } else { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x405_Method_Not_Allowed, e.Request)); return; } } // Forward REGISTER. // else{ } #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 if (e.Request.RequestLine.Uri is SIP_Uri) { SIP_Uri requestUri = (SIP_Uri)e.Request.RequestLine.Uri; // Proxy is not responsible for the domain in the Request-URI. if (!this.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); } } requestContext.Targets.Add(new SIP_ProxyTarget(requestUri, targetFlow)); } // Proxy is responsible for the domain in the Request-URI. else { // 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) { requestContext.Targets.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 (!this.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 (requestContext.Targets.Count == 0) { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x480_Temporarily_Unavailable, e.Request)); return; } } } #endregion #region x. Custom handling // x.1 Process custom request handlers, if any. foreach (SIP_ProxyHandler handler in this.Handlers) { try{ SIP_ProxyHandler h = handler; // Reusing existing handler not allowed, create new instance of handler. if (!handler.IsReusable) { h = (SIP_ProxyHandler)System.Activator.CreateInstance(handler.GetType()); } if (h.ProcessRequest(requestContext)) { // Handler processed request, we are done. return; } } catch (Exception x) { m_pStack.OnError(x); } } // x.2 URI scheme. // If no targets and request-URI non-SIP, reject request because custom handlers should have been handled it. if (requestContext.Targets.Count == 0 && !SIP_Utils.IsSipOrSipsUri(request.RequestLine.Uri.ToString())) { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x416_Unsupported_URI_Scheme, e.Request)); return; } #endregion #region 4. Forward the request (Section 16.6) #region Statefull if (statefull) { SIP_ProxyContext proxyContext = this.CreateProxyContext(requestContext, e.ServerTransaction, request, addRecordRoute); 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 = requestContext.Targets[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-" + Net_Utils.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 (requestContext.Targets[0].Flow != null) { m_pStack.TransportLayer.SendRequest(requestContext.Targets[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> /// Checks if SIP request has all required values as request line,header fields and their values. /// Throws Exception if not valid SIP request. /// </summary> public void Validate() { // Request SIP version // Via: + branch prameter // To: // From: // CallID: // CSeq // Max-Forwards RFC 3261 8.1.1. if (!RequestLine.Version.ToUpper().StartsWith("SIP/2.0")) { throw new SIP_ParseException("Not supported SIP version '" + RequestLine.Version + "' !"); } if (Via.GetTopMostValue() == null) { throw new SIP_ParseException("Via: header field is missing !"); } if (Via.GetTopMostValue().Branch == null) { throw new SIP_ParseException("Via: header field branch parameter is missing !"); } if (To == null) { throw new SIP_ParseException("To: header field is missing !"); } if (From == null) { throw new SIP_ParseException("From: header field is missing !"); } if (CallID == null) { throw new SIP_ParseException("CallID: header field is missing !"); } if (CSeq == null) { throw new SIP_ParseException("CSeq: header field is missing !"); } if (MaxForwards == -1) { // We can fix it by setting it to default value 70. MaxForwards = 70; } /* RFC 3261 12.1.2 * When a UAC sends a request that can establish a dialog (such as an INVITE) it MUST * provide a SIP or SIPS URI with global scope (i.e., the same SIP URI can be used in * messages outside this dialog) in the Contact header field of the request. If the * request has a Request-URI or a topmost Route header field value with a SIPS URI, the * Contact header field MUST contain a SIPS URI. */ if (SIP_Utils.MethodCanEstablishDialog(RequestLine.Method)) { if (Contact.GetAllValues().Length == 0) { throw new SIP_ParseException( "Contact: header field is missing, method that can establish a dialog MUST provide a SIP or SIPS URI !"); } if (Contact.GetAllValues().Length > 1) { throw new SIP_ParseException( "There may be only 1 Contact: header for the method that can establish a dialog !"); } if (!Contact.GetTopMostValue().Address.IsSipOrSipsUri) { throw new SIP_ParseException( "Method that can establish a dialog MUST have SIP or SIPS uri in Contact: header !"); } } // TODO: Invite must have From:/To: tag // TODO: Check that request-Method equals CSeq method // TODO: PRACK must have RAck and RSeq header fields. // TODO: get in transport made request, so check if sips and sip set as needed. }
/// <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> /// Processes specified request through this dialog. /// </summary> /// <param name="e">Method arguments.</param> /// <returns>Returns true if this dialog processed specified request, otherwise false.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>e</b> is null reference.</exception> protected internal override bool ProcessRequest(SIP_RequestReceivedEventArgs e) { if (e == null) { throw new ArgumentNullException("e"); } if (base.ProcessRequest(e)) { return(true); } // We must support: INVITE(re-invite),UPDATE,ACK, [BYE will be handled by base class] #region INVITE if (e.Request.RequestLine.Method == SIP_Methods.INVITE) { /* RFC 3261 14.2. * A UAS that receives a second INVITE before it sends the final * response to a first INVITE with a lower CSeq sequence number on the * same dialog MUST return a 500 (Server Internal Error) response to the * second INVITE and MUST include a Retry-After header field with a * randomly chosen value of between 0 and 10 seconds. * * A UAS that receives an INVITE on a dialog while an INVITE it had sent * on that dialog is in progress MUST return a 491 (Request Pending) * response to the received INVITE. */ if (m_pActiveInvite != null && m_pActiveInvite is SIP_ServerTransaction && (m_pActiveInvite).Request.CSeq.SequenceNumber < e.Request.CSeq.SequenceNumber) { SIP_Response response = Stack.CreateResponse( SIP_ResponseCodes.x500_Server_Internal_Error + ": INVITE with a lower CSeq is pending(RFC 3261 14.2).", e.Request); response.RetryAfter = new SIP_t_RetryAfter("10"); e.ServerTransaction.SendResponse(response); return(true); } if (m_pActiveInvite != null && m_pActiveInvite is SIP_ClientTransaction) { e.ServerTransaction.SendResponse( Stack.CreateResponse(SIP_ResponseCodes.x491_Request_Pending, e.Request)); return(true); } // Force server transaction creation and set it as active INVITE transaction. m_pActiveInvite = e.ServerTransaction; m_pActiveInvite.StateChanged += delegate { if (m_pActiveInvite.State == SIP_TransactionState.Terminated) { m_pActiveInvite = null; } }; // Once we send 2xx response, we need to retransmit it while get ACK or timeout. (RFC 3261 13.3.1.4.) ((SIP_ServerTransaction)m_pActiveInvite).ResponseSent += delegate(object s, SIP_ResponseSentEventArgs a) { if (a.Response.StatusCodeType == SIP_StatusCodeType.Success) { m_pUasInvite2xxRetransmits.Add(new UasInvite2xxRetransmit(this, a.Response)); } }; OnReinvite(((SIP_ServerTransaction)m_pActiveInvite)); return(true); } #endregion #region ACK else if (e.Request.RequestLine.Method == SIP_Methods.ACK) { // Search corresponding INVITE 2xx retransmit entry and dispose it. foreach (UasInvite2xxRetransmit t in m_pUasInvite2xxRetransmits) { if (t.MatchAck(e.Request)) { t.Dispose(); if (State == SIP_DialogState.Early) { SetState(SIP_DialogState.Confirmed, true); // TODO: If Terminating } return(true); } } return(false); } #endregion #region UPDATE //else if(request.RequestLine.Method == SIP_Methods.UPDATE){ // TODO: //} #endregion // RFC 5057 5.6. Refusing New Usages. Decline(603 Decline) new dialog usages. else if (SIP_Utils.MethodCanEstablishDialog(e.Request.RequestLine.Method)) { e.ServerTransaction.SendResponse( Stack.CreateResponse( SIP_ResponseCodes.x603_Decline + " : New dialog usages not allowed (RFC 5057).", e.Request)); return(true); } else { return(false); } }
/// <summary> /// Creates new SIP request using this dialog info. /// </summary> /// <param name="method">SIP method.</param> /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception> /// <exception cref="ArgumentNullException">Is raised when <b>method</b> is null reference.</exception> /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception> /// <returns>Returns created request.</returns> public SIP_Request CreateRequest(string method) { if (State == SIP_DialogState.Disposed) { throw new ObjectDisposedException(GetType().Name); } if (method == null) { throw new ArgumentNullException("method"); } if (method == string.Empty) { throw new ArgumentException("Argument 'method' value must be specified."); } /* RFC 3261 12.2.1.1. * A request within a dialog is constructed by using many of the * components of the state stored as part of the dialog. * * The URI in the To field of the request MUST be set to the remote URI * from the dialog state. The tag in the To header field of the request * MUST be set to the remote tag of the dialog ID. The From URI of the * request MUST be set to the local URI from the dialog state. The tag * in the From header field of the request MUST be set to the local tag * of the dialog ID. If the value of the remote or local tags is null, * the tag parameter MUST be omitted from the To or From header fields, * respectively. * * The Call-ID of the request MUST be set to the Call-ID of the dialog. * Requests within a dialog MUST contain strictly monotonically * increasing and contiguous CSeq sequence numbers (increasing-by-one) * in each direction (excepting ACK and CANCEL of course, whose numbers * equal the requests being acknowledged or cancelled). Therefore, if * the local sequence number is not empty, the value of the local * sequence number MUST be incremented by one, and this value MUST be * placed into the CSeq header field. If the local sequence number is * empty, an initial value MUST be chosen using the guidelines of * Section 8.1.1.5. The method field in the CSeq header field value * MUST match the method of the request. * * With a length of 32 bits, a client could generate, within a single * call, one request a second for about 136 years before needing to * wrap around. The initial value of the sequence number is chosen * so that subsequent requests within the same call will not wrap * around. A non-zero initial value allows clients to use a time- * based initial sequence number. A client could, for example, * choose the 31 most significant bits of a 32-bit second clock as an * initial sequence number. * * The UAC uses the remote target and route set to build the Request-URI * and Route header field of the request. * * If the route set is empty, the UAC MUST place the remote target URI * into the Request-URI. The UAC MUST NOT add a Route header field to * the request. * * If the route set is not empty, and the first URI in the route set * contains the lr parameter (see Section 19.1.1), the UAC MUST place * the remote target URI into the Request-URI and MUST include a Route * header field containing the route set values in order, including all * parameters. * * If the route set is not empty, and its first URI does not contain the * lr parameter, the UAC MUST place the first URI from the route set * into the Request-URI, stripping any parameters that are not allowed * in a Request-URI. The UAC MUST add a Route header field containing * the remainder of the route set values in order, including all * parameters. The UAC MUST then place the remote target URI into the * Route header field as the last value. * * For example, if the remote target is sip:user@remoteua and the route * set contains: * <sip:proxy1>,<sip:proxy2>,<sip:proxy3;lr>,<sip:proxy4> * * The request will be formed with the following Request-URI and Route * header field: * METHOD sip:proxy1 * Route: <sip:proxy2>,<sip:proxy3;lr>,<sip:proxy4>,<sip:user@remoteua> * * If the first URI of the route set does not contain the lr * parameter, the proxy indicated does not understand the routing * mechanisms described in this document and will act as specified in * RFC 2543, replacing the Request-URI with the first Route header * field value it receives while forwarding the message. Placing the * Request-URI at the end of the Route header field preserves the * information in that Request-URI across the strict router (it will * be returned to the Request-URI when the request reaches a loose- * router). * * A UAC SHOULD include a Contact header field in any target refresh * requests within a dialog, and unless there is a need to change it, * the URI SHOULD be the same as used in previous requests within the * dialog. If the "secure" flag is true, that URI MUST be a SIPS URI. * As discussed in Section 12.2.2, a Contact header field in a target * refresh request updates the remote target URI. This allows a UA to * provide a new contact address, should its address change during the * duration of the dialog. * * However, requests that are not target refresh requests do not affect * the remote target URI for the dialog. * * The rest of the request is formed as described in Section 8.1.1. */ lock (m_pLock) { SIP_Request request = m_pStack.CreateRequest(method, new SIP_t_NameAddress("", m_pRemoteUri), new SIP_t_NameAddress("", m_pLocalUri)); if (m_pRouteSet.Length == 0) { request.RequestLine.Uri = m_pRemoteTarget; } else { SIP_Uri topmostRoute = ((SIP_Uri)m_pRouteSet[0].Address.Uri); if (topmostRoute.Param_Lr) { request.RequestLine.Uri = m_pRemoteTarget; for (int i = 0; i < m_pRouteSet.Length; i++) { request.Route.Add(m_pRouteSet[i].ToStringValue()); } } else { request.RequestLine.Uri = SIP_Utils.UriToRequestUri(topmostRoute); for (int i = 1; i < m_pRouteSet.Length; i++) { request.Route.Add(m_pRouteSet[i].ToStringValue()); } } } request.To.Tag = m_RemoteTag; request.From.Tag = m_LocalTag; request.CallID = m_CallID; request.CSeq.SequenceNumber = ++m_LocalSeqNo; request.Contact.Add(m_pLocalContact.ToString()); return(request); } }
/// <summary> /// Processes specified request through this dialog. /// </summary> /// <param name="e">Method arguments.</param> /// <returns>Returns true if this dialog processed specified request, otherwise false.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>e</b> is null reference.</exception> internal protected override bool ProcessRequest(SIP_RequestReceivedEventArgs e) { if (e == null) { throw new ArgumentNullException("e"); } if (base.ProcessRequest(e)) { return(true); } if (e.Request.RequestLine.Method == SIP_Methods.ACK) { if (this.State == SIP_DialogState.Early) { this.SetState(SIP_DialogState.Confirmed, true); } else if (this.State == SIP_DialogState.Terminating) { this.SetState(SIP_DialogState.Confirmed, false); Terminate(m_TerminateReason, true); } } else if (e.Request.RequestLine.Method == SIP_Methods.BYE) { e.ServerTransaction.SendResponse(this.Stack.CreateResponse(SIP_ResponseCodes.x200_Ok, e.Request)); m_IsTerminatedByRemoteParty = true; OnTerminatedByRemoteParty(e); SetState(SIP_DialogState.Terminated, true); return(true); } else if (e.Request.RequestLine.Method == SIP_Methods.INVITE) { /* RFC 3261 14.2. * A UAS that receives a second INVITE before it sends the final * response to a first INVITE with a lower CSeq sequence number on the * same dialog MUST return a 500 (Server Internal Error) response to the * second INVITE and MUST include a Retry-After header field with a * randomly chosen value of between 0 and 10 seconds. */ // Dialog base class will handle this case. /* * foreach(SIP_Transaction tr in this.Transactions){ * if(tr is SIP_ServerTransaction && (tr.State == SIP_TransactionState.Calling || tr.State == SIP_TransactionState.Proceeding)){ * if(e.Request.CSeq.SequenceNumber < tr.Request.CSeq.SequenceNumber){ * SIP_Response response = this.Stack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": INVITE with higher Seq sequence number is progress.",e.Request); * response.RetryAfter = new SIP_t_RetryAfter((new Random()).Next(1,10).ToString()); * e.ServerTransaction.SendResponse(response); * * return true; * } * * break; * } * }*/ /* RFC 3261 14.2. * A UAS that receives an INVITE on a dialog while an INVITE it had sent * on that dialog is in progress MUST return a 491 (Request Pending) * response to the received INVITE. */ if (this.HasPendingInvite) { e.ServerTransaction.SendResponse(this.Stack.CreateResponse(SIP_ResponseCodes.x491_Request_Pending, e.Request)); return(true); } } // RFC 5057 5.6. Refusing New Usages. Decline(603 Decline) new dialog usages. else if (SIP_Utils.MethodCanEstablishDialog(e.Request.RequestLine.Method)) { e.ServerTransaction.SendResponse(this.Stack.CreateResponse(SIP_ResponseCodes.x603_Decline + " : New dialog usages in dialog not allowed (RFC 5057).", e.Request)); return(true); } return(false); }