PageRenderTime 53ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/sipsorcery-core/SIPSorcery.SIP.Core/SIP/Channels/SIPTCPChannel.cs

https://github.com/thecc4re/sipsorcery-mono
C# | 444 lines | 340 code | 55 blank | 49 comment | 27 complexity | 840f1aec99e9cd69bdd8e0ec376c589f MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. //-----------------------------------------------------------------------------
  2. // Filename: SIPTCPChannel.cs
  3. //
  4. // Description: SIP transport for TCP.
  5. //
  6. // History:
  7. // 19 Apr 2008 Aaron Clauson Created.
  8. //
  9. // License:
  10. // This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
  11. //
  12. // Copyright (c) 2006-2009 Aaron Clauson (aaronc@blueface.ie), Blue Face Ltd, Dublin, Ireland (www.blueface.ie)
  13. // All rights reserved.
  14. //
  15. // Redistribution and use in source and binary forms, with or without modification, are permitted provided that
  16. // the following conditions are met:
  17. //
  18. // Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  19. // Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
  20. // disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd.
  21. // nor the names of its contributors may be used to endorse or promote products derived from this software without specific
  22. // prior written permission.
  23. //
  24. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
  25. // BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  26. // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
  27. // OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
  28. // OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  29. // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  30. // POSSIBILITY OF SUCH DAMAGE.
  31. //-----------------------------------------------------------------------------
  32. using System;
  33. using System.Collections;
  34. using System.Collections.Generic;
  35. using System.Diagnostics;
  36. using System.Linq;
  37. using System.Net;
  38. using System.Net.Sockets;
  39. using System.Text;
  40. using System.Text.RegularExpressions;
  41. using System.Threading;
  42. using SIPSorcery.Sys;
  43. using log4net;
  44. #if UNITTEST
  45. using NUnit.Framework;
  46. #endif
  47. namespace SIPSorcery.SIP
  48. {
  49. public class SIPTCPChannel : SIPChannel
  50. {
  51. private const string ACCEPT_THREAD_NAME = "siptcp-";
  52. private const string PRUNE_THREAD_NAME = "siptcpprune-";
  53. private const int MAX_TCP_CONNECTIONS = 1000; // Maximum number of connections for the TCP listener.
  54. //private const int MAX_TCP_CONNECTIONS_PER_IPADDRESS = 10; // Maximum number of connections allowed for a single remote IP address.
  55. private const int CONNECTION_ATTEMPTS_ALLOWED = 3; // The number of failed connection attempts permitted before classifying a remote socket as failed.
  56. private const int FAILED_CONNECTION_DONTUSE_INTERVAL = 300; // If a socket cannot be connected to don't try and reconnect to it for this interval.
  57. private static int MaxSIPTCPMessageSize = SIPConstants.SIP_MAXIMUM_RECEIVE_LENGTH;
  58. private TcpListener m_tcpServerListener;
  59. private Dictionary<string, SIPConnection> m_connectedSockets = new Dictionary<string, SIPConnection>();
  60. private List<string> m_connectingSockets = new List<string>(); // List of sockets that are in the process of being connected to. Need to avoid SIP re-transmits initiating multiple connect attempts.
  61. private Dictionary<string, int> m_connectionFailureStrikes = new Dictionary<string, int>(); // Tracks the number of connection attempts made to a remote socket, three strikes and it's out.
  62. private Dictionary<string, DateTime> m_connectionFailures = new Dictionary<string, DateTime>(); // Tracks sockets that have had a connection failure on them to avoid endless re-connect attmepts.
  63. public SIPTCPChannel(IPEndPoint endPoint)
  64. {
  65. m_localSIPEndPoint = new SIPEndPoint(SIPProtocolsEnum.tcp, endPoint);
  66. LocalTCPSockets.Add(endPoint.ToString());
  67. m_isReliable = true;
  68. Initialise();
  69. }
  70. private void Initialise()
  71. {
  72. try
  73. {
  74. m_tcpServerListener = new TcpListener(m_localSIPEndPoint.GetIPEndPoint());
  75. m_tcpServerListener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
  76. ThreadPool.QueueUserWorkItem(delegate { AcceptConnections(ACCEPT_THREAD_NAME + m_localSIPEndPoint.Port); });
  77. ThreadPool.QueueUserWorkItem(delegate { PruneConnections(PRUNE_THREAD_NAME + m_localSIPEndPoint.Port); });
  78. logger.Debug("SIP TCP Channel listener created " + m_localSIPEndPoint.GetIPEndPoint() + ".");
  79. }
  80. catch (Exception excp)
  81. {
  82. logger.Error("Exception SIPTCPChannel Initialise. " + excp.Message);
  83. throw excp;
  84. }
  85. }
  86. private void AcceptConnections(string threadName)
  87. {
  88. try
  89. {
  90. Thread.CurrentThread.Name = threadName;
  91. //m_sipConn.Listen(MAX_TCP_CONNECTIONS);
  92. m_tcpServerListener.Start(MAX_TCP_CONNECTIONS);
  93. logger.Debug("SIPTCPChannel socket on " + m_localSIPEndPoint + " listening started.");
  94. while (!Closed)
  95. {
  96. //Socket clientSocket = m_sipConn.Accept();
  97. TcpClient tcpClient = m_tcpServerListener.AcceptTcpClient();
  98. try
  99. {
  100. tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
  101. //clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
  102. //IPEndPoint remoteEndPoint = (IPEndPoint)clientSocket.RemoteEndPoint;
  103. IPEndPoint remoteEndPoint = (IPEndPoint)tcpClient.Client.RemoteEndPoint;
  104. logger.Debug("SIP TCP Channel connection accepted from " + remoteEndPoint + ".");
  105. //SIPTCPConnection sipTCPClient = new SIPTCPConnection(this, clientSocket, remoteEndPoint, SIPTCPConnectionsEnum.Listener);
  106. SIPConnection sipTCPConnection = new SIPConnection(this, tcpClient.GetStream(), remoteEndPoint, SIPProtocolsEnum.tcp, SIPConnectionsEnum.Listener);
  107. //SIPConnection sipTCPClient = new SIPConnection(this, tcpClient.Client, remoteEndPoint, SIPProtocolsEnum.tcp, SIPConnectionsEnum.Listener);
  108. lock (m_connectedSockets)
  109. {
  110. m_connectedSockets.Add(remoteEndPoint.ToString(), sipTCPConnection);
  111. }
  112. sipTCPConnection.SIPSocketDisconnected += SIPTCPSocketDisconnected;
  113. sipTCPConnection.SIPMessageReceived += SIPTCPMessageReceived;
  114. // clientSocket.BeginReceive(sipTCPClient.SocketBuffer, 0, SIPTCPConnection.MaxSIPTCPMessageSize, SocketFlags.None, new AsyncCallback(sipTCPClient.ReceiveCallback), null);
  115. //byte[] receiveBuffer = new byte[MaxSIPTCPMessageSize];
  116. sipTCPConnection.SIPStream.BeginRead(sipTCPConnection.SocketBuffer, 0, MaxSIPTCPMessageSize, new AsyncCallback(ReceiveCallback), sipTCPConnection);
  117. }
  118. catch (Exception acceptExcp)
  119. {
  120. // This exception gets thrown if the remote end disconnects during the socket accept.
  121. logger.Warn("Exception SIPTCPChannel accepting socket (" + acceptExcp.GetType() + "). " + acceptExcp.Message);
  122. }
  123. }
  124. logger.Debug("SIPTCPChannel socket on " + m_localSIPEndPoint + " listening halted.");
  125. }
  126. catch (Exception excp)
  127. {
  128. logger.Error("Exception SIPTCPChannel Listen. " + excp.Message);
  129. //throw excp;
  130. }
  131. }
  132. public void ReceiveCallback(IAsyncResult ar)
  133. {
  134. SIPConnection sipTCPConnection = (SIPConnection)ar.AsyncState;
  135. try
  136. {
  137. int bytesRead = sipTCPConnection.SIPStream.EndRead(ar);
  138. if (sipTCPConnection.SocketReadCompleted(bytesRead))
  139. {
  140. sipTCPConnection.SIPStream.BeginRead(sipTCPConnection.SocketBuffer, sipTCPConnection.SocketBufferEndPosition, MaxSIPTCPMessageSize - sipTCPConnection.SocketBufferEndPosition, new AsyncCallback(ReceiveCallback), sipTCPConnection);
  141. }
  142. }
  143. catch (SocketException) // Occurs if the remote end gets disconnected.
  144. { }
  145. catch (Exception excp)
  146. {
  147. logger.Warn("Exception SIPTCPChannel ReceiveCallback. " + excp.Message);
  148. SIPTCPSocketDisconnected(sipTCPConnection.RemoteEndPoint);
  149. }
  150. }
  151. public override bool IsConnectionEstablished(IPEndPoint remoteEndPoint)
  152. {
  153. lock (m_connectedSockets)
  154. {
  155. return m_connectedSockets.ContainsKey(remoteEndPoint.ToString());
  156. }
  157. }
  158. protected override Dictionary<string, SIPConnection> GetConnectionsList()
  159. {
  160. return m_connectedSockets;
  161. }
  162. private void SIPTCPSocketDisconnected(IPEndPoint remoteEndPoint)
  163. {
  164. try
  165. {
  166. logger.Debug("TCP socket from " + remoteEndPoint + " disconnected.");
  167. lock (m_connectedSockets)
  168. {
  169. if(m_connectedSockets.ContainsKey(remoteEndPoint.ToString()))
  170. {
  171. m_connectedSockets.Remove(remoteEndPoint.ToString());
  172. }
  173. }
  174. }
  175. catch (Exception excp)
  176. {
  177. logger.Error("Exception SIPTCPClientDisconnected. " + excp.Message);
  178. }
  179. }
  180. private void SIPTCPMessageReceived(SIPChannel channel, SIPEndPoint remoteEndPoint, byte[] buffer)
  181. {
  182. if (m_connectionFailures.ContainsKey(remoteEndPoint.GetIPEndPoint().ToString()))
  183. {
  184. m_connectionFailures.Remove(remoteEndPoint.GetIPEndPoint().ToString());
  185. }
  186. if (m_connectionFailureStrikes.ContainsKey(remoteEndPoint.GetIPEndPoint().ToString()))
  187. {
  188. m_connectionFailureStrikes.Remove(remoteEndPoint.GetIPEndPoint().ToString());
  189. }
  190. if (SIPMessageReceived != null)
  191. {
  192. SIPMessageReceived(channel, remoteEndPoint, buffer);
  193. }
  194. }
  195. public override void Send(IPEndPoint destinationEndPoint, string message)
  196. {
  197. byte[] messageBuffer = Encoding.UTF8.GetBytes(message);
  198. Send(destinationEndPoint, messageBuffer);
  199. }
  200. public override void Send(IPEndPoint dstEndPoint, byte[] buffer)
  201. {
  202. try
  203. {
  204. if (buffer == null)
  205. {
  206. throw new ApplicationException("An empty buffer was specified to Send in SIPTCPChannel.");
  207. }
  208. else if (LocalTCPSockets.Contains(dstEndPoint.ToString()))
  209. {
  210. logger.Error("SIPTCPChannel blocked Send to " + dstEndPoint.ToString() + " as it was identified as a locally hosted TCP socket.\r\n" + Encoding.UTF8.GetString(buffer));
  211. throw new ApplicationException("A Send call was made in SIPTCPChannel to send to another local TCP socket.");
  212. }
  213. else
  214. {
  215. bool sent = false;
  216. // Lookup a client socket that is connected to the destination.
  217. //m_sipConn(buffer, buffer.Length, destinationEndPoint);
  218. if (m_connectedSockets.ContainsKey(dstEndPoint.ToString()))
  219. {
  220. SIPConnection sipTCPClient = m_connectedSockets[dstEndPoint.ToString()];
  221. try
  222. {
  223. //logger.Warn("TCP channel BeginWrite from " + SIPChannelEndPoint.ToString() + " to " + sipTCPClient.RemoteEndPoint + ": " + Encoding.ASCII.GetString(buffer, 0, 32) + ".");
  224. sipTCPClient.SIPStream.BeginWrite(buffer, 0, buffer.Length, new AsyncCallback(EndSend), sipTCPClient);
  225. //logger.Warn("TCP channel BeginWrite complete from " + SIPChannelEndPoint.ToString() + " to " + sipTCPClient.RemoteEndPoint + ".");
  226. sipTCPClient.SIPStream.Flush();
  227. sent = true;
  228. sipTCPClient.LastTransmission = DateTime.Now;
  229. }
  230. catch (SocketException)
  231. {
  232. logger.Warn("Could not send to TCP socket " + dstEndPoint + ", closing and removing.");
  233. sipTCPClient.SIPStream.Close();
  234. m_connectedSockets.Remove(dstEndPoint.ToString());
  235. }
  236. }
  237. if (!sent)
  238. {
  239. if (m_connectionFailures.ContainsKey(dstEndPoint.ToString()) && m_connectionFailures[dstEndPoint.ToString()] < DateTime.Now.AddSeconds(FAILED_CONNECTION_DONTUSE_INTERVAL * -1))
  240. {
  241. m_connectionFailures.Remove(dstEndPoint.ToString());
  242. }
  243. if (m_connectionFailures.ContainsKey(dstEndPoint.ToString()))
  244. {
  245. throw new ApplicationException("TCP connection attempt to " + dstEndPoint.ToString() + " was not attempted, too many failures.");
  246. }
  247. else if (!m_connectingSockets.Contains(dstEndPoint.ToString()))
  248. {
  249. logger.Debug("Attempting to establish TCP connection to " + dstEndPoint + ".");
  250. TcpClient tcpClient = new TcpClient();
  251. tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
  252. tcpClient.Client.Bind(m_localSIPEndPoint.GetIPEndPoint());
  253. m_connectingSockets.Add(dstEndPoint.ToString());
  254. tcpClient.BeginConnect(dstEndPoint.Address, dstEndPoint.Port, EndConnect, new object[] { tcpClient, dstEndPoint, buffer });
  255. }
  256. else
  257. {
  258. //logger.Warn("Could not send SIP packet to TCP " + dstEndPoint + " and another connection was already in progress so dropping message.");
  259. }
  260. }
  261. }
  262. }
  263. catch (ApplicationException appExcp)
  264. {
  265. logger.Warn("ApplicationException SIPTCPChannel Send (sendto=>" + dstEndPoint + "). " + appExcp.Message);
  266. throw;
  267. }
  268. catch (Exception excp)
  269. {
  270. logger.Error("Exception (" + excp.GetType().ToString() + ") SIPTCPChannel Send (sendto=>" + dstEndPoint + "). " + excp.Message);
  271. throw;
  272. }
  273. }
  274. private void EndSend(IAsyncResult ar)
  275. {
  276. try
  277. {
  278. SIPConnection sipTCPConnection = (SIPConnection)ar.AsyncState;
  279. sipTCPConnection.SIPStream.EndWrite(ar);
  280. //logger.Debug("EndSend on TCP " + SIPChannelEndPoint.ToString() + ".");
  281. }
  282. catch (Exception excp)
  283. {
  284. logger.Error("Exception EndSend. " + excp.Message);
  285. }
  286. }
  287. public override void Send(IPEndPoint dstEndPoint, byte[] buffer, string serverCertificateName)
  288. {
  289. throw new ApplicationException("This Send method is not available in the SIP TCP channel, please use an alternative overload.");
  290. }
  291. private void EndConnect(IAsyncResult ar)
  292. {
  293. bool connected = false;
  294. IPEndPoint dstEndPoint = null;
  295. try
  296. {
  297. object[] stateObj = (object[])ar.AsyncState;
  298. TcpClient tcpClient = (TcpClient)stateObj[0];
  299. dstEndPoint = (IPEndPoint)stateObj[1];
  300. byte[] buffer = (byte[])stateObj[2];
  301. m_connectingSockets.Remove(dstEndPoint.ToString());
  302. tcpClient.EndConnect(ar);
  303. if (tcpClient != null && tcpClient.Connected)
  304. {
  305. logger.Debug("Established TCP connection to " + dstEndPoint + ".");
  306. connected = true;
  307. m_connectionFailureStrikes.Remove(dstEndPoint.ToString());
  308. m_connectionFailures.Remove(dstEndPoint.ToString());
  309. SIPConnection callerConnection = new SIPConnection(this, tcpClient.GetStream(), dstEndPoint, SIPProtocolsEnum.tcp, SIPConnectionsEnum.Caller);
  310. m_connectedSockets.Add(dstEndPoint.ToString(), callerConnection);
  311. callerConnection.SIPSocketDisconnected += SIPTCPSocketDisconnected;
  312. callerConnection.SIPMessageReceived += SIPTCPMessageReceived;
  313. //byte[] receiveBuffer = new byte[MaxSIPTCPMessageSize];
  314. callerConnection.SIPStream.BeginRead(callerConnection.SocketBuffer, 0, MaxSIPTCPMessageSize, new AsyncCallback(ReceiveCallback), callerConnection);
  315. callerConnection.SIPStream.BeginWrite(buffer, 0, buffer.Length, EndSend, callerConnection);
  316. }
  317. else
  318. {
  319. logger.Warn("Could not establish TCP connection to " + dstEndPoint + ".");
  320. }
  321. }
  322. catch (SocketException sockExcp)
  323. {
  324. logger.Warn("SocketException SIPTCPChannel EndConnect. " + sockExcp.Message);
  325. }
  326. catch (Exception excp)
  327. {
  328. logger.Error("Exception SIPTCPChannel EndConnect (" + excp.GetType() + "). " + excp.Message);
  329. }
  330. finally
  331. {
  332. if (!connected && dstEndPoint != null)
  333. {
  334. if (m_connectionFailureStrikes.ContainsKey(dstEndPoint.ToString()))
  335. {
  336. m_connectionFailureStrikes[dstEndPoint.ToString()] = m_connectionFailureStrikes[dstEndPoint.ToString()] + 1;
  337. }
  338. else
  339. {
  340. m_connectionFailureStrikes.Add(dstEndPoint.ToString(), 1);
  341. }
  342. if (m_connectionFailureStrikes[dstEndPoint.ToString()] >= CONNECTION_ATTEMPTS_ALLOWED)
  343. {
  344. if (!m_connectionFailures.ContainsKey(dstEndPoint.ToString()))
  345. {
  346. m_connectionFailures.Add(dstEndPoint.ToString(), DateTime.Now);
  347. }
  348. m_connectionFailureStrikes.Remove(dstEndPoint.ToString());
  349. }
  350. }
  351. }
  352. }
  353. public override void Close()
  354. {
  355. logger.Debug("Closing SIP TCP Channel " + SIPChannelEndPoint + ".");
  356. Closed = true;
  357. try
  358. {
  359. m_tcpServerListener.Stop();
  360. }
  361. catch (Exception listenerCloseExcp)
  362. {
  363. logger.Warn("Exception SIPTCPChannel Close (shutting down listener). " + listenerCloseExcp.Message);
  364. }
  365. foreach (SIPConnection tcpConnection in m_connectedSockets.Values)
  366. {
  367. try
  368. {
  369. tcpConnection.SIPStream.Close();
  370. }
  371. catch (Exception connectionCloseExcp)
  372. {
  373. logger.Warn("Exception SIPTCPChannel Close (shutting down connection to " + tcpConnection.RemoteEndPoint + "). " + connectionCloseExcp.Message);
  374. }
  375. }
  376. }
  377. private void Dispose(bool disposing)
  378. {
  379. try
  380. {
  381. this.Close();
  382. }
  383. catch (Exception excp)
  384. {
  385. logger.Error("Exception Disposing SIPTCPChannel. " + excp.Message);
  386. }
  387. }
  388. }
  389. }