| 1 | /*	$NetBSD: ip6opt.c,v 1.15 2014/02/07 02:36:06 christos 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.15 2014/02/07 02:36:06 christos 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 <stddef.h> | 
| 47 | #include <string.h> | 
| 48 | #include <stdio.h> | 
| 49 |  | 
| 50 | #ifdef __weak_alias | 
| 51 | __weak_alias(inet6_option_alloc,_inet6_option_alloc) | 
| 52 | __weak_alias(inet6_option_append,_inet6_option_append) | 
| 53 | __weak_alias(inet6_option_find,_inet6_option_find) | 
| 54 | __weak_alias(inet6_option_init,_inet6_option_init) | 
| 55 | __weak_alias(inet6_option_next,_inet6_option_next) | 
| 56 | __weak_alias(inet6_option_space,_inet6_option_space) | 
| 57 | __weak_alias(inet6_opt_init, _inet6_opt_init) | 
| 58 | __weak_alias(inet6_opt_append, _inet6_opt_append) | 
| 59 | __weak_alias(inet6_opt_finish, _inet6_opt_finish) | 
| 60 | __weak_alias(inet6_opt_set_val, _inet6_opt_set_val) | 
| 61 | __weak_alias(inet6_opt_next, _inet6_opt_next) | 
| 62 | __weak_alias(inet6_opt_find, _inet6_opt_find) | 
| 63 | __weak_alias(inet6_opt_get_val, _inet6_opt_get_val) | 
| 64 | #endif | 
| 65 |  | 
| 66 | static int ip6optlen(uint8_t *opt, uint8_t *lim); | 
| 67 | static void inet6_insert_padopt(uint8_t *p, size_t len); | 
| 68 |  | 
| 69 | /* | 
| 70 |  * This function returns the number of bytes required to hold an option | 
| 71 |  * when it is stored as ancillary data, including the cmsghdr structure | 
| 72 |  * at the beginning, and any padding at the end (to make its size a | 
| 73 |  * multiple of 8 bytes).  The argument is the size of the structure | 
| 74 |  * defining the option, which must include any pad bytes at the | 
| 75 |  * beginning (the value y in the alignment term "xn + y"), the type | 
| 76 |  * byte, the length byte, and the option data. | 
| 77 |  */ | 
| 78 | int | 
| 79 | inet6_option_space(int nbytes) | 
| 80 | { | 
| 81 | 	size_t sp; | 
| 82 | 	nbytes += 2;	/* we need space for nxt-hdr and length fields */ | 
| 83 | 	sp = CMSG_SPACE((nbytes + 7) & ~7); | 
| 84 | 	_DIAGASSERT(__type_fit(int, sp)); | 
| 85 | 	return (int)sp; | 
| 86 | } | 
| 87 |  | 
| 88 | /* | 
| 89 |  * This function is called once per ancillary data object that will | 
| 90 |  * contain either Hop-by-Hop or Destination options.  It returns 0 on | 
| 91 |  * success or -1 on an error. | 
| 92 |  */ | 
| 93 | int | 
| 94 | inet6_option_init(void *bp, struct cmsghdr **cmsgp, int type) | 
| 95 | { | 
| 96 | 	register struct cmsghdr *ch; | 
| 97 |  | 
| 98 | 	_DIAGASSERT(bp != NULL); | 
| 99 | 	_DIAGASSERT(cmsgp != NULL); | 
| 100 |  | 
| 101 | 	ch = (struct cmsghdr *)bp; | 
| 102 |  | 
| 103 | 	/* argument validation */ | 
| 104 | 	if (type != IPV6_HOPOPTS && type != IPV6_DSTOPTS) | 
| 105 | 		return(-1); | 
| 106 | 	 | 
| 107 | 	ch->cmsg_level = IPPROTO_IPV6; | 
| 108 | 	ch->cmsg_type = type; | 
| 109 | 	ch->cmsg_len = CMSG_LEN(0); | 
| 110 |  | 
| 111 | 	*cmsgp = ch; | 
| 112 | 	return(0); | 
| 113 | } | 
| 114 |  | 
| 115 | /* | 
| 116 |  * This function appends a Hop-by-Hop option or a Destination option | 
| 117 |  * into an ancillary data object that has been initialized by | 
| 118 |  * inet6_option_init().  This function returns 0 if it succeeds or -1 on | 
| 119 |  * an error. | 
| 120 |  * multx is the value x in the alignment term "xn + y" described | 
| 121 |  * earlier.  It must have a value of 1, 2, 4, or 8. | 
| 122 |  * plusy is the value y in the alignment term "xn + y" described | 
| 123 |  * earlier.  It must have a value between 0 and 7, inclusive. | 
| 124 |  */ | 
| 125 | int | 
| 126 | inet6_option_append(struct cmsghdr *cmsg, const uint8_t *typep, int multx, | 
| 127 | 	int plusy) | 
| 128 | { | 
| 129 | 	size_t padlen, optlen, off; | 
| 130 | 	register uint8_t *bp; | 
| 131 | 	struct ip6_ext *eh; | 
| 132 |  | 
| 133 | 	_DIAGASSERT(cmsg != NULL); | 
| 134 | 	_DIAGASSERT(typep != NULL); | 
| 135 |  | 
| 136 | 	bp = (uint8_t *)(void *)cmsg + cmsg->cmsg_len; | 
| 137 | 	eh = (struct ip6_ext *)(void *)CMSG_DATA(cmsg); | 
| 138 |  | 
| 139 | 	/* argument validation */ | 
| 140 | 	if (multx != 1 && multx != 2 && multx != 4 && multx != 8) | 
| 141 | 		return(-1); | 
| 142 | 	if (plusy < 0 || plusy > 7) | 
| 143 | 		return(-1); | 
| 144 |  | 
| 145 | 	/* | 
| 146 | 	 * If this is the first option, allocate space for the | 
| 147 | 	 * first 2 bytes(for next header and length fields) of | 
| 148 | 	 * the option header. | 
| 149 | 	 */ | 
| 150 | 	if (bp == (uint8_t *)(void *)eh) { | 
| 151 | 		bp += 2; | 
| 152 | 		cmsg->cmsg_len += 2; | 
| 153 | 	} | 
| 154 |  | 
| 155 | 	/* calculate pad length before the option. */ | 
| 156 | 	off = bp - (uint8_t *)(void *)eh; | 
| 157 | 	padlen = (((off % multx) + (multx - 1)) & ~(multx - 1)) - | 
| 158 | 		(off % multx); | 
| 159 | 	padlen += plusy; | 
| 160 | 	padlen %= multx;	/* keep the pad as short as possible */ | 
| 161 | 	/* insert padding */ | 
| 162 | 	inet6_insert_padopt(bp, padlen); | 
| 163 | 	_DIAGASSERT(__type_fit(socklen_t, padlen + cmsg->cmsg_len)); | 
| 164 | 	cmsg->cmsg_len += (socklen_t)padlen; | 
| 165 | 	bp += padlen; | 
| 166 |  | 
| 167 | 	/* copy the option */ | 
| 168 | 	if (typep[0] == IP6OPT_PAD1) | 
| 169 | 		optlen = 1; | 
| 170 | 	else | 
| 171 | 		optlen = typep[1] + 2; | 
| 172 | 	memcpy(bp, typep, (size_t)optlen); | 
| 173 | 	bp += optlen; | 
| 174 | 	_DIAGASSERT(__type_fit(socklen_t, optlen + cmsg->cmsg_len)); | 
| 175 | 	cmsg->cmsg_len += (socklen_t)optlen; | 
| 176 |  | 
| 177 | 	/* calculate pad length after the option and insert the padding */ | 
| 178 | 	off = bp - (uint8_t *)(void *)eh; | 
| 179 | 	padlen = ((off + 7) & ~7) - off; | 
| 180 | 	inet6_insert_padopt(bp, padlen); | 
| 181 | 	bp += padlen; | 
| 182 | 	_DIAGASSERT(__type_fit(socklen_t, padlen + cmsg->cmsg_len)); | 
| 183 | 	cmsg->cmsg_len += (socklen_t)padlen; | 
| 184 |  | 
| 185 | 	/* update the length field of the ip6 option header */ | 
| 186 | 	off = bp - (uint8_t *)(void *)eh; | 
| 187 | 	_DIAGASSERT(__type_fit(uint8_t, (off >> 3) - 1)); | 
| 188 | 	eh->ip6e_len = (uint8_t)((off >> 3) - 1); | 
| 189 |  | 
| 190 | 	return(0); | 
| 191 | } | 
| 192 |  | 
| 193 | /* | 
| 194 |  * This function appends a Hop-by-Hop option or a Destination option | 
| 195 |  * into an ancillary data object that has been initialized by | 
| 196 |  * inet6_option_init().  This function returns a pointer to the 8-bit | 
| 197 |  * option type field that starts the option on success, or NULL on an | 
| 198 |  * error. | 
| 199 |  * The difference between this function and inet6_option_append() is | 
| 200 |  * that the latter copies the contents of a previously built option into | 
| 201 |  * the ancillary data object while the current function returns a | 
| 202 |  * pointer to the space in the data object where the option's TLV must | 
| 203 |  * then be built by the caller. | 
| 204 |  *  | 
| 205 |  */ | 
| 206 | uint8_t * | 
| 207 | inet6_option_alloc(struct cmsghdr *cmsg, int datalen, int multx, int plusy) | 
| 208 | { | 
| 209 | 	size_t padlen, off; | 
| 210 | 	register uint8_t *bp; | 
| 211 | 	uint8_t *retval; | 
| 212 | 	struct ip6_ext *eh; | 
| 213 |  | 
| 214 | 	_DIAGASSERT(cmsg != NULL); | 
| 215 |  | 
| 216 | 	bp = (uint8_t *)(void *)cmsg + cmsg->cmsg_len; | 
| 217 | 	eh = (struct ip6_ext *)(void *)CMSG_DATA(cmsg); | 
| 218 |  | 
| 219 | 	/* argument validation */ | 
| 220 | 	if (multx != 1 && multx != 2 && multx != 4 && multx != 8) | 
| 221 | 		return(NULL); | 
| 222 | 	if (plusy < 0 || plusy > 7) | 
| 223 | 		return(NULL); | 
| 224 |  | 
| 225 | 	/* | 
| 226 | 	 * If this is the first option, allocate space for the | 
| 227 | 	 * first 2 bytes(for next header and length fields) of | 
| 228 | 	 * the option header. | 
| 229 | 	 */ | 
| 230 | 	if (bp == (uint8_t *)(void *)eh) { | 
| 231 | 		bp += 2; | 
| 232 | 		cmsg->cmsg_len += 2; | 
| 233 | 	} | 
| 234 |  | 
| 235 | 	/* calculate pad length before the option. */ | 
| 236 | 	off = bp - (uint8_t *)(void *)eh; | 
| 237 | 	padlen = (((off % multx) + (multx - 1)) & ~(multx - 1)) - | 
| 238 | 		(off % multx); | 
| 239 | 	padlen += plusy; | 
| 240 | 	padlen %= multx;	/* keep the pad as short as possible */ | 
| 241 | 	/* insert padding */ | 
| 242 | 	inet6_insert_padopt(bp, padlen); | 
| 243 | 	cmsg->cmsg_len += (socklen_t)padlen; | 
| 244 | 	bp += padlen; | 
| 245 |  | 
| 246 | 	/* keep space to store specified length of data */ | 
| 247 | 	retval = bp; | 
| 248 | 	bp += datalen; | 
| 249 | 	cmsg->cmsg_len += datalen; | 
| 250 |  | 
| 251 | 	/* calculate pad length after the option and insert the padding */ | 
| 252 | 	off = bp - (uint8_t *)(void *)eh; | 
| 253 | 	padlen = ((off + 7) & ~7) - off; | 
| 254 | 	inet6_insert_padopt(bp, padlen); | 
| 255 | 	bp += padlen; | 
| 256 | 	_DIAGASSERT(__type_fit(socklen_t, padlen + cmsg->cmsg_len)); | 
| 257 | 	cmsg->cmsg_len += (socklen_t)padlen; | 
| 258 |  | 
| 259 | 	/* update the length field of the ip6 option header */ | 
| 260 | 	off = bp - (uint8_t *)(void *)eh; | 
| 261 | 	_DIAGASSERT(__type_fit(uint8_t, (off >> 3) - 1)); | 
| 262 | 	eh->ip6e_len = (uint8_t)((off >> 3) - 1); | 
| 263 |  | 
| 264 | 	return(retval); | 
| 265 | } | 
| 266 |  | 
| 267 | /* | 
| 268 |  * This function processes the next Hop-by-Hop option or Destination | 
| 269 |  * option in an ancillary data object.  If another option remains to be | 
| 270 |  * processed, the return value of the function is 0 and *tptrp points to | 
| 271 |  * the 8-bit option type field (which is followed by the 8-bit option | 
| 272 |  * data length, followed by the option data).  If no more options remain | 
| 273 |  * to be processed, the return value is -1 and *tptrp is NULL.  If an | 
| 274 |  * error occurs, the return value is -1 and *tptrp is not NULL. | 
| 275 |  * (RFC 2292, 6.3.5) | 
| 276 |  */ | 
| 277 | int | 
| 278 | inet6_option_next(const struct cmsghdr *cmsg, uint8_t **tptrp) | 
| 279 | { | 
| 280 | 	struct ip6_ext *ip6e; | 
| 281 | 	int hdrlen, optlen; | 
| 282 | 	uint8_t *lim; | 
| 283 |  | 
| 284 | 	_DIAGASSERT(cmsg != NULL); | 
| 285 | 	_DIAGASSERT(tptrp != NULL); | 
| 286 |  | 
| 287 | 	if (cmsg->cmsg_level != IPPROTO_IPV6 || | 
| 288 | 	    (cmsg->cmsg_type != IPV6_HOPOPTS && | 
| 289 | 	     cmsg->cmsg_type != IPV6_DSTOPTS)) | 
| 290 | 		return(-1); | 
| 291 |  | 
| 292 | 	/* message length validation */ | 
| 293 | 	if (cmsg->cmsg_len < CMSG_SPACE(sizeof(struct ip6_ext))) | 
| 294 | 		return(-1); | 
| 295 | 	ip6e = __UNCONST(CCMSG_DATA(cmsg)); | 
| 296 | 	hdrlen = (ip6e->ip6e_len + 1) << 3; | 
| 297 | 	if (cmsg->cmsg_len < CMSG_SPACE(hdrlen)) | 
| 298 | 		return(-1); | 
| 299 |  | 
| 300 | 	/* | 
| 301 | 	 * If the caller does not specify the starting point, | 
| 302 | 	 * simply return the 1st option. | 
| 303 | 	 * Otherwise, search the option list for the next option. | 
| 304 | 	 */ | 
| 305 | 	lim = (uint8_t *)(void *)ip6e + hdrlen; | 
| 306 | 	if (*tptrp == NULL) | 
| 307 | 		*tptrp = (uint8_t *)(void *)(ip6e + 1); | 
| 308 | 	else { | 
| 309 | 		if ((optlen = ip6optlen(*tptrp, lim)) == 0) | 
| 310 | 			return(-1); | 
| 311 |  | 
| 312 | 		*tptrp = *tptrp + optlen; | 
| 313 | 	} | 
| 314 | 	if (*tptrp >= lim) {	/* there is no option */ | 
| 315 | 		*tptrp = NULL; | 
| 316 | 		return(-1); | 
| 317 | 	} | 
| 318 | 	/* | 
| 319 | 	 * Finally, checks if the next option is safely stored in the | 
| 320 | 	 * cmsg data. | 
| 321 | 	 */ | 
| 322 | 	if (ip6optlen(*tptrp, lim) == 0) | 
| 323 | 		return(-1); | 
| 324 | 	else | 
| 325 | 		return(0); | 
| 326 | } | 
| 327 |  | 
| 328 | /* | 
| 329 |  * This function is similar to the inet6_option_next() function, | 
| 330 |  * except this function lets the caller specify the option type to be | 
| 331 |  * searched for, instead of always returning the next option in the | 
| 332 |  * ancillary data object. | 
| 333 |  * Note: RFC 2292 says the type of tptrp is uint8_t *, but we think | 
| 334 |  *       it's a typo. The variable should be type of uint8_t **. | 
| 335 |  */ | 
| 336 | int | 
| 337 | inet6_option_find(const struct cmsghdr *cmsg, uint8_t **tptrp, int type) | 
| 338 | { | 
| 339 | 	struct ip6_ext *ip6e; | 
| 340 | 	int hdrlen, optlen; | 
| 341 | 	uint8_t *optp, *lim; | 
| 342 |  | 
| 343 | 	_DIAGASSERT(cmsg != NULL); | 
| 344 | 	_DIAGASSERT(tptrp != NULL); | 
| 345 |  | 
| 346 | 	if (cmsg->cmsg_level != IPPROTO_IPV6 || | 
| 347 | 	    (cmsg->cmsg_type != IPV6_HOPOPTS && | 
| 348 | 	     cmsg->cmsg_type != IPV6_DSTOPTS)) | 
| 349 | 		return(-1); | 
| 350 |  | 
| 351 | 	/* message length validation */ | 
| 352 | 	if (cmsg->cmsg_len < CMSG_SPACE(sizeof(struct ip6_ext))) | 
| 353 | 		return(-1); | 
| 354 | 	ip6e = __UNCONST(CCMSG_DATA(cmsg)); | 
| 355 | 	hdrlen = (ip6e->ip6e_len + 1) << 3; | 
| 356 | 	if (cmsg->cmsg_len < CMSG_SPACE(hdrlen)) | 
| 357 | 		return(-1);	 | 
| 358 |  | 
| 359 | 	/* | 
| 360 | 	 * If the caller does not specify the starting point, | 
| 361 | 	 * search from the beginning of the option list. | 
| 362 | 	 * Otherwise, search from *the next option* of the specified point. | 
| 363 | 	 */ | 
| 364 | 	lim = (uint8_t *)(void *)ip6e + hdrlen; | 
| 365 | 	if (*tptrp == NULL) | 
| 366 | 		*tptrp = (uint8_t *)(void *)(ip6e + 1); | 
| 367 | 	else { | 
| 368 | 		if ((optlen = ip6optlen(*tptrp, lim)) == 0) | 
| 369 | 			return(-1); | 
| 370 |  | 
| 371 | 		*tptrp = *tptrp + optlen; | 
| 372 | 	} | 
| 373 | 	for (optp = *tptrp; optp < lim; optp += optlen) { | 
| 374 | 		if (*optp == type) { | 
| 375 | 			*tptrp = optp; | 
| 376 | 			return(0); | 
| 377 | 		} | 
| 378 | 		if ((optlen = ip6optlen(optp, lim)) == 0) | 
| 379 | 			return(-1); | 
| 380 | 	} | 
| 381 |  | 
| 382 | 	/* search failed */ | 
| 383 | 	*tptrp = NULL; | 
| 384 | 	return(-1); | 
| 385 | } | 
| 386 |  | 
| 387 | /* | 
| 388 |  * Calculate the length of a given IPv6 option. Also checks | 
| 389 |  * if the option is safely stored in user's buffer according to the | 
| 390 |  * calculated length and the limitation of the buffer. | 
| 391 |  */ | 
| 392 | static int | 
| 393 | ip6optlen(uint8_t *opt, uint8_t *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(uint8_t *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 | 		 _DIAGASSERT(__type_fit(uint8_t, len - 2)); | 
| 429 | 		 p[1] = (uint8_t)(len - 2);  | 
| 430 | 		 memset(&p[2], 0, len - 2); | 
| 431 | 		 return; | 
| 432 | 	} | 
| 433 | } | 
| 434 |  | 
| 435 | /* | 
| 436 |  * The following functions are defined in RFC3542, which is a successor | 
| 437 |  * of RFC2292. | 
| 438 |  */ | 
| 439 |  | 
| 440 | int | 
| 441 | inet6_opt_init(void *extbuf, socklen_t extlen) | 
| 442 | { | 
| 443 | 	struct ip6_ext *ext = (struct ip6_ext *)extbuf; | 
| 444 |  | 
| 445 | 	if (ext) { | 
| 446 | 		if (extlen == 0 || (extlen % 8)) | 
| 447 | 			return (-1); | 
| 448 | 		ext->ip6e_len = (extlen >> 3) - 1; | 
| 449 | 	} | 
| 450 |  | 
| 451 | 	return (2);		/* sizeof the next and the length fields */ | 
| 452 | } | 
| 453 |  | 
| 454 | int | 
| 455 | inet6_opt_append(void *extbuf, socklen_t extlen, int offset, uint8_t type, | 
| 456 | 		 socklen_t len, uint8_t align, void **databufp) | 
| 457 | { | 
| 458 | 	int currentlen = offset; | 
| 459 | 	size_t padlen = 0; | 
| 460 |  | 
| 461 | 	/* | 
| 462 | 	 * The option type must have a value from 2 to 255, inclusive. | 
| 463 | 	 * (0 and 1 are reserved for the Pad1 and PadN options, respectively.) | 
| 464 | 	 */ | 
| 465 | 	if (type < 2) | 
| 466 | 		return (-1); | 
| 467 |  | 
| 468 | 	/* | 
| 469 | 	 * The option data length must have a value between 0 and 255, | 
| 470 | 	 * inclusive, and is the length of the option data that follows. | 
| 471 | 	 */ | 
| 472 | 	if (len > 255) | 
| 473 | 		return (-1); | 
| 474 |  | 
| 475 | 	/* | 
| 476 | 	 * The align parameter must have a value of 1, 2, 4, or 8. | 
| 477 | 	 * The align value can not exceed the value of len. | 
| 478 | 	 */ | 
| 479 | 	if (align != 1 && align != 2 && align != 4 && align != 8) | 
| 480 | 		return (-1); | 
| 481 | 	if (align > len) | 
| 482 | 		return (-1); | 
| 483 |  | 
| 484 | 	/* Calculate the padding length. */ | 
| 485 | 	currentlen += 2 + len;	/* 2 means "type + len" */ | 
| 486 | 	if (currentlen % align) | 
| 487 | 		padlen = align - (currentlen % align); | 
| 488 |  | 
| 489 | 	/* The option must fit in the extension header buffer. */ | 
| 490 | 	_DIAGASSERT(__type_fit(int, currentlen + padlen)); | 
| 491 | 	currentlen += (int)padlen; | 
| 492 | 	if (extlen &&		/* XXX: right? */ | 
| 493 | 	    (socklen_t)currentlen > extlen) | 
| 494 | 		return (-1); | 
| 495 |  | 
| 496 | 	if (extbuf) { | 
| 497 | 		uint8_t *optp = (uint8_t *)extbuf + offset; | 
| 498 |  | 
| 499 | 		if (padlen == 1) { | 
| 500 | 			/* insert a Pad1 option */ | 
| 501 | 			*optp = IP6OPT_PAD1; | 
| 502 | 			optp++; | 
| 503 | 		} else if (padlen > 0) { | 
| 504 | 			/* insert a PadN option for alignment */ | 
| 505 | 			*optp++ = IP6OPT_PADN; | 
| 506 | 			_DIAGASSERT(__type_fit(uint8_t, padlen - 2)); | 
| 507 | 			*optp++ = (uint8_t)(padlen - 2); | 
| 508 | 			memset(optp, 0, padlen - 2); | 
| 509 | 			optp += (padlen - 2); | 
| 510 | 		} | 
| 511 |  | 
| 512 | 		*optp++ = type; | 
| 513 | 		*optp++ = len; | 
| 514 |  | 
| 515 | 		*databufp = optp; | 
| 516 | 	} | 
| 517 |  | 
| 518 | 	return (currentlen); | 
| 519 | } | 
| 520 |  | 
| 521 | int | 
| 522 | inet6_opt_finish(void *extbuf, socklen_t extlen, int offset) | 
| 523 | { | 
| 524 | 	int updatelen = offset > 0 ? (1 + ((offset - 1) | 7)) : 0; | 
| 525 |  | 
| 526 | 	if (extbuf) { | 
| 527 | 		uint8_t *padp; | 
| 528 | 		size_t padlen = updatelen - offset; | 
| 529 |  | 
| 530 | 		if ((socklen_t)updatelen > extlen || padlen >= 256 + 2) | 
| 531 | 			return (-1); | 
| 532 |  | 
| 533 | 		padp = (uint8_t *)extbuf + offset; | 
| 534 | 		if (padlen == 1) | 
| 535 | 			*padp = IP6OPT_PAD1; | 
| 536 | 		else if (padlen > 0) { | 
| 537 | 			*padp++ = IP6OPT_PADN; | 
| 538 | 			*padp++ = (uint8_t)(padlen - 2); | 
| 539 | 			memset(padp, 0, padlen - 2); | 
| 540 | 		} | 
| 541 | 	} | 
| 542 |  | 
| 543 | 	return (updatelen); | 
| 544 | } | 
| 545 |  | 
| 546 | int | 
| 547 | inet6_opt_set_val(void *databuf, int offset, void *val, socklen_t vallen) | 
| 548 | { | 
| 549 |  | 
| 550 | 	memcpy((uint8_t *)databuf + offset, val, vallen); | 
| 551 | 	return (offset + vallen); | 
| 552 | } | 
| 553 |  | 
| 554 | int | 
| 555 | inet6_opt_next(void *extbuf, socklen_t extlen, int offset, uint8_t *typep, | 
| 556 | 	       socklen_t *lenp, void **databufp) | 
| 557 | { | 
| 558 | 	uint8_t *optp, *lim; | 
| 559 | 	int optlen; | 
| 560 |  | 
| 561 | 	/* Validate extlen. XXX: is the variable really necessary?? */ | 
| 562 | 	if (extlen == 0 || (extlen % 8)) | 
| 563 | 		return (-1); | 
| 564 | 	lim = (uint8_t *)extbuf + extlen; | 
| 565 |  | 
| 566 | 	/* | 
| 567 | 	 * If this is the first time this function called for this options | 
| 568 | 	 * header, simply return the 1st option. | 
| 569 | 	 * Otherwise, search the option list for the next option. | 
| 570 | 	 */ | 
| 571 | 	if (offset == 0) | 
| 572 | 		optp = (uint8_t *)(void *)((struct ip6_hbh *)extbuf + 1); | 
| 573 | 	else | 
| 574 | 		optp = (uint8_t *)extbuf + offset; | 
| 575 |  | 
| 576 | 	/* Find the next option skipping any padding options. */ | 
| 577 | 	while (optp < lim) { | 
| 578 | 		ptrdiff_t rv; | 
| 579 | 		switch(*optp) { | 
| 580 | 		case IP6OPT_PAD1: | 
| 581 | 			optp++; | 
| 582 | 			break; | 
| 583 | 		case IP6OPT_PADN: | 
| 584 | 			if ((optlen = ip6optlen(optp, lim)) == 0) | 
| 585 | 				goto optend; | 
| 586 | 			optp += optlen; | 
| 587 | 			break; | 
| 588 | 		default:	/* found */ | 
| 589 | 			if ((optlen = ip6optlen(optp, lim)) == 0) | 
| 590 | 				goto optend; | 
| 591 | 			*typep = *optp; | 
| 592 | 			*lenp = optlen - 2; | 
| 593 | 			*databufp = optp + 2; | 
| 594 | 			rv = optp + optlen - (uint8_t *)extbuf; | 
| 595 | 			_DIAGASSERT(__type_fit(int, rv)); | 
| 596 | 			return (int)rv; | 
| 597 | 		} | 
| 598 | 	} | 
| 599 |  | 
| 600 |   optend: | 
| 601 | 	*databufp = NULL; /* for safety */ | 
| 602 | 	return (-1); | 
| 603 | } | 
| 604 |  | 
| 605 | int | 
| 606 | inet6_opt_find(void *extbuf, socklen_t extlen, int offset, uint8_t type, | 
| 607 | 	       socklen_t *lenp, void **databufp) | 
| 608 | { | 
| 609 | 	uint8_t *optp, *lim; | 
| 610 | 	int optlen; | 
| 611 |  | 
| 612 | 	/* Validate extlen. XXX: is the variable really necessary?? */ | 
| 613 | 	if (extlen == 0 || (extlen % 8)) | 
| 614 | 		return (-1); | 
| 615 | 	lim = (uint8_t *)extbuf + extlen; | 
| 616 |  | 
| 617 | 	/* | 
| 618 | 	 * If this is the first time this function called for this options | 
| 619 | 	 * header, simply return the 1st option. | 
| 620 | 	 * Otherwise, search the option list for the next option. | 
| 621 | 	 */ | 
| 622 | 	if (offset == 0) | 
| 623 | 		optp = (uint8_t *)(void *)((struct ip6_hbh *)extbuf + 1); | 
| 624 | 	else | 
| 625 | 		optp = (uint8_t *)extbuf + offset; | 
| 626 |  | 
| 627 | 	/* Find the specified option */ | 
| 628 | 	while (optp < lim) { | 
| 629 | 		if ((optlen = ip6optlen(optp, lim)) == 0) | 
| 630 | 			goto optend; | 
| 631 |  | 
| 632 | 		if (*optp == type) { /* found */ | 
| 633 | 			ptrdiff_t td; | 
| 634 | 			*lenp = optlen - 2; | 
| 635 | 			*databufp = optp + 2; | 
| 636 | 			td = optp + optlen - (uint8_t *)extbuf; | 
| 637 | 			_DIAGASSERT(__type_fit(int, td)); | 
| 638 | 			return (int)td; | 
| 639 | 		} | 
| 640 |  | 
| 641 | 		optp += optlen; | 
| 642 | 	} | 
| 643 |  | 
| 644 |   optend: | 
| 645 | 	*databufp = NULL; /* for safety */ | 
| 646 | 	return (-1); | 
| 647 | } | 
| 648 |  | 
| 649 | int | 
| 650 | inet6_opt_get_val(void *databuf, int offset, void *val, socklen_t vallen) | 
| 651 | { | 
| 652 |  | 
| 653 | 	/* we can't assume alignment here */ | 
| 654 | 	memcpy(val, (uint8_t *)databuf + offset, vallen); | 
| 655 |  | 
| 656 | 	return (offset + vallen); | 
| 657 | } | 
| 658 |  |