本文转自公众号欢迎关注
https://mp.weixin.qq.com/s/6MTNop3zBKdQ-gabbWo63Q
一. 前言
ICMP即Internet Control Message Protocol因特网控制消息协议。
ICMP是网络层协议,IP不可分割的一部分。
ICMP用于报告数据报处理中的错误,比如以下情况下时发送ICMP消息:当数据报无法到达其目的地时,当网关没有转发数据报的缓冲能力时,以及当网关可以指示主机在较短的路由上发送数据时。
互联网协议的设计并不是绝对可靠的。ICMP这些控制消息的目的是提供有关通信环境中问题的反馈,而不是使IP可靠。不能确保数据报的传递或控制消息的返回,一些数据报可能无法送达时也没有任何丢失报告。如果需要可靠的通信,则使用IP的更高级别协议必须实现其自己的可靠性程序。
ICMP消息通常报告数据报处理中的错误,且不发送关于ICMP消息的ICMP消息,否则消息会无限递归。此外,ICMP消息只发送关于处理分段数据报的零分段时的错误。(片段零的片段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由首部和数据两部分组成,如下
区域 | 类型Type | 代码Code | 校验和Checksum |
---|---|---|---|
字节大小 | 1 | 1 | 2 |
区域 | 首部数据,根据类型不一样而不一样,对于ping拆为了16位的ID和16位的序列号 | ||
字节大小 | 4 | ||
区域 | ICMP数据部分 | ||
字节大小 | 类型不同长度不同 |
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——优先中止生效 | 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字节数据拆分成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字节。
然后检查checksum
最后调用ip4_output_if发送响应包。
3.4输出数据流
icmp_dest_unreach
icmp_time_exceeded
都是调用
icmp_send_response
3.5发送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
发送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发送ping和对堆ping请求响应的过程。
审核编辑 黄宇
-
以太网
+关注
关注
40文章
5421浏览量
171662 -
ICMP
+关注
关注
0文章
52浏览量
14928 -
Ping
+关注
关注
0文章
69浏览量
15977 -
LwIP
+关注
关注
2文章
86浏览量
27164
发布评论请先 登录
相关推荐
评论