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