diff --git a/src/Native/Unix/System.Native/pal_networking.cpp b/src/Native/Unix/System.Native/pal_networking.cpp index cc13da3b2e53..16c0f87f8d4f 100644 --- a/src/Native/Unix/System.Native/pal_networking.cpp +++ b/src/Native/Unix/System.Native/pal_networking.cpp @@ -2071,6 +2071,51 @@ extern "C" Error SystemNative_GetSockOpt( int fd = ToFileDescriptor(socket); + // + // Handle some special cases for compatibility with Windows + // + if (socketOptionLevel == PAL_SOL_SOCKET) + { + if (socketOptionName == PAL_SO_EXCLUSIVEADDRUSE) + { + // + // SO_EXCLUSIVEADDRUSE makes Windows behave like Unix platforms do WRT the SO_REUSEADDR option. + // So, for non-Windows platforms, we act as if SO_EXCLUSIVEADDRUSE is always enabled. + // + if (*optionLen != sizeof(int32_t)) + { + return PAL_EINVAL; + } + + *reinterpret_cast(optionValue) = 1; + return PAL_SUCCESS; + } + else if (socketOptionName == PAL_SO_REUSEADDR) + { + // + // On Windows, SO_REUSEADDR allows the address *and* port to be reused. It's equivalent to + // SO_REUSEADDR + SO_REUSEPORT other systems. Se we only return "true" if both of those options are true. + // + auto optLen = static_cast(*optionLen); + + int err = getsockopt(fd, SOL_SOCKET, SO_REUSEADDR, optionValue, &optLen); + + if (err == 0 && *reinterpret_cast(optionValue) != 0) + { + err = getsockopt(fd, SOL_SOCKET, SO_REUSEPORT, optionValue, &optLen); + } + + if (err != 0) + { + return SystemNative_ConvertErrorPlatformToPal(errno); + } + + assert(optLen <= static_cast(*optionLen)); + *optionLen = static_cast(optLen); + return PAL_SUCCESS; + } + } + int optLevel, optName; if (!TryGetPlatformSocketOption(socketOptionLevel, socketOptionName, optLevel, optName)) { @@ -2099,6 +2144,47 @@ SystemNative_SetSockOpt(intptr_t socket, int32_t socketOptionLevel, int32_t sock int fd = ToFileDescriptor(socket); + // + // Handle some special cases for compatibility with Windows + // + if (socketOptionLevel == PAL_SOL_SOCKET) + { + if (socketOptionName == PAL_SO_EXCLUSIVEADDRUSE) + { + // + // SO_EXCLUSIVEADDRUSE makes Windows behave like Unix platforms do WRT the SO_REUSEADDR option. + // So, on Unix platforms, we consider SO_EXCLUSIVEADDRUSE to always be set. We allow manually setting this + // to "true", but not "false." + // + if (optionLen != sizeof(int32_t)) + { + return PAL_EINVAL; + } + + if (*reinterpret_cast(optionValue) == 0) + { + return PAL_ENOTSUP; + } + else + { + return PAL_SUCCESS; + } + } + else if (socketOptionName == PAL_SO_REUSEADDR) + { + // + // On Windows, SO_REUSEADDR allows the address *and* port to be reused. It's equivalent to + // SO_REUSEADDR + SO_REUSEPORT other systems. + // + int err = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, optionValue, static_cast(optionLen)); + if (err != 0) + { + err = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, optionValue, static_cast(optionLen)); + } + return err == 0 ? PAL_SUCCESS : SystemNative_ConvertErrorPlatformToPal(errno); + } + } + int optLevel, optName; if (!TryGetPlatformSocketOption(socketOptionLevel, socketOptionName, optLevel, optName)) { diff --git a/src/Native/Unix/System.Native/pal_networking.h b/src/Native/Unix/System.Native/pal_networking.h index 0e5497c319a3..7f5456153f83 100644 --- a/src/Native/Unix/System.Native/pal_networking.h +++ b/src/Native/Unix/System.Native/pal_networking.h @@ -141,7 +141,7 @@ enum SocketOptionName : int32_t PAL_SO_LINGER = 0x0080, PAL_SO_OOBINLINE = 0x0100, // PAL_SO_DONTLINGER = ~PAL_SO_LINGER, - // PAL_SO_EXCLUSIVEADDRUSE = ~PAL_SO_REUSEADDR, + PAL_SO_EXCLUSIVEADDRUSE = ~PAL_SO_REUSEADDR, PAL_SO_SNDBUF = 0x1001, PAL_SO_RCVBUF = 0x1002, PAL_SO_SNDLOWAT = 0x1003, diff --git a/src/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.cs b/src/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.cs index 4d4f503f05fd..b38894067766 100644 --- a/src/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.cs +++ b/src/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.cs @@ -219,5 +219,65 @@ private static Socket CreateBoundUdpSocket(out int localPort) localPort = (receiveSocket.LocalEndPoint as IPEndPoint).Port; return receiveSocket; } + + [Theory] + [InlineData(null, null, null, true)] + [InlineData(null, null, false, true)] + [InlineData(null, false, false, true)] + [InlineData(null, true, false, true)] + [InlineData(null, true, true, false)] + [InlineData(true, null, null, true)] + [InlineData(true, null, false, true)] + [InlineData(true, null, true, true)] + [InlineData(true, false, null, true)] + [InlineData(true, false, false, true)] + [InlineData(true, false, true, true)] + public void ReuseAddress(bool? exclusiveAddressUse, bool? firstSocketReuseAddress, bool? secondSocketReuseAddress, bool expectFailure) + { + using (Socket a = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) + { + if (exclusiveAddressUse.HasValue) + { + a.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, exclusiveAddressUse.Value); + } + if (firstSocketReuseAddress.HasValue) + { + a.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, firstSocketReuseAddress.Value); + } + + a.Bind(new IPEndPoint(IPAddress.Loopback, 0)); + + using (Socket b = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) + { + if (secondSocketReuseAddress.HasValue) + { + b.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, secondSocketReuseAddress.Value); + } + + if (expectFailure) + { + Assert.ThrowsAny(() => b.Bind(a.LocalEndPoint)); + } + else + { + b.Bind(a.LocalEndPoint); + } + } + } + } + + [Theory] + [PlatformSpecific(PlatformID.Windows)] + [InlineData(false, null, null, true)] + [InlineData(false, null, false, true)] + [InlineData(false, false, null, true)] + [InlineData(false, false, false, true)] + [InlineData(false, true, null, true)] + [InlineData(false, true, false, true)] + [InlineData(false, true, true, false)] + public void ReuseAddress_Windows(bool? exclusiveAddressUse, bool? firstSocketReuseAddress, bool? secondSocketReuseAddress, bool expectFailure) + { + ReuseAddress(exclusiveAddressUse, firstSocketReuseAddress, secondSocketReuseAddress, expectFailure); + } } } diff --git a/src/System.Net.Sockets/tests/FunctionalTests/TcpClientTest.cs b/src/System.Net.Sockets/tests/FunctionalTests/TcpClientTest.cs index 8dd73940727d..4caa410dd051 100644 --- a/src/System.Net.Sockets/tests/FunctionalTests/TcpClientTest.cs +++ b/src/System.Net.Sockets/tests/FunctionalTests/TcpClientTest.cs @@ -84,7 +84,7 @@ public void ConnectedAvailable_NullClient() [Fact] [PlatformSpecific(PlatformID.Windows)] - public void ExclusiveAddressUse_NullClient() + public void ExclusiveAddressUse_NullClient_Windows() { using (TcpClient client = new TcpClient()) { @@ -95,13 +95,33 @@ public void ExclusiveAddressUse_NullClient() } [Fact] - [PlatformSpecific(PlatformID.Windows)] - public void Roundtrip_ExclusiveAddressUse_GetEqualsSet() + [PlatformSpecific(~PlatformID.Windows)] + public void ExclusiveAddressUse_NullClient_NonWindows() + { + using (TcpClient client = new TcpClient()) + { + client.Client = null; + + Assert.True(client.ExclusiveAddressUse); + } + } + + [Fact] + public void Roundtrip_ExclusiveAddressUse_GetEqualsSet_True() { using (TcpClient client = new TcpClient()) { client.ExclusiveAddressUse = true; Assert.True(client.ExclusiveAddressUse); + } + } + + [Fact] + [PlatformSpecific(PlatformID.Windows)] + public void Roundtrip_ExclusiveAddressUse_GetEqualsSet_False() + { + using (TcpClient client = new TcpClient()) + { client.ExclusiveAddressUse = false; Assert.False(client.ExclusiveAddressUse); } @@ -109,14 +129,13 @@ public void Roundtrip_ExclusiveAddressUse_GetEqualsSet() [Fact] [PlatformSpecific(PlatformID.AnyUnix)] - public void ExclusiveAddressUse_NotSupported() + public void ExclusiveAddressUse_Set_False_NotSupported() { using (TcpClient client = new TcpClient()) { - Assert.Throws(() => client.ExclusiveAddressUse); Assert.Throws(() => { - client.ExclusiveAddressUse = true; + client.ExclusiveAddressUse = false; }); } }