linux/net/ipv4/gre_offload.c
<<
>>
Prefs
   1/*
   2 *      IPV4 GSO/GRO offload support
   3 *      Linux INET implementation
   4 *
   5 *      This program is free software; you can redistribute it and/or
   6 *      modify it under the terms of the GNU General Public License
   7 *      as published by the Free Software Foundation; either version
   8 *      2 of the License, or (at your option) any later version.
   9 *
  10 *      GRE GSO support
  11 */
  12
  13#include <linux/skbuff.h>
  14#include <linux/init.h>
  15#include <net/protocol.h>
  16#include <net/gre.h>
  17
  18static struct sk_buff *gre_gso_segment(struct sk_buff *skb,
  19                                       netdev_features_t features)
  20{
  21        int tnl_hlen = skb_inner_mac_header(skb) - skb_transport_header(skb);
  22        struct sk_buff *segs = ERR_PTR(-EINVAL);
  23        u16 mac_offset = skb->mac_header;
  24        __be16 protocol = skb->protocol;
  25        u16 mac_len = skb->mac_len;
  26        int gre_offset, outer_hlen;
  27        bool need_csum, gso_partial;
  28
  29        if (!skb->encapsulation)
  30                goto out;
  31
  32        if (unlikely(tnl_hlen < sizeof(struct gre_base_hdr)))
  33                goto out;
  34
  35        if (unlikely(!pskb_may_pull(skb, tnl_hlen)))
  36                goto out;
  37
  38        /* setup inner skb. */
  39        skb->encapsulation = 0;
  40        SKB_GSO_CB(skb)->encap_level = 0;
  41        __skb_pull(skb, tnl_hlen);
  42        skb_reset_mac_header(skb);
  43        skb_set_network_header(skb, skb_inner_network_offset(skb));
  44        skb->mac_len = skb_inner_network_offset(skb);
  45        skb->protocol = skb->inner_protocol;
  46
  47        need_csum = !!(skb_shinfo(skb)->gso_type & SKB_GSO_GRE_CSUM);
  48        skb->encap_hdr_csum = need_csum;
  49
  50        features &= skb->dev->hw_enc_features;
  51
  52        /* segment inner packet. */
  53        segs = skb_mac_gso_segment(skb, features);
  54        if (IS_ERR_OR_NULL(segs)) {
  55                skb_gso_error_unwind(skb, protocol, tnl_hlen, mac_offset,
  56                                     mac_len);
  57                goto out;
  58        }
  59
  60        gso_partial = !!(skb_shinfo(segs)->gso_type & SKB_GSO_PARTIAL);
  61
  62        outer_hlen = skb_tnl_header_len(skb);
  63        gre_offset = outer_hlen - tnl_hlen;
  64        skb = segs;
  65        do {
  66                struct gre_base_hdr *greh;
  67                __sum16 *pcsum;
  68
  69                /* Set up inner headers if we are offloading inner checksum */
  70                if (skb->ip_summed == CHECKSUM_PARTIAL) {
  71                        skb_reset_inner_headers(skb);
  72                        skb->encapsulation = 1;
  73                }
  74
  75                skb->mac_len = mac_len;
  76                skb->protocol = protocol;
  77
  78                __skb_push(skb, outer_hlen);
  79                skb_reset_mac_header(skb);
  80                skb_set_network_header(skb, mac_len);
  81                skb_set_transport_header(skb, gre_offset);
  82
  83                if (!need_csum)
  84                        continue;
  85
  86                greh = (struct gre_base_hdr *)skb_transport_header(skb);
  87                pcsum = (__sum16 *)(greh + 1);
  88
  89                if (gso_partial && skb_is_gso(skb)) {
  90                        unsigned int partial_adj;
  91
  92                        /* Adjust checksum to account for the fact that
  93                         * the partial checksum is based on actual size
  94                         * whereas headers should be based on MSS size.
  95                         */
  96                        partial_adj = skb->len + skb_headroom(skb) -
  97                                      SKB_GSO_CB(skb)->data_offset -
  98                                      skb_shinfo(skb)->gso_size;
  99                        *pcsum = ~csum_fold((__force __wsum)htonl(partial_adj));
 100                } else {
 101                        *pcsum = 0;
 102                }
 103
 104                *(pcsum + 1) = 0;
 105                *pcsum = gso_make_checksum(skb, 0);
 106        } while ((skb = skb->next));
 107out:
 108        return segs;
 109}
 110
 111static struct sk_buff **gre_gro_receive(struct sk_buff **head,
 112                                        struct sk_buff *skb)
 113{
 114        struct sk_buff **pp = NULL;
 115        struct sk_buff *p;
 116        const struct gre_base_hdr *greh;
 117        unsigned int hlen, grehlen;
 118        unsigned int off;
 119        int flush = 1;
 120        struct packet_offload *ptype;
 121        __be16 type;
 122
 123        if (NAPI_GRO_CB(skb)->encap_mark)
 124                goto out;
 125
 126        NAPI_GRO_CB(skb)->encap_mark = 1;
 127
 128        off = skb_gro_offset(skb);
 129        hlen = off + sizeof(*greh);
 130        greh = skb_gro_header_fast(skb, off);
 131        if (skb_gro_header_hard(skb, hlen)) {
 132                greh = skb_gro_header_slow(skb, hlen, off);
 133                if (unlikely(!greh))
 134                        goto out;
 135        }
 136
 137        /* Only support version 0 and K (key), C (csum) flags. Note that
 138         * although the support for the S (seq#) flag can be added easily
 139         * for GRO, this is problematic for GSO hence can not be enabled
 140         * here because a GRO pkt may end up in the forwarding path, thus
 141         * requiring GSO support to break it up correctly.
 142         */
 143        if ((greh->flags & ~(GRE_KEY|GRE_CSUM)) != 0)
 144                goto out;
 145
 146        /* We can only support GRE_CSUM if we can track the location of
 147         * the GRE header.  In the case of FOU/GUE we cannot because the
 148         * outer UDP header displaces the GRE header leaving us in a state
 149         * of limbo.
 150         */
 151        if ((greh->flags & GRE_CSUM) && NAPI_GRO_CB(skb)->is_fou)
 152                goto out;
 153
 154        type = greh->protocol;
 155
 156        rcu_read_lock();
 157        ptype = gro_find_receive_by_type(type);
 158        if (!ptype)
 159                goto out_unlock;
 160
 161        grehlen = GRE_HEADER_SECTION;
 162
 163        if (greh->flags & GRE_KEY)
 164                grehlen += GRE_HEADER_SECTION;
 165
 166        if (greh->flags & GRE_CSUM)
 167                grehlen += GRE_HEADER_SECTION;
 168
 169        hlen = off + grehlen;
 170        if (skb_gro_header_hard(skb, hlen)) {
 171                greh = skb_gro_header_slow(skb, hlen, off);
 172                if (unlikely(!greh))
 173                        goto out_unlock;
 174        }
 175
 176        /* Don't bother verifying checksum if we're going to flush anyway. */
 177        if ((greh->flags & GRE_CSUM) && !NAPI_GRO_CB(skb)->flush) {
 178                if (skb_gro_checksum_simple_validate(skb))
 179                        goto out_unlock;
 180
 181                skb_gro_checksum_try_convert(skb, IPPROTO_GRE, 0,
 182                                             null_compute_pseudo);
 183        }
 184
 185        for (p = *head; p; p = p->next) {
 186                const struct gre_base_hdr *greh2;
 187
 188                if (!NAPI_GRO_CB(p)->same_flow)
 189                        continue;
 190
 191                /* The following checks are needed to ensure only pkts
 192                 * from the same tunnel are considered for aggregation.
 193                 * The criteria for "the same tunnel" includes:
 194                 * 1) same version (we only support version 0 here)
 195                 * 2) same protocol (we only support ETH_P_IP for now)
 196                 * 3) same set of flags
 197                 * 4) same key if the key field is present.
 198                 */
 199                greh2 = (struct gre_base_hdr *)(p->data + off);
 200
 201                if (greh2->flags != greh->flags ||
 202                    greh2->protocol != greh->protocol) {
 203                        NAPI_GRO_CB(p)->same_flow = 0;
 204                        continue;
 205                }
 206                if (greh->flags & GRE_KEY) {
 207                        /* compare keys */
 208                        if (*(__be32 *)(greh2+1) != *(__be32 *)(greh+1)) {
 209                                NAPI_GRO_CB(p)->same_flow = 0;
 210                                continue;
 211                        }
 212                }
 213        }
 214
 215        skb_gro_pull(skb, grehlen);
 216
 217        /* Adjusted NAPI_GRO_CB(skb)->csum after skb_gro_pull()*/
 218        skb_gro_postpull_rcsum(skb, greh, grehlen);
 219
 220        pp = call_gro_receive(ptype->callbacks.gro_receive, head, skb);
 221        flush = 0;
 222
 223out_unlock:
 224        rcu_read_unlock();
 225out:
 226        skb_gro_flush_final(skb, pp, flush);
 227
 228        return pp;
 229}
 230
 231static int gre_gro_complete(struct sk_buff *skb, int nhoff)
 232{
 233        struct gre_base_hdr *greh = (struct gre_base_hdr *)(skb->data + nhoff);
 234        struct packet_offload *ptype;
 235        unsigned int grehlen = sizeof(*greh);
 236        int err = -ENOENT;
 237        __be16 type;
 238
 239        skb->encapsulation = 1;
 240        skb_shinfo(skb)->gso_type = SKB_GSO_GRE;
 241
 242        type = greh->protocol;
 243        if (greh->flags & GRE_KEY)
 244                grehlen += GRE_HEADER_SECTION;
 245
 246        if (greh->flags & GRE_CSUM)
 247                grehlen += GRE_HEADER_SECTION;
 248
 249        rcu_read_lock();
 250        ptype = gro_find_complete_by_type(type);
 251        if (ptype)
 252                err = ptype->callbacks.gro_complete(skb, nhoff + grehlen);
 253
 254        rcu_read_unlock();
 255
 256        skb_set_inner_mac_header(skb, nhoff + grehlen);
 257
 258        return err;
 259}
 260
 261static const struct net_offload gre_offload = {
 262        .callbacks = {
 263                .gso_segment = gre_gso_segment,
 264                .gro_receive = gre_gro_receive,
 265                .gro_complete = gre_gro_complete,
 266        },
 267};
 268
 269static int __init gre_offload_init(void)
 270{
 271        int err;
 272
 273        err = inet_add_offload(&gre_offload, IPPROTO_GRE);
 274#if IS_ENABLED(CONFIG_IPV6)
 275        if (err)
 276                return err;
 277
 278        err = inet6_add_offload(&gre_offload, IPPROTO_GRE);
 279        if (err)
 280                inet_del_offload(&gre_offload, IPPROTO_GRE);
 281#endif
 282
 283        return err;
 284}
 285device_initcall(gre_offload_init);
 286