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

Add cross-platform support for keep-alive socket options (closes #25040) #29963

Merged
merged 12 commits into from
Jun 12, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Native/Unix/Common/pal_config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
#cmakedefine01 BIND_ADDRLEN_UNSIGNED
#cmakedefine01 INOTIFY_RM_WATCH_WD_UNSIGNED
#cmakedefine01 HAVE_IN_EXCL_UNLINK
#cmakedefine01 HAVE_TCP_H_TCP_KEEPALIVE

// Mac OS X has stat64, but it is deprecated since plain stat now
// provides the same 64-bit aware struct when targeting OS X > 10.5
Expand Down
17 changes: 17 additions & 0 deletions src/Native/Unix/System.Native/pal_networking.c
Original file line number Diff line number Diff line change
Expand Up @@ -1654,6 +1654,23 @@ static bool TryGetPlatformSocketOption(int32_t socketOptionName, int32_t socketO

// case SocketOptionName_SO_TCP_BSDURGENT:

case SocketOptionName_SO_TCP_KEEPALIVE_RETRYCOUNT:
*optName = TCP_KEEPCNT;
return true;

case SocketOptionName_SO_TCP_KEEPALIVE_TIME:
*optName =
#if HAVE_TCP_H_TCP_KEEPALIVE
TCP_KEEPALIVE;
#else
TCP_KEEPIDLE;
#endif
return true;

case SocketOptionName_SO_TCP_KEEPALIVE_INTERVAL:
*optName = TCP_KEEPINTVL;
return true;

default:
return false;
}
Expand Down
3 changes: 3 additions & 0 deletions src/Native/Unix/System.Native/pal_networking.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ enum SocketOptionName
// Names for SocketOptionLevel_SOL_TCP
SocketOptionName_SO_TCP_NODELAY = 1,
// SocketOptionName_SO_TCP_BSDURGENT = 2,
SocketOptionName_SO_TCP_KEEPALIVE_RETRYCOUNT = 16,
SocketOptionName_SO_TCP_KEEPALIVE_TIME = 3,
SocketOptionName_SO_TCP_KEEPALIVE_INTERVAL = 17,

// Names for SocketOptionLevel_SOL_UDP
// SocketOptionName_SO_UDP_NOCHECKSUM = 1,
Expand Down
12 changes: 12 additions & 0 deletions src/Native/Unix/configure.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,18 @@ check_c_source_compiles(
"
HAVE_IN_EXCL_UNLINK)

check_c_source_compiles(
"
#include <netinet/tcp.h>
int main()
{
int x = TCP_KEEPALIVE;
return x;
}
"
HAVE_TCP_H_TCP_KEEPALIVE
)

configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/Common/pal_config.h.in
${CMAKE_CURRENT_BINARY_DIR}/Common/pal_config.h)
3 changes: 3 additions & 0 deletions src/System.Net.Sockets/ref/System.Net.Sockets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,9 @@ public enum SocketOptionName
SendBuffer = 4097,
SendLowWater = 4099,
SendTimeout = 4101,
TcpKeepAliveRetryCount = 16,
TcpKeepAliveTime = 3,
TcpKeepAliveInterval = 17,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These numbers conflict with DropSourceMembership, TypeOfService, and BlockSource, respectively.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since SocketOptionName values are traditionally operating into their own namespaces (aka levels), I proposed that we stick to the tradition of reusing native Windows values for options here, explaining the values 16, 3 and 17.

As seen in the source for SocketOptionName, there are already many conflicting enumeration values, which is supposed to be fine, since those operate at a different level.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these are all the Windows values, then sounds good.

Type = 4104,
TypeOfService = 3,
UnblockSource = 18,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ public enum SocketOptionName
NoDelay = 1,
BsdUrgent = 2,
Expedited = 2,
TcpKeepAliveRetryCount = 16,
TcpKeepAliveTime = 3,
TcpKeepAliveInterval = 17,
#endregion

#region SocketOptionlevel.Udp
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// 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;
using Xunit;

namespace System.Net.Sockets.Tests
{
public class KeepAliveTest
{
private const int RetryCount = 60;
private const int Time = 5;
private const int Interval = 2;

private static bool IsUnixOrWindowsAtLeast1703 =>
!PlatformDetection.IsWindows || PlatformDetection.IsWindows10Version1703OrGreater;

private static bool IsWindowsBelow1703 =>
PlatformDetection.IsWindows && !PlatformDetection.IsWindows10Version1703OrGreater;

private static bool IsUnixOrWindowsAtLeast1709 =>
!PlatformDetection.IsWindows || PlatformDetection.IsWindows10Version1709OrGreater;

private static bool IsWindowsBelow1709 =>
PlatformDetection.IsWindows && !PlatformDetection.IsWindows10Version1709OrGreater;

[Fact]
public void Socket_KeepAlive_Disabled_By_Default()
{
using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
Assert.Equal<int>(0, (int)socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive));
}
}

[Fact]
public void Socket_KeepAlive_Enable()
{
using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
Assert.NotEqual<int>(0, (int)socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive));
}
}

[ConditionalFact(typeof(KeepAliveTest), nameof(IsUnixOrWindowsAtLeast1703))]
public void Socket_Set_KeepAlive_RetryCount_Success()
{
using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, RetryCount);
Assert.Equal<int>(RetryCount, (int)socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount));
}
}

[ConditionalFact(typeof(KeepAliveTest), nameof(IsWindowsBelow1703))]
public void Socket_Set_KeepAlive_RetryCount_Failure()
{
using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
Assert.Throws<SocketException>(() => socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, RetryCount));
Assert.Throws<SocketException>(() => socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount));
}
}

[Fact]
public void Socket_Set_KeepAlive_Time_Success()
{
using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, Time);
Assert.Equal<int>(Time, (int)socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime));
}
}

[ConditionalFact(typeof(KeepAliveTest), nameof(IsUnixOrWindowsAtLeast1709))]
public void Socket_Set_KeepAlive_Interval_Success()
{
using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, Interval);
Assert.Equal<int>(Interval, (int)socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval));
}
}

[ConditionalFact(typeof(KeepAliveTest), nameof(IsWindowsBelow1709))]
public void Socket_Set_KeepAlive_Interval_Failure()
{
using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
Assert.Throws<SocketException>(() => socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, Interval));
Assert.Throws<SocketException>(() => socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<Compile Include="ExecutionContextFlowTest.cs" />
<Compile Include="ExecutionContextFlowTest.netcoreapp.cs" Condition="'$(TargetGroup)' != 'netstandard'" />
<Compile Include="IPPacketInformationTest.cs" />
<Compile Include="KeepAliveTest.netcoreapp.cs" Condition="'$(TargetGroup)' != 'netstandard'" />
<Compile Include="LingerStateTest.cs" />
<Compile Include="LoggingTest.cs" />
<Compile Include="NetworkStreamTest.cs" />
Expand Down