| 1 | |
| 2 | |
| 3 | * IPV6 GSO/GRO offload support
|
| 4 | * Linux INET6 implementation
|
| 5 |
|
| 6 |
|
| 7 | #include <linux/kernel.h>
|
| 8 | #include <linux/socket.h>
|
| 9 | #include <linux/netdevice.h>
|
| 10 | #include <linux/skbuff.h>
|
| 11 | #include <linux/printk.h>
|
| 12 |
|
| 13 | #include <net/protocol.h>
|
| 14 | #include <net/ipv6.h>
|
| 15 | #include <net/inet_common.h>
|
| 16 | #include <net/tcp.h>
|
| 17 | #include <net/udp.h>
|
| 18 | #include <net/gro.h>
|
| 19 | #include <net/gso.h>
|
| 20 | #include <net/ip_tunnels.h>
|
| 21 |
|
| 22 | #include "ip6_offload.h"
|
| 23 | #include "tcpv6_offload.c"
|
| 24 |
|
| 25 | static int ipv6_gro_pull_exthdrs(struct sk_buff *skb, int off, int proto)
|
| 26 | {
|
| 27 | const struct net_offload *ops = NULL;
|
| 28 | struct ipv6_opt_hdr *opth;
|
| 29 |
|
| 30 | for (;;) {
|
| 31 | int len;
|
| 32 |
|
| 33 | ops = rcu_dereference(inet6_offloads[proto]);
|
| 34 |
|
| 35 | if (unlikely(!ops))
|
| 36 | break;
|
| 37 |
|
| 38 | if (!(ops->flags & INET6_PROTO_GSO_EXTHDR))
|
| 39 | break;
|
| 40 |
|
| 41 | opth = skb_gro_header(skb, off + sizeof(*opth), off);
|
| 42 | if (unlikely(!opth))
|
| 43 | break;
|
| 44 |
|
| 45 | len = ipv6_optlen(opth);
|
| 46 |
|
| 47 | opth = skb_gro_header(skb, off + len, off);
|
| 48 | if (unlikely(!opth))
|
| 49 | break;
|
| 50 | proto = opth->nexthdr;
|
| 51 |
|
| 52 | off += len;
|
| 53 | }
|
| 54 |
|
| 55 | skb_gro_pull(skb, off - skb_gro_receive_network_offset(skb));
|
| 56 | return proto;
|
| 57 | }
|
| 58 |
|
| 59 | static int ipv6_gso_pull_exthdrs(struct sk_buff *skb, int proto)
|
| 60 | {
|
| 61 | const struct net_offload *ops = NULL;
|
| 62 |
|
| 63 | for (;;) {
|
| 64 | struct ipv6_opt_hdr *opth;
|
| 65 | int len;
|
| 66 |
|
| 67 | ops = rcu_dereference(inet6_offloads[proto]);
|
| 68 |
|
| 69 | if (unlikely(!ops))
|
| 70 | break;
|
| 71 |
|
| 72 | if (!(ops->flags & INET6_PROTO_GSO_EXTHDR))
|
| 73 | break;
|
| 74 |
|
| 75 | if (unlikely(!pskb_may_pull(skb, 8)))
|
| 76 | break;
|
| 77 |
|
| 78 | opth = (void *)skb->data;
|
| 79 | len = ipv6_optlen(opth);
|
| 80 |
|
| 81 | if (unlikely(!pskb_may_pull(skb, len)))
|
| 82 | break;
|
| 83 |
|
| 84 | opth = (void *)skb->data;
|
| 85 | proto = opth->nexthdr;
|
| 86 | __skb_pull(skb, len);
|
| 87 | }
|
| 88 |
|
| 89 | return proto;
|
| 90 | }
|
| 91 |
|
| 92 | static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb,
|
| 93 | netdev_features_t features)
|
| 94 | {
|
| 95 | struct sk_buff *segs = ERR_PTR(-EINVAL);
|
| 96 | struct ipv6hdr *ipv6h;
|
| 97 | const struct net_offload *ops;
|
| 98 | int proto;
|
| 99 | struct frag_hdr *fptr;
|
| 100 | unsigned int payload_len;
|
| 101 | u8 *prevhdr;
|
| 102 | int offset = 0;
|
| 103 | bool encap, udpfrag;
|
| 104 | int nhoff;
|
| 105 | bool gso_partial;
|
| 106 |
|
| 107 | skb_reset_network_header(skb);
|
| 108 | if (unlikely(gso_recursion_inc_test(skb,
|
| 109 | IP_TUNNEL_RECURSION_LIMIT)))
|
| 110 | goto out;
|
| 111 | nhoff = skb_network_header(skb) - skb_mac_header(skb);
|
| 112 | if (unlikely(!pskb_may_pull(skb, sizeof(*ipv6h))))
|
| 113 | goto out;
|
| 114 |
|
| 115 | encap = SKB_GSO_CB(skb)->encap_level > 0;
|
| 116 | if (encap)
|
| 117 | features &= skb->dev->hw_enc_features;
|
| 118 | SKB_GSO_CB(skb)->encap_level += sizeof(*ipv6h);
|
| 119 |
|
| 120 | ipv6h = ipv6_hdr(skb);
|
| 121 | __skb_pull(skb, sizeof(*ipv6h));
|
| 122 | segs = ERR_PTR(-EPROTONOSUPPORT);
|
| 123 |
|
| 124 | proto = ipv6_gso_pull_exthdrs(skb, ipv6h->nexthdr);
|
| 125 |
|
| 126 | if (skb->encapsulation &&
|
| 127 | skb_shinfo(skb)->gso_type & (SKB_GSO_IPXIP4 | SKB_GSO_IPXIP6))
|
| 128 | udpfrag = proto == IPPROTO_UDP && encap &&
|
| 129 | (skb_shinfo(skb)->gso_type & SKB_GSO_UDP);
|
| 130 | else
|
| 131 | udpfrag = proto == IPPROTO_UDP && !skb->encapsulation &&
|
| 132 | (skb_shinfo(skb)->gso_type & SKB_GSO_UDP);
|
| 133 |
|
| 134 | ops = rcu_dereference(inet6_offloads[proto]);
|
| 135 | if (likely(ops && ops->callbacks.gso_segment)) {
|
| 136 | if (!skb_reset_transport_header_careful(skb))
|
| 137 | goto out;
|
| 138 |
|
| 139 | segs = ops->callbacks.gso_segment(skb, features);
|
| 140 | if (!segs)
|
| 141 | skb->network_header = skb_mac_header(skb) + nhoff - skb->head;
|
| 142 | }
|
| 143 |
|
| 144 | if (IS_ERR_OR_NULL(segs))
|
| 145 | goto out;
|
| 146 |
|
| 147 | gso_partial = !!(skb_shinfo(segs)->gso_type & SKB_GSO_PARTIAL);
|
| 148 |
|
| 149 | for (skb = segs; skb; skb = skb->next) {
|
| 150 | ipv6h = (struct ipv6hdr *)(skb_mac_header(skb) + nhoff);
|
| 151 | if (gso_partial && skb_is_gso(skb))
|
| 152 | payload_len = skb_shinfo(skb)->gso_size +
|
| 153 | SKB_GSO_CB(skb)->data_offset +
|
| 154 | skb->head - (unsigned char *)(ipv6h + 1);
|
| 155 | else
|
| 156 | payload_len = skb->len - nhoff - sizeof(*ipv6h);
|
| 157 | ipv6h->payload_len = htons(payload_len);
|
| 158 | skb->network_header = (u8 *)ipv6h - skb->head;
|
| 159 | skb_reset_mac_len(skb);
|
| 160 |
|
| 161 | if (udpfrag) {
|
| 162 | int err = ip6_find_1stfragopt(skb, &prevhdr);
|
| 163 | if (err < 0) {
|
| 164 | kfree_skb_list(segs);
|
| 165 | return ERR_PTR(err);
|
| 166 | }
|
| 167 | fptr = (struct frag_hdr *)((u8 *)ipv6h + err);
|
| 168 | fptr->frag_off = htons(offset);
|
| 169 | if (skb->next)
|
| 170 | fptr->frag_off |= htons(IP6_MF);
|
| 171 | offset += (ntohs(ipv6h->payload_len) -
|
| 172 | sizeof(struct frag_hdr));
|
| 173 | }
|
| 174 | if (encap)
|
| 175 | skb_reset_inner_headers(skb);
|
| 176 | }
|
| 177 |
|
| 178 | out:
|
| 179 | return segs;
|
| 180 | }
|
| 181 |
|
| 182 | |
| 183 | * logic in ipv6_gso_pull_exthdrs() when parsing ext-hdrs.
|
| 184 |
|
| 185 | static int ipv6_exthdrs_len(struct ipv6hdr *iph,
|
| 186 | const struct net_offload **opps)
|
| 187 | {
|
| 188 | struct ipv6_opt_hdr *opth = (void *)iph;
|
| 189 | int len = 0, proto, optlen = sizeof(*iph);
|
| 190 |
|
| 191 | proto = iph->nexthdr;
|
| 192 | for (;;) {
|
| 193 | *opps = rcu_dereference(inet6_offloads[proto]);
|
| 194 | if (unlikely(!(*opps)))
|
| 195 | break;
|
| 196 | if (!((*opps)->flags & INET6_PROTO_GSO_EXTHDR))
|
| 197 | break;
|
| 198 |
|
| 199 | opth = (void *)opth + optlen;
|
| 200 | optlen = ipv6_optlen(opth);
|
| 201 | len += optlen;
|
| 202 | proto = opth->nexthdr;
|
| 203 | }
|
| 204 | return len;
|
| 205 | }
|
| 206 |
|
| 207 | INDIRECT_CALLABLE_SCOPE struct sk_buff *ipv6_gro_receive(struct list_head *head,
|
| 208 | struct sk_buff *skb)
|
| 209 | {
|
| 210 | const struct net_offload *ops;
|
| 211 | struct sk_buff *pp = NULL;
|
| 212 | struct sk_buff *p;
|
| 213 | struct ipv6hdr *iph;
|
| 214 | unsigned int nlen;
|
| 215 | unsigned int hlen;
|
| 216 | unsigned int off;
|
| 217 | u16 flush = 1;
|
| 218 | int proto;
|
| 219 |
|
| 220 | off = skb_gro_offset(skb);
|
| 221 | hlen = off + sizeof(*iph);
|
| 222 | iph = skb_gro_header(skb, hlen, off);
|
| 223 | if (unlikely(!iph))
|
| 224 | goto out;
|
| 225 |
|
| 226 | NAPI_GRO_CB(skb)->network_offsets[NAPI_GRO_CB(skb)->encap_mark] = off;
|
| 227 |
|
| 228 | flush += ntohs(iph->payload_len) != skb->len - hlen;
|
| 229 |
|
| 230 | proto = iph->nexthdr;
|
| 231 | ops = rcu_dereference(inet6_offloads[proto]);
|
| 232 | if (!ops || !ops->callbacks.gro_receive) {
|
| 233 | proto = ipv6_gro_pull_exthdrs(skb, hlen, proto);
|
| 234 |
|
| 235 | ops = rcu_dereference(inet6_offloads[proto]);
|
| 236 | if (!ops || !ops->callbacks.gro_receive)
|
| 237 | goto out;
|
| 238 |
|
| 239 | iph = skb_gro_network_header(skb);
|
| 240 | } else {
|
| 241 | skb_gro_pull(skb, sizeof(*iph));
|
| 242 | }
|
| 243 |
|
| 244 | skb_set_transport_header(skb, skb_gro_offset(skb));
|
| 245 |
|
| 246 | NAPI_GRO_CB(skb)->proto = proto;
|
| 247 |
|
| 248 | flush--;
|
| 249 | nlen = skb_gro_offset(skb) - off;
|
| 250 |
|
| 251 | list_for_each_entry(p, head, list) {
|
| 252 | const struct ipv6hdr *iph2;
|
| 253 | __be32 first_word;
|
| 254 |
|
| 255 | if (!NAPI_GRO_CB(p)->same_flow)
|
| 256 | continue;
|
| 257 |
|
| 258 | iph2 = (struct ipv6hdr *)(p->data + off);
|
| 259 | first_word = *(__be32 *)iph ^ *(__be32 *)iph2;
|
| 260 |
|
| 261 | |
| 262 | * XXX skbs on the gro_list have all been parsed and pulled
|
| 263 | * already so we don't need to compare nlen
|
| 264 | * (nlen != (sizeof(*iph2) + ipv6_exthdrs_len(iph2, &ops)))
|
| 265 | * memcmp() alone below is sufficient, right?
|
| 266 |
|
| 267 | if ((first_word & htonl(0xF00FFFFF)) ||
|
| 268 | !ipv6_addr_equal(&iph->saddr, &iph2->saddr) ||
|
| 269 | !ipv6_addr_equal(&iph->daddr, &iph2->daddr) ||
|
| 270 | iph->nexthdr != iph2->nexthdr) {
|
| 271 | not_same_flow:
|
| 272 | NAPI_GRO_CB(p)->same_flow = 0;
|
| 273 | continue;
|
| 274 | }
|
| 275 | if (unlikely(nlen > sizeof(struct ipv6hdr))) {
|
| 276 | if (memcmp(iph + 1, iph2 + 1,
|
| 277 | nlen - sizeof(struct ipv6hdr)))
|
| 278 | goto not_same_flow;
|
| 279 | }
|
| 280 | }
|
| 281 |
|
| 282 | NAPI_GRO_CB(skb)->flush |= flush;
|
| 283 |
|
| 284 | skb_gro_postpull_rcsum(skb, iph, nlen);
|
| 285 |
|
| 286 | if (unlikely(gro_recursion_inc_test(skb))) {
|
| 287 | flush = 1;
|
| 288 | goto out;
|
| 289 | }
|
| 290 |
|
| 291 | if (likely(proto == IPPROTO_TCP))
|
| 292 | pp = tcp6_gro_receive(head, skb);
|
| 293 | #if IS_ENABLED(CONFIG_IPV6)
|
| 294 | else if (likely(proto == IPPROTO_UDP))
|
| 295 | pp = udp6_gro_receive(head, skb);
|
| 296 | #endif
|
| 297 | else
|
| 298 | pp = ops->callbacks.gro_receive(head, skb);
|
| 299 | out:
|
| 300 | skb_gro_flush_final(skb, pp, flush);
|
| 301 |
|
| 302 | return pp;
|
| 303 | }
|
| 304 | EXPORT_INDIRECT_CALLABLE(ipv6_gro_receive);
|
| 305 |
|
| 306 | static struct sk_buff *sit_ip6ip6_gro_receive(struct list_head *head,
|
| 307 | struct sk_buff *skb)
|
| 308 | {
|
| 309 |
|
| 310 |
|
| 311 | if (NAPI_GRO_CB(skb)->encap_mark) {
|
| 312 | NAPI_GRO_CB(skb)->flush = 1;
|
| 313 | return NULL;
|
| 314 | }
|
| 315 |
|
| 316 | NAPI_GRO_CB(skb)->encap_mark = 1;
|
| 317 |
|
| 318 | return ipv6_gro_receive(head, skb);
|
| 319 | }
|
| 320 |
|
| 321 | static struct sk_buff *ip4ip6_gro_receive(struct list_head *head,
|
| 322 | struct sk_buff *skb)
|
| 323 | {
|
| 324 |
|
| 325 |
|
| 326 | if (NAPI_GRO_CB(skb)->encap_mark) {
|
| 327 | NAPI_GRO_CB(skb)->flush = 1;
|
| 328 | return NULL;
|
| 329 | }
|
| 330 |
|
| 331 | NAPI_GRO_CB(skb)->encap_mark = 1;
|
| 332 |
|
| 333 | return inet_gro_receive(head, skb);
|
| 334 | }
|
| 335 |
|
| 336 | INDIRECT_CALLABLE_SCOPE int ipv6_gro_complete(struct sk_buff *skb, int nhoff)
|
| 337 | {
|
| 338 | const struct net_offload *ops;
|
| 339 | struct ipv6hdr *iph;
|
| 340 | int err = -ENOSYS;
|
| 341 |
|
| 342 | if (skb->encapsulation) {
|
| 343 | skb_set_inner_protocol(skb, cpu_to_be16(ETH_P_IPV6));
|
| 344 | skb_set_inner_network_header(skb, nhoff);
|
| 345 | }
|
| 346 |
|
| 347 | iph = (struct ipv6hdr *)(skb->data + nhoff);
|
| 348 | ipv6_set_payload_len(iph, skb->len - nhoff - sizeof(*iph));
|
| 349 |
|
| 350 | nhoff += sizeof(*iph) + ipv6_exthdrs_len(iph, &ops);
|
| 351 |
|
| 352 | if (likely(ops == &net_hotdata.tcpv6_offload))
|
| 353 | return tcp6_gro_complete(skb, nhoff);
|
| 354 | #if IS_ENABLED(CONFIG_IPV6)
|
| 355 | if (ops == &net_hotdata.udpv6_offload)
|
| 356 | return udp6_gro_complete(skb, nhoff);
|
| 357 | #endif
|
| 358 |
|
| 359 | if (WARN_ON(!ops || !ops->callbacks.gro_complete))
|
| 360 | goto out;
|
| 361 |
|
| 362 | err = ops->callbacks.gro_complete(skb, nhoff);
|
| 363 |
|
| 364 | out:
|
| 365 | return err;
|
| 366 | }
|
| 367 | EXPORT_INDIRECT_CALLABLE(ipv6_gro_complete);
|
| 368 |
|
| 369 | static int sit_gro_complete(struct sk_buff *skb, int nhoff)
|
| 370 | {
|
| 371 | skb->encapsulation = 1;
|
| 372 | skb_shinfo(skb)->gso_type |= SKB_GSO_IPXIP4;
|
| 373 | return ipv6_gro_complete(skb, nhoff);
|
| 374 | }
|
| 375 |
|
| 376 | static int ip6ip6_gro_complete(struct sk_buff *skb, int nhoff)
|
| 377 | {
|
| 378 | skb->encapsulation = 1;
|
| 379 | skb_shinfo(skb)->gso_type |= SKB_GSO_IPXIP6;
|
| 380 | return ipv6_gro_complete(skb, nhoff);
|
| 381 | }
|
| 382 |
|
| 383 | static int ip4ip6_gro_complete(struct sk_buff *skb, int nhoff)
|
| 384 | {
|
| 385 | skb->encapsulation = 1;
|
| 386 | skb_shinfo(skb)->gso_type |= SKB_GSO_IPXIP6;
|
| 387 | return inet_gro_complete(skb, nhoff);
|
| 388 | }
|
| 389 |
|
| 390 |
|
| 391 | static struct sk_buff *sit_gso_segment(struct sk_buff *skb,
|
| 392 | netdev_features_t features)
|
| 393 | {
|
| 394 | if (!(skb_shinfo(skb)->gso_type & SKB_GSO_IPXIP4))
|
| 395 | return ERR_PTR(-EINVAL);
|
| 396 |
|
| 397 | return ipv6_gso_segment(skb, features);
|
| 398 | }
|
| 399 |
|
| 400 | static struct sk_buff *ip4ip6_gso_segment(struct sk_buff *skb,
|
| 401 | netdev_features_t features)
|
| 402 | {
|
| 403 | if (!(skb_shinfo(skb)->gso_type & SKB_GSO_IPXIP6))
|
| 404 | return ERR_PTR(-EINVAL);
|
| 405 |
|
| 406 | return inet_gso_segment(skb, features);
|
| 407 | }
|
| 408 |
|
| 409 | static struct sk_buff *ip6ip6_gso_segment(struct sk_buff *skb,
|
| 410 | netdev_features_t features)
|
| 411 | {
|
| 412 | if (!(skb_shinfo(skb)->gso_type & SKB_GSO_IPXIP6))
|
| 413 | return ERR_PTR(-EINVAL);
|
| 414 |
|
| 415 | return ipv6_gso_segment(skb, features);
|
| 416 | }
|
| 417 |
|
| 418 | static const struct net_offload sit_offload = {
|
| 419 | .callbacks = {
|
| 420 | .gso_segment = sit_gso_segment,
|
| 421 | .gro_receive = sit_ip6ip6_gro_receive,
|
| 422 | .gro_complete = sit_gro_complete,
|
| 423 | },
|
| 424 | };
|
| 425 |
|
| 426 | static const struct net_offload ip4ip6_offload = {
|
| 427 | .callbacks = {
|
| 428 | .gso_segment = ip4ip6_gso_segment,
|
| 429 | .gro_receive = ip4ip6_gro_receive,
|
| 430 | .gro_complete = ip4ip6_gro_complete,
|
| 431 | },
|
| 432 | };
|
| 433 |
|
| 434 | static const struct net_offload ip6ip6_offload = {
|
| 435 | .callbacks = {
|
| 436 | .gso_segment = ip6ip6_gso_segment,
|
| 437 | .gro_receive = sit_ip6ip6_gro_receive,
|
| 438 | .gro_complete = ip6ip6_gro_complete,
|
| 439 | },
|
| 440 | };
|
| 441 | static int __init ipv6_offload_init(void)
|
| 442 | {
|
| 443 |
|
| 444 | if (tcpv6_offload_init() < 0)
|
| 445 | pr_crit("%s: Cannot add TCP protocol offload\n", __func__);
|
| 446 | if (ipv6_exthdrs_offload_init() < 0)
|
| 447 | pr_crit("%s: Cannot add EXTHDRS protocol offload\n", __func__);
|
| 448 |
|
| 449 | net_hotdata.ipv6_packet_offload = (struct packet_offload) {
|
| 450 | .type = cpu_to_be16(ETH_P_IPV6),
|
| 451 | .callbacks = {
|
| 452 | .gso_segment = ipv6_gso_segment,
|
| 453 | .gro_receive = ipv6_gro_receive,
|
| 454 | .gro_complete = ipv6_gro_complete,
|
| 455 | },
|
| 456 | };
|
| 457 | dev_add_offload(&net_hotdata.ipv6_packet_offload);
|
| 458 |
|
| 459 | inet_add_offload(&sit_offload, IPPROTO_IPV6);
|
| 460 | inet6_add_offload(&ip6ip6_offload, IPPROTO_IPV6);
|
| 461 | inet6_add_offload(&ip4ip6_offload, IPPROTO_IPIP);
|
| 462 |
|
| 463 | return 0;
|
| 464 | }
|
| 465 |
|
| 466 | fs_initcall(ipv6_offload_init);
|