From 66a3c9496afb9694db13318c4f637ab2dbe1a7d5 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Thu, 12 Oct 2017 12:26:52 -0700 Subject: [PATCH] Set SO_REUSEADDR on managed listen sockets on Unix (#2111) - https://github.com/dotnet/corefx/issues/24562 --- .../SocketTransport.cs | 36 +++++++++++++++++++ .../AddressRegistrationTests.cs | 16 ++------- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/Kestrel.Transport.Sockets/SocketTransport.cs b/src/Kestrel.Transport.Sockets/SocketTransport.cs index 61ede997e3..99e3bbe3d4 100644 --- a/src/Kestrel.Transport.Sockets/SocketTransport.cs +++ b/src/Kestrel.Transport.Sockets/SocketTransport.cs @@ -6,10 +6,12 @@ using System.Diagnostics; using System.IO.Pipelines; using System.Net; using System.Net.Sockets; +using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets { @@ -51,6 +53,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets var listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + EnableRebinding(listenSocket); + // Kestrel expects IPv6Any to bind to both IPv6 and IPv4 if (endPoint.Address == IPAddress.IPv6Any) { @@ -128,5 +132,37 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets } } } + + [DllImport("libc", SetLastError = true)] + private static extern int setsockopt(IntPtr socket, int level, int option_name, IntPtr option_value, uint option_len); + + private const int SOL_SOCKET_OSX = 0xffff; + private const int SO_REUSEADDR_OSX = 0x0004; + private const int SOL_SOCKET_LINUX = 0x0001; + private const int SO_REUSEADDR_LINUX = 0x0002; + + // Without setting SO_REUSEADDR on macOS and Linux, binding to a recently used endpoint can fail. + // https://github.com/dotnet/corefx/issues/24562 + private unsafe void EnableRebinding(Socket listenSocket) + { + var optionValue = 1; + var setsockoptStatus = 0; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + setsockoptStatus = setsockopt(listenSocket.Handle, SOL_SOCKET_LINUX, SO_REUSEADDR_LINUX, + (IntPtr)(&optionValue), sizeof(int)); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + setsockoptStatus = setsockopt(listenSocket.Handle, SOL_SOCKET_OSX, SO_REUSEADDR_OSX, + (IntPtr)(&optionValue), sizeof(int)); + } + + if (setsockoptStatus != 0) + { + _trace.LogInformation("Setting SO_REUSEADDR failed with errno '{errno}'.", Marshal.GetLastWin32Error()); + } + } } } diff --git a/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs b/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs index 66f2a62c2d..7b9c9bdcda 100644 --- a/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs +++ b/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs @@ -526,10 +526,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } } - // https://github.com/dotnet/corefx/issues/24562 - [ConditionalFact] - [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Sockets transport fails to rebind on macOS.")] - [OSSkipCondition(OperatingSystems.Linux, SkipReason = "Sockets transport fails to rebind on Linux.")] + [Fact] public async Task CanRebindToEndPoint() { var port = GetNextPort(); @@ -566,10 +563,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } } - // https://github.com/dotnet/corefx/issues/24562 [ConditionalFact] - [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Sockets transport fails to rebind on macOS.")] - [OSSkipCondition(OperatingSystems.Linux, SkipReason = "Sockets transport fails to rebind on Linux.")] + [IPv6SupportedCondition] public async Task CanRebindToMultipleEndPoints() { var port = GetNextPort(); @@ -892,12 +887,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests private bool CanBindToPort() { -#if MACOS && SOCKETS - // Binding to a port with a Socket, disposing the Socket, and rebinding often fails with - // SocketError.AddressAlreadyInUse on macOS. This isn't an issue if binding with libuv second. - // https://github.com/dotnet/corefx/issues/24562 - return false; -#else try { using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) @@ -910,7 +899,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { return false; } -#endif } } }