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