mirror of
https://github.com/openvswitch/ovs
synced 2025-10-23 14:57:06 +00:00
The ERSPAN feature depends on the gre kernel module so on systems where the ERSPAN feature isn't supported the openvswitch kernel module would attempt to grab the ipv4 GRE protocol entry point and would fail to load if it could not. This patch modifies openvswitch to not fail to load when the gre kernel module is loaded and instead it will print a warning message to the kernel system log indicating that the ERSPAN feature may not be available. We need this patch because users are experiencing failures due to the conflicts and high priority bugs are resulting. Signed-off-by: Greg Rose <gvrose8192@gmail.com> Signed-off-by: Ben Pfaff <blp@ovn.org> Acked-by: William Tu <u9012063@gmail.com> Signed-off-by: Ben Pfaff <blp@ovn.org>
255 lines
5.8 KiB
C
255 lines
5.8 KiB
C
/*
|
|
* Copyright (c) 2007-2013 Nicira, Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of version 2 of the GNU General Public
|
|
* License as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301, USA
|
|
*/
|
|
|
|
#include <linux/version.h>
|
|
#include <linux/kconfig.h>
|
|
#include <linux/module.h>
|
|
#include <linux/if.h>
|
|
#include <linux/if_tunnel.h>
|
|
#include <linux/icmp.h>
|
|
#include <linux/in.h>
|
|
#include <linux/ip.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/kmod.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/spinlock.h>
|
|
|
|
#include <net/gre.h>
|
|
#include <net/icmp.h>
|
|
#include <net/protocol.h>
|
|
#include <net/route.h>
|
|
#include <net/xfrm.h>
|
|
|
|
#include "gso.h"
|
|
|
|
#ifndef USE_UPSTREAM_TUNNEL
|
|
#if IS_ENABLED(CONFIG_NET_IPGRE_DEMUX)
|
|
|
|
static const struct gre_protocol __rcu *gre_proto[GREPROTO_MAX] __read_mostly;
|
|
|
|
int rpl_gre_add_protocol(const struct gre_protocol *proto, u8 version)
|
|
{
|
|
if (version >= GREPROTO_MAX)
|
|
return -EINVAL;
|
|
|
|
return (cmpxchg((const struct gre_protocol **)&gre_proto[version], NULL, proto) == NULL) ?
|
|
0 : -EBUSY;
|
|
}
|
|
EXPORT_SYMBOL_GPL(rpl_gre_add_protocol);
|
|
|
|
int rpl_gre_del_protocol(const struct gre_protocol *proto, u8 version)
|
|
{
|
|
int ret;
|
|
|
|
if (version >= GREPROTO_MAX)
|
|
return -EINVAL;
|
|
|
|
ret = (cmpxchg((const struct gre_protocol **)&gre_proto[version], proto, NULL) == proto) ?
|
|
0 : -EBUSY;
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
synchronize_rcu();
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(rpl_gre_del_protocol);
|
|
|
|
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;
|
|
const struct iphdr *iph = (const struct iphdr *)skb->data;
|
|
u8 ver = skb->data[(iph->ihl<<2) + 1]&0x7f;
|
|
|
|
if (ver >= GREPROTO_MAX)
|
|
return;
|
|
|
|
rcu_read_lock();
|
|
proto = rcu_dereference(gre_proto[ver]);
|
|
if (proto && proto->err_handler)
|
|
proto->err_handler(skb, info);
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
static const struct net_protocol net_gre_protocol = {
|
|
.handler = gre_rcv,
|
|
.err_handler = gre_err,
|
|
.netns_ok = 1,
|
|
};
|
|
|
|
int rpl_gre_init(void)
|
|
{
|
|
pr_info("GRE over IPv4 demultiplexor driver\n");
|
|
|
|
if (inet_add_protocol(&net_gre_protocol, IPPROTO_GRE) < 0) {
|
|
pr_err("can't add protocol\n");
|
|
return -EEXIST;
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(rpl_gre_init);
|
|
|
|
void rpl_gre_exit(void)
|
|
{
|
|
inet_del_protocol(&net_gre_protocol, IPPROTO_GRE);
|
|
}
|
|
EXPORT_SYMBOL_GPL(rpl_gre_exit);
|
|
|
|
#define ip_gre_calc_hlen rpl_ip_gre_calc_hlen
|
|
#define gre_calc_hlen rpl_ip_gre_calc_hlen
|
|
static int rpl_ip_gre_calc_hlen(__be16 o_flags)
|
|
{
|
|
int addend = 4;
|
|
|
|
if (o_flags & TUNNEL_CSUM)
|
|
addend += 4;
|
|
if (o_flags & TUNNEL_KEY)
|
|
addend += 4;
|
|
if (o_flags & TUNNEL_SEQ)
|
|
addend += 4;
|
|
return addend;
|
|
}
|
|
|
|
void rpl_gre_build_header(struct sk_buff *skb, const struct tnl_ptk_info *tpi,
|
|
int hdr_len)
|
|
{
|
|
struct gre_base_hdr *greh;
|
|
|
|
skb_push(skb, hdr_len);
|
|
|
|
skb_reset_transport_header(skb);
|
|
greh = (struct gre_base_hdr *)skb->data;
|
|
greh->flags = tnl_flags_to_gre_flags(tpi->flags);
|
|
greh->protocol = tpi->proto;
|
|
|
|
if (tpi->flags&(TUNNEL_KEY|TUNNEL_CSUM|TUNNEL_SEQ)) {
|
|
__be32 *ptr = (__be32 *)(((u8 *)greh) + hdr_len - 4);
|
|
|
|
if (tpi->flags&TUNNEL_SEQ) {
|
|
*ptr = tpi->seq;
|
|
ptr--;
|
|
}
|
|
if (tpi->flags&TUNNEL_KEY) {
|
|
*ptr = tpi->key;
|
|
ptr--;
|
|
}
|
|
if (tpi->flags&TUNNEL_CSUM &&
|
|
!(skb_shinfo(skb)->gso_type &
|
|
(SKB_GSO_GRE|SKB_GSO_GRE_CSUM))) {
|
|
*ptr = 0;
|
|
*(__sum16 *)ptr = csum_fold(skb_checksum(skb, 0,
|
|
skb->len, 0));
|
|
}
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(rpl_gre_build_header);
|
|
|
|
/* Fills in tpi and returns header length to be pulled. */
|
|
int rpl_gre_parse_header(struct sk_buff *skb, struct tnl_ptk_info *tpi,
|
|
bool *csum_err, __be16 proto, int nhs)
|
|
{
|
|
const struct gre_base_hdr *greh;
|
|
__be32 *options;
|
|
int hdr_len;
|
|
|
|
if (unlikely(!pskb_may_pull(skb, nhs + sizeof(struct gre_base_hdr))))
|
|
return -EINVAL;
|
|
|
|
greh = (struct gre_base_hdr *)(skb->data + nhs);
|
|
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, nhs + hdr_len))
|
|
return -EINVAL;
|
|
|
|
greh = (struct gre_base_hdr *)(skb->data + nhs);
|
|
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 IPv4/IPv6
|
|
* - When dealing with WCCPv2, Skip extra 4 bytes in GRE header
|
|
*/
|
|
if (greh->flags == 0 && tpi->proto == htons(ETH_P_WCCP)) {
|
|
tpi->proto = proto;
|
|
if ((*(u8 *)options & 0xF0) != 0x40)
|
|
hdr_len += 4;
|
|
}
|
|
tpi->hdr_len = hdr_len;
|
|
return hdr_len;
|
|
}
|
|
EXPORT_SYMBOL(rpl_gre_parse_header);
|
|
|
|
#endif /* CONFIG_NET_IPGRE_DEMUX */
|
|
#endif /* USE_UPSTREAM_TUNNEL */
|