上一篇:DIY TCP/IP IP模块和ICMP模块的实现1
本节在8.2节的基础上扩展icmp_recv函数,检验接收到的ICMP数据帧的校验和,解析ICMP数据帧头部的type字段,根据ICMP数据帧的类型做相应处理。

  int icmp_recv(unsigned char *pkt, unsigned int sz){int ret = 0;iphdr_t *ippkt = NULL;icmphdr_t *icmppkt = NULL;unsigned short icmppkt_len = 0;unsigned char icmp_type = 0;unsigned char *payload = NULL;if (pkt == NULL || sz == 0) {ret = -1;goto out;}ippkt = (iphdr_t *)pkt;icmppkt_len = NTOHS(ippkt->total_len) - sizeof(iphdr_t);icmppkt = (icmphdr_t *)strip_header(ippkt, sizeof(iphdr_t));if (cksum(0, icmppkt, icmppkt_len, BENDIAN)) {log_printf(ERROR, "Invalid ICMP checksum\n");ret = -1;goto out;}icmp_type = icmppkt->type;log_printf(INFO, "Echo (ping) %s, id: %u, seq: %u/%u, ttl=%u\n",icmp_type == ICMP_TYPE_ECHO ? "request" : "reply",NTOHS(icmppkt->id), NTOHS(icmppkt->seq), icmppkt->seq,ippkt->ttl);dump_buf(icmppkt, icmppkt_len);switch(icmp_type) {case ICMP_TYPE_ECHO:ret = process_icmp_echo(ippkt, icmppkt);break;case ICMP_TYPE_ECHO_REPLY:ret = process_icmp_echo_reply();break;default:log_printf(WARNING,"Unhandled ICMP PKT, type: %u\n", icmp_type);ret = -1;goto out;}out:return ret;}

Line 1-13: icmp_recv函数的参数pkt指向IP数据帧的首字节地址,sz是IP数据帧的长度,包括IP头部和IP Payload。pkt的类型是void *,没有定义成iphdr_t *类型,因为icmp_recv函数是ICMP模块暴露给其他模块使用的函数接口,icmp_recv函数声明在icmp.h头文件中,如果定义icmp_recv的入参pkt为iphdr_t *类型,就需要在icmp.h头文件中引入ip.h头文件,造成ICMP模块和IP模块头文件的循环引用。读者还会在其他模块的实现中看到类似的void *类型的函数入参的定义,都是为了避免头文件的循环引用和增加模块之间的独立性。
Line 14-17: 判断入参pkt不为空且sz不为0时继续执行,将pkt强制转换为iphdr_t *类型,根据IP首部的total_len字段计算ICMP数据帧的长度,strip_header将IP数据帧的头部剥去后赋值给icmppkt,strip_header之前已经介绍过,将ippkt指针转换为unsigned char *类型,再加上sizeof(iphdr_t)。
Line 18-22: 检验ICMP数据帧的校验和,8.1节中介绍的ICMP的头部数据和ICMP的payload数据都要参与计算校验和,icmppkt指向的ICMP数据帧的头部中包含发送方的校验和数据,所以此处检验校验和的结果应当为0,否则认为接收到的ICMP数据帧不合法。
Line 23-44: 校验和检验通过后,打印输出接收的到ICMP数据帧的头部信息,dump_buf做为debug使用,输出Echo (Ping) Request数据帧的内容。根据ICMP头部中的type判断,接收到的ICMP数据帧是Echo Ping Rquest或者是Echo Ping Reply,再交给process_icmp_echo和process_icmp_echo_reply函数处理。
8.4 ICMP数据帧的发送
本节在8.3节的基础上扩展process_icmp_echo函数,构造ICMP Echo (Ping) Reply数据帧,复制ICMP Echo Ping Request数据帧的payload数据,计算ICMP数据帧的校验和,将构造好的ICMP Echo Ping Reply数据帧交给IP模块发送。在介绍代码实现之前,先来通过wireshark抓取PING的数据帧交互过程,查看如何构造ICMP Echo (Ping) Reply数据帧。

上图是本人所在局域网中的一台windows主机PING路由器的交互过程,192.168.0.105发出Echo (ping) Request,路由器192.168.0.1回复Echo (ping) Reply。
Echo (ping) Requset数据帧

Echo (Ping) Request数据帧头部结构已经在8.1节详细介绍过,data部分含有32个字节的数据,从wireshark的raw byte数据显示可以看出,32个字节的data是ascii码小写的a到w,再重复小写的a到i。
Echo (ping) Reply数据帧

Echo (ping) Reply的identifier,sequence number与Echo (ping) Request相等,在8.1节已经介绍过,RFC792 ICMP协议中有说明,ICMP头部code为0时,Echo ping Request和Echo Ping Reply的identifier,sequence number字段分别相等。表示Echo Ping Reply回复对应的Echo ping request数据帧。再来看数据部分,32个字节的数据也是相等的,与ICMP Echo数据帧的名字相呼应,接收到Echo (Ping) Request数据帧时,将数据原封不动的Echo回去。Echo (Ping) Reply除了type和校验和部分与Echo (Ping) Request不相等之外,其余部分均相等。弄清楚Echo (Ping) Reply数据帧的回复规则后,再来看process_icmp_echo的代码实现。

 static int process_icmp_echo(iphdr_t *ippkt, icmphdr_t *echo_req){int ret = 0;unsigned short data_len = 0;unsigned char *echo_req_data = NULL;icmphdr_t *echo_reply = NULL;pdbuf_t *pdbuf = NULL;if (ippkt == NULL || echo_req == NULL) {ret = -1;goto out;}echo_req_data = strip_header(echo_req, sizeof(icmphdr_t));data_len = NTOHS(ippkt->total_len) -sizeof(iphdr_t) - sizeof(icmphdr_t);pdbuf = pdbuf_alloc(data_len, !IGNORE_MTU);if (pdbuf == NULL) {ret = -1;goto out;}/* build icmp reply */pdbuf_push(pdbuf, data_len);memcpy(pdbuf->payload, echo_req_data, data_len);pdbuf_push(pdbuf, sizeof(icmphdr_t));echo_reply = (icmphdr_t *)pdbuf->payload;echo_reply->type = ICMP_TYPE_ECHO_REPLY;echo_reply->code = ICMP_CODE;echo_reply->cksum = 0;echo_reply->id = echo_req->id;echo_reply->seq = HSTON(icmp_seq ++);echo_reply->cksum = cksum(0, echo_reply,data_len + sizeof(icmphdr_t), BENDIAN);echo_reply->cksum = HSTON(echo_reply->cksum);dump_buf(echo_reply, data_len + sizeof(icmphdr_t));out:return ret;}static int process_icmp_echo_reply(){int ret = 0;return ret;}

process_icmp_echo函数的入参是ippkt指针和icmppkt指针,分别指向IP数据帧的首字节地址,和ICMP数据帧的首字节地址。之所以需要IP数据帧,是因为ICMP Echo (Ping) Reply数据帧构造完成之后,需要将其交给IP模块的发送函数处理,发送的目标IP地址就是ICMP Request数据帧的IP头部中的源IP地址。
Line 1-16: 判断ippkt和icmppkt指针都不为空时继续执行,虽然icmp_recv到process_ecmp_echo的调用,ippkt和icmppkt指针必然不为空,本人编写C语言习惯先检查指针的合法性,再使用指针。调用strip_header将ICMP数据帧的头部剥去,返回的指针赋值给echo_req_data,echo_req_data指向ICMP Echo (Ping) Request的数据部分的首字节地址。通过IP头部的total_len字段减去IP头部长度和ICMP头部长度,得到Echo (ping) Request数据部分的长度。
Line 17-21: pdbuf_alloc申请长度为data_len的buffer,6.4节已经介绍过pdbuf_alloc申请的内存空间包括data_len,Layer3和Layer4最大的协议头部,以太网头部和pdbuf_t描述符本身占用的全部内存空间。pdbuf_alloc的第二个参数是!IGNORE_MTU,表示最大申请1500字节的内存空间。介绍IP分片的重组时,会再次改写该函数,重组后的IP分片,封装的ICMP数据帧的长度会超过1500字节。
Line 22-26: pdbuf_push调整pdbuf->payload向地址减小的方向移动data_len个字节,首先将Echo Request的数据部分复制到pdbuf->payload指向的内存空间。再将pdbuf->payload向地址减小的方向移动sizeof(icmphdr_t)个字节,用于存放ICMP的头部数据。将pdbuf->payload指针强制转换为icmphdr_t *类型,用于构造ICMP头部数据。
Line 27-38: type为0x0,code为0x0,cksum字段先赋值为0,最后计算校验和的数值。Identifier和sequence number都来自Echo (ping) Request数据帧,已经是大端格式,所以不需要再次转换为大端。计算校验和,参与计算的数据包括Echo (ping) Reply的头部数据和Payload数据。校验和计算完成后再转换为大端格式填入Echo (ping) Reply数据帧的头部cksum字段中。dump_buf做为debug使用,在将Echo (ping) Reply数据帧交给IP模块发送之前,先通过dump_buf查看构造的数据帧是否符合预期。
与8.2的测试方法一致,运行DIY TCP/IP的主机记为A,与主机A在同一个局域网的主机B上Ping主机A,8.3节在完成ICMP数据帧的校验和检查后,dump_buf打印Echo Ping Request数据帧的内容,本节在ICMP数据帧的校验和计算完成后,dump_buf打印回复构造的Echo Ping Reply数据帧的内容。
主机B PING 192.168.0.7,该IP地址是DIY TCP/IP的虚拟IP地址(局域网中不存在),本节只是构造了ICMP Echo Ping Reply,并没有将数据帧交给IP模块发送,所以主机B上PING的结果仍然是失败的。

再来看DIY TCP/IP的运行结果

gannicus@ubuntu:~/guojia/tasks/DIY_USER_SPACE_TCPIP/ch5/2$ sudo ./tcp_ip_stack -i 192.168.0.7
[sudo] password for gannicus:
Network device init
filter: ether proto 0x0800 or ether proto 0x0806
Network device RX init
Network device TX init
Net device ip address: 192.168.0.7
192.168.0.7 is at 00:0c:29:2e:0a:ed
ARP Table
IP Address      MAC Address
192.168.0.105       8c:a9:82:11:d1:de
Echo (ping) request, id: 1, seq: 16/4096, ttl=64
08 00 4d 4b 00 01 00 10 61 62 63 64 65 66 67 68
69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 61
62 63 64 65 66 67 68 69
Echo (ping) reply, id: 1, seq: 16/4096, ttl=64
00 00 55 4b 00 01 00 10 61 62 63 64 65 66 67 68
69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 61
62 63 64 65 66 67 68 69
Echo (ping) request, id: 1, seq: 17/4352, ttl=64
…

从运行结果可以看出,ICMP模块接收到了Echo (PIing) Request,并且校验和检验成功,Echo (ping) request的type为0x08,0x0为code,4d 4b 为大端格式的校验和,00 01是大端格式的identification,00 10也是大端格式的sequence number,从61向后,一直到第40个字节是Echo (ping) Request的数据内容。
再来看本节构造的Echo (Ping) Reply,type为0x00,0x0为code,55 4b是大端格式的校验和,identification和sequence number与Echo (Ping) Request相等,后面的数据部分也相等,运行结果符合预期。
下一篇:DIY TCP/IP IP模块和ICMP模块的实现3

DIY TCP/IP IP模块和ICMP模块的实现2相关推荐

  1. DIY TCP/IP IP模块和ICMP模块的实现1

    上一篇:DIY TCP/IP IP模块和ICMP模块的实现0 8.2 IP数据帧的接收 本节实现DIY TCP/IP的IP数据帧的接收,6.1节介绍pdbuf模块时已经引入了IP头部结构体的定义,ip ...

  2. Gannicus Guo的DIY TCP/IP 连载目录

    Gannicus Guo的DIY TCP/IP 连载目录 该专栏描述本人自己动手写的一个简化的TCP/IP协议.经测试该协议可以运行在Ubuntu 16.10 x86_64 操作系统用户空间.便于描述 ...

  3. DIY TCP/IP TCP模块的实现9

    上一篇:DIY TCP/IP TCP模块的实现8 9.11 TCP滑动窗口的实现3 9.10节的DIY TCP/IP已经可以正确接收TCP数据帧了.9.10节只是解析一下收到的TCP数据帧携带的数据长 ...

  4. Gannicus Guo的DIY TCP/IP之旅

    Gannicus Guo的DIY TCP/IP之旅,描述本人自己动手写的一个简化的TCP/IP协议.经测试该协议可以运行在Ubuntu 16.10 x86_64 操作系统用户空间.便于描述,将该TCP ...

  5. 了解TCP协议,IP协议、ICMP协议和ARP协议(TCP报文,TCP的分成管理,TCP与UDP,TCP的三次握手四次挥手原理)

    文章目录 了解TCP/IP协议 TCP报文格式 TCP/IP 的分层管理 TCP与UDP TCP的三次握手与四次挥手 为什么要三次握手? 为什么要四次挥手? IP数据包格式 ICMP协议 ICMP协议 ...

  6. 用于excel(或wps)中进行ip处理转换的vbs模块

    从网上找到的,有一小点改动 Attribute VB_Name = "模块1"' 本模块代码来自 http://www.anyweb.co.nz/tutorial/excelipO ...

  7. Linux内核分析 - 网络[十一]:ICMP模块

    内核版本:2.6.34 ICMP模块比较简单,要注意的是icmp的速率限制策略,向IP层传输数据ip_append_data()和ip_push_pending_frames(). 在net/ipv4 ...

  8. Tnsping 和TCP/IP 中的ping 的区別

    Oracle Net 工具(命令)tnsping,是一个OSI会话层的工具,它用来: 1)验证名字解析(name resolution,当然是oracle自己的网络服务名) 2)远程的listener ...

  9. 三十天学不会TCP,UDP/IP网络编程-IP头格式祥述

    我又来了,这篇文章还是来做(da)推(guang)介(gao)我自己的!俗话说事不过三,我觉得我下次得换个说法了,不然估计要被厌恶了,但是我是好心呐,一定要相信我纯洁的眼神.由于这两年接触到了比较多的 ...

最新文章

  1. 编程之美-计算字符串的相似度方法整理
  2. 9 个技巧,解决 K8s 中的日志输出问题
  3. 简单的通讯录程序系统python
  4. 软件测试推荐专业,软件测试专业老师推荐信
  5. Python爬虫入门教程 22-100 CSDN学院课程数据抓取
  6. C/C++二维数组名和二级指针的联系与区别
  7. 2022,你的团队距离持续部署还有多远?
  8. java kafka client_Kafka Client API 基本使用
  9. 新闻列表页flex_使用css3的Flex布局实现列表展示
  10. 中兴B860AV1.1_机顶盒_(4G和8G版)刷机固件升级和教程
  11. 【Java】面向对象编程题
  12. 易语言注册机接码平台对接
  13. C语言:逗号表达式(辨析)
  14. 【Multisim仿真】光控报警电路
  15. NOR 与 NAND的区别对比分析
  16. 通达信地量指标公式 启涨地量买点选股指标天眼地量指标
  17. ECDSA算法JAVA实现加解密
  18. 一坑未平一坑又起——圆锥曲线1-1 椭圆的定义中的东西
  19. 计算机软件保护对象,计算机软件著作权保护的对象是什么
  20. 从电焊女工到Google台湾总经理

热门文章

  1. 数据库中的三种完整性:域、实体、参照完整性
  2. 自己动手做一个局域网聊天工具(一)
  3. redhat6.5进入救援
  4. Hive Select 查询数据
  5. R语言函数定义快速查看
  6. 最速下降法 c 语言程序,工程優化方法中的“最速下降法”和“DFP擬牛頓法”的 C 語言實現...
  7. Matlab2017b C++编译器配置
  8. 基于Echarts的销售企业经营数据分析-帕累托
  9. 当人说君子动口不动手时怎么回怼_故事:君子动口不动手,神人动心不动口,有情有意事后再回报...
  10. .net fileupload批量上传可删除_8uftp上传工具,8uftp上传工具的使用方法只需8步