我用原始套接字写了一个 Ping 的程序实例,本文将介绍具体的技术,以及我在写的过程中遇到的麻烦。

首先Ping 涉及 icmp  协议,通过向指定地址发送icmp 封包请求回显,(i_type = 8 ,i_code = 0 表示请求回显)
更多代码具体参见icmp 协议


下面是源代码:
  1#include <cstdlib>
  2#include <iostream>
  3#include <windows.h>
  4
  5
  6using namespace std;
  7typedef struct _iphdr //定义IP首部 
  8
  9    unsigned char h_verlen; //4位首部长度,4位IP版本号 
 10    unsigned char tos; //8位服务类型TOS 
 11    unsigned short total_len; //16位总长度(字节) 
 12    unsigned short ident; //16位标识 
 13    unsigned short frag_and_flags; //3位标志位 
 14    unsigned char ttl; //8位生存时间 TTL 
 15    unsigned char proto; //8位协议 (TCP, UDP 或其他) 
 16    unsigned short checksum; //16位IP首部校验和 
 17    unsigned int sourceIP; //32位源IP地址 
 18    unsigned int destIP; //32位目的IP地址 
 19}IP_HEADER;
 20
 21typedef struct _ihdr 
 22
 23    BYTE i_type; //8位类型 
 24    BYTE i_code; //8位代码 
 25    USHORT i_checksum; //16位校验和 
 26    USHORT i_id; //识别号(一般用进程号作为识别号) 
 27    USHORT i_sequence; //报文序列号 
 28    ULONG  i_timestamp; //时间戳 
 29}ICMP_HEADER,*PICMP_HEADER; 
 30
 31
 32USHORT checksum(USHORT *buff,int size)
 33{
 34    unsigned long cksum=0;
 35    while(size>1){
 36          cksum+=*buff++;
 37          size-=sizeof(USHORT);
 38          }
 39    if(size){
 40          cksum+=*(UCHAR*)(buff);
 41          }
 42    cksum=(cksum>>16)+(cksum&0xffff);
 43    cksum+=(cksum>>16);
 44    return (USHORT)(~cksum);
 45
 46}
 47    
 48    
 49          
 50int main(int argc, char *argv[])
 51{
 52    char a;
 53    WSADATA wsa;
 54    sockaddr_in dest;
 55    int     nRet;
 56    int     nTimeout=1000;
 57    //char        buff[sizeof(ICMP_HEADER)+32];
 58    char    buff[sizeof(ICMP_HEADER)];
 59    int     ttl=32;
 60    if(argc<2){
 61          std::cout<<"Usage : ping ip/host "<<endl;
 62          cin>>a;
 63          exit(0);
 64          }
 65    if(WSAStartup(MAKEWORD(2,1),&wsa)!=0){
 66          std::cout<<"Error when Initialize the socket"<<endl;
 67          system("pause");
 68          exit(0);
 69          } 
 70    SOCKET sRaw=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);
 71    if(sRaw==INVALID_SOCKET){
 72          std::cout<<"Error when create the rawsocket"<<endl;
 73          cin>>a;
 74          exit(0);
 75          }
 76    /*
 77    nRet=setsockopt(sRaw, IPPROTO_IP, IP_TTL, (const char*)&ttl,sizeof(ttl));
 78    if(nRet==SOCKET_ERROR){
 79          std::cout<<"Error shen setsockopt ttl         "<<endl;
 80          std::cout<<"Error Code          :"<<WSAGetLastError()<<endl;
 81          cin>>a;
 82          exit(0);
 83          } 
 84    */   
 85    nRet=setsockopt(sRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&nTimeout,sizeof(nTimeout));
 86    
 87    if(nRet==SOCKET_ERROR){
 88          std::cout<<"Error when setsockopt sendtimeout "<<endl;
 89          std::cout<<"Error Code          : "<<WSAGetLastError()<<endl;
 90          cin>>a;
 91          WSACleanup();
 92          exit(0);
 93          }
 94    dest.sin_addr.s_addr=inet_addr(argv[1]);
 95    //dest.sin_port       =htons(0);
 96    dest.sin_family     =AF_INET;
 97    
 98    
 99    
100    PICMP_HEADER picmp=(PICMP_HEADER) buff;
101    picmp->i_type=8;
102    //picmp->i_type=ICMP_ECHO;
103    picmp->i_code=0;
104    picmp->i_id  =(USHORT)GetCurrentProcessId();
105    
106    picmp->i_sequence=0;
107    //memset(&buff[sizeof(ICMP_HEADER)],'E',32);
108    USHORT nSeq=0;
109    char   recvbuff[1024];
110    sockaddr_in from;
111    int    nLen=sizeof(from);
112    while(1){
113          static int nCount;
114          int        nRet;
115          
116          picmp->i_checksum=0;
117          picmp->i_sequence=nSeq++;
118          picmp->i_timestamp=GetTickCount();
119          picmp->i_checksum=checksum((USHORT*)buff,sizeof(buff));
120          
121          if(++nCount>4)
122                 break;
123          //nRet=sendto(sRaw,buff,sizeof(ICMP_HEADER)+32,0,(SOCKADDR*)&dest,sizeof(dest));
124          nRet=sendto(sRaw,buff,sizeof(buff),0,(SOCKADDR*)&dest,sizeof(dest)); 
125          if(nRet==SOCKET_ERROR){
126                 std::cout<<"Error when sendto "<<endl;
127                 cin>>a;
128                 exit(0);
129                 }
130          nRet=recvfrom(sRaw,recvbuff,1024,0,(sockaddr*)&from,&nLen);
131          if(nRet==SOCKET_ERROR){
132                 if(WSAGetLastError()!=WSAETIMEDOUT){
133                 
134                        std::cout<<"Error when recvfrom ,Error Code: "<<WSAGetLastError()<<endl;
135                        cin>>a;
136                        exit(0);
137                 }else
138                 {
139                        std::cout<<"Recv timeout"<<endl;
140                        continue;
141                        }
142          }
143          int nTick=GetTickCount();
144          if(nRet<sizeof(IP_HEADER)+sizeof(ICMP_HEADER)){
145                 std::cout<<"recv too few bytes from : "<<inet_ntoa(from.sin_addr);
146                 }
147          ICMP_HEADER* pic=(ICMP_HEADER*)(recvbuff+sizeof(IP_HEADER));
148          if(pic->i_type!=0){
149                 std::cout<<"nonecho type ,recved type is :"<<pic->i_type<<endl;
150                 cin>>a;
151                 exit(0);
152                 }
153          if(pic->i_id!=GetCurrentProcessId()){
154                 std::cout<<"someone else's data "<<endl;
155                 cin>>a;
156                 exit(0);
157                 }
158          printf(" %d bytes from %s:",nRet,inet_ntoa(from.sin_addr));
159          printf(" icmp_seq = %d.",pic->i_sequence);
160          printf("time : %d ms \n",nTick-pic->i_timestamp);
161          Sleep(1000);
162          }
163          
164          
165          
166    
167    WSACleanup();       
168    system("PAUSE");
169    return EXIT_SUCCESS;
170}
171


上面的ip头,icmp头都作了说明 ,相信能看懂,

我刚写的时候也遇到了一个非常困惑我的问题,就是在接收的时候,总是报错 recv timeout
检查了好多遍,发现是校验和的事,在校验和算法中忘了初始化 unsigned long cksum 的值 。
它的值应该初始化为0 ,否则它的值是随机的。

还有就是,校验和一定要最后求(也就是发送之前,对数据封包做的最后一个赋值操作),而且求之前一定要使icmp 的 i_checksum 域等于0 。

要特别注意的还有就是计算校验和的算法:
将所有数据以字为单位,累加到一个双字中,如果剩余一个字节,就扩充到字后,在相加,然后对其求反,就得到了校验和。

当然算法的实现还有其他的形式。