IPv6: how to support IPv6 ext header

概述

与IPv4的几点不同

IPv6在扩展头和协议处理上跟IPv4还有些不一样。

  1. IPv6报文格式设计上,上层协议和扩展头都作为IPv6的nexthdr类型串联在一起,不像IPv4那样扩展头是单独的option,上层协议类型放到ip头里的proto字段。
  2. IPv6的扩展头大部分采用tlv格式,大部分扩展头前几个字节会保存nexthdr,本扩展的长度这两个信息,然后跟本扩展头相关的一些数据。
  3. 我们无法像IPv4那样通过IP头里的字段就能简洁的判读出报文四层协议类型以及4层协议的offer和lenth等信息。必须逐个解析全部的扩展头。

原理

协议栈通过一个inet6_protocol类型的数组,保存IPv6所有的4层处理协议入口。

1
2
struct inet6_protocol __rcu *inet6_protos[MAX_INET_PROTOS] __read_mostly;
EXPORT_SYMBOL(inet6_protos)
  1. 数组inet6_protos的下标对应的就是每个扩展协议在IPv6扩展头里的nexthdr

inet6_protocol结构体

  1. struct inet6_protocol结构体里的handler是扩展头的处理入口函数。
  2. flags字段有两个标志位: INET6_PROTO_NOPOLICYINET6_PROTO_FINAL
  • INET6_PROTO_FINAL: 这个扩展是否可以作为IPv6的最后一个扩展, 比如
    TCP可以, 但是IPPROTO_DSTOPTS不可以。
  • INET6_PROTO_NOPOLICY: 这个要求必须有对应的IPsec/xfrm XFRM_POLICY_IN的规则
    这里有个疑问, esp6和ah6 为什么也有这个标志位, 我理解不应该有这个标志位, 有esp和ah头的,必须要有xfrm规则才可。
1
2
3
4
5
6
7
8
9
10
11
12
13
53 struct inet6_protocol {
54 int (*handler)(struct sk_buff *skb);
55
56 /* This returns an error if we weren't able to handle the error. */
57 int (*err_handler)(struct sk_buff *skb,
58 struct inet6_skb_parm *opt,
59 u8 type, u8 code, int offset,
60 __be32 info);
61
62 unsigned int flags; /* INET6_PROTO_xxx */
63 u32 secret;
64 };
65
1
2
66 #define INET6_PROTO_NOPOLICY    0x1
67 #define INET6_PROTO_FINAL 0x2

函数调用栈

1
2
3
4
5
6
7
==> ipv6_rcv
==> ==> ip6_rcv_finish
==> ==> ==> dst_input(skb); <== ip6_input
==> ==> ==> ==> ip6_input
==> ==> ==> ==> ==> ip6_input_finish
==> ==> ==> ==> ==> ==> ip6_protocol_deliver_rcu(net, skb, 0, false);
==> ==> ==> ==> ==> ==> ==> ipprot = rcu_dereference(inet6_protos[`nexthdr`]);

注册和注销

1
2
3
4
5
6
7
8
9
10
11
52 struct inet6_protocol {
53 int (*handler)(struct sk_buff *skb);
54
55 /* This returns an error if we weren't able to handle the error. */
56 int (*err_handler)(struct sk_buff *skb,
57 struct inet6_skb_parm *opt,
58 u8 type, u8 code, int offset,
59 __be32 info);
60
61 unsigned int flags; /* INET6_PROTO_xxx */
62 };
1
2
3
4
5
28 int inet6_add_protocol(const struct inet6_protocol *prot, unsigned char protocol)
29 {
30 return !cmpxchg((const struct inet6_protocol **)&inet6_protos[protocol],
31 NULL, prot) ? 0 : -1;
32 }
1
2
3
4
5
6
7
8
9
10
11
35 int inet6_del_protocol(const struct inet6_protocol *prot, unsigned char protocol)
36 {
37 int ret;
38
39 ret = (cmpxchg((const struct inet6_protocol **)&inet6_protos[protocol],
40 prot, NULL) == prot) ? 0 : -1;
41
42 synchronize_net();
43
44 return ret;
45 }

内核总共支持的IPv6四层协议类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
➜  linux git:(master) grep inet6_add_protocol net include -Rw
net/dccp/ipv6.c: err = inet6_add_protocol(&dccp_v6_protocol, IPPROTO_DCCP);
net/l2tp/l2tp_ip6.c: err = inet6_add_protocol(&l2tp_ip6_protocol, IPPROTO_L2TP);
net/sctp/ipv6.c: if (inet6_add_protocol(&sctpv6_protocol, IPPROTO_SCTP) < 0)
net/ipv6/udplite.c: ret = inet6_add_protocol(&udplitev6_protocol, IPPROTO_UDPLITE);
net/ipv6/xfrm6_protocol.c: if (inet6_add_protocol(netproto(protocol), protocol)) {
net/ipv6/exthdrs.c: ret = inet6_add_protocol(&rthdr_protocol, IPPROTO_ROUTING);
net/ipv6/exthdrs.c: ret = inet6_add_protocol(&destopt_protocol, IPPROTO_DSTOPTS);
net/ipv6/exthdrs.c: ret = inet6_add_protocol(&nodata_protocol, IPPROTO_NONE);
net/ipv6/ip6mr.c: if (inet6_add_protocol(&pim6_protocol, IPPROTO_PIM) < 0) {
net/ipv6/udp.c: ret = inet6_add_protocol(&net_hotdata.udpv6_protocol, IPPROTO_UDP);
net/ipv6/ip6_gre.c: err = inet6_add_protocol(&ip6gre_protocol, IPPROTO_GRE);
net/ipv6/reassembly.c: ret = inet6_add_protocol(&frag_protocol, IPPROTO_FRAGMENT);
net/ipv6/protocol.c:int inet6_add_protocol(const struct inet6_protocol *prot, unsigned char protocol)
net/ipv6/protocol.c:EXPORT_SYMBOL(inet6_add_protocol);
net/ipv6/tcp_ipv6.c: ret = inet6_add_protocol(&net_hotdata.tcpv6_protocol, IPPROTO_TCP);
net/ipv6/icmp.c: if (inet6_add_protocol(&icmpv6_protocol, IPPROTO_ICMPV6) < 0)
net/ipv6/tunnel6.c: if (inet6_add_protocol(&tunnel6_protocol, IPPROTO_IPV6)) {
net/ipv6/tunnel6.c: if (inet6_add_protocol(&tunnel46_protocol, IPPROTO_IPIP)) {
net/ipv6/tunnel6.c: inet6_add_protocol(&tunnelmpls6_protocol, IPPROTO_MPLS)) {
include/net/protocol.h:int inet6_add_protocol(const struct inet6_protocol *prot, unsigned char num);
➜ linux git:(master)

IPPROTO_ROUTING 字段处理

1
2
3
4
835 static const struct inet6_protocol rthdr_protocol = {
836 .handler = ipv6_rthdr_rcv,
837 .flags = INET6_PROTO_NOPOLICY,
838 };
1
ret = inet6_add_protocol(&rthdr_protocol, IPPROTO_ROUTING);

TCP协议处理

1
2
3
4
5
6
2406         net_hotdata.tcpv6_protocol = (struct inet6_protocol) {
2407 .handler = tcp_v6_rcv,
2408 .err_handler = tcp_v6_err,
2409 .flags = INET6_PROTO_NOPOLICY | INET6_PROTO_FINAL,
2410 };
2411 ret = inet6_add_protocol(&net_hotdata.tcpv6_protocol, IPPROTO_TCP);

IPV6-in-IPv4 tunnel协议处理

1
2
3
4
5
6
7
8
9
10
11
12
13
239 static const struct inet6_protocol tunnel6_protocol = {
240 .handler = tunnel6_rcv,
241 .err_handler = tunnel6_err,
242 .flags = INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL,
243 };
...

257 static int __init tunnel6_init(void)
258 {
259 if (inet6_add_protocol(&tunnel6_protocol, IPPROTO_IPV6)) {
...

52 IPPROTO_IPV6 = 41, /* IPv6-in-IPv4 tunnelling */