/// <summary> /// This method is called when new SIP request received. /// </summary> /// <param name="requestContext">SIP proxy request context.</param> /// <returns>Returns true if request handled by this method, otherwise false.</returns> /// <remarks> /// This method is called when SIP proxy receives new out of transaction request. /// </remarks> public virtual bool ProcessRequest(SIP_RequestContext requestContext) { // REMOVE ME: /* Handler description. *) Any tel: URI is routed to the specified target gateway. *) URIs starting with + is routed to the specified target gateway. *) Require authentication. */ // TODO: ACK // This is not URI we want. if (requestContext.Request.RequestLine.Uri.Scheme.ToLower() != "tel" && !(requestContext.Request.RequestLine.Uri is SIP_Uri)) { return(false); } SIP_Uri requestUri = (SIP_Uri)requestContext.Request.RequestLine.Uri; long dummy = 0; if (requestUri.User.StartsWith("+") || Int64.TryParse(requestUri.User, out dummy)) { // Not authenticated, send authentication challenge. if (requestContext.User == null) { requestContext.ChallengeRequest(); return(true); } // Create staefull proxy context for request forwarding. SIP_ProxyContext proxyContext = requestContext.ProxyContext; // Add target server credentials, if any. //proxyContext.Credentials.Add(new NetworkCredential("user","password","domain-realm")); // ... // Start statefull request proxying. proxyContext.Start(); return(true); } return(false); }
/// <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 }