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
{
	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);
117
		if ((*(u8 *)options & 0xF0) != 0x40)
118 119
			hdr_len += 4;
	}
120
	return hdr_len;
121 122 123
}
EXPORT_SYMBOL(gre_parse_header);

124 125 126 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
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 已提交
155 156
	const struct iphdr *iph = (const struct iphdr *)skb->data;
	u8 ver = skb->data[(iph->ihl<<2) + 1]&0x7f;
157 158

	if (ver >= GREPROTO_MAX)
X
xeb@mail.ru 已提交
159
		return;
160 161 162

	rcu_read_lock();
	proto = rcu_dereference(gre_proto[ver]);
X
xeb@mail.ru 已提交
163 164
	if (proto && proto->err_handler)
		proto->err_handler(skb, info);
165 166 167 168 169 170 171 172 173 174 175
	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)
{
176
	pr_info("GRE over IPv4 demultiplexor driver\n");
177 178

	if (inet_add_protocol(&net_gre_protocol, IPPROTO_GRE) < 0) {
179
		pr_err("can't add protocol\n");
180
		return -EAGAIN;
181
	}
182 183 184 185 186 187 188 189 190 191 192 193 194 195
	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");