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