2 // System.Net.NetworkInformation.Ping
5 // Gonzalo Paniagua Javier (gonzalo@novell.com)
6 // Atsushi Enomoto (atsushi@ximian.com)
8 // Copyright (c) 2006-2007 Novell, Inc. (http://www.novell.com)
9 // Copyright 2015 Xamarin Inc.
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 using System.Diagnostics;
35 using System.Globalization;
36 using System.ComponentModel;
37 using System.Net.Sockets;
38 using System.Security.Principal;
39 using System.Security.Cryptography;
40 using System.Runtime.InteropServices;
41 using System.Threading;
42 using System.Threading.Tasks;
44 namespace System.Net.NetworkInformation {
45 [MonoTODO ("IPv6 support is missing")]
46 public class Ping : Component, IDisposable
49 [StructLayout(LayoutKind.Sequential)]
50 struct cap_user_header_t
52 public UInt32 version;
56 [StructLayout(LayoutKind.Sequential)]
57 struct cap_user_data_t
59 public UInt32 effective;
60 public UInt32 permitted;
61 public UInt32 inheritable;
64 const int DefaultCount = 1;
65 static readonly string [] PingBinPaths = new string [] {
73 static readonly string PingBinPath;
74 static bool canSendPrivileged;
76 const int default_timeout = 4000; // 4 sec.
79 // This value is correct as of Linux kernel version 2.6.25.9
80 // See /usr/include/linux/capability.h
81 const UInt32 linux_cap_version = 0x20071026;
83 static readonly byte [] default_buffer = new byte [0];
86 BackgroundWorker worker;
87 object user_async_state;
88 CancellationTokenSource cts;
90 public event PingCompletedEventHandler PingCompleted;
92 #if !MONOTOUCH && !ORBIS
95 if (Environment.OSVersion.Platform == PlatformID.Unix) {
96 CheckLinuxCapabilities ();
97 if (!canSendPrivileged && WindowsIdentity.GetCurrent ().Name == "root")
98 canSendPrivileged = true;
100 // Since different Unix systems can have different path to bin, we try some
101 // of the known ones.
102 foreach (string ping_path in PingBinPaths)
103 if (File.Exists (ping_path)) {
104 PingBinPath = ping_path;
109 canSendPrivileged = true;
111 if (PingBinPath == null)
112 PingBinPath = "/bin/ping"; // default, fallback value
118 // Generate a new random 16 bit identifier for every ping
119 RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider ();
120 byte [] randomIdentifier = new byte [2];
121 rng.GetBytes (randomIdentifier);
122 identifier = (ushort)(randomIdentifier [0] + (randomIdentifier [1] << 8));
125 #if !MONOTOUCH && !ORBIS
126 [DllImport ("libc", EntryPoint="capget")]
127 static extern int capget (ref cap_user_header_t header, ref cap_user_data_t data);
129 static void CheckLinuxCapabilities ()
132 cap_user_header_t header = new cap_user_header_t ();
133 cap_user_data_t data = new cap_user_data_t ();
135 header.version = linux_cap_version;
140 ret = capget (ref header, ref data);
141 } catch (Exception) {
147 canSendPrivileged = (data.effective & (1 << 13)) != 0;
149 canSendPrivileged = false;
154 void IDisposable.Dispose ()
158 protected void OnPingCompleted (PingCompletedEventArgs e)
160 user_async_state = null;
168 if (PingCompleted != null)
169 PingCompleted (this, e);
174 public PingReply Send (IPAddress address)
176 return Send (address, default_timeout);
179 public PingReply Send (IPAddress address, int timeout)
181 return Send (address, timeout, default_buffer);
184 public PingReply Send (IPAddress address, int timeout, byte [] buffer)
186 return Send (address, timeout, buffer, new PingOptions ());
189 public PingReply Send (string hostNameOrAddress)
191 return Send (hostNameOrAddress, default_timeout);
194 public PingReply Send (string hostNameOrAddress, int timeout)
196 return Send (hostNameOrAddress, timeout, default_buffer);
199 public PingReply Send (string hostNameOrAddress, int timeout, byte [] buffer)
201 return Send (hostNameOrAddress, timeout, buffer, new PingOptions ());
204 public PingReply Send (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options)
206 IPAddress [] addresses = Dns.GetHostAddresses (hostNameOrAddress);
207 return Send (addresses [0], timeout, buffer, options);
210 public PingReply Send (IPAddress address, int timeout, byte [] buffer, PingOptions options)
213 throw new ArgumentNullException ("address");
215 throw new ArgumentOutOfRangeException ("timeout", "timeout must be non-negative integer");
217 throw new ArgumentNullException ("buffer");
218 if (buffer.Length > 65500)
219 throw new ArgumentException ("buffer");
220 // options can be null.
223 throw new InvalidOperationException ();
225 if (canSendPrivileged)
226 return SendPrivileged (address, timeout, buffer, options);
227 return SendUnprivileged (address, timeout, buffer, options);
232 private PingReply SendPrivileged (IPAddress address, int timeout, byte [] buffer, PingOptions options)
234 IPEndPoint target = new IPEndPoint (address, 0);
236 // FIXME: support IPv6
237 using (Socket s = new Socket (AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp)) {
238 if (options != null) {
239 s.DontFragment = options.DontFragment;
240 s.Ttl = (short) options.Ttl;
242 s.SendTimeout = timeout;
243 s.ReceiveTimeout = timeout;
244 // not sure why Identifier = 0 is unacceptable ...
245 IcmpMessage send = new IcmpMessage (8, 0, identifier, 0, buffer);
246 byte [] bytes = send.GetBytes ();
247 s.SendBufferSize = bytes.Length;
248 s.SendTo (bytes, bytes.Length, SocketFlags.None, target);
250 DateTime sentTime = DateTime.Now;
253 bytes = new byte [100];
255 EndPoint endpoint = target;
256 SocketError error = 0;
257 int rc = s.ReceiveFrom (bytes, 0, 100, SocketFlags.None,
258 ref endpoint, out error);
260 if (error != SocketError.Success) {
261 if (error == SocketError.TimedOut) {
262 return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
264 throw new NotSupportedException (String.Format ("Unexpected socket error during ping request: {0}", error));
266 long rtt = (long) (DateTime.Now - sentTime).TotalMilliseconds;
267 int headerLength = (bytes [0] & 0xF) << 2;
268 int bodyLength = rc - headerLength;
270 // Ping reply to different request. discard it.
271 if (!((IPEndPoint) endpoint).Address.Equals (target.Address)) {
272 long t = timeout - rtt;
274 return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
275 s.ReceiveTimeout = (int) t;
279 IcmpMessage recv = new IcmpMessage (bytes, headerLength, bodyLength);
281 /* discard ping reply to different request or echo requests if running on same host. */
282 if (recv.Identifier != identifier || recv.Type == 8) {
283 long t = timeout - rtt;
285 return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
286 s.ReceiveTimeout = (int) t;
290 return new PingReply (address, recv.Data, options, rtt, recv.IPStatus);
295 private PingReply SendUnprivileged (IPAddress address, int timeout, byte [] buffer, PingOptions options)
297 #if MONO_FEATURE_PROCESS_START
298 DateTime sentTime = DateTime.UtcNow;
300 Process ping = new Process ();
301 string args = BuildPingArgs (address, timeout, options);
304 ping.StartInfo.FileName = PingBinPath;
305 ping.StartInfo.Arguments = args;
307 ping.StartInfo.CreateNoWindow = true;
308 ping.StartInfo.UseShellExecute = false;
310 ping.StartInfo.RedirectStandardOutput = true;
311 ping.StartInfo.RedirectStandardError = true;
313 IPStatus status = IPStatus.Unknown;
317 #pragma warning disable 219
318 string stdout = ping.StandardOutput.ReadToEnd ();
319 string stderr = ping.StandardError.ReadToEnd ();
320 #pragma warning restore 219
322 trip_time = (long) (DateTime.UtcNow - sentTime).TotalMilliseconds;
323 if (!ping.WaitForExit (timeout) || (ping.HasExited && ping.ExitCode == 2))
324 status = IPStatus.TimedOut;
325 else if (ping.ExitCode == 0)
326 status = IPStatus.Success;
327 else if (ping.ExitCode == 1)
328 status = IPStatus.TtlExpired;
336 return new PingReply (address, buffer, options, trip_time, status);
338 throw new PlatformNotSupportedException ("Ping is not supported on this platform.");
339 #endif // MONO_FEATURE_PROCESS_START
345 public void SendAsync (IPAddress address, int timeout, byte [] buffer, object userToken)
347 SendAsync (address, default_timeout, default_buffer, new PingOptions (), userToken);
350 public void SendAsync (IPAddress address, int timeout, object userToken)
352 SendAsync (address, default_timeout, default_buffer, userToken);
355 public void SendAsync (IPAddress address, object userToken)
357 SendAsync (address, default_timeout, userToken);
360 public void SendAsync (string hostNameOrAddress, int timeout, byte [] buffer, object userToken)
362 SendAsync (hostNameOrAddress, timeout, buffer, new PingOptions (), userToken);
365 public void SendAsync (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options, object userToken)
367 IPAddress address = Dns.GetHostEntry (hostNameOrAddress).AddressList [0];
368 SendAsync (address, timeout, buffer, options, userToken);
371 public void SendAsync (string hostNameOrAddress, int timeout, object userToken)
373 SendAsync (hostNameOrAddress, timeout, default_buffer, userToken);
376 public void SendAsync (string hostNameOrAddress, object userToken)
378 SendAsync (hostNameOrAddress, default_timeout, userToken);
381 public void SendAsync (IPAddress address, int timeout, byte [] buffer, PingOptions options, object userToken)
383 if ((worker != null) || (cts != null))
384 throw new InvalidOperationException ("Another SendAsync operation is in progress");
386 worker = new BackgroundWorker ();
387 worker.DoWork += delegate (object o, DoWorkEventArgs ea) {
389 user_async_state = ea.Argument;
390 ea.Result = Send (address, timeout, buffer, options);
391 } catch (Exception ex) {
395 worker.WorkerSupportsCancellation = true;
396 worker.RunWorkerCompleted += delegate (object o, RunWorkerCompletedEventArgs ea) {
397 // Note that RunWorkerCompletedEventArgs.UserState cannot be used (LAMESPEC)
398 OnPingCompleted (new PingCompletedEventArgs (ea.Error, ea.Cancelled, user_async_state, ea.Result as PingReply));
400 worker.RunWorkerAsync (userToken);
405 public void SendAsyncCancel ()
413 throw new InvalidOperationException ("SendAsync operation is not in progress");
414 worker.CancelAsync ();
425 public IcmpMessage (byte [] bytes, int offset, int size)
427 this.bytes = new byte [size];
428 Buffer.BlockCopy (bytes, offset, this.bytes, 0, size);
432 public IcmpMessage (byte type, byte code, ushort identifier, ushort sequence, byte [] data)
434 bytes = new byte [data.Length + 8];
437 bytes [4] = (byte) (identifier & 0xFF);
438 bytes [5] = (byte) ((int) identifier >> 8);
439 bytes [6] = (byte) (sequence & 0xFF);
440 bytes [7] = (byte) ((int) sequence >> 8);
441 Buffer.BlockCopy (data, 0, bytes, 8, data.Length);
443 ushort checksum = ComputeChecksum (bytes);
444 bytes [2] = (byte) (checksum & 0xFF);
445 bytes [3] = (byte) ((int) checksum >> 8);
449 get { return bytes [0]; }
453 get { return bytes [1]; }
456 public ushort Identifier {
457 get { return (ushort) (bytes [4] + (bytes [5] << 8)); }
460 public ushort Sequence {
461 get { return (ushort) (bytes [6] + (bytes [7] << 8)); }
464 public byte [] Data {
466 byte [] data = new byte [bytes.Length - 8];
467 Buffer.BlockCopy (bytes, 8, data, 0, data.Length);
472 public byte [] GetBytes ()
477 static ushort ComputeChecksum (byte [] data)
480 for (int i = 0; i < data.Length; i += 2) {
481 ushort us = i + 1 < data.Length ? data [i + 1] : (byte) 0;
486 ret = (ret >> 16) + (ret & 0xFFFF);
487 return (ushort) ~ ret;
490 public IPStatus IPStatus {
494 return IPStatus.Success;
495 case 3: // destination unreacheable
498 return IPStatus.DestinationNetworkUnreachable;
500 return IPStatus.DestinationHostUnreachable;
502 return IPStatus.DestinationProtocolUnreachable;
504 return IPStatus.DestinationPortUnreachable;
506 return IPStatus.BadOption; // FIXME: likely wrong
508 return IPStatus.BadRoute; // not sure if it is correct
514 return IPStatus.TimeExceeded;
516 return IPStatus.TtlReassemblyTimeExceeded;
520 return IPStatus.ParameterProblem;
522 return IPStatus.SourceQuench;
524 return IPStatus.Success;
526 return IPStatus.Unknown;
527 //throw new NotSupportedException (String.Format ("Unexpected pair of ICMP message type and code: type is {0} and code is {1}", Type, Code));
532 private string BuildPingArgs (IPAddress address, int timeout, PingOptions options)
534 CultureInfo culture = CultureInfo.InvariantCulture;
535 StringBuilder args = new StringBuilder ();
536 uint t = Convert.ToUInt32 (Math.Floor ((timeout + 1000) / 1000.0));
537 bool is_mac = Platform.IsMacOS;
539 args.AppendFormat (culture, "-q -n -c {0} -w {1} -t {2} -M ", DefaultCount, t, options.Ttl);
541 args.AppendFormat (culture, "-q -n -c {0} -t {1} -o -m {2} ", DefaultCount, t, options.Ttl);
543 args.Append (options.DontFragment ? "do " : "dont ");
544 else if (options.DontFragment)
547 args.Append (address.ToString ());
549 return args.ToString ();
553 public Task<PingReply> SendPingAsync (IPAddress address, int timeout, byte [] buffer)
555 return SendPingAsync (address, default_timeout, default_buffer, new PingOptions ());
558 public Task<PingReply> SendPingAsync (IPAddress address, int timeout)
560 return SendPingAsync (address, default_timeout, default_buffer);
563 public Task<PingReply> SendPingAsync (IPAddress address)
565 return SendPingAsync (address, default_timeout);
568 public Task<PingReply> SendPingAsync (string hostNameOrAddress, int timeout, byte [] buffer)
570 return SendPingAsync (hostNameOrAddress, timeout, buffer, new PingOptions ());
573 public Task<PingReply> SendPingAsync (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options)
575 IPAddress address = Dns.GetHostEntry (hostNameOrAddress).AddressList [0];
576 return SendPingAsync (address, timeout, buffer, options);
579 public Task<PingReply> SendPingAsync (string hostNameOrAddress, int timeout)
581 return SendPingAsync (hostNameOrAddress, timeout, default_buffer);
584 public Task<PingReply> SendPingAsync (string hostNameOrAddress)
586 return SendPingAsync (hostNameOrAddress, default_timeout);
589 public Task<PingReply> SendPingAsync (IPAddress address, int timeout, byte [] buffer, PingOptions options)
591 if ((worker != null) || (cts != null))
592 throw new InvalidOperationException ("Another SendAsync operation is in progress");
594 cts = new CancellationTokenSource();
596 var task = Task<PingReply>.Factory.StartNew (
597 () => Send (address, timeout, buffer, options), cts.Token);
599 task.ContinueWith ((t) => {
601 OnPingCompleted (new PingCompletedEventArgs (null, true, null, null));
602 else if (t.IsFaulted)
603 OnPingCompleted (new PingCompletedEventArgs (t.Exception, false, null, null));
605 OnPingCompleted (new PingCompletedEventArgs (null, false, null, t.Result));