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