ICMP报文分类
常见ICMP报文类型如下,差错报文和查询报文
ICMP报文种类 | 具体类型 | 功能 |
---|---|---|
差错报文 | 3 | 目的站不可达 |
4 | 源站抑制 | |
5 | 重定向(改变路由) | |
11 | 数据报超时 | |
12 | 数据报参数错误 | |
查询报文 | 8或0 | 回送请求或回送应答 |
10或9 | 路由查询和通告 | |
13或14 | 时间戳请求或回答 | |
15或16 | 信息请求或回答 | |
17或18 | 地址掩码请求或回答 |
差错报文中
在目的不可达(类型3)下有如下不可达原因(位于ICMP代码字段)
代码字段取值 | 含义 |
---|---|
0 | 网络不可达 |
1 | 主机不可达 |
2 | 协议不可达 |
3 | 端口不可达 |
4 | 需要分片但不分配被置位 |
5 | 源路由失败 |
6 | 目的网络未知 |
7 | 目的主机未知 |
在数据报超时(类型11)下有如下超时原因(位于ICMP代码字段)
代码字段取值 | 含义 |
---|---|
0 | 生存时间计数器超时 |
1 | 分片重装超时 |
查询报文中
主要就是回送请求和回送应答
在icmp.c/h
中实现相关数据结构和函数
数据结构
icmp.h
中
上面定义了icmp数据类型、首部结构、icmp函数声明
icmp发送处理
看icmp.c
这个宏定义了如上解释
上面发送差错保温实际都是调用icmp_send_response
,如下
/**
* Send an icmp packet in response to an incoming packet.
*
* @param p the input packet for which the 'unreachable' should be sent,
* p->payload pointing to the IP header
* @param type Type of the ICMP header
* @param code Code of the ICMP header
*/
static void
icmp_send_response(struct pbuf *p, u8_t type, u8_t code)
{
struct pbuf *q;
struct ip_hdr *iphdr;
/* we can use the echo header here */
struct icmp_echo_hdr *icmphdr;//定义icmp数据结构指针
ip_addr_t iphdr_src;//源ip
//为差错报文申请pbuf,pbuf预留以太网首部和ip首部,申请数据长度为icmp首部长度+icmp数据长度(ip首部长度+8)
/* ICMP header + IP header + 8 bytes of data */
q = pbuf_alloc(PBUF_IP, sizeof(struct icmp_echo_hdr) + IP_HLEN + ICMP_DEST_UNREACH_DATASIZE,
PBUF_RAM);
if (q == NULL) {
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_time_exceeded: failed to allocate pbuf for ICMP packet.\n"));
return;
}
LWIP_ASSERT("check that first pbuf can hold icmp message",
(q->len >= (sizeof(struct icmp_echo_hdr) + IP_HLEN + ICMP_DEST_UNREACH_DATASIZE)));
iphdr = (struct ip_hdr *)p->payload;//指向引起差错ip首部
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_time_exceeded from "));
ip_addr_debug_print(ICMP_DEBUG, &(iphdr->src));
LWIP_DEBUGF(ICMP_DEBUG, (" to "));
ip_addr_debug_print(ICMP_DEBUG, &(iphdr->dest));
LWIP_DEBUGF(ICMP_DEBUG, ("\n"));
icmphdr = (struct icmp_echo_hdr *)q->payload;//指向带填写的icmp首部
icmphdr->type = type;//填写icmp类型字段
icmphdr->code = code;//填写icmp代码字段
icmphdr->id = 0;
icmphdr->seqno = 0;//剩余置0
/* copy fields from original packet *///把引起差错ip数据报的ip首部和后续8字节拷贝到icmp数据报文中
SMEMCPY((u8_t *)q->payload + sizeof(struct icmp_echo_hdr), (u8_t *)p->payload,
IP_HLEN + ICMP_DEST_UNREACH_DATASIZE);
/* calculate checksum */
icmphdr->chksum = 0;
icmphdr->chksum = inet_chksum(icmphdr, q->len);//计算校验和
ICMP_STATS_INC(icmp.xmit);
/* increase number of messages attempted to send */
snmp_inc_icmpoutmsgs();
/* increase number of destination unreachable messages attempted to send */
snmp_inc_icmpouttimeexcds();
ip_addr_copy(iphdr_src, iphdr->src);//获得源ip(即待发送的目的ip)
ip_output(q, NULL, &iphdr_src, ICMP_TTL, 0, IP_PROTO_ICMP);//调用ip层输出函数发送出去
pbuf_free(q);//释放icmp pbuf
}
icmp接收处理
目前lwip(1.4.1)只处理icmp回送请求报文,当ip层收到数据,根据协议类型得知是icmp会调用icmp.c
中的icmp_input
处理
/**
* Processes ICMP input packets, called from ip_input().
*
* Currently only processes icmp echo requests and sends
* out the echo response.
*
* @param p the icmp echo request packet, p->payload pointing to the ip header
* @param inp the netif on which this packet was received
*/
void icmp_input(struct pbuf *p, struct netif *inp)
{
u8_t type;
#ifdef LWIP_DEBUG
u8_t code;
#endif /* LWIP_DEBUG */
struct icmp_echo_hdr *iecho;
struct ip_hdr *iphdr;
s16_t hlen;
ICMP_STATS_INC(icmp.recv);
snmp_inc_icmpinmsgs();
iphdr = (struct ip_hdr *)p->payload;//指向pbuf数据中ip首部
hlen = IPH_HL(iphdr) * 4;//获得ip首部长度
if (pbuf_header(p, -hlen) || (p->tot_len < sizeof(u16_t)*2)) {
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: short ICMP (%"U16_F" bytes) received\n", p->tot_len));
goto lenerr;//调整payload到icmp首部,调整失败或者icmp首部小于4字节则释放pbuf返回
}
type = *((u8_t *)p->payload);//获得icmp类型字段
#ifdef LWIP_DEBUG
code = *(((u8_t *)p->payload)+1);//获得icmp代码字段
#endif /* LWIP_DEBUG */
switch (type) {
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). */
break;
case ICMP_ECHO://回送请求
#if !LWIP_MULTICAST_PING || !LWIP_BROADCAST_PING
{
int accepted = 1;//标志是否要发生回送应答,1发送 0不发
#if !LWIP_MULTICAST_PING
/* multicast destination address? *///目的地址为多播则不发
if (ip_addr_ismulticast(¤t_iphdr_dest)) {
accepted = 0;
}
#endif /* LWIP_MULTICAST_PING */
#if !LWIP_BROADCAST_PING
/* broadcast destination address? *///目的地址为广播则不发
if (ip_addr_isbroadcast(¤t_iphdr_dest, inp)) {
accepted = 0;
}
#endif /* LWIP_BROADCAST_PING */
/* broadcast or multicast destination address not acceptd? */
if (!accepted) {
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: Not echoing to multicast or broadcast pings\n"));
ICMP_STATS_INC(icmp.err);
pbuf_free(p);
return;
}
}
#endif /* !LWIP_MULTICAST_PING || !LWIP_BROADCAST_PING */
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: ping\n"));
if (p->tot_len < sizeof(struct icmp_echo_hdr)) {//报文长度比icmp首部还小,释放返回
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: bad ICMP echo received\n"));
goto lenerr;
}
if (inet_chksum_pbuf(p) != 0) {//校验和判断
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: checksum failed for received ICMP echo\n"));
pbuf_free(p);
ICMP_STATS_INC(icmp.chkerr);
snmp_inc_icmpinerrors();
return;
}
#if LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN
if (pbuf_header(p, (PBUF_IP_HLEN + PBUF_LINK_HLEN))) {
/* p is not big enough to contain link headers
* allocate a new one and copy p into it
*/
struct pbuf *r;
/* switch p->payload to ip header */
if (pbuf_header(p, hlen)) {
LWIP_ASSERT("icmp_input: moving p->payload to ip header failed\n", 0);
goto memerr;
}
/* allocate new packet buffer with space for link headers */
r = pbuf_alloc(PBUF_LINK, p->tot_len, PBUF_RAM);
if (r == NULL) {
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: allocating new pbuf failed\n"));
goto memerr;
}
LWIP_ASSERT("check that first pbuf can hold struct the ICMP header",
(r->len >= hlen + sizeof(struct icmp_echo_hdr)));
/* copy the whole packet including ip header */
if (pbuf_copy(r, p) != ERR_OK) {
LWIP_ASSERT("icmp_input: copying to new pbuf failed\n", 0);
goto memerr;
}
iphdr = (struct ip_hdr *)r->payload;
/* switch r->payload back to icmp header */
if (pbuf_header(r, -hlen)) {
LWIP_ASSERT("icmp_input: restoring original p->payload failed\n", 0);
goto memerr;
}
/* free the original p */
pbuf_free(p);
/* we now have an identical copy of p that has room for link headers */
p = r;
} else {
/* restore p->payload to point to icmp header */
if (pbuf_header(p, -(s16_t)(PBUF_IP_HLEN + PBUF_LINK_HLEN))) {
LWIP_ASSERT("icmp_input: restoring original p->payload failed\n", 0);
goto memerr;
}
}
#endif /* LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN */
/* At this point, all checks are OK. *///到这里检查完毕,直接修改接收到的pbuf来作为回送应答
/* We generate an answer by switching the dest and src ip addresses,
* setting the icmp type to ECHO_RESPONSE and updating the checksum. */
iecho = (struct icmp_echo_hdr *)p->payload;//只想请求报文icmp首部
ip_addr_copy(iphdr->src, *ip_current_dest_addr());//用目的ip来修改源ip
ip_addr_copy(iphdr->dest, *ip_current_src_addr());//用源ip来修改目的ip
ICMPH_TYPE_SET(iecho, ICMP_ER);//修改icmp类型为回送应答
#if CHECKSUM_GEN_ICMP
/* adjust the checksum *///调整icmp校验和
if (iecho->chksum >= PP_HTONS(0xffffU - (ICMP_ECHO << 8))) {
iecho->chksum += PP_HTONS(ICMP_ECHO << 8) + 1;
} else {
iecho->chksum += PP_HTONS(ICMP_ECHO << 8);
}
#else /* CHECKSUM_GEN_ICMP */
iecho->chksum = 0;
#endif /* CHECKSUM_GEN_ICMP */
/* Set the correct TTL and recalculate the header checksum. */
IPH_TTL_SET(iphdr, ICMP_TTL);//设置ip首部ttl字段
IPH_CHKSUM_SET(iphdr, 0);
#if CHECKSUM_GEN_IP
IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, IP_HLEN));//计算ip首部校验和
#endif /* CHECKSUM_GEN_IP */
ICMP_STATS_INC(icmp.xmit);
/* increase number of messages attempted to send */
snmp_inc_icmpoutmsgs();
/* increase number of echo replies attempted to send */
snmp_inc_icmpoutechoreps();
if(pbuf_header(p, hlen)) {//调整payload到ip首部
LWIP_ASSERT("Can't move over header in packet", 0);
} else {
err_t ret;
/* send an ICMP packet, src addr is the dest addr of the curren packet */
ret = ip_output_if(p, ip_current_dest_addr(), IP_HDRINCL,
ICMP_TTL, 0, IP_PROTO_ICMP, inp);
if (ret != ERR_OK) {//调用ip层函数发送出去,并设置IP_HDRINCL表示ip首部字段已填好
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: ip_output_if returned an error: %c.\n", ret));
}
}
break;
default://其他类型直接释放返回
LWIP_DEBUGF(ICMP_DEBUG, ("icmp_input: ICMP type %"S16_F" code %"S16_F" not supported.\n",
(s16_t)type, (s16_t)code));
ICMP_STATS_INC(icmp.proterr);
ICMP_STATS_INC(icmp.drop);
}
pbuf_free(p);
return;
lenerr:
pbuf_free(p);
ICMP_STATS_INC(icmp.lenerr);
snmp_inc_icmpinerrors();
return;
#if LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN
memerr:
pbuf_free(p);
ICMP_STATS_INC(icmp.err);
snmp_inc_icmpinerrors();
return;
#endif /* LWIP_ICMP_ECHO_CHECK_INPUT_PBUF_LEN */
}
ip_current_src_addr
是在ip.h
中宏定义,表示当前正在处理的ip数据报源目ip
实现ping
在ip.c
中ip层输入函数ip_input
中对每个数据报都会调用raw_input
,这里为用户直接处理ip数据报提供了方式,lwip把这种方式称作原始协议控制块(raw_pcb)机制,类似原始套接字。
在raw.h
中,定义了控制块结构
IP_PCB
在ip.h
中
原始协议控制块(raw_pcb)机制原理
内核会用控制块结构raw_pcb
构建一条全局链表raw_pcbs
,每一个raw_pcb
可以定制特定协议的ip数据报、ip地址、执行函数等,当收到的ip数据报中ip地址和协议字段和某个raw_pcb
吻合时就调用回调函数。其中为ip数据报寻找匹配的raw_pcb
就是raw_input
如下
/** The list of RAW PCBs *///内核中原始协议控制快全局链表
static struct raw_pcb *raw_pcbs;
/**
* Determine if in incoming IP packet is covered by a RAW PCB
* and if so, pass it to a user-provided receive callback function.
*
* Given an incoming IP datagram (as a chain of pbufs) this function
* finds a corresponding RAW PCB and calls the corresponding receive
* callback function.
*
* @param p pbuf to be demultiplexed to a RAW PCB.
* @param inp network interface on which the datagram was received.
* @return - 1 if the packet has been eaten by a RAW PCB receive
* callback function. The caller MAY NOT not reference the
* packet any longer, and MAY NOT call pbuf_free().
* @return - 0 if packet is not eaten (pbuf is still referenced by the
* caller).
*返回0表示 p被处理并释放,协议栈不需再处理 返回1表示p未被处理,继续交给协议栈上层处理
*/
u8_t raw_input(struct pbuf *p, struct netif *inp)
{
struct raw_pcb *pcb, *prev;
struct ip_hdr *iphdr;
s16_t proto;
u8_t eaten = 0;//raw_input返回值
LWIP_UNUSED_ARG(inp);
iphdr = (struct ip_hdr *)p->payload;//指向ip首部
proto = IPH_PROTO(iphdr);//取出协议字段
prev = NULL;
pcb = raw_pcbs;//指向raw_pcbs链表头
/* loop through all raw pcbs until the packet is eaten by one */
/* this allows multiple pcbs to match against the packet by design */
while ((eaten == 0) && (pcb != NULL)) {//遍历链表
if ((pcb->protocol == proto) && //协议字段匹配且目的ip吻合
(ip_addr_isany(&pcb->local_ip) ||
ip_addr_cmp(&(pcb->local_ip), ¤t_iphdr_dest))) {
#if IP_SOF_BROADCAST_RECV
/* broadcast filter? */
if (ip_get_option(pcb, SOF_BROADCAST) || !ip_addr_isbroadcast(¤t_iphdr_dest, inp))
#endif /* IP_SOF_BROADCAST_RECV */
{
/* receive callback function available? */
if (pcb->recv != NULL) {
/* the receive callback function did not eat the packet? */
if (pcb->recv(pcb->recv_arg, pcb, p, ip_current_src_addr()) != 0) {
/* receive function ate the packet */
p = NULL;
eaten = 1;//ip数据报被已处理
if (prev != NULL) {//找到匹配的pcbs不再链表头则把他移到链表头缩短下次找到时间
/* move the pcb to the front of raw_pcbs so that is
found faster next time */
prev->next = pcb->next;
pcb->next = raw_pcbs;
raw_pcbs = pcb;
}
}
}
/* no receive callback function was set for this raw PCB */
}
/* drop the packet */
}
prev = pcb;
pcb = pcb->next;
}
return eaten;
}
了解了上面内容,可以这样实现ping:定制一个icmp类型控制块把他加入到raw_pcbs
中,其中回调函数用于处理接收到的回送响应,计算时间差。
/**
* This is an example of a "ping" sender (with raw API).
* It can be used as a start point to maintain opened a network connection, or
* like a network "watchdog" for your device.
*
*/
#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/timers.h"
#include "lwip/inet_chksum.h"
/**
* PING_DEBUG: Enable debugging for PING.
*/
#ifndef PING_DEBUG
#define PING_DEBUG LWIP_DBG_ON
#endif
/** ping target - should be a "ip_addr_t" */
#ifndef PING_TARGET
#define PING_TARGET (netif_default?netif_default->gw:ip_addr_any)
#endif
/** ping receive timeout - in milliseconds */
#ifndef PING_RCV_TIMEO
#define PING_RCV_TIMEO 1000
#endif
/** ping delay - in milliseconds *///ping请求发送间隔
#ifndef PING_DELAY
#define PING_DELAY 1000
#endif
/** ping identifier - must fit on a u16_t *///设置自定义icmp标识符
#ifndef PING_ID
#define PING_ID 0xAFAF
#endif
/** ping additional data size to include in the packet *///icmp数据长度
#ifndef PING_DATA_SIZE
#define PING_DATA_SIZE 32
#endif
/** ping result action - no default action */
#ifndef PING_RESULT
#define PING_RESULT(ping_ok) //if(ping_ok) printf("ping [%u] OK: time=[%u]ms\n",ping_seq_num, sys_now()-ping_time)
#endif
/* ping variables */
static u16_t ping_seq_num;//icmp首部序号字段
static u32_t ping_time;//ping往返时间
static struct raw_pcb *ping_pcb = NULL;//ping的控制块
static ip_addr_t ping_dst;//ping目的ip
/** 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 = 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);
}
/* 接收到icmp回送响应 */
static u8_t ping_recv(void *arg, struct raw_pcb *pcb, struct pbuf *p, 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);
//we can also check src ip here, but just egnore it
if ((p->tot_len >= (PBUF_IP_HLEN + sizeof(struct icmp_echo_hdr))))
{
iecho = (struct icmp_echo_hdr *)((u8_t*)p->payload + PBUF_IP_HLEN);
if ((iecho->type == ICMP_ER) && (iecho->id == PING_ID) && (iecho->seqno == htons(ping_seq_num)))
{//判断类型、标识符、序号
LWIP_DEBUGF( PING_DEBUG, ("ping: recv "));
ip_addr_debug_print(PING_DEBUG, addr);
LWIP_DEBUGF( PING_DEBUG, (" time=%"U32_F" ms\n", (sys_now()-ping_time)));
//计算往返时间
/* do some ping result processing */
PING_RESULT(1);
pbuf_free(p);
return 1; /* eat the packet */
}
}
return 0; /* don't eat the packet */
}
static void ping_send(struct raw_pcb *raw, 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_ASSERT("ping_size <= 0xffff", ping_size <= 0xffff);
p = pbuf_alloc(PBUF_IP, (u16_t)ping_size, PBUF_RAM);
if (!p) {
return;
}
//一个pbuf
if ((p->len == p->tot_len) && (p->next == NULL)) {
iecho = (struct icmp_echo_hdr *)p->payload;
//填写icmp首部
ping_prepare_echo(iecho, (u16_t)ping_size);
//发送数据包,最终是调用ip_output_if
raw_sendto(raw, p, addr);
ping_time = sys_now();//记下发生时间
LWIP_DEBUGF(PING_DEBUG, ("ping:[%"U32_F"] send ", ping_seq_num));
ip_addr_debug_print(PING_DEBUG, addr);
LWIP_DEBUGF( PING_DEBUG, ("\n"));
}
pbuf_free(p);//释放pbuf
}
//内核超时后调用次函数
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_dst);//发送回送请求
sys_timeout(PING_DELAY, ping_timeout, pcb);//再次注册超时事件
}
static void ping_raw_init(void)
{
ping_pcb = raw_new(IP_PROTO_ICMP);//申请一个icmp类型pcb
LWIP_ASSERT("ping_pcb != NULL", ping_pcb != NULL);
raw_recv(ping_pcb, ping_recv, NULL);//注册recv回调函数为ping_recv
raw_bind(ping_pcb, IP_ADDR_ANY);//绑定ip为0(本机任一端口)
//设置超时事件PING_DELAY后ping_timeout被内核调用,回调参数为ping_pcb
sys_timeout(PING_DELAY, ping_timeout, ping_pcb);
}
void ping_init(void)//ping初始化
{
IP4_ADDR(&ping_dst, 192,168,1,103);//ping ip
ping_raw_init();//
}
#endif /* LWIP_RAW */