| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * IPV6 GSO/GRO offload support |
|---|
| 3 | 4 | * 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 | 5 | */ |
|---|
| 10 | 6 | |
|---|
| 11 | 7 | #include <linux/kernel.h> |
|---|
| .. | .. |
|---|
| 17 | 13 | #include <net/protocol.h> |
|---|
| 18 | 14 | #include <net/ipv6.h> |
|---|
| 19 | 15 | #include <net/inet_common.h> |
|---|
| 16 | +#include <net/tcp.h> |
|---|
| 17 | +#include <net/udp.h> |
|---|
| 20 | 18 | |
|---|
| 21 | 19 | #include "ip6_offload.h" |
|---|
| 20 | + |
|---|
| 21 | +/* All GRO functions are always builtin, except UDP over ipv6, which lays in |
|---|
| 22 | + * ipv6 module, as it depends on UDPv6 lookup function, so we need special care |
|---|
| 23 | + * when ipv6 is built as a module |
|---|
| 24 | + */ |
|---|
| 25 | +#if IS_BUILTIN(CONFIG_IPV6) |
|---|
| 26 | +#define INDIRECT_CALL_L4(f, f2, f1, ...) INDIRECT_CALL_2(f, f2, f1, __VA_ARGS__) |
|---|
| 27 | +#else |
|---|
| 28 | +#define INDIRECT_CALL_L4(f, f2, f1, ...) INDIRECT_CALL_1(f, f2, __VA_ARGS__) |
|---|
| 29 | +#endif |
|---|
| 30 | + |
|---|
| 31 | +#define indirect_call_gro_receive_l4(f2, f1, cb, head, skb) \ |
|---|
| 32 | +({ \ |
|---|
| 33 | + unlikely(gro_recursion_inc_test(skb)) ? \ |
|---|
| 34 | + NAPI_GRO_CB(skb)->flush |= 1, NULL : \ |
|---|
| 35 | + INDIRECT_CALL_L4(cb, f2, f1, head, skb); \ |
|---|
| 36 | +}) |
|---|
| 22 | 37 | |
|---|
| 23 | 38 | static int ipv6_gso_pull_exthdrs(struct sk_buff *skb, int proto) |
|---|
| 24 | 39 | { |
|---|
| .. | .. |
|---|
| 166 | 181 | return len; |
|---|
| 167 | 182 | } |
|---|
| 168 | 183 | |
|---|
| 169 | | -static struct sk_buff *ipv6_gro_receive(struct list_head *head, |
|---|
| 170 | | - struct sk_buff *skb) |
|---|
| 184 | +INDIRECT_CALLABLE_SCOPE struct sk_buff *ipv6_gro_receive(struct list_head *head, |
|---|
| 185 | + struct sk_buff *skb) |
|---|
| 171 | 186 | { |
|---|
| 172 | 187 | const struct net_offload *ops; |
|---|
| 173 | 188 | struct sk_buff *pp = NULL; |
|---|
| .. | .. |
|---|
| 231 | 246 | * XXX skbs on the gro_list have all been parsed and pulled |
|---|
| 232 | 247 | * already so we don't need to compare nlen |
|---|
| 233 | 248 | * (nlen != (sizeof(*iph2) + ipv6_exthdrs_len(iph2, &ops))) |
|---|
| 234 | | - * memcmp() alone below is suffcient, right? |
|---|
| 249 | + * memcmp() alone below is sufficient, right? |
|---|
| 235 | 250 | */ |
|---|
| 236 | 251 | if ((first_word & htonl(0xF00FFFFF)) || |
|---|
| 237 | | - memcmp(&iph->nexthdr, &iph2->nexthdr, |
|---|
| 238 | | - nlen - offsetof(struct ipv6hdr, nexthdr))) { |
|---|
| 252 | + !ipv6_addr_equal(&iph->saddr, &iph2->saddr) || |
|---|
| 253 | + !ipv6_addr_equal(&iph->daddr, &iph2->daddr) || |
|---|
| 254 | + *(u16 *)&iph->nexthdr != *(u16 *)&iph2->nexthdr) { |
|---|
| 255 | +not_same_flow: |
|---|
| 239 | 256 | NAPI_GRO_CB(p)->same_flow = 0; |
|---|
| 240 | 257 | continue; |
|---|
| 258 | + } |
|---|
| 259 | + if (unlikely(nlen > sizeof(struct ipv6hdr))) { |
|---|
| 260 | + if (memcmp(iph + 1, iph2 + 1, |
|---|
| 261 | + nlen - sizeof(struct ipv6hdr))) |
|---|
| 262 | + goto not_same_flow; |
|---|
| 241 | 263 | } |
|---|
| 242 | 264 | /* flush if Traffic Class fields are different */ |
|---|
| 243 | 265 | NAPI_GRO_CB(p)->flush |= !!(first_word & htonl(0x0FF00000)); |
|---|
| .. | .. |
|---|
| 255 | 277 | |
|---|
| 256 | 278 | skb_gro_postpull_rcsum(skb, iph, nlen); |
|---|
| 257 | 279 | |
|---|
| 258 | | - pp = call_gro_receive(ops->callbacks.gro_receive, head, skb); |
|---|
| 280 | + pp = indirect_call_gro_receive_l4(tcp6_gro_receive, udp6_gro_receive, |
|---|
| 281 | + ops->callbacks.gro_receive, head, skb); |
|---|
| 259 | 282 | |
|---|
| 260 | 283 | out_unlock: |
|---|
| 261 | 284 | rcu_read_unlock(); |
|---|
| .. | .. |
|---|
| 296 | 319 | return inet_gro_receive(head, skb); |
|---|
| 297 | 320 | } |
|---|
| 298 | 321 | |
|---|
| 299 | | -static int ipv6_gro_complete(struct sk_buff *skb, int nhoff) |
|---|
| 322 | +INDIRECT_CALLABLE_SCOPE int ipv6_gro_complete(struct sk_buff *skb, int nhoff) |
|---|
| 300 | 323 | { |
|---|
| 301 | 324 | const struct net_offload *ops; |
|---|
| 302 | 325 | struct ipv6hdr *iph = (struct ipv6hdr *)(skb->data + nhoff); |
|---|
| .. | .. |
|---|
| 315 | 338 | if (WARN_ON(!ops || !ops->callbacks.gro_complete)) |
|---|
| 316 | 339 | goto out_unlock; |
|---|
| 317 | 340 | |
|---|
| 318 | | - err = ops->callbacks.gro_complete(skb, nhoff); |
|---|
| 341 | + err = INDIRECT_CALL_L4(ops->callbacks.gro_complete, tcp6_gro_complete, |
|---|
| 342 | + udp6_gro_complete, skb, nhoff); |
|---|
| 319 | 343 | |
|---|
| 320 | 344 | out_unlock: |
|---|
| 321 | 345 | rcu_read_unlock(); |
|---|
| .. | .. |
|---|
| 353 | 377 | }, |
|---|
| 354 | 378 | }; |
|---|
| 355 | 379 | |
|---|
| 380 | +static struct sk_buff *sit_gso_segment(struct sk_buff *skb, |
|---|
| 381 | + netdev_features_t features) |
|---|
| 382 | +{ |
|---|
| 383 | + if (!(skb_shinfo(skb)->gso_type & SKB_GSO_IPXIP4)) |
|---|
| 384 | + return ERR_PTR(-EINVAL); |
|---|
| 385 | + |
|---|
| 386 | + return ipv6_gso_segment(skb, features); |
|---|
| 387 | +} |
|---|
| 388 | + |
|---|
| 389 | +static struct sk_buff *ip4ip6_gso_segment(struct sk_buff *skb, |
|---|
| 390 | + netdev_features_t features) |
|---|
| 391 | +{ |
|---|
| 392 | + if (!(skb_shinfo(skb)->gso_type & SKB_GSO_IPXIP6)) |
|---|
| 393 | + return ERR_PTR(-EINVAL); |
|---|
| 394 | + |
|---|
| 395 | + return inet_gso_segment(skb, features); |
|---|
| 396 | +} |
|---|
| 397 | + |
|---|
| 398 | +static struct sk_buff *ip6ip6_gso_segment(struct sk_buff *skb, |
|---|
| 399 | + netdev_features_t features) |
|---|
| 400 | +{ |
|---|
| 401 | + if (!(skb_shinfo(skb)->gso_type & SKB_GSO_IPXIP6)) |
|---|
| 402 | + return ERR_PTR(-EINVAL); |
|---|
| 403 | + |
|---|
| 404 | + return ipv6_gso_segment(skb, features); |
|---|
| 405 | +} |
|---|
| 406 | + |
|---|
| 356 | 407 | static const struct net_offload sit_offload = { |
|---|
| 357 | 408 | .callbacks = { |
|---|
| 358 | | - .gso_segment = ipv6_gso_segment, |
|---|
| 409 | + .gso_segment = sit_gso_segment, |
|---|
| 359 | 410 | .gro_receive = sit_ip6ip6_gro_receive, |
|---|
| 360 | 411 | .gro_complete = sit_gro_complete, |
|---|
| 361 | 412 | }, |
|---|
| .. | .. |
|---|
| 363 | 414 | |
|---|
| 364 | 415 | static const struct net_offload ip4ip6_offload = { |
|---|
| 365 | 416 | .callbacks = { |
|---|
| 366 | | - .gso_segment = inet_gso_segment, |
|---|
| 417 | + .gso_segment = ip4ip6_gso_segment, |
|---|
| 367 | 418 | .gro_receive = ip4ip6_gro_receive, |
|---|
| 368 | 419 | .gro_complete = ip4ip6_gro_complete, |
|---|
| 369 | 420 | }, |
|---|
| .. | .. |
|---|
| 371 | 422 | |
|---|
| 372 | 423 | static const struct net_offload ip6ip6_offload = { |
|---|
| 373 | 424 | .callbacks = { |
|---|
| 374 | | - .gso_segment = ipv6_gso_segment, |
|---|
| 425 | + .gso_segment = ip6ip6_gso_segment, |
|---|
| 375 | 426 | .gro_receive = sit_ip6ip6_gro_receive, |
|---|
| 376 | 427 | .gro_complete = ip6ip6_gro_complete, |
|---|
| 377 | 428 | }, |
|---|