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          };
复制代码

wps_clip_image-20212

icmp_ ty pe标识特定报文, icmp_ code进一步指定该报文(图 11 -1的第1栏)。计算

icmp_cksum的算法与IP首部检验和相同,保护整个 ICMP 报文(像IP一样,不仅仅保护首部)。

联合icmp_hun(首部联合)和 icmp_dun(数据联合)按照 icmp _type和icmp_code访问多种 ICM P报文。每种 I C MP报文都使用 i cm p _h un;只有一部分报文用 ic mp _d un。没有使用的字段必须设置为 0。

我们已经看到,利用其他嵌套的结构(例如 m b u f、l e _s o f t c 和et h e r _ a rp),#define宏可以简化对结构成员的访问。图11-11显示了IC M P报文的整体结构,并再次强调 I C MP报文是封装在 IP数据报里的。我们将在分析程序时,分析所遇报文的特定结构。

因为我们的程序是查询请求应答,所以下面只做ICMP回显和回答

wps_clip_image-9490

在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 <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <errno.h>
  5 #include <sys/socket.h>
  6 #include <sys/types.h>
  7 #include <netinet/in.h>
  8 #include <arpa/inet.h>
  9 #include <netdb.h>
 10 #include <sys/time.h>
 11 #include <netinet/ip_icmp.h>
 12 #include <unistd.h>
 13 #include <signal.h>
 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 的第一个字符,也是就是上面的解释是正确的

wps_clip_image-28100

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值