diff options
Diffstat (limited to 'net/ipv6/xfrm6_protocol.c')
| -rw-r--r-- | net/ipv6/xfrm6_protocol.c | 279 | 
1 files changed, 279 insertions, 0 deletions
diff --git a/net/ipv6/xfrm6_protocol.c b/net/ipv6/xfrm6_protocol.c new file mode 100644 index 00000000000..54d13f8dbba --- /dev/null +++ b/net/ipv6/xfrm6_protocol.c @@ -0,0 +1,279 @@ +/* xfrm6_protocol.c - Generic xfrm protocol multiplexer for ipv6. + * + * Copyright (C) 2013 secunet Security Networks AG + * + * Author: + * Steffen Klassert <steffen.klassert@secunet.com> + * + * Based on: + * net/ipv4/xfrm4_protocol.c + * + *	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. + */ + +#include <linux/init.h> +#include <linux/mutex.h> +#include <linux/skbuff.h> +#include <linux/icmpv6.h> +#include <net/ipv6.h> +#include <net/protocol.h> +#include <net/xfrm.h> + +static struct xfrm6_protocol __rcu *esp6_handlers __read_mostly; +static struct xfrm6_protocol __rcu *ah6_handlers __read_mostly; +static struct xfrm6_protocol __rcu *ipcomp6_handlers __read_mostly; +static DEFINE_MUTEX(xfrm6_protocol_mutex); + +static inline struct xfrm6_protocol __rcu **proto_handlers(u8 protocol) +{ +	switch (protocol) { +	case IPPROTO_ESP: +		return &esp6_handlers; +	case IPPROTO_AH: +		return &ah6_handlers; +	case IPPROTO_COMP: +		return &ipcomp6_handlers; +	} + +	return NULL; +} + +#define for_each_protocol_rcu(head, handler)		\ +	for (handler = rcu_dereference(head);		\ +	     handler != NULL;				\ +	     handler = rcu_dereference(handler->next))	\ + +int xfrm6_rcv_cb(struct sk_buff *skb, u8 protocol, int err) +{ +	int ret; +	struct xfrm6_protocol *handler; +	struct xfrm6_protocol __rcu **head = proto_handlers(protocol); + +	if (!head) +		return 0; + +	for_each_protocol_rcu(*proto_handlers(protocol), handler) +		if ((ret = handler->cb_handler(skb, err)) <= 0) +			return ret; + +	return 0; +} +EXPORT_SYMBOL(xfrm6_rcv_cb); + +static int xfrm6_esp_rcv(struct sk_buff *skb) +{ +	int ret; +	struct xfrm6_protocol *handler; + +	XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6 = NULL; + +	for_each_protocol_rcu(esp6_handlers, handler) +		if ((ret = handler->handler(skb)) != -EINVAL) +			return ret; + +	icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH, 0); + +	kfree_skb(skb); +	return 0; +} + +static void xfrm6_esp_err(struct sk_buff *skb, struct inet6_skb_parm *opt, +			  u8 type, u8 code, int offset, __be32 info) +{ +	struct xfrm6_protocol *handler; + +	for_each_protocol_rcu(esp6_handlers, handler) +		if (!handler->err_handler(skb, opt, type, code, offset, info)) +			break; +} + +static int xfrm6_ah_rcv(struct sk_buff *skb) +{ +	int ret; +	struct xfrm6_protocol *handler; + +	XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6 = NULL; + +	for_each_protocol_rcu(ah6_handlers, handler) +		if ((ret = handler->handler(skb)) != -EINVAL) +			return ret; + +	icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH, 0); + +	kfree_skb(skb); +	return 0; +} + +static void xfrm6_ah_err(struct sk_buff *skb, struct inet6_skb_parm *opt, +			 u8 type, u8 code, int offset, __be32 info) +{ +	struct xfrm6_protocol *handler; + +	for_each_protocol_rcu(ah6_handlers, handler) +		if (!handler->err_handler(skb, opt, type, code, offset, info)) +			break; +} + +static int xfrm6_ipcomp_rcv(struct sk_buff *skb) +{ +	int ret; +	struct xfrm6_protocol *handler; + +	XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6 = NULL; + +	for_each_protocol_rcu(ipcomp6_handlers, handler) +		if ((ret = handler->handler(skb)) != -EINVAL) +			return ret; + +	icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH, 0); + +	kfree_skb(skb); +	return 0; +} + +static void xfrm6_ipcomp_err(struct sk_buff *skb, struct inet6_skb_parm *opt, +			     u8 type, u8 code, int offset, __be32 info) +{ +	struct xfrm6_protocol *handler; + +	for_each_protocol_rcu(ipcomp6_handlers, handler) +		if (!handler->err_handler(skb, opt, type, code, offset, info)) +			break; +} + +static const struct inet6_protocol esp6_protocol = { +	.handler	=	xfrm6_esp_rcv, +	.err_handler	=	xfrm6_esp_err, +	.flags		=	INET6_PROTO_NOPOLICY, +}; + +static const struct inet6_protocol ah6_protocol = { +	.handler	=	xfrm6_ah_rcv, +	.err_handler	=	xfrm6_ah_err, +	.flags		=	INET6_PROTO_NOPOLICY, +}; + +static const struct inet6_protocol ipcomp6_protocol = { +	.handler	=	xfrm6_ipcomp_rcv, +	.err_handler	=	xfrm6_ipcomp_err, +	.flags		=	INET6_PROTO_NOPOLICY, +}; + +static struct xfrm_input_afinfo xfrm6_input_afinfo = { +	.family		=	AF_INET6, +	.owner		=	THIS_MODULE, +	.callback	=	xfrm6_rcv_cb, +}; + +static inline const struct inet6_protocol *netproto(unsigned char protocol) +{ +	switch (protocol) { +	case IPPROTO_ESP: +		return &esp6_protocol; +	case IPPROTO_AH: +		return &ah6_protocol; +	case IPPROTO_COMP: +		return &ipcomp6_protocol; +	} + +	return NULL; +} + +int xfrm6_protocol_register(struct xfrm6_protocol *handler, +			    unsigned char protocol) +{ +	struct xfrm6_protocol __rcu **pprev; +	struct xfrm6_protocol *t; +	bool add_netproto = false; +	int ret = -EEXIST; +	int priority = handler->priority; + +	if (!proto_handlers(protocol) || !netproto(protocol)) +		return -EINVAL; + +	mutex_lock(&xfrm6_protocol_mutex); + +	if (!rcu_dereference_protected(*proto_handlers(protocol), +				       lockdep_is_held(&xfrm6_protocol_mutex))) +		add_netproto = true; + +	for (pprev = proto_handlers(protocol); +	     (t = rcu_dereference_protected(*pprev, +			lockdep_is_held(&xfrm6_protocol_mutex))) != NULL; +	     pprev = &t->next) { +		if (t->priority < priority) +			break; +		if (t->priority == priority) +			goto err; +	} + +	handler->next = *pprev; +	rcu_assign_pointer(*pprev, handler); + +	ret = 0; + +err: +	mutex_unlock(&xfrm6_protocol_mutex); + +	if (add_netproto) { +		if (inet6_add_protocol(netproto(protocol), protocol)) { +			pr_err("%s: can't add protocol\n", __func__); +			ret = -EAGAIN; +		} +	} + +	return ret; +} +EXPORT_SYMBOL(xfrm6_protocol_register); + +int xfrm6_protocol_deregister(struct xfrm6_protocol *handler, +			      unsigned char protocol) +{ +	struct xfrm6_protocol __rcu **pprev; +	struct xfrm6_protocol *t; +	int ret = -ENOENT; + +	if (!proto_handlers(protocol) || !netproto(protocol)) +		return -EINVAL; + +	mutex_lock(&xfrm6_protocol_mutex); + +	for (pprev = proto_handlers(protocol); +	     (t = rcu_dereference_protected(*pprev, +			lockdep_is_held(&xfrm6_protocol_mutex))) != NULL; +	     pprev = &t->next) { +		if (t == handler) { +			*pprev = handler->next; +			ret = 0; +			break; +		} +	} + +	if (!rcu_dereference_protected(*proto_handlers(protocol), +				       lockdep_is_held(&xfrm6_protocol_mutex))) { +		if (inet6_del_protocol(netproto(protocol), protocol) < 0) { +			pr_err("%s: can't remove protocol\n", __func__); +			ret = -EAGAIN; +		} +	} + +	mutex_unlock(&xfrm6_protocol_mutex); + +	synchronize_net(); + +	return ret; +} +EXPORT_SYMBOL(xfrm6_protocol_deregister); + +int __init xfrm6_protocol_init(void) +{ +	return xfrm_input_register_afinfo(&xfrm6_input_afinfo); +} + +void xfrm6_protocol_fini(void) +{ +	xfrm_input_unregister_afinfo(&xfrm6_input_afinfo); +}  | 
