[System] Remove Process.Start and related API from TvOS/WatchOS.
[mono.git] / mcs / class / System / System.Net.NetworkInformation / Ping.cs
index 036e39d8dfc535c1d565c26741b8c9a7eab1bf76..abb6d5d6bce4f5fcae3d6a2a5b1e705925971278 100644 (file)
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
-#if NET_2_0
+
 using System;
+using System.IO;
 using System.Text;
 using System.Diagnostics;
 using System.Globalization;
 using System.ComponentModel;
 using System.Net.Sockets;
 using System.Security.Principal;
+using System.Security.Cryptography;
 using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
 
 namespace System.Net.NetworkInformation {
        [MonoTODO ("IPv6 support is missing")]
@@ -56,9 +60,17 @@ namespace System.Net.NetworkInformation {
                }
                
                const int DefaultCount = 1;
-               const string PingBinPath = "/bin/ping";
+               static readonly string [] PingBinPaths = new string [] {
+                       "/bin/ping",
+                       "/sbin/ping",
+                       "/usr/sbin/ping",
+#if MONODROID
+                       "/system/bin/ping"
+#endif
+               };
+               static readonly string PingBinPath;
                const int default_timeout = 4000; // 4 sec.
-               const int identifier = 1; // no need to be const, but there's no place to change it.
+               ushort identifier;
 
                // This value is correct as of Linux kernel version 2.6.25.9
                // See /usr/include/linux/capability.h
@@ -70,6 +82,7 @@ namespace System.Net.NetworkInformation {
 
                BackgroundWorker worker;
                object user_async_state;
+               CancellationTokenSource cts;
                
                public event PingCompletedEventHandler PingCompleted;
                
@@ -79,13 +92,29 @@ namespace System.Net.NetworkInformation {
                                CheckLinuxCapabilities ();
                                if (!canSendPrivileged && WindowsIdentity.GetCurrent ().Name == "root")
                                        canSendPrivileged = true;
+                       
+                               // Since different Unix systems can have different path to bin, we try some
+                               // of the known ones.
+                               foreach (string ping_path in PingBinPaths)
+                                       if (File.Exists (ping_path)) {
+                                               PingBinPath = ping_path;
+                                               break;
+                                       }
                        }
                        else
                                canSendPrivileged = true;
+
+                       if (PingBinPath == null)
+                               PingBinPath = "/bin/ping"; // default, fallback value
                }
                
                public Ping ()
                {
+                       // Generate a new random 16 bit identifier for every ping
+                       RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider ();
+                       byte [] randomIdentifier = new byte [2];
+                       rng.GetBytes (randomIdentifier);
+                       identifier = (ushort)(randomIdentifier [0] + (randomIdentifier [1] << 8));
                }
   
                [DllImport ("libc", EntryPoint="capget")]
@@ -121,10 +150,12 @@ namespace System.Net.NetworkInformation {
 
                protected void OnPingCompleted (PingCompletedEventArgs e)
                {
-                       if (PingCompleted != null)
-                               PingCompleted (this, e);
                        user_async_state = null;
                        worker = null;
+                       cts = null;
+
+                       if (PingCompleted != null)
+                               PingCompleted (this, e);
                }
 
                // Sync
@@ -161,8 +192,8 @@ namespace System.Net.NetworkInformation {
 
                public PingReply Send (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options)
                {
-                       IPAddress address = Dns.GetHostEntry (hostNameOrAddress).AddressList [0];
-                       return Send (address, timeout, buffer, options);
+                       IPAddress [] addresses = Dns.GetHostAddresses (hostNameOrAddress);
+                       return Send (addresses [0], timeout, buffer, options);
                }
 
                static IPAddress GetNonLoopbackIP ()
@@ -196,56 +227,68 @@ namespace System.Net.NetworkInformation {
                        IPEndPoint client = new IPEndPoint (GetNonLoopbackIP (), 0);
 
                        // FIXME: support IPv6
-                       Socket s = new Socket (AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);
-                       if (options != null) {
-                               s.DontFragment = options.DontFragment;
-                               s.Ttl = (short) options.Ttl;
-                       }
-                       s.SendTimeout = timeout;
-                       s.ReceiveTimeout = timeout;
-                       // not sure why Identifier = 0 is unacceptable ...
-                       IcmpMessage send = new IcmpMessage (8, 0, identifier, 0, buffer);
-                       byte [] bytes = send.GetBytes ();
-                       s.SendBufferSize = bytes.Length;
-                       s.SendTo (bytes, bytes.Length, SocketFlags.None, target);
-
-                       DateTime sentTime = DateTime.Now;
-
-                       // receive
-                       bytes = new byte [100];
-                       do {
-                               try {
+                       using (Socket s = new Socket (AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp)) {
+                               if (options != null) {
+                                       s.DontFragment = options.DontFragment;
+                                       s.Ttl = (short) options.Ttl;
+                               }
+                               s.SendTimeout = timeout;
+                               s.ReceiveTimeout = timeout;
+                               // not sure why Identifier = 0 is unacceptable ...
+                               IcmpMessage send = new IcmpMessage (8, 0, identifier, 0, buffer);
+                               byte [] bytes = send.GetBytes ();
+                               s.SendBufferSize = bytes.Length;
+                               s.SendTo (bytes, bytes.Length, SocketFlags.None, target);
+
+                               DateTime sentTime = DateTime.Now;
+
+                               // receive
+                               bytes = new byte [100];
+                               do {
                                        EndPoint endpoint = client;
-                                       int rc = s.ReceiveFrom (bytes, 100, SocketFlags.None, ref endpoint);
+                                       int error = 0;
+                                       int rc = s.ReceiveFrom_nochecks_exc (bytes, 0, 100, SocketFlags.None,
+                                                       ref endpoint, false, out error);
+
+                                       if (error != 0) {
+                                               if (error == (int) SocketError.TimedOut) {
+                                                       return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
+                                               }
+                                               throw new NotSupportedException (String.Format ("Unexpected socket error during ping request: {0}", error));
+                                       }
                                        long rtt = (long) (DateTime.Now - sentTime).TotalMilliseconds;
                                        int headerLength = (bytes [0] & 0xF) << 2;
                                        int bodyLength = rc - headerLength;
 
-                                       if (!((IPEndPoint) endpoint).Address.Equals (target.Address)) // Ping reply to different request. discard it.
+                                       // Ping reply to different request. discard it.
+                                       if (!((IPEndPoint) endpoint).Address.Equals (target.Address)) {
+                                               long t = timeout - rtt;
+                                               if (t <= 0)
+                                                       return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
+                                               s.ReceiveTimeout = (int) t;
                                                continue;
+                                       }
 
                                        IcmpMessage recv = new IcmpMessage (bytes, headerLength, bodyLength);
-                                       if (recv.Identifier != identifier)
-                                               continue; // ping reply to different request. discard it.
 
-                                       return new PingReply (address, recv.Data, options, rtt, recv.IPStatus);
-                               } catch (SocketException ex) {
-                                       IPStatus stat;
-                                       switch (ex.SocketErrorCode) {
-                                       case SocketError.TimedOut:
-                                               stat = IPStatus.TimedOut;
-                                               break;
-                                       default:
-                                               throw new NotSupportedException (String.Format ("Unexpected socket error during ping request: {0}", ex.SocketErrorCode));
+                                       /* discard ping reply to different request or echo requests if running on same host. */
+                                       if (recv.Identifier != identifier || recv.Type == 8) {
+                                               long t = timeout - rtt;
+                                               if (t <= 0)
+                                                       return new PingReply (null, new byte [0], options, 0, IPStatus.TimedOut);
+                                               s.ReceiveTimeout = (int) t;
+                                               continue; 
                                        }
-                                       return new PingReply (null, new byte [0], options, 0, stat);
-                               }
-                       } while (true);
+
+                                       return new PingReply (address, recv.Data, options, rtt, recv.IPStatus);
+                               } while (true);
+                       }
                }
 
                private PingReply SendUnprivileged (IPAddress address, int timeout, byte [] buffer, PingOptions options)
                {
-                       DateTime sentTime = DateTime.Now;
+#if MONO_FEATURE_PROCESS_START
+                       DateTime sentTime = DateTime.UtcNow;
 
                        Process ping = new Process ();
                        string args = BuildPingArgs (address, timeout, options);
@@ -260,33 +303,33 @@ namespace System.Net.NetworkInformation {
                        ping.StartInfo.RedirectStandardOutput = true;
                        ping.StartInfo.RedirectStandardError = true;
 
-                       DateTime start = DateTime.UtcNow;
+                       IPStatus status = IPStatus.Unknown;
                        try {
                                ping.Start ();
 
 #pragma warning disable 219
-                       // 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
-                       //      string stdout = ping.StandardOutput.ReadToEnd ();
-                       //      string stderr = ping.StandardError.ReadToEnd ();
+                               string stdout = ping.StandardOutput.ReadToEnd ();
+                               string stderr = ping.StandardError.ReadToEnd ();
 #pragma warning restore 219
                                
-                               trip_time = (long) (DateTime.Now - sentTime).TotalMilliseconds;
-                               if (!ping.WaitForExit (timeout) || ping.ExitCode == 2)
-                                       return new PingReply (address, buffer, options, trip_time, IPStatus.TimedOut); 
-
-                               if (ping.ExitCode == 1)
-                                       return new PingReply (address, buffer, options, trip_time, IPStatus.TtlExpired);
-                       } catch (Exception) {
-                               return new PingReply (address, buffer, options, trip_time, IPStatus.Unknown);
+                               trip_time = (long) (DateTime.UtcNow - sentTime).TotalMilliseconds;
+                               if (!ping.WaitForExit (timeout) || (ping.HasExited && ping.ExitCode == 2))
+                                       status = IPStatus.TimedOut;
+                               else if (ping.ExitCode == 0)
+                                       status = IPStatus.Success;
+                               else if (ping.ExitCode == 1)
+                                       status = IPStatus.TtlExpired;
+                       } catch {
                        } finally {
-                               if (ping != null) {
-                                       if (!ping.HasExited)
-                                               ping.Kill ();
-                                       ping.Dispose ();
-                               }
+                               if (!ping.HasExited)
+                                       ping.Kill ();
+                               ping.Dispose ();
                        }
 
-                       return new PingReply (address, buffer, options, trip_time, IPStatus.Success);
+                       return new PingReply (address, buffer, options, trip_time, status);
+#else
+                       throw new NotSupportedException ("Ping is not supported on this platform.");
+#endif // MONO_FEATURE_PROCESS_START
                }
 
                // Async
@@ -329,7 +372,7 @@ namespace System.Net.NetworkInformation {
 
                public void SendAsync (IPAddress address, int timeout, byte [] buffer, PingOptions options, object userToken)
                {
-                       if (worker != null)
+                       if ((worker != null) || (cts != null))
                                throw new InvalidOperationException ("Another SendAsync operation is in progress");
 
                        worker = new BackgroundWorker ();
@@ -353,6 +396,11 @@ namespace System.Net.NetworkInformation {
 
                public void SendAsyncCancel ()
                {
+                       if (cts != null) {
+                               cts.Cancel ();
+                               return;
+                       }
+
                        if (worker == null)
                                throw new InvalidOperationException ("SendAsync operation is not in progress");
                        worker.CancelAsync ();
@@ -368,11 +416,11 @@ namespace System.Net.NetworkInformation {
                        public IcmpMessage (byte [] bytes, int offset, int size)
                        {
                                this.bytes = new byte [size];
-                               Array.Copy (bytes, offset, this.bytes, 0, size);
+                               Buffer.BlockCopy (bytes, offset, this.bytes, 0, size);
                        }
 
                        // to be sent
-                       public IcmpMessage (byte type, byte code, short identifier, short sequence, byte [] data)
+                       public IcmpMessage (byte type, byte code, ushort identifier, ushort sequence, byte [] data)
                        {
                                bytes = new byte [data.Length + 8];
                                bytes [0] = type;
@@ -381,7 +429,7 @@ namespace System.Net.NetworkInformation {
                                bytes [5] = (byte) ((int) identifier >> 8);
                                bytes [6] = (byte) (sequence & 0xFF);
                                bytes [7] = (byte) ((int) sequence >> 8);
-                               Array.Copy (data, 0, bytes, 8, data.Length);
+                               Buffer.BlockCopy (data, 0, bytes, 8, data.Length);
 
                                ushort checksum = ComputeChecksum (bytes);
                                bytes [2] = (byte) (checksum & 0xFF);
@@ -396,18 +444,18 @@ namespace System.Net.NetworkInformation {
                                get { return bytes [1]; }
                        }
 
-                       public byte Identifier {
-                               get { return (byte) (bytes [4] + (bytes [5] << 8)); }
+                       public ushort Identifier {
+                               get { return (ushort) (bytes [4] + (bytes [5] << 8)); }
                        }
 
-                       public byte Sequence {
-                               get { return (byte) (bytes [6] + (bytes [7] << 8)); }
+                       public ushort Sequence {
+                               get { return (ushort) (bytes [6] + (bytes [7] << 8)); }
                        }
 
                        public byte [] Data {
                                get {
                                        byte [] data = new byte [bytes.Length - 8];
-                                       Array.Copy (bytes, 0, data, 0, data.Length);
+                                       Buffer.BlockCopy (bytes, 8, data, 0, data.Length);
                                        return data;
                                }
                        }
@@ -463,8 +511,11 @@ namespace System.Net.NetworkInformation {
                                                return IPStatus.ParameterProblem;
                                        case 4:
                                                return IPStatus.SourceQuench;
+                                       case 8:
+                                               return IPStatus.Success;
                                        }
-                                       throw new NotSupportedException (String.Format ("Unexpected pair of ICMP message type and code: type is {0} and code is {1}", Type, Code));
+                                       return IPStatus.Unknown;
+                                       //throw new NotSupportedException (String.Format ("Unexpected pair of ICMP message type and code: type is {0} and code is {1}", Type, Code));
                                }
                        }
                }
@@ -474,15 +525,75 @@ namespace System.Net.NetworkInformation {
                        CultureInfo culture = CultureInfo.InvariantCulture;
                        StringBuilder args = new StringBuilder ();
                        uint t = Convert.ToUInt32 (Math.Floor ((timeout + 1000) / 1000.0));
-                       args.AppendFormat (culture, "-q -n -c {0} -w {1} -t {2} -M ", DefaultCount, t, options.Ttl);
-                       args.Append (options.DontFragment ? "do " : "dont ");
+                       bool is_mac = Platform.IsMacOS;
+                       if (!is_mac)
+                               args.AppendFormat (culture, "-q -n -c {0} -w {1} -t {2} -M ", DefaultCount, t, options.Ttl);
+                       else
+                               args.AppendFormat (culture, "-q -n -c {0} -t {1} -o -m {2} ", DefaultCount, t, options.Ttl);
+                       if (!is_mac)
+                               args.Append (options.DontFragment ? "do " : "dont ");
+                       else if (options.DontFragment)
+                               args.Append ("-D ");
 
                        args.Append (address.ToString ());
 
                        return args.ToString ();
                }
 
+               public Task<PingReply> SendPingAsync (IPAddress address, int timeout, byte [] buffer)
+               {
+                       return SendPingAsync (address, default_timeout, default_buffer, new PingOptions ());
+               }
+
+               public Task<PingReply> SendPingAsync (IPAddress address, int timeout)
+               {
+                       return SendPingAsync (address, default_timeout, default_buffer);
+               }
+
+               public Task<PingReply> SendPingAsync (IPAddress address)
+               {
+                       return SendPingAsync (address, default_timeout);
+               }
+
+               public Task<PingReply> SendPingAsync (string hostNameOrAddress, int timeout, byte [] buffer)
+               {
+                       return SendPingAsync (hostNameOrAddress, timeout, buffer, new PingOptions ());
+               }
+
+               public Task<PingReply> SendPingAsync (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options)
+               {
+                       IPAddress address = Dns.GetHostEntry (hostNameOrAddress).AddressList [0];
+                       return SendPingAsync (address, timeout, buffer, options);
+               }
+
+               public Task<PingReply> SendPingAsync (string hostNameOrAddress, int timeout)
+               {
+                       return SendPingAsync (hostNameOrAddress, timeout, default_buffer);
+               }
+
+               public Task<PingReply> SendPingAsync (string hostNameOrAddress)
+               {
+                       return SendPingAsync (hostNameOrAddress, default_timeout);
+               }
+
+               public Task<PingReply> SendPingAsync (IPAddress address, int timeout, byte [] buffer, PingOptions options)
+               {
+                       if ((worker != null) || (cts != null))
+                               throw new InvalidOperationException ("Another SendAsync operation is in progress");
+
+                       var task = Task<PingReply>.Factory.StartNew (
+                               () => Send (address, timeout, buffer, options), cts.Token);
+
+                       task.ContinueWith ((t) => {
+                               if (t.IsCanceled)
+                                       OnPingCompleted (new PingCompletedEventArgs (null, true, null, null));
+                               else if (t.IsFaulted)
+                                       OnPingCompleted (new PingCompletedEventArgs (t.Exception, false, null, null));
+                               else
+                                       OnPingCompleted (new PingCompletedEventArgs (null, false, null, t.Result));
+                       });
+
+                       return task;
+               }
        }
 }
-#endif
-