[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <CAKgT0UfP7ztrtV7smQviAXZyaiPAwCSARuKnbKnc3SP_R1ogsQ@mail.gmail.com>
Date: Wed, 25 Apr 2018 13:38:35 -0700
From: Alexander Duyck <alexander.duyck@...il.com>
To: Willem de Bruijn <willemdebruijn.kernel@...il.com>
Cc: Netdev <netdev@...r.kernel.org>,
David Miller <davem@...emloft.net>, dmichail@...gle.com,
Willem de Bruijn <willemb@...gle.com>
Subject: Re: [PATCH net-next 02/10] udp: add gso
On Wed, Apr 25, 2018 at 11:55 AM, Willem de Bruijn
<willemdebruijn.kernel@...il.com> wrote:
> From: Willem de Bruijn <willemb@...gle.com>
>
> Implement generic segmentation offload for udp datagrams. Callers can
> concatenate and send at once the payload of multiple datagrams with
> the same destination.
>
> To set segment size, the caller sets socket option UDP_SEGMENT to the
> length of each discrete payload. This value must be smaller than or
> equal to the relevant MTU.
>
> A follow-up patch adds cmsg UDP_SEGMENT to specify segment size on a
> per send call basis.
>
> Total byte length may then exceed MTU. If not an exact multiple of
> segment size, the last segment will be shorter.
>
> UDP GSO is not UFO. UFO fragments a single large datagram. GSO splits
> a large payload into a number of discrete UDP datagrams.
>
> The implementation adds a GSO type SKB_UDP_GSO_L4 to differentiate it
> from UFO (SKB_UDP_GSO). It adds a gso_size field to the udp socket,
> ip(v6) cmsg cookie and inet_cork structure to be able to set the value
> at setsockopt or cmsg time and to work with both lockless and corked
> paths.
>
> IPPROTO_UDPLITE is excluded, as that protocol has no gso handler
> registered.
>
> Initial benchmark numbers show UDP GSO about as expensive as TCP GSO.
>
> tcp tso
> 3197 MB/s 54232 msg/s 54232 calls/s
> 6,457,754,262 cycles
>
> tcp gso
> 1765 MB/s 29939 msg/s 29939 calls/s
> 11,203,021,806 cycles
>
> tcp without tso/gso *
> 739 MB/s 12548 msg/s 12548 calls/s
> 11,205,483,630 cycles
>
> udp
> 876 MB/s 14873 msg/s 624666 calls/s
> 11,205,777,429 cycles
>
> udp gso
> 2139 MB/s 36282 msg/s 36282 calls/s
> 11,204,374,561 cycles
>
> [*] after reverting commit 0a6b2a1dc2a2
> ("tcp: switch to GSO being always on")
>
> Measured total system cycles ('-a') for one core while pinning both
> the network receive path and benchmark process to that core:
>
> perf stat -a -C 12 -e cycles \
> ./udpgso_bench_tx -C 12 -4 -D "$DST" -l 4
>
> Note the reduction in calls/s with GSO. Bytes per syscall drops
> increases from 1470 to 61818.
>
> Signed-off-by: Willem de Bruijn <willemb@...gle.com>
It might be nice if you could break this into two patches. One for
actually doing the GSO in software, and another enabling the stack to
request it.
> ---
> include/linux/skbuff.h | 2 ++
> include/linux/udp.h | 3 +++
> include/net/inet_sock.h | 1 +
> include/net/ip.h | 1 +
> include/net/ipv6.h | 1 +
> include/net/udp.h | 4 ++++
> include/uapi/linux/udp.h | 1 +
> net/core/skbuff.c | 2 ++
> net/ipv4/ip_output.c | 9 ++++---
> net/ipv4/udp.c | 33 ++++++++++++++++++++++---
> net/ipv4/udp_offload.c | 52 +++++++++++++++++++++++++++++++++++++++-
> net/ipv6/ip6_offload.c | 6 +++--
> net/ipv6/ip6_output.c | 6 +++--
> net/ipv6/udp.c | 23 +++++++++++++++---
> net/ipv6/udp_offload.c | 19 ++++++++++++++-
> 15 files changed, 148 insertions(+), 15 deletions(-)
>
> diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
> index d274059529eb..a4a5c0c5cba8 100644
> --- a/include/linux/skbuff.h
> +++ b/include/linux/skbuff.h
> @@ -573,6 +573,8 @@ enum {
> SKB_GSO_ESP = 1 << 15,
>
> SKB_GSO_UDP = 1 << 16,
> +
> + SKB_GSO_UDP_L4 = 1 << 17,
> };
>
Part of me really wishes we could just rename SKB_GSO_UDP to something
like SKB_GFO_UDP since GSO implies "segmentation" but what we really
mean is "fragmentation" in the case of the existing UDP offload.
> #if BITS_PER_LONG > 32
> diff --git a/include/linux/udp.h b/include/linux/udp.h
> index eaea63bc79bb..ca840345571b 100644
> --- a/include/linux/udp.h
> +++ b/include/linux/udp.h
> @@ -55,6 +55,7 @@ struct udp_sock {
> * when the socket is uncorked.
> */
> __u16 len; /* total length of pending frames */
> + __u16 gso_size;
> /*
> * Fields specific to UDP-Lite.
> */
> @@ -87,6 +88,8 @@ struct udp_sock {
> int forward_deficit;
> };
>
> +#define UDP_MAX_SEGMENTS (1 << 6UL)
> +
> static inline struct udp_sock *udp_sk(const struct sock *sk)
> {
> return (struct udp_sock *)sk;
> diff --git a/include/net/inet_sock.h b/include/net/inet_sock.h
> index 0a671c32d6b9..83d5b3c2ac42 100644
> --- a/include/net/inet_sock.h
> +++ b/include/net/inet_sock.h
> @@ -147,6 +147,7 @@ struct inet_cork {
> __u8 ttl;
> __s16 tos;
> char priority;
> + __u16 gso_size;
> };
>
> struct inet_cork_full {
> diff --git a/include/net/ip.h b/include/net/ip.h
> index 7ec543a64bbc..bada1f1f871e 100644
> --- a/include/net/ip.h
> +++ b/include/net/ip.h
> @@ -76,6 +76,7 @@ struct ipcm_cookie {
> __u8 ttl;
> __s16 tos;
> char priority;
> + __u16 gso_size;
> };
>
> #define IPCB(skb) ((struct inet_skb_parm*)((skb)->cb))
> diff --git a/include/net/ipv6.h b/include/net/ipv6.h
> index 0dd722cab037..0a872a7c33c8 100644
> --- a/include/net/ipv6.h
> +++ b/include/net/ipv6.h
> @@ -298,6 +298,7 @@ struct ipcm6_cookie {
> __s16 tclass;
> __s8 dontfrag;
> struct ipv6_txoptions *opt;
> + __u16 gso_size;
> };
>
> static inline struct ipv6_txoptions *txopt_get(const struct ipv6_pinfo *np)
> diff --git a/include/net/udp.h b/include/net/udp.h
> index 0676b272f6ac..741d888d0fdb 100644
> --- a/include/net/udp.h
> +++ b/include/net/udp.h
> @@ -174,6 +174,10 @@ struct sk_buff **udp_gro_receive(struct sk_buff **head, struct sk_buff *skb,
> struct udphdr *uh, udp_lookup_t lookup);
> int udp_gro_complete(struct sk_buff *skb, int nhoff, udp_lookup_t lookup);
>
> +struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
> + netdev_features_t features,
> + unsigned int mss, __sum16 check);
> +
> static inline struct udphdr *udp_gro_udphdr(struct sk_buff *skb)
> {
> struct udphdr *uh;
> diff --git a/include/uapi/linux/udp.h b/include/uapi/linux/udp.h
> index efb7b5991c2f..09d00f8c442b 100644
> --- a/include/uapi/linux/udp.h
> +++ b/include/uapi/linux/udp.h
> @@ -32,6 +32,7 @@ struct udphdr {
> #define UDP_ENCAP 100 /* Set the socket to accept encapsulated packets */
> #define UDP_NO_CHECK6_TX 101 /* Disable sending checksum for UDP6X */
> #define UDP_NO_CHECK6_RX 102 /* Disable accpeting checksum for UDP6 */
> +#define UDP_SEGMENT 103 /* Set GSO segmentation size */
>
> /* UDP encapsulation types */
> #define UDP_ENCAP_ESPINUDP_NON_IKE 1 /* draft-ietf-ipsec-nat-t-ike-00/01 */
> diff --git a/net/core/skbuff.c b/net/core/skbuff.c
> index ff49e352deea..c647cfe114e0 100644
> --- a/net/core/skbuff.c
> +++ b/net/core/skbuff.c
> @@ -4940,6 +4940,8 @@ static unsigned int skb_gso_transport_seglen(const struct sk_buff *skb)
> thlen = tcp_hdrlen(skb);
> } else if (unlikely(skb_is_gso_sctp(skb))) {
> thlen = sizeof(struct sctphdr);
> + } else if (shinfo->gso_type & SKB_GSO_UDP_L4) {
> + thlen = sizeof(struct udphdr);
> }
> /* UFO sets gso_size to the size of the fragmentation
> * payload, i.e. the size of the L4 (UDP) header is already
It might make more sense to look at converting this over to a switch
statement based off of shinfo(skb)->gso_type & GSO_TRANSPORT_MASK,
where the transport mask consists of the 4 bits that are supported.
> diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c
> index 2883ff1e909c..da4abbee10f7 100644
> --- a/net/ipv4/ip_output.c
> +++ b/net/ipv4/ip_output.c
> @@ -882,7 +882,8 @@ static int __ip_append_data(struct sock *sk,
> skb = skb_peek_tail(queue);
>
> exthdrlen = !skb ? rt->dst.header_len : 0;
> - mtu = cork->fragsize;
> + mtu = cork->gso_size ? IP_MAX_MTU : cork->fragsize;
> +
> if (cork->tx_flags & SKBTX_ANY_SW_TSTAMP &&
> sk->sk_tsflags & SOF_TIMESTAMPING_OPT_ID)
> tskey = sk->sk_tskey++;
> @@ -906,7 +907,7 @@ static int __ip_append_data(struct sock *sk,
> if (transhdrlen &&
> length + fragheaderlen <= mtu &&
> rt->dst.dev->features & (NETIF_F_HW_CSUM | NETIF_F_IP_CSUM) &&
> - !(flags & MSG_MORE) &&
> + (!(flags & MSG_MORE) || cork->gso_size) &&
> !exthdrlen)
> csummode = CHECKSUM_PARTIAL;
>
> @@ -1135,6 +1136,8 @@ static int ip_setup_cork(struct sock *sk, struct inet_cork *cork,
> *rtp = NULL;
> cork->fragsize = ip_sk_use_pmtu(sk) ?
> dst_mtu(&rt->dst) : rt->dst.dev->mtu;
> +
> + cork->gso_size = sk->sk_type == SOCK_DGRAM ? ipc->gso_size : 0;
> cork->dst = &rt->dst;
> cork->length = 0;
> cork->ttl = ipc->ttl;
> @@ -1214,7 +1217,7 @@ ssize_t ip_append_page(struct sock *sk, struct flowi4 *fl4, struct page *page,
> return -EOPNOTSUPP;
>
> hh_len = LL_RESERVED_SPACE(rt->dst.dev);
> - mtu = cork->fragsize;
> + mtu = cork->gso_size ? IP_MAX_MTU : cork->fragsize;
>
> fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);
> maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;
> diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
> index 6b9d8017b319..bda022c5480b 100644
> --- a/net/ipv4/udp.c
> +++ b/net/ipv4/udp.c
> @@ -757,7 +757,8 @@ void udp_set_csum(bool nocheck, struct sk_buff *skb,
> }
> EXPORT_SYMBOL(udp_set_csum);
>
> -static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4)
> +static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4,
> + struct inet_cork *cork)
> {
> struct sock *sk = skb->sk;
> struct inet_sock *inet = inet_sk(sk);
> @@ -777,6 +778,21 @@ static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4)
> uh->len = htons(len);
> uh->check = 0;
>
> + if (cork->gso_size) {
> + const int hlen = skb_network_header_len(skb) +
> + sizeof(struct udphdr);
> +
> + if (hlen + cork->gso_size > cork->fragsize)
> + return -EINVAL;
> + if (skb->len > cork->gso_size * UDP_MAX_SEGMENTS)
> + return -EINVAL;
> + if (skb->ip_summed != CHECKSUM_PARTIAL || is_udplite)
> + return -EIO;
> +
> + skb_shinfo(skb)->gso_size = cork->gso_size;
> + skb_shinfo(skb)->gso_type = SKB_GSO_UDP_L4;
> + }
> +
> if (is_udplite) /* UDP-Lite */
> csum = udplite_csum(skb);
>
> @@ -828,7 +844,7 @@ int udp_push_pending_frames(struct sock *sk)
> if (!skb)
> goto out;
>
> - err = udp_send_skb(skb, fl4);
> + err = udp_send_skb(skb, fl4, &inet->cork.base);
>
> out:
> up->len = 0;
> @@ -922,6 +938,7 @@ int udp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
> ipc.sockc.tsflags = sk->sk_tsflags;
> ipc.addr = inet->inet_saddr;
> ipc.oif = sk->sk_bound_dev_if;
> + ipc.gso_size = up->gso_size;
>
> if (msg->msg_controllen) {
> err = ip_cmsg_send(sk, msg, &ipc, sk->sk_family == AF_INET6);
> @@ -1037,7 +1054,7 @@ int udp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
> &cork, msg->msg_flags);
> err = PTR_ERR(skb);
> if (!IS_ERR_OR_NULL(skb))
> - err = udp_send_skb(skb, fl4);
> + err = udp_send_skb(skb, fl4, &cork);
> goto out;
> }
>
> @@ -2367,6 +2384,12 @@ int udp_lib_setsockopt(struct sock *sk, int level, int optname,
> up->no_check6_rx = valbool;
> break;
>
> + case UDP_SEGMENT:
> + if (val < 0 || val > USHRT_MAX)
> + return -EINVAL;
> + up->gso_size = val;
> + break;
> +
> /*
> * UDP-Lite's partial checksum coverage (RFC 3828).
> */
> @@ -2457,6 +2480,10 @@ int udp_lib_getsockopt(struct sock *sk, int level, int optname,
> val = up->no_check6_rx;
> break;
>
> + case UDP_SEGMENT:
> + val = up->gso_size;
> + break;
> +
> /* The following two cannot be changed on UDP sockets, the return is
> * always 0 (which corresponds to the full checksum coverage of UDP). */
> case UDPLITE_SEND_CSCOV:
> diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c
> index ea6e6e7df0ee..a3244768e45f 100644
> --- a/net/ipv4/udp_offload.c
> +++ b/net/ipv4/udp_offload.c
> @@ -187,6 +187,53 @@ struct sk_buff *skb_udp_tunnel_segment(struct sk_buff *skb,
> }
> EXPORT_SYMBOL(skb_udp_tunnel_segment);
>
> +struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
> + netdev_features_t features,
> + unsigned int mss, __sum16 check)
> +{
> + struct udphdr *uh = udp_hdr(gso_skb);
> + struct sk_buff *segs;
> + unsigned int hdrlen;
> +
> + if (gso_skb->len <= sizeof(*uh) + mss)
> + return ERR_PTR(-EINVAL);
> +
> + uh->len = htons(sizeof(*uh) + mss);
> + uh->check = check;
> + skb_pull(gso_skb, sizeof(*uh));
> + hdrlen = gso_skb->data - skb_mac_header(gso_skb);
Normally I don't believe we modify the headers before calling
skb_segment. Normally that happens after via a while (skb->next) {}
type loop.
That way for things like GSO_PARTIAL we can update after segmentation
since there are only going to be 2 segments most likely instead of
multiple MSS sized segments.
> +
> + segs = skb_segment(gso_skb, features);
> + if (unlikely(IS_ERR_OR_NULL(segs)))
> + return segs;
> +
> + /* If last packet is not full, fix up its header */
> + if (segs->prev->len != hdrlen + mss) {
> + unsigned int mss_last = segs->prev->len - hdrlen;
> +
> + uh = udp_hdr(segs->prev);
> + uh->len = htons(sizeof(*uh) + mss_last);
> + csum_replace2(&uh->check, htons(mss), htons(mss_last));
> + }
> +
You could probably just assume that this last segment is always going
to need to be updated regardless. If you are doing any sort of
segmentation the last segment will always need the length and checksum
updated anyway, and since you probably need to move over to the "while
(skb->next)" style loop then you could just assume the last segment
needs updating.
> + return segs;
> +}
> +
> +static struct sk_buff *__udp4_gso_segment(struct sk_buff *gso_skb,
> + netdev_features_t features)
> +{
> + const struct iphdr *iph = ip_hdr(gso_skb);
> + unsigned int mss = skb_shinfo(gso_skb)->gso_size;
> +
> + if (!can_checksum_protocol(features, htons(ETH_P_IP)))
> + return ERR_PTR(-EIO);
> +
> + return __udp_gso_segment(gso_skb, features, mss,
> + udp_v4_check(sizeof(struct udphdr) + mss,
> + iph->saddr, iph->daddr, 0));
> +}
> +EXPORT_SYMBOL_GPL(__udp4_gso_segment);
> +
> static struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb,
> netdev_features_t features)
> {
> @@ -203,12 +250,15 @@ static struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb,
> goto out;
> }
>
> - if (!(skb_shinfo(skb)->gso_type & SKB_GSO_UDP))
> + if (!(skb_shinfo(skb)->gso_type & (SKB_GSO_UDP | SKB_GSO_UDP_L4)))
> goto out;
>
> if (!pskb_may_pull(skb, sizeof(struct udphdr)))
> goto out;
>
> + if (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4)
> + return __udp4_gso_segment(skb, features);
> +
> mss = skb_shinfo(skb)->gso_size;
> if (unlikely(skb->len <= mss))
> goto out;
> diff --git a/net/ipv6/ip6_offload.c b/net/ipv6/ip6_offload.c
> index 4a87f9428ca5..5b3f2f89ef41 100644
> --- a/net/ipv6/ip6_offload.c
> +++ b/net/ipv6/ip6_offload.c
> @@ -88,9 +88,11 @@ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb,
>
> if (skb->encapsulation &&
> skb_shinfo(skb)->gso_type & (SKB_GSO_IPXIP4 | SKB_GSO_IPXIP6))
> - udpfrag = proto == IPPROTO_UDP && encap;
> + udpfrag = proto == IPPROTO_UDP && encap &&
> + (skb_shinfo(skb)->gso_type & SKB_GSO_UDP);
> else
> - udpfrag = proto == IPPROTO_UDP && !skb->encapsulation;
> + udpfrag = proto == IPPROTO_UDP && !skb->encapsulation &&
> + (skb_shinfo(skb)->gso_type & SKB_GSO_UDP);
>
> ops = rcu_dereference(inet6_offloads[proto]);
> if (likely(ops && ops->callbacks.gso_segment)) {
> diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c
> index 7fa1db447405..a1c4a78132d2 100644
> --- a/net/ipv6/ip6_output.c
> +++ b/net/ipv6/ip6_output.c
> @@ -1240,6 +1240,8 @@ static int ip6_setup_cork(struct sock *sk, struct inet_cork_full *cork,
> if (mtu < IPV6_MIN_MTU)
> return -EINVAL;
> cork->base.fragsize = mtu;
> + cork->base.gso_size = sk->sk_type == SOCK_DGRAM ? ipc6->gso_size : 0;
> +
> if (dst_allfrag(xfrm_dst_path(&rt->dst)))
> cork->base.flags |= IPCORK_ALLFRAG;
> cork->base.length = 0;
> @@ -1281,7 +1283,7 @@ static int __ip6_append_data(struct sock *sk,
> dst_exthdrlen = rt->dst.header_len - rt->rt6i_nfheader_len;
> }
>
> - mtu = cork->fragsize;
> + mtu = cork->gso_size ? IP6_MAX_MTU : cork->fragsize;
> orig_mtu = mtu;
>
> hh_len = LL_RESERVED_SPACE(rt->dst.dev);
> @@ -1329,7 +1331,7 @@ static int __ip6_append_data(struct sock *sk,
> if (transhdrlen && sk->sk_protocol == IPPROTO_UDP &&
> headersize == sizeof(struct ipv6hdr) &&
> length <= mtu - headersize &&
> - !(flags & MSG_MORE) &&
> + (!(flags & MSG_MORE) || cork->gso_size) &&
> rt->dst.dev->features & (NETIF_F_IPV6_CSUM | NETIF_F_HW_CSUM))
> csummode = CHECKSUM_PARTIAL;
>
> diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
> index 824797f8d1ab..86b7dd58d4b4 100644
> --- a/net/ipv6/udp.c
> +++ b/net/ipv6/udp.c
> @@ -1023,7 +1023,8 @@ static void udp6_hwcsum_outgoing(struct sock *sk, struct sk_buff *skb,
> * Sending
> */
>
> -static int udp_v6_send_skb(struct sk_buff *skb, struct flowi6 *fl6)
> +static int udp_v6_send_skb(struct sk_buff *skb, struct flowi6 *fl6,
> + struct inet_cork *cork)
> {
> struct sock *sk = skb->sk;
> struct udphdr *uh;
> @@ -1042,6 +1043,21 @@ static int udp_v6_send_skb(struct sk_buff *skb, struct flowi6 *fl6)
> uh->len = htons(len);
> uh->check = 0;
>
> + if (cork->gso_size) {
> + const int hlen = skb_network_header_len(skb) +
> + sizeof(struct udphdr);
> +
> + if (hlen + cork->gso_size > cork->fragsize)
> + return -EINVAL;
> + if (skb->len > cork->gso_size * UDP_MAX_SEGMENTS)
> + return -EINVAL;
> + if (skb->ip_summed != CHECKSUM_PARTIAL || is_udplite)
> + return -EIO;
> +
> + skb_shinfo(skb)->gso_size = cork->gso_size;
> + skb_shinfo(skb)->gso_type = SKB_GSO_UDP_L4;
> + }
> +
> if (is_udplite)
> csum = udplite_csum(skb);
> else if (udp_sk(sk)->no_check6_tx) { /* UDP csum disabled */
> @@ -1093,7 +1109,7 @@ static int udp_v6_push_pending_frames(struct sock *sk)
> if (!skb)
> goto out;
>
> - err = udp_v6_send_skb(skb, &fl6);
> + err = udp_v6_send_skb(skb, &fl6, &inet_sk(sk)->cork.base);
>
> out:
> up->len = 0;
> @@ -1127,6 +1143,7 @@ int udpv6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
> ipc6.hlimit = -1;
> ipc6.tclass = -1;
> ipc6.dontfrag = -1;
> + ipc6.gso_size = up->gso_size;
> sockc.tsflags = sk->sk_tsflags;
>
> /* destination address check */
> @@ -1333,7 +1350,7 @@ int udpv6_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
> msg->msg_flags, &cork, &sockc);
> err = PTR_ERR(skb);
> if (!IS_ERR_OR_NULL(skb))
> - err = udp_v6_send_skb(skb, &fl6);
> + err = udp_v6_send_skb(skb, &fl6, &cork.base);
> goto out;
> }
>
> diff --git a/net/ipv6/udp_offload.c b/net/ipv6/udp_offload.c
> index 2a04dc9c781b..f7b85b1e6b3e 100644
> --- a/net/ipv6/udp_offload.c
> +++ b/net/ipv6/udp_offload.c
> @@ -17,6 +17,20 @@
> #include <net/ip6_checksum.h>
> #include "ip6_offload.h"
>
> +static struct sk_buff *__udp6_gso_segment(struct sk_buff *gso_skb,
> + netdev_features_t features)
> +{
> + const struct ipv6hdr *ip6h = ipv6_hdr(gso_skb);
> + unsigned int mss = skb_shinfo(gso_skb)->gso_size;
> +
> + if (!can_checksum_protocol(features, htons(ETH_P_IPV6)))
> + return ERR_PTR(-EIO);
> +
> + return __udp_gso_segment(gso_skb, features, mss,
> + udp_v6_check(sizeof(struct udphdr) + mss,
> + &ip6h->saddr, &ip6h->daddr, 0));
> +}
> +
> static struct sk_buff *udp6_ufo_fragment(struct sk_buff *skb,
> netdev_features_t features)
> {
> @@ -42,12 +56,15 @@ static struct sk_buff *udp6_ufo_fragment(struct sk_buff *skb,
> const struct ipv6hdr *ipv6h;
> struct udphdr *uh;
>
> - if (!(skb_shinfo(skb)->gso_type & SKB_GSO_UDP))
> + if (!(skb_shinfo(skb)->gso_type & (SKB_GSO_UDP | SKB_GSO_UDP_L4)))
> goto out;
>
> if (!pskb_may_pull(skb, sizeof(struct udphdr)))
> goto out;
>
> + if (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4)
> + return __udp6_gso_segment(skb, features);
> +
> /* Do software UFO. Complete and fill in the UDP checksum as HW cannot
> * do checksum of UDP packets sent as multiple IP fragments.
> */
> --
> 2.17.0.441.gb46fe60e1d-goog
>
Powered by blists - more mailing lists