1 /* $NetBSD: ip6opt.c,v 1.8 2000/07/06 02:58:07 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.8 2000/07/06 02:58:07 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 if (typep[0] > 255) 141 return(-1); 142 143 /* 144 * If this is the first option, allocate space for the 145 * first 2 bytes(for next header and length fields) of 146 * the option header. 147 */ 148 if (bp == (u_char *)(void *)eh) { 149 bp += 2; 150 cmsg->cmsg_len += 2; 151 } 152 153 /* calculate pad length before the option. */ 154 off = bp - (u_char *)(void *)eh; 155 padlen = (((off % multx) + (multx - 1)) & ~(multx - 1)) - 156 (off % multx); 157 padlen += plusy; 158 /* insert padding */ 159 inet6_insert_padopt(bp, padlen); 160 cmsg->cmsg_len += padlen; 161 bp += padlen; 162 163 /* copy the option */ 164 if (typep[0] == IP6OPT_PAD1) 165 optlen = 1; 166 else 167 optlen = typep[1] + 2; 168 memcpy(bp, typep, (size_t)optlen); 169 bp += optlen; 170 cmsg->cmsg_len += optlen; 171 172 /* calculate pad length after the option and insert the padding */ 173 off = bp - (u_char *)(void *)eh; 174 padlen = ((off + 7) & ~7) - off; 175 inet6_insert_padopt(bp, padlen); 176 bp += padlen; 177 cmsg->cmsg_len += padlen; 178 179 /* update the length field of the ip6 option header */ 180 off = bp - (u_char *)(void *)eh; 181 eh->ip6e_len = (off >> 3) - 1; 182 183 return(0); 184 } 185 186 /* 187 * This function appends a Hop-by-Hop option or a Destination option 188 * into an ancillary data object that has been initialized by 189 * inet6_option_init(). This function returns a pointer to the 8-bit 190 * option type field that starts the option on success, or NULL on an 191 * error. 192 * The difference between this function and inet6_option_append() is 193 * that the latter copies the contents of a previously built option into 194 * the ancillary data object while the current function returns a 195 * pointer to the space in the data object where the option's TLV must 196 * then be built by the caller. 197 * 198 */ 199 u_int8_t * 200 inet6_option_alloc(cmsg, datalen, multx, plusy) 201 struct cmsghdr *cmsg; 202 int datalen; 203 int multx; 204 int plusy; 205 { 206 size_t padlen, off; 207 register u_int8_t *bp; 208 u_int8_t *retval; 209 struct ip6_ext *eh; 210 211 _DIAGASSERT(cmsg != NULL); 212 213 bp = (u_char *)(void *)cmsg + cmsg->cmsg_len; 214 eh = (struct ip6_ext *)(void *)CMSG_DATA(cmsg); 215 216 /* argument validation */ 217 if (multx != 1 && multx != 2 && multx != 4 && multx != 8) 218 return(NULL); 219 if (plusy < 0 || plusy > 7) 220 return(NULL); 221 222 /* 223 * If this is the first option, allocate space for the 224 * first 2 bytes(for next header and length fields) of 225 * the option header. 226 */ 227 if (bp == (u_char *)(void *)eh) { 228 bp += 2; 229 cmsg->cmsg_len += 2; 230 } 231 232 /* calculate pad length before the option. */ 233 off = bp - (u_char *)(void *)eh; 234 padlen = (((off % multx) + (multx - 1)) & ~(multx - 1)) - 235 (off % multx); 236 padlen += plusy; 237 /* insert padding */ 238 inet6_insert_padopt(bp, padlen); 239 cmsg->cmsg_len += padlen; 240 bp += padlen; 241 242 /* keep space to store specified length of data */ 243 retval = bp; 244 bp += datalen; 245 cmsg->cmsg_len += datalen; 246 247 /* calculate pad length after the option and insert the padding */ 248 off = bp - (u_char *)(void *)eh; 249 padlen = ((off + 7) & ~7) - off; 250 inet6_insert_padopt(bp, padlen); 251 bp += padlen; 252 cmsg->cmsg_len += padlen; 253 254 /* update the length field of the ip6 option header */ 255 off = bp - (u_char *)(void *)eh; 256 eh->ip6e_len = (off >> 3) - 1; 257 258 return(retval); 259 } 260 261 /* 262 * This function processes the next Hop-by-Hop option or Destination 263 * option in an ancillary data object. If another option remains to be 264 * processed, the return value of the function is 0 and *tptrp points to 265 * the 8-bit option type field (which is followed by the 8-bit option 266 * data length, followed by the option data). If no more options remain 267 * to be processed, the return value is -1 and *tptrp is NULL. If an 268 * error occurs, the return value is -1 and *tptrp is not NULL. 269 * (RFC 2292, 6.3.5) 270 */ 271 int 272 inet6_option_next(cmsg, tptrp) 273 const struct cmsghdr *cmsg; 274 u_int8_t **tptrp; 275 { 276 struct ip6_ext *ip6e; 277 int hdrlen, optlen; 278 u_int8_t *lim; 279 280 _DIAGASSERT(cmsg != NULL); 281 _DIAGASSERT(tptrp != NULL); 282 283 if (cmsg->cmsg_level != IPPROTO_IPV6 || 284 (cmsg->cmsg_type != IPV6_HOPOPTS && 285 cmsg->cmsg_type != IPV6_DSTOPTS)) 286 return(-1); 287 288 /* message length validation */ 289 if (cmsg->cmsg_len < CMSG_SPACE(sizeof(struct ip6_ext))) 290 return(-1); 291 /* LINTED const castaway */ 292 ip6e = (struct ip6_ext *)(void *)CMSG_DATA(cmsg); 293 hdrlen = (ip6e->ip6e_len + 1) << 3; 294 if (cmsg->cmsg_len < CMSG_SPACE(hdrlen)) 295 return(-1); 296 297 /* 298 * If the caller does not specify the starting point, 299 * simply return the 1st option. 300 * Otherwise, search the option list for the next option. 301 */ 302 lim = (u_int8_t *)(void *)ip6e + hdrlen; 303 if (*tptrp == NULL) 304 *tptrp = (u_int8_t *)(void *)(ip6e + 1); 305 else { 306 if ((optlen = ip6optlen(*tptrp, lim)) == 0) 307 return(-1); 308 309 *tptrp = *tptrp + optlen; 310 } 311 if (*tptrp >= lim) { /* there is no option */ 312 *tptrp = NULL; 313 return(-1); 314 } 315 /* 316 * Finally, checks if the next option is safely stored in the 317 * cmsg data. 318 */ 319 if (ip6optlen(*tptrp, lim) == 0) 320 return(-1); 321 else 322 return(0); 323 } 324 325 /* 326 * This function is similar to the inet6_option_next() function, 327 * except this function lets the caller specify the option type to be 328 * searched for, instead of always returning the next option in the 329 * ancillary data object. 330 * Note: RFC 2292 says the type of tptrp is u_int8_t *, but we think 331 * it's a typo. The variable should be type of u_int8_t **. 332 */ 333 int 334 inet6_option_find(cmsg, tptrp, type) 335 const struct cmsghdr *cmsg; 336 u_int8_t **tptrp; 337 int type; 338 { 339 struct ip6_ext *ip6e; 340 int hdrlen, optlen; 341 u_int8_t *optp, *lim; 342 343 _DIAGASSERT(cmsg != NULL); 344 _DIAGASSERT(tptrp != NULL); 345 346 if (cmsg->cmsg_level != IPPROTO_IPV6 || 347 (cmsg->cmsg_type != IPV6_HOPOPTS && 348 cmsg->cmsg_type != IPV6_DSTOPTS)) 349 return(-1); 350 351 /* message length validation */ 352 if (cmsg->cmsg_len < CMSG_SPACE(sizeof(struct ip6_ext))) 353 return(-1); 354 /* LINTED const castaway */ 355 ip6e = (struct ip6_ext *)(void *)CMSG_DATA(cmsg); 356 hdrlen = (ip6e->ip6e_len + 1) << 3; 357 if (cmsg->cmsg_len < CMSG_SPACE(hdrlen)) 358 return(-1); 359 360 /* 361 * If the caller does not specify the starting point, 362 * search from the beginning of the option list. 363 * Otherwise, search from *the next option* of the specified point. 364 */ 365 lim = (u_int8_t *)(void *)ip6e + hdrlen; 366 if (*tptrp == NULL) 367 *tptrp = (u_int8_t *)(void *)(ip6e + 1); 368 else { 369 if ((optlen = ip6optlen(*tptrp, lim)) == 0) 370 return(-1); 371 372 *tptrp = *tptrp + optlen; 373 } 374 for (optp = *tptrp; optp < lim; optp += optlen) { 375 if (*optp == type) { 376 *tptrp = optp; 377 return(0); 378 } 379 if ((optlen = ip6optlen(optp, lim)) == 0) 380 return(-1); 381 } 382 383 /* search failed */ 384 *tptrp = NULL; 385 return(-1); 386 } 387 388 /* 389 * Calculate the length of a given IPv6 option. Also checks 390 * if the option is safely stored in user's buffer according to the 391 * calculated length and the limitation of the buffer. 392 */ 393 static int 394 ip6optlen(opt, lim) 395 u_int8_t *opt, *lim; 396 { 397 int optlen; 398 399 _DIAGASSERT(opt != NULL); 400 _DIAGASSERT(lim != NULL); 401 402 if (*opt == IP6OPT_PAD1) 403 optlen = 1; 404 else { 405 /* is there enough space to store type and len? */ 406 if (opt + 2 > lim) 407 return(0); 408 optlen = *(opt + 1) + 2; 409 } 410 if (opt + optlen <= lim) 411 return(optlen); 412 413 return(0); 414 } 415 416 static void 417 inet6_insert_padopt(u_char *p, size_t len) 418 { 419 420 _DIAGASSERT(p != NULL); 421 422 switch(len) { 423 case 0: 424 return; 425 case 1: 426 p[0] = IP6OPT_PAD1; 427 return; 428 default: 429 p[0] = IP6OPT_PADN; 430 p[1] = len - 2; 431 memset(&p[2], 0, len - 2); 432 return; 433 } 434 } 435