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