[System] Remove Process.Start and related API from TvOS/WatchOS.
[mono-project.git] / mcs / class / System / System.Net.NetworkInformation / Ping.cs
blobabb6d5d6bce4f5fcae3d6a2a5b1e705925971278
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.
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 using System.Threading;
41 using System.Threading.Tasks;
43 namespace System.Net.NetworkInformation {
44 [MonoTODO ("IPv6 support is missing")]
45 public class Ping : Component, IDisposable
47 [StructLayout(LayoutKind.Sequential)]
48 struct cap_user_header_t
50 public UInt32 version;
51 public Int32 pid;
54 [StructLayout(LayoutKind.Sequential)]
55 struct cap_user_data_t
57 public UInt32 effective;
58 public UInt32 permitted;
59 public UInt32 inheritable;
62 const int DefaultCount = 1;
63 static readonly string [] PingBinPaths = new string [] {
64 "/bin/ping",
65 "/sbin/ping",
66 "/usr/sbin/ping",
67 #if MONODROID
68 "/system/bin/ping"
69 #endif
71 static readonly string PingBinPath;
72 const int default_timeout = 4000; // 4 sec.
73 ushort identifier;
75 // This value is correct as of Linux kernel version 2.6.25.9
76 // See /usr/include/linux/capability.h
77 const UInt32 linux_cap_version = 0x20071026;
79 static readonly byte [] default_buffer = new byte [0];
80 static bool canSendPrivileged;
83 BackgroundWorker worker;
84 object user_async_state;
85 CancellationTokenSource cts;
87 public event PingCompletedEventHandler PingCompleted;
89 static Ping ()
91 if (Environment.OSVersion.Platform == PlatformID.Unix) {
92 CheckLinuxCapabilities ();
93 if (!canSendPrivileged && WindowsIdentity.GetCurrent ().Name == "root")
94 canSendPrivileged = true;
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;
104 else
105 canSendPrivileged = true;
107 if (PingBinPath == null)
108 PingBinPath = "/bin/ping"; // default, fallback value
111 public Ping ()
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));
120 [DllImport ("libc", EntryPoint="capget")]
121 static extern int capget (ref cap_user_header_t header, ref cap_user_data_t data);
123 static void CheckLinuxCapabilities ()
125 try {
126 cap_user_header_t header = new cap_user_header_t ();
127 cap_user_data_t data = new cap_user_data_t ();
129 header.version = linux_cap_version;
131 int ret = -1;
133 try {
134 ret = capget (ref header, ref data);
135 } catch (Exception) {
138 if (ret == -1)
139 return;
141 canSendPrivileged = (data.effective & (1 << 13)) != 0;
142 } catch {
143 canSendPrivileged = false;
147 void IDisposable.Dispose ()
151 protected void OnPingCompleted (PingCompletedEventArgs e)
153 user_async_state = null;
154 worker = null;
155 cts = null;
157 if (PingCompleted != null)
158 PingCompleted (this, e);
161 // Sync
163 public PingReply Send (IPAddress address)
165 return Send (address, default_timeout);
168 public PingReply Send (IPAddress address, int timeout)
170 return Send (address, timeout, default_buffer);
173 public PingReply Send (IPAddress address, int timeout, byte [] buffer)
175 return Send (address, timeout, buffer, new PingOptions ());
178 public PingReply Send (string hostNameOrAddress)
180 return Send (hostNameOrAddress, default_timeout);
183 public PingReply Send (string hostNameOrAddress, int timeout)
185 return Send (hostNameOrAddress, timeout, default_buffer);
188 public PingReply Send (string hostNameOrAddress, int timeout, byte [] buffer)
190 return Send (hostNameOrAddress, timeout, buffer, new PingOptions ());
193 public PingReply Send (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options)
195 IPAddress [] addresses = Dns.GetHostAddresses (hostNameOrAddress);
196 return Send (addresses [0], timeout, buffer, options);
199 static IPAddress GetNonLoopbackIP ()
201 foreach (IPAddress addr in Dns.GetHostByName (Dns.GetHostName ()).AddressList)
202 if (!IPAddress.IsLoopback (addr))
203 return addr;
204 throw new InvalidOperationException ("Could not resolve non-loopback IP address for localhost");
207 public PingReply Send (IPAddress address, int timeout, byte [] buffer, PingOptions options)
209 if (address == null)
210 throw new ArgumentNullException ("address");
211 if (timeout < 0)
212 throw new ArgumentOutOfRangeException ("timeout", "timeout must be non-negative integer");
213 if (buffer == null)
214 throw new ArgumentNullException ("buffer");
215 if (buffer.Length > 65500)
216 throw new ArgumentException ("buffer");
217 // options can be null.
219 if (canSendPrivileged)
220 return SendPrivileged (address, timeout, buffer, options);
221 return SendUnprivileged (address, timeout, buffer, options);
224 private PingReply SendPrivileged (IPAddress address, int timeout, byte [] buffer, PingOptions options)
226 IPEndPoint target = new IPEndPoint (address, 0);
227 IPEndPoint client = new IPEndPoint (GetNonLoopbackIP (), 0);
229 // FIXME: support IPv6
230 using (Socket s = new Socket (AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp)) {
231 if (options != null) {
232 s.DontFragment = options.DontFragment;
233 s.Ttl = (short) options.Ttl;
235 s.SendTimeout = timeout;
236 s.ReceiveTimeout = timeout;
237 // not sure why Identifier = 0 is unacceptable ...
238 IcmpMessage send = new IcmpMessage (8, 0, identifier, 0, buffer);
239 byte [] bytes = send.GetBytes ();
240 s.SendBufferSize = bytes.Length;
241 s.SendTo (bytes, bytes.Length, SocketFlags.None, target);
243 DateTime sentTime = DateTime.Now;
245 // receive
246 bytes = new byte [100];
247 do {
248 EndPoint endpoint = client;
249 int error = 0;
250 int rc = s.ReceiveFrom_nochecks_exc (bytes, 0, 100, SocketFlags.None,
251 ref endpoint, false, out error);
253 if (error != 0) {
254 if (error == (int) SocketError.TimedOut) {
255 return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
257 throw new NotSupportedException (String.Format ("Unexpected socket error during ping request: {0}", error));
259 long rtt = (long) (DateTime.Now - sentTime).TotalMilliseconds;
260 int headerLength = (bytes [0] & 0xF) << 2;
261 int bodyLength = rc - headerLength;
263 // Ping reply to different request. discard it.
264 if (!((IPEndPoint) endpoint).Address.Equals (target.Address)) {
265 long t = timeout - rtt;
266 if (t <= 0)
267 return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
268 s.ReceiveTimeout = (int) t;
269 continue;
272 IcmpMessage recv = new IcmpMessage (bytes, headerLength, bodyLength);
274 /* discard ping reply to different request or echo requests if running on same host. */
275 if (recv.Identifier != identifier || recv.Type == 8) {
276 long t = timeout - rtt;
277 if (t <= 0)
278 return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
279 s.ReceiveTimeout = (int) t;
280 continue;
283 return new PingReply (address, recv.Data, options, rtt, recv.IPStatus);
284 } while (true);
288 private PingReply SendUnprivileged (IPAddress address, int timeout, byte [] buffer, PingOptions options)
290 #if MONO_FEATURE_PROCESS_START
291 DateTime sentTime = DateTime.UtcNow;
293 Process ping = new Process ();
294 string args = BuildPingArgs (address, timeout, options);
295 long trip_time = 0;
297 ping.StartInfo.FileName = PingBinPath;
298 ping.StartInfo.Arguments = args;
300 ping.StartInfo.CreateNoWindow = true;
301 ping.StartInfo.UseShellExecute = false;
303 ping.StartInfo.RedirectStandardOutput = true;
304 ping.StartInfo.RedirectStandardError = true;
306 IPStatus status = IPStatus.Unknown;
307 try {
308 ping.Start ();
310 #pragma warning disable 219
311 string stdout = ping.StandardOutput.ReadToEnd ();
312 string stderr = ping.StandardError.ReadToEnd ();
313 #pragma warning restore 219
315 trip_time = (long) (DateTime.UtcNow - sentTime).TotalMilliseconds;
316 if (!ping.WaitForExit (timeout) || (ping.HasExited && ping.ExitCode == 2))
317 status = IPStatus.TimedOut;
318 else if (ping.ExitCode == 0)
319 status = IPStatus.Success;
320 else if (ping.ExitCode == 1)
321 status = IPStatus.TtlExpired;
322 } catch {
323 } finally {
324 if (!ping.HasExited)
325 ping.Kill ();
326 ping.Dispose ();
329 return new PingReply (address, buffer, options, trip_time, status);
330 #else
331 throw new NotSupportedException ("Ping is not supported on this platform.");
332 #endif // MONO_FEATURE_PROCESS_START
335 // Async
337 public void SendAsync (IPAddress address, int timeout, byte [] buffer, object userToken)
339 SendAsync (address, default_timeout, default_buffer, new PingOptions (), userToken);
342 public void SendAsync (IPAddress address, int timeout, object userToken)
344 SendAsync (address, default_timeout, default_buffer, userToken);
347 public void SendAsync (IPAddress address, object userToken)
349 SendAsync (address, default_timeout, userToken);
352 public void SendAsync (string hostNameOrAddress, int timeout, byte [] buffer, object userToken)
354 SendAsync (hostNameOrAddress, timeout, buffer, new PingOptions (), userToken);
357 public void SendAsync (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options, object userToken)
359 IPAddress address = Dns.GetHostEntry (hostNameOrAddress).AddressList [0];
360 SendAsync (address, timeout, buffer, options, userToken);
363 public void SendAsync (string hostNameOrAddress, int timeout, object userToken)
365 SendAsync (hostNameOrAddress, timeout, default_buffer, userToken);
368 public void SendAsync (string hostNameOrAddress, object userToken)
370 SendAsync (hostNameOrAddress, default_timeout, userToken);
373 public void SendAsync (IPAddress address, int timeout, byte [] buffer, PingOptions options, object userToken)
375 if ((worker != null) || (cts != null))
376 throw new InvalidOperationException ("Another SendAsync operation is in progress");
378 worker = new BackgroundWorker ();
379 worker.DoWork += delegate (object o, DoWorkEventArgs ea) {
380 try {
381 user_async_state = ea.Argument;
382 ea.Result = Send (address, timeout, buffer, options);
383 } catch (Exception ex) {
384 ea.Result = ex;
387 worker.WorkerSupportsCancellation = true;
388 worker.RunWorkerCompleted += delegate (object o, RunWorkerCompletedEventArgs ea) {
389 // Note that RunWorkerCompletedEventArgs.UserState cannot be used (LAMESPEC)
390 OnPingCompleted (new PingCompletedEventArgs (ea.Error, ea.Cancelled, user_async_state, ea.Result as PingReply));
392 worker.RunWorkerAsync (userToken);
395 // SendAsyncCancel
397 public void SendAsyncCancel ()
399 if (cts != null) {
400 cts.Cancel ();
401 return;
404 if (worker == null)
405 throw new InvalidOperationException ("SendAsync operation is not in progress");
406 worker.CancelAsync ();
409 // ICMP message
411 class IcmpMessage
413 byte [] bytes;
415 // received
416 public IcmpMessage (byte [] bytes, int offset, int size)
418 this.bytes = new byte [size];
419 Buffer.BlockCopy (bytes, offset, this.bytes, 0, size);
422 // to be sent
423 public IcmpMessage (byte type, byte code, ushort identifier, ushort sequence, byte [] data)
425 bytes = new byte [data.Length + 8];
426 bytes [0] = type;
427 bytes [1] = code;
428 bytes [4] = (byte) (identifier & 0xFF);
429 bytes [5] = (byte) ((int) identifier >> 8);
430 bytes [6] = (byte) (sequence & 0xFF);
431 bytes [7] = (byte) ((int) sequence >> 8);
432 Buffer.BlockCopy (data, 0, bytes, 8, data.Length);
434 ushort checksum = ComputeChecksum (bytes);
435 bytes [2] = (byte) (checksum & 0xFF);
436 bytes [3] = (byte) ((int) checksum >> 8);
439 public byte Type {
440 get { return bytes [0]; }
443 public byte Code {
444 get { return bytes [1]; }
447 public ushort Identifier {
448 get { return (ushort) (bytes [4] + (bytes [5] << 8)); }
451 public ushort Sequence {
452 get { return (ushort) (bytes [6] + (bytes [7] << 8)); }
455 public byte [] Data {
456 get {
457 byte [] data = new byte [bytes.Length - 8];
458 Buffer.BlockCopy (bytes, 8, data, 0, data.Length);
459 return data;
463 public byte [] GetBytes ()
465 return bytes;
468 static ushort ComputeChecksum (byte [] data)
470 uint ret = 0;
471 for (int i = 0; i < data.Length; i += 2) {
472 ushort us = i + 1 < data.Length ? data [i + 1] : (byte) 0;
473 us <<= 8;
474 us += data [i];
475 ret += us;
477 ret = (ret >> 16) + (ret & 0xFFFF);
478 return (ushort) ~ ret;
481 public IPStatus IPStatus {
482 get {
483 switch (Type) {
484 case 0:
485 return IPStatus.Success;
486 case 3: // destination unreacheable
487 switch (Code) {
488 case 0:
489 return IPStatus.DestinationNetworkUnreachable;
490 case 1:
491 return IPStatus.DestinationHostUnreachable;
492 case 2:
493 return IPStatus.DestinationProtocolUnreachable;
494 case 3:
495 return IPStatus.DestinationPortUnreachable;
496 case 4:
497 return IPStatus.BadOption; // FIXME: likely wrong
498 case 5:
499 return IPStatus.BadRoute; // not sure if it is correct
501 break;
502 case 11:
503 switch (Code) {
504 case 0:
505 return IPStatus.TimeExceeded;
506 case 1:
507 return IPStatus.TtlReassemblyTimeExceeded;
509 break;
510 case 12:
511 return IPStatus.ParameterProblem;
512 case 4:
513 return IPStatus.SourceQuench;
514 case 8:
515 return IPStatus.Success;
517 return IPStatus.Unknown;
518 //throw new NotSupportedException (String.Format ("Unexpected pair of ICMP message type and code: type is {0} and code is {1}", Type, Code));
523 private string BuildPingArgs (IPAddress address, int timeout, PingOptions options)
525 CultureInfo culture = CultureInfo.InvariantCulture;
526 StringBuilder args = new StringBuilder ();
527 uint t = Convert.ToUInt32 (Math.Floor ((timeout + 1000) / 1000.0));
528 bool is_mac = Platform.IsMacOS;
529 if (!is_mac)
530 args.AppendFormat (culture, "-q -n -c {0} -w {1} -t {2} -M ", DefaultCount, t, options.Ttl);
531 else
532 args.AppendFormat (culture, "-q -n -c {0} -t {1} -o -m {2} ", DefaultCount, t, options.Ttl);
533 if (!is_mac)
534 args.Append (options.DontFragment ? "do " : "dont ");
535 else if (options.DontFragment)
536 args.Append ("-D ");
538 args.Append (address.ToString ());
540 return args.ToString ();
543 public Task<PingReply> SendPingAsync (IPAddress address, int timeout, byte [] buffer)
545 return SendPingAsync (address, default_timeout, default_buffer, new PingOptions ());
548 public Task<PingReply> SendPingAsync (IPAddress address, int timeout)
550 return SendPingAsync (address, default_timeout, default_buffer);
553 public Task<PingReply> SendPingAsync (IPAddress address)
555 return SendPingAsync (address, default_timeout);
558 public Task<PingReply> SendPingAsync (string hostNameOrAddress, int timeout, byte [] buffer)
560 return SendPingAsync (hostNameOrAddress, timeout, buffer, new PingOptions ());
563 public Task<PingReply> SendPingAsync (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options)
565 IPAddress address = Dns.GetHostEntry (hostNameOrAddress).AddressList [0];
566 return SendPingAsync (address, timeout, buffer, options);
569 public Task<PingReply> SendPingAsync (string hostNameOrAddress, int timeout)
571 return SendPingAsync (hostNameOrAddress, timeout, default_buffer);
574 public Task<PingReply> SendPingAsync (string hostNameOrAddress)
576 return SendPingAsync (hostNameOrAddress, default_timeout);
579 public Task<PingReply> SendPingAsync (IPAddress address, int timeout, byte [] buffer, PingOptions options)
581 if ((worker != null) || (cts != null))
582 throw new InvalidOperationException ("Another SendAsync operation is in progress");
584 var task = Task<PingReply>.Factory.StartNew (
585 () => Send (address, timeout, buffer, options), cts.Token);
587 task.ContinueWith ((t) => {
588 if (t.IsCanceled)
589 OnPingCompleted (new PingCompletedEventArgs (null, true, null, null));
590 else if (t.IsFaulted)
591 OnPingCompleted (new PingCompletedEventArgs (t.Exception, false, null, null));
592 else
593 OnPingCompleted (new PingCompletedEventArgs (null, false, null, t.Result));
596 return task;