ping

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          };


ping_第1张图片

在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 的第一个字符,也是就是上面的解释是正确的

ping_第2张图片

以上数据是用OmniPeek抓包软件抓的包。

你可能感兴趣的:(Linux,Program)