ICMP协议简介
ICMP协议是一个网络层协议
背景:如果丢包了,IP协议并不能通知传输层是否丢包以及丢包的原因。因此我们需要ICMP协议来完成这样的功能
为什么需要ICMP协议
1,IP 协议本身不提供差错报告和差错控制机制来保证数据报递交的有效性,例如:数据报在网络中被丢弃了,源主机更希望等到该数据报递交过程中的异常信息。
2,IP 协议不能进行主机管理与查询机制,例如:不知道对方主机或者路由器的活跃,对
于不活跃的主机和路由器就没有必要发送数据报。类似于电脑cmd的ping
ICMP协议类型与结构
ICMP协议类型 |
类型 |
应用 |
差错报告报文 |
目的不可达、源站抑制、重定向、超时、参数错误 |
|
询问报文 |
回送请求/回答(类似ping)、时间戳请求/回答 |
|
消息帧结构:差错/询问一样(前4)
ICMP 差错报文
用于检测IP数据报在传输过程的异常信息(目的不可达、源站抑制、重定向、超时、参数错误)
ICMP类型 |
代码值 |
描述 |
3 |
0 |
网络不可达(找不到地方) |
1 |
主机不可达(找到地方,找不到人) |
|
2 |
协议不可达(找到地方且找到人,但口令对不上) |
|
3 |
端口不可达(找到地方且找到人以及口令对上,但事情对不上) |
|
4 |
需要分片但设置了不分片(发不了) |
|
5 |
源站路由失败(速度慢了) |
|
11 |
0 |
传输期间生存时间为0 |
1 |
数据报组装期间生存时间为0 |
ICMP 查询报文
用于诊断两个网络设备之间彼此是否能够通信
ICMP类型 |
报文结构 |
描述 |
0或8 |
回显请求/应答 |
lwip处理 |
9或10 |
路由器查询/通告 |
lwIP不处理 |
13或14 |
时间戳请求/应答 |
|
14或16 |
信息请求/应答 |
|
17或18 |
地址掩码请求/应答 |
目的主机(stm)收到 ICMP 回送请求报文后立刻回送应答报文,若源主机(电脑)能收到 ICMP 回送应答报文,则说明到达该主机的网络正常(PING)
ICMP差错报文源码
ICMP报文数据结构
struct icmp_echo_hdr
{
PACK_STRUCT_FLD_8(u8_t type); /* 类型 */
PACK_STRUCT_FLD_8(u8_t code); /* 代码 */
PACK_STRUCT_FIELD(u16_t chksum); /* 校验和 */
PACK_STRUCT_FIELD(u16_t id); /* 标志符 */
PACK_STRUCT_FIELD(u16_t seqno); /* 序号 */
}
PACK_STRUCT_STRUCT;
差错报文结构
查询报文结构
个人理解:数据里面,有一帧,请求;应答是重复这个报文(这个是错误理解,参考下一节ICMP查询章节描述)
ICMP的差错报文
lwIP只实现1、目的不可达、2、超时差错报文,它们分别为icmp_dest_unreach和icmp_time_exceeded函数
差错报文 |
类型 |
描述 |
目的不可达 |
在传递过程中出错,不能到达目标主机, 或到达目标主机后无法传递至上层协议 |
|
超时差错 |
IP首部的“TTL”字段记录着该数据报的生命值, 该数据报每被转发一次,TTL值减1。直到为0,丢弃该报 |
这两种差错报文最终都是调用icmp_send_response函数发送
\Middlewares\lwip\src\core\ipv4\icmp.c
不可到达函数icmp_dest_unreach
void
icmp_dest_unreach(struct pbuf *p, enum icmp_dur_type t)
{
MIB2_STATS_INC(mib2.icmpoutdestunreachs);
icmp_send_response(p, ICMP_DUR, t);// 参2: 类型不可到达 宏定义3 为0为网络不可到达
}
不可到达类型枚举
\Middlewares\lwip\src\include\lwip\icmp.h
/** ICMP destination unreachable codes */
enum icmp_dur_type {
/** net unreachable */
ICMP_DUR_NET = 0,
/** host unreachable */
ICMP_DUR_HOST = 1,
/** protocol unreachable */
ICMP_DUR_PROTO = 2,
/** port unreachable */
ICMP_DUR_PORT = 3,
/** fragmentation needed and DF set */
ICMP_DUR_FRAG = 4,
/** source route failed */
ICMP_DUR_SR = 5
};
ICMP超时枚举
\Middlewares\lwip\src\include\lwip\icmp.h
/** ICMP time exceeded codes */
enum icmp_te_type {
/** time to live exceeded in transit */
ICMP_TE_TTL = 0,
/** fragment reassembly time exceeded */
ICMP_TE_FRAG = 1
};
差错报文的原理
把丢弃的数据包IP首部和数据区域(前8字节)拷贝到差错报文当中
差错报文主要函数icmp_send_response
static void
icmp_send_response(struct pbuf *p, u8_t type, u8_t code)
{
struct pbuf *q;
struct ip_hdr *iphdr;
struct icmp_echo_hdr *icmphdr;
ip4_addr_t iphdr_src;
struct netif *netif;
u16_t response_pkt_len;
MIB2_STATS_INC(mib2.icmpoutmsgs);
/* response_pkt_len = 20 + 8 = 28*/
response_pkt_len = IP_HLEN + ICMP_DEST_UNREACH_DATASIZE;
if (p->tot_len < response_pkt_len) {
response_pkt_len = p->tot_len;
} /
* 申请pbuf内存 */
q = pbuf_alloc(PBUF_IP, sizeof(struct icmp_echo_hdr) + response_pkt_len,
PBUF_RAM);
if (q == NULL) {
MIB2_STATS_INC(mib2.icmpouterrors);
return;
} /
* 获取对方主机数据报的IP首部 */
iphdr = (struct ip_hdr *)p->payload;
/* 在q->payload地址附加icmp_echo_hdr结构体 */
icmphdr = (struct icmp_echo_hdr *)q->payload;
/* 设置ICMP首部信息 */
icmphdr->type = type;
icmphdr->code = code;
icmphdr->id = 0;
icmphdr->seqno = 0;
/* 把对方主机的数据复制IP首部和前8字节数据到新申请的pbuf当中 */
SMEMCPY((u8_t *)q->payload + sizeof(struct icmp_echo_hdr), (u8_t *)p->payload,
response_pkt_len);
/* 复制目标地址 */
ip4_addr_copy(iphdr_src, iphdr->src);
/* 判断是否同一网段 */
netif = ip4_route(&iphdr_src);
if (netif != NULL) {
icmphdr->chksum = 0;
ICMP_STATS_INC(icmp.xmit);
/* 发送ICMP差错报文 */
ip4_output_if(q, NULL, &iphdr_src, ICMP_TTL, 0, IP_PROTO_ICMP, netif);
}申请pbuf内存
添加ICMP首部
调用ip4_output_if函数发送数据,注:目标IP地址不为NULL
pbuf_free(q);
}
申请pbuf内存
添加ICMP首部
调用ip4_output_if函数发送数据,注:目标IP地址不为NULL
ICMP查询报文
请求报文发送,应答报文重复。简单来讲,应答包是在请求包的基础上修改得来
原理,最终形成第三个结构效果
查询报文主函数icmp_input
void
icmp_input(struct pbuf *p, struct netif *inp)
{
u8_t type;
struct icmp_echo_hdr *iecho;
const struct ip_hdr *iphdr_in;
u16_t hlen;
const ip4_addr_t *src;
ICMP_STATS_INC(icmp.recv);
MIB2_STATS_INC(mib2.icmpinmsgs);
iphdr_in = ip4_current_header();
/* 获取IP首部 */
hlen = IPH_HL_BYTES(iphdr_in);
if (hlen < IP_HLEN) {
goto lenerr;
} i
f (p->len < sizeof(u16_t) * 2) {
goto lenerr;
} /
* 获取 ICMP 的类型字段 */
type = *((u8_t *)p->payload);
switch (type) {
case ICMP_ER:/* 回送应答 */
MIB2_STATS_INC(mib2.icmpinechoreps);
break;
case ICMP_ECHO:/* 回送请求 */
MIB2_STATS_INC(mib2.icmpinechos);
/* 获取IP地址 */
src = ip4_current_dest_addr();
/* 判断是否为多播地址 */
if (ip4_addr_ismulticast(ip4_current_dest_addr())) {
goto icmperr;
} /
* 判断是否为广播地址 */
if (ip4_addr_isbroadcast(ip4_current_dest_addr(), ip_current_netif())) {
goto icmperr;
} /
* 判断这个pbuf的总长度是否小于icmp首部 */
if (p->tot_len < sizeof(struct icmp_echo_hdr)) {
goto lenerr;
}
#if LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN
/* 偏移paylosd指针 = IP首部(20) + 14 + 0 = 34 */
if (pbuf_add_header(p, hlen + PBUF_LINK_HLEN +
PBUF_LINK_ENCAPSULATION_HLEN)) {
struct pbuf *r;/* 申请大小 = p->tot_len + hlen */
u16_t alloc_len = (u16_t)(p->tot_len + hlen);
if (alloc_len < p->tot_len) {
goto icmperr;
} /
* 申请pbuf */
r = pbuf_alloc(PBUF_LINK, alloc_len, PBUF_RAM);
if (r == NULL) {
goto icmperr;
} i
f (r->len < hlen + sizeof(struct icmp_echo_hdr)) {
pbuf_free(r);
goto icmperr;
} /
* 拷贝IP首部到新申请的pbuf当中 */
MEMCPY(r->payload, iphdr_in, hlen);
/* 偏移paylosd指针 */
if (pbuf_remove_header(r, hlen)) {
pbuf_free(r);
goto icmperr;
} /
* 复制没有ip首部的其余数据包 */
if (pbuf_copy(r, p) != ERR_OK) {
pbuf_free(r);
goto icmperr;
} /
* 释放pbuf(p) */
pbuf_free(p);
/* p -> r */
p = r;
} else {
/* 偏移paylosd指针 IP首部(20) + 14 + 0 = 34 */
if (pbuf_remove_header(p, hlen + PBUF_LINK_HLEN +
PBUF_LINK_ENCAPSULATION_HLEN)) {
goto icmperr;
}
}
#endif
/* p->payload地址上附加icmp_echo_hdr结构体 */
iecho = (struct icmp_echo_hdr *)p->payload;
/* 添加IP首部 */
if (pbuf_add_header(p, hlen)) {
} else {
err_t ret;
/* 设置IP首部信息 */
struct ip_hdr *iphdr = (struct ip_hdr *)p->payload;
ip4_addr_copy(iphdr->src, *src);
ip4_addr_copy(iphdr->dest, *ip4_current_src_addr());
/* 设置ICMP首部信息 */
ICMPH_TYPE_SET(iecho, ICMP_ER);
iecho->chksum = 0;
/* 设置生存时间 */
IPH_TTL_SET(iphdr, ICMP_TTL);IP数据包递交给icmp_input函数之前的pbuf示意图
IPH_CHKSUM_SET(iphdr, 0);
ICMP_STATS_INC(icmp.xmit);
MIB2_STATS_INC(mib2.icmpoutmsgs);
MIB2_STATS_INC(mib2.icmpoutechoreps);
/* 发送ICMP回显应答包 */
ret = ip4_output_if(p, src, LWIP_IP_HDRINCL,
ICMP_TTL, 0, IP_PROTO_ICMP, inp);
if (ret != ERR_OK) {
}
} b
reak;
default: /* lwIP不作处理 */
if (type == ICMP_DUR) {
MIB2_STATS_INC(mib2.icmpindestunreachs);
} else if (type == ICMP_TE) {
MIB2_STATS_INC(mib2.icmpintimeexcds);
} else if (type == ICMP_PP) {
MIB2_STATS_INC(mib2.icmpinparmprobs);
} else if (type == ICMP_SQ) {
MIB2_STATS_INC(mib2.icmpinsrcquenchs);
} else if (type == ICMP_RD) {
MIB2_STATS_INC(mib2.icmpinredirects);
} else if (type == ICMP_TS) {
MIB2_STATS_INC(mib2.icmpintimestamps);
} else if (type == ICMP_TSR) {
MIB2_STATS_INC(mib2.icmpintimestampreps);
} else if (type == ICMP_AM) {
MIB2_STATS_INC(mib2.icmpinaddrmasks);
} else if (type == ICMP_AMR) {
MIB2_STATS_INC(mib2.icmpinaddrmaskreps);
}
} p
buf_free(p);
return;
lenerr:
pbuf_free(p);
ICMP_STATS_INC(icmp.lenerr);
MIB2_STATS_INC(mib2.icmpinerrors);
return;
#if LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN || !LWIP_MULTICAST_PING ||
!LWIP_BROADCAST_PING
icmperr:
pbuf_free(p);
ICMP_STATS_INC(icmp.err);
MIB2_STATS_INC(mib2.icmpinerrors);
return;
#endif
}
各个操作的效果图
IP数据包递交给icmp_input函数之前的pbuf示意图
如果该数据包为回送请求包,则修改ICMP首部的类型

lwIP内核对该数据包的payload指针偏移20字节,添加IP首部
设置IP首部的字段信息,设置完成之后调用ip4_output_if函数发送ICMP回送应答包。注:目标IP地址为NULL