xref: /freebsd/lib/libc/net/ip6opt.c (revision 2e3507c25e42292b45a5482e116d278f5515d04d)
1 /*	$KAME: ip6opt.c,v 1.13 2003/06/06 10:08:20 suz Exp $	*/
2 
3 /*-
4  * SPDX-License-Identifier: BSD-3-Clause
5  *
6  * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. Neither the name of the project nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 #include <sys/param.h>
35 #include <sys/socket.h>
36 
37 #include <netinet/in.h>
38 #include <netinet/ip6.h>
39 
40 #include <string.h>
41 #include <stdio.h>
42 
43 static int ip6optlen(u_int8_t *opt, u_int8_t *lim);
44 static void inet6_insert_padopt(u_char *p, int len);
45 
46 #ifndef IPV6_2292HOPOPTS
47 #define	IPV6_2292HOPOPTS	22
48 #endif
49 #ifndef IPV6_2292DSTOPTS
50 #define	IPV6_2292DSTOPTS	23
51 #endif
52 
53 #define	is_ipv6_hopopts(x)	\
54 	((x) == IPV6_HOPOPTS || (x) == IPV6_2292HOPOPTS)
55 #define	is_ipv6_dstopts(x)	\
56 	((x) == IPV6_DSTOPTS || (x) == IPV6_2292DSTOPTS)
57 
58 /*
59  * This function returns the number of bytes required to hold an option
60  * when it is stored as ancillary data, including the cmsghdr structure
61  * at the beginning, and any padding at the end (to make its size a
62  * multiple of 8 bytes).  The argument is the size of the structure
63  * defining the option, which must include any pad bytes at the
64  * beginning (the value y in the alignment term "xn + y"), the type
65  * byte, the length byte, and the option data.
66  */
67 int
68 inet6_option_space(int nbytes)
69 {
70 	nbytes += 2;	/* we need space for nxt-hdr and length fields */
71 	return(CMSG_SPACE((nbytes + 7) & ~7));
72 }
73 
74 /*
75  * This function is called once per ancillary data object that will
76  * contain either Hop-by-Hop or Destination options.  It returns 0 on
77  * success or -1 on an error.
78  */
79 int
80 inet6_option_init(void *bp, struct cmsghdr **cmsgp, int type)
81 {
82 	struct cmsghdr *ch = (struct cmsghdr *)bp;
83 
84 	/* argument validation */
85 	if (!is_ipv6_hopopts(type) && !is_ipv6_dstopts(type))
86 		return(-1);
87 
88 	ch->cmsg_level = IPPROTO_IPV6;
89 	ch->cmsg_type = type;
90 	ch->cmsg_len = CMSG_LEN(0);
91 
92 	*cmsgp = ch;
93 	return(0);
94 }
95 
96 /*
97  * This function appends a Hop-by-Hop option or a Destination option
98  * into an ancillary data object that has been initialized by
99  * inet6_option_init().  This function returns 0 if it succeeds or -1 on
100  * an error.
101  * multx is the value x in the alignment term "xn + y" described
102  * earlier.  It must have a value of 1, 2, 4, or 8.
103  * plusy is the value y in the alignment term "xn + y" described
104  * earlier.  It must have a value between 0 and 7, inclusive.
105  */
106 int
107 inet6_option_append(struct cmsghdr *cmsg, const u_int8_t *typep, int multx,
108     int plusy)
109 {
110 	int padlen, optlen, off;
111 	u_char *bp = (u_char *)cmsg + cmsg->cmsg_len;
112 	struct ip6_ext *eh = (struct ip6_ext *)CMSG_DATA(cmsg);
113 
114 	/* argument validation */
115 	if (multx != 1 && multx != 2 && multx != 4 && multx != 8)
116 		return(-1);
117 	if (plusy < 0 || plusy > 7)
118 		return(-1);
119 
120 	/*
121 	 * If this is the first option, allocate space for the
122 	 * first 2 bytes(for next header and length fields) of
123 	 * the option header.
124 	 */
125 	if (bp == (u_char *)eh) {
126 		bp += 2;
127 		cmsg->cmsg_len += 2;
128 	}
129 
130 	/* calculate pad length before the option. */
131 	off = bp - (u_char *)eh;
132 	padlen = roundup2(off % multx, multx) - (off % multx);
133 	padlen += plusy;
134 	padlen %= multx;	/* keep the pad as short as possible */
135 	/* insert padding */
136 	inet6_insert_padopt(bp, padlen);
137 	cmsg->cmsg_len += padlen;
138 	bp += padlen;
139 
140 	/* copy the option */
141 	if (typep[0] == IP6OPT_PAD1)
142 		optlen = 1;
143 	else
144 		optlen = typep[1] + 2;
145 	memcpy(bp, typep, optlen);
146 	bp += optlen;
147 	cmsg->cmsg_len += optlen;
148 
149 	/* calculate pad length after the option and insert the padding */
150 	off = bp - (u_char *)eh;
151 	padlen = ((off + 7) & ~7) - off;
152 	inet6_insert_padopt(bp, padlen);
153 	bp += padlen;
154 	cmsg->cmsg_len += padlen;
155 
156 	/* update the length field of the ip6 option header */
157 	eh->ip6e_len = ((bp - (u_char *)eh) >> 3) - 1;
158 
159 	return(0);
160 }
161 
162 /*
163  * This function appends a Hop-by-Hop option or a Destination option
164  * into an ancillary data object that has been initialized by
165  * inet6_option_init().  This function returns a pointer to the 8-bit
166  * option type field that starts the option on success, or NULL on an
167  * error.
168  * The difference between this function and inet6_option_append() is
169  * that the latter copies the contents of a previously built option into
170  * the ancillary data object while the current function returns a
171  * pointer to the space in the data object where the option's TLV must
172  * then be built by the caller.
173  *
174  */
175 u_int8_t *
176 inet6_option_alloc(struct cmsghdr *cmsg, int datalen, int multx, int plusy)
177 {
178 	int padlen, off;
179 	u_int8_t *bp = (u_char *)cmsg + cmsg->cmsg_len;
180 	u_int8_t *retval;
181 	struct ip6_ext *eh = (struct ip6_ext *)CMSG_DATA(cmsg);
182 
183 	/* argument validation */
184 	if (multx != 1 && multx != 2 && multx != 4 && multx != 8)
185 		return(NULL);
186 	if (plusy < 0 || plusy > 7)
187 		return(NULL);
188 
189 	/*
190 	 * If this is the first option, allocate space for the
191 	 * first 2 bytes(for next header and length fields) of
192 	 * the option header.
193 	 */
194 	if (bp == (u_char *)eh) {
195 		bp += 2;
196 		cmsg->cmsg_len += 2;
197 	}
198 
199 	/* calculate pad length before the option. */
200 	off = bp - (u_char *)eh;
201 	padlen = roundup2(off % multx, multx) - (off % multx);
202 	padlen += plusy;
203 	padlen %= multx;	/* keep the pad as short as possible */
204 	/* insert padding */
205 	inet6_insert_padopt(bp, padlen);
206 	cmsg->cmsg_len += padlen;
207 	bp += padlen;
208 
209 	/* keep space to store specified length of data */
210 	retval = bp;
211 	bp += datalen;
212 	cmsg->cmsg_len += datalen;
213 
214 	/* calculate pad length after the option and insert the padding */
215 	off = bp - (u_char *)eh;
216 	padlen = ((off + 7) & ~7) - off;
217 	inet6_insert_padopt(bp, padlen);
218 	bp += padlen;
219 	cmsg->cmsg_len += padlen;
220 
221 	/* update the length field of the ip6 option header */
222 	eh->ip6e_len = ((bp - (u_char *)eh) >> 3) - 1;
223 
224 	return(retval);
225 }
226 
227 /*
228  * This function processes the next Hop-by-Hop option or Destination
229  * option in an ancillary data object.  If another option remains to be
230  * processed, the return value of the function is 0 and *tptrp points to
231  * the 8-bit option type field (which is followed by the 8-bit option
232  * data length, followed by the option data).  If no more options remain
233  * to be processed, the return value is -1 and *tptrp is NULL.  If an
234  * error occurs, the return value is -1 and *tptrp is not NULL.
235  * (RFC 2292, 6.3.5)
236  */
237 int
238 inet6_option_next(const struct cmsghdr *cmsg, u_int8_t **tptrp)
239 {
240 	struct ip6_ext *ip6e;
241 	int hdrlen, optlen;
242 	u_int8_t *lim;
243 
244 	if (cmsg->cmsg_level != IPPROTO_IPV6 ||
245 	    (!is_ipv6_hopopts(cmsg->cmsg_type) &&
246 	     !is_ipv6_dstopts(cmsg->cmsg_type)))
247 		return(-1);
248 
249 	/* message length validation */
250 	if (cmsg->cmsg_len < CMSG_SPACE(sizeof(struct ip6_ext)))
251 		return(-1);
252 	ip6e = (struct ip6_ext *)CMSG_DATA(cmsg);
253 	hdrlen = (ip6e->ip6e_len + 1) << 3;
254 	if (cmsg->cmsg_len < CMSG_SPACE(hdrlen))
255 		return(-1);
256 
257 	/*
258 	 * If the caller does not specify the starting point,
259 	 * simply return the 1st option.
260 	 * Otherwise, search the option list for the next option.
261 	 */
262 	lim = (u_int8_t *)ip6e + hdrlen;
263 	if (*tptrp == NULL)
264 		*tptrp = (u_int8_t *)(ip6e + 1);
265 	else {
266 		if ((optlen = ip6optlen(*tptrp, lim)) == 0)
267 			return(-1);
268 
269 		*tptrp = *tptrp + optlen;
270 	}
271 	if (*tptrp >= lim) {	/* there is no option */
272 		*tptrp = NULL;
273 		return(-1);
274 	}
275 	/*
276 	 * Finally, checks if the next option is safely stored in the
277 	 * cmsg data.
278 	 */
279 	if (ip6optlen(*tptrp, lim) == 0)
280 		return(-1);
281 	else
282 		return(0);
283 }
284 
285 /*
286  * This function is similar to the inet6_option_next() function,
287  * except this function lets the caller specify the option type to be
288  * searched for, instead of always returning the next option in the
289  * ancillary data object.
290  * Note: RFC 2292 says the type of tptrp is u_int8_t *, but we think
291  *       it's a typo. The variable should be type of u_int8_t **.
292  */
293 int
294 inet6_option_find(const struct cmsghdr *cmsg, u_int8_t **tptrp, int type)
295 {
296 	struct ip6_ext *ip6e;
297 	int hdrlen, optlen;
298 	u_int8_t *optp, *lim;
299 
300 	if (cmsg->cmsg_level != IPPROTO_IPV6 ||
301 	    (!is_ipv6_hopopts(cmsg->cmsg_type) &&
302 	     !is_ipv6_dstopts(cmsg->cmsg_type)))
303 		return(-1);
304 
305 	/* message length validation */
306 	if (cmsg->cmsg_len < CMSG_SPACE(sizeof(struct ip6_ext)))
307 		return(-1);
308 	ip6e = (struct ip6_ext *)CMSG_DATA(cmsg);
309 	hdrlen = (ip6e->ip6e_len + 1) << 3;
310 	if (cmsg->cmsg_len < CMSG_SPACE(hdrlen))
311 		return(-1);
312 
313 	/*
314 	 * If the caller does not specify the starting point,
315 	 * search from the beginning of the option list.
316 	 * Otherwise, search from *the next option* of the specified point.
317 	 */
318 	lim = (u_int8_t *)ip6e + hdrlen;
319 	if (*tptrp == NULL)
320 		*tptrp = (u_int8_t *)(ip6e + 1);
321 	else {
322 		if ((optlen = ip6optlen(*tptrp, lim)) == 0)
323 			return(-1);
324 
325 		*tptrp = *tptrp + optlen;
326 	}
327 	for (optp = *tptrp; optp < lim; optp += optlen) {
328 		if (*optp == type) {
329 			*tptrp = optp;
330 			return(0);
331 		}
332 		if ((optlen = ip6optlen(optp, lim)) == 0)
333 			return(-1);
334 	}
335 
336 	/* search failed */
337 	*tptrp = NULL;
338 	return(-1);
339 }
340 
341 /*
342  * Calculate the length of a given IPv6 option. Also checks
343  * if the option is safely stored in user's buffer according to the
344  * calculated length and the limitation of the buffer.
345  */
346 static int
347 ip6optlen(u_int8_t *opt, u_int8_t *lim)
348 {
349 	int optlen;
350 
351 	if (*opt == IP6OPT_PAD1)
352 		optlen = 1;
353 	else {
354 		/* is there enough space to store type and len? */
355 		if (opt + 2 > lim)
356 			return(0);
357 		optlen = *(opt + 1) + 2;
358 	}
359 	if (opt + optlen <= lim)
360 		return(optlen);
361 
362 	return(0);
363 }
364 
365 static void
366 inet6_insert_padopt(u_char *p, int len)
367 {
368 	switch(len) {
369 	 case 0:
370 		 return;
371 	 case 1:
372 		 p[0] = IP6OPT_PAD1;
373 		 return;
374 	 default:
375 		 p[0] = IP6OPT_PADN;
376 		 p[1] = len - 2;
377 		 memset(&p[2], 0, len - 2);
378 		 return;
379 	}
380 }
381 
382 /*
383  * The following functions are defined in RFC3542, which is a successor
384  * of RFC2292.
385  */
386 
387 int
388 inet6_opt_init(void *extbuf, socklen_t extlen)
389 {
390 	struct ip6_ext *ext = (struct ip6_ext *)extbuf;
391 
392 	if (ext) {
393 		if (extlen <= 0 || (extlen % 8))
394 			return(-1);
395 		ext->ip6e_len = (extlen >> 3) - 1;
396 	}
397 
398 	return(2);		/* sizeof the next and the length fields */
399 }
400 
401 int
402 inet6_opt_append(void *extbuf, socklen_t extlen, int offset, u_int8_t type,
403 		 socklen_t len, u_int8_t align, void **databufp)
404 {
405 	int currentlen = offset, padlen = 0;
406 
407 	/*
408 	 * The option type must have a value from 2 to 255, inclusive.
409 	 * (0 and 1 are reserved for the Pad1 and PadN options, respectively.)
410 	 */
411 	if (type < 2)
412 		return(-1);
413 
414 	/*
415 	 * The option data length must have a value between 0 and 255,
416 	 * inclusive, and is the length of the option data that follows.
417 	 */
418 	if (len > 255 || len < 0 )
419 		return(-1);
420 
421 	/*
422 	 * The align parameter must have a value of 1, 2, 4, or 8.
423 	 * The align value can not exceed the value of len.
424 	 */
425 	if (align != 1 && align != 2 && align != 4 && align != 8)
426 		return(-1);
427 	if (align > len)
428 		return(-1);
429 
430 	/* Calculate the padding length. */
431 	currentlen += 2 + len;	/* 2 means "type + len" */
432 	if (currentlen % align)
433 		padlen = align - (currentlen % align);
434 
435 	/* The option must fit in the extension header buffer. */
436 	currentlen += padlen;
437 	if (extlen &&		/* XXX: right? */
438 	    currentlen > extlen)
439 		return(-1);
440 
441 	if (extbuf) {
442 		u_int8_t *optp = (u_int8_t *)extbuf + offset;
443 
444 		if (padlen == 1) {
445 			/* insert a Pad1 option */
446 			*optp = IP6OPT_PAD1;
447 			optp++;
448 		}
449 		else if (padlen > 0) {
450 			/* insert a PadN option for alignment */
451 			*optp++ = IP6OPT_PADN;
452 			*optp++ = padlen - 2;
453 			memset(optp, 0, padlen - 2);
454 			optp += (padlen - 2);
455 		}
456 
457 		*optp++ = type;
458 		*optp++ = len;
459 
460 		*databufp = optp;
461 	}
462 
463 	return(currentlen);
464 }
465 
466 int
467 inet6_opt_finish(void *extbuf, socklen_t extlen, int offset)
468 {
469 	int updatelen = offset > 0 ? (1 + ((offset - 1) | 7)) : 0;
470 
471 	if (extbuf) {
472 		u_int8_t *padp;
473 		int padlen = updatelen - offset;
474 
475 		if (updatelen > extlen)
476 			return(-1);
477 
478 		padp = (u_int8_t *)extbuf + offset;
479 		if (padlen == 1)
480 			*padp = IP6OPT_PAD1;
481 		else if (padlen > 0) {
482 			*padp++ = IP6OPT_PADN;
483 			*padp++ = (padlen - 2);
484 			memset(padp, 0, padlen - 2);
485 		}
486 	}
487 
488 	return(updatelen);
489 }
490 
491 int
492 inet6_opt_set_val(void *databuf, int offset, void *val, socklen_t vallen)
493 {
494 
495 	memcpy((u_int8_t *)databuf + offset, val, vallen);
496 	return(offset + vallen);
497 }
498 
499 int
500 inet6_opt_next(void *extbuf, socklen_t extlen, int offset, u_int8_t *typep,
501 	       socklen_t *lenp, void **databufp)
502 {
503 	u_int8_t *optp, *lim;
504 	int optlen;
505 
506 	/* Validate extlen. XXX: is the variable really necessary?? */
507 	if (extlen == 0 || (extlen % 8))
508 		return(-1);
509 	lim = (u_int8_t *)extbuf + extlen;
510 
511 	/*
512 	 * If this is the first time this function called for this options
513 	 * header, simply return the 1st option.
514 	 * Otherwise, search the option list for the next option.
515 	 */
516 	if (offset == 0) {
517 		optp = (u_int8_t *)((struct ip6_hbh *)extbuf + 1);
518 	}
519 	else
520 		optp = (u_int8_t *)extbuf + offset;
521 
522 	/* Find the next option skipping any padding options. */
523 	while(optp < lim) {
524 		switch(*optp) {
525 		case IP6OPT_PAD1:
526 			optp++;
527 			break;
528 		case IP6OPT_PADN:
529 			if ((optlen = ip6optlen(optp, lim)) == 0)
530 				goto optend;
531 			optp += optlen;
532 			break;
533 		default:	/* found */
534 			if ((optlen = ip6optlen(optp, lim)) == 0)
535 				goto optend;
536 			*typep = *optp;
537 			*lenp = optlen - 2;
538 			*databufp = optp + 2;
539 			return(optp + optlen - (u_int8_t *)extbuf);
540 		}
541 	}
542 
543   optend:
544 	*databufp = NULL; /* for safety */
545 	return(-1);
546 }
547 
548 int
549 inet6_opt_find(void *extbuf, socklen_t extlen, int offset, u_int8_t type,
550 	       socklen_t *lenp, void **databufp)
551 {
552 	u_int8_t *optp, *lim;
553 	int optlen;
554 
555 	/* Validate extlen. XXX: is the variable really necessary?? */
556 	if (extlen == 0 || (extlen % 8))
557 		return(-1);
558 	lim = (u_int8_t *)extbuf + extlen;
559 
560 	/*
561 	 * If this is the first time this function called for this options
562 	 * header, simply return the 1st option.
563 	 * Otherwise, search the option list for the next option.
564 	 */
565 	if (offset == 0) {
566 		optp = (u_int8_t *)((struct ip6_hbh *)extbuf + 1);
567 	}
568 	else
569 		optp = (u_int8_t *)extbuf + offset;
570 
571 	/* Find the specified option */
572 	while(optp < lim) {
573 		if ((optlen = ip6optlen(optp, lim)) == 0)
574 			goto optend;
575 
576 		if (*optp == type) { /* found */
577 			*lenp = optlen - 2;
578 			*databufp = optp + 2;
579 			return(optp + optlen - (u_int8_t *)extbuf);
580 		}
581 
582 		optp += optlen;
583 	}
584 
585   optend:
586 	*databufp = NULL; /* for safety */
587 	return(-1);
588 }
589 
590 int
591 inet6_opt_get_val(void *databuf, int offset, void *val, socklen_t vallen)
592 {
593 
594 	/* we can't assume alignment here */
595 	memcpy(val, (u_int8_t *)databuf + offset, vallen);
596 
597 	return(offset + vallen);
598 }
599