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