You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

336 lines
9.8 KiB

4 years ago
  1. #if MYSQL_6_9
  2. // Copyright (c) 2009 Sun Microsystems, Inc., 2013, 2014 Oracle and/or its affiliates. All rights reserved.
  3. //
  4. // MySQL Connector/NET is licensed under the terms of the GPLv2
  5. // <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
  6. // MySQL Connectors. There are special exceptions to the terms and
  7. // conditions of the GPLv2 as it is applied to this software, see the
  8. // FLOSS License Exception
  9. // <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
  10. //
  11. // This program is free software; you can redistribute it and/or modify
  12. // it under the terms of the GNU General Public License as published
  13. // by the Free Software Foundation; version 2 of the License.
  14. //
  15. // This program is distributed in the hope that it will be useful, but
  16. // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  17. // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  18. // for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License along
  21. // with this program; if not, write to the Free Software Foundation, Inc.,
  22. // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  23. using Externals.MySql.Data.MySqlClient;
  24. using System;
  25. using System.IO;
  26. using System.Net;
  27. using System.Net.Sockets;
  28. using System.Reflection;
  29. namespace Externals.MySql.Data.Common
  30. {
  31. internal class MyNetworkStream : NetworkStream
  32. {
  33. /// <summary>
  34. /// Wrapper around NetworkStream.
  35. ///
  36. /// MyNetworkStream is equivalent to NetworkStream, except
  37. /// 1. It throws TimeoutException if read or write timeout occurs, instead
  38. /// of IOException, to match behavior of other streams (named pipe and
  39. /// shared memory). This property comes handy in TimedStream.
  40. ///
  41. /// 2. It implements workarounds for WSAEWOULDBLOCK errors, that can start
  42. /// occuring after stream has times out. For a discussion about the CLR bug,
  43. /// refer to http://tinyurl.com/lhgpyf. This error should never occur, as
  44. /// we're not using asynchronous operations, but apparerntly it does occur
  45. /// directly after timeout has expired.
  46. /// The workaround is hinted in the URL above and implemented like this:
  47. /// For each IO operation, if it throws WSAEWOULDBLOCK, we explicitely set
  48. /// the socket to Blocking and retry the operation once again.
  49. /// </summary>
  50. const int MaxRetryCount = 2;
  51. Socket socket;
  52. public MyNetworkStream(Socket socket, bool ownsSocket)
  53. : base(socket, ownsSocket)
  54. {
  55. this.socket = socket;
  56. }
  57. bool IsTimeoutException(SocketException e)
  58. {
  59. return (e.SocketErrorCode == SocketError.TimedOut);
  60. }
  61. bool IsWouldBlockException(SocketException e)
  62. {
  63. return (e.SocketErrorCode == SocketError.WouldBlock);
  64. }
  65. void HandleOrRethrowException(Exception e)
  66. {
  67. Exception currentException = e;
  68. while (currentException != null)
  69. {
  70. if (currentException is SocketException)
  71. {
  72. SocketException socketException = (SocketException)currentException;
  73. if (IsWouldBlockException(socketException))
  74. {
  75. // Workaround for WSAEWOULDBLOCK
  76. socket.Blocking = true;
  77. // return to give the caller possibility to retry the call
  78. return;
  79. }
  80. else if (IsTimeoutException(socketException))
  81. {
  82. return;
  83. //throw new TimeoutException(socketException.Message, e);
  84. }
  85. }
  86. currentException = currentException.InnerException;
  87. }
  88. throw (e);
  89. }
  90. public override int Read(byte[] buffer, int offset, int count)
  91. {
  92. int retry = 0;
  93. Exception exception = null;
  94. do
  95. {
  96. try
  97. {
  98. return base.Read(buffer, offset, count);
  99. }
  100. catch (Exception e)
  101. {
  102. exception = e;
  103. HandleOrRethrowException(e);
  104. }
  105. }
  106. while (++retry < MaxRetryCount);
  107. if(exception.GetBaseException() is SocketException
  108. && IsTimeoutException((SocketException)exception.GetBaseException()))
  109. throw new TimeoutException(exception.Message, exception);
  110. throw exception;
  111. }
  112. public override int ReadByte()
  113. {
  114. int retry = 0;
  115. Exception exception = null;
  116. do
  117. {
  118. try
  119. {
  120. return base.ReadByte();
  121. }
  122. catch (Exception e)
  123. {
  124. exception = e;
  125. HandleOrRethrowException(e);
  126. }
  127. }
  128. while (++retry < MaxRetryCount);
  129. throw exception;
  130. }
  131. public override void Write(byte[] buffer, int offset, int count)
  132. {
  133. int retry = 0;
  134. Exception exception = null;
  135. do
  136. {
  137. try
  138. {
  139. base.Write(buffer, offset, count);
  140. return;
  141. }
  142. catch (Exception e)
  143. {
  144. exception = e;
  145. HandleOrRethrowException(e);
  146. }
  147. }
  148. while (++retry < MaxRetryCount);
  149. throw exception;
  150. }
  151. public override void Flush()
  152. {
  153. int retry = 0;
  154. Exception exception = null;
  155. do
  156. {
  157. try
  158. {
  159. base.Flush();
  160. return;
  161. }
  162. catch (Exception e)
  163. {
  164. exception = e;
  165. HandleOrRethrowException(e);
  166. }
  167. }
  168. while (++retry < MaxRetryCount);
  169. throw exception;
  170. }
  171. #region Create Code
  172. public static MyNetworkStream CreateStream(MySqlConnectionStringBuilder settings, bool unix)
  173. {
  174. MyNetworkStream stream = null;
  175. IPHostEntry ipHE = GetHostEntry(settings.Server);
  176. foreach (IPAddress address in ipHE.AddressList)
  177. {
  178. try
  179. {
  180. stream = CreateSocketStream(settings, address, unix);
  181. if (stream != null) break;
  182. }
  183. catch (Exception ex)
  184. {
  185. SocketException socketException = ex as SocketException;
  186. // if the exception is a ConnectionRefused then we eat it as we may have other address
  187. // to attempt
  188. if (socketException == null) throw;
  189. if (socketException.SocketErrorCode != SocketError.ConnectionRefused) throw;
  190. }
  191. }
  192. return stream;
  193. }
  194. private static IPHostEntry ParseIPAddress(string hostname)
  195. {
  196. IPHostEntry ipHE = null;
  197. IPAddress addr;
  198. if (IPAddress.TryParse(hostname, out addr))
  199. {
  200. ipHE = new IPHostEntry();
  201. ipHE.AddressList = new IPAddress[1];
  202. ipHE.AddressList[0] = addr;
  203. }
  204. return ipHE;
  205. }
  206. private static IPHostEntry GetHostEntry(string hostname)
  207. {
  208. IPHostEntry ipHE = ParseIPAddress(hostname);
  209. if (ipHE != null) return ipHE;
  210. return Dns.GetHostEntry(hostname);
  211. }
  212. private static EndPoint CreateUnixEndPoint(string host)
  213. {
  214. // first we need to load the Mono.posix assembly
  215. Assembly a = Assembly.Load(@"Mono.Posix, Version=2.0.0.0,
  216. Culture=neutral, PublicKeyToken=0738eb9f132ed756");
  217. // then we need to construct a UnixEndPoint object
  218. EndPoint ep = (EndPoint)a.CreateInstance("Mono.Posix.UnixEndPoint",
  219. false, BindingFlags.CreateInstance, null,
  220. new object[1] { host }, null, null);
  221. return ep;
  222. }
  223. private static MyNetworkStream CreateSocketStream(MySqlConnectionStringBuilder settings, IPAddress ip, bool unix)
  224. {
  225. EndPoint endPoint;
  226. if (!Platform.IsWindows() && unix)
  227. endPoint = CreateUnixEndPoint(settings.Server);
  228. else
  229. endPoint = new IPEndPoint(ip, (int)settings.Port);
  230. Socket socket = unix ?
  231. new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.IP) :
  232. new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
  233. if (settings.Keepalive > 0)
  234. {
  235. SetKeepAlive(socket, settings.Keepalive);
  236. }
  237. IAsyncResult ias = socket.BeginConnect(endPoint, null, null);
  238. if (!ias.AsyncWaitHandle.WaitOne((int)settings.ConnectionTimeout * 1000, false))
  239. {
  240. socket.Close();
  241. return null;
  242. }
  243. try
  244. {
  245. socket.EndConnect(ias);
  246. }
  247. catch (Exception)
  248. {
  249. socket.Close();
  250. throw;
  251. }
  252. MyNetworkStream stream = new MyNetworkStream(socket, true);
  253. GC.SuppressFinalize(socket);
  254. GC.SuppressFinalize(stream);
  255. return stream;
  256. }
  257. /// <summary>
  258. /// Set keepalive + timeout on socket.
  259. /// </summary>
  260. /// <param name="s">socket</param>
  261. /// <param name="time">keepalive timeout, in seconds</param>
  262. private static void SetKeepAlive(Socket s, uint time)
  263. {
  264. uint on = 1;
  265. uint interval = 1000; // default interval = 1 sec
  266. uint timeMilliseconds;
  267. if (time > UInt32.MaxValue / 1000)
  268. timeMilliseconds = UInt32.MaxValue;
  269. else
  270. timeMilliseconds = time * 1000;
  271. // Use Socket.IOControl to implement equivalent of
  272. // WSAIoctl with SOL_KEEPALIVE_VALS
  273. // the native structure passed to WSAIoctl is
  274. //struct tcp_keepalive {
  275. // ULONG onoff;
  276. // ULONG keepalivetime;
  277. // ULONG keepaliveinterval;
  278. //};
  279. // marshal the equivalent of the native structure into a byte array
  280. byte[] inOptionValues = new byte[12];
  281. BitConverter.GetBytes(on).CopyTo(inOptionValues, 0);
  282. BitConverter.GetBytes(timeMilliseconds).CopyTo(inOptionValues, 4);
  283. BitConverter.GetBytes(interval).CopyTo(inOptionValues, 8);
  284. try
  285. {
  286. // call WSAIoctl via IOControl
  287. s.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
  288. return;
  289. }
  290. catch (NotImplementedException)
  291. {
  292. // Mono throws not implemented currently
  293. }
  294. // Fallback if Socket.IOControl is not available ( Compact Framework )
  295. // or not implemented ( Mono ). Keepalive option will still be set, but
  296. // with timeout is kept default.
  297. s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1);
  298. }
  299. #endregion
  300. }
  301. }
  302. #endif