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