1 /* $NetBSD: ip6opt.c,v 1.9 2002/05/05 08:28:47 simonb 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.9 2002/05/05 08:28:47 simonb 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 <string.h> 47 #include <stdio.h> 48 49 #ifdef __weak_alias 50 __weak_alias(inet6_option_alloc,_inet6_option_alloc) 51 __weak_alias(inet6_option_append,_inet6_option_append) 52 __weak_alias(inet6_option_find,_inet6_option_find) 53 __weak_alias(inet6_option_init,_inet6_option_init) 54 __weak_alias(inet6_option_next,_inet6_option_next) 55 __weak_alias(inet6_option_space,_inet6_option_space) 56 #endif 57 58 static int ip6optlen(u_int8_t *opt, u_int8_t *lim); 59 static void inet6_insert_padopt(u_char *p, size_t len); 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(nbytes) 72 int nbytes; 73 { 74 nbytes += 2; /* we need space for nxt-hdr and length fields */ 75 return(CMSG_SPACE((nbytes + 7) & ~7)); 76 } 77 78 /* 79 * This function is called once per ancillary data object that will 80 * contain either Hop-by-Hop or Destination options. It returns 0 on 81 * success or -1 on an error. 82 */ 83 int 84 inet6_option_init(bp, cmsgp, type) 85 void *bp; 86 struct cmsghdr **cmsgp; 87 int type; 88 { 89 register struct cmsghdr *ch; 90 91 _DIAGASSERT(bp != NULL); 92 _DIAGASSERT(cmsgp != NULL); 93 94 ch = (struct cmsghdr *)bp; 95 96 /* argument validation */ 97 if (type != IPV6_HOPOPTS && type != IPV6_DSTOPTS) 98 return(-1); 99 100 ch->cmsg_level = IPPROTO_IPV6; 101 ch->cmsg_type = type; 102 ch->cmsg_len = CMSG_LEN(0); 103 104 *cmsgp = ch; 105 return(0); 106 } 107 108 /* 109 * This function appends a Hop-by-Hop option or a Destination option 110 * into an ancillary data object that has been initialized by 111 * inet6_option_init(). This function returns 0 if it succeeds or -1 on 112 * an error. 113 * multx is the value x in the alignment term "xn + y" described 114 * earlier. It must have a value of 1, 2, 4, or 8. 115 * plusy is the value y in the alignment term "xn + y" described 116 * earlier. It must have a value between 0 and 7, inclusive. 117 */ 118 int 119 inet6_option_append(cmsg, typep, multx, plusy) 120 struct cmsghdr *cmsg; 121 const u_int8_t *typep; 122 int multx; 123 int plusy; 124 { 125 size_t padlen, optlen, off; 126 register u_char *bp; 127 struct ip6_ext *eh; 128 129 _DIAGASSERT(cmsg != NULL); 130 _DIAGASSERT(typep != NULL); 131 132 bp = (u_char *)(void *)cmsg + cmsg->cmsg_len; 133 eh = (struct ip6_ext *)(void *)CMSG_DATA(cmsg); 134 135 /* argument validation */ 136 if (multx != 1 && multx != 2 && multx != 4 && multx != 8) 137 return(-1); 138 if (plusy < 0 || plusy > 7) 139 return(-1); 140 141 /* 142 * If this is the first option, allocate space for the 143 * first 2 bytes(for next header and length fields) of 144 * the option header. 145 */ 146 if (bp == (u_char *)(void *)eh) { 147 bp += 2; 148 cmsg->cmsg_len += 2; 149 } 150 151 /* calculate pad length before the option. */ 152 off = bp - (u_char *)(void *)eh; 153 padlen = (((off % multx) + (multx - 1)) & ~(multx - 1)) - 154 (off % multx); 155 padlen += plusy; 156 /* insert padding */ 157 inet6_insert_padopt(bp, padlen); 158 cmsg->cmsg_len += padlen; 159 bp += padlen; 160 161 /* copy the option */ 162 if (typep[0] == IP6OPT_PAD1) 163 optlen = 1; 164 else 165 optlen = typep[1] + 2; 166 memcpy(bp, typep, (size_t)optlen); 167 bp += optlen; 168 cmsg->cmsg_len += optlen; 169 170 /* calculate pad length after the option and insert the padding */ 171 off = bp - (u_char *)(void *)eh; 172 padlen = ((off + 7) & ~7) - off; 173 inet6_insert_padopt(bp, padlen); 174 bp += padlen; 175 cmsg->cmsg_len += padlen; 176 177 /* update the length field of the ip6 option header */ 178 off = bp - (u_char *)(void *)eh; 179 eh->ip6e_len = (off >> 3) - 1; 180 181 return(0); 182 } 183 184 /* 185 * This function appends a Hop-by-Hop option or a Destination option 186 * into an ancillary data object that has been initialized by 187 * inet6_option_init(). This function returns a pointer to the 8-bit 188 * option type field that starts the option on success, or NULL on an 189 * error. 190 * The difference between this function and inet6_option_append() is 191 * that the latter copies the contents of a previously built option into 192 * the ancillary data object while the current function returns a 193 * pointer to the space in the data object where the option's TLV must 194 * then be built by the caller. 195 * 196 */ 197 u_int8_t * 198 inet6_option_alloc(cmsg, datalen, multx, plusy) 199 struct cmsghdr *cmsg; 200 int datalen; 201 int multx; 202 int plusy; 203 { 204 size_t padlen, off; 205 register u_int8_t *bp; 206 u_int8_t *retval; 207 struct ip6_ext *eh; 208 209 _DIAGASSERT(cmsg != NULL); 210 211 bp = (u_char *)(void *)cmsg + cmsg->cmsg_len; 212 eh = (struct ip6_ext *)(void *)CMSG_DATA(cmsg); 213 214 /* argument validation */ 215 if (multx != 1 && multx != 2 && multx != 4 && multx != 8) 216 return(NULL); 217 if (plusy < 0 || plusy > 7) 218 return(NULL); 219 220 /* 221 * If this is the first option, allocate space for the 222 * first 2 bytes(for next header and length fields) of 223 * the option header. 224 */ 225 if (bp == (u_char *)(void *)eh) { 226 bp += 2; 227 cmsg->cmsg_len += 2; 228 } 229 230 /* calculate pad length before the option. */ 231 off = bp - (u_char *)(void *)eh; 232 padlen = (((off % multx) + (multx - 1)) & ~(multx - 1)) - 233 (off % multx); 234 padlen += plusy; 235 /* insert padding */ 236 inet6_insert_padopt(bp, padlen); 237 cmsg->cmsg_len += padlen; 238 bp += padlen; 239 240 /* keep space to store specified length of data */ 241 retval = bp; 242 bp += datalen; 243 cmsg->cmsg_len += datalen; 244 245 /* calculate pad length after the option and insert the padding */ 246 off = bp - (u_char *)(void *)eh; 247 padlen = ((off + 7) & ~7) - off; 248 inet6_insert_padopt(bp, padlen); 249 bp += padlen; 250 cmsg->cmsg_len += padlen; 251 252 /* update the length field of the ip6 option header */ 253 off = bp - (u_char *)(void *)eh; 254 eh->ip6e_len = (off >> 3) - 1; 255 256 return(retval); 257 } 258 259 /* 260 * This function processes the next Hop-by-Hop option or Destination 261 * option in an ancillary data object. If another option remains to be 262 * processed, the return value of the function is 0 and *tptrp points to 263 * the 8-bit option type field (which is followed by the 8-bit option 264 * data length, followed by the option data). If no more options remain 265 * to be processed, the return value is -1 and *tptrp is NULL. If an 266 * error occurs, the return value is -1 and *tptrp is not NULL. 267 * (RFC 2292, 6.3.5) 268 */ 269 int 270 inet6_option_next(cmsg, tptrp) 271 const struct cmsghdr *cmsg; 272 u_int8_t **tptrp; 273 { 274 struct ip6_ext *ip6e; 275 int hdrlen, optlen; 276 u_int8_t *lim; 277 278 _DIAGASSERT(cmsg != NULL); 279 _DIAGASSERT(tptrp != NULL); 280 281 if (cmsg->cmsg_level != IPPROTO_IPV6 || 282 (cmsg->cmsg_type != IPV6_HOPOPTS && 283 cmsg->cmsg_type != IPV6_DSTOPTS)) 284 return(-1); 285 286 /* message length validation */ 287 if (cmsg->cmsg_len < CMSG_SPACE(sizeof(struct ip6_ext))) 288 return(-1); 289 /* LINTED const castaway */ 290 ip6e = (struct ip6_ext *)(void *)CMSG_DATA(cmsg); 291 hdrlen = (ip6e->ip6e_len + 1) << 3; 292 if (cmsg->cmsg_len < CMSG_SPACE(hdrlen)) 293 return(-1); 294 295 /* 296 * If the caller does not specify the starting point, 297 * simply return the 1st option. 298 * Otherwise, search the option list for the next option. 299 */ 300 lim = (u_int8_t *)(void *)ip6e + hdrlen; 301 if (*tptrp == NULL) 302 *tptrp = (u_int8_t *)(void *)(ip6e + 1); 303 else { 304 if ((optlen = ip6optlen(*tptrp, lim)) == 0) 305 return(-1); 306 307 *tptrp = *tptrp + optlen; 308 } 309 if (*tptrp >= lim) { /* there is no option */ 310 *tptrp = NULL; 311 return(-1); 312 } 313 /* 314 * Finally, checks if the next option is safely stored in the 315 * cmsg data. 316 */ 317 if (ip6optlen(*tptrp, lim) == 0) 318 return(-1); 319 else 320 return(0); 321 } 322 323 /* 324 * This function is similar to the inet6_option_next() function, 325 * except this function lets the caller specify the option type to be 326 * searched for, instead of always returning the next option in the 327 * ancillary data object. 328 * Note: RFC 2292 says the type of tptrp is u_int8_t *, but we think 329 * it's a typo. The variable should be type of u_int8_t **. 330 */ 331 int 332 inet6_option_find(cmsg, tptrp, type) 333 const struct cmsghdr *cmsg; 334 u_int8_t **tptrp; 335 int type; 336 { 337 struct ip6_ext *ip6e; 338 int hdrlen, optlen; 339 u_int8_t *optp, *lim; 340 341 _DIAGASSERT(cmsg != NULL); 342 _DIAGASSERT(tptrp != NULL); 343 344 if (cmsg->cmsg_level != IPPROTO_IPV6 || 345 (cmsg->cmsg_type != IPV6_HOPOPTS && 346 cmsg->cmsg_type != IPV6_DSTOPTS)) 347 return(-1); 348 349 /* message length validation */ 350 if (cmsg->cmsg_len < CMSG_SPACE(sizeof(struct ip6_ext))) 351 return(-1); 352 /* LINTED const castaway */ 353 ip6e = (struct ip6_ext *)(void *)CMSG_DATA(cmsg); 354 hdrlen = (ip6e->ip6e_len + 1) << 3; 355 if (cmsg->cmsg_len < CMSG_SPACE(hdrlen)) 356 return(-1); 357 358 /* 359 * If the caller does not specify the starting point, 360 * search from the beginning of the option list. 361 * Otherwise, search from *the next option* of the specified point. 362 */ 363 lim = (u_int8_t *)(void *)ip6e + hdrlen; 364 if (*tptrp == NULL) 365 *tptrp = (u_int8_t *)(void *)(ip6e + 1); 366 else { 367 if ((optlen = ip6optlen(*tptrp, lim)) == 0) 368 return(-1); 369 370 *tptrp = *tptrp + optlen; 371 } 372 for (optp = *tptrp; optp < lim; optp += optlen) { 373 if (*optp == type) { 374 *tptrp = optp; 375 return(0); 376 } 377 if ((optlen = ip6optlen(optp, lim)) == 0) 378 return(-1); 379 } 380 381 /* search failed */ 382 *tptrp = NULL; 383 return(-1); 384 } 385 386 /* 387 * Calculate the length of a given IPv6 option. Also checks 388 * if the option is safely stored in user's buffer according to the 389 * calculated length and the limitation of the buffer. 390 */ 391 static int 392 ip6optlen(opt, lim) 393 u_int8_t *opt, *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(u_char *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 p[1] = len - 2; 429 memset(&p[2], 0, len - 2); 430 return; 431 } 432 } 433