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.
32 using System.Diagnostics;
33 using System.Globalization;
34 using System.ComponentModel;
35 using System.Net.Sockets;
36 using System.Security.Principal;
37 using System.Runtime.InteropServices;
39 namespace System.Net.NetworkInformation {
40 [MonoTODO ("IPv6 support is missing")]
41 public class Ping : Component, IDisposable
43 [StructLayout(LayoutKind.Sequential)]
44 struct cap_user_header_t
46 public UInt32 version;
50 [StructLayout(LayoutKind.Sequential)]
51 struct cap_user_data_t
53 public UInt32 effective;
54 public UInt32 permitted;
55 public UInt32 inheritable;
58 const int DefaultCount = 1;
59 const string PingBinPath = "/bin/ping";
60 const int default_timeout = 4000; // 4 sec.
61 const int identifier = 1; // no need to be const, but there's no place to change it.
63 // This value is correct as of Linux kernel version 2.6.25.9
64 // See /usr/include/linux/capability.h
65 const UInt32 linux_cap_version = 0x20071026;
67 static readonly byte [] default_buffer = new byte [0];
68 static bool canSendPrivileged;
71 BackgroundWorker worker;
72 object user_async_state;
74 public event PingCompletedEventHandler PingCompleted;
78 if (Environment.OSVersion.Platform == PlatformID.Unix) {
79 CheckLinuxCapabilities ();
80 if (!canSendPrivileged && WindowsIdentity.GetCurrent ().Name == "root")
81 canSendPrivileged = true;
84 canSendPrivileged = true;
91 [DllImport ("libc", EntryPoint="capget")]
92 static extern int capget (ref cap_user_header_t header, ref cap_user_data_t data);
94 static void CheckLinuxCapabilities ()
97 cap_user_header_t header = new cap_user_header_t ();
98 cap_user_data_t data = new cap_user_data_t ();
100 header.version = linux_cap_version;
105 ret = capget (ref header, ref data);
106 } catch (Exception) {
112 canSendPrivileged = (data.effective & (1 << 13)) != 0;
114 canSendPrivileged = false;
118 void IDisposable.Dispose ()
122 protected void OnPingCompleted (PingCompletedEventArgs e)
124 if (PingCompleted != null)
125 PingCompleted (this, e);
126 user_async_state = null;
132 public PingReply Send (IPAddress address)
134 return Send (address, default_timeout);
137 public PingReply Send (IPAddress address, int timeout)
139 return Send (address, timeout, default_buffer);
142 public PingReply Send (IPAddress address, int timeout, byte [] buffer)
144 return Send (address, timeout, buffer, new PingOptions ());
147 public PingReply Send (string hostNameOrAddress)
149 return Send (hostNameOrAddress, default_timeout);
152 public PingReply Send (string hostNameOrAddress, int timeout)
154 return Send (hostNameOrAddress, timeout, default_buffer);
157 public PingReply Send (string hostNameOrAddress, int timeout, byte [] buffer)
159 return Send (hostNameOrAddress, timeout, buffer, new PingOptions ());
162 public PingReply Send (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options)
164 IPAddress address = Dns.GetHostEntry (hostNameOrAddress).AddressList [0];
165 return Send (address, timeout, buffer, options);
168 static IPAddress GetNonLoopbackIP ()
170 foreach (IPAddress addr in Dns.GetHostByName (Dns.GetHostName ()).AddressList)
171 if (!IPAddress.IsLoopback (addr))
173 throw new InvalidOperationException ("Could not resolve non-loopback IP address for localhost");
176 public PingReply Send (IPAddress address, int timeout, byte [] buffer, PingOptions options)
179 throw new ArgumentNullException ("address");
181 throw new ArgumentOutOfRangeException ("timeout", "timeout must be non-negative integer");
183 throw new ArgumentNullException ("buffer");
184 if (buffer.Length > 65500)
185 throw new ArgumentException ("buffer");
186 // options can be null.
188 if (canSendPrivileged)
189 return SendPrivileged (address, timeout, buffer, options);
190 return SendUnprivileged (address, timeout, buffer, options);
193 private PingReply SendPrivileged (IPAddress address, int timeout, byte [] buffer, PingOptions options)
195 IPEndPoint target = new IPEndPoint (address, 0);
196 IPEndPoint client = new IPEndPoint (GetNonLoopbackIP (), 0);
198 // FIXME: support IPv6
199 Socket s = new Socket (AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);
200 if (options != null) {
201 s.DontFragment = options.DontFragment;
202 s.Ttl = (short) options.Ttl;
204 s.SendTimeout = timeout;
205 s.ReceiveTimeout = timeout;
206 // not sure why Identifier = 0 is unacceptable ...
207 IcmpMessage send = new IcmpMessage (8, 0, identifier, 0, buffer);
208 byte [] bytes = send.GetBytes ();
209 s.SendBufferSize = bytes.Length;
210 s.SendTo (bytes, bytes.Length, SocketFlags.None, target);
212 DateTime sentTime = DateTime.Now;
215 bytes = new byte [100];
218 EndPoint endpoint = client;
219 int rc = s.ReceiveFrom (bytes, 100, SocketFlags.None, ref endpoint);
220 long rtt = (long) (DateTime.Now - sentTime).TotalMilliseconds;
221 int headerLength = (bytes [0] & 0xF) << 2;
222 int bodyLength = rc - headerLength;
224 if (!((IPEndPoint) endpoint).Address.Equals (target.Address)) // Ping reply to different request. discard it.
227 IcmpMessage recv = new IcmpMessage (bytes, headerLength, bodyLength);
228 if (recv.Identifier != identifier)
229 continue; // ping reply to different request. discard it.
231 return new PingReply (address, recv.Data, options, rtt, recv.IPStatus);
232 } catch (SocketException ex) {
234 switch (ex.SocketErrorCode) {
235 case SocketError.TimedOut:
236 stat = IPStatus.TimedOut;
239 throw new NotSupportedException (String.Format ("Unexpected socket error during ping request: {0}", ex.SocketErrorCode));
241 return new PingReply (null, new byte [0], options, 0, stat);
246 private PingReply SendUnprivileged (IPAddress address, int timeout, byte [] buffer, PingOptions options)
248 DateTime sentTime = DateTime.Now;
250 Process ping = new Process ();
251 string args = BuildPingArgs (address, timeout, options);
254 ping.StartInfo.FileName = PingBinPath;
255 ping.StartInfo.Arguments = args;
257 ping.StartInfo.CreateNoWindow = true;
258 ping.StartInfo.UseShellExecute = false;
260 ping.StartInfo.RedirectStandardOutput = true;
261 ping.StartInfo.RedirectStandardError = true;
263 DateTime start = DateTime.UtcNow;
267 #pragma warning disable 219
268 // No need to read stdout or stderr as long as the output is less than 4k on linux <= 2.6.11 and 65k after that
269 // string stdout = ping.StandardOutput.ReadToEnd ();
270 // string stderr = ping.StandardError.ReadToEnd ();
271 #pragma warning restore 219
273 trip_time = (long) (DateTime.Now - sentTime).TotalMilliseconds;
274 if (!ping.WaitForExit (timeout) || ping.ExitCode == 2)
275 return new PingReply (address, buffer, options, trip_time, IPStatus.TimedOut);
277 if (ping.ExitCode == 1)
278 return new PingReply (address, buffer, options, trip_time, IPStatus.TtlExpired);
279 } catch (Exception) {
280 return new PingReply (address, buffer, options, trip_time, IPStatus.Unknown);
289 return new PingReply (address, buffer, options, trip_time, IPStatus.Success);
294 public void SendAsync (IPAddress address, int timeout, byte [] buffer, object userToken)
296 SendAsync (address, default_timeout, default_buffer, new PingOptions (), userToken);
299 public void SendAsync (IPAddress address, int timeout, object userToken)
301 SendAsync (address, default_timeout, default_buffer, userToken);
304 public void SendAsync (IPAddress address, object userToken)
306 SendAsync (address, default_timeout, userToken);
309 public void SendAsync (string hostNameOrAddress, int timeout, byte [] buffer, object userToken)
311 SendAsync (hostNameOrAddress, timeout, buffer, new PingOptions (), userToken);
314 public void SendAsync (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options, object userToken)
316 IPAddress address = Dns.GetHostEntry (hostNameOrAddress).AddressList [0];
317 SendAsync (address, timeout, buffer, options, userToken);
320 public void SendAsync (string hostNameOrAddress, int timeout, object userToken)
322 SendAsync (hostNameOrAddress, timeout, default_buffer, userToken);
325 public void SendAsync (string hostNameOrAddress, object userToken)
327 SendAsync (hostNameOrAddress, default_timeout, userToken);
330 public void SendAsync (IPAddress address, int timeout, byte [] buffer, PingOptions options, object userToken)
333 throw new InvalidOperationException ("Another SendAsync operation is in progress");
335 worker = new BackgroundWorker ();
336 worker.DoWork += delegate (object o, DoWorkEventArgs ea) {
338 user_async_state = ea.Argument;
339 ea.Result = Send (address, timeout, buffer, options);
340 } catch (Exception ex) {
344 worker.WorkerSupportsCancellation = true;
345 worker.RunWorkerCompleted += delegate (object o, RunWorkerCompletedEventArgs ea) {
346 // Note that RunWorkerCompletedEventArgs.UserState cannot be used (LAMESPEC)
347 OnPingCompleted (new PingCompletedEventArgs (ea.Error, ea.Cancelled, user_async_state, ea.Result as PingReply));
349 worker.RunWorkerAsync (userToken);
354 public void SendAsyncCancel ()
357 throw new InvalidOperationException ("SendAsync operation is not in progress");
358 worker.CancelAsync ();
368 public IcmpMessage (byte [] bytes, int offset, int size)
370 this.bytes = new byte [size];
371 Array.Copy (bytes, offset, this.bytes, 0, size);
375 public IcmpMessage (byte type, byte code, short identifier, short sequence, byte [] data)
377 bytes = new byte [data.Length + 8];
380 bytes [4] = (byte) (identifier & 0xFF);
381 bytes [5] = (byte) ((int) identifier >> 8);
382 bytes [6] = (byte) (sequence & 0xFF);
383 bytes [7] = (byte) ((int) sequence >> 8);
384 Array.Copy (data, 0, bytes, 8, data.Length);
386 ushort checksum = ComputeChecksum (bytes);
387 bytes [2] = (byte) (checksum & 0xFF);
388 bytes [3] = (byte) ((int) checksum >> 8);
392 get { return bytes [0]; }
396 get { return bytes [1]; }
399 public byte Identifier {
400 get { return (byte) (bytes [4] + (bytes [5] << 8)); }
403 public byte Sequence {
404 get { return (byte) (bytes [6] + (bytes [7] << 8)); }
407 public byte [] Data {
409 byte [] data = new byte [bytes.Length - 8];
410 Array.Copy (bytes, 0, data, 0, data.Length);
415 public byte [] GetBytes ()
420 static ushort ComputeChecksum (byte [] data)
423 for (int i = 0; i < data.Length; i += 2) {
424 ushort us = i + 1 < data.Length ? data [i + 1] : (byte) 0;
429 ret = (ret >> 16) + (ret & 0xFFFF);
430 return (ushort) ~ ret;
433 public IPStatus IPStatus {
437 return IPStatus.Success;
438 case 3: // destination unreacheable
441 return IPStatus.DestinationNetworkUnreachable;
443 return IPStatus.DestinationHostUnreachable;
445 return IPStatus.DestinationProtocolUnreachable;
447 return IPStatus.DestinationPortUnreachable;
449 return IPStatus.BadOption; // FIXME: likely wrong
451 return IPStatus.BadRoute; // not sure if it is correct
457 return IPStatus.TimeExceeded;
459 return IPStatus.TtlReassemblyTimeExceeded;
463 return IPStatus.ParameterProblem;
465 return IPStatus.SourceQuench;
467 throw new NotSupportedException (String.Format ("Unexpected pair of ICMP message type and code: type is {0} and code is {1}", Type, Code));
472 private string BuildPingArgs (IPAddress address, int timeout, PingOptions options)
474 CultureInfo culture = CultureInfo.InvariantCulture;
475 StringBuilder args = new StringBuilder ();
476 uint t = Convert.ToUInt32 (Math.Floor ((timeout + 1000) / 1000.0));
477 args.AppendFormat (culture, "-q -n -c {0} -w {1} -t {2} -M ", DefaultCount, t, options.Ttl);
478 args.Append (options.DontFragment ? "do " : "dont ");
480 args.Append (address.ToString ());
482 return args.ToString ();