ICMP Structure
在linux gnu c library 中函数icmp的定义
1 struct icmp
2 {
3 u_int8_t icmp_type; /* type of message, see below */
4 u_int8_t icmp_code; /* type sub code */
5 u_int16_t icmp_cksum; /* ones complement checksum of struct */
6 union /**/
7 {
8 u_char ih_pptr; /* ICMP_PARAMPROB */
9 struct in_addr ih_gwaddr; /* gateway address */
10 struct ih_idseq /* echo datagram */
11 {
12 u_int16_t icd_id;
13 u_int16_t icd_seq;
14 } ih_idseq;
15 u_int32_t ih_void;
16 /* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */
17 struct ih_pmtu
18 {
19 u_int16_t ipm_void;
20 u_int16_t ipm_nextmtu;
21 } ih_pmtu;
22 struct ih_rtradv
23 {
24 u_int8_t irt_num_addrs;
25 u_int8_t irt_wpa;
26 u_int16_t irt_lifetime;
27 } ih_rtradv;
28 } icmp_hun;
29 #define icmp_pptr icmp_hun.ih_pptr
30 #define icmp_gwaddr icmp_hun.ih_gwaddr
31 #define icmp_id icmp_hun.ih_idseq.icd_id(标识一个ICMP报文,一般我们用PID标识)
32 #define icmp_seq icmp_hun.ih_idseq.icd_seq(发送报文的序号)
33 #define icmp_void icmp_hun.ih_void
34 #define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void
35 #define icmp_nextmtu icmp_hun.ih_pmtu.ipm_nextmtu
36 #define icmp_num_addrs icmp_hun.ih_rtradv.irt_num_addrs
37 #define icmp_wpa icmp_hun.ih_rtradv.irt_wpa
38 #define icmp_lifetime icmp_hun.ih_rtradv.irt_lifetime
39 union
40 {
41 struct
42 {
43 u_int32_t its_otime;
44 u_int32_t its_rtime;
45 u_int32_t its_ttime;
46 } id_ts;
47 struct
48 {
49 struct ip idi_ip;
50 /* options and then 64 bits of data */
51 } id_ip; /// ip header 20 字节
52 struct icmp_ra_addr id_radv;
53 u_int32_t id_mask; //4 个字节
54 u_int8_t id_data[1]; / / 2 个字节 = sizeof (struct timeval )
55 } icmp_dun;
56 #define icmp_otime icmp_dun.id_ts.its_otime
57 #define icmp_rtime icmp_dun.id_ts.its_rtime
58 #define icmp_ttime icmp_dun.id_ts.its_ttime
59 #define icmp_ip icmp_dun.id_ip.idi_ip
60 #define icmp_radv icmp_dun.id_radv
61 #define icmp_mask icmp_dun.id_mask
62 #define icmp_data icmp_dun.id_data(可以看到id_data是含有一个元素的数组名,为什么这样干呀?思考...)
63 };
在linux 内核的ICMP Header 的定义
1 struct icmphdr {
2 unsigned char type;
3 unsigned char code;
4 unsigned short checksum;
5 union {
6 struct {
7 unsigned short id;
8 unsigned short sequence;
9 } echo;
10 unsigned long gateway;
11 } un;
12 };
从中我们也可以看到icmp header 至少是8B ,其中icmp_id , icmp_seq 要根据具体情况会有所不同,具体情况可以参考《TCP / IP 协议详解 卷一》。
下面是ping的简单实现源码,不支持IPV6 。
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10 #include
11 #include
12 #include
13 #include
14 #define MAX_SIZE 1024
15 char send_buf[MAX_SIZE];
16 char recv_buf[MAX_SIZE];
17 int nsend = 0,nrecv = 0;
18 int datalen = 56;
19 //统计结果
20 void statistics(int signum)
21 {
22 printf("\n----------------PING statistics---------------\n");
23 printf("%d packets transmitted,%d recevid,%%%d lost\n",nsend,nrecv,(nsend - nrecv)/nsend * 100);
24 exit(EXIT_SUCCESS);
25 }
26 //校验和算法
27 int calc_chsum(unsigned short *addr,int len)
28 {
29 int sum = 0,n = len;
30 unsigned short answer = 0;
31 unsigned short *p = addr;
32 //每两个字节相加
33 while(n > 1)
34 {
35 sum += *p ++;
36 n -= 2;
37 }
38 //处理数据大小是奇数,在最后一个字节后面补0
39 if(n == 1)
40 {
41 *((unsigned char *)&answer) = *(unsigned char *)p;
42 sum += answer;
43 }
44 //将得到的sum值的高2字节和低2字节相加
45 sum = (sum >> 16) + (sum & 0xffff);
46 //处理溢出的情况
47 sum += sum >> 16;
48 answer = ~sum;
49 return answer;
50 }
51 /*打第几个包*/
52 int pack(int pack_num)
53 {
54 int packsize;
55 /*icmp sizeof = 28 */
56 struct icmp *icmp;
57 struct timeval *tv;
58 /*send_buf 为将要被发送的缓冲区,大小还没确定*/
59 icmp = (struct icmp *)send_buf;
60 /*请求报文*/
61 icmp->icmp_type = ICMP_ECHO;
62 icmp->icmp_code = 0;
63 icmp->icmp_cksum = 0;
64 icmp->icmp_id = htons(getpid());
65 icmp->icmp_seq = htons(pack_num);
66 /*绑定时间*/
67 tv = (struct timeval *)icmp->icmp_data;
68 //记录发送时间
69 if(gettimeofday(tv,NULL) < 0)
70 {
71 perror("Fail to gettimeofday");
72 return -1;
73 }
74 /*包的大小64 = icmp首部 + datalen = 8 + 56*/
75 /*神马意思,貌似为了好计算*/
76 packsize = 8 + datalen;
77 /*icmp 报文验证 只校验 64 个字节*/
78 icmp->icmp_cksum = calc_chsum((unsigned short *)icmp,packsize);
79 return packsize;
80 }
81 int send_packet(int sockfd,struct sockaddr *paddr)
82 {
83 int packsize;
84 //将send_buf填上a 1024 个a
85 memset(send_buf,'a',sizeof(send_buf));
86 nsend ++;
87 //打icmp包 ,最后,要效的send_buf = 64 ,其中的1024 - 56 = 968 是无效的
88 packsize = pack(nsend);
89 if(sendto(sockfd,send_buf,packsize,0,paddr,sizeof(struct sockaddr)) < 0)
90 {
91 perror("Fail to sendto");
92 return -1;
93 }
94 return 0;
95 }
96 struct timeval time_sub(struct timeval *tv_send,struct timeval *tv_recv)
97 {
98 struct timeval ts;
99 if(tv_recv->tv_usec - tv_send->tv_usec < 0)
100 {
101 tv_recv->tv_sec --;
102 tv_recv->tv_usec += 1000000;
103 }
104 ts.tv_sec = tv_recv->tv_sec - tv_send->tv_sec;
105 ts.tv_usec = tv_recv->tv_usec - tv_send->tv_usec;
106 return ts;
107 }
108 int unpack(int len,struct timeval *tv_recv,struct sockaddr *paddr,char *ipname)
109 {
110 /*sizeof struct ip = 20 */
111 struct ip *ip;
112 struct icmp *icmp;
113 struct timeval *tv_send,ts;
114 int ip_head_len;
115 float rtt;
116 ip = (struct ip *)recv_buf;
117 ip_head_len = ip->ip_hl << 2;
118 /*过滤掉ip头*/
119 icmp = (struct icmp *)(recv_buf + ip_head_len);
120 len -= ip_head_len;
121 if(len < 8)
122 {
123 printf("ICMP packets\'s is less than 8.\n");
124 return -1;
125 }
126 if(ntohs(icmp->icmp_id) == getpid() && icmp->icmp_type == ICMP_ECHOREPLY)
127 {
128 nrecv ++;
129 tv_send = (struct timeval *)icmp->icmp_data;
130 ts = time_sub(tv_send,tv_recv);
131 rtt = ts.tv_sec * 1000 + (float)ts.tv_usec/1000;//以毫秒为单位
132 printf("%d bytes from %s (%s):icmp_req = %d ttl=%d time=%.3fms.\n",
133 len,ipname,inet_ntoa(((struct sockaddr_in *)paddr)->sin_addr),ntohs(icmp->icmp_seq),ip->ip_ttl,rtt);
134 }
135 return 0;
136 }
137 /*接收包*/
138 int recv_packet(int sockfd,char *ipname)
139 {
140 int addr_len ,n;
141 struct timeval tv;
142 struct sockaddr from_addr;
143 /*原始套接字的长度 = 16 B*/
144 addr_len = sizeof(struct sockaddr);
145 /*接收包放到rev_buf缓冲区里面*/
146 if((n = recvfrom(sockfd,recv_buf,sizeof(recv_buf),0,&from_addr,&addr_len)) < 0)
147 {
148 perror("Fail to recvfrom");
149 return -1;
150 }
151 if(gettimeofday(&tv,NULL) < 0)
152 {
153 perror("Fail to gettimeofday");
154 return -1;
155 }
156 /*拆包*/
157 unpack(n,&tv,&from_addr,ipname);
158 return 0;
159 }
160 int main(int argc,char *argv[])
161 {
162 int size = 50 * 1024;
163 int sockfd,netaddr;
164 /*主机协议结构信息*/
165 struct protoent *protocol;
166 /*主机信息结构*/
167 struct hostent *host;
168 /*IPv4 协议 sizeof = 16 B */
169 struct sockaddr_in peer_addr;
170 if(argc < 2)
171 {
172 fprintf(stderr,"usage : %s ip.\n",argv[0]);
173 exit(EXIT_FAILURE);
174 }
175 //获取icmp的信息
176 if((protocol = getprotobyname("icmp")) == NULL)
177 {
178 perror("Fail to getprotobyname");
179 exit(EXIT_FAILURE);
180 }
181 //创建原始套接字
182 if((sockfd = socket(AF_INET,SOCK_RAW,protocol->p_proto)) < 0)
183 {
184 perror("Fail to socket");
185 exit(EXIT_FAILURE);
186 }
187 //回收root权限,设置当前用户权限
188 setuid(getuid());
189 /*
190 扩大套接子接收缓冲区到50k,这样做主要为了减少接收缓冲区溢出的可能性
191 若无影中ping一个广播地址或多播地址,将会引来大量应答
192 */
193 if(setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size)) < 0)
194 {
195 perror("Fail to setsockopt");
196 exit(EXIT_FAILURE);
197 }
198 //填充对方的地址
199 bzero(&peer_addr,sizeof(peer_addr));
200 peer_addr.sin_family = AF_INET;
201 //判断是主机名(域名)还是ip
202 if((netaddr = inet_addr(argv[1])) == INADDR_NONE)
203 {
204 //是主机名(域名)
205 /**/
206 if((host = gethostbyname(argv[1])) == NULL)
207 {
208 fprintf(stderr,"%s unknown host : %s.\n",argv[0],argv[1]);
209 exit(EXIT_FAILURE);
210 }
211 memcpy((char *)&peer_addr.sin_addr,host->h_addr,host->h_length);
212 }
213 else //ip地址
214 {
215 peer_addr.sin_addr.s_addr = netaddr;
216 }
217 //注册信号处理函数
218 signal(SIGALRM,statistics);
219 signal(SIGINT,statistics);
220 alarm(5);
221 //开始信息
222 printf("PING %s(%s) %d bytes of data.\n",argv[1],inet_ntoa(peer_addr.sin_addr),datalen);
223 //发送包文和接收报文
224 int i = 0;
225 while(1)
226 {
227 send_packet(sockfd,(struct sockaddr *)&peer_addr);
228 printf("I:%d\n",++i);
229 recv_packet(sockfd,argv[1]);
230 alarm(5);
231 usleep(1);
232 }
233 exit(EXIT_SUCCESS);
234 }
注:
发送时:
我们要注意的是recvfrom这个函数
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
The recvfrom() and recvmsg() calls are used to receive messages from a socket, and may be used to receive data on a socket whether or not it is connection-oriented.
If src_addr is not NULL, and the underlying protocol provides the source address, this source address is filled in. When src_addr is NULL, nothing is filled in; in this case,addrlen is not used, and should also be NULL. The argument addrlen is a value-result argument, which the caller should initialize before the call to the size of the buffer associated with src_addr, and modified on return to indicate the actual size of the source address. The returned address is truncated if the buffer provided is too small; in this case, addrlen will return a value greater than was supplied to the call.
就是所revcfrom会在接收数据是把发送放的源ip 也会加到数据里面一起放到接收缓冲区里面,可以查看《UNIX网络编程 第一卷:套接口API (第三版)》里面的page204 ,并且也会存到src_addr 里,
原始数据区:其中45 对应于IP Header 下面的version :4 , 和Header Length : 5 (20 byte),其实四十五就是’E’对应的16进制的ascii 值,也就是接收缓冲区recv_buf 的第一个字符,也是就是上面的解释是正确的
以上数据是用OmniPeek抓包软件抓的包。