ICMP

ICMP

ICMP is used as a means of messaging between hosts. Also, there are two versions of ICMP. The original ICMP is used with IPv4 to pass informational messages between two hosts, usually relating to communications errors, such as destination unreachable or TTL exceeded. With IPv6, a new version of ICMP was created: ICMPv6. ICMPv6 includes the informational messages but also incorporates ND and MLD. As we discussed in Chapter 3, ND is the IPv6 equivalent to ARP and MLD is equivalent to IGMP. Our discussion of both versions of ICMP is limited to the informational messages.

As we mentioned previously, ICMP uses IPv4 addressing because it is a protocol encapsulated directly within an IPv4 datagram. Figure 11-1 illustrates the layout of an ICMP message. ICMPv6 is encapsulated in an IPv6 datagram and is identical in structure to the ICMP packet (as least in terms of the first four bytes).

Figure 11-1 ICMP header

The first field is the ICMP message type, which is typically 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. Note that the checksum computation is different for IPv4 and IPv6. For IPv4, the checksum is calculated over the ICMP header and its payload only, and for ICMPv6, the checksum is calculated over the IPv6 pseudo-header followed by the ICMPv6 header and payload. The IPv6 pseudo-header is comprised of the following fields:

  • 128-bit IPv6 source address

  • 128-bit IPv6 destination address

  • 32-bit upper layer protocol packet length

  • 24-bit zeroed field

  • 8-bit next header protocol value

IPv6 requires this pseudo-header calculation for any checksum calculation by an upper layer protocol that includes addresses from the IP header. This includes both UDP and ICMPv6. If the upper layer protocol contains its own packet length field, that value is used in the pseudo-header computation. Otherwise, the payload length from the IPv6 header is used, minus the size of all IPv6 extension headers present. Figure 11-5, later in this chapter, illustrates the IPv6 pseudo-header along with the UDP header and payload.

Finally, the ICMP contents depend on the ICMP type and code. Table 11-1 lists the most common types and codes for ICMP, and Table 11-2 lists the common types and codes for ICMPv6. The type and code of the ICMP packet dictates what is to follow the ICMP header.

Table 11-1 ICMP Message Types

Type

Query/Error (Error Type)

Code

Description

0

Query

0

Echo reply

3

Error: Destination unreachable

0

Network unreachable

1

Host unreachable

2

Protocol unreachable

3

Port unreachable

4

Fragmentation needed, but the Don't Fragment bit has been set

5

Source route failed

6

Destination network unknown

7

Destination host unknown

8

Source host isolated (obsolete)

3

Error: Destination unreachable

9

Destination network administratively prohibited

10

Destination host administratively prohibited

11

Network unreachable for TOS

12

Host unreachable for TOS

13

Communication administratively prohibited by filtering

14

Host precedence violation

15

Precedence cutoff in effect

4

Error

0

Source quench

5

Error: Redirect

0

Redirect for network

1

Redirect for host

2

Redirect for TOS and network

3

Redirect for TOS and host

8

Query

0

Echo request

9

Query

0

Router advertisement

10

Query

0

Router solicitation

11

Error: Time exceeded

0

TTL equals 0 during transit

1

TTL equals 0 during reassembly

12

Error: Parameter problem

0

IP header bad

1

Required option missing

When an ICMP error message is generated, it always contains as much of the IP header and IP payload that caused the error to occur without exceeding the MTU size. 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. In the next section, we will discuss how to use ICMP with a raw socket to generate a Ping request by using the echo request and echo reply messages. If you require more information about ICMP errors or the other types of ICMP queries, consult more in-depth sources, such as Stevens's TCP/IP Illustrated, Volume 1. Also, see RFCs 792 and 2463 for more information on ICMP and ICMPv6, respectively.

Table 11-2 ICMPv6 Message Types

Type

Query/Error (Error Type)

Code

Description

1

Error: Destination unreachable

0

No route to destination

1

Communication with destination administratively prohibited

3

Address unreachable

4

Port unreachable

2

Error: Packet too big

0

Packet is larger than MTU size and cannot be forwarded

3

Error: Time exceeded

0

Hop limit exceeded in transit

1

Fragment reassembly time exceeded

4

Error: Parameter problem

0

Erroneous header field encountered

1

Unrecognized Next Header type encountered

2

Unrecognized IPv6 option encountered

128

Query: Echo request

0

Request the destination to echo back the ICMP payload

129

Query: Echo reply

0

Reply to an echo request query

Ping Example

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 (for example, a process on the remote server might not be listening); it just means that the network layer of the remote host is responding to network events. Finally, most operating systems offer the capability to turn off responding to ICMP echo requests, which is often the case for machines running firewalls. Essentially, the Ping example performs the following steps.

  1. Creates a socket of address family AF_INET, type SOCK_RAW, and protocol IPPROTO_ICMP. For IPv6, the address family is AF_INET6, type SOCK_RAW, and protocol value 58.

  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.

Initializing the ICMP header is a straightforward task. First, the ICMP header is initialized with the type and code. Remember that the header is the same for ICMP and ICMPv6 (as shown in Figure 11-1). Following the type and code header, the echo request header must be supplied. This header is shown in Figure 11-2.

Figure 11-2 Echo request header

The first field is a 16-bit identifier, which is used to uniquely identify this request and is used to correlate echo replies received to your request and not some other process's request. Typically, the process identifier for the sending process is used. The next field is the sequence number, which identifies a given request packet from another. The 32-bit timestamp field is present only for ICMP requests (and not ICMPv6 requests). Following the request header is any payload. The following code sample illustrates initializing and sending an ICMP echo request for IPv4:

// Define the ICMP header typedef struct icmp_hdr {     unsigned char   icmp_type;     unsigned char   icmp_code;     unsigned short  icmp_checksum;     unsigned short  icmp_id;     unsigned short  icmp_sequence;     unsigned long   icmp_timestamp; } ICMP_HDR, *PICMP_HDR, FAR *LPICMP_HDR; ICMP_HDR *icmp=NULL; SOCKET  s; SOCKADDR_STORAGE dest; char  buf[sizeof(ICMP_HDR) + 32]; icmp = (ICMP_HDR *)buf; icmp->icmp_type = 8; // echo request type icmp->icmp_code = 0; icmp->icmp_id   = GetCurrentProcessId(); icmp->icmp_checksum = 0; // zero field before computing checksum icmp->icmp_sequence = 0; icmp->icmp_timestamp = GetTickCount(); // Fill in the payload with a random character memset(&buf[sizeof(ICMP_HDR)], '@', 32); // Compute the checksum over the ICMP header and payload //   The checksum() function computes the 16-bit one's //   complement on the specified buffer. See the Ping //   code sample on the companion CD for its implementation. icmp->icmp_checksum = checksum(buf, sizeof(ICMP_HDR)+32); s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); // Initialize the destination SOCKADDR_STORAGE ((SOCKADDR_IN *)&dest)->sin_family = AF_INET; ((SOCKADDR_IN *)&dest)->sin_port   = htons(0); // port is ignored for ICMP ((SOCKADDR_IN *)&dest)->sin_addr.s_addr = inet_addr("1.2.3.4"); sendto(s, buf, sizeof(ICMP_HDR)+32, 0, (SOCKADDR *)&dest,     sizeof(dest));

The only other difference between ICMP and ICMPv6 echo requests is computing the checksum contained in the ICMP header. For IPv4, the checksum is computed only over the ICMP header and payload. However, for IPv6 it is more complicated because IPv6 requires that the checksum include the IPv6 pseudo-header before the ICMPv6 header and payload. This means the Ping application must know the IPv6 source and destination address that will be in the IPv6 header to compute the checksum for any outgoing ICMPv6 requests. Because we are not building the IPv6 header by ourselves (as the case would be with the IPV6_HDRINCL option), we have no control over what goes into the IPv6 header. However, it is possible to query the transport for which local interface will be used to reach a given destination. This is performed with the SIO_ROUTING_INTERFACE_QUERY ioctl (see Chapter 7). Once this query is done, we have all the necessary information to compute the pseudo-header checksum.

When you send the ICMP echo request, the remote machine intercepts it and sends 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. Because the timestamp in the echo request is echoed, when the reply is received the elapsed time is easily calculated. The PING.CPP example on the companion CD 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 (supported for IPv4 only).

One noticeable feature of the Ping example is its use of the IP_OPTIONS socket option. We use the record route IPv4 option so that when the ICMP packet hits a router, its IPv4 address is added into the IPv4 option header at the location indicated by the offset field in the IPv4 option header. This offset is also incremented by four each time a router adds its address. The increment value is based on the fact that an IPv4 address is 4 bytes long. Once you receive the echo reply, decode the option header and print the IP addresses and host names of the routers visited. See Chapter 7 for more information about the other types of IP options available.

Traceroute

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

The idea behind Traceroute is to send a UDP packet to the destination and incrementally change the 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 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 to reach the endpoint. Once the TTL is incremented enough so that packets actually reach the endpoint, an ICMP port-unreachable message is most likely returned because 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 Traceroute programmatically than 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 UDP to send the messages and another socket of ICMP to read them. The UDP socket is a normal UDP socket, as you saw in Chapter 1. The ICMP socket is a raw socket, which we already discussed how to create. The TTL of the UDP socket needs to be manipulated via the IP_TTL or IPV6_UNICAST_HOPS 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 because it requires only one socket (of ICMP). Under the sample code folder on the companion CD, you will find a Traceroute example using ICMP packets named TRACERT.CPP. The traceroute is similar in structure and code to the Ping sample. The only difference is that the TTL value is incremented with each send.



Network Programming for Microsoft Windows
Network Programming for Microsoft Windows (Microsoft Professional Series)
ISBN: 0735605602
EAN: 2147483647
Year: 2001
Pages: 172
Authors: Anthony Jones

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