首页 > 代码库 > ping的实现(原始套接字系列三)

ping的实现(原始套接字系列三)

使用Raw Socket实现Ping

  仅仅采用ICMP.DLL并不能完全实现ICMP灵活多变的各类报文,只有使用Raw Socket才是ICMP的终极解决之道。

  使用Raw Socket发送ICMP报文前,我们要完全依靠自己的代码组装报文:

//功能:初始化ICMP的报头, 给data部分填充数据, 计算校验和
void init_ping_packet(ICMPHeader *icmp_hdr, int packet_size, int seq_no)
{
 //设置ICMP报头字段
 icmp_hdr->type = ICMP_ECHO_REQUEST;
 icmp_hdr->code = 0;
 icmp_hdr->checksum = 0;
 icmp_hdr->id = (unsigned short)GetCurrentProcessId();
 icmp_hdr->seq = seq_no;
 icmp_hdr->timestamp = GetTickCount();

 // 填充data域
 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);
 }

 // 计算校验和
 icmp_hdr->checksum = ip_checksum((unsigned short*)icmp_hdr, packet_size);
}


  计算校验和(Checksum)的函数为:

//功能:计算ICMP包的校验和
unsigned short ip_checksum(unsigned short *buffer, int size)
{
 unsigned long cksum = 0;

 // 将所有的16数相加
 while (size > 1)
 {
  cksum += *buffer++;
  size -= sizeof(unsigned short);
 }
 if (size) //加上最后一个BYTE
 {
  cksum += *(unsigned char*)buffer;
 }

 //和的前16位和后16位相加
 cksum = (cksum >> 16) + (cksum &0xffff);
 cksum += (cksum >> 16);

 return (unsigned short)(~cksum);
}


  在真正发送Ping报文前,需要先初始化Raw Socket:

// 功能:初始化RAW Socket, 设置ttl, 初始化目标地址
// 返回值:<0 失败
int setup_for_ping(char *host, int ttl, SOCKET &sd, sockaddr_in &dest)
{
 // 创建原始套接字
 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;
 }

 if (setsockopt(sd, IPPROTO_IP, IP_TTL, (const char*) &ttl, sizeof(ttl)) ==SOCKET_ERROR)
 {
  cerr << "TTL setsockopt failed: " << WSAGetLastError() << endl;
  return - 1;
 }

 // 初始化目标主机信息块
 memset(&dest, 0, sizeof(dest));

 // 将第1个参数转换为目标IP地址
 unsigned int addr = inet_addr(host);
 if (addr != INADDR_NONE)
 {
  // 为IP地址
  dest.sin_addr.s_addr = addr;
  dest.sin_family = AF_INET;
 }
 else
 {
  // 非IP地址,进行主机名和IP地址的转换
  hostent *hp = gethostbyname(host);
  if (hp != 0)
  {
   // 查找主机名对应的IP地址
   memcpy(&(dest.sin_addr), hp->h_addr, hp->h_length);
   dest.sin_family = hp->h_addrtype;
  }
  else
  {
   // 不能识别的主机名
   cerr << "Failed to resolve " << host << endl;
   return - 1;
  }
 }
 return 0;
}


  下面可以利用Raw Socket发送生成的ICMP报文:

//功能:发送生成的ICMP包
//返回值:<0 发送失败
int send_ping(SOCKET sd, const sockaddr_in &dest, ICMPHeader *send_buf, int packet_size)
{
 // 发送send_buf缓冲区中的报文
 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;
}


  发送Ping报文后,我们需要接收Ping回复ICMP报文:

//功能:接收Ping回复
//返回值: <0 接收失败
int recv_ping(SOCKET sd, sockaddr_in &source, IPHeader *recv_buf, int packet_size)
{
 // 等待Ping回复
 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;
}


  并使用如下函数对接收到的报文进行解析:

// 功能:解析接收到的ICMP报文
// 返回值: -2忽略, -1失败, 0 成功
int decode_reply(IPHeader *reply, int bytes, sockaddr_in *from)
{
 // 偏移到ICMP报头
 unsigned short header_len = reply->h_len *4;
 ICMPHeader *icmphdr = (ICMPHeader*)((char*)reply + header_len);

 // 报文太短
 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_TTL_EXPIRE)
  {
   //ttl减为零
   if (icmphdr->type == ICMP_DEST_UNREACH)
   {
    //主机不可达
    cerr << "Destination unreachable" << endl;
   }
   else
   {
    //非法的ICMP包类型
    cerr << "Unknown ICMP packet type " << int(icmphdr->type) <<" received" << endl;
   }
   return - 1;
  }
 }
 else if (icmphdr->id != (unsigned short)GetCurrentProcessId())
 {
  //不是本进程发的包, 可能是同机的其它ping进程发的
  return - 2;
 }

 // 指出往返时间TTL
 int nHops = int(256-reply->ttl);
 if (nHops == 192)
 {
  // TTL came back 64, so ping was probably to a host on the
  // LAN -- call it a single hop.
  nHops = 1;
 }
 else if (nHops == 128)
 {
  // Probably localhost
  nHops = 0;
 }

 // 输出信息
 cout << endl << bytes << " bytes from " << inet_ntoa(from->sin_addr) <<", icmp_seq " << icmphdr->seq << ", ";
 if (icmphdr->type == ICMP_TTL_EXPIRE)
 {
  cout << "TTL expired." << endl;
 }
 else
 {
  cout << nHops << " hop" << (nHops == 1 ? "" : "s");
  cout << ", time: " << (GetTickCount() - icmphdr->timestamp) << " ms." <<endl;
 }
 return 0;
}