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