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