ip6opt.c revision 1.9 1 /* $NetBSD: ip6opt.c,v 1.9 2002/05/05 08:28:47 simonb Exp $ */
2
3 /*
4 * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the project nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32 #include <sys/cdefs.h>
33 #if defined(LIBC_SCCS) && !defined(lint)
34 __RCSID("$NetBSD: ip6opt.c,v 1.9 2002/05/05 08:28:47 simonb Exp $");
35 #endif /* LIBC_SCCS and not lint */
36
37 #include "namespace.h"
38 #include <sys/param.h>
39 #include <sys/types.h>
40 #include <sys/socket.h>
41
42 #include <netinet/in.h>
43 #include <netinet/ip6.h>
44
45 #include <assert.h>
46 #include <string.h>
47 #include <stdio.h>
48
49 #ifdef __weak_alias
50 __weak_alias(inet6_option_alloc,_inet6_option_alloc)
51 __weak_alias(inet6_option_append,_inet6_option_append)
52 __weak_alias(inet6_option_find,_inet6_option_find)
53 __weak_alias(inet6_option_init,_inet6_option_init)
54 __weak_alias(inet6_option_next,_inet6_option_next)
55 __weak_alias(inet6_option_space,_inet6_option_space)
56 #endif
57
58 static int ip6optlen(u_int8_t *opt, u_int8_t *lim);
59 static void inet6_insert_padopt(u_char *p, size_t len);
60
61 /*
62 * This function returns the number of bytes required to hold an option
63 * when it is stored as ancillary data, including the cmsghdr structure
64 * at the beginning, and any padding at the end (to make its size a
65 * multiple of 8 bytes). The argument is the size of the structure
66 * defining the option, which must include any pad bytes at the
67 * beginning (the value y in the alignment term "xn + y"), the type
68 * byte, the length byte, and the option data.
69 */
70 int
71 inet6_option_space(nbytes)
72 int nbytes;
73 {
74 nbytes += 2; /* we need space for nxt-hdr and length fields */
75 return(CMSG_SPACE((nbytes + 7) & ~7));
76 }
77
78 /*
79 * This function is called once per ancillary data object that will
80 * contain either Hop-by-Hop or Destination options. It returns 0 on
81 * success or -1 on an error.
82 */
83 int
84 inet6_option_init(bp, cmsgp, type)
85 void *bp;
86 struct cmsghdr **cmsgp;
87 int type;
88 {
89 register struct cmsghdr *ch;
90
91 _DIAGASSERT(bp != NULL);
92 _DIAGASSERT(cmsgp != NULL);
93
94 ch = (struct cmsghdr *)bp;
95
96 /* argument validation */
97 if (type != IPV6_HOPOPTS && type != IPV6_DSTOPTS)
98 return(-1);
99
100 ch->cmsg_level = IPPROTO_IPV6;
101 ch->cmsg_type = type;
102 ch->cmsg_len = CMSG_LEN(0);
103
104 *cmsgp = ch;
105 return(0);
106 }
107
108 /*
109 * This function appends a Hop-by-Hop option or a Destination option
110 * into an ancillary data object that has been initialized by
111 * inet6_option_init(). This function returns 0 if it succeeds or -1 on
112 * an error.
113 * multx is the value x in the alignment term "xn + y" described
114 * earlier. It must have a value of 1, 2, 4, or 8.
115 * plusy is the value y in the alignment term "xn + y" described
116 * earlier. It must have a value between 0 and 7, inclusive.
117 */
118 int
119 inet6_option_append(cmsg, typep, multx, plusy)
120 struct cmsghdr *cmsg;
121 const u_int8_t *typep;
122 int multx;
123 int plusy;
124 {
125 size_t padlen, optlen, off;
126 register u_char *bp;
127 struct ip6_ext *eh;
128
129 _DIAGASSERT(cmsg != NULL);
130 _DIAGASSERT(typep != NULL);
131
132 bp = (u_char *)(void *)cmsg + cmsg->cmsg_len;
133 eh = (struct ip6_ext *)(void *)CMSG_DATA(cmsg);
134
135 /* argument validation */
136 if (multx != 1 && multx != 2 && multx != 4 && multx != 8)
137 return(-1);
138 if (plusy < 0 || plusy > 7)
139 return(-1);
140
141 /*
142 * If this is the first option, allocate space for the
143 * first 2 bytes(for next header and length fields) of
144 * the option header.
145 */
146 if (bp == (u_char *)(void *)eh) {
147 bp += 2;
148 cmsg->cmsg_len += 2;
149 }
150
151 /* calculate pad length before the option. */
152 off = bp - (u_char *)(void *)eh;
153 padlen = (((off % multx) + (multx - 1)) & ~(multx - 1)) -
154 (off % multx);
155 padlen += plusy;
156 /* insert padding */
157 inet6_insert_padopt(bp, padlen);
158 cmsg->cmsg_len += padlen;
159 bp += padlen;
160
161 /* copy the option */
162 if (typep[0] == IP6OPT_PAD1)
163 optlen = 1;
164 else
165 optlen = typep[1] + 2;
166 memcpy(bp, typep, (size_t)optlen);
167 bp += optlen;
168 cmsg->cmsg_len += optlen;
169
170 /* calculate pad length after the option and insert the padding */
171 off = bp - (u_char *)(void *)eh;
172 padlen = ((off + 7) & ~7) - off;
173 inet6_insert_padopt(bp, padlen);
174 bp += padlen;
175 cmsg->cmsg_len += padlen;
176
177 /* update the length field of the ip6 option header */
178 off = bp - (u_char *)(void *)eh;
179 eh->ip6e_len = (off >> 3) - 1;
180
181 return(0);
182 }
183
184 /*
185 * This function appends a Hop-by-Hop option or a Destination option
186 * into an ancillary data object that has been initialized by
187 * inet6_option_init(). This function returns a pointer to the 8-bit
188 * option type field that starts the option on success, or NULL on an
189 * error.
190 * The difference between this function and inet6_option_append() is
191 * that the latter copies the contents of a previously built option into
192 * the ancillary data object while the current function returns a
193 * pointer to the space in the data object where the option's TLV must
194 * then be built by the caller.
195 *
196 */
197 u_int8_t *
198 inet6_option_alloc(cmsg, datalen, multx, plusy)
199 struct cmsghdr *cmsg;
200 int datalen;
201 int multx;
202 int plusy;
203 {
204 size_t padlen, off;
205 register u_int8_t *bp;
206 u_int8_t *retval;
207 struct ip6_ext *eh;
208
209 _DIAGASSERT(cmsg != NULL);
210
211 bp = (u_char *)(void *)cmsg + cmsg->cmsg_len;
212 eh = (struct ip6_ext *)(void *)CMSG_DATA(cmsg);
213
214 /* argument validation */
215 if (multx != 1 && multx != 2 && multx != 4 && multx != 8)
216 return(NULL);
217 if (plusy < 0 || plusy > 7)
218 return(NULL);
219
220 /*
221 * If this is the first option, allocate space for the
222 * first 2 bytes(for next header and length fields) of
223 * the option header.
224 */
225 if (bp == (u_char *)(void *)eh) {
226 bp += 2;
227 cmsg->cmsg_len += 2;
228 }
229
230 /* calculate pad length before the option. */
231 off = bp - (u_char *)(void *)eh;
232 padlen = (((off % multx) + (multx - 1)) & ~(multx - 1)) -
233 (off % multx);
234 padlen += plusy;
235 /* insert padding */
236 inet6_insert_padopt(bp, padlen);
237 cmsg->cmsg_len += padlen;
238 bp += padlen;
239
240 /* keep space to store specified length of data */
241 retval = bp;
242 bp += datalen;
243 cmsg->cmsg_len += datalen;
244
245 /* calculate pad length after the option and insert the padding */
246 off = bp - (u_char *)(void *)eh;
247 padlen = ((off + 7) & ~7) - off;
248 inet6_insert_padopt(bp, padlen);
249 bp += padlen;
250 cmsg->cmsg_len += padlen;
251
252 /* update the length field of the ip6 option header */
253 off = bp - (u_char *)(void *)eh;
254 eh->ip6e_len = (off >> 3) - 1;
255
256 return(retval);
257 }
258
259 /*
260 * This function processes the next Hop-by-Hop option or Destination
261 * option in an ancillary data object. If another option remains to be
262 * processed, the return value of the function is 0 and *tptrp points to
263 * the 8-bit option type field (which is followed by the 8-bit option
264 * data length, followed by the option data). If no more options remain
265 * to be processed, the return value is -1 and *tptrp is NULL. If an
266 * error occurs, the return value is -1 and *tptrp is not NULL.
267 * (RFC 2292, 6.3.5)
268 */
269 int
270 inet6_option_next(cmsg, tptrp)
271 const struct cmsghdr *cmsg;
272 u_int8_t **tptrp;
273 {
274 struct ip6_ext *ip6e;
275 int hdrlen, optlen;
276 u_int8_t *lim;
277
278 _DIAGASSERT(cmsg != NULL);
279 _DIAGASSERT(tptrp != NULL);
280
281 if (cmsg->cmsg_level != IPPROTO_IPV6 ||
282 (cmsg->cmsg_type != IPV6_HOPOPTS &&
283 cmsg->cmsg_type != IPV6_DSTOPTS))
284 return(-1);
285
286 /* message length validation */
287 if (cmsg->cmsg_len < CMSG_SPACE(sizeof(struct ip6_ext)))
288 return(-1);
289 /* LINTED const castaway */
290 ip6e = (struct ip6_ext *)(void *)CMSG_DATA(cmsg);
291 hdrlen = (ip6e->ip6e_len + 1) << 3;
292 if (cmsg->cmsg_len < CMSG_SPACE(hdrlen))
293 return(-1);
294
295 /*
296 * If the caller does not specify the starting point,
297 * simply return the 1st option.
298 * Otherwise, search the option list for the next option.
299 */
300 lim = (u_int8_t *)(void *)ip6e + hdrlen;
301 if (*tptrp == NULL)
302 *tptrp = (u_int8_t *)(void *)(ip6e + 1);
303 else {
304 if ((optlen = ip6optlen(*tptrp, lim)) == 0)
305 return(-1);
306
307 *tptrp = *tptrp + optlen;
308 }
309 if (*tptrp >= lim) { /* there is no option */
310 *tptrp = NULL;
311 return(-1);
312 }
313 /*
314 * Finally, checks if the next option is safely stored in the
315 * cmsg data.
316 */
317 if (ip6optlen(*tptrp, lim) == 0)
318 return(-1);
319 else
320 return(0);
321 }
322
323 /*
324 * This function is similar to the inet6_option_next() function,
325 * except this function lets the caller specify the option type to be
326 * searched for, instead of always returning the next option in the
327 * ancillary data object.
328 * Note: RFC 2292 says the type of tptrp is u_int8_t *, but we think
329 * it's a typo. The variable should be type of u_int8_t **.
330 */
331 int
332 inet6_option_find(cmsg, tptrp, type)
333 const struct cmsghdr *cmsg;
334 u_int8_t **tptrp;
335 int type;
336 {
337 struct ip6_ext *ip6e;
338 int hdrlen, optlen;
339 u_int8_t *optp, *lim;
340
341 _DIAGASSERT(cmsg != NULL);
342 _DIAGASSERT(tptrp != NULL);
343
344 if (cmsg->cmsg_level != IPPROTO_IPV6 ||
345 (cmsg->cmsg_type != IPV6_HOPOPTS &&
346 cmsg->cmsg_type != IPV6_DSTOPTS))
347 return(-1);
348
349 /* message length validation */
350 if (cmsg->cmsg_len < CMSG_SPACE(sizeof(struct ip6_ext)))
351 return(-1);
352 /* LINTED const castaway */
353 ip6e = (struct ip6_ext *)(void *)CMSG_DATA(cmsg);
354 hdrlen = (ip6e->ip6e_len + 1) << 3;
355 if (cmsg->cmsg_len < CMSG_SPACE(hdrlen))
356 return(-1);
357
358 /*
359 * If the caller does not specify the starting point,
360 * search from the beginning of the option list.
361 * Otherwise, search from *the next option* of the specified point.
362 */
363 lim = (u_int8_t *)(void *)ip6e + hdrlen;
364 if (*tptrp == NULL)
365 *tptrp = (u_int8_t *)(void *)(ip6e + 1);
366 else {
367 if ((optlen = ip6optlen(*tptrp, lim)) == 0)
368 return(-1);
369
370 *tptrp = *tptrp + optlen;
371 }
372 for (optp = *tptrp; optp < lim; optp += optlen) {
373 if (*optp == type) {
374 *tptrp = optp;
375 return(0);
376 }
377 if ((optlen = ip6optlen(optp, lim)) == 0)
378 return(-1);
379 }
380
381 /* search failed */
382 *tptrp = NULL;
383 return(-1);
384 }
385
386 /*
387 * Calculate the length of a given IPv6 option. Also checks
388 * if the option is safely stored in user's buffer according to the
389 * calculated length and the limitation of the buffer.
390 */
391 static int
392 ip6optlen(opt, lim)
393 u_int8_t *opt, *lim;
394 {
395 int optlen;
396
397 _DIAGASSERT(opt != NULL);
398 _DIAGASSERT(lim != NULL);
399
400 if (*opt == IP6OPT_PAD1)
401 optlen = 1;
402 else {
403 /* is there enough space to store type and len? */
404 if (opt + 2 > lim)
405 return(0);
406 optlen = *(opt + 1) + 2;
407 }
408 if (opt + optlen <= lim)
409 return(optlen);
410
411 return(0);
412 }
413
414 static void
415 inet6_insert_padopt(u_char *p, size_t len)
416 {
417
418 _DIAGASSERT(p != NULL);
419
420 switch(len) {
421 case 0:
422 return;
423 case 1:
424 p[0] = IP6OPT_PAD1;
425 return;
426 default:
427 p[0] = IP6OPT_PADN;
428 p[1] = len - 2;
429 memset(&p[2], 0, len - 2);
430 return;
431 }
432 }
433