Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0-only
2 : : /*
3 : : * IPv6 library code, needed by static components when full IPv6 support is
4 : : * not configured or static.
5 : : */
6 : : #include <linux/export.h>
7 : : #include <net/ipv6.h>
8 : :
9 : : /*
10 : : * find out if nexthdr is a well-known extension header or a protocol
11 : : */
12 : :
13 : 0 : bool ipv6_ext_hdr(u8 nexthdr)
14 : : {
15 : : /*
16 : : * find out if nexthdr is an extension header or a protocol
17 : : */
18 : 0 : return (nexthdr == NEXTHDR_HOP) ||
19 : 0 : (nexthdr == NEXTHDR_ROUTING) ||
20 [ # # # # : 0 : (nexthdr == NEXTHDR_FRAGMENT) ||
# # # # ]
21 : 0 : (nexthdr == NEXTHDR_AUTH) ||
22 [ # # # # : 0 : (nexthdr == NEXTHDR_NONE) ||
# # # # #
# # # # #
# # ]
23 : : (nexthdr == NEXTHDR_DEST);
24 : : }
25 : : EXPORT_SYMBOL(ipv6_ext_hdr);
26 : :
27 : : /*
28 : : * Skip any extension headers. This is used by the ICMP module.
29 : : *
30 : : * Note that strictly speaking this conflicts with RFC 2460 4.0:
31 : : * ...The contents and semantics of each extension header determine whether
32 : : * or not to proceed to the next header. Therefore, extension headers must
33 : : * be processed strictly in the order they appear in the packet; a
34 : : * receiver must not, for example, scan through a packet looking for a
35 : : * particular kind of extension header and process that header prior to
36 : : * processing all preceding ones.
37 : : *
38 : : * We do exactly this. This is a protocol bug. We can't decide after a
39 : : * seeing an unknown discard-with-error flavour TLV option if it's a
40 : : * ICMP error message or not (errors should never be send in reply to
41 : : * ICMP error messages).
42 : : *
43 : : * But I see no other way to do this. This might need to be reexamined
44 : : * when Linux implements ESP (and maybe AUTH) headers.
45 : : * --AK
46 : : *
47 : : * This function parses (probably truncated) exthdr set "hdr".
48 : : * "nexthdrp" initially points to some place,
49 : : * where type of the first header can be found.
50 : : *
51 : : * It skips all well-known exthdrs, and returns pointer to the start
52 : : * of unparsable area i.e. the first header with unknown type.
53 : : * If it is not NULL *nexthdr is updated by type/protocol of this header.
54 : : *
55 : : * NOTES: - if packet terminated with NEXTHDR_NONE it returns NULL.
56 : : * - it may return pointer pointing beyond end of packet,
57 : : * if the last recognized header is truncated in the middle.
58 : : * - if packet is truncated, so that all parsed headers are skipped,
59 : : * it returns NULL.
60 : : * - First fragment header is skipped, not-first ones
61 : : * are considered as unparsable.
62 : : * - Reports the offset field of the final fragment header so it is
63 : : * possible to tell whether this is a first fragment, later fragment,
64 : : * or not fragmented.
65 : : * - ESP is unparsable for now and considered like
66 : : * normal payload protocol.
67 : : * - Note also special handling of AUTH header. Thanks to IPsec wizards.
68 : : *
69 : : * --ANK (980726)
70 : : */
71 : :
72 : 0 : int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp,
73 : : __be16 *frag_offp)
74 : : {
75 : 0 : u8 nexthdr = *nexthdrp;
76 : :
77 : 0 : *frag_offp = 0;
78 : :
79 [ # # ]: 0 : while (ipv6_ext_hdr(nexthdr)) {
80 : : struct ipv6_opt_hdr _hdr, *hp;
81 : : int hdrlen;
82 : :
83 [ # # ]: 0 : if (nexthdr == NEXTHDR_NONE)
84 : 0 : return -1;
85 : : hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
86 [ # # ]: 0 : if (!hp)
87 : : return -1;
88 [ # # ]: 0 : if (nexthdr == NEXTHDR_FRAGMENT) {
89 : : __be16 _frag_off, *fp;
90 : 0 : fp = skb_header_pointer(skb,
91 : 0 : start+offsetof(struct frag_hdr,
92 : : frag_off),
93 : : sizeof(_frag_off),
94 : : &_frag_off);
95 [ # # ]: 0 : if (!fp)
96 : 0 : return -1;
97 : :
98 : 0 : *frag_offp = *fp;
99 [ # # ]: 0 : if (ntohs(*frag_offp) & ~0x7)
100 : : break;
101 : : hdrlen = 8;
102 [ # # ]: 0 : } else if (nexthdr == NEXTHDR_AUTH)
103 : 0 : hdrlen = ipv6_authlen(hp);
104 : : else
105 : 0 : hdrlen = ipv6_optlen(hp);
106 : :
107 : 0 : nexthdr = hp->nexthdr;
108 : 0 : start += hdrlen;
109 : : }
110 : :
111 : 0 : *nexthdrp = nexthdr;
112 : 0 : return start;
113 : : }
114 : : EXPORT_SYMBOL(ipv6_skip_exthdr);
115 : :
116 : 0 : int ipv6_find_tlv(const struct sk_buff *skb, int offset, int type)
117 : : {
118 : : const unsigned char *nh = skb_network_header(skb);
119 : 0 : int packet_len = skb_tail_pointer(skb) - skb_network_header(skb);
120 : : struct ipv6_opt_hdr *hdr;
121 : : int len;
122 : :
123 [ # # ]: 0 : if (offset + 2 > packet_len)
124 : : goto bad;
125 : 0 : hdr = (struct ipv6_opt_hdr *)(nh + offset);
126 : 0 : len = ((hdr->hdrlen + 1) << 3);
127 : :
128 [ # # ]: 0 : if (offset + len > packet_len)
129 : : goto bad;
130 : :
131 : : offset += 2;
132 : 0 : len -= 2;
133 : :
134 [ # # ]: 0 : while (len > 0) {
135 : 0 : int opttype = nh[offset];
136 : : int optlen;
137 : :
138 [ # # ]: 0 : if (opttype == type)
139 : 0 : return offset;
140 : :
141 [ # # ]: 0 : switch (opttype) {
142 : : case IPV6_TLV_PAD1:
143 : : optlen = 1;
144 : : break;
145 : : default:
146 : 0 : optlen = nh[offset + 1] + 2;
147 [ # # ]: 0 : if (optlen > len)
148 : : goto bad;
149 : : break;
150 : : }
151 : 0 : offset += optlen;
152 : 0 : len -= optlen;
153 : : }
154 : : /* not_found */
155 : : bad:
156 : : return -1;
157 : : }
158 : : EXPORT_SYMBOL_GPL(ipv6_find_tlv);
159 : :
160 : : /*
161 : : * find the offset to specified header or the protocol number of last header
162 : : * if target < 0. "last header" is transport protocol header, ESP, or
163 : : * "No next header".
164 : : *
165 : : * Note that *offset is used as input/output parameter, and if it is not zero,
166 : : * then it must be a valid offset to an inner IPv6 header. This can be used
167 : : * to explore inner IPv6 header, eg. ICMPv6 error messages.
168 : : *
169 : : * If target header is found, its offset is set in *offset and return protocol
170 : : * number. Otherwise, return -1.
171 : : *
172 : : * If the first fragment doesn't contain the final protocol header or
173 : : * NEXTHDR_NONE it is considered invalid.
174 : : *
175 : : * Note that non-1st fragment is special case that "the protocol number
176 : : * of last header" is "next header" field in Fragment header. In this case,
177 : : * *offset is meaningless and fragment offset is stored in *fragoff if fragoff
178 : : * isn't NULL.
179 : : *
180 : : * if flags is not NULL and it's a fragment, then the frag flag
181 : : * IP6_FH_F_FRAG will be set. If it's an AH header, the
182 : : * IP6_FH_F_AUTH flag is set and target < 0, then this function will
183 : : * stop at the AH header. If IP6_FH_F_SKIP_RH flag was passed, then this
184 : : * function will skip all those routing headers, where segements_left was 0.
185 : : */
186 : 0 : int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
187 : : int target, unsigned short *fragoff, int *flags)
188 : : {
189 : 0 : unsigned int start = skb_network_offset(skb) + sizeof(struct ipv6hdr);
190 : 0 : u8 nexthdr = ipv6_hdr(skb)->nexthdr;
191 : : bool found;
192 : :
193 [ # # ]: 0 : if (fragoff)
194 : 0 : *fragoff = 0;
195 : :
196 [ # # ]: 0 : if (*offset) {
197 : : struct ipv6hdr _ip6, *ip6;
198 : :
199 : 0 : ip6 = skb_header_pointer(skb, *offset, sizeof(_ip6), &_ip6);
200 [ # # # # ]: 0 : if (!ip6 || (ip6->version != 6))
201 : 0 : return -EBADMSG;
202 : 0 : start = *offset + sizeof(struct ipv6hdr);
203 : 0 : nexthdr = ip6->nexthdr;
204 : : }
205 : :
206 : : do {
207 : : struct ipv6_opt_hdr _hdr, *hp;
208 : : unsigned int hdrlen;
209 : 0 : found = (nexthdr == target);
210 : :
211 [ # # # # ]: 0 : if ((!ipv6_ext_hdr(nexthdr)) || nexthdr == NEXTHDR_NONE) {
212 [ # # ]: 0 : if (target < 0 || found)
213 : : break;
214 : 0 : return -ENOENT;
215 : : }
216 : :
217 : 0 : hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
218 [ # # ]: 0 : if (!hp)
219 : : return -EBADMSG;
220 : :
221 [ # # ]: 0 : if (nexthdr == NEXTHDR_ROUTING) {
222 : : struct ipv6_rt_hdr _rh, *rh;
223 : :
224 : : rh = skb_header_pointer(skb, start, sizeof(_rh),
225 : : &_rh);
226 [ # # ]: 0 : if (!rh)
227 : 0 : return -EBADMSG;
228 : :
229 [ # # # # : 0 : if (flags && (*flags & IP6_FH_F_SKIP_RH) &&
# # ]
230 : 0 : rh->segments_left == 0)
231 : : found = false;
232 : : }
233 : :
234 [ # # ]: 0 : if (nexthdr == NEXTHDR_FRAGMENT) {
235 : : unsigned short _frag_off;
236 : : __be16 *fp;
237 : :
238 [ # # ]: 0 : if (flags) /* Indicate that this is a fragment */
239 : 0 : *flags |= IP6_FH_F_FRAG;
240 : 0 : fp = skb_header_pointer(skb,
241 : 0 : start+offsetof(struct frag_hdr,
242 : : frag_off),
243 : : sizeof(_frag_off),
244 : : &_frag_off);
245 [ # # ]: 0 : if (!fp)
246 : 0 : return -EBADMSG;
247 : :
248 : 0 : _frag_off = ntohs(*fp) & ~0x7;
249 [ # # ]: 0 : if (_frag_off) {
250 [ # # # # ]: 0 : if (target < 0 &&
251 [ # # ]: 0 : ((!ipv6_ext_hdr(hp->nexthdr)) ||
252 : : hp->nexthdr == NEXTHDR_NONE)) {
253 [ # # ]: 0 : if (fragoff)
254 : 0 : *fragoff = _frag_off;
255 : 0 : return hp->nexthdr;
256 : : }
257 [ # # ]: 0 : if (!found)
258 : : return -ENOENT;
259 [ # # ]: 0 : if (fragoff)
260 : 0 : *fragoff = _frag_off;
261 : 0 : break;
262 : : }
263 : : hdrlen = 8;
264 [ # # ]: 0 : } else if (nexthdr == NEXTHDR_AUTH) {
265 [ # # # # : 0 : if (flags && (*flags & IP6_FH_F_AUTH) && (target < 0))
# # ]
266 : : break;
267 : 0 : hdrlen = ipv6_authlen(hp);
268 : : } else
269 : 0 : hdrlen = ipv6_optlen(hp);
270 : :
271 [ # # ]: 0 : if (!found) {
272 : 0 : nexthdr = hp->nexthdr;
273 : 0 : start += hdrlen;
274 : : }
275 [ # # ]: 0 : } while (!found);
276 : :
277 : 0 : *offset = start;
278 : 0 : return nexthdr;
279 : : }
280 : : EXPORT_SYMBOL(ipv6_find_hdr);
|