我用原始套接字写了一个 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
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 。
要特别注意的还有就是计算校验和的算法:
将所有数据以字为单位,累加到一个双字中,如果剩余一个字节,就扩充到字后,在相加,然后对其求反,就得到了校验和。
当然算法的实现还有其他的形式。