gue: TX support for using remote checksum offload option
[firefly-linux-kernel-4.4.55.git] / net / ipv4 / fou.c
index efa70ad44906aac3204cd90e76b35c3159caa2ac..fb0db99adf9e4eefd5e5e7e231312e82ef04efb5 100644 (file)
@@ -38,21 +38,17 @@ static inline struct fou *fou_from_sock(struct sock *sk)
        return sk->sk_user_data;
 }
 
-static int fou_udp_encap_recv_deliver(struct sk_buff *skb,
-                                     u8 protocol, size_t len)
+static void fou_recv_pull(struct sk_buff *skb, size_t len)
 {
        struct iphdr *iph = ip_hdr(skb);
 
        /* Remove 'len' bytes from the packet (UDP header and
-        * FOU header if present), modify the protocol to the one
-        * we found, and then call rcv_encap.
+        * FOU header if present).
         */
        iph->tot_len = htons(ntohs(iph->tot_len) - len);
        __skb_pull(skb, len);
        skb_postpull_rcsum(skb, udp_hdr(skb), len);
        skb_reset_transport_header(skb);
-
-       return -protocol;
 }
 
 static int fou_udp_recv(struct sock *sk, struct sk_buff *skb)
@@ -62,16 +58,24 @@ static int fou_udp_recv(struct sock *sk, struct sk_buff *skb)
        if (!fou)
                return 1;
 
-       return fou_udp_encap_recv_deliver(skb, fou->protocol,
-                                         sizeof(struct udphdr));
+       fou_recv_pull(skb, sizeof(struct udphdr));
+
+       return -fou->protocol;
+}
+
+static int gue_control_message(struct sk_buff *skb, struct guehdr *guehdr)
+{
+       /* No support yet */
+       kfree_skb(skb);
+       return 0;
 }
 
 static int gue_udp_recv(struct sock *sk, struct sk_buff *skb)
 {
        struct fou *fou = fou_from_sock(sk);
-       size_t len;
+       size_t len, optlen, hdrlen;
        struct guehdr *guehdr;
-       struct udphdr *uh;
+       void *data;
 
        if (!fou)
                return 1;
@@ -80,22 +84,38 @@ static int gue_udp_recv(struct sock *sk, struct sk_buff *skb)
        if (!pskb_may_pull(skb, len))
                goto drop;
 
-       uh = udp_hdr(skb);
-       guehdr = (struct guehdr *)&uh[1];
+       guehdr = (struct guehdr *)&udp_hdr(skb)[1];
+
+       optlen = guehdr->hlen << 2;
+       len += optlen;
 
-       len += guehdr->hlen << 2;
        if (!pskb_may_pull(skb, len))
                goto drop;
 
-       if (guehdr->version != 0)
-               goto drop;
+       /* guehdr may change after pull */
+       guehdr = (struct guehdr *)&udp_hdr(skb)[1];
 
-       if (guehdr->flags) {
-               /* No support yet */
+       hdrlen = sizeof(struct guehdr) + optlen;
+
+       if (guehdr->version != 0 || validate_gue_flags(guehdr, optlen))
                goto drop;
+
+       /* Pull UDP and GUE headers */
+       fou_recv_pull(skb, len);
+
+       data = &guehdr[1];
+
+       if (guehdr->flags & GUE_FLAG_PRIV) {
+               data += GUE_LEN_PRIV;
+
+               /* Process private flags */
        }
 
-       return fou_udp_encap_recv_deliver(skb, guehdr->next_hdr, len);
+       if (unlikely(guehdr->control))
+               return gue_control_message(skb, guehdr);
+
+       return -guehdr->proto_ctype;
+
 drop:
        kfree_skb(skb);
        return 0;
@@ -151,36 +171,47 @@ static struct sk_buff **gue_gro_receive(struct sk_buff **head,
        const struct net_offload *ops;
        struct sk_buff **pp = NULL;
        struct sk_buff *p;
-       u8 proto;
        struct guehdr *guehdr;
-       unsigned int hlen, guehlen;
-       unsigned int off;
+       size_t len, optlen, hdrlen, off;
+       void *data;
        int flush = 1;
 
        off = skb_gro_offset(skb);
-       hlen = off + sizeof(*guehdr);
+       len = off + sizeof(*guehdr);
+
        guehdr = skb_gro_header_fast(skb, off);
-       if (skb_gro_header_hard(skb, hlen)) {
-               guehdr = skb_gro_header_slow(skb, hlen, off);
+       if (skb_gro_header_hard(skb, len)) {
+               guehdr = skb_gro_header_slow(skb, len, off);
                if (unlikely(!guehdr))
                        goto out;
        }
 
-       proto = guehdr->next_hdr;
+       optlen = guehdr->hlen << 2;
+       len += optlen;
 
-       rcu_read_lock();
-       offloads = NAPI_GRO_CB(skb)->is_ipv6 ? inet6_offloads : inet_offloads;
-       ops = rcu_dereference(offloads[proto]);
-       if (WARN_ON(!ops || !ops->callbacks.gro_receive))
-               goto out_unlock;
+       if (skb_gro_header_hard(skb, len)) {
+               guehdr = skb_gro_header_slow(skb, len, off);
+               if (unlikely(!guehdr))
+                       goto out;
+       }
 
-       guehlen = sizeof(*guehdr) + (guehdr->hlen << 2);
+       if (unlikely(guehdr->control) || guehdr->version != 0 ||
+           validate_gue_flags(guehdr, optlen))
+               goto out;
 
-       hlen = off + guehlen;
-       if (skb_gro_header_hard(skb, hlen)) {
-               guehdr = skb_gro_header_slow(skb, hlen, off);
-               if (unlikely(!guehdr))
-                       goto out_unlock;
+       hdrlen = sizeof(*guehdr) + optlen;
+
+       skb_gro_pull(skb, hdrlen);
+
+       /* Adjusted NAPI_GRO_CB(skb)->csum after skb_gro_pull()*/
+       skb_gro_postpull_rcsum(skb, guehdr, hdrlen);
+
+       data = &guehdr[1];
+
+       if (guehdr->flags & GUE_FLAG_PRIV) {
+               data += GUE_LEN_PRIV;
+
+               /* Process private flags */
        }
 
        flush = 0;
@@ -194,7 +225,7 @@ static struct sk_buff **gue_gro_receive(struct sk_buff **head,
                guehdr2 = (struct guehdr *)(p->data + off);
 
                /* Compare base GUE header to be equal (covers
-                * hlen, version, next_hdr, and flags.
+                * hlen, version, proto_ctype, and flags.
                 */
                if (guehdr->word != guehdr2->word) {
                        NAPI_GRO_CB(p)->same_flow = 0;
@@ -209,10 +240,11 @@ static struct sk_buff **gue_gro_receive(struct sk_buff **head,
                }
        }
 
-       skb_gro_pull(skb, guehlen);
-
-       /* Adjusted NAPI_GRO_CB(skb)->csum after skb_gro_pull()*/
-       skb_gro_postpull_rcsum(skb, guehdr, guehlen);
+       rcu_read_lock();
+       offloads = NAPI_GRO_CB(skb)->is_ipv6 ? inet6_offloads : inet_offloads;
+       ops = rcu_dereference(offloads[guehdr->proto_ctype]);
+       if (WARN_ON(!ops || !ops->callbacks.gro_receive))
+               goto out_unlock;
 
        pp = ops->callbacks.gro_receive(head, skb);
 
@@ -233,7 +265,7 @@ static int gue_gro_complete(struct sk_buff *skb, int nhoff)
        u8 proto;
        int err = -ENOENT;
 
-       proto = guehdr->next_hdr;
+       proto = guehdr->proto_ctype;
 
        guehlen = sizeof(*guehdr) + (guehdr->hlen << 2);
 
@@ -484,6 +516,125 @@ static const struct genl_ops fou_nl_ops[] = {
        },
 };
 
+static void fou_build_udp(struct sk_buff *skb, struct ip_tunnel_encap *e,
+                         struct flowi4 *fl4, u8 *protocol, __be16 sport)
+{
+       struct udphdr *uh;
+
+       skb_push(skb, sizeof(struct udphdr));
+       skb_reset_transport_header(skb);
+
+       uh = udp_hdr(skb);
+
+       uh->dest = e->dport;
+       uh->source = sport;
+       uh->len = htons(skb->len);
+       uh->check = 0;
+       udp_set_csum(!(e->flags & TUNNEL_ENCAP_FLAG_CSUM), skb,
+                    fl4->saddr, fl4->daddr, skb->len);
+
+       *protocol = IPPROTO_UDP;
+}
+
+int fou_build_header(struct sk_buff *skb, struct ip_tunnel_encap *e,
+                    u8 *protocol, struct flowi4 *fl4)
+{
+       bool csum = !!(e->flags & TUNNEL_ENCAP_FLAG_CSUM);
+       int type = csum ? SKB_GSO_UDP_TUNNEL_CSUM : SKB_GSO_UDP_TUNNEL;
+       __be16 sport;
+
+       skb = iptunnel_handle_offloads(skb, csum, type);
+
+       if (IS_ERR(skb))
+               return PTR_ERR(skb);
+
+       sport = e->sport ? : udp_flow_src_port(dev_net(skb->dev),
+                                              skb, 0, 0, false);
+       fou_build_udp(skb, e, fl4, protocol, sport);
+
+       return 0;
+}
+EXPORT_SYMBOL(fou_build_header);
+
+int gue_build_header(struct sk_buff *skb, struct ip_tunnel_encap *e,
+                    u8 *protocol, struct flowi4 *fl4)
+{
+       bool csum = !!(e->flags & TUNNEL_ENCAP_FLAG_CSUM);
+       int type = csum ? SKB_GSO_UDP_TUNNEL_CSUM : SKB_GSO_UDP_TUNNEL;
+       struct guehdr *guehdr;
+       size_t hdrlen, optlen = 0;
+       __be16 sport;
+       void *data;
+       bool need_priv = false;
+
+       if ((e->flags & TUNNEL_ENCAP_FLAG_REMCSUM) &&
+           skb->ip_summed == CHECKSUM_PARTIAL) {
+               csum = false;
+               optlen += GUE_PLEN_REMCSUM;
+               type |= SKB_GSO_TUNNEL_REMCSUM;
+               need_priv = true;
+       }
+
+       optlen += need_priv ? GUE_LEN_PRIV : 0;
+
+       skb = iptunnel_handle_offloads(skb, csum, type);
+
+       if (IS_ERR(skb))
+               return PTR_ERR(skb);
+
+       /* Get source port (based on flow hash) before skb_push */
+       sport = e->sport ? : udp_flow_src_port(dev_net(skb->dev),
+                                              skb, 0, 0, false);
+
+       hdrlen = sizeof(struct guehdr) + optlen;
+
+       skb_push(skb, hdrlen);
+
+       guehdr = (struct guehdr *)skb->data;
+
+       guehdr->control = 0;
+       guehdr->version = 0;
+       guehdr->hlen = optlen >> 2;
+       guehdr->flags = 0;
+       guehdr->proto_ctype = *protocol;
+
+       data = &guehdr[1];
+
+       if (need_priv) {
+               __be32 *flags = data;
+
+               guehdr->flags |= GUE_FLAG_PRIV;
+               *flags = 0;
+               data += GUE_LEN_PRIV;
+
+               if (type & SKB_GSO_TUNNEL_REMCSUM) {
+                       u16 csum_start = skb_checksum_start_offset(skb);
+                       __be16 *pd = data;
+
+                       if (csum_start < hdrlen)
+                               return -EINVAL;
+
+                       csum_start -= hdrlen;
+                       pd[0] = htons(csum_start);
+                       pd[1] = htons(csum_start + skb->csum_offset);
+
+                       if (!skb_is_gso(skb)) {
+                               skb->ip_summed = CHECKSUM_NONE;
+                               skb->encapsulation = 0;
+                       }
+
+                       *flags |= GUE_PFLAG_REMCSUM;
+                       data += GUE_PLEN_REMCSUM;
+               }
+
+       }
+
+       fou_build_udp(skb, e, fl4, protocol, sport);
+
+       return 0;
+}
+EXPORT_SYMBOL(gue_build_header);
+
 static int __init fou_init(void)
 {
        int ret;