ICMPv6报文概述
参考RFC4443和RFC2460
ICMPv6报文是IPv6在internal control management protocol(ICMP)的基础之上做了一些改动,得到了ICMPv6协议,IPv6的next_header为58。
Message general format
每条ICMPv6消息之前都有一个IPv6报头和零个或多个IPv6扩展报头。ICMPv6报头由前一报头的下一报头值58来标识。(这与IPv4中用来标识ICMP的值不同。)
ICMPv6的一般格式如下:
type :表示消息的类型,type的值决定剩余数据的格式。
code : code的值取决于type的值,用于更细的划分ICMPv6的消息类型。
Checksum : 校验和用于检测ICMPv6消息和部分IPv6报头中的数据损坏。
Message body :分为两类,一类是错误消息,类型是0~128;一类是信息消息,类型范围是129-255。
几种常见的ICMPv6消息格式
错误消息格式:
type | mean |
---|---|
1 | Destination Unreachable |
2 | Packet too big |
3 | Time Exceeded |
4 | Parameter Problem |
100 | Private experimentation |
101 | Private experimentation |
127 | Reserved for expansion of ICMPv6 error messages |
信息消息:
type | mean |
---|---|
128 | Echo Request |
129 | Echo Reply |
200 | Private experimentation |
201 | Private experimentation |
255 | Reserved for expansion of ICMPv6 informational messages |
ICMPv6报文解析流程
确认消息的源地址
发送ICMPv6消息的节点必须在计算校验和之前确定IPv6报头中的源IPv6地址和目的IPv6地址。如果节点有多个单播地址,则MUST按照如下方式选择消息的源地址:
- 如果消息是对发送到节点的单播地址之一的消息的响应,则应答的源地址MUST与该地址相同。
- 如果该消息是对发送到任何其他地址的消息的响应,
- 一个多播组地址,
- 一个不属于节点的单播地址由节点实现的任意播地址,
- 或者ICMPv6报文的源地址MUST为本节点的单播地址。
该地址的选择应根据该规则,该规则将用于为该节点发起的任何其他数据包选择源地址,并给定数据包的目的地址。但是,如果这将导致从ICMPv6数据包的目的地可到达的地址的更有信息的选择,则可以以另一种方式选择它。
校验和的计算
校验和是个位补码的16位补码从ICMPv6消息开始的整个ICMPv6消息的总和类型字段,并加上IPv6报头的“伪头”字段,如[IPv6,章节8.1]中指定的。下一个报头值伪头中使用的是58。
在校验和计算中包含IP报头地址的任何传输或其他上层协议必须修改以用于IPv6,包括128位IPv6地址而不是32位IPv4地址。特别是,下面的插图显示了IPv6的TCP和UDP“伪头”:
- 如果IPv6报文中包含路由头,则表示目的地址伪头中使用的地址是final目的地。在起始节点,该地址将在路由头的最后一个元素;在接收人处,的目的地址字段中IPv6报头。
- 伪报头中的下一个报头值标识上层协议(例如,TCP为6,UDP为17)。如果在IPv6报头和上层报头之间有扩展报头,它将不同于IPv6报头中的下一个报头值。
- 伪报头中的上层数据包长度是上层报头和数据的长度(例如,TCP报头加上TCP数据)。有些上层协议携带自己的协议长度信息(例如,UDP报头中的长度字段);对于这样的协议,这就是伪报头中使用的长度。其他协议(如TCP)不携带自己的长度信息,在这种情况下,伪报头中使用的长度是来自IPv6报头的有效载荷长度,减去IPv6报头和上层报头之间存在的任何扩展报头的长度。
- 与IPv4不同,当UDP数据包由IPv6节点发起时,UDP校验和不可选。也就是说,无论何时发起UDP数据包,IPv6节点必须计算数据包和伪报头的UDP校验和,如果计算结果为零,则必须将其更改为十六进制FFFF以放置在UDP报头中。IPv6接收者必须丢弃包含零校验和的UDP数据包,并记录错误。
IPv6版本的ICMP [ICMPv6]包含了上述伪头它的校验和计算;这与IPv4版本的ICMP不同,后者在校验和中不包含伪报头。改变的原因是为了保护ICMP免受误传或损坏它所依赖的IPv6报头字段,与IPv4不同,这些字段不受互联网层校验和的保护。ICMP伪报头中的下一报头字段包含值58,用于标识ICMP协议的IPv6版本。
一段处理ICMPv6目的不可达报文的代码示例
static int
handle_icmp6_err(struct mbuf *m, struct icmp6_hdr *ih6, struct ip6_hdr *ic_ip6)
{
struct udphdr *ic_udp = NULL; /* initialize to make compiler happy */
struct icmp6_hdr *ic_icmp6;
clickpcb_t *pcb;
clickpcb_udp_t *udp_pcb;
clickpcb_udp_t *udp_opcb;
clickpcb_icmp_t *icmp_pcb;
clickpcb_icmp_t *icmp_opcb;
clicktcp_enter_func(m, ih6);
if (ic_ip6->ip6_nxt == IPPROTO_ICMPV6) {
ic_icmp6 = (struct icmp6_hdr *)(ic_ip6 + 1);
pcb = clickpcb6_lookup(&ic_ip6->ip6_dst,
&ic_ip6->ip6_src,
0,
ic_icmp6->icmp6_id,
ic_ip6->ip6_nxt);
} else {
ic_udp = (struct udphdr*)(ic_ip6 + 1);
if (ATCP_IS_IP()) {
IP2L4_dispatcher(m, ntohs(ic_udp->uh_dport), ntohs(ic_udp->uh_sport));
return MBUFINUSE;
}
pcb = clickpcb6_lookup(&ic_ip6->ip6_dst,
&ic_ip6->ip6_src,
ntohs(ic_udp->uh_dport),
ntohs(ic_udp->uh_sport),
ic_ip6->ip6_nxt);
}
if (pcb == NULL)
return QUEUEBSD;
if (pcb->cp_type == PCB_UDP) {
if ((pcb->cp_flags & CLICKPCB_UDP_SERVER) == 0)
return FREEMBUF;
udp_pcb = (clickpcb_udp_t *)pcb;
if (udp_pcb->reverse == NULL)
return FREEMBUF; /* bug 64435 */
CHECK_PCB_EROUTE((clickpcb_t *)udp_pcb->reverse, udp_pcb->reverse->cp_eroute, EROUTE_LOOKUP_ONLY);
if (udp_pcb->reverse->cp_eroute.rule == NULL)
return FREEMBUF;
/* Reset connection client timeout timer */
udp_opcb = udp_pcb->reverse;
click_callout_reset(&udp_opcb->timeout_callout,
udp_opcb->timeout, clickudp_timeout, udp_opcb);
udp_opcb = udp_pcb->reverse;
clickicmp6_error_nat_patch(m, pcb, ih6, ic_ip6);
clicktcp6_output(m, ERT_RTENTRY(udp_opcb->cp_eroute.rule), ERT_NUMA_IFP(udp_opcb->cp_eroute.rule), udp_opcb->cp_eroute.rule);
return MBUFINUSE;
} else if (pcb->cp_type == IPPROTO_ICMPV6) {
if ((pcb->cp_flags & CLICKPCB_ICMP_SERVER) == 0)
return FREEMBUF;
icmp_pcb = (clickpcb_icmp_t *)pcb;
if (icmp_pcb->reverse == NULL)
return MBUFINUSE;
CHECK_PCB_EROUTE((clickpcb_t *)icmp_pcb->reverse, icmp_pcb->reverse->cp_eroute, EROUTE_LOOKUP_ONLY);
if (icmp_pcb->reverse->cp_eroute.rule == NULL)
return FREEMBUF;
/* Reset connection client timeout timer */
icmp_opcb = icmp_pcb->reverse;
click_callout_reset(&icmp_opcb->timeout_callout,
icmp_opcb->timeout, clickicmp_timeout, icmp_opcb);
icmp_opcb = icmp_pcb->reverse;
clickicmp6_error_nat_patch(m, pcb, ih6, ic_ip6);
clicktcp6_output(m, ERT_RTENTRY(icmp_opcb->cp_eroute.rule), ERT_NUMA_IFP(icmp_opcb->cp_eroute.rule), icmp_opcb->cp_eroute.rule);
return MBUFINUSE;
} else {
return FREEMBUF;
}
}
static void
clickicmp6_error_nat_patch(struct mbuf *m, clickpcb_t *pcb, struct icmp6_hdr *ih6, struct ip6_hdr *ic_ip6)
{
struct udphdr *ic_udp;
struct icmp6_hdr *ic_icmp6;
struct ip6_hdr *ip6;
clickpcb_udp_t *udp_pcb;
clickpcb_udp_t *udp_opcb;
clickpcb_icmp_t *icmp_pcb;
clickpcb_icmp_t *icmp_opcb;
uint16_t oldcksum;
uint32_t ph_sum = 0;
clicktcp_enter_func(m, pcb);
/*Calculates the pseudo-header checksum*/
struct in6_addr *src_ip6 = &(ic_ip6->ip6_src);
struct in6_addr *dst_ip6 = &(ic_ip6->ip6_dst);
for (int i = 0; i < 8; i++) {
ph_sum += htons(src_ip6->s6_addr16[i]);
ph_sum += htons(dst_ip6->s6_addr16[i]);
}
ph_sum += htons(ic_ip6->ip6_plen);
ph_sum += htons(ic_ip6->ip6_nxt);
if (pcb->cp_type == PCB_UDP) {
udp_pcb = (clickpcb_udp_t *)pcb;
ip6 = mtod(m, struct ip6_hdr *);
ic_udp = (struct udphdr *)(ic_ip6 + 1);
udp_opcb = udp_pcb->reverse;
if (ih6->icmp6_type == ICMP6_DST_UNREACH && ih6->icmp6_code == ICMP6_DST_UNREACH_NOPORT) {
ip6->ip6_src = udp_opcb->cp_localip6;
}
ip6->ip6_dst = udp_opcb->cp_remoteip6;
ic_ip6->ip6_src = udp_opcb->cp_remoteip6;
ic_ip6->ip6_dst = udp_opcb->cp_localip6;
/*Accumulates 16 bits of UDP*/
ph_sum += htons(udp_opcb->cp_localport);
ph_sum += htons(udp_opcb->cp_remoteport);
ph_sum += htons(ic_udp->uh_ulen);
ph_sum += *(uint16_t *)(ic_udp);
/*Processing overflow*/
while (ph_sum >> 16) {
ph_sum = (ph_sum & 0xFFFF) + (ph_sum >> 16);
}
/*Calculated checksum*/
uint16_t old_cksum = ic_udp->uh_sum;
uint32_t sum = ph_sum + ~(old_cksum & 0xFFFF);
while (sum >> 16) {
sum = (sum & 0xFFFF) + (sum >> 16);
}
uint16_t new_cksum = ~sum;
/*Assign the checksum to the packet header*/
ic_udp->uh_sum = new_cksum;
} else if (pcb->cp_type == IPPROTO_ICMPV6) {
icmp_pcb = (clickpcb_icmp_t *)pcb;
ip6 = mtod(m, struct ip6_hdr *);
ic_icmp6 = (struct icmp6_hdr*)(ic_ip6 + 1);
icmp_opcb = icmp_pcb->reverse;
ip6->ip6_dst = icmp_opcb->cp_remoteip6;
ic_ip6->ip6_src = icmp_opcb->cp_remoteip6;
ic_ip6->ip6_dst = icmp_opcb->cp_localip6;
ic_icmp6->icmp6_id = icmp_opcb->cp_id;
/*Accumulates 16 bits of ICMPV6*/
ph_sum += htons(ic_icmp6->icmp6_id);
ph_sum += htons(ic_icmp6->icmp6_seq);
ph_sum += *(uint16_t *)(ic_icmp6);
/*Processing overflow*/
while (ph_sum >> 16) {
ph_sum = (ph_sum & 0xFFFF) + (ph_sum >> 16);
}
/*Calculated checksum*/
uint16_t old_cksum = ic_icmp6->icmp6_cksum;
uint32_t sum = ph_sum + ~(old_cksum & 0xFFFF);
while (sum >> 16) {
sum = (sum & 0xFFFF) + (sum >> 16);
}
uint16_t new_cksum = ~sum;
/*Assign the checksum to the packet header*/
ic_icmp6->icmp6_cksum = new_cksum;
} else {
return;
}
}