From a61163c0bd0cf0beb7d515122a9b7a05a4e5186c Mon Sep 17 00:00:00 2001 From: wfurt Date: Sun, 17 Mar 2024 03:59:56 +0000 Subject: [PATCH 1/3] fix ping with TTL on Linux --- .../Unix/System.Native/Interop.IOVector.cs | 2 + .../Interop.ReceiveSocketError.cs | 15 +++++ .../src/System.Net.Ping.csproj | 15 +++++ .../Net/NetworkInformation/Ping.RawSocket.cs | 32 +++++++++- src/native/libs/Common/pal_config.h.in | 1 + src/native/libs/System.Native/entrypoints.c | 1 + .../libs/System.Native/pal_networking.c | 63 ++++++++++++++++++- .../libs/System.Native/pal_networking.h | 4 ++ src/native/libs/configure.cmake | 4 ++ 9 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Unix/System.Native/Interop.ReceiveSocketError.cs 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 9cbf1ee2c3478..b4a1ab0d6c912 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 @@ -9,8 +9,10 @@ internal static partial class Sys { internal unsafe struct IOVector { +#pragma warning disable CS0649 public byte* Base; public UIntPtr Count; +#pragma warning restore CS0649 } } } 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 0000000000000..be4888bbfb743 --- /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 99f83f5fa17b4..0cfd246889fa1 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 d535734a2f775..13e9450fedbf2 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 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 c4843bf8712df..22b1d5713acf7 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 ee842ee2b7364..30cc86f2aff97 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 ee8c9a89e28ac..cfad2d8d186aa 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 = 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 0a46f1490aab9..5dfe1c1c54df1 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 42502a34b4ef2..55e794b42769b 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 From 9eac8aabf4f3bfe68400424f812e79ab0b549b04 Mon Sep 17 00:00:00 2001 From: wfurt Date: Sun, 17 Mar 2024 17:54:13 +0000 Subject: [PATCH 2/3] feedback --- .../src/System/Net/NetworkInformation/Ping.RawSocket.cs | 2 +- src/native/libs/System.Native/pal_networking.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 13e9450fedbf2..cef4453630b65 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 @@ -279,7 +279,7 @@ private static unsafe PingReply SendIcmpEchoRequestOverRawSocket(IPAddress addre } catch (SocketException ex) when (ex.SocketErrorCode == SocketError.HostUnreachable) { - // This happens on on Linux where we explicitly subscribed to error messages + // 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; diff --git a/src/native/libs/System.Native/pal_networking.c b/src/native/libs/System.Native/pal_networking.c index cfad2d8d186aa..f8916702ef48d 100644 --- a/src/native/libs/System.Native/pal_networking.c +++ b/src/native/libs/System.Native/pal_networking.c @@ -1411,7 +1411,7 @@ int32_t SystemNative_ReceiveSocketError(intptr_t socket, MessageHeader* messageH while ((res = recvmsg(fd, &header, SocketFlags_MSG_DONTWAIT | SocketFlags_MSG_ERRQUEUE)) < 0 && errno == EINTR); struct cmsghdr *cmsg; - for (cmsg = CMSG_FIRSTHDR(&header); cmsg; cmsg = CMSG_NXTHDR(&header, cmsg)) + for (cmsg = CMSG_FIRSTHDR(&header); cmsg; cmsg = GET_CMSG_NXTHDR(&header, cmsg)) { if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_RECVERR) { From f58107bd4b141bedd350da475e8eeb1bebe05dce Mon Sep 17 00:00:00 2001 From: wfurt Date: Mon, 15 Apr 2024 11:22:19 -0700 Subject: [PATCH 3/3] feedback --- .../Common/src/Interop/Unix/System.Native/Interop.IOVector.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 b4a1ab0d6c912..a55e2ca19f0e1 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,17 +2,17 @@ // 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 { -#pragma warning disable CS0649 public byte* Base; public UIntPtr Count; -#pragma warning restore CS0649 } } }