zoukankan      html  css  js  c++  java
  • 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;
    }

  • 相关阅读:
    Kubernetes中的Service Mesh(第5部分):Dogfood环境和入口
    A Service Mesh for Kubernetes(第1部分): Service的重要指标
    A Kubernetes in Service Mesh(第9部分):使用gRPC的乐趣和收益
    Automated, Self-Service Provisioning of VMs Using HyperForm (Part 2) (使用HyperForm自动配置虚拟机(第2部分)
    Authentication in Loopback Applications Against Bluemix(在针对Bluemix的Lookback应用中进行身份认证)
    java工具类-excel jxl
    南阳199
    南阳198
    南阳168
    南阳274
  • 原文地址:https://www.cnblogs.com/happy-pm/p/3809457.html
Copyright © 2011-2022 走看看