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
?