linux/net/ipv6/ip6_offload.c
<<
>>
Prefs
   1/*
   2 *      IPV6 GSO/GRO offload support
   3 *      Linux INET6 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
  11#include <linux/kernel.h>
  12#include <linux/socket.h>
  13#include <linux/netdevice.h>
  14#include <linux/skbuff.h>
  15#include <linux/printk.h>
  16
  17#include <net/protocol.h>
  18#include <net/ipv6.h>
  19
  20#include "ip6_offload.h"
  21
  22static int ipv6_gso_pull_exthdrs(struct sk_buff *skb, int proto)
  23{
  24        const struct net_offload *ops = NULL;
  25
  26        for (;;) {
  27                struct ipv6_opt_hdr *opth;
  28                int len;
  29
  30                if (proto != NEXTHDR_HOP) {
  31                        ops = rcu_dereference(inet6_offloads[proto]);
  32
  33                        if (unlikely(!ops))
  34                                break;
  35
  36                        if (!(ops->flags & INET6_PROTO_GSO_EXTHDR))
  37                                break;
  38                }
  39
  40                if (unlikely(!pskb_may_pull(skb, 8)))
  41                        break;
  42
  43                opth = (void *)skb->data;
  44                len = ipv6_optlen(opth);
  45
  46                if (unlikely(!pskb_may_pull(skb, len)))
  47                        break;
  48
  49                proto = opth->nexthdr;
  50                __skb_pull(skb, len);
  51        }
  52
  53        return proto;
  54}
  55
  56static int ipv6_gso_send_check(struct sk_buff *skb)
  57{
  58        const struct ipv6hdr *ipv6h;
  59        const struct net_offload *ops;
  60        int err = -EINVAL;
  61
  62        if (unlikely(!pskb_may_pull(skb, sizeof(*ipv6h))))
  63                goto out;
  64
  65        ipv6h = ipv6_hdr(skb);
  66        __skb_pull(skb, sizeof(*ipv6h));
  67        err = -EPROTONOSUPPORT;
  68
  69        rcu_read_lock();
  70        ops = rcu_dereference(inet6_offloads[
  71                ipv6_gso_pull_exthdrs(skb, ipv6h->nexthdr)]);
  72
  73        if (likely(ops && ops->callbacks.gso_send_check)) {
  74                skb_reset_transport_header(skb);
  75                err = ops->callbacks.gso_send_check(skb);
  76        }
  77        rcu_read_unlock();
  78
  79out:
  80        return err;
  81}
  82
  83static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb,
  84        netdev_features_t features)
  85{
  86        struct sk_buff *segs = ERR_PTR(-EINVAL);
  87        struct ipv6hdr *ipv6h;
  88        const struct net_offload *ops;
  89        int proto;
  90        struct frag_hdr *fptr;
  91        unsigned int unfrag_ip6hlen;
  92        u8 *prevhdr;
  93        int offset = 0;
  94        bool tunnel;
  95
  96        if (unlikely(skb_shinfo(skb)->gso_type &
  97                     ~(SKB_GSO_UDP |
  98                       SKB_GSO_DODGY |
  99                       SKB_GSO_TCP_ECN |
 100                       SKB_GSO_GRE |
 101                       SKB_GSO_UDP_TUNNEL |
 102                       SKB_GSO_MPLS |
 103                       SKB_GSO_TCPV6 |
 104                       0)))
 105                goto out;
 106
 107        if (unlikely(!pskb_may_pull(skb, sizeof(*ipv6h))))
 108                goto out;
 109
 110        tunnel = skb->encapsulation;
 111        ipv6h = ipv6_hdr(skb);
 112        __skb_pull(skb, sizeof(*ipv6h));
 113        segs = ERR_PTR(-EPROTONOSUPPORT);
 114
 115        proto = ipv6_gso_pull_exthdrs(skb, ipv6h->nexthdr);
 116        rcu_read_lock();
 117        ops = rcu_dereference(inet6_offloads[proto]);
 118        if (likely(ops && ops->callbacks.gso_segment)) {
 119                skb_reset_transport_header(skb);
 120                segs = ops->callbacks.gso_segment(skb, features);
 121        }
 122        rcu_read_unlock();
 123
 124        if (IS_ERR(segs))
 125                goto out;
 126
 127        for (skb = segs; skb; skb = skb->next) {
 128                ipv6h = ipv6_hdr(skb);
 129                ipv6h->payload_len = htons(skb->len - skb->mac_len -
 130                                           sizeof(*ipv6h));
 131                if (!tunnel && proto == IPPROTO_UDP) {
 132                        unfrag_ip6hlen = ip6_find_1stfragopt(skb, &prevhdr);
 133                        fptr = (struct frag_hdr *)(skb_network_header(skb) +
 134                                unfrag_ip6hlen);
 135                        fptr->frag_off = htons(offset);
 136                        if (skb->next != NULL)
 137                                fptr->frag_off |= htons(IP6_MF);
 138                        offset += (ntohs(ipv6h->payload_len) -
 139                                   sizeof(struct frag_hdr));
 140                }
 141        }
 142
 143out:
 144        return segs;
 145}
 146
 147static struct sk_buff **ipv6_gro_receive(struct sk_buff **head,
 148                                         struct sk_buff *skb)
 149{
 150        const struct net_offload *ops;
 151        struct sk_buff **pp = NULL;
 152        struct sk_buff *p;
 153        struct ipv6hdr *iph;
 154        unsigned int nlen;
 155        unsigned int hlen;
 156        unsigned int off;
 157        int flush = 1;
 158        int proto;
 159        __wsum csum;
 160
 161        off = skb_gro_offset(skb);
 162        hlen = off + sizeof(*iph);
 163        iph = skb_gro_header_fast(skb, off);
 164        if (skb_gro_header_hard(skb, hlen)) {
 165                iph = skb_gro_header_slow(skb, hlen, off);
 166                if (unlikely(!iph))
 167                        goto out;
 168        }
 169
 170        skb_gro_pull(skb, sizeof(*iph));
 171        skb_set_transport_header(skb, skb_gro_offset(skb));
 172
 173        flush += ntohs(iph->payload_len) != skb_gro_len(skb);
 174
 175        rcu_read_lock();
 176        proto = iph->nexthdr;
 177        ops = rcu_dereference(inet6_offloads[proto]);
 178        if (!ops || !ops->callbacks.gro_receive) {
 179                __pskb_pull(skb, skb_gro_offset(skb));
 180                proto = ipv6_gso_pull_exthdrs(skb, proto);
 181                skb_gro_pull(skb, -skb_transport_offset(skb));
 182                skb_reset_transport_header(skb);
 183                __skb_push(skb, skb_gro_offset(skb));
 184
 185                ops = rcu_dereference(inet6_offloads[proto]);
 186                if (!ops || !ops->callbacks.gro_receive)
 187                        goto out_unlock;
 188
 189                iph = ipv6_hdr(skb);
 190        }
 191
 192        NAPI_GRO_CB(skb)->proto = proto;
 193
 194        flush--;
 195        nlen = skb_network_header_len(skb);
 196
 197        for (p = *head; p; p = p->next) {
 198                const struct ipv6hdr *iph2;
 199                __be32 first_word; /* <Version:4><Traffic_Class:8><Flow_Label:20> */
 200
 201                if (!NAPI_GRO_CB(p)->same_flow)
 202                        continue;
 203
 204                iph2 = ipv6_hdr(p);
 205                first_word = *(__be32 *)iph ^ *(__be32 *)iph2 ;
 206
 207                /* All fields must match except length and Traffic Class. */
 208                if (nlen != skb_network_header_len(p) ||
 209                    (first_word & htonl(0xF00FFFFF)) ||
 210                    memcmp(&iph->nexthdr, &iph2->nexthdr,
 211                           nlen - offsetof(struct ipv6hdr, nexthdr))) {
 212                        NAPI_GRO_CB(p)->same_flow = 0;
 213                        continue;
 214                }
 215                /* flush if Traffic Class fields are different */
 216                NAPI_GRO_CB(p)->flush |= !!(first_word & htonl(0x0FF00000));
 217                NAPI_GRO_CB(p)->flush |= flush;
 218        }
 219
 220        NAPI_GRO_CB(skb)->flush |= flush;
 221
 222        csum = skb->csum;
 223        skb_postpull_rcsum(skb, iph, skb_network_header_len(skb));
 224
 225        pp = ops->callbacks.gro_receive(head, skb);
 226
 227        skb->csum = csum;
 228
 229out_unlock:
 230        rcu_read_unlock();
 231
 232out:
 233        NAPI_GRO_CB(skb)->flush |= flush;
 234
 235        return pp;
 236}
 237
 238static int ipv6_gro_complete(struct sk_buff *skb)
 239{
 240        const struct net_offload *ops;
 241        struct ipv6hdr *iph = ipv6_hdr(skb);
 242        int err = -ENOSYS;
 243
 244        iph->payload_len = htons(skb->len - skb_network_offset(skb) -
 245                                 sizeof(*iph));
 246
 247        rcu_read_lock();
 248        ops = rcu_dereference(inet6_offloads[NAPI_GRO_CB(skb)->proto]);
 249        if (WARN_ON(!ops || !ops->callbacks.gro_complete))
 250                goto out_unlock;
 251
 252        err = ops->callbacks.gro_complete(skb);
 253
 254out_unlock:
 255        rcu_read_unlock();
 256
 257        return err;
 258}
 259
 260static struct packet_offload ipv6_packet_offload __read_mostly = {
 261        .type = cpu_to_be16(ETH_P_IPV6),
 262        .callbacks = {
 263                .gso_send_check = ipv6_gso_send_check,
 264                .gso_segment = ipv6_gso_segment,
 265                .gro_receive = ipv6_gro_receive,
 266                .gro_complete = ipv6_gro_complete,
 267        },
 268};
 269
 270static int __init ipv6_offload_init(void)
 271{
 272
 273        if (tcpv6_offload_init() < 0)
 274                pr_crit("%s: Cannot add TCP protocol offload\n", __func__);
 275        if (udp_offload_init() < 0)
 276                pr_crit("%s: Cannot add UDP protocol offload\n", __func__);
 277        if (ipv6_exthdrs_offload_init() < 0)
 278                pr_crit("%s: Cannot add EXTHDRS protocol offload\n", __func__);
 279
 280        dev_add_offload(&ipv6_packet_offload);
 281        return 0;
 282}
 283
 284fs_initcall(ipv6_offload_init);
 285