2003.12.22 04:10 AM
Detecting IP Network Availability
A few months ago I was asked by a client to develop a desktop utility to submit locally cached XML files to designated servers for processing. In so much as the XML files may be authored and cached on portable computers having no active connection, it was important that the utility avoid unnecessary submissions when disconnected. Simply pinging the destination servers to determine network availability was not an option, as the client's app and web servers are not configured to support ping. And, anyway, the utility needed to know the difference between network availability vs. server availability, as failure of the former was expected, but the latter was a reportable error. Finally, the utility needed to run on nearly every version of Windows, from 95 to XP, with IE versions from 4 to 6, on over 12,000 machines worldwide. Ultimately, the utility was written in Visual Basic 6, because only VB's runtime availability was assured.
In an attempt to satisfy the network detection requirement, I explored a number of different Windows APIs. The first couple of stops included the IsNetworkAlive and IsDestinationReachable functions in the System Event Notification Service (SENS) library, which provides services specifically in support of mobile computing solutions like this one. While I'm sure these are fine functions, they didn't suffice in this case, partly because their availability wasn't assured on a large number of the target machines. But, more importantly, these functions were consistently foiled by my client's Cisco VPN software (version 3.5.4).
The next stop was the WinINet function InternetGetConnectedState. It has a promising name, and the documentation says that it "returns TRUE if there is an Internet connection, or FALSE otherwise". If only it were so. In my experience (and, apparently the experience of many others), it doesn't consistently report the "state" of IP connections. It is apt to report false negatives for all sorts of reasons. And, owing to its WinINet heritage, its availability and functionality are impacted by different versions of Windows and IE.
Ultimately, I concluded that there was no widely available, VPN-friendly, high-level function I could consistently count on in all versions of Windows/IE to determine the availability and state of an IP network connection without having a specific target address (a requirement I'll discuss shortly). So, I wrote one. This function now belongs to my client, so I can't just repeat the whole thing here, but I can share some of the thinking and techniques that went into it.
The function's activities can be divided into three significant steps.
1. Determine whether there is a configured and enabled IP connection.
The only purpose of this step is to short-circuit the remaining steps and prevent users from being prompted to make dial-up connections when an IP connection isn't available. To do this, I used the WinInet InternetGetConnectedState function (yes, the same one I just dismissed). With some consideration for false negatives and LAN-based connections, the function is sufficient to perform this limited step. To get a better feel for the function's use and limitations, see Knowledge Base article 242558.
In short, if InternetGetConnectedState returns a positive response, I can assume that there's a configured and enabled IP-based path out of the machine (even if it's stone cold dead just out of the port), and attempting a connection will not cause the user to be prompted. In all the tests I performed, this function never returned a false positive.
However, it did return a lot of false negatives, particularly for LAN (as opposed to dial-up) configurations. Most false positive LAN-connected responses followed long periods of IP inactivity on the machine. To combat this, I use the connection flags returned in the function's first parameter to determine whether, following a negative response, the configured connection is a LAN connection. If it is, then I assume it's a false negative and continue. Remember, at this point I only care about whether there is a configured IP-based network connection, not whether it is any good, so this is sufficient.
Here's how it is used:
Private Declare Function InternetGetConnectedState Lib "wininet" _ (ByRef dwFlags As Long, _ ByVal dwReserved As Long) As Long Const LAN_CONNECTION As Long = 2 Dim lngResult as Long Dim lngConnFlags as Long Dim bolContinue as Boolean bolContinue = False lngResult = InternetGetConnectedState(lngConnFlags, 0&) If lngResult <> 0 Then ' Haven't seen a false positive, yet. bolContinue = True Else ' If a LAN connection is configured, assume a false negative response. If (lngConnFlags And LAN_CONNECTION) = LAN_CONNECTION Then bolContinue = True End If End If
According to the KB article cited above, but contrary to my own experience, InternetGetConnectedState only reports the status of the default Internet connectoid on Internet Explorer 4.x. If a nondefault connectoid is connected, InternetGetConnectedState always returns False (unless a LAN connection is used). With Internet Explorer 4.x configured to use a LAN connection, InternetGetConectedState always returns True. Internet Explorer 5 behaves differently. If currently dialed into a different dial-up in Internet Explorer 5, InternetGetConnectedState reports dial-up connection status as long as any connectoid is dialed or an active LAN connection exists. There are also some Windows ME issues to consider.
2. Gather IP addresses to ping.
Once I know there's an IP-based path out of the machine, I need to get some IP addresses to ping to verify that the IP connection is actually working. As I previously mentioned, the client's app and web servers aren't configured to support ping, so the utility can't just ping the servers targeted by the XML posts. Anyhow, the server addresses given in the XML are generally qualified name-based addresses (not IP addresses), which, while pingable (after some parsing), would require name translation, which would (generally) require IP network activity, which would again confuse the process of determining network availability vs. server availability.
Luckily, most of the client's internal devices (e.g., routers, switches, file servers, etc.) are configured to support ping, so there were plenty of IP addresses available for use. However, the client ruled out requiring IT staff to manually specify a pingable IP address for each machine - too much trouble. Instead, they wanted the utility to auto-discover internal IP addresses to ping at runtime. No problem, I thought. The obvious IP addresses to get at runtime were those belonging to the DNS, gateway, and/or DHCP servers to which each machine would be connected. The real difficulty was figuring out how to programmatically harvest them. Here are the approaches I considered.
On Windows 2000 and XP, this is a no-brainer (the WMI scripting interface is also available for older Windows systems, but don't count on it being installed). WMI is an amazing resource for programmatically accessing all things Windows. It can be overwhelming (there are literally thousands of providers, objects, and data points), but it's consistent and well documented. If you haven't taken the time to get familiar with WMI, do. You will not be disappointed.
I can't begin to give justice here to the topic of WMI, but I urge you to do some research. Start with the excellent WMI Scripting Primer, by Greg Stemp, Dean Tsaltas, Bob Wells, and Ethan Wilansky (be sure and read all three parts).
For my purposes, the IP addresses I want can be had via WMI by simply selecting and enumerating the IP-enabled Win32_NetworkAdapterConfiguration objects. The code is provided below.
This approach will work (the structures/UDTs used by these routines are actually shared with the WMI Management Information Base), but its availability and features vary by Windows version. Also, programmatically speaking, it's a little dangerous, requiring nasty memory pointer copies in VB using the RtlMoveMemory API. The possibility of a memory leak makes me a little nervous given the number of times this routine will be called from a constantly running process.
The IP helper API did not start shipping in the Windows OS until Windows 98, though it has been distributed by MS in many different products, including all IE versions since 5.01.
Here 's some additional info regarding potential issues:
Here are some sample implementations (note the lack of a check for ERROR_NOT_SUPPORTED on NT):
Here are some examples of real-world issues with this approach:
This is probably the most complicated of all the approaches. And, depending on the Windows version, it cannot always provide everything needed (for instance, Windows 95 RAS dial-up doesn't update the Registry to reflect runtime DHCP changes). I'm not sure how to explain just how difficult this is, so here are some links to explain some of the (undocumented) complexity inherent in this approach:
However, there is a cheaper route to be taken within this approach, based on the fact that the Parameters Registry key includes values that apply to the TCP/IP service as a whole (no need to traverse and align Interface sub-keys, etc.). Specifically, this includes the addresses of active DNS servers. The location of the Parameters key varies by OS, and it isn't officially supported on Win2K and WinXP, though it is provided for backward compatibility. Here's an explanation of the relevant Parameter sub-key for Win2K.
I kind of liked this approach, which is referred to many times in the previous links as a workaround for the many incompatibilities and issues with the WMI, API, and Registry approaches. It's extremely low-tech, which appeals to me. However, it has its own problems. Specifically, WINIPCFG on Win95 has a problem with its command-line switches in localized versions:
And, failure of WINIPCFG to interpret the /batch command-line switch, which is needed to save the results to a file, would result in it popping an interactive window. Too bad.
So, what to do? Well, after a number of false starts, here's what I finally wound up doing to harvest IP addresses:
1) Start with WMI.
2) If it isn't available, try the IP Helper API.
3) If that doesn't work, read the Registry to get DNS addresses from the Parameters key.
If all that doesn't result in a pingable IP address, then IT staffers can provide a specific IP address to ping. If they do, it will be used without bothering to execute the aforementiond logic. In code, it looks something like this:
If Len(m_strAddressToCheck) > 0 Then Call AddIPAddress(m_strAddressToCheck, "Specified") Else If Not GetIPAddressesViaWMI() Then If Not GetIPAddressesViaIPHelper() Then If Not GetIPAddressesViaRegistry() Then ' Report inability to retrieve IP addresses to check network availability. End If End If End If End If
Note that the GetIP... functions only return False if they fail to execute. If they successfully execute, but find no IP addresses, they return True.
Below are the GetIP... functions, along with their related API declarations and constants. They've been edited pretty severely for content, size, and context. I left out a bunch of code which is responsible for actually storing the IP addresses in a custom collection (m_objIPAddresses via AddIPAddress), along with a bunch of status information for use in debugging. I also don't provide all the classes and member variable declarations needed to make it all work. In short, this stuff is provided for reference purposes only. Do not rely on it. If you want more info, leave me a comment.
WMI rocks. This routine is so easy. If only I could count on WMI everywhere. Note the use of an early-bound reference to the wbemdisp.dll v1.2 typelib, which is backward compatible with the original v1.1 interface. If you prefer late-bound, switch the SWbem... variables to Object.
Private Function GetIPAddressesViaWMI() As Boolean Dim objServices As SWbemServices Dim objNetAdapterConfigs As SWbemObjectSet Dim objNetAdapterConfig As SWbemObject Dim lngIndex As Long Dim varValue As Variant Dim strSelect As String On Error Resume Next ' Attempt to instantiate the WMI scripting services object and retrieve the ' relevant network adapter configurations. Note that the IPEnabled status ' will correctly filter out disabled adapters with IP enabled. Set objServices = GetObject("winmgmts:\\.\root\cimv2") If Err.Number = 0 Then strSelect = "SELECT DefaultIPGateway, DHCPServer, DNSServerSearchOrder " & _ "FROM Win32_NetworkAdapterConfiguration " & _ "where IPEnabled = TRUE" Set objNetAdapterConfigs = objServices.ExecQuery(strSelect) If Err.Number = 0 Then ' We have an accessible and compatible WMI scripting interface. GetIPAddressesViaWMI = True If objNetAdapterConfigs.Count > 0 Then For Each objNetAdapterConfig In objNetAdapterConfigs varValue = objNetAdapterConfig.Properties_("DNSServerSearchOrder").Value If Not IsNull(varValue) Then For lngIndex = LBound(varValue) To UBound(varValue) Call AddIPAddress(varValue(lngIndex), "DNS") Next lngIndex End If varValue = objNetAdapterConfig.Properties_("DefaultIPGateway").Value If Not IsNull(varValue) Then For lngIndex = LBound(varValue) To UBound(varValue) Call AddIPAddress(varValue(lngIndex), "Gateway") Next lngIndex End If varValue = objNetAdapterConfig.Properties_("DHCPServer").Value If Not IsNull(varValue) Then If Len(varValue) > 0 Then Call AddIPAddress(varValue, "DHCP") End If End If Next objNetAdapterConfig End If Set objNetAdapterConfigs = Nothing If m_objAddresses.Count = 0 Then ' Set status indicating no IP addresses found. End If End If Set objServices = Nothing End If End Function
Without WMI, the next attempt uses some down and dirty API work. It all starts with a very simple function that aggregates the two function calls needed to get the DHCP and DNS IP addresses. Those functions both rely on a bunch of declarations, constants, and UDTs, which are shown below.
Private Function GetIPAddressesViaIPHelper() As Boolean If GetIPHelperDNSAddresses() Then If GetIPHelperGatewayDHCPAddresses() Then GetIPAddressesViaIPHelper = True If m_objAddresses.Count = 0 Then ' Set status indicating no IP addresses found. End If Else ' Clear any DNS addresses successfully obtained. Set m_objAddresses = New IPNetworkAddresses End If End If End Function ' ' IP Helper declarations, constants, UDTs, and functions. ' Private Const ERROR_SUCCESS As Long = 0 Private Const ERROR_NOT_SUPPORTED As Long = 50 Private Type IP_ADDRESS_STRING IpAddr(0 To 15) As Byte End Type Private Type IP_MASK_STRING IpMask(0 To 15) As Byte End Type Private Type IP_ADDR_STRING dwNext As Long IPAddress As IP_ADDRESS_STRING IpMask As IP_MASK_STRING dwContext As Long End Type Private Const MAX_HOSTNAME_LEN As Long = 128 Private Const MAX_DOMAIN_NAME_LEN As Long = 128 Private Const MAX_SCOPE_ID_LEN As Long = 256 Private Type FIXED_INFO HostName(0 To (MAX_HOSTNAME_LEN + 3)) As Byte DomainName(0 To (MAX_DOMAIN_NAME_LEN + 3)) As Byte CurrentDnsServer As Long DnsServerList As IP_ADDR_STRING NodeType As Long ScopeId(0 To (MAX_SCOPE_ID_LEN + 3)) As Byte EnableRouting As Long EnableProxy As Long EnableDns As Long End Type Private Const MAX_ADAPTER_NAME_LENGTH As Long = 256 Private Const MAX_ADAPTER_DESCRIPTION_LENGTH As Long = 128 Private Const MAX_ADAPTER_ADDRESS_LENGTH As Long = 8 Private Type IP_ADAPTER_INFO dwNext As Long ComboIndex As Long sAdapterName(0 To (MAX_ADAPTER_NAME_LENGTH + 3)) As Byte sDescription(0 To (MAX_ADAPTER_DESCRIPTION_LENGTH + 3)) As Byte dwAddressLength As Long sIPAddress(0 To (MAX_ADAPTER_ADDRESS_LENGTH - 1)) As Byte dwIndex As Long uType As Long uDhcpEnabled As Long CurrentIpAddress As Long IpAddressList As IP_ADDR_STRING GatewayList As IP_ADDR_STRING DhcpServer As IP_ADDR_STRING bHaveWins As Long PrimaryWinsServer As IP_ADDR_STRING SecondaryWinsServer As IP_ADDR_STRING LeaseObtained As Long LeaseExpires As Long End Type Private Declare Function GetNetworkParams Lib "iphlpapi.dll" _ (FixedInfo As Any, OutBufLen As Long) As Long Private Declare Function GetAdaptersInfo Lib "iphlpapi.dll" _ (TcpTable As Any, Size As Long) As Long Private Declare Sub CopyMemory Lib "kernel32" _ Alias "RtlMoveMemory" _ (Dest As Any, ByVal Source As Long, ByVal Size As Long) ' Here are the DNS addresses. Private Function GetIPHelperDNSAddresses() As Boolean Dim bufNetworkParameters() As Byte Dim lngSizeNeeded As Long Dim udtNetworkParameters As FIXED_INFO Dim lngNetworkParametersPointer As Long Dim udtIPAddress As IP_ADDR_STRING Dim lngIPAddressPointer As Long Dim strIPAddress As String Dim lngResult As Long On Error Resume Next ' The first API call determines the availability of the service and returns the ' total size needed to hold the array of network parameter structures. lngResult = GetNetworkParams(ByVal 0&, lngSizeNeeded) If (Err.Number = 0) And (lngResult <> ERROR_NOT_SUPPORTED) Then On Error GoTo 0 GetIPHelperDNSAddresses = True If lngSizeNeeded > 0 Then ReDim bufNetworkParameters(0 To lngSizeNeeded - 1) As Byte ' The second call actually retrieves the data. If GetNetworkParams(bufNetworkParameters(0), lngSizeNeeded) = ERROR_SUCCESS Then ' Copy the network parameters data to its own local UDT. lngNetworkParametersPointer = VarPtr(bufNetworkParameters(0)) Call CopyMemory(udtNetworkParameters, ByVal lngNetworkParametersPointer, LenB(udtNetworkParameters)) With udtNetworkParameters lngIPAddressPointer = VarPtr(.DnsServerList) Do While (lngIPAddressPointer <> 0) Call CopyMemory(udtIPAddress, ByVal lngIPAddressPointer, LenB(udtIPAddress)) With udtIPAddress strIPAddress = Split(StrConv(.IPAddress.IpAddr, vbUnicode), vbNullChar)(0) Call AddIPAddress(strIPAddress, "DNS") lngIPAddressPointer = .dwNext End With Loop End With Else ' Not sure why this would happen. An overflow or memory condition, I suppose. End If Else ' No configured IP enabled network adapters. End If Else ' The IP Helper library isn't available, or this feature isn't supported. End If End Function ' Here are the DHCP addresses. Private Function GetIPHelperGatewayDHCPAddresses() As Boolean Dim bufAdapters() As Byte Dim lngSizeNeeded As Long Dim udtAdapter As IP_ADAPTER_INFO Dim lngAdapterPointer As Long Dim udtIPAddress As IP_ADDR_STRING Dim lngIPAddressPointer As Long Dim strIPAddress As String Dim lngResult As Long On Error Resume Next ' The first API call determines the availability of the service and returns the ' total size needed to hold the array of adapter info structures. lngResult = GetAdaptersInfo(ByVal 0&, lngSizeNeeded) If (Err.Number = 0) And (lngResult <> ERROR_NOT_SUPPORTED) Then On Error GoTo 0 GetIPHelperGatewayDHCPAddresses = True If lngSizeNeeded > 0 Then ReDim bufAdapters(0 To lngSizeNeeded - 1) As Byte ' The second call actually retrieves the data. If GetAdaptersInfo(bufAdapters(0), lngSizeNeeded) = ERROR_SUCCESS Then ' Enumerate the adapter info structures, copying each into its own local UDT. lngAdapterPointer = VarPtr(bufAdapters(0)) Do While (lngAdapterPointer <> 0) Call CopyMemory(udtAdapter, ByVal lngAdapterPointer, LenB(udtAdapter)) With udtAdapter ' First the gateway addresses. lngIPAddressPointer = VarPtr(.GatewayList) Do While (lngIPAddressPointer <> 0) Call CopyMemory(udtIPAddress, ByVal lngIPAddressPointer, LenB(udtIPAddress)) With udtIPAddress strIPAddress = Split(StrConv(.IPAddress.IpAddr, vbUnicode), vbNullChar)(0) Call AddIPAddress(strIPAddress, "Gateway") lngIPAddressPointer = .dwNext End With Loop ' Next the DHCP addresses. If .uDhcpEnabled = 1 Then lngIPAddressPointer = VarPtr(.DhcpServer) Do While (lngIPAddressPointer <> 0) Call CopyMemory(udtIPAddress, ByVal lngIPAddressPointer, LenB(udtIPAddress)) With udtIPAddress strIPAddress = Split(StrConv(.IPAddress.IpAddr, vbUnicode), vbNullChar)(0) Call AddIPAddress(strIPAddress, "DHCP") lngIPAddressPointer = .dwNext End With Loop End If ' Get the pointer to the next adapter info structure. lngAdapterPointer = .dwNext End With Loop Else ' Not sure why this would happen. An overflow or memory condition, I suppose. End If Else ' No configured IP enabled network adapters. End If Else ' The IP Helper library isn't available, or this feature isn't supported. End If End Function
This is pretty straightforward, but very limited. The logic diverges some depending on the Windows version (I didn't include the version-detection logic I used, because it's pretty easy). Note the use of an early-bound Windows Script Host (WSH) Shell object to access the Registry.
Private Function GetIPAddressesViaRegistry() As Boolean Dim objShell As IWshRuntimeLibrary.WshShell Dim strRoot As String Dim varKeys As Variant Dim strFullKey As String Dim lngKeyIndex As Long Dim varIPAddresses As Variant Dim lngIPAddressIndex As Long On Error Resume Next Set objShell = CreateObject("WScript.Shell") If Err.Number = 0 Then On Error GoTo 0 GetIPAddressesViaRegistry = True strRoot = "HKLM\SYSTEM\CurrentControlSet\Services\" ' Technically, a DHCPNameServer key won't be found on Win95, but there's no ' point in differentiating. varKeys = Array("NameServer", "DHCPNameServer") Select Case GetWinVersion() Case WinNT351, WinNT4, Win2K, WinXP strRoot = strRoot & "Tcpip\Parameters\" Case Else strRoot = strRoot & "VxD\MSTCP\" End Select ' Enumerate the keys. For lngKeyIndex = 0 To UBound(varKeys) varIPAddresses = objShell.RegRead(strRoot & varKeys(lngKeyIndex)) If Not IsArray(varIPAddresses) Then varIPAddresses = Replace(varIPAddresses, " ", ",") varIPAddresses = Replace(varIPAddresses, vbNullChar, ",") varIPAddresses = Split(varIPAddresses, ",") End If ' Enumerate the IPs found in the key. For lngIPAddressIndex = LBound(varIPAddresses) To UBound(varIPAddresses) Call AddIPAddress(varIPAddresses(lngIPAddressIndex), "DNS") Next lngIPAddressIndex Next lngKeyIndex Set objShell = Nothing If m_objAddresses.Count = 0 Then ' Set status indicating no IP addresses found. End If End If End Function
3. Ping IP addresses until success or no more addresses.
Now that I have a collection of IP addresses (or I don't have any, in which case I've preserved and presented enough information to help IT staffers figure out what I tried and why it didn't work), I ping them using the IP Helper library's ICMP functions IcmpCreateFile, IcmpSendEcho (this does the actual ping), and IcmpCloseHandle.
To format the string-based IP addresses to pass to ICMP, I use the Windows Sockets 2 (Winsock) functions WSAStartup, inet_addr (this does the actual conversion), and WSACleanup. The inet_addr function converts a string containing an (Ipv4) IP dotted address into a proper address for the IN_ADDR structure, which is required by the IcmpSendEcho function.
Apparently there are a multitude of potential issues with the use of Winsock, not the least of which is versioning. In my logic, I declare each of the aforementioned functions using winsock32.dll, which is a version-independent proxy for ws2_32.dll, and then request Winsock version 1.1 via WSAStartup. Version 1.1 is widely available on all versions of Windows, and is sufficient for what I need to do. Refer to the WSAStartup documentation to get a better handle on Winsock versioning (there's a table there that will blow your mind).
I'm not going to repeat my ping code here, as there are plenty of examples available elsewhere (see Knowledge Base article 300197 for one). However, I will note that for my purposes it was not necessary to evaluate each of the status codes potentially returned by inet_addr in its ICMP_ECHO_REPLY parameter. If I got a status of 0 it was a good ping, otherwise it wasn't, regardless of the actual reason.
That's it. Who knew it would be this hard? Probably everyone but me. Now, of course, I dread someone pointing out that it isn't. Live and learn.
If you need only to check if you are connected to a network (LAN or WAN), you can use the following code (no Win95 support; only works from Win98 and WinNT4).
int main(int argc, char* argv)
DWORD uSize = 32768;
bool bConnected = false;
pBuffer = new char[uSize];
pMIBTable = (PMIB_IFTABLE)pBuffer;
uResult = GetIfTable ( pMIBTable, &uSize, false );
if ( uResult == NO_ERROR )
cout dwNumEntries; i ++ )
if ( pMIBTable->table[i].dwType != MIB_IF_TYPE_LOOPBACK )
if ( pMIBTable->table[i].dwOperStatus == MIB_IF_OPER_STATUS_CONNECTED
|| pMIBTable->table[i].dwOperStatus == MIB_IF_OPER_STATUS_OPERATIONAL )
bConnected = true;
cout table[i].dwSpeed << "\n" << flush;
cout << "GetIfTable error " << uResult << " size " << uSize << "\n" << flush;
cout << "Connected? " << ( bConnected ? "TRUE" : "FALSE" ) << "\n" << flush;
Peter Cseke | 2004.02.24 06:01 AM
Peter - Thanks for the pointer. I explored the IP Helper APIs some (as noted), but didn't notice the MIB-II interface table info, and so wound up only using it to retrieve DNS addresses. Looks easy enough, though. Wonder whether this is the same info tapped into and returned by WMI?
ewbi.develops | 2004.03.01 10:49 AM
This was a terrific article and collection of information. I am implementing a similar mechanism using IP Helpers. If anything new comes up I will be sure to post it here.
Scott Herriman | 2004.04.01 02:43 AM
Scott - Thanks for the feedback. I'm always surprised to discover someone actually reading this stuff. I appreciate you taking the time to write and I look forward to anything you might add.
ewbi.develops | 2004.04.01 03:26 AM
I have the same problem. In my case however, the ping using ICMP functions for Win98 2nd Edition doesn't return an ECHO packet. In this regard, may i request if you can give me a copy of your actual function.
Allan | 2004.08.04 10:24 PM
Additional comments concerning the sample code above:
1) The varValue variable in GetIPAddressesViaWMI should be checked to be sure it is an array before jumping into the loop; otherwise, if it isn't, you'll be off in an endless loop. Note that I've only ever seen one machine return a non-null varValue that wasn't an array, and we still can't figue out why it does.
2) InternetGetConnectedState has proven to be worthless. We get back so many false negatives from it that we've virtually abandoned it. If you do want to use it, then ignore every false response where lngConnFlags is not 0 (zero).
ewbi.develops | 2004.12.31 12:32 PM
Good article, keep em coming
PC networking | 2008.04.23 04:51 PM
TrackBack URL: http://www.typepad.com/services/trackback/6a00d8341c7bd453ef00d83424905353ef
Listed below are links to weblogs that reference Detecting IP Network Availability: