New test.
[mono.git] / mcs / class / System / System.Net.NetworkInformation / Ping.cs
1 //
2 // System.Net.NetworkInformation.Ping
3 //
4 // Authors:
5 //      Gonzalo Paniagua Javier (gonzalo@novell.com)
6 //      Atsushi Enomoto (atsushi@ximian.com)
7 //
8 // Copyright (c) 2006-2007 Novell, Inc. (http://www.novell.com)
9 //
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:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
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.
28 //
29 #if NET_2_0
30 using System;
31 using System.IO;
32 using System.Text;
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.Runtime.InteropServices;
39
40 namespace System.Net.NetworkInformation {
41         [MonoTODO ("IPv6 support is missing")]
42         public class Ping : Component, IDisposable
43         {
44                 [StructLayout(LayoutKind.Sequential)]
45                 struct cap_user_header_t
46                 {
47                         public UInt32 version;
48                         public Int32 pid;
49                 };
50
51                 [StructLayout(LayoutKind.Sequential)]
52                 struct cap_user_data_t
53                 {
54                         public UInt32 effective;
55                         public UInt32 permitted;
56                         public UInt32 inheritable;
57                 }
58                 
59                 const int DefaultCount = 1;
60                 static readonly string [] PingBinPaths = new string [] {
61                         "/bin/ping",
62                         "/sbin/ping",
63                         "/usr/sbin/ping"
64                 };
65                 static readonly string PingBinPath;
66                 const int default_timeout = 4000; // 4 sec.
67                 const int identifier = 1; // no need to be const, but there's no place to change it.
68
69                 // This value is correct as of Linux kernel version 2.6.25.9
70                 // See /usr/include/linux/capability.h
71                 const UInt32 linux_cap_version = 0x20071026;
72                 
73                 static readonly byte [] default_buffer = new byte [0];
74                 static bool canSendPrivileged;
75                 
76
77                 BackgroundWorker worker;
78                 object user_async_state;
79                 
80                 public event PingCompletedEventHandler PingCompleted;
81                 
82                 static Ping ()
83                 {
84                         if (Environment.OSVersion.Platform == PlatformID.Unix) {
85                                 CheckLinuxCapabilities ();
86                                 if (!canSendPrivileged && WindowsIdentity.GetCurrent ().Name == "root")
87                                         canSendPrivileged = true;
88                         
89                                 // Since different Unix systems can have different path to bin, we try some
90                                 // of the known ones.
91                                 foreach (string ping_path in PingBinPaths)
92                                         if (File.Exists (ping_path)) {
93                                                 PingBinPath = ping_path;
94                                                 break;
95                                         }
96                         }
97                         else
98                                 canSendPrivileged = true;
99
100                         if (PingBinPath == null)
101                                 PingBinPath = "/bin/ping"; // default, fallback value
102                 }
103                 
104                 public Ping ()
105                 {
106                 }
107   
108                 [DllImport ("libc", EntryPoint="capget")]
109                 static extern int capget (ref cap_user_header_t header, ref cap_user_data_t data);
110
111                 static void CheckLinuxCapabilities ()
112                 {
113                         try {
114                                 cap_user_header_t header = new cap_user_header_t ();
115                                 cap_user_data_t data = new cap_user_data_t ();
116
117                                 header.version = linux_cap_version;
118
119                                 int ret = -1;
120
121                                 try {
122                                         ret = capget (ref header, ref data);
123                                 } catch (Exception) {
124                                 }
125
126                                 if (ret == -1)
127                                         return;
128
129                                 canSendPrivileged = (data.effective & (1 << 13)) != 0;
130                         } catch {
131                                 canSendPrivileged = false;
132                         }
133                 }
134                 
135                 void IDisposable.Dispose ()
136                 {
137                 }
138
139                 protected void OnPingCompleted (PingCompletedEventArgs e)
140                 {
141                         if (PingCompleted != null)
142                                 PingCompleted (this, e);
143                         user_async_state = null;
144                         worker = null;
145                 }
146
147                 // Sync
148
149                 public PingReply Send (IPAddress address)
150                 {
151                         return Send (address, default_timeout);
152                 }
153
154                 public PingReply Send (IPAddress address, int timeout)
155                 {
156                         return Send (address, timeout, default_buffer);
157                 }
158
159                 public PingReply Send (IPAddress address, int timeout, byte [] buffer)
160                 {
161                         return Send (address, timeout, buffer, new PingOptions ());
162                 }
163
164                 public PingReply Send (string hostNameOrAddress)
165                 {
166                         return Send (hostNameOrAddress, default_timeout);
167                 }
168
169                 public PingReply Send (string hostNameOrAddress, int timeout)
170                 {
171                         return Send (hostNameOrAddress, timeout, default_buffer);
172                 }
173
174                 public PingReply Send (string hostNameOrAddress, int timeout, byte [] buffer)
175                 {
176                         return Send (hostNameOrAddress, timeout, buffer, new PingOptions ());
177                 }
178
179                 public PingReply Send (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options)
180                 {
181                         IPAddress [] addresses = Dns.GetHostAddresses (hostNameOrAddress);
182                         return Send (addresses [0], timeout, buffer, options);
183                 }
184
185                 static IPAddress GetNonLoopbackIP ()
186                 {
187                         foreach (IPAddress addr in Dns.GetHostByName (Dns.GetHostName ()).AddressList)
188                                 if (!IPAddress.IsLoopback (addr))
189                                         return addr;
190                         throw new InvalidOperationException ("Could not resolve non-loopback IP address for localhost");
191                 }
192
193                 public PingReply Send (IPAddress address, int timeout, byte [] buffer, PingOptions options)
194                 {
195                         if (address == null)
196                                 throw new ArgumentNullException ("address");
197                         if (timeout < 0)
198                                 throw new ArgumentOutOfRangeException ("timeout", "timeout must be non-negative integer");
199                         if (buffer == null)
200                                 throw new ArgumentNullException ("buffer");
201                         if (buffer.Length > 65500)
202                                 throw new ArgumentException ("buffer");
203                         // options can be null.
204
205                         if (canSendPrivileged)
206                                 return SendPrivileged (address, timeout, buffer, options);
207                         return SendUnprivileged (address, timeout, buffer, options);
208                 }
209
210                 private PingReply SendPrivileged (IPAddress address, int timeout, byte [] buffer, PingOptions options)
211                 {
212                         IPEndPoint target = new IPEndPoint (address, 0);
213                         IPEndPoint client = new IPEndPoint (GetNonLoopbackIP (), 0);
214
215                         // FIXME: support IPv6
216                         using (Socket s = new Socket (AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp)) {
217                                 if (options != null) {
218                                         s.DontFragment = options.DontFragment;
219                                         s.Ttl = (short) options.Ttl;
220                                 }
221                                 s.SendTimeout = timeout;
222                                 s.ReceiveTimeout = timeout;
223                                 // not sure why Identifier = 0 is unacceptable ...
224                                 IcmpMessage send = new IcmpMessage (8, 0, identifier, 0, buffer);
225                                 byte [] bytes = send.GetBytes ();
226                                 s.SendBufferSize = bytes.Length;
227                                 s.SendTo (bytes, bytes.Length, SocketFlags.None, target);
228
229                                 DateTime sentTime = DateTime.Now;
230
231                                 // receive
232                                 bytes = new byte [100];
233                                 do {
234                                         EndPoint endpoint = client;
235                                         int error = 0;
236                                         int rc = s.ReceiveFrom_nochecks_exc (bytes, 0, 100, SocketFlags.None,
237                                                         ref endpoint, false, out error);
238
239                                         if (error != 0) {
240                                                 if (error == (int) SocketError.TimedOut) {
241                                                         return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
242                                                 }
243                                                 throw new NotSupportedException (String.Format ("Unexpected socket error during ping request: {0}", error));
244                                         }
245                                         long rtt = (long) (DateTime.Now - sentTime).TotalMilliseconds;
246                                         int headerLength = (bytes [0] & 0xF) << 2;
247                                         int bodyLength = rc - headerLength;
248
249                                         // Ping reply to different request. discard it.
250                                         if (!((IPEndPoint) endpoint).Address.Equals (target.Address)) {
251                                                 long t = timeout - rtt;
252                                                 if (t <= 0)
253                                                         return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
254                                                 s.ReceiveTimeout = (int) t;
255                                                 continue;
256                                         }
257
258                                         IcmpMessage recv = new IcmpMessage (bytes, headerLength, bodyLength);
259
260                                         /* discard ping reply to different request or echo requests if running on same host. */
261                                         if (recv.Identifier != identifier || recv.Type == 8) {
262                                                 long t = timeout - rtt;
263                                                 if (t <= 0)
264                                                         return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
265                                                 s.ReceiveTimeout = (int) t;
266                                                 continue; 
267                                         }
268
269                                         return new PingReply (address, recv.Data, options, rtt, recv.IPStatus);
270                                 } while (true);
271                         }
272                 }
273
274                 private PingReply SendUnprivileged (IPAddress address, int timeout, byte [] buffer, PingOptions options)
275                 {
276                         DateTime sentTime = DateTime.Now;
277
278                         Process ping = new Process ();
279                         string args = BuildPingArgs (address, timeout, options);
280                         long trip_time = 0;
281
282                         ping.StartInfo.FileName = PingBinPath;
283                         ping.StartInfo.Arguments = args;
284
285                         ping.StartInfo.CreateNoWindow = true;
286                         ping.StartInfo.UseShellExecute = false;
287
288                         ping.StartInfo.RedirectStandardOutput = true;
289                         ping.StartInfo.RedirectStandardError = true;
290
291                         try {
292                                 ping.Start ();
293
294 #pragma warning disable 219
295                         // 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
296                         //      string stdout = ping.StandardOutput.ReadToEnd ();
297                         //      string stderr = ping.StandardError.ReadToEnd ();
298 #pragma warning restore 219
299                                 
300                                 trip_time = (long) (DateTime.Now - sentTime).TotalMilliseconds;
301                                 if (!ping.WaitForExit (timeout) || (ping.HasExited && ping.ExitCode == 2))
302                                         return new PingReply (address, buffer, options, trip_time, IPStatus.TimedOut); 
303
304                                 if (ping.ExitCode == 1)
305                                         return new PingReply (address, buffer, options, trip_time, IPStatus.TtlExpired);
306                         } catch (Exception) {
307                                 return new PingReply (address, buffer, options, trip_time, IPStatus.Unknown);
308                         } finally {
309                                 if (ping != null) {
310                                         if (!ping.HasExited)
311                                                 ping.Kill ();
312                                         ping.Dispose ();
313                                 }
314                         }
315
316                         return new PingReply (address, buffer, options, trip_time, IPStatus.Success);
317                 }
318
319                 // Async
320
321                 public void SendAsync (IPAddress address, int timeout, byte [] buffer, object userToken)
322                 {
323                         SendAsync (address, default_timeout, default_buffer, new PingOptions (), userToken);
324                 }
325
326                 public void SendAsync (IPAddress address, int timeout, object userToken)
327                 {
328                         SendAsync (address, default_timeout, default_buffer, userToken);
329                 }
330
331                 public void SendAsync (IPAddress address, object userToken)
332                 {
333                         SendAsync (address, default_timeout, userToken);
334                 }
335
336                 public void SendAsync (string hostNameOrAddress, int timeout, byte [] buffer, object userToken)
337                 {
338                         SendAsync (hostNameOrAddress, timeout, buffer, new PingOptions (), userToken);
339                 }
340
341                 public void SendAsync (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options, object userToken)
342                 {
343                         IPAddress address = Dns.GetHostEntry (hostNameOrAddress).AddressList [0];
344                         SendAsync (address, timeout, buffer, options, userToken);
345                 }
346
347                 public void SendAsync (string hostNameOrAddress, int timeout, object userToken)
348                 {
349                         SendAsync (hostNameOrAddress, timeout, default_buffer, userToken);
350                 }
351
352                 public void SendAsync (string hostNameOrAddress, object userToken)
353                 {
354                         SendAsync (hostNameOrAddress, default_timeout, userToken);
355                 }
356
357                 public void SendAsync (IPAddress address, int timeout, byte [] buffer, PingOptions options, object userToken)
358                 {
359                         if (worker != null)
360                                 throw new InvalidOperationException ("Another SendAsync operation is in progress");
361
362                         worker = new BackgroundWorker ();
363                         worker.DoWork += delegate (object o, DoWorkEventArgs ea) {
364                                 try {
365                                         user_async_state = ea.Argument;
366                                         ea.Result = Send (address, timeout, buffer, options);
367                                 } catch (Exception ex) {
368                                         ea.Result = ex;
369                                 }
370                         };
371                         worker.WorkerSupportsCancellation = true;
372                         worker.RunWorkerCompleted += delegate (object o, RunWorkerCompletedEventArgs ea) {
373                                 // Note that RunWorkerCompletedEventArgs.UserState cannot be used (LAMESPEC)
374                                 OnPingCompleted (new PingCompletedEventArgs (ea.Error, ea.Cancelled, user_async_state, ea.Result as PingReply));
375                         };
376                         worker.RunWorkerAsync (userToken);
377                 }
378
379                 // SendAsyncCancel
380
381                 public void SendAsyncCancel ()
382                 {
383                         if (worker == null)
384                                 throw new InvalidOperationException ("SendAsync operation is not in progress");
385                         worker.CancelAsync ();
386                 }
387
388                 // ICMP message
389
390                 class IcmpMessage
391                 {
392                         byte [] bytes;
393
394                         // received
395                         public IcmpMessage (byte [] bytes, int offset, int size)
396                         {
397                                 this.bytes = new byte [size];
398                                 Buffer.BlockCopy (bytes, offset, this.bytes, 0, size);
399                         }
400
401                         // to be sent
402                         public IcmpMessage (byte type, byte code, short identifier, short sequence, byte [] data)
403                         {
404                                 bytes = new byte [data.Length + 8];
405                                 bytes [0] = type;
406                                 bytes [1] = code;
407                                 bytes [4] = (byte) (identifier & 0xFF);
408                                 bytes [5] = (byte) ((int) identifier >> 8);
409                                 bytes [6] = (byte) (sequence & 0xFF);
410                                 bytes [7] = (byte) ((int) sequence >> 8);
411                                 Buffer.BlockCopy (data, 0, bytes, 8, data.Length);
412
413                                 ushort checksum = ComputeChecksum (bytes);
414                                 bytes [2] = (byte) (checksum & 0xFF);
415                                 bytes [3] = (byte) ((int) checksum >> 8);
416                         }
417
418                         public byte Type {
419                                 get { return bytes [0]; }
420                         }
421
422                         public byte Code {
423                                 get { return bytes [1]; }
424                         }
425
426                         public byte Identifier {
427                                 get { return (byte) (bytes [4] + (bytes [5] << 8)); }
428                         }
429
430                         public byte Sequence {
431                                 get { return (byte) (bytes [6] + (bytes [7] << 8)); }
432                         }
433
434                         public byte [] Data {
435                                 get {
436                                         byte [] data = new byte [bytes.Length - 8];
437                                         Buffer.BlockCopy (bytes, 0, data, 0, data.Length);
438                                         return data;
439                                 }
440                         }
441
442                         public byte [] GetBytes ()
443                         {
444                                 return bytes;
445                         }
446
447                         static ushort ComputeChecksum (byte [] data)
448                         {
449                                 uint ret = 0;
450                                 for (int i = 0; i < data.Length; i += 2) {
451                                         ushort us = i + 1 < data.Length ? data [i + 1] : (byte) 0;
452                                         us <<= 8;
453                                         us += data [i];
454                                         ret += us;
455                                 }
456                                 ret = (ret >> 16) + (ret & 0xFFFF);
457                                 return (ushort) ~ ret;
458                         }
459
460                         public IPStatus IPStatus {
461                                 get {
462                                         switch (Type) {
463                                         case 0:
464                                                 return IPStatus.Success;
465                                         case 3: // destination unreacheable
466                                                 switch (Code) {
467                                                 case 0:
468                                                         return IPStatus.DestinationNetworkUnreachable;
469                                                 case 1:
470                                                         return IPStatus.DestinationHostUnreachable;
471                                                 case 2:
472                                                         return IPStatus.DestinationProtocolUnreachable;
473                                                 case 3:
474                                                         return IPStatus.DestinationPortUnreachable;
475                                                 case 4:
476                                                         return IPStatus.BadOption; // FIXME: likely wrong
477                                                 case 5:
478                                                         return IPStatus.BadRoute; // not sure if it is correct
479                                                 }
480                                                 break;
481                                         case 11:
482                                                 switch (Code) {
483                                                 case 0:
484                                                         return IPStatus.TimeExceeded;
485                                                 case 1:
486                                                         return IPStatus.TtlReassemblyTimeExceeded;
487                                                 }
488                                                 break;
489                                         case 12:
490                                                 return IPStatus.ParameterProblem;
491                                         case 4:
492                                                 return IPStatus.SourceQuench;
493                                         case 8:
494                                                 return IPStatus.Success;
495                                         }
496                                         return IPStatus.Unknown;
497                                         //throw new NotSupportedException (String.Format ("Unexpected pair of ICMP message type and code: type is {0} and code is {1}", Type, Code));
498                                 }
499                         }
500                 }
501
502                 private string BuildPingArgs (IPAddress address, int timeout, PingOptions options)
503                 {
504                         CultureInfo culture = CultureInfo.InvariantCulture;
505                         StringBuilder args = new StringBuilder ();
506                         uint t = Convert.ToUInt32 (Math.Floor ((timeout + 1000) / 1000.0));
507 #if NET_2_0
508                         bool is_mac = ((int) Environment.OSVersion.Platform == 6);
509                         if (!is_mac)
510 #endif
511                                 args.AppendFormat (culture, "-q -n -c {0} -w {1} -t {2} -M ", DefaultCount, t, options.Ttl);
512 #if NET_2_0
513                         else
514                                 args.AppendFormat (culture, "-q -n -c {0} -t {1} -o -m {2} ", DefaultCount, t, options.Ttl);
515                         if (!is_mac)
516 #endif
517                                 args.Append (options.DontFragment ? "do " : "dont ");
518 #if NET_2_0
519                         else if (options.DontFragment)
520                                 args.Append ("-D ");
521 #endif
522
523                         args.Append (address.ToString ());
524
525                         return args.ToString ();
526                 }
527
528         }
529 }
530 #endif
531