gre_demux.c 4.8 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
#include <linux/spinlock.h>
#include <net/protocol.h>
#include <net/gre.h>
28
#include <net/erspan.h>
29

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

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

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

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

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

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

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

	if (ret)
		return ret;

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

64
/* Fills in tpi and returns header length to be pulled. */
65
int gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi,
E
Eric Dumazet 已提交
66
		     bool *csum_err, __be16 proto, int nhs)
67 68 69 70 71
{
	const struct gre_base_hdr *greh;
	__be32 *options;
	int hdr_len;

E
Eric Dumazet 已提交
72
	if (unlikely(!pskb_may_pull(skb, nhs + sizeof(struct gre_base_hdr))))
73 74
		return -EINVAL;

E
Eric Dumazet 已提交
75
	greh = (struct gre_base_hdr *)(skb->data + nhs);
76 77 78 79 80 81
	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);

E
Eric Dumazet 已提交
82
	if (!pskb_may_pull(skb, nhs + hdr_len))
83 84
		return -EINVAL;

E
Eric Dumazet 已提交
85
	greh = (struct gre_base_hdr *)(skb->data + nhs);
86 87 88 89
	tpi->proto = greh->protocol;

	options = (__be32 *)(greh + 1);
	if (greh->flags & GRE_CSUM) {
90 91 92 93
		if (!skb_checksum_simple_validate(skb)) {
			skb_checksum_try_convert(skb, IPPROTO_GRE, 0,
						 null_compute_pseudo);
		} else if (csum_err) {
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
			*csum_err = true;
			return -EINVAL;
		}

		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.
114
	 * - Change protocol to IPv4/IPv6
115 116 117
	 * - When dealing with WCCPv2, Skip extra 4 bytes in GRE header
	 */
	if (greh->flags == 0 && tpi->proto == htons(ETH_P_WCCP)) {
118
		tpi->proto = proto;
119
		if ((*(u8 *)options & 0xF0) != 0x40)
120 121
			hdr_len += 4;
	}
122
	tpi->hdr_len = hdr_len;
123 124 125 126 127 128 129 130 131 132 133 134

	/* ERSPAN ver 1 and 2 protocol sets GRE key field
	 * to 0 and sets the configured key in the
	 * inner erspan header field
	 */
	if (greh->protocol == htons(ETH_P_ERSPAN) ||
	    greh->protocol == htons(ETH_P_ERSPAN2)) {
		struct erspan_base_hdr *ershdr;

		if (!pskb_may_pull(skb, nhs + hdr_len + sizeof(*ershdr)))
			return -EINVAL;

135
		ershdr = (struct erspan_base_hdr *)(skb->data + nhs + hdr_len);
136 137 138
		tpi->key = cpu_to_be32(get_session_id(ershdr));
	}

139
	return hdr_len;
140 141 142
}
EXPORT_SYMBOL(gre_parse_header);

143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
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 已提交
174 175
	const struct iphdr *iph = (const struct iphdr *)skb->data;
	u8 ver = skb->data[(iph->ihl<<2) + 1]&0x7f;
176 177

	if (ver >= GREPROTO_MAX)
X
xeb@mail.ru 已提交
178
		return;
179 180 181

	rcu_read_lock();
	proto = rcu_dereference(gre_proto[ver]);
X
xeb@mail.ru 已提交
182 183
	if (proto && proto->err_handler)
		proto->err_handler(skb, info);
184 185 186 187 188 189 190 191 192 193 194
	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)
{
195
	pr_info("GRE over IPv4 demultiplexor driver\n");
196 197

	if (inet_add_protocol(&net_gre_protocol, IPPROTO_GRE) < 0) {
198
		pr_err("can't add protocol\n");
199
		return -EAGAIN;
200
	}
201 202 203 204 205 206 207 208 209 210 211 212 213 214
	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");