162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *	GRE over IPv4 demultiplexer driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *	Authors: Dmitry Kozlov (xeb@mail.ru)
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci#include <linux/if.h>
1262306a36Sopenharmony_ci#include <linux/icmp.h>
1362306a36Sopenharmony_ci#include <linux/kernel.h>
1462306a36Sopenharmony_ci#include <linux/kmod.h>
1562306a36Sopenharmony_ci#include <linux/skbuff.h>
1662306a36Sopenharmony_ci#include <linux/in.h>
1762306a36Sopenharmony_ci#include <linux/ip.h>
1862306a36Sopenharmony_ci#include <linux/netdevice.h>
1962306a36Sopenharmony_ci#include <linux/if_tunnel.h>
2062306a36Sopenharmony_ci#include <linux/spinlock.h>
2162306a36Sopenharmony_ci#include <net/protocol.h>
2262306a36Sopenharmony_ci#include <net/gre.h>
2362306a36Sopenharmony_ci#include <net/erspan.h>
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#include <net/icmp.h>
2662306a36Sopenharmony_ci#include <net/route.h>
2762306a36Sopenharmony_ci#include <net/xfrm.h>
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistatic const struct gre_protocol __rcu *gre_proto[GREPROTO_MAX] __read_mostly;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ciint gre_add_protocol(const struct gre_protocol *proto, u8 version)
3262306a36Sopenharmony_ci{
3362306a36Sopenharmony_ci	if (version >= GREPROTO_MAX)
3462306a36Sopenharmony_ci		return -EINVAL;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	return (cmpxchg((const struct gre_protocol **)&gre_proto[version], NULL, proto) == NULL) ?
3762306a36Sopenharmony_ci		0 : -EBUSY;
3862306a36Sopenharmony_ci}
3962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(gre_add_protocol);
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ciint gre_del_protocol(const struct gre_protocol *proto, u8 version)
4262306a36Sopenharmony_ci{
4362306a36Sopenharmony_ci	int ret;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	if (version >= GREPROTO_MAX)
4662306a36Sopenharmony_ci		return -EINVAL;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	ret = (cmpxchg((const struct gre_protocol **)&gre_proto[version], proto, NULL) == proto) ?
4962306a36Sopenharmony_ci		0 : -EBUSY;
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	if (ret)
5262306a36Sopenharmony_ci		return ret;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	synchronize_rcu();
5562306a36Sopenharmony_ci	return 0;
5662306a36Sopenharmony_ci}
5762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(gre_del_protocol);
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci/* Fills in tpi and returns header length to be pulled.
6062306a36Sopenharmony_ci * Note that caller must use pskb_may_pull() before pulling GRE header.
6162306a36Sopenharmony_ci */
6262306a36Sopenharmony_ciint gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi,
6362306a36Sopenharmony_ci		     bool *csum_err, __be16 proto, int nhs)
6462306a36Sopenharmony_ci{
6562306a36Sopenharmony_ci	const struct gre_base_hdr *greh;
6662306a36Sopenharmony_ci	__be32 *options;
6762306a36Sopenharmony_ci	int hdr_len;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	if (unlikely(!pskb_may_pull(skb, nhs + sizeof(struct gre_base_hdr))))
7062306a36Sopenharmony_ci		return -EINVAL;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	greh = (struct gre_base_hdr *)(skb->data + nhs);
7362306a36Sopenharmony_ci	if (unlikely(greh->flags & (GRE_VERSION | GRE_ROUTING)))
7462306a36Sopenharmony_ci		return -EINVAL;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	tpi->flags = gre_flags_to_tnl_flags(greh->flags);
7762306a36Sopenharmony_ci	hdr_len = gre_calc_hlen(tpi->flags);
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	if (!pskb_may_pull(skb, nhs + hdr_len))
8062306a36Sopenharmony_ci		return -EINVAL;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	greh = (struct gre_base_hdr *)(skb->data + nhs);
8362306a36Sopenharmony_ci	tpi->proto = greh->protocol;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	options = (__be32 *)(greh + 1);
8662306a36Sopenharmony_ci	if (greh->flags & GRE_CSUM) {
8762306a36Sopenharmony_ci		if (!skb_checksum_simple_validate(skb)) {
8862306a36Sopenharmony_ci			skb_checksum_try_convert(skb, IPPROTO_GRE,
8962306a36Sopenharmony_ci						 null_compute_pseudo);
9062306a36Sopenharmony_ci		} else if (csum_err) {
9162306a36Sopenharmony_ci			*csum_err = true;
9262306a36Sopenharmony_ci			return -EINVAL;
9362306a36Sopenharmony_ci		}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci		options++;
9662306a36Sopenharmony_ci	}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	if (greh->flags & GRE_KEY) {
9962306a36Sopenharmony_ci		tpi->key = *options;
10062306a36Sopenharmony_ci		options++;
10162306a36Sopenharmony_ci	} else {
10262306a36Sopenharmony_ci		tpi->key = 0;
10362306a36Sopenharmony_ci	}
10462306a36Sopenharmony_ci	if (unlikely(greh->flags & GRE_SEQ)) {
10562306a36Sopenharmony_ci		tpi->seq = *options;
10662306a36Sopenharmony_ci		options++;
10762306a36Sopenharmony_ci	} else {
10862306a36Sopenharmony_ci		tpi->seq = 0;
10962306a36Sopenharmony_ci	}
11062306a36Sopenharmony_ci	/* WCCP version 1 and 2 protocol decoding.
11162306a36Sopenharmony_ci	 * - Change protocol to IPv4/IPv6
11262306a36Sopenharmony_ci	 * - When dealing with WCCPv2, Skip extra 4 bytes in GRE header
11362306a36Sopenharmony_ci	 */
11462306a36Sopenharmony_ci	if (greh->flags == 0 && tpi->proto == htons(ETH_P_WCCP)) {
11562306a36Sopenharmony_ci		u8 _val, *val;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci		val = skb_header_pointer(skb, nhs + hdr_len,
11862306a36Sopenharmony_ci					 sizeof(_val), &_val);
11962306a36Sopenharmony_ci		if (!val)
12062306a36Sopenharmony_ci			return -EINVAL;
12162306a36Sopenharmony_ci		tpi->proto = proto;
12262306a36Sopenharmony_ci		if ((*val & 0xF0) != 0x40)
12362306a36Sopenharmony_ci			hdr_len += 4;
12462306a36Sopenharmony_ci	}
12562306a36Sopenharmony_ci	tpi->hdr_len = hdr_len;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	/* ERSPAN ver 1 and 2 protocol sets GRE key field
12862306a36Sopenharmony_ci	 * to 0 and sets the configured key in the
12962306a36Sopenharmony_ci	 * inner erspan header field
13062306a36Sopenharmony_ci	 */
13162306a36Sopenharmony_ci	if ((greh->protocol == htons(ETH_P_ERSPAN) && hdr_len != 4) ||
13262306a36Sopenharmony_ci	    greh->protocol == htons(ETH_P_ERSPAN2)) {
13362306a36Sopenharmony_ci		struct erspan_base_hdr *ershdr;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci		if (!pskb_may_pull(skb, nhs + hdr_len + sizeof(*ershdr)))
13662306a36Sopenharmony_ci			return -EINVAL;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci		ershdr = (struct erspan_base_hdr *)(skb->data + nhs + hdr_len);
13962306a36Sopenharmony_ci		tpi->key = cpu_to_be32(get_session_id(ershdr));
14062306a36Sopenharmony_ci	}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	return hdr_len;
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ciEXPORT_SYMBOL(gre_parse_header);
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_cistatic int gre_rcv(struct sk_buff *skb)
14762306a36Sopenharmony_ci{
14862306a36Sopenharmony_ci	const struct gre_protocol *proto;
14962306a36Sopenharmony_ci	u8 ver;
15062306a36Sopenharmony_ci	int ret;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	if (!pskb_may_pull(skb, 12))
15362306a36Sopenharmony_ci		goto drop;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	ver = skb->data[1]&0x7f;
15662306a36Sopenharmony_ci	if (ver >= GREPROTO_MAX)
15762306a36Sopenharmony_ci		goto drop;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	rcu_read_lock();
16062306a36Sopenharmony_ci	proto = rcu_dereference(gre_proto[ver]);
16162306a36Sopenharmony_ci	if (!proto || !proto->handler)
16262306a36Sopenharmony_ci		goto drop_unlock;
16362306a36Sopenharmony_ci	ret = proto->handler(skb);
16462306a36Sopenharmony_ci	rcu_read_unlock();
16562306a36Sopenharmony_ci	return ret;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_cidrop_unlock:
16862306a36Sopenharmony_ci	rcu_read_unlock();
16962306a36Sopenharmony_cidrop:
17062306a36Sopenharmony_ci	kfree_skb(skb);
17162306a36Sopenharmony_ci	return NET_RX_DROP;
17262306a36Sopenharmony_ci}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_cistatic int gre_err(struct sk_buff *skb, u32 info)
17562306a36Sopenharmony_ci{
17662306a36Sopenharmony_ci	const struct gre_protocol *proto;
17762306a36Sopenharmony_ci	const struct iphdr *iph = (const struct iphdr *)skb->data;
17862306a36Sopenharmony_ci	u8 ver = skb->data[(iph->ihl<<2) + 1]&0x7f;
17962306a36Sopenharmony_ci	int err = 0;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	if (ver >= GREPROTO_MAX)
18262306a36Sopenharmony_ci		return -EINVAL;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	rcu_read_lock();
18562306a36Sopenharmony_ci	proto = rcu_dereference(gre_proto[ver]);
18662306a36Sopenharmony_ci	if (proto && proto->err_handler)
18762306a36Sopenharmony_ci		proto->err_handler(skb, info);
18862306a36Sopenharmony_ci	else
18962306a36Sopenharmony_ci		err = -EPROTONOSUPPORT;
19062306a36Sopenharmony_ci	rcu_read_unlock();
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	return err;
19362306a36Sopenharmony_ci}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_cistatic const struct net_protocol net_gre_protocol = {
19662306a36Sopenharmony_ci	.handler     = gre_rcv,
19762306a36Sopenharmony_ci	.err_handler = gre_err,
19862306a36Sopenharmony_ci};
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_cistatic int __init gre_init(void)
20162306a36Sopenharmony_ci{
20262306a36Sopenharmony_ci	pr_info("GRE over IPv4 demultiplexor driver\n");
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	if (inet_add_protocol(&net_gre_protocol, IPPROTO_GRE) < 0) {
20562306a36Sopenharmony_ci		pr_err("can't add protocol\n");
20662306a36Sopenharmony_ci		return -EAGAIN;
20762306a36Sopenharmony_ci	}
20862306a36Sopenharmony_ci	return 0;
20962306a36Sopenharmony_ci}
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_cistatic void __exit gre_exit(void)
21262306a36Sopenharmony_ci{
21362306a36Sopenharmony_ci	inet_del_protocol(&net_gre_protocol, IPPROTO_GRE);
21462306a36Sopenharmony_ci}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_cimodule_init(gre_init);
21762306a36Sopenharmony_cimodule_exit(gre_exit);
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ciMODULE_DESCRIPTION("GRE over IPv4 demultiplexer driver");
22062306a36Sopenharmony_ciMODULE_AUTHOR("D. Kozlov (xeb@mail.ru)");
22162306a36Sopenharmony_ciMODULE_LICENSE("GPL");
222