一.概述                                                   

上一篇arp请求使用的是链路层的原始套接字。icmp封装在ip数据报里面,所以icmp请求可以直接使用网络层的原始套接字,即socket()第一个参数是PF_INET。如下:

 sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);

icmp报文不同的类型有不同的格式,我们以icmp回显请求和会显应答报文格式(即ping程序使用的报文类型)为例:

类型为8表示请求,为0表示应答。校验和要自己计算,标识符一般为程序的进程ID号。序号自定义,一般从1开始。选项数据里面可以放时间戳,用作计算ping一次的花费时间!

icmp报文结构定义在netinet/ip_icmp.h

 struct icmp
 {
   u_int8_t  icmp_type;    /* type of message, see below */
   u_int8_t  icmp_code;    /* type sub code */
   u_int16_t icmp_cksum;    /* ones complement checksum of struct */
   union
   {
     u_char ih_pptr;        /* ICMP_PARAMPROB */
     struct in_addr ih_gwaddr;    /* gateway address */
     struct ih_idseq        /* echo datagram */
     {
       u_int16_t icd_id;
       u_int16_t icd_seq;
     } ih_idseq;
     u_int32_t ih_void;

     /* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */
     struct ih_pmtu
     {
       u_int16_t ipm_void;
       u_int16_t ipm_nextmtu;
     } ih_pmtu;

     struct ih_rtradv
     {
       u_int8_t irt_num_addrs;
       u_int8_t irt_wpa;
       u_int16_t irt_lifetime;
     } ih_rtradv;
   } icmp_hun;
 #define    icmp_pptr    icmp_hun.ih_pptr
 #define    icmp_gwaddr    icmp_hun.ih_gwaddr
 #define    icmp_id        icmp_hun.ih_idseq.icd_id
 #define    icmp_seq    icmp_hun.ih_idseq.icd_seq
 #define    icmp_void    icmp_hun.ih_void
 #define    icmp_pmvoid    icmp_hun.ih_pmtu.ipm_void
 #define    icmp_nextmtu    icmp_hun.ih_pmtu.ipm_nextmtu
 #define    icmp_num_addrs    icmp_hun.ih_rtradv.irt_num_addrs
 #define    icmp_wpa    icmp_hun.ih_rtradv.irt_wpa
 #define    icmp_lifetime    icmp_hun.ih_rtradv.irt_lifetime
   union
   {
     struct
     {
       u_int32_t its_otime;
       u_int32_t its_rtime;
       u_int32_t its_ttime;
     } id_ts;
     struct
     {
       struct ip idi_ip;
       /* options and then 64 bits of data */
     } id_ip;
     struct icmp_ra_addr id_radv;
     u_int32_t   id_mask;
     u_int8_t    id_data[];
   } icmp_dun;
 #define    icmp_otime    icmp_dun.id_ts.its_otime
 #define    icmp_rtime    icmp_dun.id_ts.its_rtime
 #define    icmp_ttime    icmp_dun.id_ts.its_ttime
 #define    icmp_ip        icmp_dun.id_ip.idi_ip
 #define    icmp_radv    icmp_dun.id_radv
 #define    icmp_mask    icmp_dun.id_mask
 #define    icmp_data    icmp_dun.id_data
 };

二.icmp请求代码                                   

 /**
  * @file icmp_request.c
  */

 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
 #include <netinet/in.h>
 #include <netinet/ip_icmp.h>
 #include <sys/time.h>

 /* icmp报文长度 */
 #define ICMP_PACKET_LEN sizeof(struct icmp)

 void err_exit(const char *err_msg)
 {
     perror(err_msg);
     exit();
 }

 /* 校验和 */
 unsigned short check_sum(unsigned short *addr, int len)
 {
     int nleft = len;
     ;
     unsigned short *w = addr;
     unsigned ;

     )
     {
         sum += *w++;
         nleft -= ;
     }
     )
     {
         *(unsigned char *)(&answer) = *(unsigned char *)w;
         sum += answer;
     }

     sum = (sum >> ) + (sum & 0xffff);
     sum += (sum >> );
     answer = ~sum;

     return answer;
 }

 /* 填充icmp报文 */
 struct icmp *fill_icmp_packet(int icmp_type, int icmp_sequ)
 {
     struct icmp *icmp_packet;

     icmp_packet = (struct icmp *)malloc(ICMP_PACKET_LEN);
     icmp_packet->icmp_type = icmp_type;
     icmp_packet->icmp_code = ;
     icmp_packet->icmp_cksum = ;
     icmp_packet->icmp_id = htons(getpid());
     icmp_packet->icmp_seq = htons(icmp_sequ);
     /* 发送时间 */
     gettimeofday((struct timeval *)icmp_packet->icmp_data, NULL);
     /* 校验和 */
     icmp_packet->icmp_cksum = check_sum((unsigned short *)icmp_packet, ICMP_PACKET_LEN);

     return icmp_packet;
 }

 /* 发送icmp请求 */
 void icmp_request(const char *dst_ip, int icmp_type, int icmp_sequ)
 {
     struct sockaddr_in dst_addr;
     struct icmp *icmp_packet;
     int sockfd, ret_len;
     char buf[ICMP_PACKET_LEN];

     /* 请求的地址 */
     bzero(&dst_addr, sizeof(struct sockaddr_in));
     dst_addr.sin_family = AF_INET;
     dst_addr.sin_addr.s_addr = inet_addr(dst_ip);

     )
         err_exit("sockfd()");

     /* icmp包 */
     icmp_packet = fill_icmp_packet(icmp_type, icmp_sequ);
     memcpy(buf, icmp_packet, ICMP_PACKET_LEN);

     /* 发送请求 */
     ret_len = sendto(sockfd, buf, ICMP_PACKET_LEN, , (struct sockaddr *)&dst_addr, sizeof(struct sockaddr_in));
     )
         printf("sendto() ok!!!\n");

     close(sockfd);
 }

 int main(int argc, const char *argv[])
 {
     )
     {
         printf(]);
         exit();
     }

     /* 发送icmp请求 */
     icmp_request(argv[], , );

     ;
 }

流程:命令行接收icmp请求的目标IP,106行发送请求,指定icmp类型是8,序列号是1。然后通过目标IP地址创建网络地址结构,接着创建ICMP类型的原始套接字,填充icmp报文,并把发送时间填到icmp的数据结构。

三.icmp接收代码                                   

 /**
  * @file icmp_recv.c
  */

 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include <sys/time.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
 #include <netinet/in.h>
 #include <netinet/ip.h>
 #include <netinet/ip_icmp.h>

 /* IP首部长度 */
 #define IP_HEADER_LEN sizeof(struct ip)
 /* icmp报文长度 */
 #define ICMP_PACKET_LEN sizeof(struct icmp)
 /* IP + ICMP长度 */
 #define IP_ICMP_PACKET_LEN IP_HEADER_LEN + ICMP_PACKET_LEN

 void err_exit(const char *err_msg)
 {
     perror(err_msg);
     exit();
 }

 /* 计算发送时间与接收时间的毫秒差 */
 float time_interval(struct timeval *recv_time, struct timeval *send_time)
 {
     ;

     /* 如果接收的时间微妙小于发送的微妙 */
     if (recv_time->tv_usec < send_time->tv_usec)
     {
         recv_time->tv_sec -= ;
         recv_time->tv_usec += ;
     }
     msec = (recv_time->tv_sec - send_time->tv_sec) * 1000.0 + (recv_time->tv_usec - send_time->tv_usec) / 1000.0;

     return msec;
 }

 int main(void)
 {
     struct ip *ip_header;
     struct icmp *icmp_packet;
     char buf[IP_ICMP_PACKET_LEN];
     struct timeval *recv_timeval, *send_timeval;
     int sockfd, ret_len;

     )
         err_exit("sockfd()");

     recv_timeval = malloc(sizeof(struct timeval));
     )
     {
         ret_len = recv(sockfd, buf, IP_ICMP_PACKET_LEN, );
         )
         {
             /* 接收时间 */
             gettimeofday(recv_timeval, NULL);
             /* 取出ip首部 */
             /* 取出icmp报文 */
             ip_header = (struct ip *)buf;
             icmp_packet = (struct icmp *)(buf + IP_HEADER_LEN);
             /* 取出发送时间 */
             send_timeval = (struct timeval *)icmp_packet->icmp_data;
             printf("===============================\n");
             printf("from ip:%s\n", inet_ntoa(ip_header->ip_src));
             printf("icmp_type:%d\n", icmp_packet->icmp_type);
             printf("icmp_code:%d\n", icmp_packet->icmp_code);
             printf("time interval:%.3fms\n", time_interval(recv_timeval, send_timeval));
         }
     }

     free(recv_timeval);
     close(sockfd);
     ;
 }

流程:创建ICMP类型的原始套接字后直接接收。首先获取接收时间,然后依次取出ip首部,icmp报文,再取出icmp的请求时间。从ip首部获取源ip地址,从icmp报文获取该报文的类型,代码号,通过发送时间和接收时间计算毫秒差!

四.实验                                                   

.打开wireshark一起观察。以root运行icmp_recv,再运行icmp_request

可以看到icmp的类型是0,代码也是0。响应时间跟我们的程序差不多。

.现在我们请求一个不可达的ip地址

主机不可达时,返回的icmp报文类型是3,代码是1。报文结构不同,取出的发送时间是不正常的,所以这里计算的时间间隔也不正常。wireshark里面的结果是,本机自动广播了一个arp请求,但没有机器回答本机。

部分icmp类型:

linux原始套接字(2)-icmp请求与接收的更多相关文章

  1. linux原始套接字(1)-arp请求与接收

    一.概述                                                   以太网的arp数据包结构: arp结构op操作参数:1为请求,2为应答. 常用的数据结构如 ...

  2. Linux原始套接字实现分析---转

    http://blog.chinaunix.net/uid-27074062-id-3388166.html 本文从IPV4协议栈原始套接字的分类入手,详细介绍了链路层和网络层原始套接字的特点及其内核 ...

  3. 关于linux 原始套接字编程

    关于linux 网络编程最权威的书是<<unix网络编程>>,但是看这本书时有些内容你可能理解的不是很深刻,或者说只知其然而不知其所以然,那么如果你想搞懂的话那么我建议你可以看 ...

  4. Linux原始套接字抓取底层报文

    1.原始套接字使用场景 我们平常所用到的网络编程都是在应用层收发数据,每个程序只能收到发给自己的数据,即每个程序只能收到来自该程序绑定的端口的数据.收到的数据往往只包括应用层数据,原有的头部信息在传递 ...

  5. linux原始套接字(4)-构造IP_UDP

    一.概述                                                    同上一篇tcp一样,udp也是封装在ip报文里面.创建UDP的原始套接字如下: (soc ...

  6. linux原始套接字(3)-构造IP_TCP发送与接收

    一.概述                                                    tcp报文封装在ip报文中,创建tcp的原始套接字如下: sockfd = socket ...

  7. Linux网络编程——原始套接字编程

    原始套接字编程和之前的 UDP 编程差不多,无非就是创建一个套接字后,通过这个套接字接收数据或者发送数据.区别在于,原始套接字可以自行组装数据包(伪装本地 IP,本地 MAC),可以接收本机网卡上所有 ...

  8. UNIX网络编程——原始套接字的魔力【续】

    如何从链路层直接发送数据帧 上一篇里面提到的是从链路层"收发"数据,该篇是从链路层发送数据帧. 上一节我们主要研究了如何从链路层直接接收数据帧,可以通过bind函数来将原始套接字绑 ...

  9. Linux基础(11)原始套接字

    一边接收函数返回一边判断返回值时一定要把接收的优先级加()提高再去判断 例 if((sockfd = socket()) < 0) 问题: 如何实现SYN扫描器扫描端口 , 比如AB两个设备要进 ...

随机推荐

  1. Centos安装Memcached和(Nginx)Memcache扩展详细教程

    下载memadmin,下载地址:http://www.junopen.com/memadmin/ 并在IIS新建站点. 测试地址:http://wap.yousawang.com/mem , 1.重启 ...

  2. HubSpot – 网站开发必备的 jQuery 信息提示库

    HubSpot 一款功能丰富的 jQuery 消息提示插件.它可以帮助你个性化显示您的应用程序的事务性消息.您可以轻松地包裹 Ajax 请求进度,成功和错误消息,还可以添加操作链接到您的消息中. Hu ...

  3. React Native – 使用 JavaScript 开发原生应用

    前不久,Facebook 在F8开发者大会上正式开源了 React Native 项目.不过目前只有 iOS 版,Android 版还需要再等一段时间,这是最新的用 JavaScript 语言开发原生 ...

  4. Codrops 教程:实现内容倾斜的 3D 幻灯片效果

    今天给大家分享的优秀教程来自 Codrops 网站,实现一个内容倾斜的 3D 幻灯片效果.我们平常见到的都是那种水平或者垂直滚动的效果,这个倾斜的内容滑动效果相信会让你眼前一亮.因为使用了 CSS 3 ...

  5. ASP.NET使用jQuery AJAX实现MD5加密实例

    一个asp.net ajax例子,使用jquery,实现md5加密.在.NET 4.0,Visual Studio 2010上成功运行. 效果体验:http://tool.keleyi.com/t/m ...

  6. 【初探移动前端开发03】jQuery Mobile(上)

    前言 到目前为止,我打了几天酱油了,这几天落实了工作,并且看了一部电视连续剧(陈道明-手机),我很少看连续剧了,但是手机质量很高啊,各位可以看看. 我们今天先学习一下jquery mobile的基础知 ...

  7. Web持久化存储Web SQL、Local Storage、Cookies(常用)

    在浏览器客户端记录一些信息,有三种常用的Web数据持久化存储的方式,分别是Web SQL.Local Storage.Cookies. Web SQL 作为html5本地数据库,可通过一套API来操纵 ...

  8. Java WebService 开发简单实例

    Web Service 是一种新的web应用程序分支,他们是自包含.自描述.模块化的应用,可以发布.定位.通过web调用.Web Service可以执行从简单的请求到复杂商务处理的任何功能.一旦部署以 ...

  9. 我理解的OAuth 1.0a 的验证过程

    故事梗概: 淘宝店主糖糖在京郊仓库存了一批大白兔奶糖,为了防止仓库钥匙被偷把仓库的钥匙交给了专业的钥匙保管员公司. 糖糖卖了一斤大白兔需要快递公司的小迪送货.快递员小迪找钥匙保管公司借钥匙,然后去京郊 ...

  10. onMeasure流程解析

    0.预备知识 我们的手机屏幕的布局其实是嵌套的,最外层是一个phoneWindow,这个view和手机屏幕一样大,里面是一个frameLayout,再里面才是我们自己写的布局文件. 我们在绘制控件前必 ...