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