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