94-ICMP 协议(回显请求与应答)

当 ICMP 首部 type = 8, code = 0,该 ICMP 是回显请求报文。当 type = 0, code = 0 时,是回显应答报文。

1. 回显请求与应答报文

1.1 首部格式


这里写图片描述
图1 ICMP 回显请求与应答报文首部

当 ICMP 报文是回显请求与应答报文时,我们可以看到首部的第 4、5 两个字节是标识符字段,第 6、7 两个字节是序号字段。

1.2 结构体

该结构体定义在 unp/program/include/icmp.h 中。

// icmp 回显报文头部 struct icmp_echo {   uint8_t icmp_type;   uint8_t icmp_code;   uint16_t icmp_cksum;   uint16_t icmp_id;   uint16_t icmp_seq;   char icmp_data[0]; };

2. 回显请求与应答过程

应用进程可以发送一个 type = 8, code = 0 的 ICMP 回显请求报文给目标主机,目标主机收到该 ICMP 报文后,将该报文的 type 字段值改为 0(回显应答报文),原封不动的发送回去。


这里写图片描述
图2 ICMP 回显请求与应答

如图 2,主机 A 上的进程发送一个回显请求报文(type = 8),同时附带数据 "hello",发送到主机 B. 主机 B 收到该报文后,主动发送回显应答给主机 A.

3. 程序设计

3.1 本文程序路径

本文使用的程序托管在 gitos 上:http://git.oschina.net/ivan_allen/unp

如果你已经 clone 过这个代码了,请使用 git pull 更新一下。本节程序所使用的程序路径是 unp/program/icmp/icmpecho.

3.2 设计思路

之前已经练习过接收 ICMP 报文了,但是我们还没有写过如何发送 ICMP 报文,其实发送也很简单。不过要注意,要不要我们自己构造 IP 数据报?

回答:默认情况下,我们不需要自己构造,直接构造好 IP 数据报的内容发出去就行了。如果你想自己构造,可以使用 setsockotp 函数指定选项 IP_HDRINCL,像下面这样:

int onoff = 1; setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &onoff, sizeof(onoff));

在这里,我们并不想自己构造 IP 数据报,因此也不用开启 IP_HDRINCL 选项。所以我们只需要构造一个 ICMP 回显请求报文就行了。

char sendbuf[4096]; struct icmp_echo *icmp_echo = (struct icmp_echo*)sendbuf; // 开始填充 icmp_echo icmp_echo->icmp_type = 8; // 类型为 8,表示回显请求 icmp_echo->icmp_code = 0; icmp_echo->icmp_cksum = 0; // 注意,这个字段一定要正确填写 icmp_echo->icmp_id = getpid() & 0xffff; // 这个字段随便你填,我就填一个进程的 id 号。 icmp_echo->icmp_seq = 0; // 这个字段也是随便你填什么的。 strcpy(icmp_echo->data, "hello");   // 计算整个 icmp 报文的长度(首部长度 + 数据部分长度) icmplen = sizeof(struct icmp_echo) + strlen("hello");  // 所有字段填写完成后,就开始计算校验和 icmp_echo->icmp_cksum = cksum((unsigned short*)sendbuf, icmplen);

如果校验和填不对,这个 IP 数据报会直接被你的主机一声不吭的丢弃。校验和的算法叫 one’s complement sum,中译名为二进制反码求和,如果你感兴趣可以自行搜索它的算法。这里直接给出代码:

unsigned short cksum(unsigned short *addr, int len){   unsigned int sum = 0;     while(len > 1){      sum += *addr++;     len -= 2;   }    // 处理剩下的一个字节   if(len == 1){      sum += *(unsigned char*)addr;   }    // 将32位的高16位与低16位相加   sum = (sum >> 16) + (sum & 0xffff);   sum += (sum >> 16);    return (unsigned short) ~sum; }

3.3 程序框架(伪代码)

void handler(int sig) {   // 每隔一秒发送一个回显请求报文给目标主机   sender();   alarm(1); }   int main() {   registSignal(SIGALRM, handler);   alarm(1);    recver(); }  void sender() {   // 构造 ICMP 回显请求报文发送给目标主机   // ... }  void recver() {   // 接收 IP 数据报,并解析出 ICMP 报文   // 过滤出 ICMP 回显应答报文(type = 0, code = 0)   // 打印 ICMP 报文中的数据部分 }

当然你也可以使用线程的方式发送 ICMP 报文……无所谓啦。

4. 实验

程序 icmp/icmpecho 每隔一秒发送一个 ICMP 请求报文给目标主机 baidu.com,同时接收 baidu.com 发送回来的回显应答报文,然后将接收到的回显数据打印出来。


这里写图片描述
图1 发送回显请求与接收回显应答

从上面的结果可以看到,我们的程序给主机 baidu.com 发送了一个回显请求,然后又接收到了 baidu.com 的回显应答。

5. 总结

很开心,我们离 ping 命令的实现非常接近了,你完全可以将本程序稍作修改,它就是一个 ping 命令。

  • 掌握 ICMP 回显报文的构造
  • 掌握发送 ICMP 报文的方法

说明:本文转自blog.csdn.net,用于学习交流分享,仅代表原文作者观点。如有疑问,请联系我们删除~