[System] Request 32-bit capabilities
[mono-project.git] / mcs / class / System / System.Net.NetworkInformation / Ping.cs
blob3f08b563774354d73bea8fa6999603313934def2
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.
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.
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;
44 namespace System.Net.NetworkInformation {
45 [MonoTODO ("IPv6 support is missing")]
46 public class Ping : Component, IDisposable
48 #if !MONOTOUCH
49 [StructLayout(LayoutKind.Sequential)]
50 struct cap_user_header_t
52 public UInt32 version;
53 public Int32 pid;
56 [StructLayout(LayoutKind.Sequential)]
57 struct cap_user_data_t
59 public UInt32 effective;
60 public UInt32 permitted;
61 public UInt32 inheritable;
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
73 static readonly string PingBinPath;
74 static bool canSendPrivileged;
75 #endif
76 const int default_timeout = 4000; // 4 sec.
77 ushort identifier;
79 // Request 32-bit capabilities by using version 1
80 const UInt32 _LINUX_CAPABILITY_VERSION_1 = 0x19980330;
82 static readonly byte [] default_buffer = new byte [0];
85 BackgroundWorker worker;
86 object user_async_state;
87 CancellationTokenSource cts;
89 public event PingCompletedEventHandler PingCompleted;
91 #if !MONOTOUCH && !ORBIS
92 static Ping ()
94 if (Environment.OSVersion.Platform == PlatformID.Unix) {
95 CheckLinuxCapabilities ();
96 if (!canSendPrivileged && WindowsIdentity.GetCurrent ().Name == "root")
97 canSendPrivileged = true;
99 // Since different Unix systems can have different path to bin, we try some
100 // of the known ones.
101 foreach (string ping_path in PingBinPaths)
102 if (File.Exists (ping_path)) {
103 PingBinPath = ping_path;
104 break;
107 else
108 canSendPrivileged = true;
110 if (PingBinPath == null)
111 PingBinPath = "/bin/ping"; // default, fallback value
113 #endif
115 public Ping ()
117 // Generate a new random 16 bit identifier for every ping
118 RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider ();
119 byte [] randomIdentifier = new byte [2];
120 rng.GetBytes (randomIdentifier);
121 identifier = (ushort)(randomIdentifier [0] + (randomIdentifier [1] << 8));
124 #if !MONOTOUCH && !ORBIS
125 [DllImport ("libc", EntryPoint="capget")]
126 static extern int capget (ref cap_user_header_t header, ref cap_user_data_t data);
128 static void CheckLinuxCapabilities ()
130 try {
131 cap_user_header_t header = new cap_user_header_t ();
132 cap_user_data_t data = new cap_user_data_t ();
134 header.version = _LINUX_CAPABILITY_VERSION_1;
136 int ret = -1;
138 try {
139 ret = capget (ref header, ref data);
140 } catch (Exception) {
143 if (ret == -1)
144 return;
146 canSendPrivileged = (data.effective & (1 << 13)) != 0;
147 } catch {
148 canSendPrivileged = false;
151 #endif
153 void IDisposable.Dispose ()
157 protected void OnPingCompleted (PingCompletedEventArgs e)
159 user_async_state = null;
160 worker = null;
162 if (cts != null) {
163 cts.Dispose();
164 cts = null;
167 if (PingCompleted != null)
168 PingCompleted (this, e);
171 // Sync
173 public PingReply Send (IPAddress address)
175 return Send (address, default_timeout);
178 public PingReply Send (IPAddress address, int timeout)
180 return Send (address, timeout, default_buffer);
183 public PingReply Send (IPAddress address, int timeout, byte [] buffer)
185 return Send (address, timeout, buffer, new PingOptions ());
188 public PingReply Send (string hostNameOrAddress)
190 return Send (hostNameOrAddress, default_timeout);
193 public PingReply Send (string hostNameOrAddress, int timeout)
195 return Send (hostNameOrAddress, timeout, default_buffer);
198 public PingReply Send (string hostNameOrAddress, int timeout, byte [] buffer)
200 return Send (hostNameOrAddress, timeout, buffer, new PingOptions ());
203 public PingReply Send (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options)
205 IPAddress [] addresses = Dns.GetHostAddresses (hostNameOrAddress);
206 return Send (addresses [0], timeout, buffer, options);
209 public PingReply Send (IPAddress address, int timeout, byte [] buffer, PingOptions options)
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.
221 #if MONOTOUCH
222 throw new InvalidOperationException ();
223 #else
224 if (canSendPrivileged)
225 return SendPrivileged (address, timeout, buffer, options);
226 return SendUnprivileged (address, timeout, buffer, options);
227 #endif
230 #if !MONOTOUCH
231 private PingReply SendPrivileged (IPAddress address, int timeout, byte [] buffer, PingOptions options)
233 IPEndPoint target = new IPEndPoint (address, 0);
235 // FIXME: support IPv6
236 using (Socket s = new Socket (AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp)) {
237 if (options != null) {
238 s.DontFragment = options.DontFragment;
239 s.Ttl = (short) options.Ttl;
241 s.SendTimeout = timeout;
242 s.ReceiveTimeout = timeout;
243 // not sure why Identifier = 0 is unacceptable ...
244 IcmpMessage send = new IcmpMessage (8, 0, identifier, 0, buffer);
245 byte [] bytes = send.GetBytes ();
246 s.SendBufferSize = bytes.Length;
247 s.SendTo (bytes, bytes.Length, SocketFlags.None, target);
249 DateTime sentTime = DateTime.Now;
251 // receive
252 bytes = new byte [100];
253 do {
254 EndPoint endpoint = target;
255 SocketError error = 0;
256 int rc = s.ReceiveFrom (bytes, 0, 100, SocketFlags.None,
257 ref endpoint, out error);
259 if (error != SocketError.Success) {
260 if (error == SocketError.TimedOut) {
261 return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
263 throw new NotSupportedException (String.Format ("Unexpected socket error during ping request: {0}", error));
265 long rtt = (long) (DateTime.Now - sentTime).TotalMilliseconds;
266 int headerLength = (bytes [0] & 0xF) << 2;
267 int bodyLength = rc - headerLength;
269 // Ping reply to different request. discard it.
270 if (!((IPEndPoint) endpoint).Address.Equals (target.Address)) {
271 long t = timeout - rtt;
272 if (t <= 0)
273 return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
274 s.ReceiveTimeout = (int) t;
275 continue;
278 IcmpMessage recv = new IcmpMessage (bytes, headerLength, bodyLength);
280 /* discard ping reply to different request or echo requests if running on same host. */
281 if (recv.Identifier != identifier || recv.Type == 8) {
282 long t = timeout - rtt;
283 if (t <= 0)
284 return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
285 s.ReceiveTimeout = (int) t;
286 continue;
289 return new PingReply (address, recv.Data, options, rtt, recv.IPStatus);
290 } while (true);
294 private PingReply SendUnprivileged (IPAddress address, int timeout, byte [] buffer, PingOptions options)
296 #if MONO_FEATURE_PROCESS_START
297 DateTime sentTime = DateTime.UtcNow;
299 Process ping = new Process ();
300 string args = BuildPingArgs (address, timeout, options);
301 long trip_time = 0;
303 ping.StartInfo.FileName = PingBinPath;
304 ping.StartInfo.Arguments = args;
306 ping.StartInfo.CreateNoWindow = true;
307 ping.StartInfo.UseShellExecute = false;
309 ping.StartInfo.RedirectStandardOutput = true;
310 ping.StartInfo.RedirectStandardError = true;
312 IPStatus status = IPStatus.Unknown;
313 try {
314 ping.Start ();
316 #pragma warning disable 219
317 string stdout = ping.StandardOutput.ReadToEnd ();
318 string stderr = ping.StandardError.ReadToEnd ();
319 #pragma warning restore 219
321 trip_time = (long) (DateTime.UtcNow - sentTime).TotalMilliseconds;
322 if (!ping.WaitForExit (timeout) || (ping.HasExited && ping.ExitCode == 2))
323 status = IPStatus.TimedOut;
324 else if (ping.ExitCode == 0)
325 status = IPStatus.Success;
326 else if (ping.ExitCode == 1)
327 status = IPStatus.TtlExpired;
328 } catch {
329 } finally {
330 if (!ping.HasExited)
331 ping.Kill ();
332 ping.Dispose ();
335 return new PingReply (address, buffer, options, trip_time, status);
336 #else
337 throw new PlatformNotSupportedException ("Ping is not supported on this platform.");
338 #endif // MONO_FEATURE_PROCESS_START
340 #endif // !MONOTOUCH
342 // Async
344 public void SendAsync (IPAddress address, int timeout, byte [] buffer, object userToken)
346 SendAsync (address, default_timeout, default_buffer, new PingOptions (), userToken);
349 public void SendAsync (IPAddress address, int timeout, object userToken)
351 SendAsync (address, default_timeout, default_buffer, userToken);
354 public void SendAsync (IPAddress address, object userToken)
356 SendAsync (address, default_timeout, userToken);
359 public void SendAsync (string hostNameOrAddress, int timeout, byte [] buffer, object userToken)
361 SendAsync (hostNameOrAddress, timeout, buffer, new PingOptions (), userToken);
364 public void SendAsync (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options, object userToken)
366 IPAddress address = Dns.GetHostEntry (hostNameOrAddress).AddressList [0];
367 SendAsync (address, timeout, buffer, options, userToken);
370 public void SendAsync (string hostNameOrAddress, int timeout, object userToken)
372 SendAsync (hostNameOrAddress, timeout, default_buffer, userToken);
375 public void SendAsync (string hostNameOrAddress, object userToken)
377 SendAsync (hostNameOrAddress, default_timeout, userToken);
380 public void SendAsync (IPAddress address, int timeout, byte [] buffer, PingOptions options, object userToken)
382 if ((worker != null) || (cts != null))
383 throw new InvalidOperationException ("Another SendAsync operation is in progress");
385 worker = new BackgroundWorker ();
386 worker.DoWork += delegate (object o, DoWorkEventArgs ea) {
387 try {
388 user_async_state = ea.Argument;
389 ea.Result = Send (address, timeout, buffer, options);
390 } catch (Exception ex) {
391 ea.Result = ex;
394 worker.WorkerSupportsCancellation = true;
395 worker.RunWorkerCompleted += delegate (object o, RunWorkerCompletedEventArgs ea) {
396 // Note that RunWorkerCompletedEventArgs.UserState cannot be used (LAMESPEC)
397 OnPingCompleted (new PingCompletedEventArgs (ea.Error, ea.Cancelled, user_async_state, ea.Result as PingReply));
399 worker.RunWorkerAsync (userToken);
402 // SendAsyncCancel
404 public void SendAsyncCancel ()
406 if (cts != null) {
407 cts.Cancel ();
408 return;
411 if (worker == null)
412 throw new InvalidOperationException ("SendAsync operation is not in progress");
413 worker.CancelAsync ();
416 #if !MONOTOUCH
417 // ICMP message
419 class IcmpMessage
421 byte [] bytes;
423 // received
424 public IcmpMessage (byte [] bytes, int offset, int size)
426 this.bytes = new byte [size];
427 Buffer.BlockCopy (bytes, offset, this.bytes, 0, size);
430 // to be sent
431 public IcmpMessage (byte type, byte code, ushort identifier, ushort sequence, byte [] data)
433 bytes = new byte [data.Length + 8];
434 bytes [0] = type;
435 bytes [1] = code;
436 bytes [4] = (byte) (identifier & 0xFF);
437 bytes [5] = (byte) ((int) identifier >> 8);
438 bytes [6] = (byte) (sequence & 0xFF);
439 bytes [7] = (byte) ((int) sequence >> 8);
440 Buffer.BlockCopy (data, 0, bytes, 8, data.Length);
442 ushort checksum = ComputeChecksum (bytes);
443 bytes [2] = (byte) (checksum & 0xFF);
444 bytes [3] = (byte) ((int) checksum >> 8);
447 public byte Type {
448 get { return bytes [0]; }
451 public byte Code {
452 get { return bytes [1]; }
455 public ushort Identifier {
456 get { return (ushort) (bytes [4] + (bytes [5] << 8)); }
459 public ushort Sequence {
460 get { return (ushort) (bytes [6] + (bytes [7] << 8)); }
463 public byte [] Data {
464 get {
465 byte [] data = new byte [bytes.Length - 8];
466 Buffer.BlockCopy (bytes, 8, data, 0, data.Length);
467 return data;
471 public byte [] GetBytes ()
473 return bytes;
476 static ushort ComputeChecksum (byte [] data)
478 uint ret = 0;
479 for (int i = 0; i < data.Length; i += 2) {
480 ushort us = i + 1 < data.Length ? data [i + 1] : (byte) 0;
481 us <<= 8;
482 us += data [i];
483 ret += us;
485 ret = (ret >> 16) + (ret & 0xFFFF);
486 return (ushort) ~ ret;
489 public IPStatus IPStatus {
490 get {
491 switch (Type) {
492 case 0:
493 return IPStatus.Success;
494 case 3: // destination unreacheable
495 switch (Code) {
496 case 0:
497 return IPStatus.DestinationNetworkUnreachable;
498 case 1:
499 return IPStatus.DestinationHostUnreachable;
500 case 2:
501 return IPStatus.DestinationProtocolUnreachable;
502 case 3:
503 return IPStatus.DestinationPortUnreachable;
504 case 4:
505 return IPStatus.BadOption; // FIXME: likely wrong
506 case 5:
507 return IPStatus.BadRoute; // not sure if it is correct
509 break;
510 case 11:
511 switch (Code) {
512 case 0:
513 return IPStatus.TimeExceeded;
514 case 1:
515 return IPStatus.TtlReassemblyTimeExceeded;
517 break;
518 case 12:
519 return IPStatus.ParameterProblem;
520 case 4:
521 return IPStatus.SourceQuench;
522 case 8:
523 return IPStatus.Success;
525 return IPStatus.Unknown;
526 //throw new NotSupportedException (String.Format ("Unexpected pair of ICMP message type and code: type is {0} and code is {1}", Type, Code));
531 private string BuildPingArgs (IPAddress address, int timeout, PingOptions options)
533 CultureInfo culture = CultureInfo.InvariantCulture;
534 StringBuilder args = new StringBuilder ();
535 uint t = Convert.ToUInt32 (Math.Floor ((timeout + 1000) / 1000.0));
536 bool is_mac = Platform.IsMacOS;
537 if (!is_mac)
538 args.AppendFormat (culture, "-q -n -c {0} -w {1} -t {2} -M ", DefaultCount, t, options.Ttl);
539 else
540 args.AppendFormat (culture, "-q -n -c {0} -t {1} -o -m {2} ", DefaultCount, t, options.Ttl);
541 if (!is_mac)
542 args.Append (options.DontFragment ? "do " : "dont ");
543 else if (options.DontFragment)
544 args.Append ("-D ");
546 args.Append (address.ToString ());
548 return args.ToString ();
550 #endif // !MONOTOUCH
552 public Task<PingReply> SendPingAsync (IPAddress address, int timeout, byte [] buffer)
554 return SendPingAsync (address, default_timeout, default_buffer, new PingOptions ());
557 public Task<PingReply> SendPingAsync (IPAddress address, int timeout)
559 return SendPingAsync (address, default_timeout, default_buffer);
562 public Task<PingReply> SendPingAsync (IPAddress address)
564 return SendPingAsync (address, default_timeout);
567 public Task<PingReply> SendPingAsync (string hostNameOrAddress, int timeout, byte [] buffer)
569 return SendPingAsync (hostNameOrAddress, timeout, buffer, new PingOptions ());
572 public Task<PingReply> SendPingAsync (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options)
574 IPAddress address = Dns.GetHostEntry (hostNameOrAddress).AddressList [0];
575 return SendPingAsync (address, timeout, buffer, options);
578 public Task<PingReply> SendPingAsync (string hostNameOrAddress, int timeout)
580 return SendPingAsync (hostNameOrAddress, timeout, default_buffer);
583 public Task<PingReply> SendPingAsync (string hostNameOrAddress)
585 return SendPingAsync (hostNameOrAddress, default_timeout);
588 public Task<PingReply> SendPingAsync (IPAddress address, int timeout, byte [] buffer, PingOptions options)
590 if ((worker != null) || (cts != null))
591 throw new InvalidOperationException ("Another SendAsync operation is in progress");
593 cts = new CancellationTokenSource();
595 var task = Task<PingReply>.Factory.StartNew (
596 () => Send (address, timeout, buffer, options), cts.Token);
598 task.ContinueWith ((t) => {
599 if (t.IsCanceled)
600 OnPingCompleted (new PingCompletedEventArgs (null, true, null, null));
601 else if (t.IsFaulted)
602 OnPingCompleted (new PingCompletedEventArgs (t.Exception, false, null, null));
603 else
604 OnPingCompleted (new PingCompletedEventArgs (null, false, null, t.Result));
607 return task;