From 953fd351ae1c5c8c11ba865b3613b63f8f774624 Mon Sep 17 00:00:00 2001 From: Tomas Weinfurt Date: Thu, 27 Jan 2022 23:03:31 -0800 Subject: [PATCH] fix ping with SendIpHeader on FreeBSD (#63531) * fix ping with SendIpHeader on FreeBSD * update to fix command line and DF * add comment --- .../Net/NetworkInformation/UnixCommandLinePing.cs | 6 ++++-- .../Net/NetworkInformation/Ping.RawSocket.cs | 15 +++++++++------ .../System/Net/NetworkInformation/Ping.Unix.cs | 2 +- .../tests/FunctionalTests/TestSettings.cs | 4 +++- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/libraries/Common/src/System/Net/NetworkInformation/UnixCommandLinePing.cs b/src/libraries/Common/src/System/Net/NetworkInformation/UnixCommandLinePing.cs index 579e69a153f045..c2fcca169d21dc 100644 --- a/src/libraries/Common/src/System/Net/NetworkInformation/UnixCommandLinePing.cs +++ b/src/libraries/Common/src/System/Net/NetworkInformation/UnixCommandLinePing.cs @@ -90,7 +90,8 @@ public static string ConstructCommandLine(int packetSize, int timeout, string ad // OSX: ping requires -W flag which accepts timeout in MILLISECONDS; ping6 doesn't support timeout if (OperatingSystem.IsFreeBSD()) { - if (ipv4) + // Syntax changed in FreeBSD 13.0 and options are not common for both address families + if (ipv4 || Environment.OSVersion.Version.Major > 12) { sb.Append(" -W "); } @@ -135,7 +136,8 @@ public static string ConstructCommandLine(int packetSize, int timeout, string ad if (OperatingSystem.IsFreeBSD() || OperatingSystem.IsMacOS()) { // OSX and FreeBSD use -h to set hop limit for IPv6 and -m ttl for IPv4 - if (ipv4) + // Syntax changed in FreeBSD 13.0 and options are not common for both address families + if (ipv4 || (OperatingSystem.IsFreeBSD() && Environment.OSVersion.Version.Major > 12)) { sb.Append(" -m "); } 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 0d81532f2bc144..94cdeb2fe890c6 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 @@ -19,6 +19,7 @@ public partial class Ping private const int MinIpHeaderLengthInBytes = 20; private const int MaxIpHeaderLengthInBytes = 60; private const int IpV6HeaderLengthInBytes = 40; + private static ushort DontFragment = OperatingSystem.IsFreeBSD() ? (ushort)IPAddress.HostToNetworkOrder((short)0x4000) : (ushort)0x4000; private unsafe SocketConfig GetSocketConfig(IPAddress address, byte[] buffer, int timeout, PingOptions? options) { @@ -29,15 +30,17 @@ private unsafe SocketConfig GetSocketConfig(IPAddress address, byte[] buffer, in bool ipv4 = address.AddressFamily == AddressFamily.InterNetwork; bool sendIpHeader = ipv4 && options != null && SendIpHeader; + int totalLength = 0; if (sendIpHeader) { iph.VersionAndLength = 0x45; + totalLength = sizeof(IpHeader) + checked(sizeof(IcmpHeader) + buffer.Length); // On OSX this strangely must be host byte order. - iph.TotalLength = (ushort)(sizeof(IpHeader) + checked(sizeof(IcmpHeader) + buffer.Length)); + iph.TotalLength = OperatingSystem.IsFreeBSD() ? (ushort)IPAddress.HostToNetworkOrder((short)totalLength) : (ushort)totalLength; iph.Protocol = 1; // ICMP iph.Ttl = (byte)options!.Ttl; - iph.Flags = (ushort)(options.DontFragment ? 0x4000 : 0); + iph.Flags = (ushort)(options.DontFragment ? DontFragment : 0); #pragma warning disable 618 iph.DestinationAddress = (uint)address.Address; #pragma warning restore 618 @@ -52,7 +55,7 @@ private unsafe SocketConfig GetSocketConfig(IPAddress address, byte[] buffer, in { Type = ipv4 ? (byte)IcmpV4MessageType.EchoRequest : (byte)IcmpV6MessageType.EchoRequest, Identifier = id, - }, buffer)); + }, buffer, totalLength)); } private Socket GetRawSocket(SocketConfig socketConfig) @@ -405,14 +408,14 @@ public SocketConfig(EndPoint endPoint, int timeout, PingOptions? options, bool i public readonly byte[] SendBuffer; } - private static unsafe byte[] CreateSendMessageBuffer(IpHeader ipHeader, IcmpHeader icmpHeader, byte[] payload) + private static unsafe byte[] CreateSendMessageBuffer(IpHeader ipHeader, IcmpHeader icmpHeader, byte[] payload, int totalLength = 0) { int icmpHeaderSize = sizeof(IcmpHeader); int offset = 0; - int packetSize = ipHeader.TotalLength != 0 ? ipHeader.TotalLength : checked(icmpHeaderSize + payload.Length); + int packetSize = totalLength != 0 ? totalLength : checked(icmpHeaderSize + payload.Length); byte[] result = new byte[packetSize]; - if (ipHeader.TotalLength != 0) + if (totalLength != 0) { int ipHeaderSize = sizeof(IpHeader); new Span(&ipHeader, sizeof(IpHeader)).CopyTo(result); diff --git a/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.Unix.cs b/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.Unix.cs index 3897853f6d856f..987d0462d70cae 100644 --- a/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.Unix.cs +++ b/src/libraries/System.Net.Ping/src/System/Net/NetworkInformation/Ping.Unix.cs @@ -15,7 +15,7 @@ namespace System.Net.NetworkInformation { public partial class Ping { - private static bool SendIpHeader => false; + private static bool SendIpHeader => OperatingSystem.IsFreeBSD(); private static bool NeedsConnect => OperatingSystem.IsLinux(); private static bool SupportsDualMode => true; diff --git a/src/libraries/System.Net.Ping/tests/FunctionalTests/TestSettings.cs b/src/libraries/System.Net.Ping/tests/FunctionalTests/TestSettings.cs index a36d4f93deb02b..8a87d2f0111e67 100644 --- a/src/libraries/System.Net.Ping/tests/FunctionalTests/TestSettings.cs +++ b/src/libraries/System.Net.Ping/tests/FunctionalTests/TestSettings.cs @@ -14,7 +14,9 @@ internal static class TestSettings public const int PingTimeout = 10 * 1000; public const string PayloadAsString = "'Post hoc ergo propter hoc'. 'After it, therefore because of it'. It means one thing follows the other, therefore it was caused by the other. But it's not always true. In fact it's hardly ever true."; - public static readonly byte[] PayloadAsBytes = Encoding.UTF8.GetBytes(TestSettings.PayloadAsString); + + // By default, FreeBSD supports buffer only up to 56 bytes + public static readonly byte[] PayloadAsBytes = Encoding.UTF8.GetBytes(OperatingSystem.IsFreeBSD() ? TestSettings.PayloadAsString.Substring(0, 55) : TestSettings.PayloadAsString); public static readonly byte[] PayloadAsBytesShort = Encoding.UTF8.GetBytes("ABCDEF0123456789");