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