Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Unix: fix UDP ReuseAddress with non .NET Core UDP clients #32046

Merged
merged 8 commits into from Oct 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 14 additions & 8 deletions src/Native/Unix/System.Native/pal_networking.c
Expand Up @@ -1812,14 +1812,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)
{
if (optionLen != sizeof(int32_t))
Expand All @@ -1842,7 +1840,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);
}
}
Expand Down
Expand Up @@ -2,8 +2,10 @@
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<BuildConfigurations>
netstandard;
netcoreapp;
netstandard-Windows_NT;
netstandard-Unix;
netcoreapp-Windows_NT;
netcoreapp-Unix;
</BuildConfigurations>
</PropertyGroup>
</Project>
@@ -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);
}
}
@@ -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();
}
}
}
Expand Up @@ -13,7 +13,7 @@

namespace System.Net.Sockets.Tests
{
public class SocketOptionNameTest
public partial class SocketOptionNameTest
{
private static bool SocketsReuseUnicastPortSupport => Capability.SocketsReuseUnicastPortSupport().HasValue;

Expand Down Expand Up @@ -490,6 +490,42 @@ public void ExclusiveAddressUseTcp()
}
}

[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
Expand Down
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<ProjectGuid>{8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}</ProjectGuid>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Configurations>netcoreapp-Debug;netcoreapp-Release;netstandard-Debug;netstandard-Release</Configurations>
<Configurations>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</Configurations>
</PropertyGroup>
<ItemGroup>
<Compile Include="Accept.cs" />
Expand Down Expand Up @@ -44,6 +44,8 @@
<Compile Include="SocketAsyncEventArgsTest.cs" />
<Compile Include="SocketAsyncEventArgsTest.netcoreapp.cs" Condition="'$(TargetGroup)' != 'netstandard'" />
<Compile Include="SocketOptionNameTest.cs" />
<Compile Include="SocketOptionNameTest.Unix.cs" Condition="'$(TargetsUnix)' == 'true'" />
<Compile Include="SocketOptionNameTest.Windows.cs" Condition="'$(TargetsWindows)' == 'true'" />
<Compile Include="MulticastOptionTest.cs" />
<Compile Include="UdpClientTest.cs" />
<Compile Include="UnixDomainSocketTest.netcoreapp.cs" Condition="'$(TargetGroup)' != 'netstandard'" />
Expand Down