使用 ICMP 和 RAW Sockets实现 ping 类

ICMP Ping 实现
本文介绍了一个使用 C 语言实现的 ICMP Ping 程序,该程序通过 Raw Sockets 发送和接收 ICMP 回显请求及响应包。文章详细展示了 CPing 类的设计,包括初始化 WinSock、发送回显请求、接收回显回复等功能,并提供了一个测试用例。
//
// Ping.h
//
#ifndef __PING_H
#define __PING_H

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <winsock.h>
#pragma comment(lib, "ws2_32.lib")

class CPing
{
  #define ICMP_ECHOREPLY	0
  #define ICMP_ECHOREQ	8

#pragma pack(push, 1)
  // IP Header -- RFC 791
  typedef struct tagIPHDR
  {
    u_char  VIHL;			// Version and IHL
    u_char	TOS;			// Type Of Service
    short	TotLen;			// Total Length
    short	ID;				// Identification
    short	FlagOff;		// Flags and Fragment Offset
    u_char	TTL;			// Time To Live
    u_char	Protocol;		// Protocol
    u_short	Checksum;		// Checksum
    struct	in_addr iaSrc;	// Internet Address - Source
    struct	in_addr iaDst;	// Internet Address - Destination
  }IPHDR, *PIPHDR;

  // ICMP Header - RFC 792
  typedef struct tagICMPHDR
  {
    u_char	Type;			// Type
    u_char	Code;			// Code
    u_short	Checksum;		// Checksum
    u_short	ID;				// Identification
    u_short	Seq;			// Sequence
    char	Data;			// Data
  }ICMPHDR, *PICMPHDR;

  // ICMP Echo Request
  #define REQ_DATASIZE 32		// Echo Request Data size
  typedef struct tagECHOREQUEST
  {
    ICMPHDR icmpHdr;
    DWORD	dwTime;
    char	cData[REQ_DATASIZE];
  }ECHOREQUEST, *PECHOREQUEST;

  // ICMP Echo Reply
  typedef struct tagECHOREPLY
  {
    IPHDR	ipHdr;
    ECHOREQUEST	echoRequest;
    char    cFiller[256];
  }ECHOREPLY, *PECHOREPLY;
#pragma pack(pop)

private:
  bool bInitWinSockOK;
  u_short in_cksum(u_short *addr, int len);
  int SendEchoRequest(SOCKET s,LPSOCKADDR_IN lpstToAddr);
  DWORD RecvEchoReply(SOCKET s, LPSOCKADDR_IN lpsaFrom, u_char *pTTL);
  int WaitForEchoReply(SOCKET s);
  void __cdecl Report(LPCSTR format, ...);

public:
  CPing();
  virtual ~CPing();

  void ping(LPCSTR pstrHost);

};

#endif //__PING_H
// PING.CPP -- Ping program using ICMP and RAW Sockets

#include "ping.h"

CPing::CPing()
{
  bInitWinSockOK = false;

  // Init WinSock
  WSADATA wsaData={0};
  WORD wVersionRequested = MAKEWORD(1,1);
  if ( WSAStartup(wVersionRequested, &wsaData) )
  {
    Report("\nError initializing WinSock\n");
  }  
  else if (wsaData.wVersion != wVersionRequested)// Check version
  {
    Report("\nWinSock version not supported\n");
  }
  else
  {
    bInitWinSockOK = true;
  }
}

CPing::~CPing()
{
  if(bInitWinSockOK)
    WSACleanup();// Free WinSock
}

// ping()
// Calls SendEchoRequest() and
// RecvEchoReply() and retport results
void CPing::ping(LPCSTR pstrHost)
{
  SOCKET	  rawSocket;
  LPHOSTENT lpHost;
  struct    sockaddr_in saDest;
  struct    sockaddr_in saSrc;
  DWORD	  dwTimeSent;
  DWORD	  dwElapsed;
  u_char    cTTL;
  int       nLoop;
  int       nRet;

  if(!bInitWinSockOK)
  {
    Report("\nWinSock must be initializing\n");
    return;
  }

  // Create a Raw socket
  rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
  if (rawSocket == SOCKET_ERROR) 
  {
    Report("socket() error: %d\n", WSAGetLastError());
    return;
  }

  // Lookup host
  lpHost = gethostbyname(pstrHost);
  if (lpHost == NULL)
  {
    Report("\nHost not found: %s\n", pstrHost);
    return;
  }

  // Setup destination socket address
  saDest.sin_addr.s_addr = *((u_long FAR *) (lpHost->h_addr));
  saDest.sin_family = AF_INET;
  saDest.sin_port = 0;

  // Tell the user what we're doing
  Report("\nPinging %s [%s] with %d bytes of data:\n",
    pstrHost,
    inet_ntoa(saDest.sin_addr),
    REQ_DATASIZE);

  // Ping multiple times
  for (nLoop = 0; nLoop < 4; nLoop++)
  {
    // Send ICMP echo request
    SendEchoRequest(rawSocket, &saDest);

    // Use select() to wait for data to be received
    nRet = WaitForEchoReply(rawSocket);
    if (nRet == SOCKET_ERROR)
    {
      Report("select() error: %d\n", WSAGetLastError());
      break;
    }
    if (!nRet)
    {
      Report("\nTimeOut");
      break;
    }

    // Receive reply
    dwTimeSent = RecvEchoReply(rawSocket, &saSrc, &cTTL);

    // Calculate elapsed time
    dwElapsed = GetTickCount() - dwTimeSent;
    Report("\nReply from: %s: bytes=%d time=%ldms TTL=%d", 
      inet_ntoa(saSrc.sin_addr), 
      REQ_DATASIZE,
      dwElapsed,
      cTTL);
  }
  Report("\n");
  nRet = closesocket(rawSocket);
  if (nRet == SOCKET_ERROR)
    Report("closesocket() error: %d\n", WSAGetLastError());
}

void __cdecl CPing::Report(LPCSTR format, ...)
{
  char _Buff[8192];
  memset(_Buff, 0, sizeof(_Buff));

  va_list arg;  
  va_start(arg, format);
  int charSize = _vsnprintf(_Buff, sizeof(_Buff), format, arg);
  va_end(arg);

  OutputDebugStringA(_Buff);
}

//
//  Checksum routine for Internet Protocol family headers (C Version)
u_short CPing::in_cksum(u_short *addr, int len)
{
  register int nleft = len;
  register u_short *w = addr;
  register u_short answer;
  register int sum = 0;

  /*
  *  Our algorithm is simple, using a 32 bit accumulator (sum),
  *  we add sequential 16 bit words to it, and at the end, fold
  *  back all the carry bits from the top 16 bits into the lower
  *  16 bits.
  */
  while( nleft > 1 )  
  {
    sum += *w++;
    nleft -= 2;
  }

  /* mop up an odd byte, if necessary */
  if( nleft == 1 ) 
  {
    u_short	u = 0;
    *(u_char *)(&u) = *(u_char *)w ;
    sum += u;
  }

  /*
  * add back carry outs from top 16 bits to low 16 bits
  */
  sum = (sum >> 16) + (sum & 0xffff);	/* add hi 16 to low 16 */
  sum += (sum >> 16);			/* add carry */
  answer = ~sum;				/* truncate to 16 bits */
  return (answer);
}

// SendEchoRequest()
// Fill in echo request header
// and send to destination
int CPing::SendEchoRequest(SOCKET s,LPSOCKADDR_IN lpstToAddr) 
{
  static ECHOREQUEST echoReq;
  static u_short nId = 1;
  static u_short nSeq = 1;
  int nRet;

  // Fill in echo request
  echoReq.icmpHdr.Type		= ICMP_ECHOREQ;
  echoReq.icmpHdr.Code		= 0;
  echoReq.icmpHdr.Checksum	= 0;
  echoReq.icmpHdr.ID			= nId++;
  echoReq.icmpHdr.Seq			= nSeq++;

  // Fill in some data to send
  for (nRet = 0; nRet < REQ_DATASIZE; nRet++)
    echoReq.cData[nRet] = ' '+nRet;

  // Save tick count when sent
  echoReq.dwTime				= GetTickCount();

  // Put data in packet and compute checksum
  echoReq.icmpHdr.Checksum = in_cksum((u_short *)&echoReq, sizeof(ECHOREQUEST));

  // Send the echo request  								  
  nRet = sendto(s,						/* socket */
    (LPSTR)&echoReq,			/* buffer */
    sizeof(ECHOREQUEST),
    0,							/* flags */
    (LPSOCKADDR)lpstToAddr, /* destination */
    sizeof(SOCKADDR_IN));   /* address length */

  if (nRet == SOCKET_ERROR) 
    Report("sendto() error: %d\n", WSAGetLastError());
  return (nRet);
}

// RecvEchoReply()
// Receive incoming data
// and parse out fields
DWORD CPing::RecvEchoReply(SOCKET s, LPSOCKADDR_IN lpsaFrom, u_char *pTTL) 
{
  ECHOREPLY echoReply;
  int nRet;
  int nAddrLen = sizeof(struct sockaddr_in);

  // Receive the echo reply	
  nRet = recvfrom(s,					// socket
    (LPSTR)&echoReply,	// buffer
    sizeof(ECHOREPLY),	// size of buffer
    0,					// flags
    (LPSOCKADDR)lpsaFrom,	// From address
    &nAddrLen);			// pointer to address len

  // Check return value
  if (nRet == SOCKET_ERROR) 
    Report("recvfrom() error: %d\n", WSAGetLastError());

  // return time sent and IP TTL
  *pTTL = echoReply.ipHdr.TTL;
  return(echoReply.echoRequest.dwTime);   		
}

// WaitForEchoReply()
// Use select() to determine when
// data is waiting to be read
int CPing::WaitForEchoReply(SOCKET s)
{
  struct timeval Timeout;
  fd_set readfds;

  readfds.fd_count = 1;
  readfds.fd_array[0] = s;
  Timeout.tv_sec = 5;
  Timeout.tv_usec = 0;

  return(select(1, &readfds, NULL, NULL, &Timeout));
}


 

//test code 
CPing ping; 
ping.ping( "www.sina.com.cn" ); 

//output 
Pinging www.sina.com.cn [61.172.201.194] with 32 bytes of data: 
Reply from: 61.172.201.194: bytes=32 time=15ms TTL=249 
Reply from: 61.172.201.194: bytes=32 time=16ms TTL=249 
Reply from: 61.172.201.194: bytes=32 time=0ms TTL=249 
Reply from: 61.172.201.194: bytes=32 time=15ms TTL=249

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值