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

来源:互联网 发布:微信电影网站源码 编辑:程序博客网 时间:2024/06/05 07:12

当 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_echoicmp_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 报文的方法
原创粉丝点击