从1写TCPIP协议栈6:ICMP协议及Ping响应实现
引言
承接第五小节的随笔,ICMP是IP协议的辅助与补充协议,它挂载在IP数据包中进行传输,因此ICMP协议同IP协议一样严格来说介于网络层和传输层之间。ICMP(Intel control message protocol)控制报文协议只负责传递差错信息。细品一下就能明白ICMP也不会为IP协议提供可靠性,只会为IP协议提供错误检查。本篇本白只介绍ICMP-IPV4~
ICMP协议简介
ICMP协议是RFC792
提出并逐渐作为标准被大众所接收,ICMP包的结构非常简单~
字段解释
- Type类型:在ICMPV4中,类型字段一共保留了42个不同的类型的值。但是一般只有8个是常用的,比如本次练习所使用的8代表Echo-Request报文。
- Code代码:基于Type类型的描述进一步给出报文的具体类别,也就是更加具体的差错信息。
- Checksum校验:采用Intel校验,作用整个ICMP报头。
Code与Type字段
如上所述,Code和Type共同描述差错信息,类型和代码字段参考链接:ICMP报文分类。
ICMP-IPV4差错报文
ICMP的差错报文根据报文类型共有五种:
报文类型 | 名称 | 原因 |
3 | 目的不可达 | 不可达的主机/协议 |
4 | 源端抑制 | CP拥塞/UDP弃用 |
5 | 重定向 | 发回主机重新发给其他路由器 |
11 | 超时 | TTL耗尽 |
12 | 参数问题 | 数据包有错误 |
再提醒一点,学习过CAN的小伙伴了解这样一种规则,CAN通讯时的错误帧爆出针对一些特殊情况做了避免,比如仲裁期间不产生错误帧,对总线上其他节点发送的错误帧不产生错误帧。该设计和ICMP差错报文的设计有一点共通之处,那就是针对自己的差错不会差错报文,这避免了广播风暴,即不会进入差错的差错的差错....这种死循环。当然,还有一些其他情况,总结下来就是:
- 差错报文。
- IP分片报文。
- 目的地址为广播或组播的数据包。
- 源地址是特殊地址 , 比如0地址,环回地址,广播地址,组播地址。不发送 ICMP 差错报文。
Ping响应实现
实现说明
本次练习我们只实现Ping的响应用于学习了解ICMP的基本机制,为什莫呢?因为Ping的请求中包括了两个特殊字段:
ID和Seq是为了区分不同的Ping进程所添加的辅助识别信息,每一次ping中会发送四次或多次ping报文(Seq++
),每一个ping请求和ping响应的ID与Seq一致,但是ID一般来说跟随系统是固定的。本白为了偷懒,本次练习不做ping请求的实现~如果想更详细的了解ICMP相关的内容,推荐《用TCP/IP进行网际互联:原理、协议与结构(第一卷 第四版)》,在第20章讲述了具体的实现。
代码实现
首先定义一个ICMP结构体:
typedef struct _icmp_packet
{
uint8_t type;//报文类型
uint8_t code;//差错代码号
uint16_t checksum;
uint16_t ID;//ICMP-ID
uint16_t Seq;//ICMP-Seq
}_icmp_packet;
ICMP既然挂载在IP数据中,那只需要针对IP数据包做一次分类就好:
switch (iphdr->protocol)
{
case ICMP:
_eth_packet_del_header(packet, sizeof(_ip_header));
_icmp_check_in(&Srcip, packet);
break;
case TCP:
break;
}
根据IP协议的协议类型字段进行ICMP的判断,然后提出IP头,剩下的就是ICMP的数据了,根据拿到的数据我们可以提取相关信息存入发送的ICMP包中,重新组IP包发送时可以根据接收到的IP包发送方IP地址在ARP表项中查询对应MAC:
//ICMP响应
int _icmp_make_response(_icmp_packet* icmphdr, _ip_addr* srcip, _eth_packet* packet)
{
//配置发送区大小,准备发送
_eth_packet* tx = _eth_packet_tx(packet->size);//此时packet只有icmp报文了
_icmp_packet* reply_hdr = (_icmp_packet*)tx->dataptr;
reply_hdr->code = 0;
reply_hdr->ID = icmphdr->ID;//应该和请求ID一致
reply_hdr->Seq = icmphdr->Seq;//应该和请求Seq一致
reply_hdr->checksum = 0;
reply_hdr->type = 0;
//做好的icmp数据复制到定义的reply中去
memcpy((uint8_t*)reply_hdr+sizeof(_icmp_packet), ((uint8_t*)icmphdr) +sizeof(_icmp_packet),
packet->size- sizeof(_icmp_packet));
reply_hdr->checksum = checksum16((uint16_t*)reply_hdr, tx->size,0,1);
printf("ICMP : %x %x %x %x %x\n", *packet->dataptr, *(packet->dataptr + 1), *(packet->dataptr + 2), *(packet->dataptr + 3), *(packet->dataptr + 4));
return _IP_output(1, srcip, tx);
}
//IP包组装发送
_type_drv_err _IP_output(_type_eth_ptl ptl/*上层协议类型*/, _ip_addr* ipaddr/*目的IP*/, _eth_packet* packet/*以太网包*/)
{
_ip_header* iphdr;
static uint32_t ID_Counter=0;
_eth_packet_add_header(packet,sizeof(_ip_header));//添加包头
iphdr = (_ip_header*)packet->dataptr;
iphdr->version = 4;//IPV4
iphdr->hdr_len = 20/4; //headr size
iphdr->tos = 0; //暂时不关注
iphdr->totalLen = swap_order16(packet->size);
iphdr->ID = swap_order16(ID_Counter); ID_Counter++;
iphdr->flag_fragment = 0;
iphdr->ttl = 64; //最大255
iphdr->protocol = ptl;//暂时不定义 1-icmp 17-udp 6-tcp
memcpy(iphdr->srcip, _ip_cfg.array, SIZE_IPV4_ADDR);
memcpy(iphdr->destip, &ipaddr->array,SIZE_IPV4_ADDR);
iphdr->hdr_checksum = 0;
iphdr->hdr_checksum = checksum16((uint16_t*)iphdr,sizeof(_ip_header),0,1);
return _ethernet_out(ipaddr,packet);
}
static _type_drv_err _ethernet_out(_ip_addr* destip, _eth_packet* packet)
{
_type_drv_err err;
uint8_t* macaddr;
printf("_ethernet_out");
if ((DRIVE_ERR_OK == _arp_convert(destip, &macaddr)))//ARP表项查询
{
return _ethernet_send(TCP_PROTOCOL, macaddr, packet);
}
return DRIVE_ERR_IO;
}
ICMP目的不可达报文实现
除了正常的响应报文,跟着大佬本白再实现一个差错报文-目的不可达,目的不可达主要有以下几种错误代码:
我们挑一个端口不可达,端口不可达同理。另外差错报文我们只需要注意其Seq与ID就是Unused-00000000就可以了:
_type_drv_err _icmp_dest_unreach(uint8_t code, _ip_header* ipheader)
{
_icmp_packet* icmphdr;
_eth_packet* packet;
_ip_addr dest_ip;
uint16_t ip_header_size = ipheader->hdr_len * 4;
uint16_t ip_data_size = swap_order16(ipheader->totalLen) - ip_header_size;
packet = _eth_packet_tx(sizeof(_icmp_packet)+ ip_header_size+ ip_data_size+8/*rfc指明最小为头部长度+8字节*/);//icmp包头+ip头+数据部分
icmphdr = (_icmp_packet*)packet->dataptr;
icmphdr->code = code;
icmphdr->ID = 0;//不可达报文为00
icmphdr->Seq = 0;//不可达报文为00
icmphdr->checksum = 0;
icmphdr->type = XICMP_TYPE;
memcpy((uint8_t*)icmphdr +sizeof(_icmp_packet),ipheader, ip_data_size);
icmphdr->checksum = checksum16((uint16_t*)icmphdr, packet->size, 0, 1);
printf("unreach ICMP : %x %x %x %x %x\n", *packet->dataptr, *(packet->dataptr + 1), *(packet->dataptr + 2), *(packet->dataptr + 3), *(packet->dataptr + 4));
SRC_IP_extract(&dest_ip, ipheader->srcip);
return _IP_output(1, ipheader->srcip, packet);
}
关于ICMP报文的思考
ICMP报文仍然是挂载在IP数据包中的,因此是IP协议的子协议。ICMP的报文大致分为差错报文和信息报文,差错报文一般要求不会回复差错报文来防止广播泛洪,但我们仍需要IP报文不分片的情况下使用拓展字段让ICMP报文尽可能多的包含差错信息来帮助主机检查错误。即对于IP数据保障,扩展字段在ICMPv4与v6之间的位置相同(填充位实现兼容性)。ICMP提供了差错机制,最核心的差错便是回显与请求。另外,简单看了下ICMPv6,ICMPv6增加了邻居发现功能,也可以借用IGMP进行组播成员的资格管理,这些机制后续等练习做完也会专门写一篇随笔来对这部分做介绍。
希望计划不要延迟吧~
本文来自博客园,作者:{张一默},转载请注明原文链接:https://www.cnblogs.com/YiMo9929/p/17157537.html