1/*	$KAME: rthdr.c,v 1.19 2003/06/06 10:48:51 itojun 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/socket.h>
37
38#include <netinet/in.h>
39#include <netinet/ip6.h>
40
41#include <string.h>
42#include <stdio.h>
43
44/*
45 * RFC2292 API
46 */
47
48size_t
49inet6_rthdr_space(int type, int seg)
50{
51	switch (type) {
52	case IPV6_RTHDR_TYPE_0:
53		if (seg < 1 || seg > 23)
54			return (0);
55#ifdef COMPAT_RFC2292
56		return (CMSG_SPACE(sizeof(struct in6_addr) * (seg - 1) +
57		    sizeof(struct ip6_rthdr0)));
58#else
59		return (CMSG_SPACE(sizeof(struct in6_addr) * seg +
60		    sizeof(struct ip6_rthdr0)));
61#endif
62	default:
63		return (0);
64	}
65}
66
67struct cmsghdr *
68inet6_rthdr_init(void *bp, int type)
69{
70	struct cmsghdr *ch = (struct cmsghdr *)bp;
71	struct ip6_rthdr *rthdr;
72
73	rthdr = (struct ip6_rthdr *)CMSG_DATA(ch);
74
75	ch->cmsg_level = IPPROTO_IPV6;
76	ch->cmsg_type = IPV6_RTHDR;
77
78	switch (type) {
79	case IPV6_RTHDR_TYPE_0:
80#ifdef COMPAT_RFC2292
81		ch->cmsg_len = CMSG_LEN(sizeof(struct ip6_rthdr0) -
82		    sizeof(struct in6_addr));
83#else
84		ch->cmsg_len = CMSG_LEN(sizeof(struct ip6_rthdr0));
85#endif
86
87		bzero(rthdr, sizeof(struct ip6_rthdr0));
88		rthdr->ip6r_type = IPV6_RTHDR_TYPE_0;
89		return (ch);
90	default:
91		return (NULL);
92	}
93}
94
95/* ARGSUSED */
96int
97inet6_rthdr_add(struct cmsghdr *cmsg, const struct in6_addr *addr, u_int flags)
98{
99	struct ip6_rthdr *rthdr;
100
101	rthdr = (struct ip6_rthdr *)CMSG_DATA(cmsg);
102
103	switch (rthdr->ip6r_type) {
104	case IPV6_RTHDR_TYPE_0:
105	{
106		struct ip6_rthdr0 *rt0 = (struct ip6_rthdr0 *)rthdr;
107		if (flags != IPV6_RTHDR_LOOSE && flags != IPV6_RTHDR_STRICT)
108			return (-1);
109		if (rt0->ip6r0_segleft == 23)
110			return (-1);
111
112#ifdef COMPAT_RFC1883		/* XXX */
113		if (flags == IPV6_RTHDR_STRICT) {
114			int c, b;
115			c = rt0->ip6r0_segleft / 8;
116			b = rt0->ip6r0_segleft % 8;
117			rt0->ip6r0_slmap[c] |= (1 << (7 - b));
118		}
119#else
120		if (flags != IPV6_RTHDR_LOOSE)
121			return (-1);
122#endif
123		rt0->ip6r0_segleft++;
124		bcopy(addr, (caddr_t)rt0 + ((rt0->ip6r0_len + 1) << 3),
125		    sizeof(struct in6_addr));
126		rt0->ip6r0_len += sizeof(struct in6_addr) >> 3;
127		cmsg->cmsg_len = CMSG_LEN((rt0->ip6r0_len + 1) << 3);
128		break;
129	}
130	default:
131		return (-1);
132	}
133
134	return (0);
135}
136
137/* ARGSUSED */
138int
139inet6_rthdr_lasthop(struct cmsghdr *cmsg, unsigned int flags)
140{
141	struct ip6_rthdr *rthdr;
142
143	rthdr = (struct ip6_rthdr *)CMSG_DATA(cmsg);
144
145	switch (rthdr->ip6r_type) {
146	case IPV6_RTHDR_TYPE_0:
147	{
148		struct ip6_rthdr0 *rt0 = (struct ip6_rthdr0 *)rthdr;
149#ifdef COMPAT_RFC1883		/* XXX */
150		if (flags != IPV6_RTHDR_LOOSE && flags != IPV6_RTHDR_STRICT)
151			return (-1);
152#endif /* COMPAT_RFC1883 */
153		if (rt0->ip6r0_segleft > 23)
154			return (-1);
155#ifdef COMPAT_RFC1883		/* XXX */
156		if (flags == IPV6_RTHDR_STRICT) {
157			int c, b;
158			c = rt0->ip6r0_segleft / 8;
159			b = rt0->ip6r0_segleft % 8;
160			rt0->ip6r0_slmap[c] |= (1 << (7 - b));
161		}
162#else
163		if (flags != IPV6_RTHDR_LOOSE)
164			return (-1);
165#endif /* COMPAT_RFC1883 */
166		break;
167	}
168	default:
169		return (-1);
170	}
171
172	return (0);
173}
174
175#if 0
176int
177inet6_rthdr_reverse(const struct cmsghdr *in, struct cmsghdr *out)
178{
179
180	return (-1);
181}
182#endif
183
184int
185inet6_rthdr_segments(const struct cmsghdr *cmsg)
186{
187	struct ip6_rthdr *rthdr;
188
189	rthdr = (struct ip6_rthdr *)CMSG_DATA(cmsg);
190
191	switch (rthdr->ip6r_type) {
192	case IPV6_RTHDR_TYPE_0:
193	{
194		struct ip6_rthdr0 *rt0 = (struct ip6_rthdr0 *)rthdr;
195
196		if (rt0->ip6r0_len % 2 || 46 < rt0->ip6r0_len)
197			return (-1);
198
199		return (rt0->ip6r0_len * 8) / sizeof(struct in6_addr);
200	}
201
202	default:
203		return (-1);
204	}
205}
206
207struct in6_addr *
208inet6_rthdr_getaddr(struct cmsghdr *cmsg, int idx)
209{
210	struct ip6_rthdr *rthdr;
211
212	rthdr = (struct ip6_rthdr *)CMSG_DATA(cmsg);
213
214	switch (rthdr->ip6r_type) {
215	case IPV6_RTHDR_TYPE_0:
216	{
217		struct ip6_rthdr0 *rt0 = (struct ip6_rthdr0 *)rthdr;
218		int naddr;
219
220		if (rt0->ip6r0_len % 2 || 46 < rt0->ip6r0_len)
221			return NULL;
222		naddr = (rt0->ip6r0_len * 8) / sizeof(struct in6_addr);
223		if (idx <= 0 || naddr < idx)
224			return NULL;
225#ifdef COMPAT_RFC2292
226		return (((struct in6_addr *)(rt0 + 1)) + idx - 1);
227#else
228		return (((struct in6_addr *)(rt0 + 1)) + idx);
229#endif
230	}
231
232	default:
233		return NULL;
234	}
235}
236
237int
238inet6_rthdr_getflags(const struct cmsghdr *cmsg, int idx)
239{
240	struct ip6_rthdr *rthdr;
241
242	rthdr = (struct ip6_rthdr *)CMSG_DATA(cmsg);
243
244	switch (rthdr->ip6r_type) {
245	case IPV6_RTHDR_TYPE_0:
246	{
247		struct ip6_rthdr0 *rt0 = (struct ip6_rthdr0 *)rthdr;
248		int naddr;
249
250		if (rt0->ip6r0_len % 2 || 46 < rt0->ip6r0_len)
251			return (-1);
252		naddr = (rt0->ip6r0_len * 8) / sizeof(struct in6_addr);
253		if (idx < 0 || naddr < idx)
254			return (-1);
255#ifdef COMPAT_RFC1883		/* XXX */
256		if (rt0->ip6r0_slmap[idx / 8] & (0x80 >> (idx % 8)))
257			return IPV6_RTHDR_STRICT;
258		else
259			return IPV6_RTHDR_LOOSE;
260#else
261		return IPV6_RTHDR_LOOSE;
262#endif /* COMPAT_RFC1883 */
263	}
264
265	default:
266		return (-1);
267	}
268}
269
270/*
271 * RFC3542 API
272 */
273
274socklen_t
275inet6_rth_space(int type, int segments)
276{
277	switch (type) {
278	case IPV6_RTHDR_TYPE_0:
279		if ((segments >= 0) && (segments <= 127))
280			return (((segments * 2) + 1) << 3);
281		/* FALLTHROUGH */
282	default:
283		return (0);	/* type not suppported */
284	}
285}
286
287void *
288inet6_rth_init(void *bp, socklen_t bp_len, int type, int segments)
289{
290	struct ip6_rthdr *rth = (struct ip6_rthdr *)bp;
291	struct ip6_rthdr0 *rth0;
292
293	switch (type) {
294	case IPV6_RTHDR_TYPE_0:
295		/* length validation */
296		if (bp_len < inet6_rth_space(IPV6_RTHDR_TYPE_0, segments))
297			return (NULL);
298		/* segment validation */
299		if ((segments < 0) || (segments > 127))
300			return (NULL);
301
302		memset(bp, 0, bp_len);
303		rth0 = (struct ip6_rthdr0 *)rth;
304		rth0->ip6r0_len = segments * 2;
305		rth0->ip6r0_type = IPV6_RTHDR_TYPE_0;
306		rth0->ip6r0_segleft = 0;
307		rth0->ip6r0_reserved = 0;
308		break;
309	default:
310		return (NULL);	/* type not supported */
311	}
312
313	return (bp);
314}
315
316int
317inet6_rth_add(void *bp, const struct in6_addr *addr)
318{
319	struct ip6_rthdr *rth = (struct ip6_rthdr *)bp;
320	struct ip6_rthdr0 *rth0;
321	struct in6_addr *nextaddr;
322
323	switch (rth->ip6r_type) {
324	case IPV6_RTHDR_TYPE_0:
325		rth0 = (struct ip6_rthdr0 *)rth;
326		/* Don't exceed the number of stated segments */
327		if (rth0->ip6r0_segleft == (rth0->ip6r0_len / 2))
328			return (-1);
329		nextaddr = (struct in6_addr *)(rth0 + 1) + rth0->ip6r0_segleft;
330		*nextaddr = *addr;
331		rth0->ip6r0_segleft++;
332		break;
333	default:
334		return (-1);	/* type not supported */
335	}
336
337	return (0);
338}
339
340int
341inet6_rth_reverse(const void *in, void *out)
342{
343	struct ip6_rthdr *rth_in = (struct ip6_rthdr *)in;
344	struct ip6_rthdr0 *rth0_in, *rth0_out;
345	int i, segments;
346
347	switch (rth_in->ip6r_type) {
348	case IPV6_RTHDR_TYPE_0:
349		rth0_in = (struct ip6_rthdr0 *)in;
350		rth0_out = (struct ip6_rthdr0 *)out;
351
352		/* parameter validation XXX too paranoid? */
353		if (rth0_in->ip6r0_len % 2)
354			return (-1);
355		segments = rth0_in->ip6r0_len / 2;
356
357		/* we can't use memcpy here, since in and out may overlap */
358		memmove((void *)rth0_out, (void *)rth0_in,
359			((rth0_in->ip6r0_len) + 1) << 3);
360		rth0_out->ip6r0_segleft = segments;
361
362		/* reverse the addresses */
363		for (i = 0; i < segments / 2; i++) {
364			struct in6_addr addr_tmp, *addr1, *addr2;
365
366			addr1 = (struct in6_addr *)(rth0_out + 1) + i;
367			addr2 = (struct in6_addr *)(rth0_out + 1) +
368				(segments - i - 1);
369			addr_tmp = *addr1;
370			*addr1 = *addr2;
371			*addr2 = addr_tmp;
372		}
373
374		break;
375	default:
376		return (-1);	/* type not supported */
377	}
378
379	return (0);
380}
381
382int
383inet6_rth_segments(const void *bp)
384{
385	struct ip6_rthdr *rh = (struct ip6_rthdr *)bp;
386	struct ip6_rthdr0 *rh0;
387	int addrs;
388
389	switch (rh->ip6r_type) {
390	case IPV6_RTHDR_TYPE_0:
391		rh0 = (struct ip6_rthdr0 *)bp;
392
393		/*
394		 * Validation for a type-0 routing header.
395		 * Is this too strict?
396		 */
397		if ((rh0->ip6r0_len % 2) != 0 ||
398		    (addrs = (rh0->ip6r0_len >> 1)) < rh0->ip6r0_segleft)
399			return (-1);
400
401		return (addrs);
402	default:
403		return (-1);	/* unknown type */
404	}
405}
406
407struct in6_addr *
408inet6_rth_getaddr(const void *bp, int idx)
409{
410	struct ip6_rthdr *rh = (struct ip6_rthdr *)bp;
411	struct ip6_rthdr0 *rh0;
412	int addrs;
413
414	switch (rh->ip6r_type) {
415	case IPV6_RTHDR_TYPE_0:
416		 rh0 = (struct ip6_rthdr0 *)bp;
417
418		/*
419		 * Validation for a type-0 routing header.
420		 * Is this too strict?
421		 */
422		if ((rh0->ip6r0_len % 2) != 0 ||
423		    (addrs = (rh0->ip6r0_len >> 1)) < rh0->ip6r0_segleft)
424			return (NULL);
425
426		if (idx < 0 || addrs <= idx)
427			return (NULL);
428
429		return (((struct in6_addr *)(rh0 + 1)) + idx);
430	default:
431		return (NULL);	/* unknown type */
432		break;
433	}
434}
435