//
// System.Net.NetworkInformation.Ping
//
-// Author:
+// Authors:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
+// Atsushi Enomoto (atsushi@ximian.com)
//
-// Copyright (c) 2006 Novell, Inc. (http://www.novell.com)
+// Copyright (c) 2006-2007 Novell, Inc. (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// 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;
+#if NET_4_5
+using System.Threading;
+using System.Threading.Tasks;
+#endif
namespace System.Net.NetworkInformation {
+ [MonoTODO ("IPv6 support is missing")]
public class Ping : Component, IDisposable
{
+ [StructLayout(LayoutKind.Sequential)]
+ struct cap_user_header_t
+ {
+ public UInt32 version;
+ public Int32 pid;
+ };
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct cap_user_data_t
+ {
+ public UInt32 effective;
+ public UInt32 permitted;
+ public UInt32 inheritable;
+ }
+
+ const int DefaultCount = 1;
+ static readonly string [] PingBinPaths = new string [] {
+ "/bin/ping",
+ "/sbin/ping",
+ "/usr/sbin/ping"
+ };
+ static readonly string PingBinPath;
+ const int default_timeout = 4000; // 4 sec.
+ ushort identifier;
+
+ // This value is correct as of Linux kernel version 2.6.25.9
+ // See /usr/include/linux/capability.h
+ const UInt32 linux_cap_version = 0x20071026;
+
+ static readonly byte [] default_buffer = new byte [0];
+ static bool canSendPrivileged;
+
+ BackgroundWorker worker;
+ object user_async_state;
+#if NET_4_5
+ CancellationTokenSource cts;
+#endif
+
public event PingCompletedEventHandler PingCompleted;
+
+ static Ping ()
+ {
+ if (Environment.OSVersion.Platform == PlatformID.Unix) {
+ 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")]
+ static extern int capget (ref cap_user_header_t header, ref cap_user_data_t data);
+
+ static void CheckLinuxCapabilities ()
+ {
+ try {
+ cap_user_header_t header = new cap_user_header_t ();
+ cap_user_data_t data = new cap_user_data_t ();
+
+ header.version = linux_cap_version;
+
+ int ret = -1;
+
+ try {
+ ret = capget (ref header, ref data);
+ } catch (Exception) {
+ }
+
+ if (ret == -1)
+ return;
+
+ canSendPrivileged = (data.effective & (1 << 13)) != 0;
+ } catch {
+ canSendPrivileged = false;
+ }
+ }
+
+ void IDisposable.Dispose ()
{
}
protected void OnPingCompleted (PingCompletedEventArgs e)
{
+ user_async_state = null;
+ worker = null;
+#if NET_4_5
+ cts = null;
+#endif
+
if (PingCompleted != null)
PingCompleted (this, e);
}
- [MonoTODO]
+ // Sync
+
public PingReply Send (IPAddress address)
{
- return null;
+ return Send (address, default_timeout);
}
- [MonoTODO]
public PingReply Send (IPAddress address, int timeout)
{
- return null;
+ return Send (address, timeout, default_buffer);
}
- [MonoTODO]
public PingReply Send (IPAddress address, int timeout, byte [] buffer)
{
- return null;
+ return Send (address, timeout, buffer, new PingOptions ());
}
- [MonoTODO]
- public PingReply Send (IPAddress address, int timeout, byte [] buffer, PingOptions options)
- {
- return null;
- }
-
- [MonoTODO]
public PingReply Send (string hostNameOrAddress)
{
- return null;
+ return Send (hostNameOrAddress, default_timeout);
}
- [MonoTODO]
public PingReply Send (string hostNameOrAddress, int timeout)
{
- return null;
+ return Send (hostNameOrAddress, timeout, default_buffer);
}
- [MonoTODO]
public PingReply Send (string hostNameOrAddress, int timeout, byte [] buffer)
{
- return null;
+ return Send (hostNameOrAddress, timeout, buffer, new PingOptions ());
}
- [MonoTODO]
public PingReply Send (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options)
{
- return null;
+ IPAddress [] addresses = Dns.GetHostAddresses (hostNameOrAddress);
+ return Send (addresses [0], timeout, buffer, options);
}
- [MonoTODO]
- public void SendAsync (IPAddress address, int timeout, byte [] buffer, object userToken)
+ static IPAddress GetNonLoopbackIP ()
{
+ foreach (IPAddress addr in Dns.GetHostByName (Dns.GetHostName ()).AddressList)
+ if (!IPAddress.IsLoopback (addr))
+ return addr;
+ throw new InvalidOperationException ("Could not resolve non-loopback IP address for localhost");
}
- [MonoTODO]
- public void SendAsync (IPAddress address, int timeout, byte [] buffer, PingOptions options, object userToken)
+ public PingReply Send (IPAddress address, int timeout, byte [] buffer, PingOptions options)
+ {
+ if (address == null)
+ throw new ArgumentNullException ("address");
+ if (timeout < 0)
+ throw new ArgumentOutOfRangeException ("timeout", "timeout must be non-negative integer");
+ if (buffer == null)
+ throw new ArgumentNullException ("buffer");
+ if (buffer.Length > 65500)
+ throw new ArgumentException ("buffer");
+ // options can be null.
+
+ if (canSendPrivileged)
+ return SendPrivileged (address, timeout, buffer, options);
+ return SendUnprivileged (address, timeout, buffer, options);
+ }
+
+ private PingReply SendPrivileged (IPAddress address, int timeout, byte [] buffer, PingOptions options)
+ {
+ IPEndPoint target = new IPEndPoint (address, 0);
+ IPEndPoint client = new IPEndPoint (GetNonLoopbackIP (), 0);
+
+ // FIXME: support IPv6
+ 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 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;
+
+ // 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);
+
+ /* 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 (address, recv.Data, options, rtt, recv.IPStatus);
+ } while (true);
+ }
+ }
+
+ private PingReply SendUnprivileged (IPAddress address, int timeout, byte [] buffer, PingOptions options)
+ {
+ DateTime sentTime = DateTime.Now;
+
+ Process ping = new Process ();
+ string args = BuildPingArgs (address, timeout, options);
+ long trip_time = 0;
+
+ ping.StartInfo.FileName = PingBinPath;
+ ping.StartInfo.Arguments = args;
+
+ ping.StartInfo.CreateNoWindow = true;
+ ping.StartInfo.UseShellExecute = false;
+
+ ping.StartInfo.RedirectStandardOutput = true;
+ ping.StartInfo.RedirectStandardError = true;
+
+ try {
+ ping.Start ();
+
+#pragma warning disable 219
+ 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.HasExited && 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);
+ } finally {
+ if (ping != null) {
+ if (!ping.HasExited)
+ ping.Kill ();
+ ping.Dispose ();
+ }
+ }
+
+ return new PingReply (address, buffer, options, trip_time, IPStatus.Success);
+ }
+
+ // Async
+
+ public void SendAsync (IPAddress address, int timeout, byte [] buffer, object userToken)
{
+ SendAsync (address, default_timeout, default_buffer, new PingOptions (), userToken);
}
- [MonoTODO]
public void SendAsync (IPAddress address, int timeout, object userToken)
{
+ SendAsync (address, default_timeout, default_buffer, userToken);
}
- [MonoTODO]
public void SendAsync (IPAddress address, object userToken)
{
+ SendAsync (address, default_timeout, userToken);
}
- [MonoTODO]
public void SendAsync (string hostNameOrAddress, int timeout, byte [] buffer, object userToken)
{
+ SendAsync (hostNameOrAddress, timeout, buffer, new PingOptions (), userToken);
}
- [MonoTODO]
public void SendAsync (string hostNameOrAddress, int timeout, byte [] buffer, PingOptions options, object userToken)
{
+ IPAddress address = Dns.GetHostEntry (hostNameOrAddress).AddressList [0];
+ SendAsync (address, timeout, buffer, options, userToken);
}
- [MonoTODO]
public void SendAsync (string hostNameOrAddress, int timeout, object userToken)
{
+ SendAsync (hostNameOrAddress, timeout, default_buffer, userToken);
}
- [MonoTODO]
public void SendAsync (string hostNameOrAddress, object userToken)
{
+ SendAsync (hostNameOrAddress, default_timeout, userToken);
+ }
+
+ public void SendAsync (IPAddress address, int timeout, byte [] buffer, PingOptions options, object userToken)
+ {
+#if NET_4_5
+ if ((worker != null) || (cts != null))
+ throw new InvalidOperationException ("Another SendAsync operation is in progress");
+#else
+ if (worker != null)
+ throw new InvalidOperationException ("Another SendAsync operation is in progress");
+#endif
+
+ worker = new BackgroundWorker ();
+ worker.DoWork += delegate (object o, DoWorkEventArgs ea) {
+ try {
+ user_async_state = ea.Argument;
+ ea.Result = Send (address, timeout, buffer, options);
+ } catch (Exception ex) {
+ ea.Result = ex;
+ }
+ };
+ worker.WorkerSupportsCancellation = true;
+ worker.RunWorkerCompleted += delegate (object o, RunWorkerCompletedEventArgs ea) {
+ // Note that RunWorkerCompletedEventArgs.UserState cannot be used (LAMESPEC)
+ OnPingCompleted (new PingCompletedEventArgs (ea.Error, ea.Cancelled, user_async_state, ea.Result as PingReply));
+ };
+ worker.RunWorkerAsync (userToken);
}
- [MonoTODO]
+ // SendAsyncCancel
+
public void SendAsyncCancel ()
{
+#if NET_4_5
+ if (cts != null) {
+ cts.Cancel ();
+ return;
+ }
+#endif
+
+ if (worker == null)
+ throw new InvalidOperationException ("SendAsync operation is not in progress");
+ worker.CancelAsync ();
}
- void IDisposable.Dispose ()
+ // ICMP message
+
+ class IcmpMessage
{
+ byte [] bytes;
+
+ // received
+ public IcmpMessage (byte [] bytes, int offset, int size)
+ {
+ this.bytes = new byte [size];
+ Buffer.BlockCopy (bytes, offset, this.bytes, 0, size);
+ }
+
+ // to be sent
+ public IcmpMessage (byte type, byte code, ushort identifier, ushort sequence, byte [] data)
+ {
+ bytes = new byte [data.Length + 8];
+ bytes [0] = type;
+ bytes [1] = code;
+ bytes [4] = (byte) (identifier & 0xFF);
+ bytes [5] = (byte) ((int) identifier >> 8);
+ bytes [6] = (byte) (sequence & 0xFF);
+ bytes [7] = (byte) ((int) sequence >> 8);
+ Buffer.BlockCopy (data, 0, bytes, 8, data.Length);
+
+ ushort checksum = ComputeChecksum (bytes);
+ bytes [2] = (byte) (checksum & 0xFF);
+ bytes [3] = (byte) ((int) checksum >> 8);
+ }
+
+ public byte Type {
+ get { return bytes [0]; }
+ }
+
+ public byte Code {
+ get { return bytes [1]; }
+ }
+
+ public ushort Identifier {
+ get { return (ushort) (bytes [4] + (bytes [5] << 8)); }
+ }
+
+ public ushort Sequence {
+ get { return (ushort) (bytes [6] + (bytes [7] << 8)); }
+ }
+
+ public byte [] Data {
+ get {
+ byte [] data = new byte [bytes.Length - 8];
+ Buffer.BlockCopy (bytes, 8, data, 0, data.Length);
+ return data;
+ }
+ }
+
+ public byte [] GetBytes ()
+ {
+ return bytes;
+ }
+
+ static ushort ComputeChecksum (byte [] data)
+ {
+ uint ret = 0;
+ for (int i = 0; i < data.Length; i += 2) {
+ ushort us = i + 1 < data.Length ? data [i + 1] : (byte) 0;
+ us <<= 8;
+ us += data [i];
+ ret += us;
+ }
+ ret = (ret >> 16) + (ret & 0xFFFF);
+ return (ushort) ~ ret;
+ }
+
+ public IPStatus IPStatus {
+ get {
+ switch (Type) {
+ case 0:
+ return IPStatus.Success;
+ case 3: // destination unreacheable
+ switch (Code) {
+ case 0:
+ return IPStatus.DestinationNetworkUnreachable;
+ case 1:
+ return IPStatus.DestinationHostUnreachable;
+ case 2:
+ return IPStatus.DestinationProtocolUnreachable;
+ case 3:
+ return IPStatus.DestinationPortUnreachable;
+ case 4:
+ return IPStatus.BadOption; // FIXME: likely wrong
+ case 5:
+ return IPStatus.BadRoute; // not sure if it is correct
+ }
+ break;
+ case 11:
+ switch (Code) {
+ case 0:
+ return IPStatus.TimeExceeded;
+ case 1:
+ return IPStatus.TtlReassemblyTimeExceeded;
+ }
+ break;
+ case 12:
+ return IPStatus.ParameterProblem;
+ case 4:
+ return IPStatus.SourceQuench;
+ case 8:
+ return IPStatus.Success;
+ }
+ 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));
+ }
+ }
}
+
+ private string BuildPingArgs (IPAddress address, int timeout, PingOptions options)
+ {
+ CultureInfo culture = CultureInfo.InvariantCulture;
+ StringBuilder args = new StringBuilder ();
+ uint t = Convert.ToUInt32 (Math.Floor ((timeout + 1000) / 1000.0));
+ bool is_mac = ((int) Environment.OSVersion.Platform == 6);
+ 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 ();
+ }
+
+#if NET_4_5
+ 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
}
}
-#endif