[System] Don't use DateTime.Now for measuring elapsed time
[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 // Copyright 2015 Xamarin Inc.
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System;
32 using System.IO;
33 using System.Text;
34 using System.Diagnostics;
35 using System.Globalization;
36 using System.ComponentModel;
37 using System.Net.Sockets;
38 using System.Security.Principal;
39 using System.Security.Cryptography;
40 using System.Runtime.InteropServices;
41 using System.Threading;
42 using System.Threading.Tasks;
43
44 namespace System.Net.NetworkInformation {
45         [MonoTODO ("IPv6 support is missing")]
46         public class Ping : Component, IDisposable
47         {
48 #if !MONOTOUCH
49                 [StructLayout(LayoutKind.Sequential)]
50                 struct cap_user_header_t
51                 {
52                         public UInt32 version;
53                         public Int32 pid;
54                 };
55
56                 [StructLayout(LayoutKind.Sequential)]
57                 struct cap_user_data_t
58                 {
59                         public UInt32 effective;
60                         public UInt32 permitted;
61                         public UInt32 inheritable;
62                 }
63                 
64                 const int DefaultCount = 1;
65                 static readonly string [] PingBinPaths = new string [] {
66                         "/bin/ping",
67                         "/sbin/ping",
68                         "/usr/sbin/ping",
69 #if MONODROID
70                         "/system/bin/ping"
71 #endif
72                 };
73                 static readonly string PingBinPath;
74                 static bool canSendPrivileged;
75 #endif
76                 const int default_timeout = 4000; // 4 sec.
77                 ushort identifier;
78
79                 // Request 32-bit capabilities by using version 1
80                 const UInt32 _LINUX_CAPABILITY_VERSION_1 = 0x19980330;
81                 
82                 static readonly byte [] default_buffer = new byte [0];
83                 
84
85                 BackgroundWorker worker;
86                 object user_async_state;
87                 CancellationTokenSource cts;
88                 
89                 public event PingCompletedEventHandler PingCompleted;
90
91 #if !MONOTOUCH && !ORBIS
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 #endif
114                 
115                 public Ping ()
116                 {
117                         // Generate a new random 16 bit identifier for every ping
118                         RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider ();
119                         byte [] randomIdentifier = new byte [2];
120                         rng.GetBytes (randomIdentifier);
121                         identifier = (ushort)(randomIdentifier [0] + (randomIdentifier [1] << 8));
122                 }
123
124 #if !MONOTOUCH && !ORBIS
125                 [DllImport ("libc", EntryPoint="capget")]
126                 static extern int capget (ref cap_user_header_t header, ref cap_user_data_t data);
127
128                 static void CheckLinuxCapabilities ()
129                 {
130                         try {
131                                 cap_user_header_t header = new cap_user_header_t ();
132                                 cap_user_data_t data = new cap_user_data_t ();
133
134                                 header.version = _LINUX_CAPABILITY_VERSION_1;
135
136                                 int ret = -1;
137
138                                 try {
139                                         ret = capget (ref header, ref data);
140                                 } catch (Exception) {
141                                 }
142
143                                 if (ret == -1)
144                                         return;
145
146                                 canSendPrivileged = (data.effective & (1 << 13)) != 0;
147                         } catch {
148                                 canSendPrivileged = false;
149                         }
150                 }
151 #endif
152                 
153                 void IDisposable.Dispose ()
154                 {
155                 }
156
157                 protected void OnPingCompleted (PingCompletedEventArgs e)
158                 {
159                         user_async_state = null;
160                         worker = null;
161
162                         if (cts != null) {
163                                 cts.Dispose();
164                                 cts = null;
165                         }
166
167                         if (PingCompleted != null)
168                                 PingCompleted (this, e);
169                 }
170
171                 // Sync
172
173                 public PingReply Send (IPAddress address)
174                 {
175                         return Send (address, default_timeout);
176                 }
177
178                 public PingReply Send (IPAddress address, int timeout)
179                 {
180                         return Send (address, timeout, default_buffer);
181                 }
182
183                 public PingReply Send (IPAddress address, int timeout, byte [] buffer)
184                 {
185                         return Send (address, timeout, buffer, new PingOptions ());
186                 }
187
188                 public PingReply Send (string hostNameOrAddress)
189                 {
190                         return Send (hostNameOrAddress, default_timeout);
191                 }
192
193                 public PingReply Send (string hostNameOrAddress, int timeout)
194                 {
195                         return Send (hostNameOrAddress, timeout, default_buffer);
196                 }
197
198                 public PingReply Send (string hostNameOrAddress, int timeout, byte [] buffer)
199                 {
200                         return Send (hostNameOrAddress, timeout, buffer, new PingOptions ());
201                 }
202
203                 public PingReply Send (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options)
204                 {
205                         IPAddress [] addresses = Dns.GetHostAddresses (hostNameOrAddress);
206                         return Send (addresses [0], timeout, buffer, options);
207                 }
208
209                 public PingReply Send (IPAddress address, int timeout, byte [] buffer, PingOptions options)
210                 {
211                         if (address == null)
212                                 throw new ArgumentNullException ("address");
213                         if (timeout < 0)
214                                 throw new ArgumentOutOfRangeException ("timeout", "timeout must be non-negative integer");
215                         if (buffer == null)
216                                 throw new ArgumentNullException ("buffer");
217                         if (buffer.Length > 65500)
218                                 throw new ArgumentException ("buffer");
219                         // options can be null.
220
221 #if MONOTOUCH
222                         throw new InvalidOperationException ();
223 #else
224                         if (canSendPrivileged)
225                                 return SendPrivileged (address, timeout, buffer, options);
226                         return SendUnprivileged (address, timeout, buffer, options);
227 #endif
228                 }
229
230 #if !MONOTOUCH
231                 private PingReply SendPrivileged (IPAddress address, int timeout, byte [] buffer, PingOptions options)
232                 {
233                         IPEndPoint target = new IPEndPoint (address, 0);
234                         
235                         // FIXME: support IPv6
236                         using (Socket s = new Socket (AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp)) {
237                                 if (options != null) {
238                                         s.DontFragment = options.DontFragment;
239                                         s.Ttl = (short) options.Ttl;
240                                 }
241                                 s.SendTimeout = timeout;
242                                 s.ReceiveTimeout = timeout;
243                                 // not sure why Identifier = 0 is unacceptable ...
244                                 IcmpMessage send = new IcmpMessage (8, 0, identifier, 0, buffer);
245                                 byte [] bytes = send.GetBytes ();
246                                 s.SendBufferSize = bytes.Length;
247                                 s.SendTo (bytes, bytes.Length, SocketFlags.None, target);
248
249                                 var sw = Stopwatch.StartNew ();
250
251                                 // receive
252                                 bytes = new byte [100];
253                                 do {
254                                         EndPoint endpoint = target;
255                                         SocketError error = 0;
256                                         int rc = s.ReceiveFrom (bytes, 0, 100, SocketFlags.None,
257                                                         ref endpoint, out error);
258
259                                         if (error != SocketError.Success) {
260                                                 if (error == SocketError.TimedOut) {
261                                                         return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
262                                                 }
263                                                 throw new NotSupportedException (String.Format ("Unexpected socket error during ping request: {0}", error));
264                                         }
265                                         long rtt = (long) sw.ElapsedMilliseconds;
266                                         int headerLength = (bytes [0] & 0xF) << 2;
267                                         int bodyLength = rc - headerLength;
268
269                                         // Ping reply to different request. discard it.
270                                         if (!((IPEndPoint) endpoint).Address.Equals (target.Address)) {
271                                                 long t = timeout - rtt;
272                                                 if (t <= 0)
273                                                         return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
274                                                 s.ReceiveTimeout = (int) t;
275                                                 continue;
276                                         }
277
278                                         IcmpMessage recv = new IcmpMessage (bytes, headerLength, bodyLength);
279
280                                         /* discard ping reply to different request or echo requests if running on same host. */
281                                         if (recv.Identifier != identifier || recv.Type == 8) {
282                                                 long t = timeout - rtt;
283                                                 if (t <= 0)
284                                                         return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
285                                                 s.ReceiveTimeout = (int) t;
286                                                 continue; 
287                                         }
288
289                                         return new PingReply (address, recv.Data, options, rtt, recv.IPStatus);
290                                 } while (true);
291                         }
292                 }
293
294                 private PingReply SendUnprivileged (IPAddress address, int timeout, byte [] buffer, PingOptions options)
295                 {
296 #if MONO_FEATURE_PROCESS_START
297                         var sw = Stopwatch.StartNew ();
298
299                         Process ping = new Process ();
300                         string args = BuildPingArgs (address, timeout, options);
301                         long trip_time = 0;
302
303                         ping.StartInfo.FileName = PingBinPath;
304                         ping.StartInfo.Arguments = args;
305
306                         ping.StartInfo.CreateNoWindow = true;
307                         ping.StartInfo.UseShellExecute = false;
308
309                         ping.StartInfo.RedirectStandardOutput = true;
310                         ping.StartInfo.RedirectStandardError = true;
311
312                         IPStatus status = IPStatus.Unknown;
313                         try {
314                                 ping.Start ();
315
316 #pragma warning disable 219
317                                 string stdout = ping.StandardOutput.ReadToEnd ();
318                                 string stderr = ping.StandardError.ReadToEnd ();
319 #pragma warning restore 219
320                                 
321                                 trip_time = (long) sw.ElapsedMilliseconds;
322                                 if (!ping.WaitForExit (timeout) || (ping.HasExited && ping.ExitCode == 2))
323                                         status = IPStatus.TimedOut;
324                                 else if (ping.ExitCode == 0)
325                                         status = IPStatus.Success;
326                                 else if (ping.ExitCode == 1)
327                                         status = IPStatus.TtlExpired;
328                         } catch {
329                         } finally {
330                                 if (!ping.HasExited)
331                                         ping.Kill ();
332                                 ping.Dispose ();
333                         }
334
335                         return new PingReply (address, buffer, options, trip_time, status);
336 #else
337                         throw new PlatformNotSupportedException ("Ping is not supported on this platform.");
338 #endif // MONO_FEATURE_PROCESS_START
339                 }
340 #endif // !MONOTOUCH
341
342                 // Async
343
344                 public void SendAsync (IPAddress address, int timeout, byte [] buffer, object userToken)
345                 {
346                         SendAsync (address, default_timeout, default_buffer, new PingOptions (), userToken);
347                 }
348
349                 public void SendAsync (IPAddress address, int timeout, object userToken)
350                 {
351                         SendAsync (address, default_timeout, default_buffer, userToken);
352                 }
353
354                 public void SendAsync (IPAddress address, object userToken)
355                 {
356                         SendAsync (address, default_timeout, userToken);
357                 }
358
359                 public void SendAsync (string hostNameOrAddress, int timeout, byte [] buffer, object userToken)
360                 {
361                         SendAsync (hostNameOrAddress, timeout, buffer, new PingOptions (), userToken);
362                 }
363
364                 public void SendAsync (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options, object userToken)
365                 {
366                         IPAddress address = Dns.GetHostEntry (hostNameOrAddress).AddressList [0];
367                         SendAsync (address, timeout, buffer, options, userToken);
368                 }
369
370                 public void SendAsync (string hostNameOrAddress, int timeout, object userToken)
371                 {
372                         SendAsync (hostNameOrAddress, timeout, default_buffer, userToken);
373                 }
374
375                 public void SendAsync (string hostNameOrAddress, object userToken)
376                 {
377                         SendAsync (hostNameOrAddress, default_timeout, userToken);
378                 }
379
380                 public void SendAsync (IPAddress address, int timeout, byte [] buffer, PingOptions options, object userToken)
381                 {
382                         if ((worker != null) || (cts != null))
383                                 throw new InvalidOperationException ("Another SendAsync operation is in progress");
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 (cts != null) {
407                                 cts.Cancel ();
408                                 return;
409                         }
410
411                         if (worker == null)
412                                 throw new InvalidOperationException ("SendAsync operation is not in progress");
413                         worker.CancelAsync ();
414                 }
415
416 #if !MONOTOUCH
417                 // ICMP message
418
419                 class IcmpMessage
420                 {
421                         byte [] bytes;
422
423                         // received
424                         public IcmpMessage (byte [] bytes, int offset, int size)
425                         {
426                                 this.bytes = new byte [size];
427                                 Buffer.BlockCopy (bytes, offset, this.bytes, 0, size);
428                         }
429
430                         // to be sent
431                         public IcmpMessage (byte type, byte code, ushort identifier, ushort sequence, byte [] data)
432                         {
433                                 bytes = new byte [data.Length + 8];
434                                 bytes [0] = type;
435                                 bytes [1] = code;
436                                 bytes [4] = (byte) (identifier & 0xFF);
437                                 bytes [5] = (byte) ((int) identifier >> 8);
438                                 bytes [6] = (byte) (sequence & 0xFF);
439                                 bytes [7] = (byte) ((int) sequence >> 8);
440                                 Buffer.BlockCopy (data, 0, bytes, 8, data.Length);
441
442                                 ushort checksum = ComputeChecksum (bytes);
443                                 bytes [2] = (byte) (checksum & 0xFF);
444                                 bytes [3] = (byte) ((int) checksum >> 8);
445                         }
446
447                         public byte Type {
448                                 get { return bytes [0]; }
449                         }
450
451                         public byte Code {
452                                 get { return bytes [1]; }
453                         }
454
455                         public ushort Identifier {
456                                 get { return (ushort) (bytes [4] + (bytes [5] << 8)); }
457                         }
458
459                         public ushort Sequence {
460                                 get { return (ushort) (bytes [6] + (bytes [7] << 8)); }
461                         }
462
463                         public byte [] Data {
464                                 get {
465                                         byte [] data = new byte [bytes.Length - 8];
466                                         Buffer.BlockCopy (bytes, 8, data, 0, data.Length);
467                                         return data;
468                                 }
469                         }
470
471                         public byte [] GetBytes ()
472                         {
473                                 return bytes;
474                         }
475
476                         static ushort ComputeChecksum (byte [] data)
477                         {
478                                 uint ret = 0;
479                                 for (int i = 0; i < data.Length; i += 2) {
480                                         ushort us = i + 1 < data.Length ? data [i + 1] : (byte) 0;
481                                         us <<= 8;
482                                         us += data [i];
483                                         ret += us;
484                                 }
485                                 ret = (ret >> 16) + (ret & 0xFFFF);
486                                 return (ushort) ~ ret;
487                         }
488
489                         public IPStatus IPStatus {
490                                 get {
491                                         switch (Type) {
492                                         case 0:
493                                                 return IPStatus.Success;
494                                         case 3: // destination unreacheable
495                                                 switch (Code) {
496                                                 case 0:
497                                                         return IPStatus.DestinationNetworkUnreachable;
498                                                 case 1:
499                                                         return IPStatus.DestinationHostUnreachable;
500                                                 case 2:
501                                                         return IPStatus.DestinationProtocolUnreachable;
502                                                 case 3:
503                                                         return IPStatus.DestinationPortUnreachable;
504                                                 case 4:
505                                                         return IPStatus.BadOption; // FIXME: likely wrong
506                                                 case 5:
507                                                         return IPStatus.BadRoute; // not sure if it is correct
508                                                 }
509                                                 break;
510                                         case 11:
511                                                 switch (Code) {
512                                                 case 0:
513                                                         return IPStatus.TimeExceeded;
514                                                 case 1:
515                                                         return IPStatus.TtlReassemblyTimeExceeded;
516                                                 }
517                                                 break;
518                                         case 12:
519                                                 return IPStatus.ParameterProblem;
520                                         case 4:
521                                                 return IPStatus.SourceQuench;
522                                         case 8:
523                                                 return IPStatus.Success;
524                                         }
525                                         return IPStatus.Unknown;
526                                         //throw new NotSupportedException (String.Format ("Unexpected pair of ICMP message type and code: type is {0} and code is {1}", Type, Code));
527                                 }
528                         }
529                 }
530
531                 private string BuildPingArgs (IPAddress address, int timeout, PingOptions options)
532                 {
533                         CultureInfo culture = CultureInfo.InvariantCulture;
534                         StringBuilder args = new StringBuilder ();
535                         uint t = Convert.ToUInt32 (Math.Floor ((timeout + 1000) / 1000.0));
536                         bool is_mac = Platform.IsMacOS;
537                         if (!is_mac)
538                                 args.AppendFormat (culture, "-q -n -c {0} -w {1} -t {2} -M ", DefaultCount, t, options.Ttl);
539                         else
540                                 args.AppendFormat (culture, "-q -n -c {0} -t {1} -o -m {2} ", DefaultCount, t, options.Ttl);
541                         if (!is_mac)
542                                 args.Append (options.DontFragment ? "do " : "dont ");
543                         else if (options.DontFragment)
544                                 args.Append ("-D ");
545
546                         args.Append (address.ToString ());
547
548                         return args.ToString ();
549                 }
550 #endif // !MONOTOUCH
551
552                 public Task<PingReply> SendPingAsync (IPAddress address, int timeout, byte [] buffer)
553                 {
554                         return SendPingAsync (address, default_timeout, default_buffer, new PingOptions ());
555                 }
556
557                 public Task<PingReply> SendPingAsync (IPAddress address, int timeout)
558                 {
559                         return SendPingAsync (address, default_timeout, default_buffer);
560                 }
561
562                 public Task<PingReply> SendPingAsync (IPAddress address)
563                 {
564                         return SendPingAsync (address, default_timeout);
565                 }
566
567                 public Task<PingReply> SendPingAsync (string hostNameOrAddress, int timeout, byte [] buffer)
568                 {
569                         return SendPingAsync (hostNameOrAddress, timeout, buffer, new PingOptions ());
570                 }
571
572                 public Task<PingReply> SendPingAsync (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options)
573                 {
574                         IPAddress address = Dns.GetHostEntry (hostNameOrAddress).AddressList [0];
575                         return SendPingAsync (address, timeout, buffer, options);
576                 }
577
578                 public Task<PingReply> SendPingAsync (string hostNameOrAddress, int timeout)
579                 {
580                         return SendPingAsync (hostNameOrAddress, timeout, default_buffer);
581                 }
582
583                 public Task<PingReply> SendPingAsync (string hostNameOrAddress)
584                 {
585                         return SendPingAsync (hostNameOrAddress, default_timeout);
586                 }
587
588                 public Task<PingReply> SendPingAsync (IPAddress address, int timeout, byte [] buffer, PingOptions options)
589                 {
590                         if ((worker != null) || (cts != null))
591                                 throw new InvalidOperationException ("Another SendAsync operation is in progress");
592
593                         cts = new CancellationTokenSource();
594
595                         var task = Task<PingReply>.Factory.StartNew (
596                                 () => Send (address, timeout, buffer, options), cts.Token);
597
598                         task.ContinueWith ((t) => {
599                                 if (t.IsCanceled)
600                                         OnPingCompleted (new PingCompletedEventArgs (null, true, null, null));
601                                 else if (t.IsFaulted)
602                                         OnPingCompleted (new PingCompletedEventArgs (t.Exception, false, null, null));
603                                 else
604                                         OnPingCompleted (new PingCompletedEventArgs (null, false, null, t.Result));
605                         });
606
607                         return task;
608                 }
609         }
610 }