Populate GRO receive and GRO complete functions for GTP-Uv0 and v1.

Signed-off-by: Tom Herbert <t...@quantonium.net>
---
 drivers/net/gtp.c | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 204 insertions(+)

diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c
index b53946f8b10b..2f9d810cf19f 100644
--- a/drivers/net/gtp.c
+++ b/drivers/net/gtp.c
@@ -22,6 +22,7 @@
 #include <linux/jhash.h>
 #include <linux/if_tunnel.h>
 #include <linux/net.h>
+#include <linux/netdevice.h>
 #include <linux/file.h>
 #include <linux/gtp.h>
 
@@ -429,6 +430,205 @@ static int gtp1u_udp_encap_recv(struct sock *sk, struct 
sk_buff *skb)
        return 1;
 }
 
+static struct sk_buff **gtp_gro_receive_finish(struct sock *sk,
+                                              struct sk_buff **head,
+                                              struct sk_buff *skb,
+                                              void *hdr, size_t hdrlen)
+{
+       const struct packet_offload *ptype;
+       struct sk_buff **pp;
+       __be16 type;
+
+       type = ipver_to_eth((struct iphdr *)((void *)hdr + hdrlen));
+       if (!type)
+               goto out_err;
+
+       rcu_read_lock();
+
+       ptype = gro_find_receive_by_type(type);
+       if (!ptype)
+               goto out_unlock_err;
+
+       skb_gro_pull(skb, hdrlen);
+       skb_gro_postpull_rcsum(skb, hdr, hdrlen);
+       pp = call_gro_receive(ptype->callbacks.gro_receive, head, skb);
+
+       rcu_read_unlock();
+
+       return pp;
+
+out_unlock_err:
+       rcu_read_unlock();
+out_err:
+       NAPI_GRO_CB(skb)->flush |= 1;
+       return NULL;
+}
+
+static struct sk_buff **gtp0_gro_receive(struct sock *sk,
+                                        struct sk_buff **head,
+                                        struct sk_buff *skb)
+{
+       struct gtp0_header *gtp0;
+       size_t len, hdrlen, off;
+       struct sk_buff *p;
+
+       off = skb_gro_offset(skb);
+       len = off + sizeof(*gtp0);
+       hdrlen = sizeof(*gtp0);
+
+       gtp0 = skb_gro_header_fast(skb, off);
+       if (skb_gro_header_hard(skb, len)) {
+               gtp0 = skb_gro_header_slow(skb, len, off);
+               if (unlikely(!gtp0))
+                       goto out;
+       }
+
+       if ((gtp0->flags >> 5) != GTP_V0 || gtp0->type != GTP_TPDU)
+               goto out;
+
+       hdrlen += sizeof(*gtp0);
+
+       /* To get IP version */
+       len += sizeof(struct iphdr);
+
+       /* Now get header with GTP header an IPv4 header (for version) */
+       if (skb_gro_header_hard(skb, len)) {
+               gtp0 = skb_gro_header_slow(skb, len, off);
+               if (unlikely(!gtp0))
+                       goto out;
+       }
+
+       for (p = *head; p; p = p->next) {
+               const struct gtp0_header *gtp0_t;
+
+               if (!NAPI_GRO_CB(p)->same_flow)
+                       continue;
+
+               gtp0_t = (struct gtp0_header *)(p->data + off);
+
+               if (gtp0->flags != gtp0_t->flags ||
+                   gtp0->type != gtp0_t->type ||
+                   gtp0->flow != gtp0_t->flow ||
+                   gtp0->tid != gtp0_t->tid) {
+                       NAPI_GRO_CB(p)->same_flow = 0;
+                       continue;
+               }
+       }
+
+       return gtp_gro_receive_finish(sk, head, skb, gtp0, hdrlen);
+
+out:
+       NAPI_GRO_CB(skb)->flush |= 1;
+
+       return NULL;
+}
+
+static struct sk_buff **gtp1u_gro_receive(struct sock *sk,
+                                         struct sk_buff **head,
+                                         struct sk_buff *skb)
+{
+       struct gtp1_header *gtp1;
+       size_t len, hdrlen, off;
+       struct sk_buff *p;
+
+       off = skb_gro_offset(skb);
+       len = off + sizeof(*gtp1);
+       hdrlen = sizeof(*gtp1);
+
+       gtp1 = skb_gro_header_fast(skb, off);
+       if (skb_gro_header_hard(skb, len)) {
+               gtp1 = skb_gro_header_slow(skb, len, off);
+               if (unlikely(!gtp1))
+                       goto out;
+       }
+
+       if ((gtp1->flags >> 5) != GTP_V1 || gtp1->type != GTP_TPDU)
+               goto out;
+
+       if (gtp1->flags & GTP1_F_MASK) {
+               hdrlen += 4;
+               len += 4;
+       }
+
+       len += sizeof(struct iphdr);
+
+       /* Now get header with GTP header an IPv4 header (for version) */
+       if (skb_gro_header_hard(skb, len)) {
+               gtp1 = skb_gro_header_slow(skb, len, off);
+               if (unlikely(!gtp1))
+                       goto out;
+       }
+
+       for (p = *head; p; p = p->next) {
+               const struct gtp1_header *gtp1_t;
+
+               if (!NAPI_GRO_CB(p)->same_flow)
+                       continue;
+
+               gtp1_t = (struct gtp1_header *)(p->data + off);
+
+               if (gtp1->flags != gtp1_t->flags ||
+                   gtp1->type != gtp1_t->type ||
+                   gtp1->tid != gtp1_t->tid) {
+                       NAPI_GRO_CB(p)->same_flow = 0;
+                       continue;
+               }
+       }
+
+       return gtp_gro_receive_finish(sk, head, skb, gtp1, hdrlen);
+
+out:
+       NAPI_GRO_CB(skb)->flush = 1;
+
+       return NULL;
+}
+
+static int gtp_gro_complete_finish(struct sock *sk, struct sk_buff *skb,
+                                  int nhoff, size_t hdrlen)
+{
+       struct packet_offload *ptype;
+       int err = -EINVAL;
+       __be16 type;
+
+       type = ipver_to_eth((struct iphdr *)(skb->data + nhoff + hdrlen));
+       if (!type)
+               return err;
+
+       rcu_read_lock();
+       ptype = gro_find_complete_by_type(type);
+       if (ptype)
+               err = ptype->callbacks.gro_complete(skb, nhoff + hdrlen);
+
+       rcu_read_unlock();
+
+       skb_set_inner_mac_header(skb, nhoff + hdrlen);
+
+       return err;
+}
+
+static int gtp0_gro_complete(struct sock *sk, struct sk_buff *skb, int nhoff)
+{
+       struct gtp0_header *gtp0 = (struct gtp0_header *)(skb->data + nhoff);
+       size_t hdrlen = sizeof(struct gtp0_header);
+
+       gtp0->length = htons(skb->len - nhoff - hdrlen);
+
+       return gtp_gro_complete_finish(sk, skb, nhoff, hdrlen);
+}
+
+static int gtp1u_gro_complete(struct sock *sk, struct sk_buff *skb, int nhoff)
+{
+       struct gtp1_header *gtp1 = (struct gtp1_header *)(skb->data + nhoff);
+       size_t hdrlen = sizeof(struct gtp1_header);
+
+       if (gtp1->flags & GTP1_F_MASK)
+               hdrlen += 4;
+
+       gtp1->length = htons(skb->len - nhoff - hdrlen);
+
+       return gtp_gro_complete_finish(sk, skb, nhoff, hdrlen);
+}
+
 static void gtp_encap_destroy(struct sock *sk)
 {
        struct gtp_dev *gtp;
@@ -946,9 +1146,13 @@ static int gtp_encap_enable_sock(struct socket *sock, int 
type,
        switch (type) {
        case UDP_ENCAP_GTP0:
                tuncfg.encap_rcv = gtp0_udp_encap_recv;
+               tuncfg.gro_receive = gtp0_gro_receive;
+               tuncfg.gro_complete = gtp0_gro_complete;
                break;
        case UDP_ENCAP_GTP1U:
                tuncfg.encap_rcv = gtp1u_udp_encap_recv;
+               tuncfg.gro_receive = gtp1u_gro_receive;
+               tuncfg.gro_complete = gtp1u_gro_complete;
                break;
        default:
                pr_debug("Unknown encap type %u\n", type);
-- 
2.11.0

Reply via email to