Contiki 系列文章

IPv6(2)- ICMPv6

作者:陈广
日期:2016-03-22


看来看去,先从ICMPv6开始吧,相对简单些。ICMPv6标准文档是RFC4443,有中文版,请从本日志最下方链接下载。

ICMPv6 由 IPv6 节点使用,用于报告在分组处理过程中出现的错误,以及执行其他网络互连层功能,诸如诊断ICMPv6“ping”)。ICMPv6 是 IPv6 的整体部分,是基础协议,RFC4443标准规定的所有消息和行为每个 IPv6 节点必须无条件执行。 先来看看ICMPv6的消息通用格式:

Type:

类型字段:指出消息类型,它的值决定其余数据格式。

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 */

消息解释:

  1. 目的不可达
  2. 分组(包)太大
  3. 超时
  4. 参数问题(ip6头损坏)

指示消息:

#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

Code:

代码字段:依赖于消息类型。它用于生成消息粒度的附加层。

下面依次讨论Type为出错消息时的代码字段

Type值: 1 目的地不可达

/*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值:

  1. 没有去目的地的路由
  2. 管理上禁止与目的地通信
  3. 超出源地址范围
  4. 地址不可达
  5. 端口不可达
  6. 源地址与入境/出境策略抵触
  7. 去目的地的拒绝路由

可以看到,值5和6在Contiki中没有实现

上图中的未使用字段(Unused)占32bit空间,此字段未用于所有代码值。它必须被发起者初始化为 0,被接收者忽略。

Type值: 2 分组太大

Code值:由发起者设置为 0(零),被接收者忽略。

MTU:下一跳链路的MTU。

最大传输单元(Maximum Transmission Unit,MTU)是指一种通信协议的某一层上面所能通过的最大数据包大小(以字节为单位)。

Type值: 3 超时

/** \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值:

  1. 在传输中超过跳数限制
  2. 分段重组时间超过

未使用字段(Unused):此字段未用于所有代码值。它必须被发起者初始化为 0,被接收者忽略。

Type值: 4 参数问题

/** \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值:

  1. 遭遇出错的首部字段
  2. 遭遇不能识别的 Next Header 类型
  3. 遭遇不能识别的 IPv6 选项

指示器(Pointer):

标识调用分组内的字节偏移,在那里检测到出错。如果出错中的此字段超过 ICMPv6 出错消息的最大长度,指示器将指出超出 ICMPv6 分组的末端。

接下来讨论ICMPv6指示消息:

Type值: 128 回显请求

  • 代码(Code):0
  • 标识符(Identifier):帮助 Echo Replies 匹配这个 Echo Request 的标识符。可以是 0。
  • 序列号(Sequence Number):帮助 Echo Replies 匹配这个 Echo Request 的序列号。可以是 0。
  • 数据(Data):0 或更多字节任意数据。

Type值: 129 回显响应

消息结构和上图一模一样。

  • 代码(Code):0
  • 标识符(Identifier):来自调用 Echo Request 消息的标识符。
  • 序列号(Sequence Number):来自调用 Echo Request 消息的序列号。
  • 数据(Data):来自调用 Echo Request 消息的数据。

下面是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);

输入管理者链表

input_handler_lookup

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时,也算符合条件。

uip_icmp6_input

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所对应的处理函数。应该翻译为处理机更合适些,但管理者更好记,好听。

uip_icmp6_register_input_handler

void
uip_icmp6_register_input_handler(uip_icmp6_input_handler_t *handler)
{
      list_add(input_handler_list, handler);
}

注册一个输入管理者,实际上就是加入链表。

echo_request_input

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和校验和。

此函数是输入管理者的回显请求处理函数

uip_icmp6_error_output

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出错消息。此函数没有进行回发,而是做回发前的准备工作。

uip_icmp6_send

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消息,这个应该是真正的发送了。

echo_reply_input

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;
}

此为输入管理者回显应答处理函数。

uip_icmp6_echo_reply_callback_add

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应答添加一个回调函数,请参考上一篇日志

uip_icmp6_echo_reply_callback_rm

void
uip_icmp6_echo_reply_callback_rm(struct uip_icmp6_echo_reply_notification *n)
{
      list_remove(echo_reply_callback_list, n);
}

为ping应答移除一个回调函数,请参考上一篇日志

uip_icmp6_init

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)

;

© 2018 - IOT小分队文章发布系统 v0.3