From af5f3e6052cb9d14b998d36ea7291a92e598c565 Mon Sep 17 00:00:00 2001 From: Jens Geyer Date: Fri, 17 Apr 2015 20:55:05 +0200 Subject: [PATCH] THRIFT-3000 .NET implementation has trouble with mixed IP modes Client: C# Patch: sharpdevel & Jens Geyer TcpListener and TcpClient are created based on the capabilities of the used runtime framework. For windows the changes automatically handle IPv4 and IPv6 sockets. In mono it behaves as before. When using TcpListener and TcpClient it depends on the network configuration if IPv4 or IPv6 is used. By upgrading the framework to .NET 4.5 the DualMode can be set on the sockets of the listener and the client. The sockets then try to establish IPv6 sockets before they fallback to IPv4 --- configure.ac | 2 + lib/csharp/Makefile.am | 5 + .../ThriftMSBuildTask.csproj | 2 +- lib/csharp/src/Thrift.csproj | 5 +- lib/csharp/src/Transport/TServerSocket.cs | 9 +- lib/csharp/src/Transport/TServerTransport.cs | 9 +- lib/csharp/src/Transport/TSocket.cs | 54 ++++++----- .../src/Transport/TSocketVersionizer.cs | 92 +++++++++++++++++++ lib/csharp/src/Transport/TTLSServerSocket.cs | 4 +- lib/csharp/src/Transport/TTLSSocket.cs | 6 +- lib/csharp/src/Transport/TTransport.cs | 2 +- .../Multiplex/Client/MultiplexClient.csproj | 2 +- .../Multiplex/Server/MultiplexServer.csproj | 2 +- lib/csharp/test/ThriftTest/TestClient.cs | 8 +- lib/csharp/test/ThriftTest/ThriftTest.csproj | 4 +- 15 files changed, 157 insertions(+), 49 deletions(-) create mode 100644 lib/csharp/src/Transport/TSocketVersionizer.cs diff --git a/configure.ac b/configure.ac index 0032354899c..aae784adc0a 100755 --- a/configure.ac +++ b/configure.ac @@ -194,6 +194,7 @@ AM_CONDITIONAL(WITH_C_GLIB, [test "$have_glib2" = "yes" -a "$have_gobject2" = "y AX_THRIFT_LIB(csharp, [C#], yes) if test "$with_csharp" = "yes"; then + PKG_CHECK_MODULES(MONO, mono >= 3.0.0, net_4_5=yes, net_4_5=no) PKG_CHECK_MODULES(MONO, mono >= 2.0.0, net_3_5=yes, net_3_5=no) PKG_CHECK_MODULES(MONO, mono >= 1.2.4, have_mono=yes, have_mono=no) if test "$have_mono" = "yes" ; then @@ -202,6 +203,7 @@ if test "$with_csharp" = "yes"; then fi AM_CONDITIONAL(WITH_MONO, [test "$have_csharp" = "yes"]) AM_CONDITIONAL(NET_2_0, [test "$net_3_5" = "no"]) +AM_CONDITIONAL(NET_4_5, [test "$net_4_5" = "yes"]) AX_THRIFT_LIB(java, [Java], yes) if test "$with_java" = "yes"; then diff --git a/lib/csharp/Makefile.am b/lib/csharp/Makefile.am index 5ce42761906..7cdec058c82 100644 --- a/lib/csharp/Makefile.am +++ b/lib/csharp/Makefile.am @@ -52,6 +52,7 @@ THRIFTCODE= \ src/Transport/TBufferedTransport.cs \ src/Transport/TTransport.cs \ src/Transport/TSocket.cs \ + src/Transport/TSocketVersionizer.cs \ src/Transport/TTransportException.cs \ src/Transport/TStreamTransport.cs \ src/Transport/TFramedTransport.cs \ @@ -75,6 +76,10 @@ if NET_2_0 MONO_DEFINES=/d:NET_2_0 endif +if NET_4_5 +MONO_DEFINES=/d:NET_4_5 -sdk:4.5 +endif + all-local: Thrift.dll Thrift.dll: $(THRIFTCODE) diff --git a/lib/csharp/ThriftMSBuildTask/ThriftMSBuildTask.csproj b/lib/csharp/ThriftMSBuildTask/ThriftMSBuildTask.csproj index ae8608159dc..1148eaa78d2 100644 --- a/lib/csharp/ThriftMSBuildTask/ThriftMSBuildTask.csproj +++ b/lib/csharp/ThriftMSBuildTask/ThriftMSBuildTask.csproj @@ -28,7 +28,7 @@ Properties ThriftMSBuildTask ThriftMSBuildTask - v3.5 + v4.5 512 diff --git a/lib/csharp/src/Thrift.csproj b/lib/csharp/src/Thrift.csproj index 195005a1457..dc0aa6d1ed3 100644 --- a/lib/csharp/src/Thrift.csproj +++ b/lib/csharp/src/Thrift.csproj @@ -27,7 +27,7 @@ Library false Thrift - v3.5 + v4.5 512 Thrift @@ -109,6 +109,7 @@ + @@ -147,4 +148,4 @@ - + \ No newline at end of file diff --git a/lib/csharp/src/Transport/TServerSocket.cs b/lib/csharp/src/Transport/TServerSocket.cs index 82a367c2647..524003eb2b3 100644 --- a/lib/csharp/src/Transport/TServerSocket.cs +++ b/lib/csharp/src/Transport/TServerSocket.cs @@ -23,6 +23,7 @@ using System; using System.Net.Sockets; +using System.Reflection; namespace Thrift.Transport @@ -53,7 +54,7 @@ public class TServerSocket : TServerTransport * Creates a server socket from underlying socket object */ public TServerSocket(TcpListener listener) - :this(listener, 0) + : this(listener, 0) { } @@ -78,7 +79,7 @@ public TServerSocket(int port) * Creates just a port listening server socket */ public TServerSocket(int port, int clientTimeout) - :this(port, clientTimeout, false) + : this(port, clientTimeout, false) { } @@ -90,8 +91,8 @@ public TServerSocket(int port, int clientTimeout, bool useBufferedSockets) try { // Make server socket - server = new TcpListener(System.Net.IPAddress.Any, this.port); - server.Server.NoDelay = true; + this.server = TSocketVersionizer.CreateTcpListener(port); + this.server.Server.NoDelay = true; } catch (Exception) { diff --git a/lib/csharp/src/Transport/TServerTransport.cs b/lib/csharp/src/Transport/TServerTransport.cs index 05d7d0faa4e..8e8432396fa 100644 --- a/lib/csharp/src/Transport/TServerTransport.cs +++ b/lib/csharp/src/Transport/TServerTransport.cs @@ -22,6 +22,8 @@ */ using System; +using System.Net.Sockets; +using System.Reflection; namespace Thrift.Transport { @@ -34,10 +36,11 @@ public abstract class TServerTransport public TTransport Accept() { TTransport transport = AcceptImpl(); - if (transport == null) { - throw new TTransportException("accept() may not return NULL"); + if (transport == null) + { + throw new TTransportException("accept() may not return NULL"); } return transport; - } + } } } diff --git a/lib/csharp/src/Transport/TSocket.cs b/lib/csharp/src/Transport/TSocket.cs index cf1a440b020..0b475728425 100644 --- a/lib/csharp/src/Transport/TSocket.cs +++ b/lib/csharp/src/Transport/TSocket.cs @@ -60,9 +60,9 @@ public TSocket(string host, int port, int timeout) private void InitSocket() { - client = new TcpClient(); - client.ReceiveTimeout = client.SendTimeout = timeout; - client.Client.NoDelay = true; + this.client = TSocketVersionizer.CreateTcpClient(); + this.client.ReceiveTimeout = client.SendTimeout = timeout; + this.client.Client.NoDelay = true; } public int Timeout @@ -132,7 +132,7 @@ public override void Open() InitSocket(); } - if( timeout == 0) // no timeout -> infinite + if (timeout == 0) // no timeout -> infinite { client.Connect(host, port); } @@ -145,7 +145,7 @@ public override void Open() { lock (hlp.Mutex) { - if( hlp.CallbackDone) + if (hlp.CallbackDone) { asyncres.AsyncWaitHandle.Close(); client.Close(); @@ -174,7 +174,7 @@ static void ConnectCallback(IAsyncResult asyncres) try { - if( hlp.Client.Client != null) + if (hlp.Client.Client != null) hlp.Client.EndConnect(asyncres); } catch (Exception) @@ -184,14 +184,18 @@ static void ConnectCallback(IAsyncResult asyncres) if (hlp.DoCleanup) { - try { + try + { asyncres.AsyncWaitHandle.Close(); - } catch (Exception) {} + } + catch (Exception) { } - try { + try + { if (hlp.Client is IDisposable) ((IDisposable)hlp.Client).Dispose(); - } catch (Exception) {} + } + catch (Exception) { } hlp.Client = null; } } @@ -219,23 +223,23 @@ public override void Close() } } - #region " IDisposable Support " - private bool _IsDisposed; + #region " IDisposable Support " + private bool _IsDisposed; - // IDisposable - protected override void Dispose(bool disposing) - { - if (!_IsDisposed) - { - if (disposing) + // IDisposable + protected override void Dispose(bool disposing) { - if (client != null) - ((IDisposable)client).Dispose(); - base.Dispose(disposing); + if (!_IsDisposed) + { + if (disposing) + { + if (client != null) + ((IDisposable)client).Dispose(); + base.Dispose(disposing); + } + } + _IsDisposed = true; } - } - _IsDisposed = true; + #endregion } - #endregion - } } diff --git a/lib/csharp/src/Transport/TSocketVersionizer.cs b/lib/csharp/src/Transport/TSocketVersionizer.cs new file mode 100644 index 00000000000..1928facd691 --- /dev/null +++ b/lib/csharp/src/Transport/TSocketVersionizer.cs @@ -0,0 +1,92 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Reflection; +using System.Text; + +namespace Thrift.Transport +{ + /** + * PropertyInfo for the DualMode property of the System.Net.Sockets.Socket class. Used to determine if the sockets are capable of + * automatic IPv4 and IPv6 handling. If DualMode is present the sockets automatically handle IPv4 and IPv6 connections. + * If the DualMode is not available the system configuration determines whether IPv4 or IPv6 is used. + */ + internal static class TSocketVersionizer + { + /* + * PropertyInfo for the DualMode property of System.Net.Sockets.Socket. + */ + private static PropertyInfo DualModeProperty = typeof(Socket).GetProperty("DualMode"); + + /* + * Indicates whether the used framework supports DualMode on sockets or not. + */ + internal static Boolean SupportsDualMode + { + get + { + return TSocketVersionizer.DualModeProperty != null; + } + } + + /* + * Creates a TcpClient according to the capabilitites of the used framework + */ + internal static TcpClient CreateTcpClient() + { + TcpClient client = null; + + if (TSocketVersionizer.SupportsDualMode) + { + client = new TcpClient(AddressFamily.InterNetworkV6); + TSocketVersionizer.DualModeProperty.SetValue(client.Client, true); + } + else + { + client = new TcpClient(AddressFamily.InterNetwork); + } + + return client; + } + + /* + * Creates a TcpListener according to the capabilitites of the used framework + */ + internal static TcpListener CreateTcpListener(Int32 port) + { + TcpListener listener = null; + + if (TSocketVersionizer.SupportsDualMode) + { + listener = new TcpListener(System.Net.IPAddress.IPv6Any, port); + TSocketVersionizer.DualModeProperty.SetValue(listener.Server, true); + } + else + { + listener = new TcpListener(System.Net.IPAddress.Any, port); + } + + return listener; + } + } +} diff --git a/lib/csharp/src/Transport/TTLSServerSocket.cs b/lib/csharp/src/Transport/TTLSServerSocket.cs index 631a593a980..e56c66c8f87 100644 --- a/lib/csharp/src/Transport/TTLSServerSocket.cs +++ b/lib/csharp/src/Transport/TTLSServerSocket.cs @@ -115,8 +115,8 @@ public TTLSServerSocket( try { // Create server socket - server = new TcpListener(System.Net.IPAddress.Any, this.port); - server.Server.NoDelay = true; + this.server = TSocketVersionizer.CreateTcpListener(port); + this.server.Server.NoDelay = true; } catch (Exception) { diff --git a/lib/csharp/src/Transport/TTLSSocket.cs b/lib/csharp/src/Transport/TTLSSocket.cs index 565255607ea..d48b7d557e0 100644 --- a/lib/csharp/src/Transport/TTLSSocket.cs +++ b/lib/csharp/src/Transport/TTLSSocket.cs @@ -172,7 +172,7 @@ public TTLSSocket( /// private void InitSocket() { - this.client = new TcpClient(); + this.client = TSocketVersionizer.CreateTcpClient(); client.ReceiveTimeout = client.SendTimeout = timeout; client.Client.NoDelay = true; } @@ -286,7 +286,7 @@ public override void Open() public void setupTLS() { RemoteCertificateValidationCallback validator = this.certValidator ?? DefaultCertificateValidator; - + if( this.localCertificateSelectionCallback != null) { this.secureStream = new SslStream( @@ -304,7 +304,7 @@ public void setupTLS() validator ); } - + try { if (isServer) diff --git a/lib/csharp/src/Transport/TTransport.cs b/lib/csharp/src/Transport/TTransport.cs index 28113999eb9..d4977f18f92 100644 --- a/lib/csharp/src/Transport/TTransport.cs +++ b/lib/csharp/src/Transport/TTransport.cs @@ -55,7 +55,7 @@ public bool Peek() } catch( IOException) { - return false; + return false; } _hasPeekByte = true; diff --git a/lib/csharp/test/Multiplex/Client/MultiplexClient.csproj b/lib/csharp/test/Multiplex/Client/MultiplexClient.csproj index 6221e141836..ff787230ed6 100644 --- a/lib/csharp/test/Multiplex/Client/MultiplexClient.csproj +++ b/lib/csharp/test/Multiplex/Client/MultiplexClient.csproj @@ -28,7 +28,7 @@ Properties MultiplexClient MultiplexClient - v3.5 + v4.5 512 false diff --git a/lib/csharp/test/Multiplex/Server/MultiplexServer.csproj b/lib/csharp/test/Multiplex/Server/MultiplexServer.csproj index dc1d123e4fa..6a8177ee605 100644 --- a/lib/csharp/test/Multiplex/Server/MultiplexServer.csproj +++ b/lib/csharp/test/Multiplex/Server/MultiplexServer.csproj @@ -28,7 +28,7 @@ Properties MultiplexServer MultiplexServer - v3.5 + v4.5 512 false diff --git a/lib/csharp/test/ThriftTest/TestClient.cs b/lib/csharp/test/ThriftTest/TestClient.cs index ec0696abc51..b54d09d2d98 100644 --- a/lib/csharp/test/ThriftTest/TestClient.cs +++ b/lib/csharp/test/ThriftTest/TestClient.cs @@ -185,14 +185,14 @@ public static byte[] PrepareTestData(bool randomDist) // linear distribution, unless random is requested if (!randomDist) { - for (var i = 0; i < initLen; ++i) { + for (var i = 0; i < initLen; ++i) { retval[i] = (byte)i; } return retval; } // random distribution - for (var i = 0; i < initLen; ++i) { + for (var i = 0; i < initLen; ++i) { retval[i] = (byte)0; } var rnd = new Random(); @@ -270,13 +270,13 @@ public static void ClientTest(TTransport transport) if (binIn[ofs] != binOut[ofs]) throw new Exception("testBinary: content mismatch at offset " + ofs.ToString()); } - catch (Thrift.TApplicationException e) + catch (Thrift.TApplicationException e) { Console.Write("testBinary(" + BytesToHex(binOut) + "): "+e.Message); } // binary equals? only with hashcode option enabled ... - if( typeof(CrazyNesting).GetMethod("Equals").DeclaringType == typeof(CrazyNesting)) + if( typeof(CrazyNesting).GetMethod("Equals").DeclaringType == typeof(CrazyNesting)) { CrazyNesting one = new CrazyNesting(); CrazyNesting two = new CrazyNesting(); diff --git a/lib/csharp/test/ThriftTest/ThriftTest.csproj b/lib/csharp/test/ThriftTest/ThriftTest.csproj index d67199774f3..5f9f151f7f0 100644 --- a/lib/csharp/test/ThriftTest/ThriftTest.csproj +++ b/lib/csharp/test/ThriftTest/ThriftTest.csproj @@ -28,7 +28,7 @@ Properties ThriftTest ThriftTest - v3.5 + v4.5 512 false @@ -136,6 +136,6 @@ SET THRIFT_FILE=$(ProjectDir)\..\..\..\..\test\ThriftTest.thrift for %25%25I in ("%25OUTPUT_DIR%25") do set SHORT_DIR=%25%25~fsI for %25%25I in ("%25THRIFT_FILE%25") do set THRIFT_SHORT=%25%25~fsI "$(ProjectDir)\..\..\..\..\compiler\cpp\thrift.exe" --gen csharp -o %25SHORT_DIR%25 %25THRIFT_SHORT%25 -$(MSBuildToolsPath)\Csc.exe /t:library /out:"$(ProjectDir)ThriftImpl.dll" /recurse:"$(ProjectDir)gen-csharp"\* /reference:"$(ProjectDir)..\..\src\bin\Debug\Thrift.dll" +"$(MSBuildToolsPath)\Csc.exe" /t:library /out:"$(ProjectDir)ThriftImpl.dll" /recurse:"$(ProjectDir)gen-csharp"\* /reference:"$(ProjectDir)..\..\src\bin\Debug\Thrift.dll" \ No newline at end of file