gre_demux.c 4.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
/*
 *	GRE over IPv4 demultiplexer driver
 *
 *	Authors: Dmitry Kozlov (xeb@mail.ru)
 *
 *	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.
 *
 */

13 14
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

15
#include <linux/module.h>
16 17
#include <linux/if.h>
#include <linux/icmp.h>
18 19 20 21
#include <linux/kernel.h>
#include <linux/kmod.h>
#include <linux/skbuff.h>
#include <linux/in.h>
X
xeb@mail.ru 已提交
22
#include <linux/ip.h>
23
#include <linux/netdevice.h>
24
#include <linux/if_tunnel.h>
25 26 27 28
#include <linux/spinlock.h>
#include <net/protocol.h>
#include <net/gre.h>

29 30 31
#include <net/icmp.h>
#include <net/route.h>
#include <net/xfrm.h>
32

E
Eric Dumazet 已提交
33
static const struct gre_protocol __rcu *gre_proto[GREPROTO_MAX] __read_mostly;
34 35 36 37

int gre_add_protocol(const struct gre_protocol *proto, u8 version)
{
	if (version >= GREPROTO_MAX)
38
		return -EINVAL;
39

40 41
	return (cmpxchg((const struct gre_protocol **)&gre_proto[version], NULL, proto) == NULL) ?
		0 : -EBUSY;
42 43 44 45 46
}
EXPORT_SYMBOL_GPL(gre_add_protocol);

int gre_del_protocol(const struct gre_protocol *proto, u8 version)
{
47 48
	int ret;

49
	if (version >= GREPROTO_MAX)
50 51 52 53 54 55 56 57
		return -EINVAL;

	ret = (cmpxchg((const struct gre_protocol **)&gre_proto[version], proto, NULL) == proto) ?
		0 : -EBUSY;

	if (ret)
		return ret;

58 59 60 61 62
	synchronize_rcu();
	return 0;
}
EXPORT_SYMBOL_GPL(gre_del_protocol);

63
/* Fills in tpi and returns header length to be pulled. */
64
int gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi,
65
		     bool *csum_err)
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
{
	const struct gre_base_hdr *greh;
	__be32 *options;
	int hdr_len;

	if (unlikely(!pskb_may_pull(skb, sizeof(struct gre_base_hdr))))
		return -EINVAL;

	greh = (struct gre_base_hdr *)skb_transport_header(skb);
	if (unlikely(greh->flags & (GRE_VERSION | GRE_ROUTING)))
		return -EINVAL;

	tpi->flags = gre_flags_to_tnl_flags(greh->flags);
	hdr_len = gre_calc_hlen(tpi->flags);

	if (!pskb_may_pull(skb, hdr_len))
		return -EINVAL;

	greh = (struct gre_base_hdr *)skb_transport_header(skb);
	tpi->proto = greh->protocol;

	options = (__be32 *)(greh + 1);
	if (greh->flags & GRE_CSUM) {
		if (skb_checksum_simple_validate(skb)) {
			*csum_err = true;
			return -EINVAL;
		}

		skb_checksum_try_convert(skb, IPPROTO_GRE, 0,
					 null_compute_pseudo);
		options++;
	}

	if (greh->flags & GRE_KEY) {
		tpi->key = *options;
		options++;
	} else {
		tpi->key = 0;
	}
	if (unlikely(greh->flags & GRE_SEQ)) {
		tpi->seq = *options;
		options++;
	} else {
		tpi->seq = 0;
	}
	/* WCCP version 1 and 2 protocol decoding.
	 * - Change protocol to IP
	 * - When dealing with WCCPv2, Skip extra 4 bytes in GRE header
	 */
	if (greh->flags == 0 && tpi->proto == htons(ETH_P_WCCP)) {
		tpi->proto = htons(ETH_P_IP);
		if ((*(u8 *)options & 0xF0) != 0x40) {
			hdr_len += 4;
			if (!pskb_may_pull(skb, hdr_len))
				return -EINVAL;
		}
	}
123
	return hdr_len;
124 125 126
}
EXPORT_SYMBOL(gre_parse_header);

127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
static int gre_rcv(struct sk_buff *skb)
{
	const struct gre_protocol *proto;
	u8 ver;
	int ret;

	if (!pskb_may_pull(skb, 12))
		goto drop;

	ver = skb->data[1]&0x7f;
	if (ver >= GREPROTO_MAX)
		goto drop;

	rcu_read_lock();
	proto = rcu_dereference(gre_proto[ver]);
	if (!proto || !proto->handler)
		goto drop_unlock;
	ret = proto->handler(skb);
	rcu_read_unlock();
	return ret;

drop_unlock:
	rcu_read_unlock();
drop:
	kfree_skb(skb);
	return NET_RX_DROP;
}

static void gre_err(struct sk_buff *skb, u32 info)
{
	const struct gre_protocol *proto;
X
xeb@mail.ru 已提交
158 159
	const struct iphdr *iph = (const struct iphdr *)skb->data;
	u8 ver = skb->data[(iph->ihl<<2) + 1]&0x7f;
160 161

	if (ver >= GREPROTO_MAX)
X
xeb@mail.ru 已提交
162
		return;
163 164 165

	rcu_read_lock();
	proto = rcu_dereference(gre_proto[ver]);
X
xeb@mail.ru 已提交
166 167
	if (proto && proto->err_handler)
		proto->err_handler(skb, info);
168 169 170 171 172 173 174 175 176 177 178
	rcu_read_unlock();
}

static const struct net_protocol net_gre_protocol = {
	.handler     = gre_rcv,
	.err_handler = gre_err,
	.netns_ok    = 1,
};

static int __init gre_init(void)
{
179
	pr_info("GRE over IPv4 demultiplexor driver\n");
180 181

	if (inet_add_protocol(&net_gre_protocol, IPPROTO_GRE) < 0) {
182
		pr_err("can't add protocol\n");
183
		return -EAGAIN;
184
	}
185 186 187 188 189 190 191 192 193 194 195 196 197 198
	return 0;
}

static void __exit gre_exit(void)
{
	inet_del_protocol(&net_gre_protocol, IPPROTO_GRE);
}

module_init(gre_init);
module_exit(gre_exit);

MODULE_DESCRIPTION("GRE over IPv4 demultiplexer driver");
MODULE_AUTHOR("D. Kozlov (xeb@mail.ru)");
MODULE_LICENSE("GPL");