Friday 4 November 2011

How to get notified when network adapters' IP addresses change? (Windows OS)

TCP/IP networking software requires at least one network adapter bounded to TCP/IP protocol stack. Each physical network card has unique MAC address and static or dynamic IP address. Static address (one or possibly more) is configured manually and dynamic address is assigned by Dynamic Host Configuration Protocol (DHCP) server (if DHCP is enabled; Obtain IP address automatically is ticked in TCP/IPv4 Properties window). Operating system maintains table which maps IP addresses to network interfaces. Use ipconfig command in command prompt window to display that table. If your adapter has DHCP enabled and you unplug/plug network cable (if using LAN adapter), disable/enable network adapter through adapter settings, issue ipconfig /release or /renew command, get out of/into WiFi range (if using WiFi adapter)...your adapter will loose existing or get a new IP address. NIC to IP address table will change.

Networking applications often need to be aware when changes in this table occur. Windows API function NotifyAddrChange notifies caller on this event. Here is an example of how to call it synchronously (when it's blocking):

#if !defined(_MT)
#error _beginthreadex requires a multithreaded C run-time library.
#endif

#include <winsock2.h>
#include <iphlpapi.h>
#include <Windows.h>
#include <iostream>
#include <sstream>
#include <process.h>
#include <iomanip>

#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "ws2_32.lib")

#define APP_RETURN_CODE_SUCCESS 0
#define APP_RETURN_CODE_ERROR 1

#define THREAD_RETURN_CODE_SUCCESS 0
#define THREAD_RETURN_CODE_ERROR 1

// 127.0.0.1 in network byte order
#define IP_LOCALHOST 0x0100007F

void PrintIPTable()
    PMIB_IPADDRTABLE pIPAddrTable;

 pIPAddrTable = (MIB_IPADDRTABLE*)malloc(sizeof(MIB_IPADDRTABLE));

 if(!pIPAddrTable) 
 {
  std::cout << "malloc() failed" << std::endl;
  return;
 }
  
 // Before calling AddIPAddress we use GetIpAddrTable to get
 // an adapter to which we can add the IP
 // Make an initial call to GetIpAddrTable to get the
 // necessary size into the dwSize variable
 
 DWORD dwSize = 0;

 if(GetIpAddrTable(pIPAddrTable, &dwSize, 0) == ERROR_INSUFFICIENT_BUFFER) 
 {
  free(pIPAddrTable);
  pIPAddrTable = (MIB_IPADDRTABLE*)malloc(dwSize);

  if(!pIPAddrTable) 
  {
   std::cout << "malloc() failed" << std::endl;
   return;
  }
 }
 
 DWORD dwRetVal = 0;
 LPVOID lpMsgBuf;

 // Make a second call to GetIpAddrTable to get the actual data we want
 if((dwRetVal = GetIpAddrTable(pIPAddrTable, &dwSize, 0)) != NO_ERROR ) 
 { 
  std::cout << "GetIpAddrTable() failed. Error code: " << dwRetVal << std::endl;
  
  if(FormatMessage(
   FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 
   NULL, dwRetVal, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),       
   (LPTSTR) & lpMsgBuf, 0, NULL)) 
  {
   std::cout << "\tError: " << lpMsgBuf << std::endl;
   LocalFree(lpMsgBuf);
  }

  free(pIPAddrTable);
  return;  
 }

 std::cout << "\tNum Entries: " << pIPAddrTable->dwNumEntries << std::endl;
  
 for(int i = 0; i < (int)pIPAddrTable->dwNumEntries; i++) 
 {   
  std::cout << "\n\tInterface Index[" << i << "]:\t" << pIPAddrTable->table[i].dwIndex << std::endl;

  IN_ADDR IPAddr;

  IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table[i].dwAddr;
  std::cout << "\tIP Address[" << i << "]:     \t" << inet_ntoa(IPAddr) << std::endl;

  IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table[i].dwMask;
  std::cout << "\tSubnet Mask[" << i << "]:    \t" << inet_ntoa(IPAddr) << std::endl;

  IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table[i].dwBCastAddr;
  std::cout << "\tBroadCast[" << i << "]:      \t" << inet_ntoa(IPAddr) << "(" << pIPAddrTable->table[i].dwBCastAddr << ")" << std::endl;
  std::cout << "\tReassembly size[" << i << "]:\t" << pIPAddrTable->table[i].dwReasmSize << std::endl;

  std::cout << "\tType and State[" << i << "]:";

  if(pIPAddrTable->table[i].wType & MIB_IPADDR_PRIMARY)
   std::cout << "\tPrimary IP Address";

  if(pIPAddrTable->table[i].wType & MIB_IPADDR_DYNAMIC)
   std::cout << "\tDynamic IP Address";

  if(pIPAddrTable->table[i].wType & MIB_IPADDR_DISCONNECTED)
   std::cout << "\tAddress is on disconnected interface";

  if(pIPAddrTable->table[i].wType & MIB_IPADDR_DELETED)
   std::cout << "\tAddress is being deleted";

  if(pIPAddrTable->table[i].wType & MIB_IPADDR_TRANSIENT)
   std::cout << "\tTransient address";

  std::cout << std::endl;

  if(IP_LOCALHOST == pIPAddrTable->table[i].dwAddr)
  {
   std::cout << "\tLOCALHOST interface" << std::endl;
   // continue;
  }
    
  MIB_IFROW iInfo;
  memset(&iInfo, 0, sizeof(MIB_IFROW));
  iInfo.dwIndex = pIPAddrTable->table[i].dwIndex;
  GetIfEntry(&iInfo);

  std::cout << "\tNetwork interface name: " << iInfo.bDescr << std::endl;

  std::cout << "\tNetwork interface type: ";

  switch(iInfo.dwType)
  {
  case MIB_IF_TYPE_OTHER:
   std::cout << "OTHER" << std::endl;
   break;
  case MIB_IF_TYPE_ETHERNET:
   std::cout << "ETHERNET" << std::endl;
   break;
  case MIB_IF_TYPE_TOKENRING:
   std::cout << "TOKENRING" << std::endl;
   break;
  case MIB_IF_TYPE_FDDI:
   std::cout << "FDDI" << std::endl;
   break;
  case MIB_IF_TYPE_PPP:
   std::cout << "PPP" << std::endl;
   break;
  case MIB_IF_TYPE_LOOPBACK:
   std::cout << "LOOPBACK" << std::endl;
   break;
  case MIB_IF_TYPE_SLIP:
   std::cout << "SLIP" << std::endl;
   break;
  }

  const int unMACSegmentsCount = 6;

  if(unMACSegmentsCount == iInfo.dwPhysAddrLen)
  {      
   std::ostringstream ossMAC;
   ossMAC.fill('0');
   
   ossMAC << std::setw(2) << std::hex << static_cast<unsigned int>(iInfo.bPhysAddr[0]);

   for(int i = 1; i < unMACSegmentsCount; i++)
   {
    ossMAC << '-' << std::setw(2) << std::hex << static_cast<unsigned int>(iInfo.bPhysAddr[i]);
   }
    
   std::cout << "\tMAC Address:            " << ossMAC.str() << std::endl;   
  }  

  std::cout << std::endl;
 } 

 if (pIPAddrTable) 
 {
  free(pIPAddrTable);
  pIPAddrTable = 0;
 }
}

unsigned __stdcall thfn(void* args)
{
 HANDLE hTerminateEvent = *(HANDLE*)args;
 BOOL bTerminate = FALSE;
 
 OVERLAPPED overlap;
 overlap.hEvent = WSACreateEvent();

 if(overlap.hEvent == WSA_INVALID_EVENT)
 {
  std::cout << "WSACreateEvent() failed. Error code: " << WSAGetLastError() << std::endl;
  return THREAD_RETURN_CODE_ERROR;
 }

 while(!bTerminate)
 { 
  
  HANDLE h = 0;

  // call NotifyAddrChange in synchronous mode
  DWORD dwRetVal = NotifyAddrChange(&h, &overlap);

  if(dwRetVal != ERROR_IO_PENDING)
  {
   std::cout << "NotifyAddrChange() failed. Error code: " << WSAGetLastError() << std::endl;
   break;
  }

  HANDLE waitObjects[2] = {hTerminateEvent, overlap.hEvent};

  std::cout << "\nWaiting for IP Table change or termination request..." << std::endl;

  dwRetVal = WaitForMultipleObjects(2, waitObjects, FALSE, INFINITE);

  switch(dwRetVal)
  {
  case WAIT_OBJECT_0:
   std::cout << "WaitForSingleObject(waitObjects) returned WAIT_OBJECT_0 (hTerminateEvent is signaled)" << std::endl;
   bTerminate = TRUE;
   break;
  case WAIT_OBJECT_0 + 1:
   std::cout << "WaitForSingleObject(waitObjects) returned WAIT_OBJECT_0 + 1 (overlap.hEvent is signaled)" << std::endl;   
   
   if(!WSAResetEvent(overlap.hEvent))
   {
    std::cout << "WSAResetEvent() failed. Error code: " << WSAGetLastError() << std::endl;
    bTerminate = TRUE;
    break;
   }

   PrintIPTable();
   break;
  case WAIT_FAILED:
   std::cout << "WaitForSingleObject(waitObjects) returned WAIT_FAILED (function failed)" << std::endl;
   bTerminate = TRUE;
   break;
  }  
 }
 
 if(!WSACloseEvent(overlap.hEvent))
 {
  std::cout << "WSACloseEvent() failed. Error code: " << WSAGetLastError() << std::endl;
  return THREAD_RETURN_CODE_ERROR;
 }

 return THREAD_RETURN_CODE_SUCCESS;
}

void WaitUserInput()
{
 std::cin.clear(); 
 std::cin.ignore(1, '\n');
}

// NOTE: std::cout is shared between two threads and is not thread safe!
int main(int argc, char* argv[])
{
// std::cout << "main()" << std::endl;

 HANDLE hTerminateEvent = CreateEvent(0, TRUE, FALSE, 0);

 if(!hTerminateEvent)
 {
  std::cout << "CreateEvent() failed. Error code: " << GetLastError() << std::endl;
  return APP_RETURN_CODE_ERROR;
 }

 unsigned unThreadID = 0;
 HANDLE hThread = 0;

// std::cout << "Creating thread..." << std::endl;
 hThread = (HANDLE) _beginthreadex(0, 0, thfn, &hTerminateEvent, 0, &unThreadID);

 if(!hThread)
 {
  std::cout << "_beginthreadex() failed. Error code: " << errno << std::endl;
  return APP_RETURN_CODE_ERROR;
 }

 // (not reliable way to) make sure child thread has started before main thread continues
 Sleep(1000);

// std::cout << "Created thread with ID: " << unThreadID << std::endl;

 std::cout << "Press ENTER to terminate listening for changes in IP address table..." << std::endl;
 WaitUserInput();

 SetEvent(hTerminateEvent);

 // wait for thread to terminate
 DWORD dwRetVal = WaitForSingleObject(hThread, INFINITE);

 switch(dwRetVal)
 {
 case WAIT_OBJECT_0:
  std::cout << "WaitForSingleObject(hThread) returned WAIT_OBJECT_0 (event is signaled)" << std::endl;
  break;
 case WAIT_TIMEOUT:
  std::cout << "WaitForSingleObject(hThread) returned WAIT_TIMEOUT (timeout elapsed; event is nonsignaled)" << std::endl;
  break;
 case WAIT_FAILED:
  std::cout << "WaitForSingleObject(hThread) returned WAIT_FAILED (function failed)" << std::endl;
  break;
 }

 if(!CloseHandle(hThread))
 {
  std::cout << "CloseHandle() failed. Error code: " << GetLastError() << std::endl;
  return APP_RETURN_CODE_ERROR;
 }

 std::cout << "Press ENTER to exit..." << std::endl;
 WaitUserInput();

// std::cout << "~main()" << std::endl;
 return APP_RETURN_CODE_SUCCESS;
}

This is the output if we unplug network cable and then plug it back again:


Waiting for IP Table change or termination request...
Press ENTER to terminate listening for changes in IP address table...
WaitForSingleObject(waitObjects) returned WAIT_OBJECT_0 + 1 (overlap.hEvent is s
ignaled)
Num Entries: 2

Interface Index[0]: 17
IP Address[0]: 192.168.56.1
Subnet Mask[0]: 255.255.255.0
BroadCast[0]: 1.0.0.0(1)
Reassembly size[0]: 65535
Type and State[0]: Primary IP Address
Network interface name: VirtualBox Host-Only Ethernet Adapter
Network interface type: ETHERNET
MAC Address: 08-00-27-00-68-56


Interface Index[1]: 1
IP Address[1]: 127.0.0.1
Subnet Mask[1]: 255.0.0.0
BroadCast[1]: 1.0.0.0(1)
Reassembly size[1]: 65535
Type and State[1]: Primary IP Address
LOCALHOST interface
Network interface name: Software Loopback Interface 1
Network interface type: LOOPBACK


Waiting for IP Table change or termination request...
WaitForSingleObject(waitObjects) returned WAIT_OBJECT_0 + 1 (overlap.hEvent is s
ignaled)
Num Entries: 3

Interface Index[0]: 10
IP Address[0]: 192.168.253.122
Subnet Mask[0]: 255.255.255.0
BroadCast[0]: 1.0.0.0(1)
Reassembly size[0]: 65535
Type and State[0]: Primary IP Address Dynamic IP Address
Network interface name: Intel(R) 82566DC Gigabit Network Connection
Network interface type: ETHERNET
MAC Address: 00-19-d1-1b-e0-88


Interface Index[1]: 17
IP Address[1]: 192.168.56.1
Subnet Mask[1]: 255.255.255.0
BroadCast[1]: 1.0.0.0(1)
Reassembly size[1]: 65535
Type and State[1]: Primary IP Address
Network interface name: VirtualBox Host-Only Ethernet Adapter
Network interface type: ETHERNET
MAC Address: 08-00-27-00-68-56


Interface Index[2]: 1
IP Address[2]: 127.0.0.1
Subnet Mask[2]: 255.0.0.0
BroadCast[2]: 1.0.0.0(1)
Reassembly size[2]: 65535
Type and State[2]: Primary IP Address
LOCALHOST interface
Network interface name: Software Loopback Interface 1
Network interface type: LOOPBACK


Waiting for IP Table change or termination request...

WaitForSingleObject(waitObjects) returned WAIT_OBJECT_0 (hTerminateEvent is sign
aled)
WaitForSingleObject(hThread) returned WAIT_OBJECT_0 (event is signaled)
Press ENTER to exit...