Internet Control Message Protocol (ICMP)

The Internet Control Message Protocol (ICMP) is a "support" protocol that is used for sending informational, control, and error messages between network endpoints. It was originally designed to give network routers a way to deliver errors to the network layer of the OSI model so it could decide how to handle the error. ICMP uses a form of IP datagrams known as raw sockets to send information between hosts and servers. Pocket PC, however, does not allow you to explicitly create raw sockets; rather, it uses a set of functions that enable you to send limited ICMP ping messages.

ICMP pinging can be extremely useful when performing functions such as network diagnostics, as it can be used to test network connections, routing, and your TCP/IP stack. To use the ICMP functions, you should include the files icmpapi.h, ipexport.h, and winsock.h in your project's source, as well as link with both the icmplib.lib and winsock.lib libraries.

In order to send an ICMP message, you must first obtain an ICMP handle by calling the following function:

HANDLE IcmpCreateFile (void);

No parameters are required, and an ICMP handle will be returned. If an error has occurred, you will receive an INVALID_HANDLE_VALUE and you can use the GetLastError() function to obtain more information about why it failed.

Once you have a valid ICMP handle, you can send your ICMP request by calling the IcmpSendEcho() function:

DWORD IcmpSendEcho (HANDLE IcmpHandle,
   IPAddr DestinationAddress,
   LPVOID RequestData, WORD RequestSize,
   PIP_OPTION_INFORMATION RequestOptions,
   LPVOID ReplyBuffer,
   DWORD ReplySize, DWORD Timeout);

For the first parameter, we pass in the handle to the newly created ICMP packet. The DestinationAddress parameter is an unsigned long value specifying the target IP address. You can get this by using one of the IP support functions, such as inet_addr(), described in the section "Name Resolution," later in this chapter. If you want to pass any additional data along with the ICMP message, you can pass a pointer to your buffer using the RequestData parameter, and set its size with the RequestSize field. You will also want to make sure that you don't try to stuff large messages into this buffer, as there is a limit of 8KB on most systems.

The next parameter, RequestOptions, is a pointer to an IP_OPTION_INFORMATION structure:

struct ip_option_information {
   unsigned char Ttl;
   unsigned char Tos;
   unsigned char Flags;
   unsigned char OptionsSize;
   unsigned char FAR *OptionsData;
};

The IP_OPTION_INFORMATION structure is used to configure your echo request. The Ttl (time to live) parameter determines the amount of time that the packet will be around before it expires. Next, the Tos field specifies the type of service for the ICMP packet, which should be set to 0. The only option that the Flags parameter supports on Pocket PC is IP_FLAG_DF, which instructs ICMP not to fragment the message. Finally, OptionsSize is set to the size of the OptionsData parameter, which is a pointer to any additional options. Currently, Pocket PC supports only IP_OPT_RR, which records the record route; and IP_OPT_TS, which records the timestamp.

The next parameters in IcmpSendEcho() are ReplyBuffer and ReplySize. The ReplyBuffer is a pointer to an array of ICMP_ECHO_REPLY structures that contains responses to our ICMP echo message from each machine to which our request was sent. It is important to ensure that the buffer you have allocated to receive the reply is set to be the size of ICMP_ECHO_REPLY plus eight additional bytes, which are used for any additional error information. The last parameter is Timeout, which is the amount of time IcmpSendEcho() will wait in milliseconds before failing.

IcmpSendEcho() will return the number of packets that are waiting in the ReplyBuffer if it is successful; otherwise, it will return 0. The ICMP_ECHO_REPLY response structure is defined as follows:

struct icmp_echo_reply {
   IPAddr Address;
   unsigned long Status;
   unsigned long RoundTripTime;
   unsigned short DataSize;
   unsigned short Reserved;
   void FAR *Data;
   struct ip_option_information Options;
};

Each network node with which we communicated during our echo request will respond with an ICMP_ECHO_REPLY package. The first field contains the Address of the machine that responded. The Status field should return IP_SUCCESS if successful; otherwise, it will contain an error code (defined in ipexport.h). Next, RoundTripTime contains the amount of time in milliseconds that it took for our request to get there. The DataSize field contains the size of the data, returned to us in the Data field. The last field, Options, is an IP_OPTIONS_STRUCTURE containing any options of the returning packet.

Finally, when you are finished sending your echo requests, you can close and clean up your ICMP packets by calling into the IcmpCloseHandle() function, which is defined as follows:

BOOL IcmpCloseHandle(HANDLE IcmpHandle);

The only parameter, IcmpHandle, is the ICMP session handle that you have been using. If ICMP closes successfully, this function will return TRUE. FALSE will be returned to you if an error has occurred.

The following example shows how we can use the ICMP functions to ping another network endpoint to determine whether our Internet connection is active (similar to the desktop ping utility):

// Initialize Winsock
WSADATA wsaData;

memset(&wsaData, 0, sizeof(WSADATA));
if(WSAStartup(MAKEWORD(1,1), &wsaData) != 0)
   return FALSE;

// Create a new ICMP message session
HANDLE hIcmpSession = IcmpCreateFile();
if(hIcmpSession == INVALID_HANDLE_VALUE)
   return FALSE;

// Convert the target address to a network address
HOSTENT *hostServer = gethostbyname("www.furrygoat.com");
if(hostServer == NULL) {
   int dwError = WSAGetLastError();
   return FALSE;
}

DWORD dwTargetAddress = *((u_long*)hostServer>h_addr_list[0]);

// Setup the option_information structure
IP_OPTION_INFORMATION ipOptions;
memset(&ipOptions, 0, sizeof(IP_OPTION_INFORMATION));

ipOptions.Ttl = 32;
ipOptions.Flags = IP_FLAG_DF;

// Send our request
BYTE bOutPacket[32];
BYTE bInPacket[1024];

int nTrace = IcmpSendEcho(hIcmpSession, dwTargetAddress, bOutPacket,
   sizeof(bOutPacket), &ipOptions, bInPacket, 1024, 5000);
if(nTrace == 0) {
   DWORD dwError = GetLastError();
   IcmpCloseHandle(hIcmpSession);
   return 0;
}

ICMP_ECHO_REPLY *pER = (PICMP_ECHO_REPLY)bInPacket;
for(int i =0;i<nTrace;i++) {
   TCHAR tchOutput[512] = TEXT("\0");
   struct in_addr sReplyAddr;
   sReplyAddr.S_un.S_addr = (IPAddr)pER->Address;

   wsprintf(tchOutput, TEXT("Reply from %hs"),
      inet_ntoa(sReplyAddr));
   MessageBox(NULL, tchOutput, TEXT("Ping"), MB_OK);
   pER++;
}

IcmpCloseHandle(hIcmpSession);