using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using Yaulw.Win32; using System.Management; using System.IO; namespace Yaulw.Net { /// /// Helper for common IP / Host Machine /// public static class IPHostHelper { #region Public consts /// /// This automation URL used to work, now it no longer works :( /// Caller must set this before calling ExternalIP() Functions /// public static string WHAT_IS_MY_IP_AUTOMATION_URL = "http://automation.whatismyip.com/n09230945.asp"; #endregion #region IPv4 Checkers /// /// Checks to see that the passed in ip is a valid Local IP /// address that is of version 4 and not a broadcast/loopback/None /// /// /// true to allow Loopback address as valid Local IP /// /// /// IPAddress.IPv6Any = "::"; /// IPAddress.Any = "0.0.0.0" /// IPAddress.Broadcast = "255.255.255.255" /// IPAddress.None = "255.255.255.255" /// IPAddress.Loopback = "127.0.0.1" /// public static bool IsValidIPv4Address(IPAddress ip, bool bAllowLoopback) { if (ip != null) { if (ip.AddressFamily != AddressFamily.InterNetwork) return false; if (ip.AddressFamily.ToString().Contains(IPAddress.IPv6Any.ToString())) return false; if (ip == IPAddress.Any) return false; if (ip == IPAddress.Broadcast) return false; if (ip == IPAddress.None) return false; // Loopback technically is a valid Local IP that can be used to communicate // to ourselves (on the same machine) if (ip == IPAddress.Loopback) return bAllowLoopback; // This must be a valid local IPv4 return true; } return false; } /// /// Checks to see if the passed in ip is in a valid Private subnet (i.e. not public) /// /// /// /// /// Private subnets as defined in /// Loopback address 127.0.0.1/8 /// Zeroconf/bonjour self assigned addresses 169.254.0.0/16 /// public static bool IsIPv4AddressInPrivateSubnet(IPAddress ip) { if (!IsValidIPv4Address(ip, true)) return false; String[] netAddrs = { "192.168.0.0", "10.0.0.0", "172.16.0.0", "127.0.0.1", "169.254.0.0" }; String[] netMasks = { "255.255.0.0", "255.0.0.0", "255.240.0.0", "255.0.0.0", "255.255.0.0" }; UInt32 myIP = BitConverter.ToUInt32(ip.GetAddressBytes(), 0); for (int i = 0; i < netMasks.Length; i++) { IPAddress netAddr = IPAddress.Parse(netAddrs[i]); UInt32 netIP = BitConverter.ToUInt32(netAddr.GetAddressBytes(), 0); IPAddress maskAddr = IPAddress.Parse(netMasks[i]); UInt32 maskIP = BitConverter.ToUInt32(maskAddr.GetAddressBytes(), 0); if ((myIP & maskIP) == (netIP & maskIP)) return true; } return false; } /// /// Compares two IP Addresses, if the Left One is different from the right one, assigns /// the right one to the LeftOrRight (simple) and returns true. Otherwise assigns left to it and returns false. /// public static bool AreIPAddressesDifferent(IPAddress left, IPAddress right, out IPAddress LeftOrRight) { LeftOrRight = left; if (left != null && right != null) { if (String.Compare(left.ToString(), right.ToString(), true) != 0) { LeftOrRight = right; return true; } } else if (left == null && right != null) { LeftOrRight = right; return true; } return false; } #endregion #region Connectivity /// /// Method to check if the Local Computer has a valid Network Connection /// /// public static bool HasConnection() { //instance of our ConnectionStatusEnum Definitions.ConnectionStatusEnum state = 0; //call the API bool bIsConnected = Wininet.InternetGetConnectedState(ref state, 0); if (!bIsConnected) return false; //check the status, if in offline mode then return false if (((int)Definitions.ConnectionStatusEnum.INTERNET_CONNECTION_OFFLINE & (int)state) != 0) return false; return true; } /// /// Method to check to see if local Computer has a valid Network connection /// and can open the specified ip and port /// /// /// /// public static bool HasConnectivity(IPAddress ip, uint port) { if(HasConnection() && IsValidIPv4Address(ip, true) && port > 0) return IsPortOpen(ip, (int) port); return false; } /// /// Method to check to see if local Computer has a valid Network connection /// and can open the specified host and port /// /// /// /// /// public static bool HasConnectivity(string host, uint port, int nTimeout) { if (HasConnection() && port > 0) { IPAddress ip = GetIpForHost(host); if(ip != IPAddress.None && IsValidIPv4Address(ip, false)) return IsPortOpen(ip, (int) port, nTimeout); } return false; } /// /// Retrieves the Servername from a DataBaseConnectionString, if possible, /// then gets the ip for the server name and tries to open up the port specified /// /// pass in either a DataSource or ConnectionString /// /// public static bool HasConnectivity(string DataBaseConnectionString, uint port) { if (HasConnection() && !String.IsNullOrEmpty(DataBaseConnectionString)) { // Allow the SharedConnectionDataSource to be a ConnectionString also via calling GetDataSource() DataBaseConnectionString = GetDataSource(DataBaseConnectionString); // Retrieve the IP for the Data Source and test the ip and set it in the API string host = GetServerNameFromADataSource(DataBaseConnectionString); IPAddress ip = GetIpForHost(host); if (ip != IPAddress.None) { if (IsPortOpen(ip, (int) port)) return true; } } return false; } #endregion #region Core IPHostHelper Functionallity /// /// Converts all incl. Loopback, if contained, valid IP addresses into a String array of IPs /// /// public static string[] ConvertIPArrayToStringArray(IPAddress[] IPs) { List ips = new List(); if (IPs != null && IPs.Length > 0) { foreach (IPAddress ip in IPs) { if (IsValidIPv4Address(ip, true)) ips.Add(ip.ToString()); } } return ips.ToArray(); } /// /// Get all valid IPv4 Local IP addresses besides the loopback address /// /// public static IPAddress[] GetAllLocalIPAddresses() { // Get host name String HostName = Dns.GetHostName(); IPHostEntry iphostentry = Dns.GetHostEntry(HostName); // Enumerate IP addresses List IPs = new List(); foreach (IPAddress ipaddress in iphostentry.AddressList) { if (IsValidIPv4Address(ipaddress, false)) IPs.Add(ipaddress); } // Return them as a string[] of IPs return IPs.ToArray(); } /// /// Get First Found Local IP Address (or IPAddress.None, if none found) /// /// get the first Local IP Address found public static IPAddress GetFirstLocalIPAddress() { IPAddress[] IPs = GetAllLocalIPAddresses(); if (IPs.Length > 0) return IPs[0]; else return IPAddress.None; } /// /// Get Nth Found Local IP Address (or first one found, if n > IPs Found) /// (or IPAddress.None, if none found) /// /// get the Nth Local IP Address found public static IPAddress GetNthLocalIPAddress(int n) { IPAddress[] IPs = GetAllLocalIPAddresses(); if (n >= 0 && IPs.Length < n) return IPs[n]; else if (IPs.Length > 0) return IPs[0]; else return IPAddress.None; } /// /// Check to see if the host passed in is Local /// /// can be ip or host name /// true, if host or ip is local, false otherwise /// When host is empty public static bool IsHostOrIPLocal(string host) { if (!String.IsNullOrEmpty(host)) { IPAddress ip = null; if (IPAddress.TryParse(host, out ip)) { IPAddress[] localIPs = GetAllLocalIPAddresses(); foreach(IPAddress localip in localIPs) { if (localip == ip) return true; } return false; } else { return (String.Compare(host.Trim(), Dns.GetHostName(), true) == 0); } } throw new ArgumentException("Invalid host"); } /// /// Method for resolving a Host (DNS Resolve) /// /// the host /// Set to true, if the host must be in the Private IP Range /// true, if host can be resolved to an IP Address, false otherwise public static bool CanResolveHost(string host, bool bMustBeInPrivateIPRange) { if(!String.IsNullOrEmpty(host)) { IPAddress ip = GetIpForHost(host); if (bMustBeInPrivateIPRange) return IsIPv4AddressInPrivateSubnet(ip); return (ip != IPAddress.None); } return false; } /// /// Method for retrieving the IP address for a Host (DNS Resolve) /// /// the host we need the ip address for /// an IPAddress if found, otherwise IPAddress.None public static IPAddress GetIpForHost(string host) { //variable to hold our error message (if something fails) string errMessage = string.Empty; //IPAddress instance for holding the returned host IPAddress address = IPAddress.None; //wrap the attempt in a try..catch to capture //any exceptions that may occur try { // if it's already an ip address being passed in, // we are done if (!IPAddress.TryParse(host, out address)) { //get the host IP from the name provided address = Dns.GetHostEntry(host).AddressList[0]; // We must be called for the local host, that // could explain why it is not a valid IP if (!IsValidIPv4Address(address, false)) address = GetFirstLocalIPAddress(); } else { address = IPAddress.Parse(host); } } catch (Exception) { /* ignore */ } if (address == null) return IPAddress.None; else return address; } /// /// Ping a host /// /// url/host/ip /// timeout value in miliseconds /// number of times to ping /// the number of successful pings public static int PingHost(string host, int nTimeout, int nTrys) { //string to hold our return messge //string returnMessage = string.Empty; // Check to see if the host passed in is an IP Address // otherwise, just resolve the host IPAddress address = null; if(!IPAddress.TryParse(host, out address)) address = GetIpForHost(host); // Error occured resolving host if (!IsValidIPv4Address(address, false)) return 0; //set the ping options, TTL 128 PingOptions pingOptions = new PingOptions(128, true); //create a new ping instance Ping ping = new Ping(); //32 byte buffer (create empty) byte[] buffer = new byte[32]; //first make sure we actually have an internet connection int nPingSuccess = 0; if (HasConnection()) { //here we will ping the host nTrys times for (int i = 0; i < nTrys; i++) { try { PingReply pingReply = ping.Send(address, nTimeout, buffer, pingOptions); if (!(pingReply == null)) { switch (pingReply.Status) { case IPStatus.Success: nPingSuccess++; break; case IPStatus.TimedOut: default: break; } } } catch (Exception) { /* ignore and continue iterating */ } } } return nPingSuccess; } #endregion #region Port Open /// /// Check to see if a port is open for the specified ip /// /// /// /// public static bool IsPortOpen(IPAddress ip, int port) { bool bCanConnect = false; if (IsValidIPv4Address(ip, true) && port > 0) { try { TcpClient tcP = new System.Net.Sockets.TcpClient(); tcP.Connect(ip, port); bCanConnect = true; tcP.Close(); } catch (Exception) { /* ignore */ } } return bCanConnect; } /// /// Check to see if a port is open for the specified ip /// /// /// /// public static bool IsPortOpen(IPAddress ip, int port, int nTimeout) { bool bCanConnect = false; if (IsValidIPv4Address(ip, true) && port > 0) { try { TcpClient tcP = new System.Net.Sockets.TcpClient(); if (nTimeout > 0) tcP.ReceiveTimeout = nTimeout; tcP.Connect(ip, port); bCanConnect = true; tcP.Close(); } catch (Exception) { /* ignore */ } } return bCanConnect; } #endregion #region Get External IP /// /// Get the External IP /// /// Timeout in MiliSeconds /// a valid external IPAddress or IPAddress.None, if error occured public static IPAddress GetExternalIP(int pTimeOutMiliSeconds) { IPAddress extIP = IPAddress.None; try { using (WebClient wc = new WebClient()) { wc.Headers.Add("user-agent", WCHelper.USER_AGENT_GENERIC); UTF8Encoding utf8 = new UTF8Encoding(); string ipaddr = null; bool done = false; wc.DownloadDataCompleted += new DownloadDataCompletedEventHandler((object sender, DownloadDataCompletedEventArgs e) => { ipaddr = utf8.GetString(e.Result); done = true; }); wc.DownloadDataAsync(new Uri(WHAT_IS_MY_IP_AUTOMATION_URL)); System.DateTime startTime = System.DateTime.Now; while (!done) { System.TimeSpan sp = System.DateTime.Now - startTime; // We should get a response in less than timeout. // If not, we cancel all and return the internal IP Address if (sp.TotalMilliseconds > pTimeOutMiliSeconds) { done = true; wc.CancelAsync(); } } if (IPAddress.TryParse(ipaddr, out extIP)) return extIP; } } catch { /* ignore */ } return IPAddress.None; } /// /// Get the External IP /// /// a valid external IPAddress or IPAddress.None, if error occured public static IPAddress GetExternalIP() { string ipaddr = WCHelper.ScreenScrapeFromURL(WHAT_IS_MY_IP_AUTOMATION_URL); IPAddress extIP = IPAddress.None; if (IPAddress.TryParse(ipaddr, out extIP)) return extIP; return IPAddress.None; } /// /// Call this to retrieve the external IP Address (also makes sure we can connect /// to WhatIsMyIp site) /// /// /// Forces 4 checks at 2.5/5.0/7.5/10 seconds timeout values /// otherwise makes one request defaulting to 5.0 /// /// valid external IP Address or null if not found public static IPAddress GetExternalIP(bool bForceMultipleChecks) { if (HasConnection()) { IPAddress ipExtIP = null; if (bForceMultipleChecks) { // Check with an timout of 2.5/5.0/7.5/10 seconds for (int n = 2500; n <= 10000; n = n + 2500) { ipExtIP = GetExternalIP(n); if(ipExtIP != IPAddress.None) return ipExtIP; } } else { // Check with whatever is the default timeout ipExtIP = Yaulw.Net.IPHostHelper.GetExternalIP(5000); return ipExtIP; } } return IPAddress.None; } #endregion #region UNC and Path Related Windows Network Helpers /// /// Allows us to use either pass in a ConnectionString or a DataSource * Added flexibility * /// will return the DataSource part of the connection string, if exists, otherwise the connection string /// /// pass in either a DataSource or ConnectionString /// public static string GetDataSource(string DataBaseConnectionString) { if (IsDataBaseConnectionString(DataBaseConnectionString)) { String[] tokens = DataBaseConnectionString.Split(';'); foreach (string Pair in tokens) { string[] KeyValuePair = Pair.Split('='); try { string left = KeyValuePair[0]; string right = KeyValuePair[1]; string FoundValue = ""; if (String.Compare(KeyValuePair[0].ToUpper().Trim(), "DATA SOURCE", true) == 0) { if (!String.IsNullOrEmpty(right)) { FoundValue = right.Trim(); return FoundValue; } } } catch (Exception) { /* ignore and continue iteration */ } } } // Migth still be a data source just not from a Connection string // so just return the value trimmed if (!String.IsNullOrEmpty(DataBaseConnectionString)) return DataBaseConnectionString.Trim(); else return String.Empty; } /// /// Quick Check to see if a connection string is valid * must have DataSource Key and /// a Value * in order to be considered a valid Connection String /// /// /// public static bool IsDataBaseConnectionString(string ConnectionString) { if (!String.IsNullOrEmpty(ConnectionString) && ConnectionString.ToUpper().Contains("DATA SOURCE") && ConnectionString.Contains("=")) { String[] tokens = ConnectionString.Split(';'); foreach (string Pair in tokens) { string[] KeyValuePair = Pair.Split('='); try { string left = KeyValuePair[0]; string right = KeyValuePair[1]; if (String.Compare(KeyValuePair[0].ToUpper().Trim(), "DATA SOURCE", true) == 0) return !String.IsNullOrEmpty(right); } catch (Exception) { /* ignore and continue iteration */ } } return true; } return false; } /// /// Returns the Server Name from a Database DataSource Connection String /// /// /// [DriveLetter]:\{SomeFileNameNPath} /// \\{Server]\{SomeFileNameNPath} /// \\{Server]:[Port]\{SomeFileNameNPath} /// [Server]\Instance /// [Server] /// /// Server string returned can be a name or an IP address /// Server Host Name or IP public static string GetServerNameFromADataSource(string DataSource) { if (!String.IsNullOrEmpty(DataSource)) { // Always strip out port in the DataSource *before doing anything* // only do this for non-local paths if (DataSource.Contains(":") && DataSource[1] != ':') { int nLoc = DataSource.IndexOf(":"); if (nLoc != -1) DataSource = DataSource.Substring(0, nLoc); } // Let's begin ... string Server = String.Empty; // Local Medisoft bool bIsLocalFilePath = DataSource.Contains(@":\"); if (bIsLocalFilePath) { string PathInCaseMapped = ResolveToUNCIfNeeded(DataSource); // It's a local or a UNC path if (!PathInCaseMapped.StartsWith(@"\\")) { // If Local file that the host is this computer return Dns.GetHostName(); } else { // else it's the other host * so follow UNC logic * // Stripe out first 2 chars Server = PathInCaseMapped.Substring(2); // Stripe out the rest int nLoc = Server.IndexOf(@"\"); if (nLoc != -1) Server = Server.Substring(0, nLoc); } } // Remote Medisoft bool bIsNetworkFilePath = DataSource.StartsWith(@"\\"); if (bIsNetworkFilePath) { // Stripe out first 2 chars Server = DataSource.Substring(2); // Stripe out the rest int nLoc = Server.IndexOf(@"\"); if (nLoc != -1) Server = Server.Substring(0, nLoc); } // It's Lytec! :) bool bIsSQLServerInstance = !bIsLocalFilePath && !bIsNetworkFilePath; if (bIsSQLServerInstance) { // Stripe out Instance, if it exists int nLoc = DataSource.IndexOf(@"\"); if (nLoc != -1) Server = DataSource.Substring(0, nLoc); else Server = DataSource; } return Server; } return String.Empty; } /// /// Determines if the passed in string is a connection string or a data Source /// and tries to make the best of things to retrieve the host/server name /// /// /// public static string GetServerNameFromAConnectionString(string ConnectionString) { if (IsDataBaseConnectionString(ConnectionString)) { string DataSource = GetDataSource(ConnectionString); return GetServerNameFromADataSource(DataSource); } else { // Pretentd that it is just a DataSource return GetServerNameFromADataSource(ConnectionString); } } /// Resolves the given path to a UNC path, or local drive path. /// /// \\server\share\[path] OR [driveletter]:\[path] public static string ResolveToUNCIfNeeded(string pPath) { if (String.IsNullOrEmpty(pPath)) return String.Empty; ManagementObject mo = new ManagementObject(); // Already UNC, we are done here if (pPath.StartsWith(@"\\")) { return pPath; } // Get just the drive letter for WMI call string driveletter = GetDriveLetter(pPath); mo.Path = new ManagementPath(string.Format("Win32_LogicalDisk='{0}'", driveletter)); // Get the data we need uint DriveType = System.Convert.ToUInt32(mo["DriveType"]); string NetworkRoot = System.Convert.ToString(mo["ProviderName"]); mo = null; // Return the root UNC path if network drive, otherwise return the root path to the local drive if (DriveType == 4) { return NetworkRoot + pPath.Substring(2); } else { return driveletter + pPath.Substring(2); } } /// Checks if the given path is on a network drive. /// /// public static bool isNetworkDrive(string pPath) { ManagementObject mo = new ManagementObject(); if (pPath.StartsWith(@"\\")) { return true; } // Get just the drive letter for WMI call string driveletter = GetDriveLetter(pPath); mo.Path = new ManagementPath(string.Format("Win32_LogicalDisk='{0}'", driveletter)); // Get the data we need uint DriveType = System.Convert.ToUInt32(mo["DriveType"]); mo = null; return DriveType == 4; } #endregion #region Private Helpers /// Given a path will extract just the drive letter with volume separator. /// /// C: private static string GetDriveLetter(string pPath) { if (pPath.StartsWith(@"\\")) { throw new ArgumentException("A UNC path was passed to GetDriveLetter"); } return Directory.GetDirectoryRoot(pPath).Replace(Path.DirectorySeparatorChar.ToString(), ""); } #endregion } }