Internet Control Message Protocol

ICMP is used as a means of messaging between hosts. Most ICMP messages relate to errors that occur in communication between hosts; the remaining ICMP messages are used to query hosts. The ICMP protocol uses IP addressing because it is a protocol encapsulated within an IP datagram. Figure 13-1 illustrates the fields of an ICMP message. The ICMP message is wrapped in an IP header.

The first field is the ICMP message type, which can be classified as either a query or an error. The code field further defines the type of query or message. The checksum field is the 16-bit one's complement sum of the ICMP header. Finally, the ICMP contents depend on the ICMP type and code. Table 13-1 lists the various types and codes.

When an ICMP error message is generated, the message always contains the IP header and the first 8 bytes of the IP datagram that caused the ICMP error to occur. This allows the host receiving the ICMP error to associate the message with one particular protocol and process associated with that error. In our case, Ping relies on the echo request and echo reply ICMP queries rather than on error messages. Hosts generate ICMP messages in response to problems with TCP or UDP; ICMP doesn't have many applications beyond that. In the next section, we will discuss how to use the ICMP protocol with a raw socket to generate a Ping request by using the echo request and echo reply messages. If you require additional information about ICMP errors or the other types of ICMP queries, consult more in-depth sources, such as Stevens's TCP/IP Illustrated Vol. 1.

click to view at full size.

Figure 13-1. ICMP header

Table 13-1. ICMP message types

TypeQuery/Error (Error Type)CodeDescription
0Query0Echo reply
3Error: Destination unreachable 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Network unreachable
Host unreachable
Protocol unreachable
Port unreachable
Fragmentation needed, but the Don't Fragment bit has been set
Source route failed
Destination network unknown
Destination host unknown
Source host isolated (obsolete)
Destination network administratively prohibited
Destination host administratively prohibited
Network unreachable for TOS
Host unreachable for TOS
Communication administratively prohibited by filtering
Host precedence violation
Precedence cutoff in effect
4Error0Source quench
5Error: Redirect0
1
2
3
Redirect for network
Redirect for host
Redirect for TOS and network
Redirect for TOS and host
8Query0Echo request
9Query0Router advertisement
10Query0Router solicitation
11Error: Time exceeded0
1
TTL equals 0 during transit
TTL equals 0 during reassembly
12Error: Parameter problem0IP header bad
Required option missing
13Query0Time stamp request
14Query0Time stamp reply
15Query0Information request
16Query0Information reply
17Query0Address mask request
18Query0Address mask reply

Ping Example

A ping is often used to determine whether a particular host is alive and reachable through the network. By generating an ICMP echo request and directing it to the host you are interested in, you can determine whether you can successfully reach that machine. Of course, this does not guarantee that a socket client will be able to connect to a process on that host (a process on the remote server might not be listening, for example); it just means that the network layer of the remote host is responding to network events. Essentially, the Ping example performs the following steps:

  1. Creates a socket of type SOCK_RAW and protocol IPPROTO_ICMP
  2. Creates and initializes the ICMP header
  3. Calls sendto or WSASendto to send the ICMP request to the remote host
  4. Calls recvfrom or WSARecvfrom to receive any ICMP responses

When you send the ICMP echo request, the remote machine intercepts the ICMP query and generates an echo reply message back to you. If for some reason the host is not reachable, the appropriate ICMP error message—such as destination host unreachable—will be returned by a router somewhere along the path to the intended recipient. If the physical network connection to the host is good but the remote host is either down or not responding to network events, you need to perform your own timeout to determine this. The Ping.c example in Figure 13-2 illustrates how to create a socket capable of sending and receiving ICMP packets, as well as how to use the IP_OPTIONS socket option to implement the record route option.

Figure 13-2. Ping.c

 // Module Name: Ping.c // // Command Line Options/Parameters: // Ping [host] [packet-size] // // host String name of host to ping // packet-size Integer size of packet to send // (smaller than 1024 bytes) // //#pragma pack(1) #define WIN32_LEAN_AND_MEAN #include <winsock2.h> #include <ws2tcpip.h> #include <stdio.h> #include <stdlib.h> #define IP_RECORD_ROUTE 0x7 // // IP header structure // typedef struct _iphdr { unsigned int h_len:4; // Length of the header unsigned int version:4; // Version of IP unsigned char tos; // Type of service unsigned short total_len; // Total length of the packet unsigned short ident; // Unique identifier unsigned short frag_and_flags; // Flags unsigned char ttl; // Time to live unsigned char proto; // Protocol (TCP, UDP, etc.) unsigned short checksum; // IP checksum unsigned int sourceIP; unsigned int destIP; } IpHeader; #define ICMP_ECHO 8 #define ICMP_ECHOREPLY 0 #define ICMP_MIN 8 // Minimum 8-byte ICMP packet (header) // // ICMP header structure // typedef struct _icmphdr { BYTE i_type; BYTE i_code; // Type sub code USHORT i_cksum; USHORT i_id; USHORT i_seq; // This is not the standard header, but we reserve space for time ULONG timestamp; } IcmpHeader; // // IP option header--use with socket option IP_OPTIONS // typedef struct _ipoptionhdr { unsigned char code; // Option type unsigned char len; // Length of option hdr unsigned char ptr; // Offset into options unsigned long addr[9]; // List of IP addrs } IpOptionHeader; #define DEF_PACKET_SIZE 32 // Default packet size #define MAX_PACKET 1024 // Max ICMP packet size #define MAX_IP_HDR_SIZE 60 // Max IP header size w/options BOOL bRecordRoute; int datasize; char *lpdest; // // Function: usage // // Description: // Print usage information // void usage(char *progname) { printf("usage: ping -r <host> [data size]\n"); printf(" -r record route\n"); printf(" host remote machine to Ping\n"); printf(" datasize can be up to 1 KB\n"); ExitProcess(-1); } // // Function: FillICMPData // // Description: // Helper function to fill in various fields for our ICMP request // void FillICMPData(char *icmp_data, int datasize) { IcmpHeader *icmp_hdr = NULL; char *datapart = NULL; icmp_hdr = (IcmpHeader*)icmp_data; icmp_hdr->i_type = ICMP_ECHO; // Request an ICMP echo icmp_hdr->i_code = 0; icmp_hdr->i_id = (USHORT)GetCurrentProcessId(); icmp_hdr->i_cksum = 0; icmp_hdr->i_seq = 0; datapart = icmp_data + sizeof(IcmpHeader); // // Place some junk in the buffer // memset(datapart,'E', datasize - sizeof(IcmpHeader)); } // // Function: checksum // // Description: // This function calculates the 16-bit one's complement sum // of the supplied buffer (ICMP) header // USHORT checksum(USHORT *buffer, int size) { unsigned long cksum=0; while (size > 1) { cksum += *buffer++; size -= sizeof(USHORT); } if (size) { cksum += *(UCHAR*)buffer; } cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >>16); return (USHORT)(~cksum); } // // Function: DecodeIPOptions // // Description: // If the IP option header is present, find the IP options // within the IP header and print the record route option // values // void DecodeIPOptions(char *buf, int bytes) { IpOptionHeader *ipopt = NULL; IN_ADDR inaddr; int i; HOSTENT *host = NULL; ipopt = (IpOptionHeader *)(buf + 20); printf("RR: "); for(i = 0; i < (ipopt->ptr / 4) - 1; i++) { inaddr.S_un.S_addr = ipopt->addr[i]; if (i != 0) printf(" "); host = gethostbyaddr((char *)&inaddr.S_un.S_addr, sizeof(inaddr.S_un.S_addr), AF_INET); if (host) printf("(%-15s) %s\n", inet_ntoa(inaddr), host->h_name); else printf("(%-15s)\n", inet_ntoa(inaddr)); } return; } // // Function: DecodeICMPHeader // // Description: // The response is an IP packet. We must decode the IP header to // locate the ICMP data. // void DecodeICMPHeader(char *buf, int bytes, struct sockaddr_in *from) { IpHeader *iphdr = NULL; IcmpHeader *icmphdr = NULL; unsigned short iphdrlen; DWORD tick; static int icmpcount = 0; iphdr = (IpHeader *)buf; // Number of 32-bit words * 4 = bytes iphdrlen = iphdr->h_len * 4; tick = GetTickCount(); if ((iphdrlen == MAX_IP_HDR_SIZE) && (!icmpcount)) DecodeIPOptions(buf, bytes); if (bytes < iphdrlen + ICMP_MIN) { printf("Too few bytes from %s\n", inet_ntoa(from->sin_addr)); } icmphdr = (IcmpHeader*)(buf + iphdrlen); if (icmphdr->i_type != ICMP_ECHOREPLY) { printf("nonecho type %d recvd\n", icmphdr->i_type); return; } // Make sure this is an ICMP reply to something we sent! // if (icmphdr->i_id != (USHORT)GetCurrentProcessId()) { printf("someone else's packet!\n"); return ; } printf("%d bytes from %s:", bytes, inet_ntoa(from->sin_addr)); printf(" icmp_seq = %d. ", icmphdr->i_seq); printf(" time: %d ms", tick - icmphdr->timestamp); printf("\n"); icmpcount++; return; } void ValidateArgs(int argc, char **argv) { int i; bRecordRoute = FALSE; lpdest = NULL; datasize = DEF_PACKET_SIZE; for(i = 1; i < argc; i++) { if ((argv[i][0] == '-') || (argv[i][0] == '/')) { switch (tolower(argv[i][1])) { case 'r': // Record route option bRecordRoute = TRUE; break; default: usage(argv[0]); break; } } else if (isdigit(argv[i][0])) datasize = atoi(argv[i]); else lpdest = argv[i]; } } // // Function: main // // Description: // Set up the ICMP raw socket, and create the ICMP header. Add // the appropriate IP option header, and start sending ICMP // echo requests to the endpoint. For each send and receive, // we set a timeout value so that we don't wait forever for a // response in case the endpoint is not responding. When we // receive a packet, decode it. // int main(int argc, char **argv) { WSADATA wsaData; SOCKET sockRaw = INVALID_SOCKET; struct sockaddr_in dest, from; int bread, fromlen = sizeof(from), timeout = 1000, ret; char *icmp_data = NULL, *recvbuf = NULL; unsigned int addr = 0; USHORT seq_no = 0; struct hostent *hp = NULL; IpOptionHeader ipopt; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf("WSAStartup() failed: %d\n", GetLastError()); return -1; } ValidateArgs(argc, argv); // // WSA_FLAG_OVERLAPPED flag is required for SO_RCVTIMEO, // SO_SNDTIMEO option. If NULL is used as last param for // WSASocket, all I/O on the socket is synchronous, the // internal user mode wait code never gets a chance to // execute, and therefore kernel-mode I/O blocks forever. // A socket created via the socket function has the over- // lapped I/O attribute set internally. But here we need // to use WSASocket to specify a raw socket. // // If you want to use timeout with a synchronous // nonoverlapped socket created by WSASocket with last // param set to NULL, you can set the timeout by using // the select function, or you can use WSAEventSelect and // set the timeout in the WSAWaitForMultipleEvents // function. // sockRaw = WSASocket (AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED); if (sockRaw == INVALID_SOCKET) { printf("WSASocket() failed: %d\n", WSAGetLastError()); return -1; } if (bRecordRoute) { // Setup the IP option header to go out on every ICMP packet // ZeroMemory(&ipopt, sizeof(ipopt)); ipopt.code = IP_RECORD_ROUTE; // Record route option ipopt.ptr = 4; // Point to the first addr offset ipopt.len = 39; // Length of option header ret = setsockopt(sockRaw, IPPROTO_IP, IP_OPTIONS, (char *)&ipopt, sizeof(ipopt)); if (ret == SOCKET_ERROR) { printf("setsockopt(IP_OPTIONS) failed: %d\n", WSAGetLastError()); } } // Set the send/recv timeout values // bread = setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)); if(bread == SOCKET_ERROR) { printf("setsockopt(SO_RCVTIMEO) failed: %d\n", WSAGetLastError()); return -1; } timeout = 1000; bread = setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout)); if (bread == SOCKET_ERROR) { printf("setsockopt(SO_SNDTIMEO) failed: %d\n", WSAGetLastError()); return -1; } memset(&dest, 0, sizeof(dest)); // // Resolve the endpoint's name if necessary // dest.sin_family = AF_INET; if ((dest.sin_addr.s_addr = inet_addr(lpdest)) == INADDR_NONE) { if ((hp = gethostbyname(lpdest)) != NULL) { memcpy(&(dest.sin_addr), hp->h_addr, hp->h_length); dest.sin_family = hp->h_addrtype; printf("dest.sin_addr = %s\n", inet_ntoa(dest.sin_addr)); } else { printf("gethostbyname() failed: %d\n", WSAGetLastError()); return -1; } } // // Create the ICMP packet // datasize += sizeof(IcmpHeader); icmp_data = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PACKET); recvbuf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PACKET); if (!icmp_data) { printf("HeapAlloc() failed: %d\n", GetLastError()); return -1; } memset(icmp_data,0,MAX_PACKET); FillICMPData(icmp_data,datasize); // // Start sending/receiving ICMP packets // while(1) { static int nCount = 0; int bwrote; if (nCount++ == 4) break; ((IcmpHeader*)icmp_data)->i_cksum = 0; ((IcmpHeader*)icmp_data)->timestamp = GetTickCount(); ((IcmpHeader*)icmp_data)->i_seq = seq_no++; ((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data, datasize); bwrote = sendto(sockRaw, icmp_data, datasize, 0, (struct sockaddr*)&dest, sizeof(dest)); if (bwrote == SOCKET_ERROR) { if (WSAGetLastError() == WSAETIMEDOUT) { printf("timed out\n"); continue; } printf("sendto() failed: %d\n", WSAGetLastError()); return -1; } if (bwrote < datasize) { printf("Wrote %d bytes\n", bwrote); } bread = recvfrom(sockRaw, recvbuf, MAX_PACKET, 0, (struct sockaddr*)&from, &fromlen); if (bread == SOCKET_ERROR) { if (WSAGetLastError() == WSAETIMEDOUT) { printf("timed out\n"); continue; } printf("recvfrom() failed: %d\n", WSAGetLastError()); return -1; } DecodeICMPHeader(recvbuf, bread, &from); Sleep(1000); } // Cleanup // if (sockRaw != INVALID_SOCKET) closesocket(sockRaw); HeapFree(GetProcessHeap(), 0, recvbuf); HeapFree(GetProcessHeap(), 0, icmp_data); WSACleanup(); return 0; } 

One noticeable feature of the Ping example is its use of the IP_OPTIONS socket option. We use the record route IP option so that when our ICMP packet hits a router, its IP address is added into the IP option header at the location indicated by the offset field in the IP option header. This offset is also incremented by 4 each time a router adds its address. The increment value is based on the fact that an IP version 4 address is 4 bytes in length. This book does not address any IP version 6 concerns, as no current Windows platforms support this yet. Once you receive the echo reply, decode the option header and print the IP addresses and host names of the routers visited. See Chapter 9 for more information on the other types of IP options available.

Traceroute

Another valuable IP networking tool is the Traceroute utility. This allows you to determine the IP addresses of the routers that are traversed in order to reach a certain host on the network. With Ping, using the record route option in the IP option header also allows you to determine the IP addresses of intermediary routers, but Ping is limited to only 9 hops—the maximum space allocated for addresses in the option header. A hop occurs whenever an IP datagram must pass through a router in order to traverse multiple physical networks. For routes with more than 9 hops, use Traceroute.

The idea behind Traceroute is to send a UDP packet to the destination and incrementally change the IP time-to-live (TTL) value. Initially, the TTL value is 1, which means the UDP packet will reach the first router, where the TTL will expire. The expiration will cause the router to generate an ICMP time-exceeded packet. Then the initial TTL value increases by 1, so this time the UDP packet gets one router farther and an ICMP time-exceeded packet is sent from that router. Collecting each of the ICMP messages gives you a clear path of the IP addresses traversed in order to reach the endpoint. Once the TTL is incremented enough so that packets actually reach the endpoint in question, an ICMP port-unreachable message is most likely returned, as no process on the recipient is waiting for this message.

Traceroute is a useful utility because it gives you a lot of information about the route to a particular host, which is often a concern when you use multicasting or when you experience routing problems. Fewer applications need to perform a Traceroute programmatically than a ping, but certain tasks might require Traceroute-like capabilities.

Two methods can be used to implement the Traceroute program. First you can use UDP packets and send datagrams, incrementally changing the TTL. Each time the TTL expires, an ICMP message will be returned to you. This method requires one socket of the UDP protocol to send the messages and another socket of the ICMP protocol to read the returned messages. The UDP socket is a normal UDP socket, as you saw in Chapter 7. The ICMP socket is of type SOCK_RAW and protocol IPPROTO_ICMP. The TTL of the UDP socket needs to be manipulated via the IP_TTL socket option. Alternatively, you can create a UDP socket and use the IP_HDRINCL option (discussed later in this chapter) to set the TTL manually within the IP header, but this is quite a lot of work.

The other method is simply to send ICMP packets to the destination, also incrementally changing the TTL. This also results in ICMP error messages being returned when the TTL expires. This method resembles the Ping example in that it requires only one socket (of the ICMP protocol). Under the sample code folder on the companion CD, you will find a Traceroute example using ICMP packets named Traceroute.c. We won't include the whole example in this chapter, as it is similar in design to the Ping example.



Network Programming for Microsoft Windows
Linux Server Hacks, Volume Two: Tips & Tools for Connecting, Monitoring, and Troubleshooting
ISBN: 735615799
EAN: 2147483647
Year: 1998
Pages: 159

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net