// 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")]
}
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
BackgroundWorker worker;
object user_async_state;
+ CancellationTokenSource cts;
public event PingCompletedEventHandler PingCompleted;
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")]
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
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 ()
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.
+
+ /* 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;
+#if MONO_FEATURE_PROCESS_START
+ DateTime sentTime = DateTime.UtcNow;
Process ping = new Process ();
string args = BuildPingArgs (address, timeout, options);
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
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 ();
public void SendAsyncCancel ()
{
+ if (cts != null) {
+ cts.Cancel ();
+ return;
+ }
+
if (worker == null)
throw new InvalidOperationException ("SendAsync operation is not in progress");
worker.CancelAsync ();
}
// 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;
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];
- Buffer.BlockCopy (bytes, 0, data, 0, data.Length);
+ Buffer.BlockCopy (bytes, 8, data, 0, data.Length);
return data;
}
}
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));
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
-