private void toResponse(SIP_RequestReceivedEventArgs e) { SIP_Uri uri = e.Request.RequestLine.Uri as SIP_Uri; try { _sdp = SDP_Message.Parse(MyEncoder.Encoder.GetString(e.Request.Data)); RemoteIP = _sdp.Origin.UnicastAddress; if (_sdp.MediaDescriptions.Count == 0) { e.ServerTransaction.SendResponse(_sipServer.Stack.CreateResponse(SIP_ResponseCodes.x400_Bad_Request, e.Request)); return; } else { RemotePort = _sdp.MediaDescriptions[0].Port; } } catch (Exception) { //解析SDP失败。 e.ServerTransaction.SendResponse(_sipServer.Stack.CreateResponse(SIP_ResponseCodes.x400_Bad_Request, e.Request)); return; } //send 100 Trying; e.ServerTransaction.SendResponse(_sipServer.Stack.CreateResponse(SIP_ResponseCodes.x100_Trying, e.Request)); _videoId = _sipServer.DeviceManager.GetVideoId(uri.User); if (_videoId != null) { RTPServer rtp = _sipServer.RTPManager.GetOrAddServer(_videoId); SDP_Message respSDP = new SDP_Message(); respSDP.Version = "0"; respSDP.Origin = new SDP_Origin(uri.User, 0, 0, "IN", "IPV4", rtp.LocalIP); respSDP.SessionName = "Play"; respSDP.Connection = new SDP_Connection("IN", "IPV4", rtp.LocalIP); respSDP.SSRC = SDP_Utils.SSRC2String(SDP_Utils.GenSSRC(uri.User, true)); //根据国标补充协议标准生成SSRC。 respSDP.Times.Add(new SDP_Time(0, 0)); respSDP.MediaDescriptions.Add(new SDP_MediaDescription("video", rtp.Port, 2, "RTP/AVP", new string[] { "96", "97", "98" })); respSDP.Attributes.Add(new SDP_Attribute("sendonly", "")); respSDP.Attributes.Add(new SDP_Attribute("rtpmap", "96 PS/90000")); respSDP.Attributes.Add(new SDP_Attribute("rtpmap", "97 MPEG4/90000")); respSDP.Attributes.Add(new SDP_Attribute("rtpmap", "98 H264/90000")); SIP_Response resp = _sipServer.Stack.CreateResponse(SIP_ResponseCodes.x200_Ok, e.Request); resp.Data = respSDP.ToByte(); e.ServerTransaction.SendResponse(resp); } else { //没有找到视频源。 e.ServerTransaction.SendResponse(_sipServer.Stack.CreateResponse(SIP_ResponseCodes.x404_Not_Found, e.Request)); } }
public static SIP_Message ParseSIPMessage(byte[] data) { try { return(SIP_Request.Parse(data)); } catch { try { return(SIP_Response.Parse(data)); } catch { return(null); } } }
/// <summary> /// Sends ringing to remote party. /// </summary> /// <param name="sdp">Early media answer or early media offer when initial INVITE don't have SDP.</param> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception> /// <exception cref="InvalidOperationException">Is raised when call is not in valid state and this method is called.</exception> public void SendRinging(SDP_Message sdp) { if (m_State == SIP_UA_CallState.Disposed) { throw new ObjectDisposedException(this.GetType().Name); } if (m_State != SIP_UA_CallState.WaitingToAccept) { throw new InvalidOperationException("Accept method can be called only in 'SIP_UA_CallState.WaitingToAccept' state."); } SIP_Response response = m_pUA.Stack.CreateResponse(SIP_ResponseCodes.x180_Ringing, m_pInitialInviteTransaction.Request, m_pInitialInviteTransaction.Flow); if (sdp != null) { response.ContentType = "application/sdp"; response.Data = sdp.ToByte(); m_pLocalSDP = sdp; } m_pInitialInviteTransaction.SendResponse(response); }
/// <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> /// 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 }
private static void device_OnPacketArrival(object sender, CaptureEventArgs e) { var time = e.Packet.Timeval.Date; var len = e.Packet.Data.Length; var packet = PacketDotNet.Packet.ParsePacket(e.Packet.LinkLayerType, e.Packet.Data); var udpPacket = PacketDotNet.UdpPacket.GetEncapsulated(packet); if (udpPacket != null) { try { // signalling packet SIP_Message msg = ParseSIPMessage(udpPacket.PayloadData); if (msg != null && msg.CallID != null) { SDP_Message sdp = null; try { sdp = SDP_Message.Parse(System.Text.ASCIIEncoding.Default.GetString(msg.Data)); } catch { } if (msg is SIP_Request && msg.CallID != null) { SIP_Request r = (SIP_Request)msg; if (!Call.Calls.ContainsKey(r.CallID)) { if (r.RequestLine.Method == "INVITE") { Call.Calls.Add(r.CallID, new Call(r.CallID)); Call.Calls[r.CallID].CallerIP = ((IpPacket)udpPacket.ParentPacket).SourceAddress; Call.Calls[r.CallID].CalleeIP = ((IpPacket)udpPacket.ParentPacket).DestinationAddress; } else { return; // Ignore this conversation } } // if this is an invite, do we have an audio rtp port defined? if (r.RequestLine.Method == "INVITE") { if (sdp != null) { foreach (var a in sdp.MediaDescriptions) { Console.Out.WriteLine(r.CallID + " - Got RTP Media Port: " + ((IpPacket)udpPacket.ParentPacket).SourceAddress + ":" + a.Port.ToString()); if (Call.Calls[r.CallID].CallerIP.ToString() == ((IpPacket)udpPacket.ParentPacket).SourceAddress.ToString()) { Call.Calls[r.CallID].CallerRTPPort = a.Port; } else { Call.Calls[r.CallID].CalleeRTPPort = a.Port; } } } } if (r.RequestLine.Method == "BYE") { if (Call.Calls.ContainsKey(r.CallID)) { // Log bye was recevied Call.Calls[r.CallID].SeenBYE = true; // Now indicate who hung up Call.Calls[r.CallID].WhoHungUp = ((IpPacket)udpPacket.ParentPacket).SourceAddress == Call.Calls[r.CallID].CallerIP ? Call.CallDirection.Caller : Call.CallDirection.Callee; } else { Console.WriteLine("Unknown CallID: " + r.CallID); } } } else if (msg is SIP_Response && msg.CallID != null) { SIP_Response r = (SIP_Response)msg; if (sdp != null) { foreach (var a in sdp.MediaDescriptions) { Console.Out.WriteLine(r.CallID + " - Got RTP Media Port: " + ((IpPacket)udpPacket.ParentPacket).SourceAddress + ":" + a.Port.ToString()); if (Call.Calls[r.CallID].CallerIP.ToString() == ((IpPacket)udpPacket.ParentPacket).SourceAddress.ToString()) { Call.Calls[r.CallID].CallerRTPPort = a.Port; } else { Call.Calls[r.CallID].CalleeRTPPort = a.Port; } } } if (Call.Calls.ContainsKey(r.CallID)) { if (r.StatusCodeType == SIP_StatusCodeType.Success && Call.Calls[r.CallID].SeenBYE) { Call.Calls[r.CallID].Confirmed = true; } } } // Add packet to history if (Call.Calls.ContainsKey(msg.CallID)) { Call.Calls[msg.CallID].WritePacket(e.Packet, Call.PacketType.SIPDialog); // Check to see is this call has been terminated if (Call.Calls[msg.CallID].Confirmed) { // Close off the call now last data has been written Console.WriteLine("Call Ended: " + msg.CallID); // Close off the call Call.Calls[msg.CallID].CloseCall(); // Remove the call from the in-memory list Call.Calls.Remove(msg.CallID); } } } else { Call c = Call.GetCallByRTPPort(udpPacket.SourcePort); if (c != null) { c.WritePacket(e.Packet, Call.PacketType.RTP); } } } catch (Exception ex) { Console.WriteLine(ex.ToString()); } } }
public void Handler(Packet packet) { var udpPacket = UdpPacket.GetEncapsulated(packet); // if it's not udp , udpPacket will be null and we don't handle it. if (udpPacket != null) { try { // signalling packet SIP_Message msg = ParseSIPMessage(udpPacket.PayloadData); if (msg != null && msg.CallID != null) { SDP_Message sdp = null; Console.WriteLine("SIP capture"); try { sdp = SDP_Message.Parse(System.Text.Encoding.Default.GetString(msg.Data)); } catch { } if (msg is SIP_Request && msg.CallID != null) { SIP_Request r = (SIP_Request)msg; //already containsKey if (!Call.SIPSessions.ContainsKey(r.CallID)) { if (r.RequestLine.Method == "INVITE") { Call.SIPSessions.Add(r.CallID, new Call(r.CallID)); Call.SIPSessions[r.CallID].CallerIP = ((IpPacket)udpPacket.ParentPacket).SourceAddress; Call.SIPSessions[r.CallID].CalleeIP = ((IpPacket)udpPacket.ParentPacket).DestinationAddress; } else { return; // Ignore this conversation } } // if this is an invite, do we have an audio rtp port defined? if (r.RequestLine.Method == "INVITE") { if (sdp != null) { foreach (var a in sdp.MediaDescriptions) { Console.Out.WriteLine(r.CallID + " - Got RTP Media Port: " + ((IpPacket)udpPacket.ParentPacket).SourceAddress + ":" + a.Port.ToString()); if (Call.SIPSessions[r.CallID].CallerIP.ToString() == ((IpPacket)udpPacket.ParentPacket).SourceAddress.ToString()) { Call.SIPSessions[r.CallID].CallerRTPPort = a.Port; } else { Call.SIPSessions[r.CallID].CalleeRTPPort = a.Port; } a.MediaFormats.GetType(); break; // First description is about audio . Second is about viedo and we don't need it, so break. } } } if (r.RequestLine.Method == "BYE") { if (Call.SIPSessions.ContainsKey(r.CallID)) { // Log bye was recevied Call.SIPSessions[r.CallID].SeenBYE = true; // Now indicate who hung up Call.SIPSessions[r.CallID].WhoHungUp = ((IpPacket)udpPacket.ParentPacket).SourceAddress == Call.SIPSessions[r.CallID].CallerIP ? Call.CallDirection.Caller : Call.CallDirection.Callee; } else { Console.WriteLine("Unknown CallID: " + r.CallID); } } }// if (msg is SIP_Request && msg.CallID != null) else if (msg is SIP_Response && msg.CallID != null) { SIP_Response r = (SIP_Response)msg; if (r.StatusCode != 183 && r.StatusCode != 100 && r.StatusCode != 200) { Call.SIPSessions[r.CallID].isEnd = true; } if (sdp != null) { foreach (var a in sdp.MediaDescriptions) { Console.Out.WriteLine(r.CallID + " - Got RTP Media Port: " + ((IpPacket)udpPacket.ParentPacket).SourceAddress + ":" + a.Port.ToString()); if (Call.SIPSessions[r.CallID].CallerIP.ToString() == ((IpPacket)udpPacket.ParentPacket).SourceAddress.ToString()) { Call.SIPSessions[r.CallID].CallerRTPPort = a.Port; } else { Call.SIPSessions[r.CallID].CalleeRTPPort = a.Port; } break; // First description is about audio . Second is about viedo and we don't need it, so break. } } if (Call.SIPSessions.ContainsKey(r.CallID)) { if (r.StatusCodeType == SIP_StatusCodeType.Success && Call.SIPSessions[r.CallID].SeenBYE) { Call.SIPSessions[r.CallID].Confirmed = true; Call.SIPSessions[r.CallID].isEnd = true; } } } // Add packet to history if (Call.SIPSessions.ContainsKey(msg.CallID)) { Call.SIPSessions[msg.CallID].WritePacket(packet, Call.PacketType.SIPDialog); // Check to see is this call has been terminated if (Call.SIPSessions[msg.CallID].Confirmed) { // Close off the call now last data has been written Console.WriteLine("Call Ended: " + msg.CallID); // Close off the call Call.SIPSessions[msg.CallID].CloseCall(); StringBuilder file = new StringBuilder(Directory.GetCurrentDirectory() + "//" + Call.SIPSessions[msg.CallID].SIPPacketFilePathAndName); StringBuilder StoragePath = new StringBuilder(Directory.GetCurrentDirectory() + "//" + Call.SIPSessions[msg.CallID].SIPPacketFilePath); pacp_to_wav(file, StoragePath); } if (Call.SIPSessions[msg.CallID].isEnd == true) { Call.SIPSessions.Remove(msg.CallID); } } } else { Call c = Call.GetCallByRTPPort(udpPacket.SourcePort); if (c != null) { c.WritePacket(packet, Call.PacketType.RTP); } } } catch (Exception ex) { Console.WriteLine(ex.ToString()); } } }
/// <summary> /// Cleans up any resources being used. /// </summary> public void Dispose() { lock(m_pLock){ if(m_IsDisposed){ return; } m_IsDisposed = true; m_pDialog.m_pUasInvite2xxRetransmits.Remove(this); if(m_pTimer != null){ m_pTimer.Dispose(); m_pTimer = null; } m_pDialog = null; m_pResponse = null; } }
/// <summary> /// Default constructor. /// </summary> /// <param name="dialog">Owner INVITE dialog.</param> /// <param name="response">INVITE 2xx response.</param> /// <exception cref="ArgumentNullException">Is raised when <b>dialog</b> or <b>response</b> is null reference.</exception> public UasInvite2xxRetransmit(SIP_Dialog_Invite dialog,SIP_Response response) { if(dialog == null){ throw new ArgumentNullException("dialog"); } if(response == null){ throw new ArgumentNullException("response"); } m_pDialog = dialog; m_pResponse = response; /* RFC 3261 13.3.1.4. Once the response has been constructed, it is passed to the INVITE server transaction. Note, however, that the INVITE server transaction will be destroyed as soon as it receives this final response and passes it to the transport. Therefore, it is necessary to periodically pass the response directly to the transport until the ACK arrives. The 2xx response is passed to the transport with an interval that starts at T1 seconds and doubles for each retransmission until it reaches T2 seconds (T1 and T2 are defined in Section 17). Response retransmissions cease when an ACK request for the response is received. This is independent of whatever transport protocols are used to send the response. Since 2xx is retransmitted end-to-end, there may be hops between UAS and UAC that are UDP. To ensure reliable delivery across these hops, the response is retransmitted periodically even if the transport at the UAS is reliable. */ m_pTimer = new TimerEx(SIP_TimerConstants.T1,false); m_pTimer.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimer_Elapsed); m_pTimer.Enabled = true; }
/// <summary> /// Processes retransmited INVITE 2xx response. /// </summary> /// <param name="response">INVITE 2xx response.</param> /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null reference.</exception> public void Process(SIP_Response response) { if(response == null){ throw new ArgumentNullException("response"); } lock(m_pLock){ SIP_Request ack = CreateAck(); try{ // Try existing flow. m_pDialog.Flow.Send(ack); // Log if(m_pDialog.Stack.Logger != null){ byte[] ackBytes = ack.ToByteData(); m_pDialog.Stack.Logger.AddWrite( m_pDialog.ID, null, ackBytes.Length, "Dialog [id='" + m_pDialog.ID + "] ACK sent for 2xx response.", m_pDialog.Flow.LocalEP, m_pDialog.Flow.RemoteEP, ackBytes ); } } catch{ /* RFC 3261 13.2.2.4. Once the ACK has been constructed, the procedures of [4] are used to determine the destination address, port and transport. However, the request is passed to the transport layer directly for transmission, rather than a client transaction. */ try{ m_pDialog.Stack.TransportLayer.SendRequest(ack); } catch(Exception x){ // Log if(m_pDialog.Stack.Logger != null){ m_pDialog.Stack.Logger.AddText("Dialog [id='" + m_pDialog.ID + "'] ACK send for 2xx response failed: " + x.Message + "."); } } } } }
/// <summary> /// Checks if specified response matches this 2xx response retransmission wait entry. /// </summary> /// <param name="response">INVITE 2xx response.</param> /// <returns>Returns true if response matches, othwerwise false.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null reference value.</exception> public bool Match(SIP_Response response) { if(response == null){ throw new ArgumentNullException("response"); } if(m_pInvite.CSeq.RequestMethod == response.CSeq.RequestMethod && m_pInvite.CSeq.SequenceNumber == response.CSeq.SequenceNumber){ return true; } else{ return false; } }
/// <summary> /// Sends SIP response to caller. If proxy context is in B2BUA mode, new response is generated /// as needed. /// </summary> /// <param name="transaction">Client transaction what response it is.</param> /// <param name="response">Response to send.</param> private void SendResponse(SIP_ClientTransaction transaction, SIP_Response response) { if (m_IsB2BUA) { /* draft-marjou-sipping-b2bua-00 4.1.3. When the UAC side of the B2BUA receives the downstream SIP response of a forwarded request, its associated UAS creates an upstream response (except for 100 responses). The creation of the Via, Max- Forwards, Call-Id, CSeq, Record-Route and Contact header fields follows the rules of [2]. The Record-Route header fields of the downstream response are not copied in the new upstream response, as Record-Route is meaningful for the downstream dialog. The UAS SHOULD copy other header fields and body from the downstream response into this upstream response before sending it. */ SIP_Request originalRequest = m_pServerTransaction.Request; // We need to use caller original request to construct response from proxied response. SIP_Response b2buaResponse = response.Copy(); b2buaResponse.Via.RemoveAll(); b2buaResponse.Via.AddToTop(originalRequest.Via.GetTopMostValue().ToStringValue()); b2buaResponse.CallID = originalRequest.CallID; b2buaResponse.CSeq = originalRequest.CSeq; b2buaResponse.Contact.RemoveAll(); //b2buaResponse.Contact.Add(m_pProxy.CreateContact(originalRequest.From.Address).ToStringValue()); b2buaResponse.RecordRoute.RemoveAll(); b2buaResponse.Allow.RemoveAll(); b2buaResponse.Supported.RemoveAll(); // Accept to non ACK,BYE request. if (originalRequest.RequestLine.Method != SIP_Methods.ACK && originalRequest.RequestLine.Method != SIP_Methods.BYE) { b2buaResponse.Allow.Add("INVITE,ACK,OPTIONS,CANCEL,BYE,PRACK"); } // Supported to non ACK request. if (originalRequest.RequestLine.Method != SIP_Methods.ACK) { b2buaResponse.Supported.Add("100rel,timer"); } // Remove Require: header. b2buaResponse.Require.RemoveAll(); m_pServerTransaction.SendResponse(b2buaResponse); // If INVITE 2xx response do call here. if (response.CSeq.RequestMethod.ToUpper() == SIP_Methods.INVITE && response.StatusCodeType == SIP_StatusCodeType.Success) { m_pProxy.B2BUA.AddCall(m_pServerTransaction.Dialog, transaction.Dialog); } } else { m_pServerTransaction.SendResponse(response); } }
/// <summary> /// Processes received response. /// </summary> /// <param name="handler">Target handler what received response.</param> /// <param name="transaction">Client transaction what response it is.</param> /// <param name="response">Response received.</param> /// <exception cref="ArgumentNullException">Is raised when <b>handler</b>,<b>transaction</b> or <b>response</b> is null reference.</exception> private void ProcessResponse(TargetHandler handler, SIP_ClientTransaction transaction, SIP_Response response) { if (handler == null) { throw new ArgumentNullException("handler"); } if (transaction == null) { throw new ArgumentNullException("transaction"); } if (response == null) { throw new ArgumentNullException("response"); } /* RFC 3261 16.7 Response Processing. Steps 1 - 2 handled in TargetHandler. 3. Remove the topmost Via 4. Add the response to the response context 5. Check to see if this response should be forwarded immediately 6. When necessary, choose the best final response from the response context. If no final response has been forwarded after every client transaction associated with the response context has been terminated, the proxy must choose and forward the "best" response from those it has seen so far. The following processing MUST be performed on each response that is forwarded. It is likely that more than one response to each request will be forwarded: at least each provisional and one final response. 7. Aggregate authorization header field values if necessary 8. Optionally rewrite Record-Route header field values 9. Forward the response 10. Generate any necessary CANCEL requests */ bool forwardResponse = false; lock (m_pLock) { #region 3. Remove the topmost Via /* The proxy removes the topmost Via header field value from the response. If no Via header field values remain in the response, the response was meant for this element and MUST NOT be forwarded. The remainder of the processing described in this section is not performed on this message, the UAC processing rules described in Section 8.1.3 are followed instead (transport layer processing has already occurred). This will happen, for instance, when the element generates CANCEL requests as described in Section 10. NOTE: We MAY NOT do it for B2BUA, skip it for B2BUA */ if (!m_IsB2BUA) { response.Via.RemoveTopMostValue(); if (response.Via.GetAllValues().Length == 0) { return; } } #endregion #region 4. Add the response to the response context /* Final responses received are stored in the response context until a final response is generated on the server transaction associated with this context. The response may be a candidate for the best final response to be returned on that server transaction. Information from this response may be needed in forming the best response, even if this response is not chosen. If the proxy chooses to recurse on any contacts in a 3xx response by adding them to the target set, it MUST remove them from the response before adding the response to the response context. However, a proxy SHOULD NOT recurse to a non-SIPS URI if the Request-URI of the original request was a SIPS URI. If the proxy recurses on all of the contacts in a 3xx response, the proxy SHOULD NOT add the resulting contactless response to the response context. Removing the contact before adding the response to the response context prevents the next element upstream from retrying a location this proxy has already attempted. 3xx responses may contain a mixture of SIP, SIPS, and non-SIP URIs. A proxy may choose to recurse on the SIP and SIPS URIs and place the remainder into the response context to be returned, potentially in the final response. */ if (response.StatusCodeType == SIP_StatusCodeType.Redirection && !m_NoRecurse && !handler.IsRecursed) { // Get SIP contacts and remove them from response. SIP_t_ContactParam[] contacts = response.Contact.GetAllValues(); // Remove all contacts from response, we add non-SIP URIs back. response.Contact.RemoveAll(); foreach (SIP_t_ContactParam contact in contacts) { // SIP URI add it to fork list. if (contact.Address.IsSipOrSipsUri) { m_pTargets.Enqueue(new TargetHandler(this, null, (SIP_Uri) contact.Address.Uri, m_AddRecordRoute, true)); } // Add specified URI back to response. else { response.Contact.Add(contact.ToStringValue()); } } // There are remaining non-SIP contacts, so we need to add the response to reponses collection. if (response.Contact.GetAllValues().Length > 0) { m_pResponses.Add(response); } // Handle forking if (m_pTargets.Count > 0) { if (m_ForkingMode == SIP_ForkingMode.Parallel) { while (m_pTargets.Count > 0) { TargetHandler h = m_pTargets.Dequeue(); m_pTargetsHandlers.Add(handler); h.Start(); } } // Just fork next. else { TargetHandler h = m_pTargets.Dequeue(); m_pTargetsHandlers.Add(handler); h.Start(); } // Because we forked request to new target(s), we don't need to do steps 5 - 10. return; } } // Not 3xx response or recursing disabled. else { m_pResponses.Add(response); } #endregion #region 5. Check to see if this response should be forwarded immediately /* Until a final response has been sent on the server transaction, the following responses MUST be forwarded immediately: - Any provisional response other than 100 (Trying) - Any 2xx response If a 6xx response is received, it is not immediately forwarded, but the stateful proxy SHOULD cancel all client pending transactions as described in Section 10, and it MUST NOT create any new branches in this context. After a final response has been sent on the server transaction, the following responses MUST be forwarded immediately: - Any 2xx response to an INVITE request */ if (!m_IsFinalResponseSent) { if (response.StatusCodeType == SIP_StatusCodeType.Provisional && response.StatusCode != 100) { forwardResponse = true; } else if (response.StatusCodeType == SIP_StatusCodeType.Success) { forwardResponse = true; } else if (response.StatusCodeType == SIP_StatusCodeType.GlobalFailure) { CancelAllTargets(); } } else { if (response.StatusCodeType == SIP_StatusCodeType.Success && m_pServerTransaction.Request.RequestLine.Method == SIP_Methods.INVITE) { forwardResponse = true; } } #endregion #region x. Handle sequential forking /* Sequential Search: In a sequential search, a proxy server attempts each contact address in sequence, proceeding to the next one only after the previous has generated a final response. A 2xx or 6xx class final response always terminates a sequential search. */ if (m_ForkingMode == SIP_ForkingMode.Sequential && response.StatusCodeType != SIP_StatusCodeType.Provisional) { if (response.StatusCodeType == SIP_StatusCodeType.Success) { // Do nothing, 2xx will be always forwarded and step 10. Cancels all targets. } else if (response.StatusCodeType == SIP_StatusCodeType.GlobalFailure) { // Do nothing, 6xx is already handled in setp 5. } else if (m_pTargets.Count > 0) { TargetHandler h = m_pTargets.Dequeue(); m_pTargetsHandlers.Add(handler); h.Start(); // Skip all next steps, we will get new responses from new target. return; } } #endregion #region 6. When necessary, choose the best final response from the response context /* A stateful proxy MUST send a final response to a response context's server transaction if no final responses have been immediately forwarded by the above rules and all client transactions in this response context have been terminated. The stateful proxy MUST choose the "best" final response among those received and stored in the response context. If there are no final responses in the context, the proxy MUST send a 408 (Request Timeout) response to the server transaction. */ if (!m_IsFinalResponseSent && !forwardResponse && m_pTargets.Count == 0) { bool mustChooseBestFinalResponse = true; // Check if all transactions terminated. foreach (TargetHandler h in m_pTargetsHandlers) { if (!h.IsCompleted) { mustChooseBestFinalResponse = false; break; } } if (mustChooseBestFinalResponse) { response = GetBestFinalResponse(); if (response == null) { response = Proxy.Stack.CreateResponse(SIP_ResponseCodes.x408_Request_Timeout, m_pServerTransaction.Request); } forwardResponse = true; } } #endregion if (forwardResponse) { #region 7. Aggregate authorization header field values if necessary /* If the selected response is a 401 (Unauthorized) or 407 (Proxy Authentication Required), the proxy MUST collect any WWW-Authenticate and Proxy-Authenticate header field values from all other 401 (Unauthorized) and 407 (Proxy Authentication Required) responses received so far in this response context and add them to this response without modification before forwarding. The resulting 401 (Unauthorized) or 407 (Proxy Authentication Required) response could have several WWW-Authenticate AND Proxy-Authenticate header field values. This is necessary because any or all of the destinations the request was forwarded to may have requested credentials. The client needs to receive all of those challenges and supply credentials for each of them when it retries the request. */ if (response.StatusCode == 401 || response.StatusCode == 407) { foreach (SIP_Response resp in m_pResponses.ToArray()) { if (response != resp && (resp.StatusCode == 401 || resp.StatusCode == 407)) { // WWW-Authenticate foreach (SIP_HeaderField hf in resp.WWWAuthenticate.HeaderFields) { resp.WWWAuthenticate.Add(hf.Value); } // Proxy-Authenticate foreach (SIP_HeaderField hf in resp.ProxyAuthenticate.HeaderFields) { resp.ProxyAuthenticate.Add(hf.Value); } } } } #endregion #region 8. Optionally rewrite Record-Route header field values // This is optional so we currently won't do that. #endregion #region 9. Forward the response SendResponse(transaction, response); if (response.StatusCodeType != SIP_StatusCodeType.Provisional) { m_IsFinalResponseSent = true; } #endregion #region 10. Generate any necessary CANCEL requests /* If the forwarded response was a final response, the proxy MUST generate a CANCEL request for all pending client transactions associated with this response context. */ if (response.StatusCodeType != SIP_StatusCodeType.Provisional) { CancelAllTargets(); } #endregion } } }