002.ICMP--拼接ICMP包,实现简单Ping程序(原始套接字)

一.大致流程:

    将ICMP头和时间数据设置好后,通过创建好的原始套接字socket发出去。目的主机计算效验和后会将数据原样返回,用当前时间和返回的数据结算时间差,计算出rtt。

 

二.数据结构:

ICMP时间戳请求与应答报文格式:

                                    

ICMP头部数据结构:

struct icmp   //占8字节,在<netinet/ip_icmp.h>头文件中定义
{
    u_int8_t icmp_type; //类型
    u_int8_t icmp_code; //代码
    u_int16_t icmp_cksum; //检验和
    u_int16_t icd_id;  //标识符
    u_int16_t icd_seq; //序列号
}

注:对于icmp时间戳请求与应答是这样的,我将不必要的数据定义省略了。

struct timeval时间结构体:

struct timeval   //该结构在<time.h>中定义
{
    __time_t tv_sec;          // 秒,实际上为long int类型
    __suseconds_t tv_usec;    // 微秒(百万分之一秒),实际上为long int类型
};

 

三.说明:

    1.我们以ping www.baidu.com为例,但我们并没有计算平均rtt

    2.我们并没有手动创建IP头,而是交给了内核去帮我们处理

    3.中间设计到的函数需要查相关资料

 

四.代码实现:

  1 /*
  2  ============================================================================
  3  Name        : myping.c
  4  Author      : huh
  5  Version     : 0.01
  6  Copyright   : Your copyright notice
  7  Description : Hello World in C, Ansi-style
  8  ============================================================================
  9  */
 10 
 11 #include <sys/types.h>
 12 #include <sys/select.h>
 13 #include <stdio.h>
 14 #include <time.h>
 15 #include <string.h>
 16 #include <netdb.h>
 17 #include <arpa/inet.h>
 18 #include <unistd.h>
 19 #include <signal.h>
 20 #include <sys/time.h>
 21 #include <sys/socket.h>
 22 #include <netinet/ip_icmp.h>
 23 
 24 #define PACKET_SIZE 1024*4
 25 
 26 int pid;
 27 int sockfd;
 28 int datalen = 56;
 29 int nsent = 1, nrecv = 1;
 30 char sendbuf[PACKET_SIZE];
 31 char recvbuf[PACKET_SIZE];
 32 struct sockaddr_in dest_addr; //socket目的地址
 33 struct sockaddr_in src_addr;
 34 struct timeval tvrecv;
 35 
 36 void send_packet();
 37 void un_packet(int);
 38 void tv_sub(struct timeval *out, struct timeval *in);
 39 unsigned short in_chksum(unsigned short *addr, int len);
 40 
 41 int main()
 42 {
 43     pid = getpid();
 44     char str[20];
 45     unsigned int inaddr;
 46     struct hostent *host;
 47     int size = 1024 * 25;
 48     strcpy(str,"www.baidu.com");
 49     //IP地址(域名)到无符号整数的转换
 50     inaddr = inet_addr(str);
 51     if (inaddr == INADDR_NONE)
 52     {
 53         host = gethostbyname(str);
 54         if (host == NULL)
 55         {
 56             printf("参数格式不正确,请重新输入!\n");
 57             return 0;
 58         }
 59         memcpy((char*) &inaddr, host->h_addr, sizeof(dest_addr.sin_addr));
 60     }
 61     //设置套接字地址
 62     dest_addr.sin_family = AF_INET;
 63     memcpy((char *)&dest_addr.sin_addr, (char *)&inaddr,sizeof(inaddr));
 64     //创建套接字
 65     sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
 66     //改变socket缓冲区大小
 67     setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
 68 
 69     printf("PING %s (%s) %d(84) bytes of data.\n",str, inet_ntoa(dest_addr.sin_addr), datalen);
 70     //不停的发送和接受ICMP数据包
 71     while (1)
 72     {
 73         send_packet();
 74         int src_addr_len = sizeof(struct sockaddr_in);
 75         //接收数据包,一直阻塞到有数据包到达为止
 76         int len = recvfrom(sockfd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *) &src_addr, (socklen_t *) &src_addr_len);
 77         if(len < 0)
 78             printf("recvfrom error!\n");
 79         un_packet(len);
 80         sleep(1); //间隔一秒,后面会用信号实现每秒发送一个ICMP包。
 81     }
 82     return 0;
 83 }
 84 
 85 //解包
 86 void un_packet(int len)
 87 {
 88     int hlen1;
 89     double rtt;
 90     struct ip *ip;
 91     struct icmp *icmp;
 92     struct timeval *tvsend;
 93 
 94     ip = (struct ip *) recvbuf;
 95     hlen1 = ip->ip_hl << 2;
 96     if (ip->ip_p != IPPROTO_ICMP)
 97         return ;
 98 
 99     icmp = (struct icmp *) (recvbuf + hlen1);
100     len -= hlen1;
101 
102     if ((icmp->icmp_type == ICMP_ECHOREPLY))
103     {
104         if (icmp->icmp_id != pid)
105             return;
106         tvsend = (struct timeval *) icmp->icmp_data;  //发送时间
107         gettimeofday(&tvrecv, NULL);  //得到当前时间
108 
109         tv_sub(&tvrecv, tvsend); //计算接收和发送的时间差
110         rtt = tvrecv.tv_sec * 1000.0 + tvrecv.tv_usec / 1000.0; //以毫秒单位计算rtt
111         printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%.3fms\n", len, inet_ntoa(src_addr.sin_addr), icmp->icmp_seq, ip->ip_ttl, rtt);
112         nrecv++;
113     }
114 }
115 
116 //手动构建数据包,并通过原始套接字发送
117 void send_packet()
118 {
119     int len;
120     struct icmp *icmp;
121     icmp = (struct icmp *) (sendbuf);
122     icmp->icmp_type = ICMP_ECHO;  //拼接icmp
123     icmp->icmp_code = 0;
124     icmp->icmp_id = pid;   //2字节
125     icmp->icmp_seq = nsent++; //2字节
126     memset(icmp->icmp_data, 0xa5, datalen);
127     gettimeofday((struct timeval *) icmp->icmp_data, NULL);    //将发送时间作为数据传递过去
128 
129     len = datalen + 8;
130     icmp->icmp_cksum = 0;  //校验和需要先置0
131     icmp->icmp_cksum = in_chksum((unsigned short *) icmp, len);  //计算效验和
132 
133     sendto(sockfd, sendbuf, len, 0, (struct sockaddr *) &dest_addr,
134             sizeof(dest_addr));  //将包发出去
135     //printf("package have sent!\n");
136 }
137 
138 //计算效验和
139 unsigned short in_chksum(unsigned short *addr, int len)
140 {
141     int nleft = len;
142     int sum = 0;
143     unsigned short *w = addr;
144     unsigned short answer = 0;
145     //把ICMP报头二进制数据以2字节为单位累加起来
146     while (nleft > 1)
147     {
148         sum += *w++;
149         nleft -= 2;
150     }
151     if (nleft == 1)
152     {
153         *(unsigned char *) (&answer) = *(unsigned char *) w;
154         sum += answer;
155     }
156     sum = (sum >> 16) + (sum & 0xffff);
157     sum += (sum >> 16);
158     answer = ~sum;
159     return answer;
160 }
161 
162 //计算时间差
163 void tv_sub(struct timeval *out, struct timeval *in)
164 {
165     if ((out->tv_usec -= in->tv_usec) < 0)
166     {
167         --out->tv_sec;
168         out->tv_usec += 1000000;
169     }
170     out->tv_sec -= in->tv_sec;
171 }
myping

 

转载于:https://www.cnblogs.com/ruo-yu/p/4978968.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值