作者:陈广
日期:2016-03-22
看来看去,先从ICMPv6开始吧,相对简单些。ICMPv6标准文档是RFC4443,有中文版,请从本日志最下方链接下载。
ICMPv6 由 IPv6 节点使用,用于报告在分组处理过程中出现的错误,以及执行其他网络互连层功能,诸如诊断ICMPv6“ping”)。ICMPv6 是 IPv6 的整体部分,是基础协议,RFC4443标准规定的所有消息和行为每个 IPv6 节点必须无条件执行。 先来看看ICMPv6的消息通用格式:
类型字段:指出消息类型,它的值决定其余数据格式。
ICMPv6 消息分为两类:出错消息和指示消息。出错消息由在它们的消息 Type 字段的二进制值的高阶位取 0 标识。那么,出错消息的消息类型值从 0 到 127;指示消息的消息类型值从 128 到 255。
先从出错消息开始讲,打开\core\net\ipv6\uip-icmp6.h头文件,看看相应的出错消息宏:
#define ICMP6_DST_UNREACH 1 /**< dest unreachable */
#define ICMP6_PACKET_TOO_BIG 2 /**< packet too big */
#define ICMP6_TIME_EXCEEDED 3 /**< time exceeded */
#define ICMP6_PARAM_PROB 4 /**< ip6 header bad */
消息解释:
#define ICMP6_ECHO_REQUEST 128 /**< Echo request */
#define ICMP6_ECHO_REPLY 129 /**< Echo reply */
#define ICMP6_RS 133 /**< Router Solicitation */
#define ICMP6_RA 134 /**< Router Advertisement */
#define ICMP6_NS 135 /**< Neighbor Solicitation */
#define ICMP6_NA 136 /**< Neighbor advertisement */
#define ICMP6_REDIRECT 137 /**< Redirect */
#define ICMP6_RPL 155 /**< RPL */
#define ICMP6_PRIV_EXP_100 100 /**< Private Experimentation */
#define ICMP6_PRIV_EXP_101 101 /**< Private Experimentation */
#define ICMP6_PRIV_EXP_200 200 /**< Private Experimentation */
#define ICMP6_PRIV_EXP_201 201 /**< Private Experimentation */
#define ICMP6_ROLL_TM ICMP6_PRIV_EXP_200 /**< ROLL Trickle Multicast */
这里面,100、101属出错消息,用于私有试验,200、201也用于私有试验。看上面最后一行代码,200用于滚流组播试验了。 128. 回显请求 129. 回显响应 下面这几个消息在RFC4443中未见定义,但程序中却有声明,也可在RFC4861中找到 133. 路由请求 134. 路由广告 135. 邻居请求 136. 邻居广告 137. 重定向 155. RPL
代码字段:依赖于消息类型。它用于生成消息粒度的附加层。
下面依次讨论Type为出错消息时的代码字段
/*name ICMPv6 Destination Unreachable message codes*/
#define ICMP6_DST_UNREACH_NOROUTE 0 /**< no route to destination */
#define ICMP6_DST_UNREACH_ADMIN 1 /**< administratively prohibited */
#define ICMP6_DST_UNREACH_NOTNEIGHBOR 2 /**< not a neighbor(obsolete) */
#define ICMP6_DST_UNREACH_BEYONDSCOPE 2 /**< beyond scope of source address */
#define ICMP6_DST_UNREACH_ADDR 3 /**< address unreachable */
#define ICMP6_DST_UNREACH_NOPORT 4 /**< port unreachable */
Code值:
可以看到,值5和6在Contiki中没有实现
上图中的未使用字段(Unused)占32bit空间,此字段未用于所有代码值。它必须被发起者初始化为 0,被接收者忽略。
Code值:由发起者设置为 0(零),被接收者忽略。
MTU:下一跳链路的MTU。
最大传输单元(Maximum Transmission Unit,MTU)是指一种通信协议的某一层上面所能通过的最大数据包大小(以字节为单位)。
/** \name ICMPv6 Time Exceeded message codes*/
/** @{ */
#define ICMP6_TIME_EXCEED_TRANSIT 0 /**< ttl==0 in transit */
#define ICMP6_TIME_EXCEED_REASSEMBLY 1 /**< ttl==0 in reass */
/** @} */
Code值:
未使用字段(Unused):此字段未用于所有代码值。它必须被发起者初始化为 0,被接收者忽略。
/** \name ICMPv6 Parameter Problem message codes*/
/** @{ */
#define ICMP6_PARAMPROB_HEADER 0 /**< erroneous header field */
#define ICMP6_PARAMPROB_NEXTHEADER 1 /**< unrecognized next header */
#define ICMP6_PARAMPROB_OPTION 2 /**< unrecognized option */
/** @} */
Code值:
指示器(Pointer):
标识调用分组内的字节偏移,在那里检测到出错。如果出错中的此字段超过 ICMPv6 出错消息的最大长度,指示器将指出超出 ICMPv6 分组的末端。
接下来讨论ICMPv6指示消息:
消息结构和上图一模一样。
下面是uip-icmp6.h的剩余部分代码:
/*回显请求固定部分长度,4字节 */
#define UIP_ICMP6_ECHO_REQUEST_LEN 4
/*ICMPv6错误消息固定部分长度 */
#define UIP_ICMP6_ERROR_LEN 4
/*ICMPv6 错误消息固定部分 */
typedef struct uip_icmp6_error
{
uint32_t param;
} uip_icmp6_error;
/** \name ICMPv6 RFC4443 消息处理和发送 */
/** @{ */
/**
* \brief 发送一个icmpv6错误消息
* \param type 出错消息的type
* \param code 出错消息的code
* \param param 32 bit出错消息参数, 依赖于错误的语义
*/
void
uip_icmp6_error_output(uint8_t type, uint8_t code, uint32_t param);
/**
* \brief 发送一个icmpv6消息
* \param dest 发送消息的目的地地址
* \param type 消息的type
* \param code 消息的code
* \param payload_len 有效载荷大小
*/
void
uip_icmp6_send(const uip_ipaddr_t *dest, int type, int code, int payload_len);
//回显应答回调函数
typedef void (* uip_icmp6_echo_reply_callback_t)(uip_ipaddr_t *source,
uint8_t ttl,
uint8_t *data,
uint16_t datalen);
//回显应答通知,看来这些通知是用链表连起来的
struct uip_icmp6_echo_reply_notification
{
struct uip_icmp6_echo_reply_notification *next;
uip_icmp6_echo_reply_callback_t callback;
};
/**
* \brief 为ping应答添加一个回调函数
* \param n 一个uip_icmp6_echo_reply_notification结构体,必须由调用者分配
* \param c 一个回调函数指针
*
* 当收到ICMP回显应答(ping应答)时,调用此函数添加一个回
* 调函数到回调函数链表中。它用于执行ping协议:当使用
* uip_icmp6_send发送一个ping包后,为ping应答附加一个回调函数。
*
* 调用者必须静态地分配一个uip_icmp6_echo_reply_notification
* 结构体以保存回调函数的内部状态
*
* 当收到ping应答包,所有注册的回调函数都会被调用。回调函数必须
* 保证不会uIP缓冲的内容。
*/
void
uip_icmp6_echo_reply_callback_add(struct uip_icmp6_echo_reply_notification *n,
uip_icmp6_echo_reply_callback_t c);
/**
* \brief 为ping应答移除一个回调函数
* \param n 一个uip_icmp6_echo_reply_notification结构体,之前必须已经通过
* uip_icmp6_echo_reply_callback_add()函数添加
*
* 此函数从回调函数链表中删除一个回调函数。当收到一个ICMP应答
* (ping reply)时调用
*/
void
uip_icmp6_echo_reply_callback_rm(struct uip_icmp6_echo_reply_notification *n);
/* 通用 ICMPv6 输入管理者 */
typedef struct uip_icmp6_input_handler
{
struct uip_icmp6_input_handler *next;
uint8_t type;
uint8_t icode;
void (*handler)(void);
} uip_icmp6_input_handler_t;
//用于下面的uip_icmp6_input()函数的返回值
#define UIP_ICMP6_INPUT_SUCCESS 0
#define UIP_ICMP6_INPUT_ERROR 1
#define UIP_ICMP6_HANDLER_CODE_ANY 0xFF /* 代表某type的所有code */
/*
* 初始化一个 uip_icmp6_input_handler类型变量,用于稍后的
* uip_icmp6_register_input_handler()函数的参数
*
* 此变量中的函数指针将被调用并用于处理接收到的ICMPv6数据报中指定的type/code
*
* 如果code值为UIP_ICMP6_HANDLER_CODE_ANY,函数将处理type所对应的所有code。
* 就是说,ICMPv6消息的code是无关的
*/
#define UIP_ICMP6_HANDLER(name, type, code, func) \
static uip_icmp6_input_handler_t name = { NULL, type, code, func }
/**
* \brief 处理一个接收到的ICMPv6消息
* \param type ICMPv6消息的type
* \param icode ICMPv6消息的code
* \return 成功: UIP_ICMP6_INPUT_SUCCESS, 失败: UIP_ICMP6_INPUT_ERROR
*
* 未知ICMPv6类型的通用管理者。它将寻找能处理此消息类型的注册函数。此函数必须
* 首先被uip_icmp6_register_input_handler注册过,它负责设置uip_len 为0,否则
* uIPv6内核将试图发送UIP_IP_BUF中的任何内容
*
* 返回一个UIP_ICMP6_INPUT_ERROR意味着管理者无法被调用。多数是因为ICMPv6 type
* 没有附着一个有效的管理者
*
* 返回一个UIP_ICMP6_INPUT_SUCCESS意味着已经为ICMPv6 type找到一个管理者,并已
* 调用。此函数不会提供任何有关管理者是否成功完成任务的指示。
*/
uint8_t uip_icmp6_input(uint8_t type, uint8_t icode);
/**
* \brief 注册一个可以处理指定ICMPv6消息type的管理者
* \param handler 一个指向管理者的指针
*/
void uip_icmp6_register_input_handler(uip_icmp6_input_handler_t *handler);
/**
* \brief 初始化uIP ICMPv6内核
*/
void uip_icmp6_init(void);
打开uip-icmp6.c文件
先来看看各类缓冲:
//uIP缓冲,默认长度40字节
#define UIP_IP_BUF
((struct uip_ip_hdr *)&uip_buf[UIP_LLH_LEN])
//ICMP缓冲,默认长度,IPv6的情况下是8字节
#define UIP_ICMP_BUF
((struct uip_icmp_hdr *)&uip_buf[uip_l2_l3_hdr_len])
//ICMP错误信息缓冲,默认长度,IPv6的情况下是4字节
#define UIP_ICMP6_ERROR_BUF
((struct uip_icmp6_error *)&uip_buf[uip_l2_l3_icmp_hdr_len])
//uIP扩展缓冲,默认长度2
#define UIP_EXT_BUF
((struct uip_ext_hdr *)&uip_buf[uip_l2_l3_hdr_len])
//uIP首个扩展缓冲,默认长度2
#define UIP_FIRST_EXT_BUF
((struct uip_ext_hdr *)&uip_buf[UIP_LLIPH_LEN])
这缓冲,相对于rime中的packetbuf,清爽太多了,这才是高手的设计。画张图,这缓冲就清清楚楚了。
上面所有的缓冲,实际都指向同一段内存uip_buf,只是指向的是uip_buf的不同地方。图上都已经标出来了,有这张图,下面的代码就容易读了。
LIST(echo_reply_callback_list);
回显应答回调函数链表
LIST(input_handler_list);
输入管理者链表
static uip_icmp6_input_handler_t *
input_handler_lookup(uint8_t type, uint8_t icode)
{
uip_icmp6_input_handler_t *handler = NULL;
for(handler = list_head(input_handler_list);
handler != NULL;
handler = list_item_next(handler))
{
if(handler->type == type &&
(handler->icode == icode ||
handler->icode == UIP_ICMP6_HANDLER_CODE_ANY))
{
return handler;
}
}
return NULL;
}
在链表中查找类型为type,代码为icode的输入管理者。需要注意的是如果icode为UIP_ICMP6_HANDLER_CODE_ANY时,也算符合条件。
uint8_t uip_icmp6_input(uint8_t type, uint8_t icode)
{
uip_icmp6_input_handler_t *handler = input_handler_lookup(type, icode);
//链表没有相应的输入管理者,失败
if(handler == NULL)
{
return UIP_ICMP6_INPUT_ERROR;
}
//输入管理者没有相应管理函数,失败
if(handler->handler == NULL)
{
return UIP_ICMP6_INPUT_ERROR;
}
//调用管理函数
handler->handler();
return UIP_ICMP6_INPUT_SUCCESS;
}
上篇日志对此函数有详细注释,请参考。所谓管理者,就是每个type和code所对应的处理函数。应该翻译为处理机更合适些,但管理者更好记,好听。
void
uip_icmp6_register_input_handler(uip_icmp6_input_handler_t *handler)
{
list_add(input_handler_list, handler);
}
注册一个输入管理者,实际上就是加入链表。
static void
echo_request_input(void)
{
#if UIP_CONF_IPV6_RPL
uint8_t temp_ext_len;
#endif /* UIP_CONF_IPV6_RPL */
/* 我们发送一个回显应答。通常在请求中没有扩展首部,如果有的话就需
* 要移除扩展首部并改变一些域。*/
PRINTF("Received Echo Request from");
PRINT6ADDR(&UIP_IP_BUF->srcipaddr);
PRINTF("to");
PRINT6ADDR(&UIP_IP_BUF->destipaddr);
PRINTF("\n");
/* 收到回显请求后,先构造IP首部 */
UIP_IP_BUF->ttl = uip_ds6_if.cur_hop_limit;
//如果收到的是一个组播地址,必须要改成单播地址发回
if(uip_is_addr_mcast(&UIP_IP_BUF->destipaddr))
{ //将源地址拷贝到目的地址,准备回发
uip_ipaddr_copy(&UIP_IP_BUF->destipaddr, &UIP_IP_BUF->srcipaddr);
//现在这两个地址指向同一地址,这句有何用?看到ds6才知道
uip_ds6_select_src(&UIP_IP_BUF->srcipaddr, &UIP_IP_BUF->destipaddr);
}
else
{//不是组播地址,则交换源地址和目的地址即可
uip_ipaddr_copy(&tmp_ipaddr, &UIP_IP_BUF->srcipaddr);
uip_ipaddr_copy(&UIP_IP_BUF->srcipaddr, &UIP_IP_BUF->destipaddr);
uip_ipaddr_copy(&UIP_IP_BUF->destipaddr, &tmp_ipaddr);
}
//如果存在扩展首部
if(uip_ext_len > 0)
{
#if UIP_CONF_IPV6_RPL
//如果是其它类型的扩展首部
if((temp_ext_len = rpl_invert_header()))
{
UIP_FIRST_EXT_BUF->next = UIP_PROTO_ICMP6;
if (uip_ext_len != temp_ext_len)
{
uip_len -= (uip_ext_len - temp_ext_len);
//这两句代码是把一个16位数字压进两个8位字节
UIP_IP_BUF->len[0] = ((uip_len - UIP_IPH_LEN) >> 8);
UIP_IP_BUF->len[1] = ((uip_len - UIP_IPH_LEN) & 0xff);
/*
* 移动回显请求的有效载荷(开始于icmp首部后之后)到应答新区域。
* 移动距离等于当前剩余扩展首部长度。
* 注意:UIP_ICMP_BUF现阶段仍然指向回显请求
*/
memmove((uint8_t *)UIP_ICMP_BUF + UIP_ICMPH_LEN - (uip_ext_len - temp_ext_len),
(uint8_t *)UIP_ICMP_BUF + UIP_ICMPH_LEN,
(uip_len - UIP_IPH_LEN - temp_ext_len - UIP_ICMPH_LEN));
}
uip_ext_len = temp_ext_len;
}
else
{
#endif /* UIP_CONF_IPV6_RPL */
/* 如果存在扩展首部,下面这段就是把扩展首部删除,下图的uip_ext_hdr部分*/
UIP_IP_BUF->proto = UIP_PROTO_ICMP6;
uip_len -= uip_ext_len;
UIP_IP_BUF->len[0] = ((uip_len - UIP_IPH_LEN) >> 8);
UIP_IP_BUF->len[1] = ((uip_len - UIP_IPH_LEN) & 0xff);
/*
* 移动回显请求的有效载荷(开始于icmp首部后之后)到应答新区域。
* 移动距离等于当前扩展首部长度。
* 注意: UIP_ICMP_BUF现阶段仍然指向回显请求
*/
memmove((uint8_t *)UIP_ICMP_BUF + UIP_ICMPH_LEN - uip_ext_len,
(uint8_t *)UIP_ICMP_BUF + UIP_ICMPH_LEN,
(uip_len - UIP_IPH_LEN - UIP_ICMPH_LEN));
uip_ext_len = 0;
#if UIP_CONF_IPV6_RPL
}
#endif /* UIP_CONF_IPV6_RPL */
}
/* 以下对于UIP_ICMP_BUF和校验和的正确性非常重要 */
/* 注意: 现在 UIP_ICMP_BUF指向回显应答的开始部分 */
UIP_ICMP_BUF->type = ICMP6_ECHO_REPLY;
UIP_ICMP_BUF->icode = 0;
UIP_ICMP_BUF->icmpchksum = 0;
UIP_ICMP_BUF->icmpchksum = ~uip_icmp6chksum();
PRINTF("Sending Echo Reply to");
PRINT6ADDR(&UIP_IP_BUF->destipaddr);
PRINTF("from");
PRINT6ADDR(&UIP_IP_BUF->srcipaddr);
PRINTF("\n");
//更改状态统计
UIP_STAT(++uip_stat.icmp.sent);
return;
}
为方便理解,得把首部图给画出来,ICMP6首部是被包在IPv6的数据部分的:
首先要明确,回显请求和应答的消息格式完全一样(参考上一篇日志)。收到回显请求后,消息存在于缓冲中,只需在原请求消息上直接做修改,然后发送回去即可。
这个函数的主要作用是在收到一个回显请求后,首先在收到数据的缓冲中调换源地址和目的地址。查看消息包是否带扩展首部,如果带则删除扩展首部。最后更改type和code和校验和。
此函数是输入管理者的回显请求处理函数
void uip_icmp6_error_output(uint8_t type, uint8_t code, uint32_t param)
{
/* 检查原始包是否不是一个ICMP出错包*/
if (uip_ext_len)
{
if(UIP_EXT_BUF->next == UIP_PROTO_ICMP6 && UIP_ICMP_BUF->type < 128)
{
uip_len = 0;
return;
}
}
else
{
if(UIP_IP_BUF->proto == UIP_PROTO_ICMP6 && UIP_ICMP_BUF->type < 128)
{
uip_len = 0;
return;
}
}
#if UIP_CONF_IPV6_RPL
uip_ext_len = rpl_invert_header();
#else /* UIP_CONF_IPV6_RPL */
uip_ext_len = 0;
#endif /* UIP_CONF_IPV6_RPL */
/* 在移动前记录原始包数据*/
uip_ipaddr_copy(&tmp_ipaddr, &UIP_IP_BUF->destipaddr);
//IP头40+ICMP头4+ICMP6_Error头4=48字节
uip_len += UIP_IPICMPH_LEN + UIP_ICMP6_ERROR_LEN;
//如果此值大于最大传输单元(MTU),则让它等于MTU
if(uip_len > UIP_LINK_MTU)
uip_len = UIP_LINK_MTU;
//这段看代码是把整段IP缓冲数据拷贝到ICMP6_ERROR之后,不明白啥意思
memmove((uint8_t *)UIP_ICMP6_ERROR_BUF + uip_ext_len + UIP_ICMP6_ERROR_LEN,
(void *)UIP_IP_BUF, uip_len - UIP_IPICMPH_LEN -
uip_ext_len - UIP_ICMP6_ERROR_LEN);
//版本号为6表示IPv6
UIP_IP_BUF->vtc = 0x60;
UIP_IP_BUF->tcflow = 0;
UIP_IP_BUF->flow = 0;
if (uip_ext_len)
{
UIP_FIRST_EXT_BUF->next = UIP_PROTO_ICMP6;
}
else
{
UIP_IP_BUF->proto = UIP_PROTO_ICMP6;
}
UIP_IP_BUF->ttl = uip_ds6_if.cur_hop_limit;
/* 源地址不能为未指定地址,也不能为组播地址。组播地址的检查在
* uip_process中进行 */
if(uip_is_addr_unspecified(&UIP_IP_BUF->srcipaddr))
{
uip_len = 0;
return;
}
//把源地址变为目的地址,实际上就是回发
uip_ipaddr_copy(&UIP_IP_BUF->destipaddr, &UIP_IP_BUF->srcipaddr);
//如果之前是通过组播形式发送过来的消息
if(uip_is_addr_mcast(&tmp_ipaddr))
{ //如果是遭遇不能识别的 IPv6 选项
if(type == ICMP6_PARAM_PROB && code == ICMP6_PARAMPROB_OPTION)
{
uip_ds6_select_src(&UIP_IP_BUF->srcipaddr, &tmp_ipaddr);
}
else
{
uip_len = 0;
return;
}
}
else
{
#if UIP_CONF_ROUTER
/* 挑选一个符合此节点的源地址 */
uip_ds6_select_src(&UIP_IP_BUF->srcipaddr, &tmp_ipaddr);
#else
uip_ipaddr_copy(&UIP_IP_BUF->srcipaddr, &tmp_ipaddr);
#endif
}
UIP_ICMP_BUF->type = type; //压入type
UIP_ICMP_BUF->icode = code; //压入code
UIP_ICMP6_ERROR_BUF->param = uip_htonl(param); //压入param
//压入IP数据段长度
UIP_IP_BUF->len[0] = ((uip_len - UIP_IPH_LEN) >> 8);
UIP_IP_BUF->len[1] = ((uip_len - UIP_IPH_LEN) & 0xff);
//压入校验和
UIP_ICMP_BUF->icmpchksum = 0;
UIP_ICMP_BUF->icmpchksum = ~uip_icmp6chksum();
//状态统计
UIP_STAT(++uip_stat.icmp.sent);
//debug状态下打印信息
PRINTF("Sending ICMPv6 ERROR message type %d code %d to", type, code);
PRINT6ADDR(&UIP_IP_BUF->destipaddr);
PRINTF("from");
PRINT6ADDR(&UIP_IP_BUF->srcipaddr);
PRINTF("\n");
return;
}
这段还无法完全理解,先这样吧。应该是收到一个数据包,出错,然后回发一个ICMP出错消息。此函数没有进行回发,而是做回发前的准备工作。
void uip_icmp6_send(const uip_ipaddr_t *dest, int type,
int code, int payload_len)
{ //版本号为6代表使用IPv6协议
UIP_IP_BUF->vtc = 0x60;
UIP_IP_BUF->tcflow = 0;
UIP_IP_BUF->flow = 0;
UIP_IP_BUF->proto = UIP_PROTO_ICMP6;
UIP_IP_BUF->ttl = uip_ds6_if.cur_hop_limit;
UIP_IP_BUF->len[0] = (UIP_ICMPH_LEN + payload_len) >> 8;
UIP_IP_BUF->len[1] = (UIP_ICMPH_LEN + payload_len) & 0xff;
//压入目的地地址
memcpy(&UIP_IP_BUF->destipaddr, dest, sizeof(*dest));
uip_ds6_select_src(&UIP_IP_BUF->srcipaddr, &UIP_IP_BUF->destipaddr);
//压入type和code
UIP_ICMP_BUF->type = type;
UIP_ICMP_BUF->icode = code;
//压入校验和
UIP_ICMP_BUF->icmpchksum = 0;
UIP_ICMP_BUF->icmpchksum = ~uip_icmp6chksum();
//计算长度
uip_len = UIP_IPH_LEN + UIP_ICMPH_LEN + payload_len;
//这个应该是用tcp协议发送吧
tcpip_ipv6_output();
}
发送一个icmpv6消息,这个应该是真正的发送了。
static void echo_reply_input(void)
{
int ttl;
uip_ipaddr_t sender;
#if UIP_CONF_IPV6_RPL
uint8_t temp_ext_len;
#endif /* UIP_CONF_IPV6_RPL */
uip_ipaddr_copy(&sender, &UIP_IP_BUF->srcipaddr);
ttl = UIP_IP_BUF->ttl;
if(uip_ext_len > 0)//如果存在扩展首部
{
#if UIP_CONF_IPV6_RPL
if((temp_ext_len = rpl_invert_header()))
{ /*如果是其它类型的扩展首部*/
UIP_FIRST_EXT_BUF->next = UIP_PROTO_ICMP6;
if (uip_ext_len != temp_ext_len)
{
uip_len -= (uip_ext_len - temp_ext_len);
UIP_IP_BUF->len[0] = ((uip_len - UIP_IPH_LEN) >> 8);
UIP_IP_BUF->len[1] = ((uip_len - UIP_IPH_LEN) & 0xff);
/* 移动回显应答的有效载荷(开始于icmp首部后之后)到应答新区域。
* 移动距离等于当前剩余扩展首部长度。
* 注意:UIP_ICMP_BUF现阶段仍然指向回显应答 */
memmove((uint8_t *)UIP_ICMP_BUF + UIP_ICMPH_LEN - (uip_ext_len - temp_ext_len),
(uint8_t *)UIP_ICMP_BUF + UIP_ICMPH_LEN,
(uip_len - UIP_IPH_LEN - temp_ext_len - UIP_ICMPH_LEN));
}
uip_ext_len = temp_ext_len;
uip_len -= uip_ext_len;
}
else
{
#endif /* UIP_CONF_IPV6_RPL */
/* 如果存在扩展首部*/
UIP_IP_BUF->proto = UIP_PROTO_ICMP6;
uip_len -= uip_ext_len;
UIP_IP_BUF->len[0] = ((uip_len - UIP_IPH_LEN) >> 8);
UIP_IP_BUF->len[1] = ((uip_len - UIP_IPH_LEN) & 0xff);
/* 移动回显请求的有效载荷(开始于icmp首部后之后)到应答新区域。
* 移动距离等于当前扩展首部长度。
* 注意: UIP_ICMP_BUF现阶段仍然指向回显请求 */
memmove((uint8_t *)UIP_ICMP_BUF + UIP_ICMPH_LEN - uip_ext_len,
(uint8_t *)UIP_ICMP_BUF + UIP_ICMPH_LEN,
(uip_len - UIP_IPH_LEN - UIP_ICMPH_LEN));
uip_ext_len = 0;
#if UIP_CONF_IPV6_RPL
}
#endif /* UIP_CONF_IPV6_RPL */
}
/* 调用所有注册的应用,让他们知道已经收到一个回显应答 */
{
struct uip_icmp6_echo_reply_notification *n;
for(n = list_head(echo_reply_callback_list);
n != NULL;
n = list_item_next(n))
{
if(n->callback != NULL)
{
n->callback(&sender, ttl,
(uint8_t *)&UIP_ICMP_BUF[sizeof(struct uip_icmp_hdr)],
uip_len - sizeof(struct uip_icmp_hdr) - UIP_IPH_LEN);
}
}
}
uip_len = 0;
return;
}
此为输入管理者回显应答处理函数。
void
uip_icmp6_echo_reply_callback_add(struct uip_icmp6_echo_reply_notification *n,
uip_icmp6_echo_reply_callback_t c)
{
if(n != NULL && c != NULL)
{
n->callback = c;
list_add(echo_reply_callback_list, n);
}
}
为ping应答添加一个回调函数,请参考上一篇日志
void
uip_icmp6_echo_reply_callback_rm(struct uip_icmp6_echo_reply_notification *n)
{
list_remove(echo_reply_callback_list, n);
}
为ping应答移除一个回调函数,请参考上一篇日志
UIP_ICMP6_HANDLER(echo_request_handler, ICMP6_ECHO_REQUEST,
UIP_ICMP6_HANDLER_CODE_ANY, echo_request_input);
UIP_ICMP6_HANDLER(echo_reply_handler, ICMP6_ECHO_REPLY,
UIP_ICMP6_HANDLER_CODE_ANY, echo_reply_input);
/*---------------------------------------------------------------------------*/
void
uip_icmp6_init()
{
/* 注册请求和应答管理者 */
uip_icmp6_register_input_handler(&echo_request_handler);
uip_icmp6_register_input_handler(&echo_reply_handler);
}
icmp6的初始化就是注册请求和应答管理者,也就是之前讲的两个函数:echo_request_input和echo_reply_input)
;