PageRenderTime 54ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/sipsorcery-core/SIPSorcery.Servers.Cores/SIPApplicationServer/SIPDialogueManager.cs

https://github.com/thecc4re/sipsorcery-mono
C# | 1311 lines | 1009 code | 142 blank | 160 comment | 172 complexity | e72e75535177c5c5791f3f3ac374ad96 MD5 | raw file
Possible License(s): CC-BY-SA-3.0

Large files files are truncated, but you can click here to view the full file

  1. // ============================================================================
  2. // FileName: SIPDialogueManager.cs
  3. //
  4. // Description:
  5. // Manages established dialogues.
  6. //
  7. // Author(s):
  8. // Aaron Clauson
  9. //
  10. // History:
  11. // 10 Feb 2008 Aaron Clauson Created.
  12. //
  13. // License:
  14. // This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
  15. //
  16. // Copyright (c) 2008 Aaron Clauson (aaronc@blueface.ie), Blue Face Ltd, Dublin, Ireland (www.blueface.ie)
  17. // All rights reserved.
  18. //
  19. // Redistribution and use in source and binary forms, with or without modification, are permitted provided that
  20. // the following conditions are met:
  21. //
  22. // Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  23. // Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
  24. // disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd.
  25. // nor the names of its contributors may be used to endorse or promote products derived from this software without specific
  26. // prior written permission.
  27. //
  28. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
  29. // BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  30. // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
  31. // OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
  32. // OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  33. // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  34. // POSSIBILITY OF SUCH DAMAGE.
  35. // ============================================================================
  36. using System;
  37. using System.Collections.Generic;
  38. using System.Data;
  39. using System.Data.SqlClient;
  40. using System.Linq;
  41. using System.Net;
  42. using System.Text;
  43. using System.Text.RegularExpressions;
  44. using System.Threading;
  45. using System.Transactions;
  46. using SIPSorcery.AppServer.DialPlan;
  47. using SIPSorcery.CRM;
  48. using SIPSorcery.Net;
  49. using SIPSorcery.Persistence;
  50. using SIPSorcery.SIP;
  51. using SIPSorcery.SIP.App;
  52. using SIPSorcery.Sys;
  53. using log4net;
  54. namespace SIPSorcery.Servers
  55. {
  56. public class SIPDialogueManager : ISIPDialogueManager
  57. {
  58. private static ILog logger = AppState.logger;
  59. private static string m_userAgentString = SIPConstants.SIP_USERAGENT_STRING;
  60. private static string m_remoteHangupCause = SIPConstants.SIP_REMOTEHANGUP_CAUSE;
  61. private static string m_referReplacesParameter = SIPHeaderAncillary.SIP_REFER_REPLACES;
  62. private static string m_referNotifyEventValue = SIPEventPackage.Refer.ToString();
  63. private static string m_referNotifyContentType = SIPMIMETypes.REFER_CONTENT_TYPE;
  64. private static readonly int m_defaultSIPPort = SIPConstants.DEFAULT_SIP_PORT;
  65. private static readonly string m_sdpContentType = SDP.SDP_MIME_CONTENTTYPE;
  66. private SIPMonitorLogDelegate Log_External;
  67. private SIPAuthenticateRequestDelegate SIPAuthenticateRequest_External;
  68. private SIPAssetGetDelegate<SIPAccount> GetSIPAccount_External;
  69. private GetCanonicalDomainDelegate GetCanonicalDomain_External;
  70. private SIPTransport m_sipTransport;
  71. private SIPEndPoint m_outboundProxy;
  72. private SIPAssetPersistor<SIPDialogueAsset> m_sipDialoguePersistor;
  73. private SIPAssetPersistor<SIPCDRAsset> m_sipCDRPersistor;
  74. private Dictionary<string, string> m_inDialogueTransactions = new Dictionary<string, string>(); // <Forwarded transaction id, Origin transaction id>.
  75. public static IPAddress PublicIPAddress;
  76. public SIPDialogueManager(
  77. SIPTransport sipTransport,
  78. SIPEndPoint outboundProxy,
  79. SIPMonitorLogDelegate logDelegate,
  80. SIPAssetPersistor<SIPDialogueAsset> sipDialoguePersistor,
  81. SIPAssetPersistor<SIPCDRAsset> sipCDRPersistor,
  82. SIPAuthenticateRequestDelegate authenticateRequestDelegate,
  83. SIPAssetGetDelegate<SIPAccount> getSIPAccount,
  84. GetCanonicalDomainDelegate getCanonicalDomain)
  85. {
  86. m_sipTransport = sipTransport;
  87. m_outboundProxy = outboundProxy;
  88. Log_External = logDelegate;
  89. m_sipDialoguePersistor = sipDialoguePersistor;
  90. m_sipCDRPersistor = sipCDRPersistor;
  91. SIPAuthenticateRequest_External = authenticateRequestDelegate;
  92. GetSIPAccount_External = getSIPAccount;
  93. GetCanonicalDomain_External = getCanonicalDomain;
  94. }
  95. public void ProcessInDialogueRequest(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest, SIPDialogue dialogue)
  96. {
  97. try
  98. {
  99. if (sipRequest.Method == SIPMethodsEnum.BYE)
  100. {
  101. SIPNonInviteTransaction byeTransaction = m_sipTransport.CreateNonInviteTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, m_outboundProxy);
  102. //logger.Debug("Matching dialogue found for BYE request to " + sipRequest.URI.ToString() + ".");
  103. SIPResponse byeResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
  104. byeTransaction.SendFinalResponse(byeResponse);
  105. string hangupReason = sipRequest.Header.Reason;
  106. if (hangupReason.IsNullOrBlank())
  107. {
  108. hangupReason = sipRequest.Header.GetUnknownHeaderValue("X-Asterisk-HangupCause");
  109. }
  110. if (sipRequest.Header.SwitchboardTerminate == "both")
  111. {
  112. // BYE request from switchboard that's requesting two dialogues to be hungup by the server.
  113. CallHungup(dialogue, hangupReason, true);
  114. }
  115. else
  116. {
  117. // Normal BYE request.
  118. CallHungup(dialogue, hangupReason, false);
  119. }
  120. }
  121. else if (sipRequest.Method == SIPMethodsEnum.INVITE)
  122. {
  123. UASInviteTransaction reInviteTransaction = m_sipTransport.CreateUASTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, m_outboundProxy);
  124. SIPResponse tryingResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Trying, null);
  125. reInviteTransaction.SendInformationalResponse(tryingResponse);
  126. reInviteTransaction.CDR = null; // Don't want CDR's on re-INVITEs.
  127. ForwardInDialogueRequest(dialogue, reInviteTransaction, localSIPEndPoint, remoteEndPoint);
  128. }
  129. else if (sipRequest.Method == SIPMethodsEnum.OPTIONS)
  130. {
  131. // Send back the remote SDP.
  132. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "OPTIONS request for established dialogue " + dialogue.DialogueName + ".", dialogue.Owner));
  133. SIPNonInviteTransaction optionsTransaction = m_sipTransport.CreateNonInviteTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, m_outboundProxy);
  134. SIPResponse okResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
  135. okResponse.Body = dialogue.RemoteSDP;
  136. okResponse.Header.ContentLength = okResponse.Body.Length;
  137. okResponse.Header.ContentType = m_sdpContentType;
  138. optionsTransaction.SendFinalResponse(okResponse);
  139. }
  140. else if (sipRequest.Method == SIPMethodsEnum.MESSAGE)
  141. {
  142. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "MESSAGE for call " + sipRequest.URI.ToString() + ": " + sipRequest.Body + ".", dialogue.Owner));
  143. SIPNonInviteTransaction messageTransaction = m_sipTransport.CreateNonInviteTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, m_outboundProxy);
  144. SIPResponse okResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
  145. messageTransaction.SendFinalResponse(okResponse);
  146. }
  147. else
  148. {
  149. // This is a request on an established call forward through to the opposite dialogue.
  150. SIPNonInviteTransaction passThruTransaction = m_sipTransport.CreateNonInviteTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, m_outboundProxy);
  151. ForwardInDialogueRequest(dialogue, passThruTransaction, localSIPEndPoint, remoteEndPoint);
  152. }
  153. }
  154. catch (Exception excp)
  155. {
  156. logger.Error("Exception ProcessInDialogueRequest. " + excp.Message);
  157. throw;
  158. }
  159. }
  160. public void ProcessInDialogueReferRequest(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest, SIPDialogue dialogue, Func<string, SIPURI, string, SIPDialogue, ISIPServerUserAgent> blindTransfer)
  161. {
  162. try
  163. {
  164. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "REFER received on dialogue " + dialogue.DialogueName + ", transfer mode is " + dialogue.TransferMode + ".", dialogue.Owner));
  165. SIPNonInviteTransaction referTransaction = m_sipTransport.CreateNonInviteTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, m_outboundProxy);
  166. if (sipRequest.Header.ReferTo.IsNullOrBlank())
  167. {
  168. // A REFER request must have a Refer-To header.
  169. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Bad REFER request, no Refer-To header.", dialogue.Owner));
  170. SIPResponse invalidResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.BadRequest, "Missing mandatory Refer-To header");
  171. referTransaction.SendFinalResponse(invalidResponse);
  172. }
  173. else
  174. {
  175. if (dialogue.TransferMode == SIPDialogueTransferModesEnum.NotAllowed)
  176. {
  177. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "REFER rejected due to dialogue permissions.", dialogue.Owner));
  178. SIPResponse declineTransferResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Decline, "Transfers are disabled on dialogue");
  179. referTransaction.SendFinalResponse(declineTransferResponse);
  180. }
  181. else if (Regex.Match(sipRequest.Header.ReferTo, m_referReplacesParameter).Success)
  182. {
  183. // Attended transfers are allowed unless explicitly blocked. Attended transfers are not dangerous
  184. // as no new call is created and it's the same as a re-invite.
  185. if (dialogue.TransferMode == SIPDialogueTransferModesEnum.PassThru)
  186. {
  187. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "REFER received, attended transfer, passing through, Referred-By=" + sipRequest.Header.ReferredBy + ".", dialogue.Owner));
  188. ForwardInDialogueRequest(dialogue, referTransaction, localSIPEndPoint, remoteEndPoint);
  189. }
  190. else
  191. {
  192. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "REFER received, attended transfer, processing on app server, Referred-By=" + sipRequest.Header.ReferredBy + ".", dialogue.Owner));
  193. ProcessAttendedRefer(dialogue, referTransaction, sipRequest, localSIPEndPoint, remoteEndPoint);
  194. }
  195. }
  196. else
  197. {
  198. bool referAuthenticated = false;
  199. if (dialogue.TransferMode == SIPDialogueTransferModesEnum.Default)
  200. {
  201. string canonicalDomain = GetCanonicalDomain_External(sipRequest.Header.From.FromURI.Host, false);
  202. if (!canonicalDomain.IsNullOrBlank())
  203. {
  204. referAuthenticated = AuthenticateReferRequest(referTransaction, sipRequest.Header.From.FromURI.User, canonicalDomain);
  205. }
  206. }
  207. if (dialogue.TransferMode == SIPDialogueTransferModesEnum.BlindPlaceCall || referAuthenticated)
  208. {
  209. // A blind transfer that is permitted to initiate a new call.
  210. //logger.Debug("Blind Transfer starting.");
  211. SIPResponse acceptedResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Accepted, null);
  212. referTransaction.SendFinalResponse(acceptedResponse);
  213. SendNotifyRequestForRefer(sipRequest, dialogue, localSIPEndPoint, SIPResponseStatusCodesEnum.Trying, null);
  214. //SIPDialogue oppositeDialogue = GetOppositeDialogue(dialogue);
  215. SIPUserField replacesUserField = SIPUserField.ParseSIPUserField(sipRequest.Header.ReferTo);
  216. ISIPServerUserAgent transferUAS = blindTransfer(dialogue.Owner, replacesUserField.URI, "transfer", dialogue);
  217. bool sendNotifications = true;
  218. Guid originalBridgeID = dialogue.BridgeId;
  219. transferUAS.UASStateChanged += (uas, status, reason) =>
  220. {
  221. if (sendNotifications)
  222. {
  223. if (status != SIPResponseStatusCodesEnum.Trying)
  224. {
  225. // As soon as a blind transfer receives a non-100 response break the bridge as most UA's will immediately hangup the call once
  226. // they are informed it's proceeding.
  227. if (dialogue.BridgeId != Guid.Empty)
  228. {
  229. dialogue.BridgeId = Guid.Empty;
  230. m_sipDialoguePersistor.UpdateProperty(dialogue.Id, "BridgeId", dialogue.BridgeId.ToString());
  231. }
  232. // Retrieve the dialogue anew each time a new response is received in order to check if it still exists.
  233. SIPDialogueAsset updatedDialogue = m_sipDialoguePersistor.Get(dialogue.Id);
  234. if (updatedDialogue != null)
  235. {
  236. SendNotifyRequestForRefer(sipRequest, updatedDialogue.SIPDialogue, localSIPEndPoint, status, reason);
  237. }
  238. else
  239. {
  240. // The dialogue the blind transfer notifications were being sent on has been hungup no pint sending any more notifications.
  241. sendNotifications = false;
  242. }
  243. }
  244. }
  245. if ((int)status >= 400)
  246. {
  247. // The transfer has failed. Attempt to re-bridge if possible or if not hangup the orphaned end of the call.
  248. SIPDialogueAsset referDialogue = m_sipDialoguePersistor.Get(dialogue.Id); // Dialogue that initiated the REFER.
  249. if (referDialogue != null)
  250. {
  251. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Blind transfer to " + replacesUserField.URI.ToParameterlessString() + " failed with " + status + ", the initiating dialogue is still available re-creating the original bridge.", dialogue.Owner));
  252. // Re-bridging the two original dialogues.
  253. m_sipDialoguePersistor.UpdateProperty(dialogue.Id, "BridgeId", originalBridgeID.ToString());
  254. }
  255. else
  256. {
  257. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Blind transfer to " + replacesUserField.URI.ToParameterlessString() + " failed with " + status + ", the initiating dialogue hungup, hanging up remaining dialogue.", dialogue.Owner));
  258. // The transfer failed and the dialogue that initiated the transfer has hungup. No point keeping the other end up so hang it up as well.
  259. string bridgeIDStr = originalBridgeID.ToString();
  260. SIPDialogueAsset orphanedDialogueAsset = m_sipDialoguePersistor.Get(d => d.BridgeId == bridgeIDStr);
  261. if (orphanedDialogueAsset != null)
  262. {
  263. HangupDialogue(orphanedDialogueAsset.SIPDialogue, "Blind transfer failed and remote end already hungup.", true);
  264. }
  265. }
  266. }
  267. };
  268. //logger.Debug("Blind Transfer successfully initated, dial plan processing now in progress.");
  269. }
  270. else
  271. {
  272. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "REFER received, blind transfer, Refer-To=" + sipRequest.Header.ReferTo + ", Referred-By=" + sipRequest.Header.ReferredBy + ".", dialogue.Owner));
  273. //SIPNonInviteTransaction passThruTransaction = m_sipTransport.CreateNonInviteTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, m_outboundProxy);
  274. ForwardInDialogueRequest(dialogue, referTransaction, localSIPEndPoint, remoteEndPoint);
  275. }
  276. }
  277. }
  278. }
  279. catch (Exception excp)
  280. {
  281. logger.Error("Exception ProcessInDialogueReferRequest. " + excp.Message);
  282. throw;
  283. }
  284. }
  285. private bool AuthenticateReferRequest(SIPNonInviteTransaction referTransaction, string sipUsername, string sipDomain)
  286. {
  287. SIPRequest referRequest = referTransaction.TransactionRequest;
  288. try
  289. {
  290. if (SIPAuthenticateRequest_External == null)
  291. {
  292. // No point trying to authenticate if we haven't been given an authentication delegate.
  293. logger.Warn("Missing SIP request authentication delegate in SIPDialogueManager AuthenticateReferRequest.");
  294. SIPResponse errorResponse = SIPTransport.GetResponse(referRequest, SIPResponseStatusCodesEnum.InternalServerError, null);
  295. referTransaction.SendFinalResponse(errorResponse);
  296. }
  297. else if (GetSIPAccount_External == null)
  298. {
  299. // No point trying to authenticate if we haven't been given a delegate to load the SIP account.
  300. logger.Warn("Missing get SIP account delegate in SIPDialogueManager AuthenticateReferRequest.");
  301. SIPResponse errorResponse = SIPTransport.GetResponse(referRequest, SIPResponseStatusCodesEnum.InternalServerError, null);
  302. referTransaction.SendFinalResponse(errorResponse);
  303. }
  304. else
  305. {
  306. SIPAccount sipAccount = GetSIPAccount_External(s => s.SIPUsername == sipUsername && s.SIPDomain == sipDomain);
  307. if (sipAccount == null)
  308. {
  309. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Rejecting authentication required call for " + sipUsername + "@" + sipDomain + ", SIP account not found.", null));
  310. SIPResponse errorResponse = SIPTransport.GetResponse(referRequest, SIPResponseStatusCodesEnum.Forbidden, null);
  311. referTransaction.SendFinalResponse(errorResponse);
  312. }
  313. else
  314. {
  315. SIPEndPoint localSIPEndPoint = (!referRequest.Header.ProxyReceivedOn.IsNullOrBlank()) ? SIPEndPoint.ParseSIPEndPoint(referRequest.Header.ProxyReceivedOn) : referRequest.LocalSIPEndPoint;
  316. SIPEndPoint remoteEndPoint = (!referRequest.Header.ProxyReceivedFrom.IsNullOrBlank()) ? SIPEndPoint.ParseSIPEndPoint(referRequest.Header.ProxyReceivedFrom) : referRequest.RemoteSIPEndPoint;
  317. SIPRequestAuthenticationResult authenticationResult = SIPAuthenticateRequest_External(localSIPEndPoint, remoteEndPoint, referRequest, sipAccount, Log_External);
  318. if (authenticationResult.Authenticated)
  319. {
  320. if (authenticationResult.WasAuthenticatedByIP)
  321. {
  322. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "REFER from " + remoteEndPoint.ToString() + " successfully authenticated by IP address.", sipAccount.Owner));
  323. }
  324. else
  325. {
  326. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "REFER from " + remoteEndPoint.ToString() + " successfully authenticated by digest.", sipAccount.Owner));
  327. }
  328. return true;
  329. }
  330. else
  331. {
  332. // Send authorisation failure or required response
  333. SIPResponse authReqdResponse = SIPTransport.GetResponse(referRequest, authenticationResult.ErrorResponse, null);
  334. authReqdResponse.Header.AuthenticationHeader = authenticationResult.AuthenticationRequiredHeader;
  335. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "REFER not authenticated for " + sipUsername + "@" + sipDomain + ", responding with " + authenticationResult.ErrorResponse + ".", null));
  336. referTransaction.SendFinalResponse(authReqdResponse);
  337. return false;
  338. }
  339. }
  340. }
  341. return false;
  342. }
  343. catch (Exception excp)
  344. {
  345. logger.Error("Exception SIDialogueManager AuthenticateReferRequest. " + excp.Message);
  346. SIPResponse errorResponse = SIPTransport.GetResponse(referRequest, SIPResponseStatusCodesEnum.InternalServerError, null);
  347. referTransaction.SendFinalResponse(errorResponse);
  348. return false;
  349. }
  350. }
  351. public void CreateDialogueBridge(SIPDialogue clientDiaglogue, SIPDialogue forwardedDialogue, string owner)
  352. {
  353. logger.Debug("Creating dialogue bridge between " + clientDiaglogue.DialogueName + " and " + forwardedDialogue.DialogueName + ".");
  354. Guid bridgeId = Guid.NewGuid();
  355. clientDiaglogue.BridgeId = bridgeId;
  356. forwardedDialogue.BridgeId = bridgeId;
  357. m_sipDialoguePersistor.Add(new SIPDialogueAsset(clientDiaglogue));
  358. m_sipDialoguePersistor.Add(new SIPDialogueAsset(forwardedDialogue));
  359. SIPEndPoint clientDialogueRemoteEP = (IPSocket.IsIPSocket(clientDiaglogue.RemoteTarget.Host)) ? SIPEndPoint.ParseSIPEndPoint(clientDiaglogue.RemoteTarget.Host) : null;
  360. Log_External(new SIPMonitorMachineEvent(SIPMonitorMachineEventTypesEnum.SIPDialogueCreated, clientDiaglogue.Owner, clientDiaglogue.Id.ToString(), clientDiaglogue.LocalUserField.URI));
  361. SIPEndPoint forwardedDialogueRemoteEP = (IPSocket.IsIPSocket(forwardedDialogue.RemoteTarget.Host)) ? SIPEndPoint.ParseSIPEndPoint(forwardedDialogue.RemoteTarget.Host) : null;
  362. Log_External(new SIPMonitorMachineEvent(SIPMonitorMachineEventTypesEnum.SIPDialogueCreated, forwardedDialogue.Owner, forwardedDialogue.Id.ToString(), forwardedDialogue.LocalUserField.URI));
  363. }
  364. /// <summary>
  365. /// This method takes the necessary actions to terminate a bridged call.
  366. /// </summary>
  367. /// <param name="sipDialogue">The dialogue that the BYE request was received on.</param>
  368. /// <param name="hangupCause">If present an informational field to indicate the hangup cause.</param>
  369. /// <param name="sendBYEForOriginDialogue">If true means a BYE should be sent for the origin dialogue as well. This is used when a 3rd party
  370. /// call control agent is attempting to hangup a call.</param>
  371. public void CallHungup(SIPDialogue sipDialogue, string hangupCause, bool sendBYEForOriginDialogue)
  372. {
  373. try
  374. {
  375. if (sipDialogue != null)
  376. {
  377. //logger.Debug("BYE received on dialogue " + sipDialogue.DialogueName + ".");
  378. HangupDialogue(sipDialogue, hangupCause, sendBYEForOriginDialogue);
  379. if (sipDialogue.BridgeId != Guid.Empty)
  380. {
  381. SIPDialogue orphanedDialogue = GetOppositeDialogue(sipDialogue);
  382. if (orphanedDialogue != null)
  383. {
  384. HangupDialogue(orphanedDialogue, m_remoteHangupCause, true);
  385. }
  386. }
  387. else
  388. {
  389. logger.Warn("No bridge could be found for hungup call.");
  390. }
  391. }
  392. }
  393. catch (Exception excp)
  394. {
  395. logger.Error("Exception CallManager CallHungup. " + excp.Message);
  396. }
  397. }
  398. private void HangupDialogue(SIPDialogue dialogue, string hangupCause, bool sendBye)
  399. {
  400. try
  401. {
  402. //logger.Debug("Hanging up orphaned dialogue " + dialogue.DialogueName + ".");
  403. if (dialogue.CDRId != Guid.Empty)
  404. {
  405. SIPCDRAsset cdr = m_sipCDRPersistor.Get(dialogue.CDRId);
  406. if (cdr != null)
  407. {
  408. cdr.BridgeId = dialogue.BridgeId.ToString();
  409. cdr.Hungup(hangupCause);
  410. }
  411. else
  412. {
  413. logger.Warn("CDR could not be found for remote dialogue in SIPCallManager CallHungup.");
  414. }
  415. }
  416. else
  417. {
  418. logger.Warn("There was no CDR attached to orphaned dialogue in SIPCallManager CallHungup.");
  419. }
  420. if (sendBye)
  421. {
  422. dialogue.Hangup(m_sipTransport, m_outboundProxy);
  423. }
  424. m_sipDialoguePersistor.Delete(new SIPDialogueAsset(dialogue));
  425. SIPEndPoint orphanedDialogueRemoteEP = (IPSocket.IsIPSocket(dialogue.RemoteTarget.Host)) ? SIPEndPoint.ParseSIPEndPoint(dialogue.RemoteTarget.Host) : null;
  426. Log_External(new SIPMonitorMachineEvent(SIPMonitorMachineEventTypesEnum.SIPDialogueRemoved, dialogue.Owner, dialogue.Id.ToString(), dialogue.LocalUserField.URI));
  427. }
  428. catch (Exception excp)
  429. {
  430. logger.Error("Exception HangupDialogue. " + excp.Message);
  431. }
  432. }
  433. /// <summary>
  434. /// Attempts to locate a dialogue for an in-dialogue transaction.
  435. /// </summary>
  436. /// <param name="transaction"></param>
  437. /// <returns></returns>
  438. public SIPDialogue GetDialogue(SIPRequest sipRequest)
  439. {
  440. try
  441. {
  442. string callId = sipRequest.Header.CallId;
  443. string localTag = sipRequest.Header.To.ToTag;
  444. string remoteTag = sipRequest.Header.From.FromTag;
  445. return GetDialogue(callId, localTag, remoteTag);
  446. }
  447. catch (Exception excp)
  448. {
  449. logger.Error("Exception GetDialogue. " + excp);
  450. return null;
  451. }
  452. }
  453. public SIPDialogue GetDialogue(string replaces)
  454. {
  455. try
  456. {
  457. if (replaces.IsNullOrBlank())
  458. {
  459. return null;
  460. }
  461. string unescapedReplaces = SIPEscape.SIPUnescapeString(replaces);
  462. Match replacesMatch = Regex.Match(unescapedReplaces, "^(?<callid>.*?);to-tag=(?<totag>.*?);from-tag=(?<fromtag>.*)");
  463. if (replacesMatch.Success)
  464. {
  465. string callId = replacesMatch.Result("${callid}");
  466. string localTag = replacesMatch.Result("${totag}");
  467. string remoteTag = replacesMatch.Result("${fromtag}");
  468. logger.Debug("Call-ID=" + callId + ", localtag=" + localTag + ", remotetag=" + remoteTag + ".");
  469. SIPDialogue replacesDialogue = GetDialogue(callId, localTag, remoteTag);
  470. if (replacesDialogue == null)
  471. {
  472. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "A dialogue was not found for the Replaces parameter on a Refer-To header.", null));
  473. }
  474. return replacesDialogue;
  475. }
  476. else
  477. {
  478. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "The Replaces parameter on a Refer-To header was not in the expected fromat, " + replaces + ".", null));
  479. return null;
  480. }
  481. }
  482. catch (Exception excp)
  483. {
  484. logger.Error("Exception GetDialogue (replaces). " + excp);
  485. return null;
  486. }
  487. }
  488. public SIPDialogue GetDialogue(string callId, string localTag, string remoteTag)
  489. {
  490. try
  491. {
  492. //string dialogueId = SIPDialogue.GetDialogueId(callId, localTag, remoteTag);
  493. SIPDialogueAsset dialogueAsset = m_sipDialoguePersistor.Get(d => d.CallId == callId && d.LocalTag == localTag && d.RemoteTag == remoteTag);
  494. if (dialogueAsset != null)
  495. {
  496. //logger.Debug("SIPDialogueManager dialogue match correctly found on dialogue hash.");
  497. return dialogueAsset.SIPDialogue;
  498. }
  499. else
  500. {
  501. // Try on To tag.
  502. dialogueAsset = m_sipDialoguePersistor.Get(d => d.LocalTag == localTag);
  503. if (dialogueAsset != null)
  504. {
  505. logger.Warn("SIPDialogueManager dialogue match found on fallback mechanism of To tag.");
  506. return dialogueAsset.SIPDialogue;
  507. }
  508. // Try on From tag.
  509. dialogueAsset = m_sipDialoguePersistor.Get(d => d.RemoteTag == remoteTag);
  510. if (dialogueAsset != null)
  511. {
  512. logger.Warn("SIPDialogueManager dialogue match found on fallback mechanism of From tag.");
  513. return dialogueAsset.SIPDialogue;
  514. }
  515. // As an experiment will try on the Call-ID as well. However as a safeguard it will only succeed if there is only one instance of the
  516. // Call-ID in use. Since the Call-ID is not mandated by the SIP standard as being unique there it may be that matching on it causes more
  517. // problems then it solves.
  518. dialogueAsset = m_sipDialoguePersistor.Get(d => d.CallId == callId);
  519. if (dialogueAsset != null)
  520. {
  521. logger.Warn("SIPDialogueManager dialogue match found on fallback mechanism of Call-ID.");
  522. return dialogueAsset.SIPDialogue;
  523. }
  524. }
  525. return null;
  526. }
  527. catch (Exception excp)
  528. {
  529. logger.Error("Exception GetDialogue. " + excp);
  530. return null;
  531. }
  532. }
  533. /// <summary>
  534. /// This method applies very liberal rules to find a matching dialogue:
  535. /// 1. Treat the call identifier as a Call-ID,
  536. /// 2. If no dialogue matches for that try with the call identifier as the from username on the local user field,
  537. /// </summary>
  538. /// <param name="owner">The dialogue owner to use when attempting to find a match.</param>
  539. /// <param name="callIdentifier">A call identifier field to try and match a dialogue against.</param>
  540. /// <returns>A dialogue if a match is found or null otherwise.</returns>
  541. public SIPDialogue GetDialogueRelaxed(string owner, string callIdentifier)
  542. {
  543. if (owner.IsNullOrBlank() || callIdentifier.IsNullOrBlank())
  544. {
  545. return null;
  546. }
  547. else
  548. {
  549. owner = owner.ToLower();
  550. SIPDialogue callIDDialogue = GetDialogue(callIdentifier, null, null);
  551. if (callIDDialogue != null && callIDDialogue.Owner == owner)
  552. {
  553. return callIDDialogue;
  554. }
  555. else
  556. {
  557. List<SIPDialogueAsset> dialogueAssets = m_sipDialoguePersistor.Get(d => d.Owner == owner, null, 0, Int32.MaxValue);
  558. if (dialogueAssets != null && dialogueAssets.Count > 0)
  559. {
  560. SIPDialogueAsset matchingDialogue = null;
  561. foreach (SIPDialogueAsset dialogueAsset in dialogueAssets)
  562. {
  563. //if (dialogueAsset.LocalUserField.Contains(callIdentifier))
  564. if (dialogueAsset.RemoteUserField.Contains(callIdentifier))
  565. {
  566. if (matchingDialogue == null)
  567. {
  568. matchingDialogue = dialogueAsset;
  569. }
  570. else
  571. {
  572. // Ambiguous match, two or more dialogues match when matching on the call identifier string.
  573. return null;
  574. }
  575. }
  576. }
  577. if (matchingDialogue != null)
  578. {
  579. return matchingDialogue.SIPDialogue;
  580. }
  581. }
  582. }
  583. }
  584. return null;
  585. }
  586. /// <summary>
  587. /// Retrieves the other end of a call given the dialogue from one end.
  588. /// </summary>
  589. /// <param name="dialogue"></param>
  590. /// <returns></returns>
  591. public SIPDialogue GetOppositeDialogue(SIPDialogue dialogue)
  592. {
  593. if (dialogue.BridgeId != Guid.Empty)
  594. {
  595. string bridgeIdString = dialogue.BridgeId.ToString();
  596. SIPDialogueAsset dialogueAsset = m_sipDialoguePersistor.Get(d => d.BridgeId == bridgeIdString && d.Id != dialogue.Id);
  597. return (dialogueAsset != null) ? dialogueAsset.SIPDialogue : null;
  598. }
  599. else
  600. {
  601. return null;
  602. }
  603. }
  604. /// <summary>
  605. /// Attempts to reinvite an existing end of a call by sending a new SDP.
  606. /// </summary>
  607. /// <param name="dialogue">The dialogue describing the end of the call to be re-invited.</param>
  608. /// <param name="newSDP">The session description for the new dialogue desired.</param>
  609. public void ReInvite(SIPDialogue dialogue, SIPDialogue substituteDialogue)
  610. {
  611. try
  612. {
  613. string replacementSDP = substituteDialogue.RemoteSDP;
  614. // Determine whether the SDP needs to be mangled.
  615. IPEndPoint dialogueSDPSocket = SDP.GetSDPRTPEndPoint(dialogue.RemoteSDP);
  616. IPEndPoint replacementSDPSocket = SDP.GetSDPRTPEndPoint(substituteDialogue.RemoteSDP);
  617. bool wasMangled = false;
  618. if (!IPSocket.IsPrivateAddress(dialogueSDPSocket.Address.ToString()) && IPSocket.IsPrivateAddress(replacementSDPSocket.Address.ToString()))
  619. {
  620. // The SDP being used in the re-invite uses a private IP address but the SDP on the ua it's being sent to does not so mangle.
  621. string publicIPAddress = (PublicIPAddress != null) ? PublicIPAddress.ToString() : IPSocket.ParseHostFromSocket(substituteDialogue.RemoteTarget.Host);
  622. replacementSDP = SIPPacketMangler.MangleSDP(replacementSDP, publicIPAddress, out wasMangled);
  623. }
  624. if (wasMangled)
  625. {
  626. logger.Debug("The SDP being used in a re-INVITE was mangled to " + SDP.GetSDPRTPEndPoint(replacementSDP) + ".");
  627. }
  628. // Check whether there is a need to send the re-invite by comparing the new SDP being sent with what has already been sent.
  629. if (dialogue.SDP == replacementSDP)
  630. {
  631. logger.Debug("A reinvite was not sent to " + dialogue.RemoteTarget.ToString() + " as the SDP has not changed.");
  632. }
  633. else
  634. {
  635. logger.Debug("Reinvite SDP being sent to " + dialogue.RemoteTarget.ToString() + ":\r\n" + replacementSDP);
  636. dialogue.CSeq = dialogue.CSeq + 1;
  637. m_sipDialoguePersistor.UpdateProperty(dialogue.Id, "CSeq", dialogue.CSeq);
  638. SIPEndPoint localSIPEndPoint = (m_outboundProxy != null) ? m_sipTransport.GetDefaultTransportContact(m_outboundProxy.Protocol) : m_sipTransport.GetDefaultTransportContact(SIPProtocolsEnum.udp);
  639. SIPRequest reInviteReq = GetInviteRequest(dialogue, localSIPEndPoint, replacementSDP);
  640. SIPEndPoint reinviteEndPoint = null;
  641. // If the outbound proxy is a loopback address, as it will normally be for local deployments, then it cannot be overriden.
  642. if (m_outboundProxy != null && IPAddress.IsLoopback(m_outboundProxy.Address))
  643. {
  644. reInviteReq.Header.ProxySendFrom = dialogue.ProxySendFrom;
  645. reinviteEndPoint = m_outboundProxy;
  646. }
  647. if (!dialogue.ProxySendFrom.IsNullOrBlank())
  648. {
  649. reInviteReq.Header.ProxySendFrom = dialogue.ProxySendFrom;
  650. // The proxy will always be listening on UDP port 5060 for requests from internal servers.
  651. reinviteEndPoint = new SIPEndPoint(SIPProtocolsEnum.udp, new IPEndPoint(SIPEndPoint.ParseSIPEndPoint(dialogue.ProxySendFrom).Address, m_defaultSIPPort));
  652. }
  653. else
  654. {
  655. SIPDNSLookupResult lookupResult = m_sipTransport.GetRequestEndPoint(reInviteReq, m_outboundProxy, false);
  656. if (lookupResult.LookupError != null)
  657. {
  658. logger.Warn("ReInvite Failed to resolve " + lookupResult.URI.Host + ".");
  659. }
  660. else
  661. {
  662. reinviteEndPoint = lookupResult.GetSIPEndPoint();
  663. }
  664. }
  665. if (reinviteEndPoint != null)
  666. {
  667. UACInviteTransaction reInviteTransaction = m_sipTransport.CreateUACTransaction(reInviteReq, reinviteEndPoint, localSIPEndPoint, reinviteEndPoint);
  668. reInviteTransaction.CDR = null; // Don't want CDRs on re-invites.
  669. reInviteTransaction.UACInviteTransactionFinalResponseReceived += ReInviteTransactionFinalResponseReceived;
  670. reInviteTransaction.SendInviteRequest(reinviteEndPoint, reInviteReq);
  671. }
  672. else
  673. {
  674. throw new ApplicationException("Could not forward re-invite as request end point could not be determined.\r\n" + reInviteReq.ToString());
  675. }
  676. }
  677. }
  678. catch (Exception excp)
  679. {
  680. logger.Error("Exception CallManager ReInvite. " + excp.Message);
  681. throw excp;
  682. }
  683. }
  684. private void ReInviteTransactionFinalResponseReceived(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPTransaction sipTransaction, SIPResponse sipResponse)
  685. {
  686. try
  687. {
  688. //SIPRequest inviteRequest = sipTransaction.TransactionRequest;
  689. //SIPDialogue dialogue = GetDialogue(inviteRequest.Header.CallId, inviteRequest.Header.From.FromTag, inviteRequest.Header.To.ToTag);
  690. //m_dialogueBridges[dialogueId] = m_reInvitedDialogues[dialogueId];
  691. //m_reInvitedDialogues.Remove(dialogueId);
  692. if (sipResponse.StatusCode >= 200 && sipResponse.StatusCode <= 299)
  693. {
  694. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Reinvite request " + sipTransaction.TransactionRequest.URI.ToString() + " succeeeded with " + sipResponse.Status + ".", null));
  695. }
  696. else
  697. {
  698. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Reinvite request " + sipTransaction.TransactionRequest.URI.ToString() + " failed with " + sipResponse.Status + " " + sipResponse.ReasonPhrase + ".", null));
  699. }
  700. }
  701. catch (Exception excp)
  702. {
  703. logger.Error("Exception ReInviteTransactionFinalResponseReceived. " + excp.Message);
  704. throw excp;
  705. }
  706. }
  707. private void ForwardInDialogueRequest(SIPDialogue dialogue, SIPTransaction inDialogueTransaction, SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint)
  708. {
  709. try
  710. {
  711. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "In dialogue request " + inDialogueTransaction.TransactionRequest.Method + " received from for uri=" + inDialogueTransaction.TransactionRequest.URI.ToString() + ".", null));
  712. // Update the CSeq based on the latest received request.
  713. dialogue.CSeq = inDialogueTransaction.TransactionRequest.Header.CSeq;
  714. // Get the dialogue for the other end of the bridge.
  715. SIPDialogue bridgedDialogue = GetOppositeDialogue(dialogue);
  716. SIPEndPoint forwardSIPEndPoint = m_sipTransport.GetDefaultSIPEndPoint(new SIPEndPoint(bridgedDialogue.RemoteTarget));
  717. IPAddress remoteUAIPAddress = (inDialogueTransaction.TransactionRequest.Header.ProxyReceivedFrom.IsNullOrBlank()) ? remoteEndPoint.Address : SIPEndPoint.ParseSIPEndPoint(inDialogueTransaction.TransactionRequest.Header.ProxyReceivedFrom).Address;
  718. SIPRequest forwardedRequest = inDialogueTransaction.TransactionRequest.Copy();
  719. forwardedRequest.URI = bridgedDialogue.RemoteTarget;
  720. forwardedRequest.Header.Routes = bridgedDialogue.RouteSet;
  721. forwardedRequest.Header.CallId = bridgedDialogue.CallId;
  722. bridgedDialogue.CSeq = bridgedDialogue.CSeq + 1;
  723. forwardedRequest.Header.CSeq = bridgedDialogue.CSeq;
  724. forwardedRequest.Header.To = new SIPToHeader(bridgedDialogue.RemoteUserField.Name, bridgedDialogue.RemoteUserField.URI, bridgedDialogue.RemoteTag);
  725. forwardedRequest.Header.From = new SIPFromHeader(bridgedDialogue.LocalUserField.Name, bridgedDialogue.LocalUserField.URI, bridgedDialogue.LocalTag);
  726. forwardedRequest.Header.Contact = new List<SIPContactHeader>() { new SIPContactHeader(null, new SIPURI(bridgedDialogue.RemoteTarget.Scheme, forwardSIPEndPoint)) };
  727. forwardedRequest.Header.Vias = new SIPViaSet();
  728. forwardedRequest.Header.Vias.PushViaHeader(new SIPViaHeader(forwardSIPEndPoint, CallProperties.CreateBranchId()));
  729. forwardedRequest.Header.UserAgent = m_userAgentString;
  730. forwardedRequest.Header.AuthenticationHeader = null;
  731. if (inDialogueTransaction.TransactionRequest.Body != null && inDialogueTransaction.TransactionRequest.Method == SIPMethodsEnum.INVITE)
  732. {
  733. bool wasMangled = false;
  734. forwardedRequest.Body = SIPPacketMangler.MangleSDP(inDialogueTransaction.TransactionRequest.Body, remoteUAIPAddress.ToString(), out wasMangled);
  735. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Re-INVITE wasmangled=" + wasMangled + " remote=" + remoteUAIPAddress.ToString() + ".", null));
  736. forwardedRequest.Header.ContentLength = forwardedRequest.Body.Length;
  737. }
  738. SIPEndPoint forwardEndPoint = null;
  739. if (!bridgedDialogue.ProxySendFrom.IsNullOrBlank())
  740. {
  741. forwardedRequest.Header.ProxySendFrom = bridgedDialogue.ProxySendFrom;
  742. // The proxy will always be listening on UDP port 5060 for requests from internal servers.
  743. forwardEndPoint = new SIPEndPoint(SIPProtocolsEnum.udp, new IPEndPoint(SIPEndPoint.ParseSIPEndPoint(bridgedDialogue.ProxySendFrom).Address, m_defaultSIPPort));
  744. }
  745. else
  746. {
  747. SIPDNSLookupResult lookupResult = m_sipTransport.GetRequestEndPoint(forwardedRequest, m_outboundProxy, false);
  748. if (lookupResult.LookupError != null)
  749. {
  750. logger.Warn("ForwardInDialogueRequest Failed to resolve " + lookupResult.URI.Host + ".");
  751. }
  752. else
  753. {
  754. forwardEndPoint = lookupResult.GetSIPEndPoint();
  755. }
  756. }
  757. if (forwardEndPoint != null)
  758. {
  759. if (inDialogueTransaction.TransactionRequest.Method == SIPMethodsEnum.INVITE)
  760. {
  761. UACInviteTransaction forwardedTransaction = m_sipTransport.CreateUACTransaction(forwardedRequest, forwardEndPoint, localSIPEndPoint, m_outboundProxy);
  762. forwardedTransaction.CDR = null; // Don't want CDR's on re-INVITES.
  763. forwardedTransaction.UACInviteTransactionFinalResponseReceived += InDialogueTransactionFinalResponseReceived;
  764. forwardedTransaction.UACInviteTransactionInformationResponseReceived += InDialogueTransactionInfoResponseReceived;
  765. forwardedTransaction.TransactionRemoved += InDialogueTransactionRemoved;
  766. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Forwarding re-INVITE from " + remoteEndPoint + " to " + forwardedRequest.URI.ToString() + ", first hop " + forwardEndPoint + ".", dialogue.Owner));
  767. forwardedTransaction.SendReliableRequest();
  768. lock (m_inDialogueTransactions)

Large files files are truncated, but you can click here to view the full file