Skip to content
Snippets Groups Projects
xfrm4_protocol.c 6.37 KiB
Newer Older
  • Learn to ignore specific revisions
  • /* xfrm4_protocol.c - Generic xfrm protocol multiplexer.
     *
     * Copyright (C) 2013 secunet Security Networks AG
     *
     * Author:
     * Steffen Klassert <steffen.klassert@secunet.com>
     *
     * Based on:
     * net/ipv4/tunnel4.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 <net/icmp.h>
    #include <net/ip.h>
    #include <net/protocol.h>
    #include <net/xfrm.h>
    
    static struct xfrm4_protocol __rcu *esp4_handlers __read_mostly;
    static struct xfrm4_protocol __rcu *ah4_handlers __read_mostly;
    static struct xfrm4_protocol __rcu *ipcomp4_handlers __read_mostly;
    static DEFINE_MUTEX(xfrm4_protocol_mutex);
    
    static inline struct xfrm4_protocol __rcu **proto_handlers(u8 protocol)
    {
    	switch (protocol) {
    	case IPPROTO_ESP:
    		return &esp4_handlers;
    	case IPPROTO_AH:
    		return &ah4_handlers;
    	case IPPROTO_COMP:
    		return &ipcomp4_handlers;
    	}
    
    	return NULL;
    }
    
    #define for_each_protocol_rcu(head, handler)		\
    	for (handler = rcu_dereference(head);		\
    	     handler != NULL;				\
    	     handler = rcu_dereference(handler->next))	\
    
    int xfrm4_rcv_cb(struct sk_buff *skb, u8 protocol, int err)
    {
    	int ret;
    	struct xfrm4_protocol *handler;
    
    	for_each_protocol_rcu(*proto_handlers(protocol), handler)
    		if ((ret = handler->cb_handler(skb, err)) <= 0)
    			return ret;
    
    	return 0;
    }
    EXPORT_SYMBOL(xfrm4_rcv_cb);
    
    int xfrm4_rcv_encap(struct sk_buff *skb, int nexthdr, __be32 spi,
    		    int encap_type)
    {
    	int ret;
    	struct xfrm4_protocol *handler;
    
    
    	XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip4 = NULL;
    
    	XFRM_SPI_SKB_CB(skb)->family = AF_INET;
    	XFRM_SPI_SKB_CB(skb)->daddroff = offsetof(struct iphdr, daddr);
    
    	for_each_protocol_rcu(*proto_handlers(nexthdr), handler)
    		if ((ret = handler->input_handler(skb, nexthdr, spi, encap_type)) != -EINVAL)
    			return ret;
    
    	icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
    
    	kfree_skb(skb);
    	return 0;
    }
    EXPORT_SYMBOL(xfrm4_rcv_encap);
    
    static int xfrm4_esp_rcv(struct sk_buff *skb)
    {
    	int ret;
    	struct xfrm4_protocol *handler;
    
    
    	XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip4 = NULL;
    
    
    	for_each_protocol_rcu(esp4_handlers, handler)
    		if ((ret = handler->handler(skb)) != -EINVAL)
    			return ret;
    
    	icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
    
    	kfree_skb(skb);
    	return 0;
    }
    
    static void xfrm4_esp_err(struct sk_buff *skb, u32 info)
    {
    	struct xfrm4_protocol *handler;
    
    	for_each_protocol_rcu(esp4_handlers, handler)
    		if (!handler->err_handler(skb, info))
    			break;
    }
    
    static int xfrm4_ah_rcv(struct sk_buff *skb)
    {
    	int ret;
    	struct xfrm4_protocol *handler;
    
    
    	XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip4 = NULL;
    
    
    	for_each_protocol_rcu(ah4_handlers, handler)
    		if ((ret = handler->handler(skb)) != -EINVAL)
    			return ret;;
    
    	icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
    
    	kfree_skb(skb);
    	return 0;
    }
    
    static void xfrm4_ah_err(struct sk_buff *skb, u32 info)
    {
    	struct xfrm4_protocol *handler;
    
    	for_each_protocol_rcu(ah4_handlers, handler)
    		if (!handler->err_handler(skb, info))
    			break;
    }
    
    static int xfrm4_ipcomp_rcv(struct sk_buff *skb)
    {
    	int ret;
    	struct xfrm4_protocol *handler;
    
    
    	XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip4 = NULL;
    
    
    	for_each_protocol_rcu(ipcomp4_handlers, handler)
    		if ((ret = handler->handler(skb)) != -EINVAL)
    			return ret;
    
    	icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
    
    	kfree_skb(skb);
    	return 0;
    }
    
    static void xfrm4_ipcomp_err(struct sk_buff *skb, u32 info)
    {
    	struct xfrm4_protocol *handler;
    
    	for_each_protocol_rcu(ipcomp4_handlers, handler)
    		if (!handler->err_handler(skb, info))
    			break;
    }
    
    static const struct net_protocol esp4_protocol = {
    	.handler	=	xfrm4_esp_rcv,
    	.err_handler	=	xfrm4_esp_err,
    	.no_policy	=	1,
    	.netns_ok	=	1,
    };
    
    static const struct net_protocol ah4_protocol = {
    	.handler	=	xfrm4_ah_rcv,
    	.err_handler	=	xfrm4_ah_err,
    	.no_policy	=	1,
    	.netns_ok	=	1,
    };
    
    static const struct net_protocol ipcomp4_protocol = {
    	.handler	=	xfrm4_ipcomp_rcv,
    	.err_handler	=	xfrm4_ipcomp_err,
    	.no_policy	=	1,
    	.netns_ok	=	1,
    };
    
    
    static struct xfrm_input_afinfo xfrm4_input_afinfo = {
    	.family		=	AF_INET,
    	.owner		=	THIS_MODULE,
    	.callback	=	xfrm4_rcv_cb,
    };
    
    
    static inline const struct net_protocol *netproto(unsigned char protocol)
    {
    	switch (protocol) {
    	case IPPROTO_ESP:
    		return &esp4_protocol;
    	case IPPROTO_AH:
    		return &ah4_protocol;
    	case IPPROTO_COMP:
    		return &ipcomp4_protocol;
    	}
    
    	return NULL;
    }
    
    int xfrm4_protocol_register(struct xfrm4_protocol *handler,
    			    unsigned char protocol)
    {
    	struct xfrm4_protocol __rcu **pprev;
    	struct xfrm4_protocol *t;
    	bool add_netproto = false;
    	int ret = -EEXIST;
    	int priority = handler->priority;
    
    	mutex_lock(&xfrm4_protocol_mutex);
    
    	if (!rcu_dereference_protected(*proto_handlers(protocol),
    				       lockdep_is_held(&xfrm4_protocol_mutex)))
    		add_netproto = true;
    
    	for (pprev = proto_handlers(protocol);
    	     (t = rcu_dereference_protected(*pprev,
    			lockdep_is_held(&xfrm4_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(&xfrm4_protocol_mutex);
    
    	if (add_netproto) {
    		if (inet_add_protocol(netproto(protocol), protocol)) {
    			pr_err("%s: can't add protocol\n", __func__);
    			ret = -EAGAIN;
    		}
    	}
    
    	return ret;
    }
    EXPORT_SYMBOL(xfrm4_protocol_register);
    
    int xfrm4_protocol_deregister(struct xfrm4_protocol *handler,
    			      unsigned char protocol)
    {
    	struct xfrm4_protocol __rcu **pprev;
    	struct xfrm4_protocol *t;
    	int ret = -ENOENT;
    
    	mutex_lock(&xfrm4_protocol_mutex);
    
    	for (pprev = proto_handlers(protocol);
    	     (t = rcu_dereference_protected(*pprev,
    			lockdep_is_held(&xfrm4_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(&xfrm4_protocol_mutex))) {
    		if (inet_del_protocol(netproto(protocol), protocol) < 0) {
    			pr_err("%s: can't remove protocol\n", __func__);
    			ret = -EAGAIN;
    		}
    	}
    
    	mutex_unlock(&xfrm4_protocol_mutex);
    
    	synchronize_net();
    
    	return ret;
    }
    EXPORT_SYMBOL(xfrm4_protocol_deregister);
    
    
    void __init xfrm4_protocol_init(void)
    {
    	xfrm_input_register_afinfo(&xfrm4_input_afinfo);
    }
    EXPORT_SYMBOL(xfrm4_protocol_init);