datapath: Use upstream ipv6_find_hdr().
[cascardo/ovs.git] / datapath / linux / compat / exthdrs_core.c
1 #include <linux/ipv6.h>
2 #include <linux/version.h>
3 #include <net/ipv6.h>
4
5 #ifndef HAVE_IP6_FH_F_SKIP_RH
6 #if LINUX_VERSION_CODE < KERNEL_VERSION(3,3,0)
7 int rpl_ipv6_skip_exthdr(const struct sk_buff *skb, int start,
8                          u8 *nexthdrp, __be16 *frag_offp)
9 {
10         u8 nexthdr = *nexthdrp;
11
12         *frag_offp = 0;
13
14         while (ipv6_ext_hdr(nexthdr)) {
15                 struct ipv6_opt_hdr _hdr, *hp;
16                 int hdrlen;
17
18                 if (nexthdr == NEXTHDR_NONE)
19                         return -1;
20                 hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
21                 if (hp == NULL)
22                         return -1;
23                 if (nexthdr == NEXTHDR_FRAGMENT) {
24                         __be16 _frag_off, *fp;
25                         fp = skb_header_pointer(skb,
26                                                 start+offsetof(struct frag_hdr,
27                                                                frag_off),
28                                                 sizeof(_frag_off),
29                                                 &_frag_off);
30                         if (fp == NULL)
31                                 return -1;
32
33                         *frag_offp = *fp;
34                         if (ntohs(*frag_offp) & ~0x7)
35                                 break;
36                         hdrlen = 8;
37                 } else if (nexthdr == NEXTHDR_AUTH)
38                         hdrlen = (hp->hdrlen+2)<<2;
39                 else
40                         hdrlen = ipv6_optlen(hp);
41
42                 nexthdr = hp->nexthdr;
43                 start += hdrlen;
44         }
45
46         *nexthdrp = nexthdr;
47         return start;
48 }
49 #endif /* Kernel version < 3.3 */
50
51 /*
52  * find the offset to specified header or the protocol number of last header
53  * if target < 0. "last header" is transport protocol header, ESP, or
54  * "No next header".
55  *
56  * Note that *offset is used as input/output parameter. an if it is not zero,
57  * then it must be a valid offset to an inner IPv6 header. This can be used
58  * to explore inner IPv6 header, eg. ICMPv6 error messages.
59  *
60  * If target header is found, its offset is set in *offset and return protocol
61  * number. Otherwise, return -1.
62  *
63  * If the first fragment doesn't contain the final protocol header or
64  * NEXTHDR_NONE it is considered invalid.
65  *
66  * Note that non-1st fragment is special case that "the protocol number
67  * of last header" is "next header" field in Fragment header. In this case,
68  * *offset is meaningless and fragment offset is stored in *fragoff if fragoff
69  * isn't NULL.
70  *
71  * if flags is not NULL and it's a fragment, then the frag flag
72  * IP6_FH_F_FRAG will be set. If it's an AH header, the
73  * IP6_FH_F_AUTH flag is set and target < 0, then this function will
74  * stop at the AH header. If IP6_FH_F_SKIP_RH flag was passed, then this
75  * function will skip all those routing headers, where segements_left was 0.
76  */
77 int rpl_ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
78                   int target, unsigned short *fragoff, int *flags)
79 {
80         unsigned int start = skb_network_offset(skb) + sizeof(struct ipv6hdr);
81         u8 nexthdr = ipv6_hdr(skb)->nexthdr;
82         unsigned int len;
83         bool found;
84
85         if (fragoff)
86                 *fragoff = 0;
87
88         if (*offset) {
89                 struct ipv6hdr _ip6, *ip6;
90
91                 ip6 = skb_header_pointer(skb, *offset, sizeof(_ip6), &_ip6);
92                 if (!ip6 || (ip6->version != 6)) {
93                         printk(KERN_ERR "IPv6 header not found\n");
94                         return -EBADMSG;
95                 }
96                 start = *offset + sizeof(struct ipv6hdr);
97                 nexthdr = ip6->nexthdr;
98         }
99         len = skb->len - start;
100
101         do {
102                 struct ipv6_opt_hdr _hdr, *hp;
103                 unsigned int hdrlen;
104                 found = (nexthdr == target);
105
106                 if ((!ipv6_ext_hdr(nexthdr)) || nexthdr == NEXTHDR_NONE) {
107                         if (target < 0 || found)
108                                 break;
109                         return -ENOENT;
110                 }
111
112                 hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
113                 if (hp == NULL)
114                         return -EBADMSG;
115
116                 if (nexthdr == NEXTHDR_ROUTING) {
117                         struct ipv6_rt_hdr _rh, *rh;
118
119                         rh = skb_header_pointer(skb, start, sizeof(_rh),
120                                                 &_rh);
121                         if (rh == NULL)
122                                 return -EBADMSG;
123
124                         if (flags && (*flags & IP6_FH_F_SKIP_RH) &&
125                             rh->segments_left == 0)
126                                 found = false;
127                 }
128
129                 if (nexthdr == NEXTHDR_FRAGMENT) {
130                         unsigned short _frag_off;
131                         __be16 *fp;
132
133                         if (flags)      /* Indicate that this is a fragment */
134                                 *flags |= IP6_FH_F_FRAG;
135                         fp = skb_header_pointer(skb,
136                                                 start+offsetof(struct frag_hdr,
137                                                                frag_off),
138                                                 sizeof(_frag_off),
139                                                 &_frag_off);
140                         if (fp == NULL)
141                                 return -EBADMSG;
142
143                         _frag_off = ntohs(*fp) & ~0x7;
144                         if (_frag_off) {
145                                 if (target < 0 &&
146                                     ((!ipv6_ext_hdr(hp->nexthdr)) ||
147                                      hp->nexthdr == NEXTHDR_NONE)) {
148                                         if (fragoff)
149                                                 *fragoff = _frag_off;
150                                         return hp->nexthdr;
151                                 }
152                                 return -ENOENT;
153                         }
154                         hdrlen = 8;
155                 } else if (nexthdr == NEXTHDR_AUTH) {
156                         if (flags && (*flags & IP6_FH_F_AUTH) && (target < 0))
157                                 break;
158                         hdrlen = (hp->hdrlen + 2) << 2;
159                 } else
160                         hdrlen = ipv6_optlen(hp);
161
162                 if (!found) {
163                         nexthdr = hp->nexthdr;
164                         len -= hdrlen;
165                         start += hdrlen;
166                 }
167         } while (!found);
168
169         *offset = start;
170         return nexthdr;
171 }
172
173 #endif