Example - How to Ping Another Machine, Raw Sockets Method

I have finally come up with a short ping program that uses raw sockets. It's still about 3 times longer (365 lines versus 122) than the ICMP method, but it will continue to work, while the ICMP method might fail to work on Windows 98 and Windows NT 5.0. Also, this version is more flexible.

This program is split into two major parts: a driver part and a "pinger" part. The driver mainly just declares main(), which calls the functions in the pinger part in the proper sequence. The pinger part is mostly reusable as-is, although you will probably want to do things like exchanging the output statements for encoded return values. There is also a separate module for the IP checksum calculation function because it is not specifically tied to pinging; this same algorithm is used in other parts of TCP/IP.

This program is based on a program in the Win32 SDK. However, hardly any of the original code remains. Also, this version is a bit smarter, compiles under both Microsoft and Borland C++, and should be much easier to understand and reuse.

There are a couple of other ping programs that use raw sockets available on the Net. One is Donald C. Asonye's 1,100-line port of the BSD ping program. This port seemed a little flakey. Also, being a "full-fledged" pinger, it has a bad case of featuritis, which makes it hard to learn from. Bob Quinn has different ping program, but I was unable to get it to compile. I didn't try very hard, however.


rawping_driver.cpp

/***********************************************************************
 rawping_driver.cpp - A driver program to test the rawping.cpp module.

 Building under Microsoft C++ 5.0: 
 
    cl -GX rawping.cpp rawping_driver.cpp ip_checksum.cpp ws2_32.lib

 Building under Borland C++ 5.0: 
 
    bcc32 rawping.cpp rawping_driver.cpp ip_checksum.cpp ws2_32.lib
  
 ----------------------------------------------------------------------
 Change log:
    2/14/1998 - Polished the program up and separated out the 
        rawping.cpp and ip_checksum.cpp modules.  Also got it to work
        under Borland C++.

    2/12/1998 - Fixed a problem with the checksum calculation.  Program
        works now.

    2/6/1998 - Created using Microsoft's "raw ping" sample in the Win32
        SDK as a model.  Not much remains of the original code.
***********************************************************************/

#include <winsock2.h>
#include <iostream.h>

#include "rawping.h"

#define DEF_PACKET_SIZE 32
#define MAX_PING_PACKET_SIZE (1024 + sizeof(IPHeader))
#define MAX_PING_DATA_SIZE (MAX_PING_PACKET_SIZE - sizeof(IPHeader))

int allocate_buffers(ICMPHeader*& send_buf, IPHeader*& recv_buf,
        int packet_size);


///////////////////////////////////////////////////////////////////////
// Program entry point

int main(int argc, char* argv[])
{
    // Init some variables at top, so they aren't skipped by the
    // cleanup routines.
    int seq_no = 0;
    ICMPHeader* send_buf = 0;
    IPHeader* recv_buf = 0;

    // Did user pass enough parameters?
    if (argc < 2) {
        cerr << "usage: " << argv[0] << " <host> [data_size]" << endl;
        cerr << "\tdata_size can be up to " << MAX_PING_DATA_SIZE <<
                " bytes." << endl;
        return 1;
    }

    // Figure out how big to make the ping packet
    int packet_size = DEF_PACKET_SIZE;
    if (argc > 2) {
        int temp = atoi(argv[2]);
        if (temp != 0) {
            packet_size = temp;
        }
    }
    packet_size = max(sizeof(ICMPHeader), 
            min(MAX_PING_DATA_SIZE, (unsigned int)packet_size));

    // Start Winsock up
    WSAData wsaData;
    if (WSAStartup(MAKEWORD(2, 1), &wsaData) != 0) {
        cerr << "Failed to find Winsock 2.1 or better." << endl;
        return 1;
    }

    // Set up for pinging
    SOCKET sd;
    sockaddr_in dest, source;
    if (setup_for_ping(argv[1], sd, dest) < 0) {
        goto cleanup;
    }
    if (allocate_buffers(send_buf, recv_buf, packet_size) < 0) {
        goto cleanup;
    }
    init_ping_packet(send_buf, packet_size, seq_no);

    // Send the ping and receive the reply
    if (send_ping(sd, dest, send_buf, packet_size) >= 0) {
        while (1) {
            // Receive replies until we either get a successful read,
            // or a fatal error occurs.
            if (recv_ping(sd, source, recv_buf, MAX_PING_PACKET_SIZE) <
                    0) {
                // Pull the sequence number out of the ICMP header.  If 
                // it's bad, we just complain, but otherwise we take 
                // off, because the read failed for some reason.
                unsigned short header_len = recv_buf->h_len * 4;
                ICMPHeader* icmphdr = (ICMPHeader*)
                        ((char*)recv_buf + header_len);
                if (icmphdr->seq != seq_no) {
                    cerr << "bad sequence number!" << endl;
                    continue;
                }
                else {
                    break;
                }
            }
            if (decode_reply(recv_buf, packet_size, &source) != -2) {
                // Success or fatal error (as opposed to a minor error) 
                // so take off.
                break;
            }
        }
    }

cleanup:
    delete[]send_buf;
    delete[]recv_buf;
    WSACleanup();
    return 0;
}


/////////////////////////// allocate_buffers ///////////////////////////
// Allocates send and receive buffers.  Returns < 0 for failure.

int allocate_buffers(ICMPHeader*& send_buf, IPHeader*& recv_buf,
        int packet_size)
{
    // First the send buffer
    send_buf = (ICMPHeader*)new char[packet_size];  
    if (send_buf == 0) {
        cerr << "Failed to allocate output buffer." << endl;
        return -1;
    }

    // And then the receive buffer
    recv_buf = (IPHeader*)new char[MAX_PING_PACKET_SIZE];
    if (recv_buf == 0) {
        cerr << "Failed to allocate output buffer." << endl;
        return -1;
    }
    
    return 0;
}

rawping.cpp

/***********************************************************************
 rawping.cpp - Contains all of the functions essential to sending "ping"
    packets using Winsock 2 raw sockets.  Depends on ip_checksum.cpp for 
    calculating IP-style checksums on blocks of data, however.
***********************************************************************/

#include <winsock2.h>
#include <iostream.h>

#include "rawping.h"
#include "ip_checksum.h"


//////////////////////////// setup_for_ping ////////////////////////////
// Creates the Winsock structures necessary for sending and recieving
// ping packets.  host can be either a dotted-quad IP address, or a
// host name.  The other two parameters are outputs from the function.
// Returns < 0 for failure.

int setup_for_ping(char* host, SOCKET& sd, sockaddr_in& dest)
{
    // Create the socket
    sd = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, 0, 0, 0);
    if (sd == INVALID_SOCKET) {
        cerr << "Failed to create raw socket: " << WSAGetLastError() <<
                endl;
        return -1;
    }

    // Initialize the destination host info block
    memset(&dest, 0, sizeof(dest));

    // Turn first passed parameter into an IP address to ping
    unsigned int addr = inet_addr(host);
    if (addr != INADDR_NONE) {
        // It was a dotted quad number, so save result
        dest.sin_addr.s_addr = addr;
        dest.sin_family = AF_INET;
    }
    else {
        // Not in dotted quad form, so try and look it up
        hostent* hp = gethostbyname(host);
        if (hp != 0) {
            // Found an address for that host, so save it
            memcpy(&(dest.sin_addr), hp->h_addr, hp->h_length);
            dest.sin_family = hp->h_addrtype;
        }
        else {
            // Not a recognized hostname either!
            cerr << "Failed to resolve " << host << endl;
            return -1;
        }
    }

    return 0;
}



/////////////////////////// init_ping_packet ///////////////////////////
// Fill in the fields and data area of an ICMP packet

void init_ping_packet(ICMPHeader* icmp_hdr, int packet_size, int seq_no)
{
    // Set up the packet's fields
    icmp_hdr->type = ICMP_ECHO_REQUEST;
    icmp_hdr->code = 0;
    icmp_hdr->checksum = 0;
    icmp_hdr->id = (USHORT)GetCurrentProcessId();
    icmp_hdr->seq = seq_no;
    icmp_hdr->timestamp = GetTickCount();

    // "You're dead meat now, packet!"
    const unsigned long int deadmeat = 0xDEADBEEF;
    char* datapart = (char*)icmp_hdr + sizeof(ICMPHeader);
    int bytes_left = packet_size - sizeof(ICMPHeader);
    while (bytes_left > 0) {
        memcpy(datapart, &deadmeat, min(int(sizeof(deadmeat)), 
                bytes_left));
        bytes_left -= sizeof(deadmeat);
        datapart += sizeof(deadmeat);
    }

    // Calculate a checksum on the result
    icmp_hdr->checksum = ip_checksum((USHORT*)icmp_hdr, packet_size);
}


/////////////////////////////// send_ping //////////////////////////////
// Send an ICMP echo ("ping") packet to host with packet_size bytes of
// extra data attached and a sequence number of seq_no.  sd and dest
// are the outputs of setup_for_ping.  send_buf is a pointer to a
// buffer we can use to build our outgoing ping packet.  packet_size is 
// the total size of the ping packet to send; it is not checked for 
// sanity, so make sure that it's at least sizeof(ICMPHeader) bytes, 
// and that send_buf points to at least packet_size bytes.  Returns < 0 
// for failure.

int send_ping(SOCKET sd, const sockaddr_in& dest, ICMPHeader* send_buf,
        int packet_size)
{
    // Send the ping packet in send_buf as-is
    cout << "Sending " << packet_size << " bytes to " << 
            inet_ntoa(dest.sin_addr) << "..." << flush;
    int bwrote = sendto(sd, (char*)send_buf, packet_size, 0, 
            (sockaddr*)&dest, sizeof(dest));
    if (bwrote == SOCKET_ERROR) {
        cerr << "send failed: " << WSAGetLastError() << endl;
        return -1;
    }
    else if (bwrote < packet_size) {
        cout << "sent " << bwrote << " bytes..." << flush;
    }

    return 0;
}


/////////////////////////////// recv_ping //////////////////////////////
// Receive a ping reply on sd into recv_buf, and stores address info
// for sender in source.  On failure, returns < 0, 0 otherwise.  
// 
// Note that recv_buf must be larger than send_buf (passed to send_ping)
// because the incoming packet has the IP header attached.  It can also 
// have IP options set, so it is not sufficient to make it 
// sizeof(send_buf) + sizeof(IPHeader).  We suggest just making it
// fairly large and not worrying about wasting space.

int recv_ping(SOCKET sd, sockaddr_in& source, IPHeader* recv_buf, 
        int packet_size)
{
    // Wait for the ping reply
    int fromlen = sizeof(source);
    int bread = recvfrom(sd, (char*)recv_buf, 
            packet_size + sizeof(IPHeader), 0,
            (sockaddr*)&source, &fromlen);
    if (bread == SOCKET_ERROR) {
        cerr << "read failed: ";
        if (WSAGetLastError() == WSAEMSGSIZE) {
            cerr << "buffer too small" << endl;
        }
        else {
            cerr << "error #" << WSAGetLastError() << endl;
        }
        return -1;
    }

    return 0;
}


///////////////////////////// decode_reply /////////////////////////////
// Decode and output details about an ICMP reply packet.  Returns -1
// on failure, -2 on "try again" and 0 on success.

int decode_reply(IPHeader* reply, int bytes, sockaddr_in* from) 
{
    // Skip ahead to the ICMP header within the IP packet
    unsigned short header_len = reply->h_len * 4;
    ICMPHeader* icmphdr = (ICMPHeader*)((char*)reply + header_len);

    // Make sure the reply is sane
    if (bytes < header_len + ICMP_MIN) {
        cerr << "too few bytes from " << inet_ntoa(from->sin_addr) << 
                endl;
        return -1;
    }
    else if (icmphdr->type != ICMP_ECHO_REPLY) {
        if (icmphdr->type == ICMP_DEST_UNREACH) {
            cerr << "Destination unreachable" << endl;
        }
        else if (icmphdr->type == ICMP_TTL_EXPIRE) {
            cerr << "TTL expired in transit" << endl;
        }
        else {
            cerr << "Unknown ICMP packet type " << int(icmphdr->type) <<
                    " received" << endl;
        }
        return -1;
    }
    else if (icmphdr->id != (USHORT)GetCurrentProcessId()) {
        // Must be a reply for another pinger running locally, so just
        // ignore it.
        return -2;
    }

    // Okay, we ran the gamut, so the packet must be legal -- dump it
    cout << endl << bytes << " bytes from " << 
            inet_ntoa(from->sin_addr) << ", icmp_seq " << 
            icmphdr->seq << " time: " << 
            (GetTickCount() - icmphdr->timestamp) << " ms." << endl;

    return 0;
}

rawping.h

/***********************************************************************
 rawping.h - Declares the types, constants and prototypes required to
    use the rawping.cpp module.
***********************************************************************/

#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>

// ICMP packet types
#define ICMP_ECHO_REPLY 0
#define ICMP_DEST_UNREACH 3
#define ICMP_TTL_EXPIRE 11
#define ICMP_ECHO_REQUEST 8

// Minimum ICMP packet size, in bytes
#define ICMP_MIN 8

#ifdef _MSC_VER
// The following two structures need to be packed tightly, but unlike
// Borland C++, Microsoft C++ does not do this by default.
#pragma pack(1)
#endif

// The IP header
struct IPHeader {
    BYTE h_len:4;           // Length of the header in dwords
    BYTE version:4;         // Version of IP
    BYTE tos;               // Type of service
    USHORT total_len;       // Length of the packet in dwords
    USHORT ident;           // unique identifier
    USHORT flags;           // Flags
    BYTE ttl;               // Time to live
    BYTE proto;             // Protocol number (TCP, UDP etc)
    USHORT checksum;        // IP checksum
    ULONG source_ip;
    ULONG dest_ip;
};

// ICMP header
struct ICMPHeader {
    BYTE type;          // ICMP packet type
    BYTE code;          // Type sub code
    USHORT checksum;
    USHORT id;
    USHORT seq;
    ULONG timestamp;    // not part of ICMP, but we need it
};

#ifdef _MSC_VER
#pragma pack()
#endif

extern int setup_for_ping(char* host, SOCKET& sd, sockaddr_in& dest);
extern int send_ping(SOCKET sd, const sockaddr_in& dest, 
        ICMPHeader* send_buf, int packet_size);
extern int recv_ping(SOCKET sd, sockaddr_in& source, IPHeader* recv_buf,
        int packet_size);
extern int decode_reply(IPHeader* reply, int bytes, sockaddr_in* from);
extern void init_ping_packet(ICMPHeader* icmp_hdr, int packet_size, 
        int seq_no);

ip_checksum.cpp

/***********************************************************************
 ip_checksum.cpp - Calculates IP-style checksums on a block of data.
***********************************************************************/

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

USHORT ip_checksum(USHORT* buffer, int size) 
{
    unsigned long cksum = 0;
    
    // Sum all the words together, adding the final byte if size is odd
    while (size > 1) {
        cksum += *buffer++;
        size -= sizeof(USHORT);
    }
    if (size) {
        cksum += *(UCHAR*)buffer;
    }

    // Do a little shuffling
    cksum = (cksum >> 16) + (cksum & 0xffff);
    cksum += (cksum >> 16);
    
    // Return the bitwise complement of the resulting mishmash
    return (USHORT)(~cksum);
}

ip_checksum.h

extern USHORT ip_checksum(USHORT* buffer, int size);

Back to the Advanced Issues page...
Back to the Examples page...


Go to my home page Go to my Important RFC Lists page Go to the main Programming Resources page

Please send updates and corrections to <tangent@cyberport.com>.