xfrm6_tunnel.c 9.13 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3 4 5 6 7
/*
 * Copyright (C)2003,2004 USAGI/WIDE Project
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
8
 *
Linus Torvalds's avatar
Linus Torvalds committed
9 10 11 12
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
13
 *
Linus Torvalds's avatar
Linus Torvalds committed
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Authors	Mitsuru KANDA  <mk@linux-ipv6.org>
 * 		YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
 *
 * Based on net/ipv4/xfrm4_tunnel.c
 *
 */
#include <linux/module.h>
#include <linux/xfrm.h>
#include <linux/list.h>
#include <net/ip.h>
#include <net/xfrm.h>
#include <net/ipv6.h>
#include <linux/ipv6.h>
#include <linux/icmpv6.h>
Arjan van de Ven's avatar
Arjan van de Ven committed
32
#include <linux/mutex.h>
Linus Torvalds's avatar
Linus Torvalds committed
33 34

/*
35
 * xfrm_tunnel_spi things are for allocating unique id ("spi")
Linus Torvalds's avatar
Linus Torvalds committed
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
 * per xfrm_address_t.
 */
struct xfrm6_tunnel_spi {
	struct hlist_node list_byaddr;
	struct hlist_node list_byspi;
	xfrm_address_t addr;
	u32 spi;
	atomic_t refcnt;
};

static DEFINE_RWLOCK(xfrm6_tunnel_spi_lock);

static u32 xfrm6_tunnel_spi;

#define XFRM6_TUNNEL_SPI_MIN	1
#define XFRM6_TUNNEL_SPI_MAX	0xffffffff

53
static struct kmem_cache *xfrm6_tunnel_spi_kmem __read_mostly;
Linus Torvalds's avatar
Linus Torvalds committed
54 55 56 57 58 59 60

#define XFRM6_TUNNEL_SPI_BYADDR_HSIZE 256
#define XFRM6_TUNNEL_SPI_BYSPI_HSIZE 256

static struct hlist_head xfrm6_tunnel_spi_byaddr[XFRM6_TUNNEL_SPI_BYADDR_HSIZE];
static struct hlist_head xfrm6_tunnel_spi_byspi[XFRM6_TUNNEL_SPI_BYSPI_HSIZE];

61
static inline unsigned xfrm6_tunnel_spi_hash_byaddr(xfrm_address_t *addr)
Linus Torvalds's avatar
Linus Torvalds committed
62 63 64
{
	unsigned h;

Al Viro's avatar
Al Viro committed
65
	h = (__force u32)(addr->a6[0] ^ addr->a6[1] ^ addr->a6[2] ^ addr->a6[3]);
Linus Torvalds's avatar
Linus Torvalds committed
66 67 68 69 70 71 72
	h ^= h >> 16;
	h ^= h >> 8;
	h &= XFRM6_TUNNEL_SPI_BYADDR_HSIZE - 1;

	return h;
}

73
static inline unsigned xfrm6_tunnel_spi_hash_byspi(u32 spi)
Linus Torvalds's avatar
Linus Torvalds committed
74 75 76 77 78 79 80 81 82 83 84 85 86 87
{
	return spi % XFRM6_TUNNEL_SPI_BYSPI_HSIZE;
}


static int xfrm6_tunnel_spi_init(void)
{
	int i;

	xfrm6_tunnel_spi = 0;
	xfrm6_tunnel_spi_kmem = kmem_cache_create("xfrm6_tunnel_spi",
						  sizeof(struct xfrm6_tunnel_spi),
						  0, SLAB_HWCACHE_ALIGN,
						  NULL, NULL);
88
	if (!xfrm6_tunnel_spi_kmem)
Linus Torvalds's avatar
Linus Torvalds committed
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
		return -ENOMEM;

	for (i = 0; i < XFRM6_TUNNEL_SPI_BYADDR_HSIZE; i++)
		INIT_HLIST_HEAD(&xfrm6_tunnel_spi_byaddr[i]);
	for (i = 0; i < XFRM6_TUNNEL_SPI_BYSPI_HSIZE; i++)
		INIT_HLIST_HEAD(&xfrm6_tunnel_spi_byspi[i]);
	return 0;
}

static void xfrm6_tunnel_spi_fini(void)
{
	int i;

	for (i = 0; i < XFRM6_TUNNEL_SPI_BYADDR_HSIZE; i++) {
		if (!hlist_empty(&xfrm6_tunnel_spi_byaddr[i]))
104
			return;
Linus Torvalds's avatar
Linus Torvalds committed
105 106 107
	}
	for (i = 0; i < XFRM6_TUNNEL_SPI_BYSPI_HSIZE; i++) {
		if (!hlist_empty(&xfrm6_tunnel_spi_byspi[i]))
108
			return;
Linus Torvalds's avatar
Linus Torvalds committed
109 110 111 112 113 114 115 116 117 118 119 120 121
	}
	kmem_cache_destroy(xfrm6_tunnel_spi_kmem);
	xfrm6_tunnel_spi_kmem = NULL;
}

static struct xfrm6_tunnel_spi *__xfrm6_tunnel_spi_lookup(xfrm_address_t *saddr)
{
	struct xfrm6_tunnel_spi *x6spi;
	struct hlist_node *pos;

	hlist_for_each_entry(x6spi, pos,
			     &xfrm6_tunnel_spi_byaddr[xfrm6_tunnel_spi_hash_byaddr(saddr)],
			     list_byaddr) {
122
		if (memcmp(&x6spi->addr, saddr, sizeof(x6spi->addr)) == 0)
Linus Torvalds's avatar
Linus Torvalds committed
123 124 125 126 127 128
			return x6spi;
	}

	return NULL;
}

Al Viro's avatar
Al Viro committed
129
__be32 xfrm6_tunnel_spi_lookup(xfrm_address_t *saddr)
Linus Torvalds's avatar
Linus Torvalds committed
130 131 132 133 134 135 136 137
{
	struct xfrm6_tunnel_spi *x6spi;
	u32 spi;

	read_lock_bh(&xfrm6_tunnel_spi_lock);
	x6spi = __xfrm6_tunnel_spi_lookup(saddr);
	spi = x6spi ? x6spi->spi : 0;
	read_unlock_bh(&xfrm6_tunnel_spi_lock);
138
	return htonl(spi);
Linus Torvalds's avatar
Linus Torvalds committed
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
}

EXPORT_SYMBOL(xfrm6_tunnel_spi_lookup);

static u32 __xfrm6_tunnel_alloc_spi(xfrm_address_t *saddr)
{
	u32 spi;
	struct xfrm6_tunnel_spi *x6spi;
	struct hlist_node *pos;
	unsigned index;

	if (xfrm6_tunnel_spi < XFRM6_TUNNEL_SPI_MIN ||
	    xfrm6_tunnel_spi >= XFRM6_TUNNEL_SPI_MAX)
		xfrm6_tunnel_spi = XFRM6_TUNNEL_SPI_MIN;
	else
		xfrm6_tunnel_spi++;

	for (spi = xfrm6_tunnel_spi; spi <= XFRM6_TUNNEL_SPI_MAX; spi++) {
		index = xfrm6_tunnel_spi_hash_byspi(spi);
158 159
		hlist_for_each_entry(x6spi, pos,
				     &xfrm6_tunnel_spi_byspi[index],
Linus Torvalds's avatar
Linus Torvalds committed
160 161 162 163 164 165 166 167 168 169
				     list_byspi) {
			if (x6spi->spi == spi)
				goto try_next_1;
		}
		xfrm6_tunnel_spi = spi;
		goto alloc_spi;
try_next_1:;
	}
	for (spi = XFRM6_TUNNEL_SPI_MIN; spi < xfrm6_tunnel_spi; spi++) {
		index = xfrm6_tunnel_spi_hash_byspi(spi);
170 171
		hlist_for_each_entry(x6spi, pos,
				     &xfrm6_tunnel_spi_byspi[index],
Linus Torvalds's avatar
Linus Torvalds committed
172 173 174 175 176 177 178 179 180 181 182
				     list_byspi) {
			if (x6spi->spi == spi)
				goto try_next_2;
		}
		xfrm6_tunnel_spi = spi;
		goto alloc_spi;
try_next_2:;
	}
	spi = 0;
	goto out;
alloc_spi:
183
	x6spi = kmem_cache_alloc(xfrm6_tunnel_spi_kmem, GFP_ATOMIC);
184
	if (!x6spi)
Linus Torvalds's avatar
Linus Torvalds committed
185
		goto out;
186

Linus Torvalds's avatar
Linus Torvalds committed
187 188 189 190 191 192 193 194 195 196 197 198
	memcpy(&x6spi->addr, saddr, sizeof(x6spi->addr));
	x6spi->spi = spi;
	atomic_set(&x6spi->refcnt, 1);

	hlist_add_head(&x6spi->list_byspi, &xfrm6_tunnel_spi_byspi[index]);

	index = xfrm6_tunnel_spi_hash_byaddr(saddr);
	hlist_add_head(&x6spi->list_byaddr, &xfrm6_tunnel_spi_byaddr[index]);
out:
	return spi;
}

Al Viro's avatar
Al Viro committed
199
__be32 xfrm6_tunnel_alloc_spi(xfrm_address_t *saddr)
Linus Torvalds's avatar
Linus Torvalds committed
200 201 202 203 204 205 206 207 208 209 210 211 212
{
	struct xfrm6_tunnel_spi *x6spi;
	u32 spi;

	write_lock_bh(&xfrm6_tunnel_spi_lock);
	x6spi = __xfrm6_tunnel_spi_lookup(saddr);
	if (x6spi) {
		atomic_inc(&x6spi->refcnt);
		spi = x6spi->spi;
	} else
		spi = __xfrm6_tunnel_alloc_spi(saddr);
	write_unlock_bh(&xfrm6_tunnel_spi_lock);

213
	return htonl(spi);
Linus Torvalds's avatar
Linus Torvalds committed
214 215 216 217 218 219 220 221 222 223 224
}

EXPORT_SYMBOL(xfrm6_tunnel_alloc_spi);

void xfrm6_tunnel_free_spi(xfrm_address_t *saddr)
{
	struct xfrm6_tunnel_spi *x6spi;
	struct hlist_node *pos, *n;

	write_lock_bh(&xfrm6_tunnel_spi_lock);

225
	hlist_for_each_entry_safe(x6spi, pos, n,
Linus Torvalds's avatar
Linus Torvalds committed
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
				  &xfrm6_tunnel_spi_byaddr[xfrm6_tunnel_spi_hash_byaddr(saddr)],
				  list_byaddr)
	{
		if (memcmp(&x6spi->addr, saddr, sizeof(x6spi->addr)) == 0) {
			if (atomic_dec_and_test(&x6spi->refcnt)) {
				hlist_del(&x6spi->list_byaddr);
				hlist_del(&x6spi->list_byspi);
				kmem_cache_free(xfrm6_tunnel_spi_kmem, x6spi);
				break;
			}
		}
	}
	write_unlock_bh(&xfrm6_tunnel_spi_lock);
}

EXPORT_SYMBOL(xfrm6_tunnel_free_spi);

static int xfrm6_tunnel_output(struct xfrm_state *x, struct sk_buff *skb)
{
	struct ipv6hdr *top_iph;

	top_iph = (struct ipv6hdr *)skb->data;
	top_iph->payload_len = htons(skb->len - sizeof(struct ipv6hdr));

	return 0;
}

253
static int xfrm6_tunnel_input(struct xfrm_state *x, struct sk_buff *skb)
Linus Torvalds's avatar
Linus Torvalds committed
254 255 256 257
{
	return 0;
}

258
static int xfrm6_tunnel_rcv(struct sk_buff *skb)
Linus Torvalds's avatar
Linus Torvalds committed
259
{
260
	struct ipv6hdr *iph = ipv6_hdr(skb);
261
	__be32 spi;
Linus Torvalds's avatar
Linus Torvalds committed
262 263

	spi = xfrm6_tunnel_spi_lookup((xfrm_address_t *)&iph->saddr);
264
	return xfrm6_rcv_spi(skb, spi) > 0 ? : 0;
Linus Torvalds's avatar
Linus Torvalds committed
265 266
}

267
static int xfrm6_tunnel_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
268
			    int type, int code, int offset, __be32 info)
Linus Torvalds's avatar
Linus Torvalds committed
269 270 271
{
	/* xfrm6_tunnel native err handling */
	switch (type) {
272
	case ICMPV6_DEST_UNREACH:
Linus Torvalds's avatar
Linus Torvalds committed
273
		switch (code) {
274
		case ICMPV6_NOROUTE:
Linus Torvalds's avatar
Linus Torvalds committed
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
		case ICMPV6_ADM_PROHIBITED:
		case ICMPV6_NOT_NEIGHBOUR:
		case ICMPV6_ADDR_UNREACH:
		case ICMPV6_PORT_UNREACH:
		default:
			break;
		}
		break;
	case ICMPV6_PKT_TOOBIG:
		break;
	case ICMPV6_TIME_EXCEED:
		switch (code) {
		case ICMPV6_EXC_HOPLIMIT:
			break;
		case ICMPV6_EXC_FRAGTIME:
290
		default:
Linus Torvalds's avatar
Linus Torvalds committed
291 292 293 294 295 296 297 298 299 300 301 302 303
			break;
		}
		break;
	case ICMPV6_PARAMPROB:
		switch (code) {
		case ICMPV6_HDR_FIELD: break;
		case ICMPV6_UNK_NEXTHDR: break;
		case ICMPV6_UNK_OPTION: break;
		}
		break;
	default:
		break;
	}
304 305

	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
306 307
}

Herbert Xu's avatar
Herbert Xu committed
308
static int xfrm6_tunnel_init_state(struct xfrm_state *x)
Linus Torvalds's avatar
Linus Torvalds committed
309
{
310
	if (x->props.mode != XFRM_MODE_TUNNEL)
Linus Torvalds's avatar
Linus Torvalds committed
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
		return -EINVAL;

	if (x->encap)
		return -EINVAL;

	x->props.header_len = sizeof(struct ipv6hdr);

	return 0;
}

static void xfrm6_tunnel_destroy(struct xfrm_state *x)
{
	xfrm6_tunnel_free_spi((xfrm_address_t *)&x->props.saddr);
}

static struct xfrm_type xfrm6_tunnel_type = {
	.description	= "IP6IP6",
	.owner          = THIS_MODULE,
	.proto		= IPPROTO_IPV6,
	.init_state	= xfrm6_tunnel_init_state,
	.destructor	= xfrm6_tunnel_destroy,
	.input		= xfrm6_tunnel_input,
	.output		= xfrm6_tunnel_output,
};

336
static struct xfrm6_tunnel xfrm6_tunnel_handler = {
Linus Torvalds's avatar
Linus Torvalds committed
337
	.handler	= xfrm6_tunnel_rcv,
338 339
	.err_handler	= xfrm6_tunnel_err,
	.priority	= 2,
Linus Torvalds's avatar
Linus Torvalds committed
340 341
};

342 343 344 345 346 347
static struct xfrm6_tunnel xfrm46_tunnel_handler = {
	.handler	= xfrm6_tunnel_rcv,
	.err_handler	= xfrm6_tunnel_err,
	.priority	= 2,
};

Linus Torvalds's avatar
Linus Torvalds committed
348 349
static int __init xfrm6_tunnel_init(void)
{
350
	if (xfrm_register_type(&xfrm6_tunnel_type, AF_INET6) < 0)
Linus Torvalds's avatar
Linus Torvalds committed
351
		return -EAGAIN;
352

353 354 355 356 357 358
	if (xfrm6_tunnel_register(&xfrm6_tunnel_handler, AF_INET6)) {
		xfrm_unregister_type(&xfrm6_tunnel_type, AF_INET6);
		return -EAGAIN;
	}
	if (xfrm6_tunnel_register(&xfrm46_tunnel_handler, AF_INET)) {
		xfrm6_tunnel_deregister(&xfrm6_tunnel_handler, AF_INET6);
Linus Torvalds's avatar
Linus Torvalds committed
359 360 361 362
		xfrm_unregister_type(&xfrm6_tunnel_type, AF_INET6);
		return -EAGAIN;
	}
	if (xfrm6_tunnel_spi_init() < 0) {
363 364
		xfrm6_tunnel_deregister(&xfrm46_tunnel_handler, AF_INET);
		xfrm6_tunnel_deregister(&xfrm6_tunnel_handler, AF_INET6);
Linus Torvalds's avatar
Linus Torvalds committed
365 366 367 368 369 370 371 372 373
		xfrm_unregister_type(&xfrm6_tunnel_type, AF_INET6);
		return -EAGAIN;
	}
	return 0;
}

static void __exit xfrm6_tunnel_fini(void)
{
	xfrm6_tunnel_spi_fini();
374 375
	xfrm6_tunnel_deregister(&xfrm46_tunnel_handler, AF_INET);
	xfrm6_tunnel_deregister(&xfrm6_tunnel_handler, AF_INET6);
376
	xfrm_unregister_type(&xfrm6_tunnel_type, AF_INET6);
Linus Torvalds's avatar
Linus Torvalds committed
377 378 379 380 381
}

module_init(xfrm6_tunnel_init);
module_exit(xfrm6_tunnel_fini);
MODULE_LICENSE("GPL");