1 /* $NetBSD: ip6opt.c,v 1.10 2005/11/29 03:11:59 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.10 2005/11/29 03:11:59 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 <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 ip6e = __UNCONST(CCMSG_DATA(cmsg)); 290 hdrlen = (ip6e->ip6e_len + 1) << 3; 291 if (cmsg->cmsg_len < CMSG_SPACE(hdrlen)) 292 return(-1); 293 294 /* 295 * If the caller does not specify the starting point, 296 * simply return the 1st option. 297 * Otherwise, search the option list for the next option. 298 */ 299 lim = (u_int8_t *)(void *)ip6e + hdrlen; 300 if (*tptrp == NULL) 301 *tptrp = (u_int8_t *)(void *)(ip6e + 1); 302 else { 303 if ((optlen = ip6optlen(*tptrp, lim)) == 0) 304 return(-1); 305 306 *tptrp = *tptrp + optlen; 307 } 308 if (*tptrp >= lim) { /* there is no option */ 309 *tptrp = NULL; 310 return(-1); 311 } 312 /* 313 * Finally, checks if the next option is safely stored in the 314 * cmsg data. 315 */ 316 if (ip6optlen(*tptrp, lim) == 0) 317 return(-1); 318 else 319 return(0); 320 } 321 322 /* 323 * This function is similar to the inet6_option_next() function, 324 * except this function lets the caller specify the option type to be 325 * searched for, instead of always returning the next option in the 326 * ancillary data object. 327 * Note: RFC 2292 says the type of tptrp is u_int8_t *, but we think 328 * it's a typo. The variable should be type of u_int8_t **. 329 */ 330 int 331 inet6_option_find(cmsg, tptrp, type) 332 const struct cmsghdr *cmsg; 333 u_int8_t **tptrp; 334 int type; 335 { 336 struct ip6_ext *ip6e; 337 int hdrlen, optlen; 338 u_int8_t *optp, *lim; 339 340 _DIAGASSERT(cmsg != NULL); 341 _DIAGASSERT(tptrp != NULL); 342 343 if (cmsg->cmsg_level != IPPROTO_IPV6 || 344 (cmsg->cmsg_type != IPV6_HOPOPTS && 345 cmsg->cmsg_type != IPV6_DSTOPTS)) 346 return(-1); 347 348 /* message length validation */ 349 if (cmsg->cmsg_len < CMSG_SPACE(sizeof(struct ip6_ext))) 350 return(-1); 351 ip6e = __UNCONST(CCMSG_DATA(cmsg)); 352 hdrlen = (ip6e->ip6e_len + 1) << 3; 353 if (cmsg->cmsg_len < CMSG_SPACE(hdrlen)) 354 return(-1); 355 356 /* 357 * If the caller does not specify the starting point, 358 * search from the beginning of the option list. 359 * Otherwise, search from *the next option* of the specified point. 360 */ 361 lim = (u_int8_t *)(void *)ip6e + hdrlen; 362 if (*tptrp == NULL) 363 *tptrp = (u_int8_t *)(void *)(ip6e + 1); 364 else { 365 if ((optlen = ip6optlen(*tptrp, lim)) == 0) 366 return(-1); 367 368 *tptrp = *tptrp + optlen; 369 } 370 for (optp = *tptrp; optp < lim; optp += optlen) { 371 if (*optp == type) { 372 *tptrp = optp; 373 return(0); 374 } 375 if ((optlen = ip6optlen(optp, lim)) == 0) 376 return(-1); 377 } 378 379 /* search failed */ 380 *tptrp = NULL; 381 return(-1); 382 } 383 384 /* 385 * Calculate the length of a given IPv6 option. Also checks 386 * if the option is safely stored in user's buffer according to the 387 * calculated length and the limitation of the buffer. 388 */ 389 static int 390 ip6optlen(opt, lim) 391 u_int8_t *opt, *lim; 392 { 393 int optlen; 394 395 _DIAGASSERT(opt != NULL); 396 _DIAGASSERT(lim != NULL); 397 398 if (*opt == IP6OPT_PAD1) 399 optlen = 1; 400 else { 401 /* is there enough space to store type and len? */ 402 if (opt + 2 > lim) 403 return(0); 404 optlen = *(opt + 1) + 2; 405 } 406 if (opt + optlen <= lim) 407 return(optlen); 408 409 return(0); 410 } 411 412 static void 413 inet6_insert_padopt(u_char *p, size_t len) 414 { 415 416 _DIAGASSERT(p != NULL); 417 418 switch(len) { 419 case 0: 420 return; 421 case 1: 422 p[0] = IP6OPT_PAD1; 423 return; 424 default: 425 p[0] = IP6OPT_PADN; 426 p[1] = len - 2; 427 memset(&p[2], 0, len - 2); 428 return; 429 } 430 } 431