我用原始套接字写了一个 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 。
要特别注意的还有就是计算校验和的算法: 将所有数据以字为单位,累加到一个双字中,如果剩余一个字节,就扩充到字后,在相加,然后对其求反,就得到了校验和。
当然算法的实现还有其他的形式。
|
|
公告
C plus plus
Java( J2EE , IoC , Spring , Struts2 .. )
Ruby on Rails ...
常用链接
随笔分类
文章分类
Blog
搜索
最新评论
|
|