1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * This code is conformant to RFC 3542.
31  */
32 
33 #include <assert.h>
34 #include <string.h>
35 #include <sys/types.h>
36 #include <sys/socket.h>
37 #include <sys/sysmacros.h>
38 #include <netinet/in.h>
39 #include <netinet/ip6.h>
40 
41 #include <stdio.h>
42 
43 #define	bufpos(p) ((p) - (uint8_t *)extbuf)
44 
45 /*
46  * Section 10.1 RFC3542.  This function returns the size of the empty
47  * extension header.  If extbuf is not NULL then it initializes its length
48  * field.  If extlen is invalid then -1 is returned.
49  */
50 int
inet6_opt_init(void * extbuf,socklen_t extlen)51 inet6_opt_init(void *extbuf, socklen_t extlen)
52 {
53 	if (extbuf && ((extlen < 0) || (extlen % 8))) {
54 		return (-1);
55 	}
56 
57 	if (extbuf) {
58 		*(uint8_t *)extbuf = 0;
59 		*((uint8_t *)extbuf + 1) = extlen/8 - 1;
60 	}
61 
62 	return (2);
63 }
64 
65 /*
66  * Section 10.2 RFC3542.  This function appends an option to an already
67  * initialized option buffer.  inet6_opt_append() returns the total length
68  * after adding the option.
69  */
70 int
inet6_opt_append(void * extbuf,socklen_t extlen,int offset,uint8_t type,socklen_t len,uint_t align,void ** databufp)71 inet6_opt_append(void *extbuf, socklen_t extlen, int offset, uint8_t type,
72 	socklen_t len, uint_t align, void **databufp)
73 {
74 	uint8_t *p;
75 	socklen_t endlen;
76 	int remainder, padbytes;
77 
78 	if (align > len ||
79 	    (align != 1 && align != 2 && align != 4 && align != 8) ||
80 	    len < 0 || len > 255 || type < 2) {
81 		return (-1);
82 	}
83 
84 	if (extbuf) {
85 		/*
86 		 * The length of the buffer is the minimum of the length
87 		 * passed in and the length stamped onto the buffer.  The
88 		 * length stamped onto the buffer is the number of 8 byte
89 		 * octets in the buffer minus 1.
90 		 */
91 		extlen = MIN(extlen, (*((uint8_t *)extbuf + 1) + 1) * 8);
92 	}
93 
94 	remainder = (offset + 2 + len) % align;
95 	if (remainder == 0) {
96 		padbytes = 0;
97 	} else {
98 		padbytes = align - remainder;
99 	}
100 
101 	endlen = offset + padbytes + 2 + len;
102 	if ((endlen > extlen) || !extbuf) {
103 		if (extbuf) {
104 			return (-1);
105 		} else {
106 			return (endlen);
107 		}
108 	}
109 
110 	p = (uint8_t *)extbuf + offset;
111 	if (padbytes != 0) {
112 		/*
113 		 * Pad out the buffer here with pad options.  If its only
114 		 * one byte then there is a special TLV with no L or V, just
115 		 * a zero to say skip this byte.  For two bytes or more
116 		 * we have a special TLV with type 0 and length the number of
117 		 * padbytes.
118 		 */
119 		if (padbytes == 1) {
120 			*p = IP6OPT_PAD1;
121 		} else {
122 			*p = IP6OPT_PADN;
123 			*(p + 1) = padbytes - 2;
124 			memset(p + 2, 0, padbytes - 2);
125 		}
126 		p += padbytes;
127 	}
128 
129 	*p++ = type;
130 	*p++ = len;
131 	if (databufp) {
132 		*databufp = p;
133 	}
134 	return (endlen);
135 }
136 
137 /*
138  * Section 10.3 RFC3542.  This function returns the updated total length.
139  * This functions inserts pad options to complete the option header as
140  * needed.
141  */
142 int
inet6_opt_finish(void * extbuf,socklen_t extlen,int offset)143 inet6_opt_finish(void *extbuf, socklen_t extlen, int offset)
144 {
145 	uint8_t *p;
146 	int padbytes;
147 
148 	if (extbuf) {
149 	/*
150 	 * The length of the buffer is the minimum of the length
151 	 * passed in and the length stamped onto the buffer.  The
152 	 * length stamped onto the buffer is the number of 8 byte
153 	 * octets in the buffer minus 1.
154 	 */
155 		extlen = MIN(extlen, (*((uint8_t *)extbuf + 1) + 1) * 8);
156 	}
157 
158 	padbytes = 8 - (offset % 8);
159 	if (padbytes == 8)
160 		padbytes = 0;
161 
162 	if ((offset + padbytes > extlen) || !extbuf) {
163 		if (extbuf) {
164 			return (-1);
165 		} else {
166 			return (offset + padbytes);
167 		}
168 	}
169 
170 	p = (uint8_t *)extbuf + offset;
171 	if (padbytes != 0) {
172 		/*
173 		 * Pad out the buffer here with pad options.  If its only
174 		 * one byte then there is a special TLV with no L or V, just
175 		 * a zero to say skip this byte.  For two bytes or more
176 		 * we have a special TLV with type 0 and length the number of
177 		 * padbytes.
178 		 */
179 		if (padbytes == 1) {
180 			*p = IP6OPT_PAD1;
181 		} else {
182 			*p = IP6OPT_PADN;
183 			*(p + 1) = padbytes - 2;
184 			memset(p + 2, 0, padbytes - 2);
185 		}
186 		p += padbytes;
187 	}
188 
189 	return (offset + padbytes);
190 }
191 
192 /*
193  * Section 10.4 RFC3542.  Ths function takes a pointer to the data as
194  * returned by inet6_opt_append and inserts the data.
195  */
196 int
inet6_opt_set_val(void * databuf,int offset,void * val,socklen_t vallen)197 inet6_opt_set_val(void *databuf, int offset, void *val, socklen_t vallen)
198 {
199 	memcpy((uint8_t *)databuf + offset, val, vallen);
200 	return (offset + vallen);
201 }
202 
203 /*
204  * Section 10.5 RFC 3542.  Starting walking the option header offset into the
205  * header.  Returns where we left off.  You take the output of this function
206  * and pass it back in as offset to iterate. -1 is returned on error.
207  *
208  * We use the fact that the first unsigned 8 bit quantity in the option
209  * header is the type and the second is the length.
210  */
211 int
inet6_opt_next(void * extbuf,socklen_t extlen,int offset,uint8_t * typep,socklen_t * lenp,void ** databufp)212 inet6_opt_next(void *extbuf, socklen_t extlen, int offset, uint8_t *typep,
213 	socklen_t *lenp, void **databufp)
214 {
215 	uint8_t *p;
216 	uint8_t *end;
217 
218 	/*
219 	 * The length of the buffer is the minimum of the length
220 	 * passed in and the length stamped onto the buffer.  The
221 	 * length stamped onto the buffer is the number of 8 byte
222 	 * octets in the buffer minus 1.
223 	 */
224 	extlen = MIN(extlen, (*((uint8_t *)extbuf + 1) + 1) * 8);
225 	end = (uint8_t *)extbuf + extlen;
226 	if (offset == 0) {
227 		offset = 2;
228 	}
229 
230 	/* assumption: IP6OPT_PAD1 == 0 and IP6OPT_PADN == 1 */
231 	p = (uint8_t *)extbuf + offset;
232 	while (*p == IP6OPT_PAD1 || *p == IP6OPT_PADN) {
233 		switch (*p) {
234 		case IP6OPT_PAD1:
235 			p++;
236 			break;
237 		case IP6OPT_PADN:
238 			/* *(p + 1) is the length of the option. */
239 			if (p + 2 + *(p + 1) >= end)
240 				return (-1);
241 			p += *(p + 1) + 2;
242 			break;
243 		}
244 	}
245 
246 	/* type, len, and data must fit... */
247 	if ((p + 2 >= end) || (p + 2 + *(p + 1) > end)) {
248 		return (-1);
249 	}
250 
251 	if (typep) {
252 		*typep = *p;
253 	}
254 	if (lenp) {
255 		*lenp = *(p + 1);
256 	}
257 	if (databufp) {
258 		*databufp = p + 2;
259 	}
260 
261 	return ((p - (uint8_t *)extbuf) + 2 + *lenp);
262 }
263 
264 /*
265  * Section 10.6 RFC 3542.  Starting walking the option header offset into the
266  * header.  Returns where we left off.  You take the output of this function
267  * and pass it back in as offset to iterate. -1 is returned on error.
268  *
269  * We use the fact that the first unsigned 8 bit quantity in the option
270  * header is the type and the second is the length.
271  */
272 int
inet6_opt_find(void * extbuf,socklen_t extlen,int offset,uint8_t type,socklen_t * lenp,void ** databufp)273 inet6_opt_find(void *extbuf, socklen_t extlen, int offset, uint8_t type,
274 	socklen_t *lenp, void **databufp)
275 {
276 	uint8_t newtype;
277 
278 	do {
279 		offset = inet6_opt_next(extbuf, extlen, offset, &newtype, lenp,
280 		    databufp);
281 
282 		if (offset == -1)
283 			return (-1);
284 	} while (newtype != type);
285 
286 	/* value to feed back into inet6_opt_find() as offset */
287 	return (offset);
288 }
289 
290 /*
291  * Section 10.7 RFC 3542.  databuf should be a pointer as returned by
292  * inet6_opt_next or inet6_opt_find.  The data is extracted from the option
293  * at that point.
294  */
295 int
inet6_opt_get_val(void * databuf,int offset,void * val,socklen_t vallen)296 inet6_opt_get_val(void *databuf, int offset, void *val, socklen_t vallen)
297 {
298 	memcpy(val, (uint8_t *)databuf + offset, vallen);
299 	return (offset + vallen);
300 }
301