871 lines
36 KiB
C#
871 lines
36 KiB
C#
using DTS.Common.Interface.StatusAndProgressBar;
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Data;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.NetworkInformation;
|
|
using System.Net.Sockets;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using DTS.Common.Utilities.Logging;
|
|
|
|
namespace DTS.Common.Utils
|
|
{
|
|
public class PingUtils
|
|
{
|
|
public static void EliminateBadHosts(ref List<HostInfo> list, string[] ips)
|
|
{
|
|
var hosts = list.ToArray();
|
|
foreach (var host in hosts)
|
|
{
|
|
if (!HostMatchesAnyIPs(host, ips))
|
|
{
|
|
APILogger.Log($"Removing host {host.HostIpAddress} as it can't reach any ips");
|
|
list.Remove(host);
|
|
}
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// returns false if the host can not access any ips provided after looking at it's ip address and netmask
|
|
/// returns true if it can reach an ip or it can't be determined if it can reach an IP this might happen if someone used
|
|
/// a host name instead of a xxx.yyy.zzz.aaa style address or an empty string somewhere
|
|
/// </summary>
|
|
/// <param name="host"></param>
|
|
/// <param name="ips"></param>
|
|
/// <returns></returns>
|
|
protected static bool HostMatchesAnyIPs(HostInfo host, string[] ips)
|
|
{
|
|
if (ips.Length == 0) { return true; }
|
|
if (string.Empty.Equals(host.StartAddress) || string.Empty.Equals(host.EndAddress)) { return true; }
|
|
//FB 43919
|
|
List<bool> result = new List<bool>();
|
|
foreach (var ip in ips)
|
|
{
|
|
try
|
|
{
|
|
if (!IPAddress.TryParse(ip, out var ipToConnectTo))
|
|
{
|
|
//it's not a traditional format ip, give up trying to match it
|
|
return true;
|
|
}
|
|
if (!IPAddress.TryParse(host.StartAddress, out var startAddress))
|
|
{
|
|
//start address is not a traditional format, give up trying to match
|
|
return true;
|
|
}
|
|
if (!IPAddress.TryParse(host.EndAddress, out var endAddress))
|
|
{
|
|
//start address is not in traditional format, give up trying to match
|
|
return true;
|
|
}
|
|
var startBytes = startAddress.GetAddressBytes();
|
|
var endBytes = endAddress.GetAddressBytes();
|
|
var ipByte = ipToConnectTo.GetAddressBytes();
|
|
|
|
for (var i = 0; i < startBytes.Length && i < endBytes.Length && i < ipByte.Length; i++)
|
|
{
|
|
//FB 43919 If this condition is true as first iteration then break immediately and result collection should have only one false
|
|
//which means we need to remove this ip since it's not in the network
|
|
if (ipByte[i] < startBytes[i] || ipByte[i] > endBytes[i])
|
|
{
|
|
result.Add(false);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
result.Add(true);
|
|
}
|
|
}
|
|
//FB 43919 if any of the segment was true then don't remove the ip
|
|
return result.Exists(p => p);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
APILogger.Log(ex);
|
|
}
|
|
}
|
|
//FB 43919
|
|
return false;
|
|
}
|
|
//FB 18152 & 25642 This dictionary keeps the host info for each DAS
|
|
public static ConcurrentDictionary<string, HostInfo> DasToHost { get; set; } = new ConcurrentDictionary<string, HostInfo>();
|
|
public class PingDevice
|
|
{
|
|
|
|
private const int MAX_PING_ATTEMPTS = 3;
|
|
private const string ALPHABET = "abcdefghijklmnopqrstuvwxyz";
|
|
|
|
#region Ping
|
|
|
|
public bool PingDevices(List<string> ipList)
|
|
{
|
|
//FB 18152 Get the avilable hosts
|
|
var hostInfos = NetworkUtils.GetAvailableHosts();
|
|
EliminateBadHosts(ref hostInfos, ipList?.ToArray());
|
|
return PingAllDevices(hostInfos, ipList);
|
|
}
|
|
|
|
#region Ping All Devices
|
|
|
|
|
|
/// <summary>
|
|
/// ICMP ping
|
|
/// </summary>
|
|
/// <param name="hostIpAddress">host IP address</param>
|
|
/// <param name="ipList">List of connected devices IP addresses </param>
|
|
/// <returns>false - if as least one device disconnected</returns>
|
|
private bool PingAllDevices(List<HostInfo> hostInfos, List<string> ipList)
|
|
{
|
|
var hostStringList = new List<string>();
|
|
foreach (var host in hostInfos)
|
|
{
|
|
hostStringList.Add(host.HostIpAddress);
|
|
}
|
|
APILogger.Log($"trying to ping {string.Join(", ", ipList.ToArray())} using hosts: {string.Join(", ", hostStringList.ToArray())}");
|
|
var allConnected = true;
|
|
Parallel.ForEach(ipList, (item, state) =>
|
|
{
|
|
if (hostInfos.Any())
|
|
{
|
|
var lastHost = hostInfos.Last();
|
|
//FB 18152 & 25642 Perform the ping on all the devices in all hosts
|
|
foreach (var hostInfo in hostInfos)
|
|
{
|
|
var eachConnected = PingOneDevice(item, hostInfo.HostIpAddress);
|
|
if (eachConnected)
|
|
{
|
|
//We found the host update the dictionary and break from the loop
|
|
DasToHost[item] = hostInfo;
|
|
allConnected = eachConnected;
|
|
APILogger.Log($"pinging {string.Join(", ", ipList.ToArray())} worked for {hostInfo.HostIpAddress}");
|
|
break;
|
|
}
|
|
if (hostInfo.Equals(lastHost))
|
|
{
|
|
//all the hosts are processed
|
|
allConnected = eachConnected;
|
|
}
|
|
}
|
|
if (!allConnected) state.Break();
|
|
}
|
|
});
|
|
if (allConnected) { APILogger.Log("An interface could ping"); }
|
|
else { APILogger.Log("no intefaces could ping"); }
|
|
return allConnected;
|
|
}
|
|
|
|
/// <summary>
|
|
/// TCP ping
|
|
/// </summary>
|
|
/// <param name="ipList">List of connected devices IP addresses </param>
|
|
/// <returns>false - if as least one device disconnected</returns>
|
|
private bool PingAllDevices(List<string> ipList)
|
|
{
|
|
var allConnected = true;
|
|
Parallel.ForEach(ipList, (item, state) =>
|
|
{
|
|
allConnected = PingOneDevice(item);
|
|
if (!allConnected) state.Break();
|
|
});
|
|
return allConnected;
|
|
}
|
|
|
|
#endregion Ping All Devices
|
|
|
|
#region Ping One Device
|
|
/// <summary>
|
|
/// TCP ping
|
|
/// </summary>
|
|
/// <param name="deviceIpAddress">device IP addresses </param>
|
|
/// <returns>false - if device disconnected</returns>
|
|
private bool PingOneDevice(string deviceIpAddress)
|
|
{
|
|
for (var i = 0; i < MAX_PING_ATTEMPTS; i++)
|
|
{
|
|
var pingSender = new Ping();
|
|
|
|
var reply = pingSender.Send(deviceIpAddress);
|
|
if (reply != null && reply.Status == IPStatus.Success) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// ICMP ping
|
|
/// </summary>
|
|
/// <param name="deviceIp">device IP addresses </param>
|
|
/// <param name="hostIpAddress">host IP address</param>
|
|
/// <returns>false - if device disconnected</returns>
|
|
private bool PingOneDevice(string deviceIp, string hostIpAddress)
|
|
{
|
|
for (var i = 0; i < MAX_PING_ATTEMPTS; i++)
|
|
{
|
|
if (PingICMP(IPAddress.Parse(hostIpAddress), IPAddress.Parse(deviceIp), Constants.PING_ICMP_TIMEOUT, Encoding.ASCII.GetBytes(ALPHABET)).Status == IPStatus.Success)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endregion Ping One Device
|
|
|
|
#region ICMP Ping
|
|
|
|
/// <summary>
|
|
/// ICMP ping
|
|
/// </summary>
|
|
/// <param name="srcAddress">host address</param>
|
|
/// <param name="destAddress">device address</param>
|
|
/// <param name="timeout">timeout</param>
|
|
/// <param name="buffer">Function will fail without buffer (used alphabet)</param>
|
|
/// <param name="po">Ping options - not been usexd</param>
|
|
/// <returns>PingReplyUtils class</returns>
|
|
private static PingReplyUtils PingICMP(IPAddress srcAddress, IPAddress destAddress, int timeout = 5000, byte[] buffer = null, PingOptions po = null)
|
|
{
|
|
var ipSrc = string.Empty;
|
|
var ipDest = string.Empty;
|
|
|
|
try
|
|
{
|
|
var ipSrcBytes = srcAddress?.GetAddressBytes() ?? new byte[0];
|
|
if (4 <= ipSrcBytes.Length) { ipSrc = $"{ipSrcBytes[0]}.{ipSrcBytes[1]}.{ipSrcBytes[2]}.{ipSrcBytes[3]}"; }
|
|
var destBytes = destAddress?.GetAddressBytes() ?? new byte[0];
|
|
if (4 <= destBytes.Length) { ipDest = $"{destBytes[0]}.{destBytes[1]}.{destBytes[2]}.{destBytes[3]}"; }
|
|
}
|
|
catch (Exception ex) { APILogger.Log(ex); }
|
|
if (destAddress == null || destAddress.AddressFamily != AddressFamily.InterNetwork ||
|
|
destAddress.Equals(IPAddress.Any))
|
|
{
|
|
APILogger.Log($"Ping -S {ipSrc} {ipDest} failed - Argument exception");
|
|
throw new ArgumentException(string.Format("PingAll failed on IP:{0}", srcAddress));
|
|
}
|
|
|
|
//Defining pinvoke args
|
|
var source = srcAddress == null ? 0 : BitConverter.ToUInt32(srcAddress.GetAddressBytes(), 0);
|
|
var destination = BitConverter.ToUInt32(destAddress.GetAddressBytes(), 0);
|
|
var sendbuffer = buffer ?? new byte[] { };
|
|
var options = new Interop.Option
|
|
{
|
|
Ttl = (po == null ? (byte)255 : (byte)po.Ttl),
|
|
Flags = (po == null ? (byte)0 : po.DontFragment ? (byte)0x02 : (byte)0) //0x02
|
|
};
|
|
var fullReplyBufferSize = Interop.ReplyMarshalLength + sendbuffer.Length;
|
|
//Size of Reply struct and the transmitted buffer length.
|
|
|
|
|
|
|
|
var allocSpace = Marshal.AllocHGlobal(fullReplyBufferSize);
|
|
// unmanaged allocation of reply size. TODO Maybe should be allocated on stack
|
|
try
|
|
{
|
|
var start = DateTime.Now;
|
|
var nativeCode = Interop.IcmpSendEcho2Ex(
|
|
/* _In_ HANDLE IcmpHandle, */
|
|
Interop.IcmpHandle,
|
|
/* _In_opt_ HANDLE Event, */
|
|
default(IntPtr),
|
|
/* _In_opt_ PIO_APC_ROUTINE ApcRoutine, */
|
|
default(IntPtr),
|
|
/* _In_opt_ PVOID ApcContext */
|
|
default(IntPtr),
|
|
/* _In_ IPAddr SourceAddress, */
|
|
source,
|
|
/* _In_ IPAddr DestinationAddress, */
|
|
destination,
|
|
/* _In_ LPVOID RequestData, */
|
|
sendbuffer,
|
|
/* _In_ WORD RequestSize, */
|
|
(short)sendbuffer.Length,
|
|
/* _In_opt_ PIP_OPTION_INFORMATION RequestOptions, */
|
|
ref options,
|
|
/* _Out_ LPVOID ReplyBuffer, */
|
|
allocSpace,
|
|
/* _In_ DWORD ReplySize, */
|
|
fullReplyBufferSize,
|
|
/* _In_ DWORD Timeout */
|
|
timeout);
|
|
var duration = DateTime.Now - start;
|
|
var reply = (Interop.Reply)Marshal.PtrToStructure(allocSpace, typeof(Interop.Reply));
|
|
// Parse the beginning of reply memory to reply struct
|
|
|
|
byte[] replyBuffer = null;
|
|
if (sendbuffer.Length != 0)
|
|
{
|
|
replyBuffer = new byte[sendbuffer.Length];
|
|
Marshal.Copy(allocSpace + Interop.ReplyMarshalLength, replyBuffer, 0, sendbuffer.Length);
|
|
//copy the rest of the reply memory to managed byte[]
|
|
}
|
|
|
|
var errorCode = 0;
|
|
if (0 != nativeCode)
|
|
{
|
|
errorCode = Marshal.GetLastWin32Error();
|
|
}
|
|
APILogger.Log($"Ping -S {ipSrc} {ipDest} result: {reply.Status} LastError: {errorCode}");
|
|
return nativeCode == 0
|
|
? new PingReplyUtils(nativeCode, reply.Status, new IPAddress(reply.Address), duration)
|
|
: new PingReplyUtils(nativeCode, reply.Status, new IPAddress(reply.Address), reply.RoundTripTime,
|
|
replyBuffer);
|
|
}
|
|
finally { Marshal.FreeHGlobal(allocSpace); /*free allocated space*/ }
|
|
}
|
|
|
|
#endregion ICMP Ping
|
|
|
|
#region Support ICMP Ping
|
|
|
|
/// <summary>Interoperability Helper
|
|
/// <see cref="http://msdn.microsoft.com/en-us/library/windows/desktop/bb309069(v=vs.85).aspx" />
|
|
/// </summary>
|
|
private static class Interop
|
|
{
|
|
private static IntPtr? _icmpHandle;
|
|
private static int? _replyStructLength;
|
|
|
|
/// <summary>Returns the application legal icmp handle. Should be close by IcmpCloseHandle
|
|
/// <see cref="http://msdn.microsoft.com/en-us/library/windows/desktop/aa366045(v=vs.85).aspx" />
|
|
/// </summary>
|
|
public static IntPtr IcmpHandle
|
|
{
|
|
get
|
|
{
|
|
if (_icmpHandle == null)
|
|
{
|
|
_icmpHandle = IcmpCreateFile();
|
|
//TODO Close Icmp Handle appropiate
|
|
}
|
|
|
|
return _icmpHandle.GetValueOrDefault();
|
|
}
|
|
}
|
|
|
|
/// <summary>Returns the the marshaled size of the reply struct.</summary>
|
|
public static int ReplyMarshalLength
|
|
{
|
|
get
|
|
{
|
|
if (_replyStructLength == null)
|
|
{
|
|
_replyStructLength = Marshal.SizeOf(typeof(Reply));
|
|
}
|
|
return _replyStructLength.GetValueOrDefault();
|
|
}
|
|
}
|
|
|
|
|
|
[DllImport("Iphlpapi.dll", SetLastError = true)]
|
|
private static extern IntPtr IcmpCreateFile();
|
|
|
|
[DllImport("Iphlpapi.dll", SetLastError = true)]
|
|
private static extern bool IcmpCloseHandle(IntPtr handle);
|
|
|
|
[DllImport("Iphlpapi.dll", SetLastError = true)]
|
|
public static extern uint IcmpSendEcho2Ex(IntPtr icmpHandle, IntPtr Event, IntPtr apcroutine,
|
|
IntPtr apccontext, uint sourceAddress, uint destinationAddress, byte[] requestData,
|
|
short requestSize, ref Option requestOptions, IntPtr replyBuffer, int replySize, int timeout);
|
|
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
|
public struct Option
|
|
{
|
|
public byte Ttl;
|
|
public readonly byte Tos;
|
|
public byte Flags;
|
|
public readonly byte OptionsSize;
|
|
public readonly IntPtr OptionsData;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
|
public struct Reply
|
|
{
|
|
public readonly uint Address;
|
|
public readonly int Status;
|
|
public readonly int RoundTripTime;
|
|
public readonly short DataSize;
|
|
public readonly short Reserved;
|
|
public readonly IntPtr DataPtr;
|
|
public readonly Option Options;
|
|
}
|
|
}
|
|
|
|
#endregion Support ICMP Ping
|
|
|
|
#endregion Ping
|
|
private static readonly object LogLock = new object();
|
|
public static void PingLog(params string[] args)
|
|
{
|
|
var sb = new StringBuilder(200);
|
|
sb.AppendFormat("=== {0} ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
|
|
foreach (var arg in args)
|
|
{
|
|
sb.Append(" ");
|
|
sb.Append(arg);
|
|
}
|
|
sb.Append(Environment.NewLine);
|
|
lock (LogLock)
|
|
{
|
|
try
|
|
{
|
|
File.AppendAllText("Logs/ping.log", sb.ToString());
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Trace.WriteLine(ex);
|
|
}
|
|
}
|
|
}
|
|
#region OldPing
|
|
public void PingFunction(object o)
|
|
{
|
|
var td = o as PingThreadData;
|
|
if (td == null) return;
|
|
td.UpdateProgress(0D);
|
|
if (null == td.DoneEvent)
|
|
{
|
|
td.DoneEvent = new ManualResetEvent(false);
|
|
}
|
|
if (null == td.CancelEvent)
|
|
{
|
|
td.CancelEvent = new ManualResetEvent(false);
|
|
}
|
|
var sb = new StringBuilder();
|
|
sb.AppendFormat("PingFunction ");
|
|
foreach (var item in td.Entries)
|
|
{
|
|
sb.AppendFormat("{0}, ", item.IPAddress);
|
|
}
|
|
PingLog(sb.ToString());
|
|
|
|
if (0 == td.Entries.Length)
|
|
{
|
|
td.UpdateProgress(100D);
|
|
Thread.Sleep(100);
|
|
td.Complete();
|
|
return;
|
|
}
|
|
// Kick off the background thread while we monitor/simulate progress.
|
|
Task.Run(() => CheckOnline(td)).ConfigureAwait(false);
|
|
// FB 25642 Consider the number of hosts as well
|
|
var maxTime = Constants.PING_ICMP_TIMEOUT * td.Entries.Length * NetworkUtils.GetAvailableHosts().Count;
|
|
if (maxTime > 0 && maxTime < 1000)
|
|
{
|
|
maxTime = 1000; //minimum of 1 second
|
|
}
|
|
// Tick along progress as if it will take MAXPINGTIME. If we finish early, great.
|
|
var timeWaited = 0D;
|
|
while (!td.DoneEvent.WaitOne(150, false) && !td.CancelEvent.WaitOne(0, false))
|
|
{
|
|
timeWaited += 150D;
|
|
if (timeWaited >= maxTime)
|
|
{
|
|
PingLog("WARNING timewaited >= maxpingtime: ", timeWaited.ToString(), ">=", maxTime.ToString());
|
|
}
|
|
}
|
|
|
|
if (td.CancelEvent.WaitOne(0, false))
|
|
{
|
|
td.Complete();
|
|
}
|
|
//else if (timeWaited >= Maxpingtime)
|
|
//{
|
|
// PingLog("WARNING timewaited >= maxpingtime: ", timeWaited.ToString(), ">=",
|
|
// Maxpingtime.ToString());
|
|
// //td.CancelEvent.Set();
|
|
// //td.Complete();
|
|
// //td.DoneEvent.WaitOne();
|
|
// Thread.Sleep(100);
|
|
//}
|
|
else
|
|
{
|
|
td.UpdateProgress(100D);
|
|
Thread.Sleep(100);
|
|
td.Complete();
|
|
}
|
|
}
|
|
//used to hold the number of ips pinged completed while actively pinging
|
|
private volatile int _pingsDone = 0;
|
|
/// <summary>
|
|
/// Pinging device from specific IP address function will use IMC protocol, TC Protocol will be used if host IP address is empty
|
|
/// </summary>
|
|
/// <param name="o">A <see cref="PingThreadData"/> object</param>
|
|
private void CheckOnline(object o)
|
|
{
|
|
var td = o as PingThreadData;
|
|
if (td != null && null == td.CancelEvent)
|
|
{
|
|
td.CancelEvent = new ManualResetEvent(false);
|
|
}
|
|
try
|
|
{
|
|
var maxPingTime = Convert.ToInt32(Constants.PING_ICMP_TIMEOUT);
|
|
if (td == null) return;
|
|
var ipList = new List<string>();
|
|
foreach (var entry in td.Entries)
|
|
{
|
|
ipList.Add(entry.IPAddress);
|
|
}
|
|
var hostInfos = NetworkUtils.GetAvailableHosts();
|
|
EliminateBadHosts(ref hostInfos, ipList.ToArray());
|
|
if (!hostInfos.Any()) { return; }
|
|
var lastHost = hostInfos.Last();
|
|
var alphabet = Encoding.ASCII.GetBytes(ALPHABET);
|
|
var toPing = td.Entries.Length;
|
|
_pingsDone = 0;
|
|
_ = Parallel.ForEach(td.Entries, item =>
|
|
{
|
|
if (td.CancelEvent.WaitOne(0, false))
|
|
{
|
|
td.UpdateEntry(item, PingProgressStates.Cancel, 0);
|
|
return;
|
|
}
|
|
td.UpdateEntry(item, PingProgressStates.Pinging, 0);
|
|
var succeded = false;
|
|
var attempt = 0;
|
|
|
|
//FB 25642 Get available hosts
|
|
|
|
//avoid having to re-parse the ip address x thousand of times
|
|
var lookup = new Dictionary<HostInfo, IPAddress>();
|
|
foreach (var hostInfo in hostInfos)
|
|
{
|
|
lookup[hostInfo] = IPAddress.Parse(hostInfo.HostIpAddress);
|
|
}
|
|
while (!succeded && attempt < MAX_PING_ATTEMPTS && !td.CancelEvent.WaitOne(0, false))
|
|
{
|
|
if (td.CancelEvent.WaitOne(0, false))
|
|
{
|
|
td.UpdateEntry(item, PingProgressStates.Cancel, 0);
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
PingReplyUtils allReply = null;
|
|
//FB 25642 we need to go through all the hosts
|
|
foreach (var hostInfo in hostInfos)
|
|
{
|
|
if (!IPAddress.TryParse(item.IPAddress, out var ip))
|
|
{
|
|
attempt = MAX_PING_ATTEMPTS;
|
|
continue;
|
|
}
|
|
//won't wpork if buffer is empty
|
|
|
|
var eachReply = PingICMP(lookup[hostInfo], ip, Constants.PING_ICMP_TIMEOUT, alphabet);
|
|
if ((eachReply.Status == IPStatus.Success) && (eachReply.NativeCode != 0))
|
|
{
|
|
//we found the host, update the dictionary & break
|
|
DasToHost[item.IPAddress] = hostInfo;
|
|
allReply = eachReply;
|
|
break;
|
|
}
|
|
if (hostInfo.Equals(lastHost))
|
|
{
|
|
//if it's the last host update the overall reply
|
|
allReply = eachReply;
|
|
}
|
|
}
|
|
|
|
|
|
//If the default gateway of the network interface isn't set appropriately, the call to PingICMP will
|
|
//return with a Status of IPStatus.Success. So, to differentiate this from a valid successful ping, we check NativeCode
|
|
|
|
if (allReply != null)
|
|
{
|
|
//FB 25642 allReply shows the ping was successfull or not
|
|
if ((allReply.Status == IPStatus.Success) && (allReply.NativeCode != 0))
|
|
{
|
|
succeded = true;
|
|
td.UpdateEntry(item, PingProgressStates.Ping_Good, allReply.RoundTripTime.Milliseconds);
|
|
PingLog("ping succeeded: ", DasToHost[item.IPAddress]?.HostIpAddress, " -> ", item.IPAddress);
|
|
}
|
|
else
|
|
{
|
|
attempt++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PingLog("couldn't parse ip: ", item.IPAddress);
|
|
attempt++;
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
td.UpdateEntry(item, PingProgressStates.InvalidIPAddress, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
if (succeded)
|
|
{
|
|
_pingsDone++;
|
|
td.UpdateProgress(100D * _pingsDone / toPing);
|
|
}
|
|
else if (MAX_PING_ATTEMPTS <= attempt)
|
|
{
|
|
_pingsDone++;
|
|
td.UpdateEntry(item, PingProgressStates.NoReply, 0);
|
|
td.UpdateProgress(100D * _pingsDone / toPing);
|
|
PingLog("failed (maxpings <= attempt: ", item.IPAddress, " - ", MAX_PING_ATTEMPTS.ToString(), "<=", attempt.ToString());
|
|
}
|
|
|
|
if (td.CancelEvent.WaitOne(0, false))
|
|
{
|
|
td.UpdateEntry(item, PingProgressStates.Cancel, 0);
|
|
}
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
APILogger.Log(ex);
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
_ = td.DoneEvent.Set();
|
|
}
|
|
}
|
|
#endregion OldPing
|
|
}
|
|
}
|
|
|
|
#region PingReply
|
|
[Serializable]
|
|
public class PingReplyUtils
|
|
{
|
|
private Win32Exception _exception;
|
|
|
|
public PingReplyUtils()
|
|
{
|
|
|
|
}
|
|
|
|
internal PingReplyUtils(uint nativeCode, int replystatus, IPAddress ipAddress, TimeSpan duration)
|
|
{
|
|
NativeCode = nativeCode;
|
|
IpAddress = ipAddress;
|
|
if (Enum.IsDefined(typeof(IPStatus), replystatus))
|
|
{
|
|
Status = (IPStatus)replystatus;
|
|
}
|
|
}
|
|
|
|
internal PingReplyUtils(uint nativeCode, int replystatus, IPAddress ipAddress, int roundTripTime,
|
|
byte[] buffer)
|
|
{
|
|
NativeCode = nativeCode;
|
|
IpAddress = ipAddress;
|
|
RoundTripTime = TimeSpan.FromMilliseconds(roundTripTime);
|
|
Buffer = buffer;
|
|
if (Enum.IsDefined(typeof(IPStatus), replystatus))
|
|
{
|
|
Status = (IPStatus)replystatus;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>Native result from <code>IcmpSendEcho2Ex</code>.</summary>
|
|
public uint NativeCode { get; } = 0;
|
|
|
|
public IPStatus Status { get; } = IPStatus.Unknown;
|
|
|
|
/// <summary>The source address of the reply.</summary>
|
|
public IPAddress IpAddress { get; } = null;
|
|
|
|
public byte[] Buffer { get; } = null;
|
|
|
|
public TimeSpan RoundTripTime { get; } = TimeSpan.Zero;
|
|
|
|
/// <summary>Resolves the <code>Win32Exception</code> from native code</summary>
|
|
public Win32Exception Exception
|
|
{
|
|
get
|
|
{
|
|
if (Status != IPStatus.Success)
|
|
{
|
|
return _exception ?? (_exception = new Win32Exception((int)NativeCode, Status.ToString()));
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
if (Status == IPStatus.Success)
|
|
{
|
|
return Status + " from " + IpAddress + " in " + RoundTripTime + " ms with " + Buffer.Length +
|
|
" bytes";
|
|
}
|
|
if (Status != IPStatus.Unknown)
|
|
{
|
|
return Status + " from " + IpAddress;
|
|
}
|
|
|
|
return Exception.Message + " from " + IpAddress;
|
|
}
|
|
}
|
|
|
|
#endregion PingReply
|
|
|
|
#region Entry
|
|
/// <summary>
|
|
/// Trivial class to represent an IP address entry, its type, and a visual representation
|
|
/// </summary>
|
|
public class Entry
|
|
{
|
|
public string IPAddress { get; set; }
|
|
public int DasType { get; set; }
|
|
public DataRow DR { get; set; }
|
|
public object UserData { get; set; }
|
|
}
|
|
#endregion Entry
|
|
|
|
#region HostInfo
|
|
//FB 25642 & 25590 & 18152 This class encapsulate information for the host in one place
|
|
public class HostInfo
|
|
{
|
|
public string HostIpAddress { get; set; } = string.Empty;
|
|
public string HostMacAddress { get; set; } = string.Empty;
|
|
public string HostNetworkId { get; set; } = string.Empty;
|
|
|
|
public string StartAddress { get; set; } = string.Empty;
|
|
public string EndAddress { get; set; } = string.Empty;
|
|
|
|
}
|
|
#endregion
|
|
|
|
#region PingProgressStates
|
|
/// <summary>
|
|
/// The possible states for a given <see cref="Entry"/>.
|
|
/// </summary>
|
|
public enum PingProgressStates
|
|
{
|
|
Pinging,
|
|
Connecting,
|
|
Querying,
|
|
Added,
|
|
Updated,
|
|
InvalidIPAddress,
|
|
NoReply,
|
|
Ping_Good,
|
|
Online,
|
|
Connected,
|
|
NoConnection,
|
|
Cancel,
|
|
Ready,
|
|
Armed,
|
|
QueryFailed,
|
|
QueryTimedOut,
|
|
QueryComplete,
|
|
UnexpectedMaxMemory,
|
|
UnexpectedFirmwareVersion,
|
|
Passed,
|
|
ExpiredCalDate,
|
|
ChannelCountMismatch,
|
|
MissingModules,
|
|
Canceled,
|
|
NoMemory,
|
|
AutoArmed,
|
|
Realtime,
|
|
ReadyToStream,
|
|
Streaming,
|
|
LostDock,
|
|
Rebooting,
|
|
UnexpectedFirstUseDate,
|
|
InvalidRecordingMode,
|
|
InvalidStreamingMode,
|
|
StreamingNotAvailable
|
|
}
|
|
#endregion PingProgressStates
|
|
|
|
#region PingThreadData
|
|
/// <summary>
|
|
/// A wrapper class to represent the progress and feedback for ping activity from background threads.
|
|
/// </summary>
|
|
public class PingThreadData
|
|
{
|
|
/// <summary>
|
|
/// <see cref="Entry"/> list to act on.
|
|
/// </summary>
|
|
public Entry[] Entries { get; set; }
|
|
|
|
/// <summary>
|
|
/// Optional <see cref="System.Threading.ManualResetEvent"/> to tell the process to cancel.
|
|
/// </summary>
|
|
public ManualResetEvent CancelEvent { get; set; }
|
|
|
|
/// <summary>
|
|
/// Optional <see cref="System.Threading.ManualResetEvent"/> to fire when the entire process is done.
|
|
/// </summary>
|
|
public ManualResetEvent DoneEvent { get; set; }
|
|
|
|
|
|
/// <summary>
|
|
/// An optional delgate to be called whenever status text has changed.
|
|
/// </summary>
|
|
public SetStatusTextDelegate setStatusTextCallback;
|
|
|
|
/// <summary>
|
|
/// An optional delgate to be called whenever progress has changed.
|
|
/// </summary>
|
|
public SetProgressValueDelegate progressCallback;
|
|
|
|
/// <summary>
|
|
/// A delegate type used for reporting progress on an individual entry
|
|
/// </summary>
|
|
/// <param name="entry"><see cref="Entry"/> that is being reported on</param>
|
|
/// <param name="state"><see cref="PingProgressStates"/> indicating the new state</param>
|
|
public delegate void EntryProgressCallback(Entry entry, PingProgressStates state, long msRoundTrip);
|
|
|
|
/// <summary>
|
|
/// An optional delegate to be called whenever an individual entry has changed.
|
|
/// </summary>
|
|
public EntryProgressCallback entryProgressCallback;
|
|
|
|
/// <summary>
|
|
/// An optional delegate to be called when the entire process is complete. Called before <seealso cref="DoneEvent"/> is fired.
|
|
/// </summary>
|
|
public ActionCompleteDelegate completeCallback;
|
|
|
|
/// <summary>
|
|
/// Wrapper for posting progress
|
|
/// </summary>
|
|
/// <param name="Percent">From 0 to 100 to indicate progress</param>
|
|
public void UpdateProgress(double Percent)
|
|
{
|
|
progressCallback?.Invoke(Percent);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wrapper for posting entry progress
|
|
/// </summary>
|
|
/// <param name="entry">The entry being updated</param>
|
|
/// <param name="state">The new state</param>
|
|
public void UpdateEntry(Entry entry, PingProgressStates state, long msResponseTime)
|
|
{
|
|
entryProgressCallback?.Invoke(entry, state, msResponseTime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wrapper for posting complete
|
|
/// </summary>
|
|
public void Complete()
|
|
{
|
|
completeCallback?.Invoke();
|
|
DoneEvent?.Set();
|
|
}
|
|
|
|
public bool AvoidConnectPortion { get; set; } = false;
|
|
|
|
public bool PingFailed { get; set; } = false;
|
|
}
|
|
#endregion PingThreadData
|
|
}
|
|
|