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)
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.Diagnostics;
34 using System.Globalization;
35 using System.ComponentModel;
36 using System.Net.Sockets;
37 using System.Security.Principal;
38 using System.Security.Cryptography;
39 using System.Runtime.InteropServices;
41 namespace System.Net.NetworkInformation {
42 [MonoTODO ("IPv6 support is missing")]
43 public class Ping : Component, IDisposable
45 [StructLayout(LayoutKind.Sequential)]
46 struct cap_user_header_t
48 public UInt32 version;
52 [StructLayout(LayoutKind.Sequential)]
53 struct cap_user_data_t
55 public UInt32 effective;
56 public UInt32 permitted;
57 public UInt32 inheritable;
60 const int DefaultCount = 1;
61 static readonly string [] PingBinPaths = new string [] {
66 static readonly string PingBinPath;
67 const int default_timeout = 4000; // 4 sec.
70 // This value is correct as of Linux kernel version 2.6.25.9
71 // See /usr/include/linux/capability.h
72 const UInt32 linux_cap_version = 0x20071026;
74 static readonly byte [] default_buffer = new byte [0];
75 static bool canSendPrivileged;
78 BackgroundWorker worker;
79 object user_async_state;
81 public event PingCompletedEventHandler PingCompleted;
85 if (Environment.OSVersion.Platform == PlatformID.Unix) {
86 CheckLinuxCapabilities ();
87 if (!canSendPrivileged && WindowsIdentity.GetCurrent ().Name == "root")
88 canSendPrivileged = true;
90 // Since different Unix systems can have different path to bin, we try some
92 foreach (string ping_path in PingBinPaths)
93 if (File.Exists (ping_path)) {
94 PingBinPath = ping_path;
99 canSendPrivileged = true;
101 if (PingBinPath == null)
102 PingBinPath = "/bin/ping"; // default, fallback value
107 // Generate a new random 16 bit identifier for every ping
108 RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider ();
109 byte [] randomIdentifier = new byte [2];
110 rng.GetBytes (randomIdentifier);
111 identifier = (ushort)(randomIdentifier [0] + (randomIdentifier [1] << 8));
114 [DllImport ("libc", EntryPoint="capget")]
115 static extern int capget (ref cap_user_header_t header, ref cap_user_data_t data);
117 static void CheckLinuxCapabilities ()
120 cap_user_header_t header = new cap_user_header_t ();
121 cap_user_data_t data = new cap_user_data_t ();
123 header.version = linux_cap_version;
128 ret = capget (ref header, ref data);
129 } catch (Exception) {
135 canSendPrivileged = (data.effective & (1 << 13)) != 0;
137 canSendPrivileged = false;
141 void IDisposable.Dispose ()
145 protected void OnPingCompleted (PingCompletedEventArgs e)
147 if (PingCompleted != null)
148 PingCompleted (this, e);
149 user_async_state = null;
155 public PingReply Send (IPAddress address)
157 return Send (address, default_timeout);
160 public PingReply Send (IPAddress address, int timeout)
162 return Send (address, timeout, default_buffer);
165 public PingReply Send (IPAddress address, int timeout, byte [] buffer)
167 return Send (address, timeout, buffer, new PingOptions ());
170 public PingReply Send (string hostNameOrAddress)
172 return Send (hostNameOrAddress, default_timeout);
175 public PingReply Send (string hostNameOrAddress, int timeout)
177 return Send (hostNameOrAddress, timeout, default_buffer);
180 public PingReply Send (string hostNameOrAddress, int timeout, byte [] buffer)
182 return Send (hostNameOrAddress, timeout, buffer, new PingOptions ());
185 public PingReply Send (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options)
187 IPAddress [] addresses = Dns.GetHostAddresses (hostNameOrAddress);
188 return Send (addresses [0], timeout, buffer, options);
191 static IPAddress GetNonLoopbackIP ()
193 foreach (IPAddress addr in Dns.GetHostByName (Dns.GetHostName ()).AddressList)
194 if (!IPAddress.IsLoopback (addr))
196 throw new InvalidOperationException ("Could not resolve non-loopback IP address for localhost");
199 public PingReply Send (IPAddress address, int timeout, byte [] buffer, PingOptions options)
202 throw new ArgumentNullException ("address");
204 throw new ArgumentOutOfRangeException ("timeout", "timeout must be non-negative integer");
206 throw new ArgumentNullException ("buffer");
207 if (buffer.Length > 65500)
208 throw new ArgumentException ("buffer");
209 // options can be null.
211 if (canSendPrivileged)
212 return SendPrivileged (address, timeout, buffer, options);
213 return SendUnprivileged (address, timeout, buffer, options);
216 private PingReply SendPrivileged (IPAddress address, int timeout, byte [] buffer, PingOptions options)
218 IPEndPoint target = new IPEndPoint (address, 0);
219 IPEndPoint client = new IPEndPoint (GetNonLoopbackIP (), 0);
221 // FIXME: support IPv6
222 using (Socket s = new Socket (AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp)) {
223 if (options != null) {
224 s.DontFragment = options.DontFragment;
225 s.Ttl = (short) options.Ttl;
227 s.SendTimeout = timeout;
228 s.ReceiveTimeout = timeout;
229 // not sure why Identifier = 0 is unacceptable ...
230 IcmpMessage send = new IcmpMessage (8, 0, identifier, 0, buffer);
231 byte [] bytes = send.GetBytes ();
232 s.SendBufferSize = bytes.Length;
233 s.SendTo (bytes, bytes.Length, SocketFlags.None, target);
235 DateTime sentTime = DateTime.Now;
238 bytes = new byte [100];
240 EndPoint endpoint = client;
242 int rc = s.ReceiveFrom_nochecks_exc (bytes, 0, 100, SocketFlags.None,
243 ref endpoint, false, out error);
246 if (error == (int) SocketError.TimedOut) {
247 return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
249 throw new NotSupportedException (String.Format ("Unexpected socket error during ping request: {0}", error));
251 long rtt = (long) (DateTime.Now - sentTime).TotalMilliseconds;
252 int headerLength = (bytes [0] & 0xF) << 2;
253 int bodyLength = rc - headerLength;
255 // Ping reply to different request. discard it.
256 if (!((IPEndPoint) endpoint).Address.Equals (target.Address)) {
257 long t = timeout - rtt;
259 return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
260 s.ReceiveTimeout = (int) t;
264 IcmpMessage recv = new IcmpMessage (bytes, headerLength, bodyLength);
266 /* discard ping reply to different request or echo requests if running on same host. */
267 if (recv.Identifier != identifier || recv.Type == 8) {
268 long t = timeout - rtt;
270 return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
271 s.ReceiveTimeout = (int) t;
275 return new PingReply (address, recv.Data, options, rtt, recv.IPStatus);
280 private PingReply SendUnprivileged (IPAddress address, int timeout, byte [] buffer, PingOptions options)
282 DateTime sentTime = DateTime.Now;
284 Process ping = new Process ();
285 string args = BuildPingArgs (address, timeout, options);
288 ping.StartInfo.FileName = PingBinPath;
289 ping.StartInfo.Arguments = args;
291 ping.StartInfo.CreateNoWindow = true;
292 ping.StartInfo.UseShellExecute = false;
294 ping.StartInfo.RedirectStandardOutput = true;
295 ping.StartInfo.RedirectStandardError = true;
300 #pragma warning disable 219
301 string stdout = ping.StandardOutput.ReadToEnd ();
302 string stderr = ping.StandardError.ReadToEnd ();
303 #pragma warning restore 219
305 trip_time = (long) (DateTime.Now - sentTime).TotalMilliseconds;
306 if (!ping.WaitForExit (timeout) || (ping.HasExited && ping.ExitCode == 2))
307 return new PingReply (address, buffer, options, trip_time, IPStatus.TimedOut);
309 if (ping.ExitCode == 1)
310 return new PingReply (address, buffer, options, trip_time, IPStatus.TtlExpired);
311 } catch (Exception) {
312 return new PingReply (address, buffer, options, trip_time, IPStatus.Unknown);
321 return new PingReply (address, buffer, options, trip_time, IPStatus.Success);
326 public void SendAsync (IPAddress address, int timeout, byte [] buffer, object userToken)
328 SendAsync (address, default_timeout, default_buffer, new PingOptions (), userToken);
331 public void SendAsync (IPAddress address, int timeout, object userToken)
333 SendAsync (address, default_timeout, default_buffer, userToken);
336 public void SendAsync (IPAddress address, object userToken)
338 SendAsync (address, default_timeout, userToken);
341 public void SendAsync (string hostNameOrAddress, int timeout, byte [] buffer, object userToken)
343 SendAsync (hostNameOrAddress, timeout, buffer, new PingOptions (), userToken);
346 public void SendAsync (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options, object userToken)
348 IPAddress address = Dns.GetHostEntry (hostNameOrAddress).AddressList [0];
349 SendAsync (address, timeout, buffer, options, userToken);
352 public void SendAsync (string hostNameOrAddress, int timeout, object userToken)
354 SendAsync (hostNameOrAddress, timeout, default_buffer, userToken);
357 public void SendAsync (string hostNameOrAddress, object userToken)
359 SendAsync (hostNameOrAddress, default_timeout, userToken);
362 public void SendAsync (IPAddress address, int timeout, byte [] buffer, PingOptions options, object userToken)
365 throw new InvalidOperationException ("Another SendAsync operation is in progress");
367 worker = new BackgroundWorker ();
368 worker.DoWork += delegate (object o, DoWorkEventArgs ea) {
370 user_async_state = ea.Argument;
371 ea.Result = Send (address, timeout, buffer, options);
372 } catch (Exception ex) {
376 worker.WorkerSupportsCancellation = true;
377 worker.RunWorkerCompleted += delegate (object o, RunWorkerCompletedEventArgs ea) {
378 // Note that RunWorkerCompletedEventArgs.UserState cannot be used (LAMESPEC)
379 OnPingCompleted (new PingCompletedEventArgs (ea.Error, ea.Cancelled, user_async_state, ea.Result as PingReply));
381 worker.RunWorkerAsync (userToken);
386 public void SendAsyncCancel ()
389 throw new InvalidOperationException ("SendAsync operation is not in progress");
390 worker.CancelAsync ();
400 public IcmpMessage (byte [] bytes, int offset, int size)
402 this.bytes = new byte [size];
403 Buffer.BlockCopy (bytes, offset, this.bytes, 0, size);
407 public IcmpMessage (byte type, byte code, ushort identifier, ushort sequence, byte [] data)
409 bytes = new byte [data.Length + 8];
412 bytes [4] = (byte) (identifier & 0xFF);
413 bytes [5] = (byte) ((int) identifier >> 8);
414 bytes [6] = (byte) (sequence & 0xFF);
415 bytes [7] = (byte) ((int) sequence >> 8);
416 Buffer.BlockCopy (data, 0, bytes, 8, data.Length);
418 ushort checksum = ComputeChecksum (bytes);
419 bytes [2] = (byte) (checksum & 0xFF);
420 bytes [3] = (byte) ((int) checksum >> 8);
424 get { return bytes [0]; }
428 get { return bytes [1]; }
431 public ushort Identifier {
432 get { return (ushort) (bytes [4] + (bytes [5] << 8)); }
435 public ushort Sequence {
436 get { return (ushort) (bytes [6] + (bytes [7] << 8)); }
439 public byte [] Data {
441 byte [] data = new byte [bytes.Length - 8];
442 Buffer.BlockCopy (bytes, 8, data, 0, data.Length);
447 public byte [] GetBytes ()
452 static ushort ComputeChecksum (byte [] data)
455 for (int i = 0; i < data.Length; i += 2) {
456 ushort us = i + 1 < data.Length ? data [i + 1] : (byte) 0;
461 ret = (ret >> 16) + (ret & 0xFFFF);
462 return (ushort) ~ ret;
465 public IPStatus IPStatus {
469 return IPStatus.Success;
470 case 3: // destination unreacheable
473 return IPStatus.DestinationNetworkUnreachable;
475 return IPStatus.DestinationHostUnreachable;
477 return IPStatus.DestinationProtocolUnreachable;
479 return IPStatus.DestinationPortUnreachable;
481 return IPStatus.BadOption; // FIXME: likely wrong
483 return IPStatus.BadRoute; // not sure if it is correct
489 return IPStatus.TimeExceeded;
491 return IPStatus.TtlReassemblyTimeExceeded;
495 return IPStatus.ParameterProblem;
497 return IPStatus.SourceQuench;
499 return IPStatus.Success;
501 return IPStatus.Unknown;
502 //throw new NotSupportedException (String.Format ("Unexpected pair of ICMP message type and code: type is {0} and code is {1}", Type, Code));
507 private string BuildPingArgs (IPAddress address, int timeout, PingOptions options)
509 CultureInfo culture = CultureInfo.InvariantCulture;
510 StringBuilder args = new StringBuilder ();
511 uint t = Convert.ToUInt32 (Math.Floor ((timeout + 1000) / 1000.0));
513 bool is_mac = ((int) Environment.OSVersion.Platform == 6);
516 args.AppendFormat (culture, "-q -n -c {0} -w {1} -t {2} -M ", DefaultCount, t, options.Ttl);
519 args.AppendFormat (culture, "-q -n -c {0} -t {1} -o -m {2} ", DefaultCount, t, options.Ttl);
522 args.Append (options.DontFragment ? "do " : "dont ");
524 else if (options.DontFragment)
528 args.Append (address.ToString ());
530 return args.ToString ();