From 6fbecaa314b04fff56c7f584456e3db45a6f4be0 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 9 Oct 2018 13:17:06 +0200 Subject: [PATCH] Unix: fix UDP ReuseAddress with non .NET Core UDP clients (dotnet/corefx#32046) * Unix: fix UDP ReuseAddress with non .NET Core UDP clients .NET Core sets SO_REUSEPORT to enable reusing the address with other sockets. This works when the other sockets also set SO_REUSEPORT. SO_REUSEADDR can also be used for this. When one application uses SO_REUSEADDR and another uses SO_REUSEPORT, the second application will fail to bind. To implement SocketOptionName.ReuseAddress we need to set both options. This enables sharing with applications that set SO_REUSEPORT, SO_REUSEADDR or both. * Fix comment * wording: share -> reuse * Add test * Fix test on mac * Move DllImport to separate file * Add separate csproj Configurations for Windows and Unix * Add Windows and Unix to Configurations.props Commit migrated from https://github.com/dotnet/corefx/commit/0ec9c9d7caac1ac378e2135620bf2f8b80e6ed86 --- .../Native/Unix/System.Native/pal_networking.c | 22 ++++++++----- .../tests/FunctionalTests/Configurations.props | 6 ++-- .../FunctionalTests/SocketOptionNameTest.Unix.cs | 14 ++++++++ .../SocketOptionNameTest.Windows.cs | 14 ++++++++ .../tests/FunctionalTests/SocketOptionNameTest.cs | 38 +++++++++++++++++++++- .../System.Net.Sockets.Tests.csproj | 4 ++- 6 files changed, 86 insertions(+), 12 deletions(-) create mode 100644 src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.Unix.cs create mode 100644 src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.Windows.cs diff --git a/src/libraries/Native/Unix/System.Native/pal_networking.c b/src/libraries/Native/Unix/System.Native/pal_networking.c index 7442eb7..380d7a2 100644 --- a/src/libraries/Native/Unix/System.Native/pal_networking.c +++ b/src/libraries/Native/Unix/System.Native/pal_networking.c @@ -1816,14 +1816,12 @@ SystemNative_SetSockOpt(intptr_t socket, int32_t socketOptionLevel, int32_t sock // if (socketOptionLevel == SocketOptionLevel_SOL_SOCKET) { - // Windows supports 3 address sharing modes: - // - not sharing (SO_EXCLUSIVEADDRUSE=1, SO_REUSEADDR=0) - // - explicit sharing (SO_EXCLUSIVEADDRUSE=0, SO_REUSEADDR=1) - // - implicit sharing (SO_EXCLUSIVEADDRUSE=0, SO_REUSEADDR=0) - // On Unix we have two address sharing modes: - // - not sharing (SO_REUSEPORT=0) - // - explicit sharing (SO_REUSEPORT=1) - // We make both SocketOptionName_SO_REUSEADDR and SocketOptionName_SO_EXCLUSIVEADDRUSE control SO_REUSEPORT. + // Windows supports 3 address reuse modes: + // - reuse not allowed (SO_EXCLUSIVEADDRUSE=1, SO_REUSEADDR=0) + // - reuse explicily allowed (SO_EXCLUSIVEADDRUSE=0, SO_REUSEADDR=1) + // - reuse implicitly allowed (SO_EXCLUSIVEADDRUSE=0, SO_REUSEADDR=0) + // On Unix we can reuse or not, there is no implicit reuse. + // We make both SocketOptionName_SO_REUSEADDR and SocketOptionName_SO_EXCLUSIVEADDRUSE control SO_REUSEPORT/SO_REUSEADDR. if (socketOptionName == SocketOptionName_SO_EXCLUSIVEADDRUSE || socketOptionName == SocketOptionName_SO_REUSEADDR) { #ifdef SO_REUSEPORT @@ -1847,7 +1845,15 @@ SystemNative_SetSockOpt(intptr_t socket, int32_t socketOptionLevel, int32_t sock } } + // An application that sets SO_REUSEPORT/SO_REUSEADDR can reuse the endpoint with another + // application that sets the same option. If one application sets SO_REUSEPORT and another + // sets SO_REUSEADDR the second application will fail to bind. We set both options, this + // enables reuse with applications that set one or both options. int err = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &value, (socklen_t)optionLen); + if (err == 0) + { + err = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &value, (socklen_t)optionLen); + } return err == 0 ? Error_SUCCESS : SystemNative_ConvertErrorPlatformToPal(errno); #else // !SO_REUSEPORT return Error_SUCCESS; diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/Configurations.props b/src/libraries/System.Net.Sockets/tests/FunctionalTests/Configurations.props index 77a4b65..eddfd3a 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/Configurations.props +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/Configurations.props @@ -2,8 +2,10 @@ - netstandard; - netcoreapp; + netstandard-Windows_NT; + netstandard-Unix; + netcoreapp-Windows_NT; + netcoreapp-Unix; \ No newline at end of file diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.Unix.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.Unix.cs new file mode 100644 index 0000000..b94f514 --- /dev/null +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.Unix.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.InteropServices; + +namespace System.Net.Sockets.Tests +{ + public partial class SocketOptionNameTest + { + [DllImport("libc", SetLastError = true)] + private unsafe static extern int setsockopt(int socket, int level, int option_name, void* option_value, uint option_len); + } +} diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.Windows.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.Windows.cs new file mode 100644 index 0000000..8c6aa93 --- /dev/null +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.Windows.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Net.Sockets.Tests +{ + public partial class SocketOptionNameTest + { + private unsafe static int setsockopt(int socket, int level, int option_name, void* option_value, uint option_len) + { + throw new PlatformNotSupportedException(); + } + } +} diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.cs index 997ddd5..3a3b3a8 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.cs @@ -13,7 +13,7 @@ using Xunit; namespace System.Net.Sockets.Tests { - public class SocketOptionNameTest + public partial class SocketOptionNameTest { private static bool SocketsReuseUnicastPortSupport => Capability.SocketsReuseUnicastPortSupport().HasValue; @@ -490,6 +490,42 @@ namespace System.Net.Sockets.Tests } } + [Fact] + [PlatformSpecific(TestPlatforms.Linux | TestPlatforms.OSX)] + public unsafe void ReuseAddressUdp() + { + // Verify that .NET Core Sockets can bind to the UDP address from applications + // that allow binding the same address. + int SOL_SOCKET = -1; + int option = -1; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + // Linux: use SO_REUSEADDR to allow binding the same address. + SOL_SOCKET = 1; + const int SO_REUSEADDR = 2; + option = SO_REUSEADDR; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + // BSD: use SO_REUSEPORT to allow binding the same address. + SOL_SOCKET = 0xffff; + const int SO_REUSEPORT = 0x200; + option = SO_REUSEPORT; + } + using (Socket s1 = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) + { + int value = 1; + int rv = setsockopt(s1.Handle.ToInt32(), SOL_SOCKET, option, &value, sizeof(int)); + Assert.Equal(0, rv); + s1.Bind(new IPEndPoint(IPAddress.Any, 0)); + using (Socket s2 = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) + { + s2.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + s2.Bind(s1.LocalEndPoint); + } + } + } + [OuterLoop] // TODO: Issue #11345 [Theory] [PlatformSpecific(TestPlatforms.Windows)] // SetIPProtectionLevel not supported on Unix diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj b/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj index 46c8332..0f6732e 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/System.Net.Sockets.Tests.csproj @@ -2,7 +2,7 @@ {8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6} true - netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netstandard-Unix-Debug;netstandard-Unix-Release;netstandard-Windows_NT-Debug;netstandard-Windows_NT-Release @@ -44,6 +44,8 @@ + + -- 2.7.4