Note: Common resolve.h and resolve.cpp files used in the program examples can be found in the last program sample.
A raw socket is one that allows access to the underlying transport protocol. This chapter is dedicated to illustrating how raw sockets can be used to simulate IP utilities, such as Traceroute and Ping. Raw sockets can also be used to manipulate IP header information. This chapter is concerned with the IPv4 and IPv6 protocols only; we will not address raw sockets with any other protocol because most protocols (except ATM) do not support raw sockets. All raw sockets are created using the SOCK_RAW socket type and are currently supported only under Winsock 2. Therefore, neither Microsoft Windows CE nor Windows 95 (without the Winsock 2 update) can use raw sockets.
In addition, using raw sockets requires substantial knowledge of the underlying protocol structure, which is not the focus of this book. In this chapter, we will discuss ICMP, ICMPv6, and UDP. ICMP (both versions) is used by the Ping utility, which can detect whether a route to a host is valid and whether the host machine is responding. Developers often need a programmatic method of determining whether a machine is alive and reachable. We will also examine UDP in conjunction with the IP_HDRINCL socket option to send completely fabricated IP packets. For all of these protocols, we will cover only the aspects necessary to fully explain the code in this chapter and in the example programs.
Raw Socket Creation
The first step in using raw sockets is creating the socket. You can use either socket() or WSASocket(). Note that for Windows 95, Windows 98, and Windows Me, no catalog entry in Winsock for IP has the SOCK_RAW socket type. However, this does not prevent you from creating this type of socket. It just means that you cannot create a raw socket using a WSAPROTOCOL_INFO structure. Refer back to Chapter 2 for information about enumerating protocol entries with the WSAEnumProtocols() function and the WSAPROTOCOL_INFO structure. You must specify the SOCK_RAW flag yourself in socket creation. The following code snippet illustrates the creation of a raw socket using ICMP as the underlying IP protocol:
SOCKET s;
s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
// Or
s = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (s == INVALID_SOCKET)
{
// Socket creation failed
}
|
When creating a raw socket, the protocol parameter of the socket call becomes the protocol value in the IP header. That is, if a raw AF_INET6 socket is created with the protocol value 66, the IPv6 header for outgoing packets will contain the value 66 in the next header field.
Because raw sockets offer the capability to manipulate the underlying transport, they can be used for malicious purposes and are a security issue in Windows NT. Therefore, only members of the Administrators group can create sockets of type SOCK_RAW. Anyone can create a raw socket on Windows NT, but non-Administrators will not be able to do anything with it because the bind API will fail with WSAEACCES. Windows 95, Windows 98, and Windows Me do not impose any kind of limitation.
To work around this limitation on Windows NT, you can disable the security check on raw sockets by creating the following registry variable and setting its value to the integer 1 as a DWORD type.
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\AFD\Parameters\DisableRawSecurity
After the registry change, you need to reboot the machine. In the socket creation code in the example, we used the ICMP protocol, but you can also use IGMP, UDP, IP, or raw IP using the flags IPPROTO_IGMP, IPPROTO_UDP, IPPROTO_IP, or IPPROTO_RAW, respectively. However, be aware that on Windows 95 (with Winsock 2), Windows 98, and Windows NT 4, you can use only IGMP and ICMP when creating raw sockets. The protocol flags IPPROTO_UDP, IPPROTO_IP, and IPPROTO_RAW require the use of the socket option IP_HDRINCL, which is not supported on those platforms. Windows Me and Windows 2000 and later versions support IP_HDRINCL, so it is possible to manipulate the IP header (IPPROTO_RAW), the TCP header (IPPROTO_TCP), and the UDP header (IPPROTO_UDP).
Once the raw socket is created with the appropriate protocol flags, you can use the socket handle in send and receive calls. When creating raw sockets, the IP header will be included in the data returned upon any receive, regardless of whether the IP_HDRINCL option is set. Applications will have to know the layout of the IP header and have to determine the length of the IP header to find the payload data within the received buffer.
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.
- 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.
- Creates and initializes the ICMP header.
- Calls sendto or WSASendTo to send the ICMP request to the remote host.
- 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:
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.
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. 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.
No comments:
Post a Comment