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 */
50int
51inet6_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 */
70int
71inet6_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 */
142int
143inet6_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 */
196int
197inet6_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 */
211int
212inet6_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 */
272int
273inet6_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 */
295int
296inet6_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