Windows ICMP API in C/C++

Introduction

In the old days, pinging a computer on windows required building an ICMP packet from scratch and using RAW sockets to send the packet to its destination. Worse was that you then had to listen for a response and parse this manually.

Thankfully, Microsoft since Windows 2000 made available an ICMP helper library so you don’t have to perform any hard work anymore. Well, admittedly, it’s much easier to use .NET than Win32 API but if you’re curious about how to do it with C/C++, read on.

Ping class and reply structure

Because we can sometimes resolve multiple addresses for one hostname, I’ve defined a structure to hold information about each response.

It contains basic information like the IP address as a string, the hostname (if available) and an status message.

typedef struct _ICMP_REPLY {
  DWORD dwCode;
  wchar_t Message[256];
  wchar_t Dns[255];
  wchar_t Ip[255];
  struct _ICMP_REPLY *next;
} ICMP_REPLY, *PICMP_REPLY;

The class with properties and methods.

class Ping {
  private:
    BOOL FlushDnsCache ();
    PICMP_REPLY rlist, current;
    void Add (PICMP_REPLY);
    void Clear (void);
    int SendEcho(ICMP_REPLY*, PADDRINFOW, int);
    HANDLE IcmpCreate(int);
  public:
    Ping() { rlist=NULL; }
    ~Ping();
    
    BOOL Send (wchar_t address[], int);
    BOOL Send (wchar_t address[], int, DWORD timeout);
    
    PICMP_REPLY GetReplies (void);
};

Flushing the DNS cache

Let’s say a machine has rebooted and you’re waiting for it to come back up and it just won’t respond to a ping anymore. Chances are your cache is out of date. While this may not be a problem, it’s still always good practice to flush the DNS cache before trying again.

So from the command line, you can use

ipconfig /flushdns

But if you don’t want to execute this command through an API, you can call an undocumented API DnsFlushResolverCache.

/**
 *
 * Uses undocumented API from DNSAPI.DLL
 *
 * Same as : ipconfig /flushdns
 *
 */
BOOL Ping::FlushDnsCache (VOID) {
  BOOL bResult = FALSE;

  BOOL (WINAPI *Flush) ();
  HMODULE hDNS = LoadLibrary (L"dnsapi");

  if (hDNS != NULL) {
    *(FARPROC *)&Flush = GetProcAddress (hDNS, "DnsFlushResolverCache");
    
    if (Flush != NULL) {
      bResult = Flush ();
    }
    FreeLibrary (hDNS);
  }
  return bResult;
}

Create an ICMP file

First thing to do is create an ICMP file handle. The following function will return handle for either IPV4 or IPV6 depending on value of family parameter which should be AF_INET or AF_INET6.

// create an ICMP handle for ipv4 or ipv6
HANDLE Ping::IcmpCreate(int family) 
{
  if (family==AF_INET) {
    return IcmpCreateFile();
  } else {
    return Icmp6CreateFile();
  }
}

Resolving network address for hostname

Steps to resolve all network addresses for a hostname

  1. Resolve all addresses using GetAddrInfo
  2. Resolve name for each address using GetNameInfo
  3. Convert network address to string using WSAAddressToString
BOOL Ping::Send (wchar_t address[], int family, DWORD timeout)
{
  ADDRINFOW  hints;
  PADDRINFOW e, list = NULL;
  ICMP_REPLY r;
  wchar_t    host[NI_MAXHOST], serv[NI_MAXSERV], ip[INET6_ADDRSTRLEN];
  DWORD      res, size, idx;
    
  // clear any previous entries
  Clear();
  
  if (FlushDnsCache ()) 
  {
    ZeroMemory (&hints, sizeof (hints));

    hints.ai_family   = family;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    
    // resolve all available addresses
    if (GetAddrInfo (address, NULL, &hints, &list) == NO_ERROR) 
    {    
      // loop through each entry
      for (e=list; e!=NULL; e=e->ai_next) 
      {  
        // resolve name if available
        res = GetNameInfo (e->ai_addr, sizeof (SOCKADDR), host,
            NI_MAXHOST, serv, NI_MAXSERV, NI_NUMERICSERV);
        
        // copy name to structure
        StrCpy (r.Dns, (res == NO_ERROR) ? host : L"unresolved");
        
        // convert ip address to string
        size = sizeof (ip);
        
        res = WSAAddressToString (e->ai_addr, 
            (DWORD)e->ai_addrlen, NULL, ip, &size);
        
        StrCpy (r.Ip, 
          (res == NO_ERROR) ? ip : family==AF_INET ? L"0.0.0.0" : L"::");
          
        if (SendEcho(&r, e, timeout)) {
          Add (&r);        
        }
      }
      FreeAddrInfo (list);
    } else {
      printf ("\n%i", GetLastError());
    }
  }
  return rlist != NULL;
}

Sending echo

  • Create ICMP file handle
  • If IPV4, use IcmpSendEcho, if IPV6, use Icmp6SendEcho2
int Ping::SendEcho(ICMP_REPLY *r, PADDRINFOW addr, int timeout)
{
  wchar_t             req_data[4];
  PICMP_ECHO_REPLY    pReply4;
  PICMPV6_ECHO_REPLY  pReply6;
  LPVOID              reply[sizeof(ICMPV6_ECHO_REPLY) + sizeof(req_data) * 2];
  HANDLE              hIcmpFile;
  struct sockaddr_in  v4;
  struct sockaddr_in6 v6, sa;  
  DWORD               reply_size, idx;
  
  reply_size = sizeof(reply);
  
  // create icmp file handle
  hIcmpFile = IcmpCreate(addr->ai_family);
  
  if (hIcmpFile==NULL) return 0;
  
  if (addr->ai_family==AF_INET)
  {
    // send ipv4
    memcpy (&v4, addr->ai_addr, addr->ai_addrlen);
    
    IcmpSendEcho (hIcmpFile,
      (IPAddr)v4.sin_addr.S_un.S_addr, req_data, 
      sizeof(req_data), NULL, 
      reply, reply_size, timeout);
      
      pReply4 = (PICMP_ECHO_REPLY)reply;
      r->dwCode = pReply4->Status;
  } else {
    // send ipv6
    memcpy(&v6, addr->ai_addr, addr->ai_addrlen);
    
    sa.sin6_addr     = in6addr_any;
    sa.sin6_family   = AF_INET6;
    sa.sin6_flowinfo = 0;
    sa.sin6_port     = 0;
    
    Icmp6SendEcho2 (hIcmpFile, NULL, NULL, NULL, 
      &sa, &v6, req_data, sizeof(req_data),
      NULL, reply, reply_size, timeout);
          
    pReply6 = (PICMPV6_ECHO_REPLY)reply; 
    r->dwCode = pReply6->Status;    
  }
 
  StrCpy (r->Message, L"Check status code");

  for (idx=0; idx<sizeof(pStatus)/sizeof(STATUS_MSG); idx++) 
  {
    if (r->dwCode == pStatus[idx].dwCode) 
    {
      StrCpy (r->Message, pStatus[idx].pMessage);
    }
  }
  IcmpCloseHandle (hIcmpFile);
  return 1;  
}

Demo

Simple demonstration using both ipv4 and ipv6

See sources here

Advertisements
This entry was posted in networking, programming, windows and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s