Skip to content

Commit 1e42214

Browse files
authored
fix ping with TTL on Linux (#99875)
* fix ping with TTL on Linux * feedback * feedback
1 parent 92b9dca commit 1e42214

File tree

9 files changed

+134
-3
lines changed

9 files changed

+134
-3
lines changed

src/libraries/Common/src/Interop/Unix/System.Native/Interop.IOVector.cs

+2
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5+
using System.Runtime.InteropServices;
56

67
internal static partial class Interop
78
{
89
internal static partial class Sys
910
{
11+
[StructLayout(LayoutKind.Sequential)]
1012
internal unsafe struct IOVector
1113
{
1214
public byte* Base;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Net.Sockets;
6+
using System.Runtime.InteropServices;
7+
8+
internal static partial class Interop
9+
{
10+
internal static partial class Sys
11+
{
12+
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_ReceiveSocketError")]
13+
internal static unsafe partial SocketError ReceiveSocketError(SafeHandle socket, MessageHeader* messageHeader);
14+
}
15+
}

src/libraries/System.Net.Ping/src/System.Net.Ping.csproj

+15
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,29 @@
4141
Link="Common\System\Net\SocketProtocolSupportPal.Unix.cs" />
4242
<Compile Include="$(CommonPath)System\Net\NetworkInformation\UnixCommandLinePing.cs"
4343
Link="Common\System\Net\NetworkInformation\UnixCommandLinePing.cs" />
44+
<Compile Include="$(CommonPath)System\Net\IPEndPointExtensions.cs"
45+
Link="Common\System\Net\IPEndPointExtensions.cs" />
46+
<Compile Include="$(CommonPath)System\Net\SocketAddressPal.Unix.cs"
47+
Link="Common\System\Net\SocketAddressPal.Unix.cs" />
48+
<Compile Include="$(CommonPath)System\Net\IPAddressParserStatics.cs"
49+
Link="Common\System\Net\IPAddressParserStatics.cs" />
50+
<Compile Include="$(CommonPath)System\Net\Sockets\SocketErrorPal.Unix.cs"
51+
Link="Common\System\Net\Sockets\SocketErrorPal.Unix" />
4452
<!-- Interop -->
4553
<Compile Include="$(CommonPath)Interop\Unix\Interop.DefaultPathBufferSize.cs"
4654
Link="Common\Interop\Unix\Interop.DefaultPathBufferSize.cs" />
4755
<Compile Include="$(CommonPath)Interop\Unix\Interop.Errors.cs"
4856
Link="Common\Interop\Unix\Interop.Errors.cs" />
4957
<Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs"
5058
Link="Common\Interop\Unix\Interop.Libraries.cs" />
59+
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.IOVector.cs"
60+
Link="Common\Interop\Unix\System.Native\Interop.IOVector.cs" />
61+
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.ReceiveSocketError.cs"
62+
Link="Common\Interop\Unix\System.Native\Interop.ReceiveSocketError.cs" />
5163
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Close.cs"
5264
Link="Common\Interop\Unix\System.Native\Interop.Close.cs" />
65+
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.MessageHeader.cs"
66+
Link="Common\Interop\Unix\System.Native\Interop.MessageHeader.cs" />
5367
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Socket.cs"
5468
Link="Common\Interop\Unix\System.Native\Interop.Socket.cs" />
5569
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.SocketAddress.cs"
@@ -99,6 +113,7 @@
99113

100114
<ItemGroup>
101115
<Reference Include="Microsoft.Win32.Primitives" />
116+
<Reference Include="System.Collections" />
102117
<Reference Include="System.ComponentModel.EventBasedAsync" />
103118
<Reference Include="System.ComponentModel.Primitives" />
104119
<Reference Include="System.Diagnostics.Tracing" />

src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.RawSocket.cs

+31-1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ private static Socket GetRawSocket(SocketConfig socketConfig)
102102
{
103103
// If it is not multicast, use Connect to scope responses only to the target address.
104104
socket.Connect(socketConfig.EndPoint);
105+
unsafe
106+
{
107+
int opt = 1;
108+
// setsockopt(fd, IPPROTO_IP, IP_RECVERR, &value, sizeof(int))
109+
socket.SetRawSocketOption(0, 11, new ReadOnlySpan<byte>(&opt, sizeof(int)));
110+
}
105111
}
106112
#pragma warning restore 618
107113

@@ -232,11 +238,12 @@ private static bool TryGetPingReply(
232238
return true;
233239
}
234240

235-
private static PingReply SendIcmpEchoRequestOverRawSocket(IPAddress address, byte[] buffer, int timeout, PingOptions? options)
241+
private static unsafe PingReply SendIcmpEchoRequestOverRawSocket(IPAddress address, byte[] buffer, int timeout, PingOptions? options)
236242
{
237243
SocketConfig socketConfig = GetSocketConfig(address, buffer, timeout, options);
238244
using (Socket socket = GetRawSocket(socketConfig))
239245
{
246+
Span<byte> socketAddress = stackalloc byte[SocketAddress.GetMaximumAddressSize(address.AddressFamily)];
240247
int ipHeaderLength = socketConfig.IsIpv4 ? MinIpHeaderLengthInBytes : 0;
241248
try
242249
{
@@ -270,6 +277,29 @@ private static PingReply SendIcmpEchoRequestOverRawSocket(IPAddress address, byt
270277
{
271278
return CreatePingReply(IPStatus.PacketTooBig);
272279
}
280+
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.HostUnreachable)
281+
{
282+
// This happens on Linux where we explicitly subscribed to error messages
283+
// We should be able to get more info by getting extended socket error from error queue.
284+
285+
Interop.Sys.MessageHeader header = default;
286+
287+
SocketError result;
288+
fixed (byte* sockAddr = &MemoryMarshal.GetReference(socketAddress))
289+
{
290+
header.SocketAddress = sockAddr;
291+
header.SocketAddressLen = socketAddress.Length;
292+
header.IOVectors = null;
293+
header.IOVectorCount = 0;
294+
295+
result = Interop.Sys.ReceiveSocketError(socket.SafeHandle, &header);
296+
}
297+
298+
if (result == SocketError.Success && header.SocketAddressLen > 0)
299+
{
300+
return CreatePingReply(IPStatus.TtlExpired, IPEndPointExtensions.GetIPAddress(socketAddress.Slice(0, header.SocketAddressLen)));
301+
}
302+
}
273303

274304
// We have exceeded our timeout duration, and no reply has been received.
275305
return CreatePingReply(IPStatus.TimedOut);

src/native/libs/Common/pal_config.h.in

+1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
#cmakedefine01 HAVE_IOS_NET_IFMEDIA_H
101101
#cmakedefine01 HAVE_LINUX_RTNETLINK_H
102102
#cmakedefine01 HAVE_LINUX_CAN_H
103+
#cmakedefine01 HAVE_LINUX_ERRQUEUE_H
103104
#cmakedefine01 HAVE_GETDOMAINNAME_SIZET
104105
#cmakedefine01 HAVE_INOTIFY
105106
#cmakedefine01 HAVE_CLOCK_MONOTONIC

src/native/libs/System.Native/entrypoints.c

+1
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ static const Entry s_sysNative[] =
160160
DllImportEntry(SystemNative_SetSendTimeout)
161161
DllImportEntry(SystemNative_Receive)
162162
DllImportEntry(SystemNative_ReceiveMessage)
163+
DllImportEntry(SystemNative_ReceiveSocketError)
163164
DllImportEntry(SystemNative_Send)
164165
DllImportEntry(SystemNative_SendMessage)
165166
DllImportEntry(SystemNative_Accept)

src/native/libs/System.Native/pal_networking.c

+61-2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@
6262
#if HAVE_SYS_FILIO_H
6363
#include <sys/filio.h>
6464
#endif
65+
#if HAVE_LINUX_ERRQUEUE_H
66+
#include <linux/errqueue.h>
67+
#endif
68+
6569

6670
#if HAVE_KQUEUE
6771
#if KEVENT_HAS_VOID_UDATA
@@ -1325,7 +1329,11 @@ int32_t SystemNative_SetSendTimeout(intptr_t socket, int32_t millisecondsTimeout
13251329

13261330
static int8_t ConvertSocketFlagsPalToPlatform(int32_t palFlags, int* platformFlags)
13271331
{
1328-
const int32_t SupportedFlagsMask = SocketFlags_MSG_OOB | SocketFlags_MSG_PEEK | SocketFlags_MSG_DONTROUTE | SocketFlags_MSG_TRUNC | SocketFlags_MSG_CTRUNC;
1332+
const int32_t SupportedFlagsMask =
1333+
#ifdef MSG_ERRQUEUE
1334+
SocketFlags_MSG_ERRQUEUE |
1335+
#endif
1336+
SocketFlags_MSG_OOB | SocketFlags_MSG_PEEK | SocketFlags_MSG_DONTROUTE | SocketFlags_MSG_TRUNC | SocketFlags_MSG_CTRUNC | SocketFlags_MSG_DONTWAIT;
13291337

13301338
if ((palFlags & ~SupportedFlagsMask) != 0)
13311339
{
@@ -1335,9 +1343,15 @@ static int8_t ConvertSocketFlagsPalToPlatform(int32_t palFlags, int* platformFla
13351343
*platformFlags = ((palFlags & SocketFlags_MSG_OOB) == 0 ? 0 : MSG_OOB) |
13361344
((palFlags & SocketFlags_MSG_PEEK) == 0 ? 0 : MSG_PEEK) |
13371345
((palFlags & SocketFlags_MSG_DONTROUTE) == 0 ? 0 : MSG_DONTROUTE) |
1346+
((palFlags & SocketFlags_MSG_DONTWAIT) == 0 ? 0 : MSG_DONTWAIT) |
13381347
((palFlags & SocketFlags_MSG_TRUNC) == 0 ? 0 : MSG_TRUNC) |
13391348
((palFlags & SocketFlags_MSG_CTRUNC) == 0 ? 0 : MSG_CTRUNC);
1340-
1349+
#ifdef MSG_ERRQUEUE
1350+
if ((palFlags & SocketFlags_MSG_ERRQUEUE) != 0)
1351+
{
1352+
*platformFlags |= MSG_ERRQUEUE;
1353+
}
1354+
#endif
13411355
return true;
13421356
}
13431357

@@ -1381,6 +1395,51 @@ int32_t SystemNative_Receive(intptr_t socket, void* buffer, int32_t bufferLen, i
13811395
return SystemNative_ConvertErrorPlatformToPal(errno);
13821396
}
13831397

1398+
int32_t SystemNative_ReceiveSocketError(intptr_t socket, MessageHeader* messageHeader)
1399+
{
1400+
int fd = ToFileDescriptor(socket);
1401+
ssize_t res;
1402+
1403+
#if HAVE_LINUX_ERRQUEUE_H
1404+
char buffer[sizeof(struct sock_extended_err) + sizeof(struct sockaddr_storage)];
1405+
messageHeader->ControlBufferLen = sizeof(buffer);
1406+
messageHeader->ControlBuffer = (void*)buffer;
1407+
1408+
struct msghdr header;
1409+
ConvertMessageHeaderToMsghdr(&header, messageHeader, fd);
1410+
1411+
while ((res = recvmsg(fd, &header, SocketFlags_MSG_DONTWAIT | SocketFlags_MSG_ERRQUEUE)) < 0 && errno == EINTR);
1412+
1413+
struct cmsghdr *cmsg;
1414+
for (cmsg = CMSG_FIRSTHDR(&header); cmsg; cmsg = GET_CMSG_NXTHDR(&header, cmsg))
1415+
{
1416+
if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_RECVERR)
1417+
{
1418+
struct sock_extended_err *e = (struct sock_extended_err *)CMSG_DATA(cmsg);
1419+
if (e->ee_origin == SO_EE_ORIGIN_ICMP)
1420+
{
1421+
int size = (int)(cmsg->cmsg_len - sizeof(struct sock_extended_err));
1422+
messageHeader->SocketAddressLen = size < messageHeader->SocketAddressLen ? size : messageHeader->SocketAddressLen;
1423+
memcpy(messageHeader->SocketAddress, (struct sockaddr_in*)(e+1), (size_t)messageHeader->SocketAddressLen);
1424+
return Error_SUCCESS;
1425+
}
1426+
}
1427+
}
1428+
#else
1429+
res = -1;
1430+
errno = ENOTSUP;
1431+
#endif
1432+
1433+
messageHeader->SocketAddressLen = 0;
1434+
1435+
if (res != -1)
1436+
{
1437+
return Error_SUCCESS;
1438+
}
1439+
1440+
return SystemNative_ConvertErrorPlatformToPal(errno);
1441+
}
1442+
13841443
int32_t SystemNative_ReceiveMessage(intptr_t socket, MessageHeader* messageHeader, int32_t flags, int64_t* received)
13851444
{
13861445
if (messageHeader == NULL || received == NULL || messageHeader->SocketAddressLen < 0 ||

src/native/libs/System.Native/pal_networking.h

+4
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,8 @@ typedef enum
206206
SocketFlags_MSG_DONTROUTE = 0x0004, // SocketFlags.DontRoute
207207
SocketFlags_MSG_TRUNC = 0x0100, // SocketFlags.Truncated
208208
SocketFlags_MSG_CTRUNC = 0x0200, // SocketFlags.ControlDataTruncated
209+
SocketFlags_MSG_DONTWAIT = 0x1000, // used privately by Ping
210+
SocketFlags_MSG_ERRQUEUE = 0x2000, // used privately by Ping
209211
} SocketFlags;
210212

211213
/*
@@ -356,6 +358,8 @@ PALEXPORT int32_t SystemNative_Receive(intptr_t socket, void* buffer, int32_t bu
356358

357359
PALEXPORT int32_t SystemNative_ReceiveMessage(intptr_t socket, MessageHeader* messageHeader, int32_t flags, int64_t* received);
358360

361+
PALEXPORT int32_t SystemNative_ReceiveSocketError(intptr_t socket, MessageHeader* messageHeader);
362+
359363
PALEXPORT int32_t SystemNative_Send(intptr_t socket, void* buffer, int32_t bufferLen, int32_t flags, int32_t* sent);
360364

361365
PALEXPORT int32_t SystemNative_SendMessage(intptr_t socket, MessageHeader* messageHeader, int32_t flags, int64_t* sent);

src/native/libs/configure.cmake

+4
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,10 @@ check_include_files(
500500
"sys/proc_info.h"
501501
HAVE_SYS_PROCINFO_H)
502502

503+
check_include_files(
504+
"time.h;linux/errqueue.h"
505+
HAVE_LINUX_ERRQUEUE_H)
506+
503507
check_symbol_exists(
504508
epoll_create1
505509
sys/epoll.h

0 commit comments

Comments
 (0)