271 lines
		
	
	
	
		
			6.2 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			271 lines
		
	
	
	
		
			6.2 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
| 
								 | 
							
								/* 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;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									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;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									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;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									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);
							 | 
						||
| 
								 | 
							
								}
							 |