diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.IOVector.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.IOVector.cs
index 9cbf1ee2c34785..a55e2ca19f0e15 100644
--- a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.IOVector.cs
+++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.IOVector.cs
@@ -2,11 +2,13 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Runtime.InteropServices;
internal static partial class Interop
{
internal static partial class Sys
{
+ [StructLayout(LayoutKind.Sequential)]
internal unsafe struct IOVector
{
public byte* Base;
diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.ReceiveSocketError.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.ReceiveSocketError.cs
new file mode 100644
index 00000000000000..be4888bbfb743f
--- /dev/null
+++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.ReceiveSocketError.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_ReceiveSocketError")]
+ internal static unsafe partial SocketError ReceiveSocketError(SafeHandle socket, MessageHeader* messageHeader);
+ }
+}
diff --git a/src/libraries/System.Net.Ping/src/System.Net.Ping.csproj b/src/libraries/System.Net.Ping/src/System.Net.Ping.csproj
index 99f83f5fa17b4c..0cfd246889fa19 100644
--- a/src/libraries/System.Net.Ping/src/System.Net.Ping.csproj
+++ b/src/libraries/System.Net.Ping/src/System.Net.Ping.csproj
@@ -41,6 +41,14 @@
Link="Common\System\Net\SocketProtocolSupportPal.Unix.cs" />
+
+
+
+
@@ -48,8 +56,14 @@
Link="Common\Interop\Unix\Interop.Errors.cs" />
+
+
+
+
diff --git a/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.RawSocket.cs b/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.RawSocket.cs
index d535734a2f7753..cef4453630b651 100644
--- a/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.RawSocket.cs
+++ b/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.RawSocket.cs
@@ -102,6 +102,12 @@ private static Socket GetRawSocket(SocketConfig socketConfig)
{
// If it is not multicast, use Connect to scope responses only to the target address.
socket.Connect(socketConfig.EndPoint);
+ unsafe
+ {
+ int opt = 1;
+ // setsockopt(fd, IPPROTO_IP, IP_RECVERR, &value, sizeof(int))
+ socket.SetRawSocketOption(0, 11, new ReadOnlySpan(&opt, sizeof(int)));
+ }
}
#pragma warning restore 618
@@ -232,11 +238,12 @@ private static bool TryGetPingReply(
return true;
}
- private static PingReply SendIcmpEchoRequestOverRawSocket(IPAddress address, byte[] buffer, int timeout, PingOptions? options)
+ private static unsafe PingReply SendIcmpEchoRequestOverRawSocket(IPAddress address, byte[] buffer, int timeout, PingOptions? options)
{
SocketConfig socketConfig = GetSocketConfig(address, buffer, timeout, options);
using (Socket socket = GetRawSocket(socketConfig))
{
+ Span socketAddress = stackalloc byte[SocketAddress.GetMaximumAddressSize(address.AddressFamily)];
int ipHeaderLength = socketConfig.IsIpv4 ? MinIpHeaderLengthInBytes : 0;
try
{
@@ -270,6 +277,29 @@ private static PingReply SendIcmpEchoRequestOverRawSocket(IPAddress address, byt
{
return CreatePingReply(IPStatus.PacketTooBig);
}
+ catch (SocketException ex) when (ex.SocketErrorCode == SocketError.HostUnreachable)
+ {
+ // This happens on Linux where we explicitly subscribed to error messages
+ // We should be able to get more info by getting extended socket error from error queue.
+
+ Interop.Sys.MessageHeader header = default;
+
+ SocketError result;
+ fixed (byte* sockAddr = &MemoryMarshal.GetReference(socketAddress))
+ {
+ header.SocketAddress = sockAddr;
+ header.SocketAddressLen = socketAddress.Length;
+ header.IOVectors = null;
+ header.IOVectorCount = 0;
+
+ result = Interop.Sys.ReceiveSocketError(socket.SafeHandle, &header);
+ }
+
+ if (result == SocketError.Success && header.SocketAddressLen > 0)
+ {
+ return CreatePingReply(IPStatus.TtlExpired, IPEndPointExtensions.GetIPAddress(socketAddress.Slice(0, header.SocketAddressLen)));
+ }
+ }
// We have exceeded our timeout duration, and no reply has been received.
return CreatePingReply(IPStatus.TimedOut);
diff --git a/src/native/libs/Common/pal_config.h.in b/src/native/libs/Common/pal_config.h.in
index c4843bf8712df7..22b1d5713acf79 100644
--- a/src/native/libs/Common/pal_config.h.in
+++ b/src/native/libs/Common/pal_config.h.in
@@ -100,6 +100,7 @@
#cmakedefine01 HAVE_IOS_NET_IFMEDIA_H
#cmakedefine01 HAVE_LINUX_RTNETLINK_H
#cmakedefine01 HAVE_LINUX_CAN_H
+#cmakedefine01 HAVE_LINUX_ERRQUEUE_H
#cmakedefine01 HAVE_GETDOMAINNAME_SIZET
#cmakedefine01 HAVE_INOTIFY
#cmakedefine01 HAVE_CLOCK_MONOTONIC
diff --git a/src/native/libs/System.Native/entrypoints.c b/src/native/libs/System.Native/entrypoints.c
index ee842ee2b73648..30cc86f2aff976 100644
--- a/src/native/libs/System.Native/entrypoints.c
+++ b/src/native/libs/System.Native/entrypoints.c
@@ -160,6 +160,7 @@ static const Entry s_sysNative[] =
DllImportEntry(SystemNative_SetSendTimeout)
DllImportEntry(SystemNative_Receive)
DllImportEntry(SystemNative_ReceiveMessage)
+ DllImportEntry(SystemNative_ReceiveSocketError)
DllImportEntry(SystemNative_Send)
DllImportEntry(SystemNative_SendMessage)
DllImportEntry(SystemNative_Accept)
diff --git a/src/native/libs/System.Native/pal_networking.c b/src/native/libs/System.Native/pal_networking.c
index ee8c9a89e28acb..f8916702ef48d5 100644
--- a/src/native/libs/System.Native/pal_networking.c
+++ b/src/native/libs/System.Native/pal_networking.c
@@ -62,6 +62,10 @@
#if HAVE_SYS_FILIO_H
#include
#endif
+#if HAVE_LINUX_ERRQUEUE_H
+#include
+#endif
+
#if HAVE_KQUEUE
#if KEVENT_HAS_VOID_UDATA
@@ -1325,7 +1329,11 @@ int32_t SystemNative_SetSendTimeout(intptr_t socket, int32_t millisecondsTimeout
static int8_t ConvertSocketFlagsPalToPlatform(int32_t palFlags, int* platformFlags)
{
- const int32_t SupportedFlagsMask = SocketFlags_MSG_OOB | SocketFlags_MSG_PEEK | SocketFlags_MSG_DONTROUTE | SocketFlags_MSG_TRUNC | SocketFlags_MSG_CTRUNC;
+ const int32_t SupportedFlagsMask =
+#ifdef MSG_ERRQUEUE
+ SocketFlags_MSG_ERRQUEUE |
+#endif
+ SocketFlags_MSG_OOB | SocketFlags_MSG_PEEK | SocketFlags_MSG_DONTROUTE | SocketFlags_MSG_TRUNC | SocketFlags_MSG_CTRUNC | SocketFlags_MSG_DONTWAIT;
if ((palFlags & ~SupportedFlagsMask) != 0)
{
@@ -1335,9 +1343,15 @@ static int8_t ConvertSocketFlagsPalToPlatform(int32_t palFlags, int* platformFla
*platformFlags = ((palFlags & SocketFlags_MSG_OOB) == 0 ? 0 : MSG_OOB) |
((palFlags & SocketFlags_MSG_PEEK) == 0 ? 0 : MSG_PEEK) |
((palFlags & SocketFlags_MSG_DONTROUTE) == 0 ? 0 : MSG_DONTROUTE) |
+ ((palFlags & SocketFlags_MSG_DONTWAIT) == 0 ? 0 : MSG_DONTWAIT) |
((palFlags & SocketFlags_MSG_TRUNC) == 0 ? 0 : MSG_TRUNC) |
((palFlags & SocketFlags_MSG_CTRUNC) == 0 ? 0 : MSG_CTRUNC);
-
+#ifdef MSG_ERRQUEUE
+ if ((palFlags & SocketFlags_MSG_ERRQUEUE) != 0)
+ {
+ *platformFlags |= MSG_ERRQUEUE;
+ }
+#endif
return true;
}
@@ -1381,6 +1395,51 @@ int32_t SystemNative_Receive(intptr_t socket, void* buffer, int32_t bufferLen, i
return SystemNative_ConvertErrorPlatformToPal(errno);
}
+int32_t SystemNative_ReceiveSocketError(intptr_t socket, MessageHeader* messageHeader)
+{
+ int fd = ToFileDescriptor(socket);
+ ssize_t res;
+
+#if HAVE_LINUX_ERRQUEUE_H
+ char buffer[sizeof(struct sock_extended_err) + sizeof(struct sockaddr_storage)];
+ messageHeader->ControlBufferLen = sizeof(buffer);
+ messageHeader->ControlBuffer = (void*)buffer;
+
+ struct msghdr header;
+ ConvertMessageHeaderToMsghdr(&header, messageHeader, fd);
+
+ while ((res = recvmsg(fd, &header, SocketFlags_MSG_DONTWAIT | SocketFlags_MSG_ERRQUEUE)) < 0 && errno == EINTR);
+
+ struct cmsghdr *cmsg;
+ for (cmsg = CMSG_FIRSTHDR(&header); cmsg; cmsg = GET_CMSG_NXTHDR(&header, cmsg))
+ {
+ if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_RECVERR)
+ {
+ struct sock_extended_err *e = (struct sock_extended_err *)CMSG_DATA(cmsg);
+ if (e->ee_origin == SO_EE_ORIGIN_ICMP)
+ {
+ int size = (int)(cmsg->cmsg_len - sizeof(struct sock_extended_err));
+ messageHeader->SocketAddressLen = size < messageHeader->SocketAddressLen ? size : messageHeader->SocketAddressLen;
+ memcpy(messageHeader->SocketAddress, (struct sockaddr_in*)(e+1), (size_t)messageHeader->SocketAddressLen);
+ return Error_SUCCESS;
+ }
+ }
+ }
+#else
+ res = -1;
+ errno = ENOTSUP;
+#endif
+
+ messageHeader->SocketAddressLen = 0;
+
+ if (res != -1)
+ {
+ return Error_SUCCESS;
+ }
+
+ return SystemNative_ConvertErrorPlatformToPal(errno);
+}
+
int32_t SystemNative_ReceiveMessage(intptr_t socket, MessageHeader* messageHeader, int32_t flags, int64_t* received)
{
if (messageHeader == NULL || received == NULL || messageHeader->SocketAddressLen < 0 ||
diff --git a/src/native/libs/System.Native/pal_networking.h b/src/native/libs/System.Native/pal_networking.h
index 0a46f1490aab96..5dfe1c1c54df10 100644
--- a/src/native/libs/System.Native/pal_networking.h
+++ b/src/native/libs/System.Native/pal_networking.h
@@ -206,6 +206,8 @@ typedef enum
SocketFlags_MSG_DONTROUTE = 0x0004, // SocketFlags.DontRoute
SocketFlags_MSG_TRUNC = 0x0100, // SocketFlags.Truncated
SocketFlags_MSG_CTRUNC = 0x0200, // SocketFlags.ControlDataTruncated
+ SocketFlags_MSG_DONTWAIT = 0x1000, // used privately by Ping
+ SocketFlags_MSG_ERRQUEUE = 0x2000, // used privately by Ping
} SocketFlags;
/*
@@ -356,6 +358,8 @@ PALEXPORT int32_t SystemNative_Receive(intptr_t socket, void* buffer, int32_t bu
PALEXPORT int32_t SystemNative_ReceiveMessage(intptr_t socket, MessageHeader* messageHeader, int32_t flags, int64_t* received);
+PALEXPORT int32_t SystemNative_ReceiveSocketError(intptr_t socket, MessageHeader* messageHeader);
+
PALEXPORT int32_t SystemNative_Send(intptr_t socket, void* buffer, int32_t bufferLen, int32_t flags, int32_t* sent);
PALEXPORT int32_t SystemNative_SendMessage(intptr_t socket, MessageHeader* messageHeader, int32_t flags, int64_t* sent);
diff --git a/src/native/libs/configure.cmake b/src/native/libs/configure.cmake
index 42502a34b4ef24..55e794b42769be 100644
--- a/src/native/libs/configure.cmake
+++ b/src/native/libs/configure.cmake
@@ -500,6 +500,10 @@ check_include_files(
"sys/proc_info.h"
HAVE_SYS_PROCINFO_H)
+check_include_files(
+ "time.h;linux/errqueue.h"
+ HAVE_LINUX_ERRQUEUE_H)
+
check_symbol_exists(
epoll_create1
sys/epoll.h