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