vxlan: fix GRO with VXLAN-GPE
authorJiri Benc <jbenc@redhat.com>
Fri, 21 Jul 2023 14:30:47 +0000 (16:30 +0200)
committerDavid S. Miller <davem@davemloft.net>
Mon, 24 Jul 2023 09:47:09 +0000 (10:47 +0100)
In VXLAN-GPE, there may not be an Ethernet header following the VXLAN
header. But in GRO, the vxlan driver calls eth_gro_receive
unconditionally, which means the following header is incorrectly parsed
as Ethernet.

Introduce GPE specific GRO handling.

For better performance, do not check for GPE during GRO but rather
install a different set of functions at setup time.

Fixes: e1e5314de08ba ("vxlan: implement GPE")
Reported-by: Paolo Abeni <pabeni@redhat.com>
Signed-off-by: Jiri Benc <jbenc@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/vxlan/vxlan_core.c

index 6d82adf..c9a9373 100644 (file)
@@ -675,26 +675,24 @@ static struct vxlanhdr *vxlan_gro_remcsum(struct sk_buff *skb,
        return vh;
 }
 
-static struct sk_buff *vxlan_gro_receive(struct sock *sk,
-                                        struct list_head *head,
-                                        struct sk_buff *skb)
+static struct vxlanhdr *vxlan_gro_prepare_receive(struct sock *sk,
+                                                 struct list_head *head,
+                                                 struct sk_buff *skb,
+                                                 struct gro_remcsum *grc)
 {
-       struct sk_buff *pp = NULL;
        struct sk_buff *p;
        struct vxlanhdr *vh, *vh2;
        unsigned int hlen, off_vx;
-       int flush = 1;
        struct vxlan_sock *vs = rcu_dereference_sk_user_data(sk);
        __be32 flags;
-       struct gro_remcsum grc;
 
-       skb_gro_remcsum_init(&grc);
+       skb_gro_remcsum_init(grc);
 
        off_vx = skb_gro_offset(skb);
        hlen = off_vx + sizeof(*vh);
        vh = skb_gro_header(skb, hlen, off_vx);
        if (unlikely(!vh))
-               goto out;
+               return NULL;
 
        skb_gro_postpull_rcsum(skb, vh, sizeof(struct vxlanhdr));
 
@@ -702,12 +700,12 @@ static struct sk_buff *vxlan_gro_receive(struct sock *sk,
 
        if ((flags & VXLAN_HF_RCO) && (vs->flags & VXLAN_F_REMCSUM_RX)) {
                vh = vxlan_gro_remcsum(skb, off_vx, vh, sizeof(struct vxlanhdr),
-                                      vh->vx_vni, &grc,
+                                      vh->vx_vni, grc,
                                       !!(vs->flags &
                                          VXLAN_F_REMCSUM_NOPARTIAL));
 
                if (!vh)
-                       goto out;
+                       return NULL;
        }
 
        skb_gro_pull(skb, sizeof(struct vxlanhdr)); /* pull vxlan header */
@@ -724,12 +722,48 @@ static struct sk_buff *vxlan_gro_receive(struct sock *sk,
                }
        }
 
-       pp = call_gro_receive(eth_gro_receive, head, skb);
-       flush = 0;
+       return vh;
+}
+
+static struct sk_buff *vxlan_gro_receive(struct sock *sk,
+                                        struct list_head *head,
+                                        struct sk_buff *skb)
+{
+       struct sk_buff *pp = NULL;
+       struct gro_remcsum grc;
+       int flush = 1;
 
-out:
+       if (vxlan_gro_prepare_receive(sk, head, skb, &grc)) {
+               pp = call_gro_receive(eth_gro_receive, head, skb);
+               flush = 0;
+       }
        skb_gro_flush_final_remcsum(skb, pp, flush, &grc);
+       return pp;
+}
 
+static struct sk_buff *vxlan_gpe_gro_receive(struct sock *sk,
+                                            struct list_head *head,
+                                            struct sk_buff *skb)
+{
+       const struct packet_offload *ptype;
+       struct sk_buff *pp = NULL;
+       struct gro_remcsum grc;
+       struct vxlanhdr *vh;
+       __be16 protocol;
+       int flush = 1;
+
+       vh = vxlan_gro_prepare_receive(sk, head, skb, &grc);
+       if (vh) {
+               if (!vxlan_parse_gpe_proto(vh, &protocol))
+                       goto out;
+               ptype = gro_find_receive_by_type(protocol);
+               if (!ptype)
+                       goto out;
+               pp = call_gro_receive(ptype->callbacks.gro_receive, head, skb);
+               flush = 0;
+       }
+out:
+       skb_gro_flush_final_remcsum(skb, pp, flush, &grc);
        return pp;
 }
 
@@ -741,6 +775,21 @@ static int vxlan_gro_complete(struct sock *sk, struct sk_buff *skb, int nhoff)
        return eth_gro_complete(skb, nhoff + sizeof(struct vxlanhdr));
 }
 
+static int vxlan_gpe_gro_complete(struct sock *sk, struct sk_buff *skb, int nhoff)
+{
+       struct vxlanhdr *vh = (struct vxlanhdr *)(skb->data + nhoff);
+       const struct packet_offload *ptype;
+       int err = -ENOSYS;
+       __be16 protocol;
+
+       if (!vxlan_parse_gpe_proto(vh, &protocol))
+               return err;
+       ptype = gro_find_complete_by_type(protocol);
+       if (ptype)
+               err = ptype->callbacks.gro_complete(skb, nhoff + sizeof(struct vxlanhdr));
+       return err;
+}
+
 static struct vxlan_fdb *vxlan_fdb_alloc(struct vxlan_dev *vxlan, const u8 *mac,
                                         __u16 state, __be32 src_vni,
                                         __u16 ndm_flags)
@@ -3376,8 +3425,13 @@ static struct vxlan_sock *vxlan_socket_create(struct net *net, bool ipv6,
        tunnel_cfg.encap_rcv = vxlan_rcv;
        tunnel_cfg.encap_err_lookup = vxlan_err_lookup;
        tunnel_cfg.encap_destroy = NULL;
-       tunnel_cfg.gro_receive = vxlan_gro_receive;
-       tunnel_cfg.gro_complete = vxlan_gro_complete;
+       if (vs->flags & VXLAN_F_GPE) {
+               tunnel_cfg.gro_receive = vxlan_gpe_gro_receive;
+               tunnel_cfg.gro_complete = vxlan_gpe_gro_complete;
+       } else {
+               tunnel_cfg.gro_receive = vxlan_gro_receive;
+               tunnel_cfg.gro_complete = vxlan_gro_complete;
+       }
 
        setup_udp_tunnel_sock(net, sock, &tunnel_cfg);