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

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


下面是源代码:
  1 #include  < cstdlib >
  2 #include  < iostream >
  3 #include  < windows.h >
  4
  5
  6 using namespace std;
  7 typedef 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
 21 typedef 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
 32 USHORT 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           
 50 int  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 。

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

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