本文轉自公眾號歡迎關注
https://mp.weixin.qq.com/s/6MTNop3zBKdQ-gabbWo63Q
一. 前言
ICMP即Internet Control Message Protocol因特網控制消息協議。
ICMP是網絡層協議,IP不可分割的一部分。
ICMP用于報告數據報處理中的錯誤,比如以下情況下時發(fā)送ICMP消息:當數據報無法到達其目的地時,當網關沒有轉發(fā)數據報的緩沖能力時,以及當網關可以指示主機在較短的路由上發(fā)送數據時。
互聯網協議的設計并不是絕對可靠的。ICMP這些控制消息的目的是提供有關通信環(huán)境中問題的反饋,而不是使IP可靠。不能確保數據報的傳遞或控制消息的返回,一些數據報可能無法送達時也沒有任何丟失報告。如果需要可靠的通信,則使用IP的更高級別協議必須實現其自己的可靠性程序。
ICMP消息通常報告數據報處理中的錯誤,且不發(fā)送關于ICMP消息的ICMP消息,否則消息會無限遞歸。此外,ICMP消息只發(fā)送關于處理分段數據報的零分段時的錯誤。(片段零的片段offeset等于零)。
參考
RFC 792
https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml
二. ICMP消息格式
ICMP消息格式
從 ICMP 的報文格式來說,ICMP 是 IP 的上層協議,他是在IP報的基礎上再添加ICMP報格式。但是 ICMP 是分擔了 IP 的一部分功能。所以,他也被認為是與 IP 同層的協議。
我們這里只關注ICMP部分,ICMP由首部和數據兩部分組成,如下
區(qū)域 | 類型Type | 代碼Code | 校驗和Checksum |
---|---|---|---|
字節(jié)大小 | 1 | 1 | 2 |
區(qū)域 | 首部數據,根據類型不一樣而不一樣,對于ping拆為了16位的ID和16位的序列號 | ||
字節(jié)大小 | 4 | ||
區(qū)域 | ICMP數據部分 | ||
字節(jié)大小 | 類型不同長度不同 |
Type和Code如下表
類型TYPE | 代碼CODE | **用途 | 描述 Description** | 查詢類Query | 差錯類Error |
---|---|---|---|---|---|
0 | 0 | Echo Reply——回顯應答(Ping應答) | x | ||
3 | 0 | Network Unreachable——網絡不可達 | x | ||
3 | 1 | Host Unreachable——主機不可達 | x | ||
3 | 2 | Protocol Unreachable——協議不可達 | x | ||
3 | 3 | Port Unreachable——端口不可達 | x | ||
3 | 4 | Fragmentation needed but no frag. bit set——需要進行分片但設置不分片比特 | x | ||
3 | 5 | Source routing failed——源站選路失敗 | x | ||
3 | 6 | Destination network unknown——目的網絡未知 | x | ||
3 | 7 | Destination host unknown——目的主機未知 | x | ||
3 | 8 | Source host isolated (obsolete)——源主機被隔離(作廢不用) | x | ||
3 | 9 | Destination network administratively prohibited——目的網絡被強制禁止 | x | ||
3 | 10 | Destination host administratively prohibited——目的主機被強制禁止 | x | ||
3 | 11 | Network unreachable for TOS——由于服務類型TOS,網絡不可達 | x | ||
3 | 12 | Host unreachable for TOS——由于服務類型TOS,主機不可達 | x | ||
3 | 13 | Communication administratively prohibited by filtering——由于過濾,通信被強制禁止 | x | ||
3 | 14 | Host precedence violation——主機越權 | x | ||
3 | 15 | Precedence cutoff in effect——優(yōu)先中止生效 | x | ||
4 | 0 | Source quench——源端被關閉(基本流控制) | |||
5 | 0 | Redirect for network——對網絡重定向 | |||
5 | 1 | Redirect for host——對主機重定向 | |||
5 | 2 | Redirect for TOS and network——對服務類型和網絡重定向 | |||
5 | 3 | Redirect for TOS and host——對服務類型和主機重定向 | |||
8 | 0 | Echo request——回顯請求(Ping請求) | x | ||
9 | 0 | Router advertisement——路由器通告 | |||
10 | 0 | Route solicitation——路由器請求 | |||
11 | 0 | TTL equals 0 during transit——傳輸期間生存時間為0 | x | ||
11 | 1 | TTL equals 0 during reassembly——在數據報組裝期間生存時間為0 | x | ||
12 | 0 | IP header bad (catchall error)——壞的IP首部(包括各種差錯) | x | ||
12 | 1 | Required options missing——缺少必需的選項 | x | ||
13 | 0 | Timestamp request (obsolete)——時間戳請求(作廢不用) | x | ||
14 | Timestamp reply (obsolete)——時間戳應答(作廢不用) | x | |||
15 | 0 | Information request (obsolete)——信息請求(作廢不用) | x | ||
16 | 0 | Information reply (obsolete)——信息應答(作廢不用) | x | ||
17 | 0 | Address mask request——地址掩碼請求 | x | ||
18 | 0 | Address mask reply——地址掩碼應答 |
使用wirshark協助解析
三. LWIP中ICMP相關代碼分析
這里只看IPV4相關的,IPV6下也有對應的實現。
ipv4/icmp.c
icmp.h
3.1調試打印
可以配置宏ICMP_DEBUG,使能調試打印,一遍跟蹤對應的程序處理過程。
lwipopts.h中配置
#define ICMP_DEBUG LWIP_DBG_ON
3.2數據結構
實現了以下Type
#define ICMP_ER 0 /* echo reply */
#define ICMP_DUR 3 /* destination unreachable */
#define ICMP_SQ 4 /* source quench */
#define ICMP_RD 5 /* redirect */
#define ICMP_ECHO 8 /* echo */
#define ICMP_TE 11 /* time exceeded */
#define ICMP_PP 12 /* parameter problem */
#define ICMP_TS 13 /* timestamp */
#define ICMP_TSR 14 /* timestamp reply */
#define ICMP_IRQ 15 /* information request */
#define ICMP_IR 16 /* information reply */
#define ICMP_AM 17 /* address mask request */
#define ICMP_AMR 18 /* address mask reply */
定義了頭的數據結構
struct icmp_hdr {
PACK_STRUCT_FLD_8(u8_t type);
PACK_STRUCT_FLD_8(u8_t code);
PACK_STRUCT_FIELD(u16_t chksum);
PACK_STRUCT_FIELD(u32_t data);
} PACK_STRUCT_STRUCT;
如果四echo則,ICMP首部后面4字節(jié)數據拆分成id和序列號
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;
3.3輸入數據流
關鍵代碼是icmp_input
ethernet_input->Type=0x0800 ip4_input-> Protocol=0x01 icmp_input
通過switch處理各種類型
switch (type) {
case ICMP_ER:
比如對于收到別人的ping請求就是進入
case ICMP_ECHO:
如果是多播地址不響應
然后檢查ICMP頭部至少要8字節(jié)。
然后檢查checksum
最后調用ip4_output_if發(fā)送響應包。
3.4輸出數據流
icmp_dest_unreach
icmp_time_exceeded
都是調用
icmp_send_response
3.5發(fā)送PING
收到響應進入icmp_input的
case ICMP_ER:
/* This is OK, echo reply might have been parsed by a raw PCB
(as obviously, an echo request has been sent, too). */
MIB2_STATS_INC(mib2.icmpinechoreps);
break
發(fā)送ping可以裸機可以使用raw PCB,帶OS可以使用socket實現
詳見ping.c/h
#include "lwip/opt.h"
#if LWIP_RAW /* don't build if not configured for use in lwipopts.h */
#include "ping.h"
#include "lwip/mem.h"
#include "lwip/raw.h"
#include "lwip/icmp.h"
#include "lwip/netif.h"
#include "lwip/sys.h"
#include "lwip/timeouts.h"
#include "lwip/inet_chksum.h"
#include "lwip/prot/ip4.h"
#if PING_USE_SOCKETS
#include "lwip/sockets.h"
#include "lwip/inet.h"
#include < string.h >
#endif /* PING_USE_SOCKETS */
/**
* PING_DEBUG: Enable debugging for PING.
*/
#ifndef PING_DEBUG
#define PING_DEBUG LWIP_DBG_ON
#endif
/** ping receive timeout - in milliseconds */
#ifndef PING_RCV_TIMEO
#define PING_RCV_TIMEO 1000
#endif
/** ping delay - in milliseconds */
#ifndef PING_DELAY
#define PING_DELAY 1000
#endif
/** ping identifier - must fit on a u16_t */
#ifndef PING_ID
#define PING_ID 0xAFAF
#endif
/** ping additional data size to include in the packet */
#ifndef PING_DATA_SIZE
#define PING_DATA_SIZE 32
#endif
/** ping result action - no default action */
#ifndef PING_RESULT
#define PING_RESULT(ping_ok)
#endif
/* ping variables */
static const ip_addr_t* ping_target;
static u16_t ping_seq_num;
#ifdef LWIP_DEBUG
static u32_t ping_time;
#endif /* LWIP_DEBUG */
#if !PING_USE_SOCKETS
static struct raw_pcb *ping_pcb;
#endif /* PING_USE_SOCKETS */
/** Prepare a echo ICMP request */
static void
ping_prepare_echo( struct icmp_echo_hdr *iecho, u16_t len)
{
size_t i;
size_t data_len = len - sizeof(struct icmp_echo_hdr);
ICMPH_TYPE_SET(iecho, ICMP_ECHO);
ICMPH_CODE_SET(iecho, 0);
iecho- >chksum = 0;
iecho- >id = PING_ID;
iecho- >seqno = lwip_htons(++ping_seq_num);
/* fill the additional data buffer with some data */
for(i = 0; i < data_len; i++) {
((char*)iecho)[sizeof(struct icmp_echo_hdr) + i] = (char)i;
}
iecho- >chksum = inet_chksum(iecho, len);
}
#if PING_USE_SOCKETS
/* Ping using the socket ip */
static err_t
ping_send(int s, const ip_addr_t *addr)
{
int err;
struct icmp_echo_hdr *iecho;
struct sockaddr_storage to;
size_t ping_size = sizeof(struct icmp_echo_hdr) + PING_DATA_SIZE;
LWIP_ASSERT("ping_size is too big", ping_size <= 0xffff);
#if LWIP_IPV6
if(IP_IS_V6(addr) && !ip6_addr_isipv4mappedipv6(ip_2_ip6(addr))) {
/* todo: support ICMP6 echo */
return ERR_VAL;
}
#endif /* LWIP_IPV6 */
iecho = (struct icmp_echo_hdr *)mem_malloc((mem_size_t)ping_size);
if (!iecho) {
return ERR_MEM;
}
ping_prepare_echo(iecho, (u16_t)ping_size);
#if LWIP_IPV4
if(IP_IS_V4(addr)) {
struct sockaddr_in *to4 = (struct sockaddr_in*)&to;
to4- >sin_len = sizeof(*to4);
to4- >sin_family = AF_INET;
inet_addr_from_ip4addr(&to4- >sin_addr, ip_2_ip4(addr));
}
#endif /* LWIP_IPV4 */
#if LWIP_IPV6
if(IP_IS_V6(addr)) {
struct sockaddr_in6 *to6 = (struct sockaddr_in6*)&to;
to6- >sin6_len = sizeof(*to6);
to6- >sin6_family = AF_INET6;
inet6_addr_from_ip6addr(&to6- >sin6_addr, ip_2_ip6(addr));
}
#endif /* LWIP_IPV6 */
err = lwip_sendto(s, iecho, ping_size, 0, (struct sockaddr*)&to, sizeof(to));
mem_free(iecho);
return (err ? ERR_OK : ERR_VAL);
}
static void
ping_recv(int s)
{
char buf[64];
int len;
struct sockaddr_storage from;
int fromlen = sizeof(from);
while((len = lwip_recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr*)&from, (socklen_t*)&fromlen)) > 0) {
if (len >= (int)(sizeof(struct ip_hdr)+sizeof(struct icmp_echo_hdr))) {
ip_addr_t fromaddr;
memset(&fromaddr, 0, sizeof(fromaddr));
#if LWIP_IPV4
if(from.ss_family == AF_INET) {
struct sockaddr_in *from4 = (struct sockaddr_in*)&from;
inet_addr_to_ip4addr(ip_2_ip4(&fromaddr), &from4- >sin_addr);
IP_SET_TYPE_VAL(fromaddr, IPADDR_TYPE_V4);
}
#endif /* LWIP_IPV4 */
#if LWIP_IPV6
if(from.ss_family == AF_INET6) {
struct sockaddr_in6 *from6 = (struct sockaddr_in6*)&from;
inet6_addr_to_ip6addr(ip_2_ip6(&fromaddr), &from6- >sin6_addr);
IP_SET_TYPE_VAL(fromaddr, IPADDR_TYPE_V6);
}
#endif /* LWIP_IPV6 */
LWIP_DEBUGF( PING_DEBUG, ("ping: recv "));
ip_addr_debug_print_val(PING_DEBUG, fromaddr);
LWIP_DEBUGF( PING_DEBUG, (" %"U32_F" msn", (sys_now() - ping_time)));
/* todo: support ICMP6 echo */
#if LWIP_IPV4
if (IP_IS_V4_VAL(fromaddr)) {
struct ip_hdr *iphdr;
struct icmp_echo_hdr *iecho;
iphdr = (struct ip_hdr *)buf;
iecho = (struct icmp_echo_hdr *)(buf + (IPH_HL(iphdr) * 4));
if ((iecho- >id == PING_ID) && (iecho- >seqno == lwip_htons(ping_seq_num))) {
/* do some ping result processing */
PING_RESULT((ICMPH_TYPE(iecho) == ICMP_ER));
return;
} else {
LWIP_DEBUGF( PING_DEBUG, ("ping: dropn"));
}
}
#endif /* LWIP_IPV4 */
}
fromlen = sizeof(from);
}
if (len == 0) {
LWIP_DEBUGF( PING_DEBUG, ("ping: recv - %"U32_F" ms - timeoutn", (sys_now()-ping_time)));
}
/* do some ping result processing */
PING_RESULT(0);
}
static void
ping_thread(void *arg)
{
int s;
int ret;
#if LWIP_SO_SNDRCVTIMEO_NONSTANDARD
int timeout = PING_RCV_TIMEO;
#else
struct timeval timeout;
timeout.tv_sec = PING_RCV_TIMEO/1000;
timeout.tv_usec = (PING_RCV_TIMEO%1000)*1000;
#endif
LWIP_UNUSED_ARG(arg);
#if LWIP_IPV6
if(IP_IS_V4(ping_target) || ip6_addr_isipv4mappedipv6(ip_2_ip6(ping_target))) {
s = lwip_socket(AF_INET6, SOCK_RAW, IP_PROTO_ICMP);
} else {
s = lwip_socket(AF_INET6, SOCK_RAW, IP6_NEXTH_ICMP6);
}
#else
s = lwip_socket(AF_INET, SOCK_RAW, IP_PROTO_ICMP);
#endif
if (s < 0) {
return;
}
ret = lwip_setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
LWIP_ASSERT("setting receive timeout failed", ret == 0);
LWIP_UNUSED_ARG(ret);
while (1) {
if (ping_send(s, ping_target) == ERR_OK) {
LWIP_DEBUGF( PING_DEBUG, ("ping: send "));
ip_addr_debug_print(PING_DEBUG, ping_target);
LWIP_DEBUGF( PING_DEBUG, ("n"));
#ifdef LWIP_DEBUG
ping_time = sys_now();
#endif /* LWIP_DEBUG */
ping_recv(s);
} else {
LWIP_DEBUGF( PING_DEBUG, ("ping: send "));
ip_addr_debug_print(PING_DEBUG, ping_target);
LWIP_DEBUGF( PING_DEBUG, (" - errorn"));
}
sys_msleep(PING_DELAY);
}
}
#else /* PING_USE_SOCKETS */
/* Ping using the raw ip */
static u8_t
ping_recv(void *arg, struct raw_pcb *pcb, struct pbuf *p, const ip_addr_t *addr)
{
struct icmp_echo_hdr *iecho;
LWIP_UNUSED_ARG(arg);
LWIP_UNUSED_ARG(pcb);
LWIP_UNUSED_ARG(addr);
LWIP_ASSERT("p != NULL", p != NULL);
if ((p- >tot_len >= (PBUF_IP_HLEN + sizeof(struct icmp_echo_hdr))) &&
pbuf_remove_header(p, PBUF_IP_HLEN) == 0) {
iecho = (struct icmp_echo_hdr *)p- >payload;
if ((iecho- >id == PING_ID) && (iecho- >seqno == lwip_htons(ping_seq_num))) {
LWIP_DEBUGF( PING_DEBUG, ("ping: recv "));
ip_addr_debug_print(PING_DEBUG, addr);
LWIP_DEBUGF( PING_DEBUG, (" %"U32_F" msn", (sys_now()-ping_time)));
/* do some ping result processing */
PING_RESULT(1);
pbuf_free(p);
return 1; /* eat the packet */
}
/* not eaten, restore original packet */
pbuf_add_header(p, PBUF_IP_HLEN);
}
return 0; /* don't eat the packet */
}
static void
ping_send(struct raw_pcb *raw, const ip_addr_t *addr)
{
struct pbuf *p;
struct icmp_echo_hdr *iecho;
size_t ping_size = sizeof(struct icmp_echo_hdr) + PING_DATA_SIZE;
LWIP_DEBUGF( PING_DEBUG, ("ping: send "));
ip_addr_debug_print(PING_DEBUG, addr);
LWIP_DEBUGF( PING_DEBUG, ("n"));
LWIP_ASSERT("ping_size <= 0xffff", ping_size <= 0xffff);
p = pbuf_alloc(PBUF_IP, (u16_t)ping_size, PBUF_RAM);
if (!p) {
return;
}
if ((p- >len == p- >tot_len) && (p- >next == NULL)) {
iecho = (struct icmp_echo_hdr *)p- >payload;
ping_prepare_echo(iecho, (u16_t)ping_size);
raw_sendto(raw, p, addr);
#ifdef LWIP_DEBUG
ping_time = sys_now();
#endif /* LWIP_DEBUG */
}
pbuf_free(p);
}
static void
ping_timeout(void *arg)
{
struct raw_pcb *pcb = (struct raw_pcb*)arg;
LWIP_ASSERT("ping_timeout: no pcb given!", pcb != NULL);
ping_send(pcb, ping_target);
sys_timeout(PING_DELAY, ping_timeout, pcb);
}
static void
ping_raw_init(void)
{
ping_pcb = raw_new(IP_PROTO_ICMP);
LWIP_ASSERT("ping_pcb != NULL", ping_pcb != NULL);
raw_recv(ping_pcb, ping_recv, NULL);
raw_bind(ping_pcb, IP_ADDR_ANY);
sys_timeout(PING_DELAY, ping_timeout, ping_pcb);
}
void
ping_send_now(void)
{
LWIP_ASSERT("ping_pcb != NULL", ping_pcb != NULL);
ping_send(ping_pcb, ping_target);
}
#endif /* PING_USE_SOCKETS */
void
ping_init(const ip_addr_t* ping_addr)
{
ping_target = ping_addr;
#if PING_USE_SOCKETS
sys_thread_new("ping_thread", ping_thread, NULL, DEFAULT_THREAD_STACKSIZE, DEFAULT_THREAD_PRIO);
#else /* PING_USE_SOCKETS */
ping_raw_init();
#endif /* PING_USE_SOCKETS */
}
#endif /* LWIP_RAW */
#ifndef LWIP_PING_H
#define LWIP_PING_H
#include "lwip/ip_addr.h"
/**
* PING_USE_SOCKETS: Set to 1 to use sockets, otherwise the raw api is used
*/
#ifndef PING_USE_SOCKETS
#define PING_USE_SOCKETS LWIP_SOCKET
#endif
void ping_init(const ip_addr_t* ping_addr);
#if !PING_USE_SOCKETS
void ping_send_now(void);
#endif /* !PING_USE_SOCKETS */
#endif /* LWIP_PING_H */
如下是帶OS的測試
ip_addr_t ping_addr;
IP4_ADDR(&ping_addr, 192, 168, 1, 9);
ping_init(&ping_addr);
打印如下,(這里printf不支持某些格式所以IP地址打印顯示不對)
四. 總結
了解ICMP包的格式,了解LWIP發(fā)送ping和對堆ping請求響應的過程。
審核編輯 黃宇
-
以太網
+關注
關注
40文章
5423瀏覽量
171683 -
ICMP
+關注
關注
0文章
52瀏覽量
14928 -
Ping
+關注
關注
0文章
69瀏覽量
15977 -
LwIP
+關注
關注
2文章
86瀏覽量
27166
發(fā)布評論請先 登錄
相關推薦
評論