Skip to content

Commit 12fd27b

Browse files
author
Eric Eilebrecht
authored
Rework ExclusiveAddressUse and ReuseAddress on non-Windows platforms (dotnet/corefx#11509)
The ExclusiveAddressUse socket option is a Windows-specific option that, when set to "true," tells Windows not to allow another socket to use the same local address as this socket. This is only needed on Windows because it otherwise (at least in some versions/configurations) allows any socket with the ReuseAddress option set to "steal" the address from a socket that did not have *any* options set. On Unix, we previously treated this as an "unsupported" option. However, it is recommended to set this option to "true" on Windows, to avoid malicious theft of a service's address, so we need to support the option, in some fashion, on Unix, so that it's possible to write portable code that works reliably everywhere. Since the *only* behavior on Linux/OSX is equivalent to "ExclusiveAddressUse=true" on Windows, we implement this option as a no-op if it's set to "true," and as an unsupported option if set to "false." (It's possible that we could come up with a better failure for the "false" case, but I'm treating it as "unsupported" for compatiblity with the 1.0 release). Another related option is ReuseAddress. On Windows, this option allows a socket's address *and* port to be reused. It's equivalent to *two* native options on Unix: SO_REUSEADDR and SO_REUSEPORT. Again, for portability, we need an option that will work roughly the same way on all platforms. We could introduce a new option (ReuseAddressAndPort?) but existing code is already using the current ReuseAddress option. So this change makes ReuseAddress set both SO_REUSEADDR and SO_REUSEPORT on Unix. If we need to support these options individually, on Unix only, in the future, we'll need to introduce two new options (maybe ReuseAddressOnly and ReusePort) which will likely need to be treated as "unsupported" options on Windows. For now, no need for managed support for more fine-grained options has been demonstrated. For more information on the underlying native options on Windows, Linux, OSX, etc., see this great writeup [on stackoverflow](http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t). Also, the Windows docs [discuss the SO_EXCLUSIVEADDR option](https://msdn.microsoft.com/en-us/library/windows/desktop/cc150667(v=vs.85).aspx) in depth. Commit migrated from dotnet/corefx@875675f
1 parent 2c31c8e commit 12fd27b

File tree

4 files changed

+172
-7
lines changed

4 files changed

+172
-7
lines changed

src/libraries/Native/Unix/System.Native/pal_networking.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2071,6 +2071,51 @@ extern "C" Error SystemNative_GetSockOpt(
20712071

20722072
int fd = ToFileDescriptor(socket);
20732073

2074+
//
2075+
// Handle some special cases for compatibility with Windows
2076+
//
2077+
if (socketOptionLevel == PAL_SOL_SOCKET)
2078+
{
2079+
if (socketOptionName == PAL_SO_EXCLUSIVEADDRUSE)
2080+
{
2081+
//
2082+
// SO_EXCLUSIVEADDRUSE makes Windows behave like Unix platforms do WRT the SO_REUSEADDR option.
2083+
// So, for non-Windows platforms, we act as if SO_EXCLUSIVEADDRUSE is always enabled.
2084+
//
2085+
if (*optionLen != sizeof(int32_t))
2086+
{
2087+
return PAL_EINVAL;
2088+
}
2089+
2090+
*reinterpret_cast<int32_t*>(optionValue) = 1;
2091+
return PAL_SUCCESS;
2092+
}
2093+
else if (socketOptionName == PAL_SO_REUSEADDR)
2094+
{
2095+
//
2096+
// On Windows, SO_REUSEADDR allows the address *and* port to be reused. It's equivalent to
2097+
// SO_REUSEADDR + SO_REUSEPORT other systems. Se we only return "true" if both of those options are true.
2098+
//
2099+
auto optLen = static_cast<socklen_t>(*optionLen);
2100+
2101+
int err = getsockopt(fd, SOL_SOCKET, SO_REUSEADDR, optionValue, &optLen);
2102+
2103+
if (err == 0 && *reinterpret_cast<uint32_t*>(optionValue) != 0)
2104+
{
2105+
err = getsockopt(fd, SOL_SOCKET, SO_REUSEPORT, optionValue, &optLen);
2106+
}
2107+
2108+
if (err != 0)
2109+
{
2110+
return SystemNative_ConvertErrorPlatformToPal(errno);
2111+
}
2112+
2113+
assert(optLen <= static_cast<socklen_t>(*optionLen));
2114+
*optionLen = static_cast<int32_t>(optLen);
2115+
return PAL_SUCCESS;
2116+
}
2117+
}
2118+
20742119
int optLevel, optName;
20752120
if (!TryGetPlatformSocketOption(socketOptionLevel, socketOptionName, optLevel, optName))
20762121
{
@@ -2099,6 +2144,47 @@ SystemNative_SetSockOpt(intptr_t socket, int32_t socketOptionLevel, int32_t sock
20992144

21002145
int fd = ToFileDescriptor(socket);
21012146

2147+
//
2148+
// Handle some special cases for compatibility with Windows
2149+
//
2150+
if (socketOptionLevel == PAL_SOL_SOCKET)
2151+
{
2152+
if (socketOptionName == PAL_SO_EXCLUSIVEADDRUSE)
2153+
{
2154+
//
2155+
// SO_EXCLUSIVEADDRUSE makes Windows behave like Unix platforms do WRT the SO_REUSEADDR option.
2156+
// So, on Unix platforms, we consider SO_EXCLUSIVEADDRUSE to always be set. We allow manually setting this
2157+
// to "true", but not "false."
2158+
//
2159+
if (optionLen != sizeof(int32_t))
2160+
{
2161+
return PAL_EINVAL;
2162+
}
2163+
2164+
if (*reinterpret_cast<int32_t*>(optionValue) == 0)
2165+
{
2166+
return PAL_ENOTSUP;
2167+
}
2168+
else
2169+
{
2170+
return PAL_SUCCESS;
2171+
}
2172+
}
2173+
else if (socketOptionName == PAL_SO_REUSEADDR)
2174+
{
2175+
//
2176+
// On Windows, SO_REUSEADDR allows the address *and* port to be reused. It's equivalent to
2177+
// SO_REUSEADDR + SO_REUSEPORT other systems.
2178+
//
2179+
int err = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, optionValue, static_cast<socklen_t>(optionLen));
2180+
if (err == 0)
2181+
{
2182+
err = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, optionValue, static_cast<socklen_t>(optionLen));
2183+
}
2184+
return err == 0 ? PAL_SUCCESS : SystemNative_ConvertErrorPlatformToPal(errno);
2185+
}
2186+
}
2187+
21022188
int optLevel, optName;
21032189
if (!TryGetPlatformSocketOption(socketOptionLevel, socketOptionName, optLevel, optName))
21042190
{

src/libraries/Native/Unix/System.Native/pal_networking.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ enum SocketOptionName : int32_t
141141
PAL_SO_LINGER = 0x0080,
142142
PAL_SO_OOBINLINE = 0x0100,
143143
// PAL_SO_DONTLINGER = ~PAL_SO_LINGER,
144-
// PAL_SO_EXCLUSIVEADDRUSE = ~PAL_SO_REUSEADDR,
144+
PAL_SO_EXCLUSIVEADDRUSE = ~PAL_SO_REUSEADDR,
145145
PAL_SO_SNDBUF = 0x1001,
146146
PAL_SO_RCVBUF = 0x1002,
147147
PAL_SO_SNDLOWAT = 0x1003,

src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,5 +233,65 @@ private static Socket CreateBoundUdpSocket(out int localPort)
233233
localPort = (receiveSocket.LocalEndPoint as IPEndPoint).Port;
234234
return receiveSocket;
235235
}
236+
237+
[Theory]
238+
[InlineData(null, null, null, true)]
239+
[InlineData(null, null, false, true)]
240+
[InlineData(null, false, false, true)]
241+
[InlineData(null, true, false, true)]
242+
[InlineData(null, true, true, false)]
243+
[InlineData(true, null, null, true)]
244+
[InlineData(true, null, false, true)]
245+
[InlineData(true, null, true, true)]
246+
[InlineData(true, false, null, true)]
247+
[InlineData(true, false, false, true)]
248+
[InlineData(true, false, true, true)]
249+
public void ReuseAddress(bool? exclusiveAddressUse, bool? firstSocketReuseAddress, bool? secondSocketReuseAddress, bool expectFailure)
250+
{
251+
using (Socket a = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
252+
{
253+
if (exclusiveAddressUse.HasValue)
254+
{
255+
a.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, exclusiveAddressUse.Value);
256+
}
257+
if (firstSocketReuseAddress.HasValue)
258+
{
259+
a.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, firstSocketReuseAddress.Value);
260+
}
261+
262+
a.Bind(new IPEndPoint(IPAddress.Loopback, 0));
263+
264+
using (Socket b = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
265+
{
266+
if (secondSocketReuseAddress.HasValue)
267+
{
268+
b.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, secondSocketReuseAddress.Value);
269+
}
270+
271+
if (expectFailure)
272+
{
273+
Assert.ThrowsAny<SocketException>(() => b.Bind(a.LocalEndPoint));
274+
}
275+
else
276+
{
277+
b.Bind(a.LocalEndPoint);
278+
}
279+
}
280+
}
281+
}
282+
283+
[Theory]
284+
[PlatformSpecific(PlatformID.Windows)]
285+
[InlineData(false, null, null, true)]
286+
[InlineData(false, null, false, true)]
287+
[InlineData(false, false, null, true)]
288+
[InlineData(false, false, false, true)]
289+
[InlineData(false, true, null, true)]
290+
[InlineData(false, true, false, true)]
291+
[InlineData(false, true, true, false)]
292+
public void ReuseAddress_Windows(bool? exclusiveAddressUse, bool? firstSocketReuseAddress, bool? secondSocketReuseAddress, bool expectFailure)
293+
{
294+
ReuseAddress(exclusiveAddressUse, firstSocketReuseAddress, secondSocketReuseAddress, expectFailure);
295+
}
236296
}
237297
}

src/libraries/System.Net.Sockets/tests/FunctionalTests/TcpClientTest.cs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public void ConnectedAvailable_NullClient()
8888
[OuterLoop] // TODO: Issue #11345
8989
[Fact]
9090
[PlatformSpecific(PlatformID.Windows)]
91-
public void ExclusiveAddressUse_NullClient()
91+
public void ExclusiveAddressUse_NullClient_Windows()
9292
{
9393
using (TcpClient client = new TcpClient())
9494
{
@@ -100,13 +100,33 @@ public void ExclusiveAddressUse_NullClient()
100100

101101
[OuterLoop] // TODO: Issue #11345
102102
[Fact]
103-
[PlatformSpecific(PlatformID.Windows)]
104-
public void Roundtrip_ExclusiveAddressUse_GetEqualsSet()
103+
[PlatformSpecific(~PlatformID.Windows)]
104+
public void ExclusiveAddressUse_NullClient_NonWindows()
105+
{
106+
using (TcpClient client = new TcpClient())
107+
{
108+
client.Client = null;
109+
110+
Assert.True(client.ExclusiveAddressUse);
111+
}
112+
}
113+
114+
[Fact]
115+
public void Roundtrip_ExclusiveAddressUse_GetEqualsSet_True()
105116
{
106117
using (TcpClient client = new TcpClient())
107118
{
108119
client.ExclusiveAddressUse = true;
109120
Assert.True(client.ExclusiveAddressUse);
121+
}
122+
}
123+
124+
[Fact]
125+
[PlatformSpecific(PlatformID.Windows)]
126+
public void Roundtrip_ExclusiveAddressUse_GetEqualsSet_False()
127+
{
128+
using (TcpClient client = new TcpClient())
129+
{
110130
client.ExclusiveAddressUse = false;
111131
Assert.False(client.ExclusiveAddressUse);
112132
}
@@ -115,14 +135,13 @@ public void Roundtrip_ExclusiveAddressUse_GetEqualsSet()
115135
[OuterLoop] // TODO: Issue #11345
116136
[Fact]
117137
[PlatformSpecific(PlatformID.AnyUnix)]
118-
public void ExclusiveAddressUse_NotSupported()
138+
public void ExclusiveAddressUse_Set_False_NotSupported()
119139
{
120140
using (TcpClient client = new TcpClient())
121141
{
122-
Assert.Throws<SocketException>(() => client.ExclusiveAddressUse);
123142
Assert.Throws<SocketException>(() =>
124143
{
125-
client.ExclusiveAddressUse = true;
144+
client.ExclusiveAddressUse = false;
126145
});
127146
}
128147
}

0 commit comments

Comments
 (0)