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.


  • Use the Windows Management Instrumentation (WMI) Scripting API (wbemdisp.dll)
  • 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.


  • Use the IP Helper API (iphlpapi.dll) functions GetNetworkParams and GetAdaptersInfo
  • 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:

    http://support.microsoft.com/?id=193059
    http://support.microsoft.com/default.aspx?scid=kb;EN-US;q234573

    Here are some sample implementations (note the lack of a check for ERROR_NOT_SUPPORTED on NT):

    http://www.mvps.org/vbnet/code/network/getnetworkparamsdns.htm
    http://www.mvps.org/vbnet/code/network/getadaptersinfo-dhcpserveraddresses.htm

    Here are some examples of real-world issues with this approach:

    http://groups.google.com/groups?selm=OL8R2D0z%23GA.215%40cppssbbsa02.microsoft.com
    http://groups.google.com/groups?selm=OXqwS6NwBHA.492%40tkmsftngp02
    http://groups.google.com/groups?selm=%232hPJLyaDHA.2668%40TK2MSFTNGP09.phx.gbl


  • Read the IP addresses directly from the Registry
  • 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:

    http://www.phekda.freeserve.co.uk/richdawe/dl/winnetip.htm
    http://groups.google.com/groups?selm=t733majkll10c2%40corp.supernews.com
    http://groups.google.com/groups?selm=37A29067.E3042FDC%40netvigator.com
    http://www.microsoft.com/windows2000/techinfo/reskit/en-us/regentry/58823.asp

    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.


  • Shell execute IPCONFIG.exe and WINIPCFG.exe and parse the results
  • 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:

    http://support.microsoft.com/support/kb/articles/Q155/6/02.ASP

    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.

    GetIPAddressesViaWMI

    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
    

    GetIPAddressesViaIPHelper

    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
    

    GetIPAddressesViaRegistry

    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.


    Comments

    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).

    #include
    #include
    #include

    int main(int argc, char* argv[])
    {
    PMIB_IFTABLE pMIBTable;
    char* pBuffer;
    DWORD uResult;
    DWORD uSize = 32768;
    int i;
    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].dwType;
    cout table[i].dwOperStatus;
    cout table[i].dwAdminStatus;
    cout table[i].dwMtu;
    cout table[i].dwSpeed << "\n" << flush;
    }
    }
    else
    {
    cout << "GetIfTable error " << uResult << " size " << uSize << "\n" << flush;
    }

    cout << "Connected? " << ( bConnected ? "TRUE" : "FALSE" ) << "\n" << flush;

    delete[] pBuffer;

    return 0;
    }

    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?

    Thanks again!

    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.

    Thanks,
    Scott

    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

    TrackBack URL:  http://www.typepad.com/services/trackback/6a00d8341c7bd453ef00d83424905353ef

    Listed below are links to weblogs that reference Detecting IP Network Availability: