diff --git a/include/net/udp.h b/include/net/udp.h index 05990746810eaae7802426ca9527d5fe4dddd995..9289b64250322b861400cc19f1089b36755daedb 100644 --- a/include/net/udp.h +++ b/include/net/udp.h @@ -175,8 +175,7 @@ struct sk_buff **udp_gro_receive(struct sk_buff **head, struct sk_buff *skb, 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); + netdev_features_t features); static inline struct udphdr *udp_gro_udphdr(struct sk_buff *skb) { diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index dd3102a37ef937283558f07983489b636edf4876..e07db83b311efd48d323e41242105a153fb1bbc8 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -793,6 +793,8 @@ static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4, skb_shinfo(skb)->gso_size = cork->gso_size; skb_shinfo(skb)->gso_type = SKB_GSO_UDP_L4; + skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(len - sizeof(uh), + cork->gso_size); goto csum_partial; } diff --git a/net/ipv4/udp_offload.c b/net/ipv4/udp_offload.c index 006257092f06de863087bfd7d1ad65a6928fc3bf..ede2a7305b90f789c748d911530453ec2cbbfab7 100644 --- a/net/ipv4/udp_offload.c +++ b/net/ipv4/udp_offload.c @@ -188,66 +188,92 @@ 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) + netdev_features_t features) { struct sock *sk = gso_skb->sk; unsigned int sum_truesize = 0; struct sk_buff *segs, *seg; - unsigned int hdrlen; struct udphdr *uh; + unsigned int mss; + bool copy_dtor; + __sum16 check; + __be16 newlen; + mss = skb_shinfo(gso_skb)->gso_size; if (gso_skb->len <= sizeof(*uh) + mss) return ERR_PTR(-EINVAL); - hdrlen = gso_skb->data - skb_mac_header(gso_skb); skb_pull(gso_skb, sizeof(*uh)); /* clear destructor to avoid skb_segment assigning it to tail */ - WARN_ON_ONCE(gso_skb->destructor != sock_wfree); - gso_skb->destructor = NULL; + copy_dtor = gso_skb->destructor == sock_wfree; + if (copy_dtor) + gso_skb->destructor = NULL; segs = skb_segment(gso_skb, features); if (unlikely(IS_ERR_OR_NULL(segs))) { - gso_skb->destructor = sock_wfree; + if (copy_dtor) + gso_skb->destructor = sock_wfree; return segs; } - for (seg = segs; seg; seg = seg->next) { - uh = udp_hdr(seg); - uh->len = htons(seg->len - hdrlen); - uh->check = check; + /* GSO partial and frag_list segmentation only requires splitting + * the frame into an MSS multiple and possibly a remainder, both + * cases return a GSO skb. So update the mss now. + */ + if (skb_is_gso(segs)) + mss *= skb_shinfo(segs)->gso_segs; + + seg = segs; + uh = udp_hdr(seg); + + /* compute checksum adjustment based on old length versus new */ + newlen = htons(sizeof(*uh) + mss); + check = csum16_add(csum16_sub(uh->check, uh->len), newlen); + + for (;;) { + if (copy_dtor) { + seg->destructor = sock_wfree; + seg->sk = sk; + sum_truesize += seg->truesize; + } - /* last packet can be partial gso_size */ if (!seg->next) - csum_replace2(&uh->check, htons(mss), - htons(seg->len - hdrlen - sizeof(*uh))); + break; - uh->check = ~uh->check; - seg->destructor = sock_wfree; - seg->sk = sk; - sum_truesize += seg->truesize; - } + uh->len = newlen; + uh->check = check; - refcount_add(sum_truesize - gso_skb->truesize, &sk->sk_wmem_alloc); + if (seg->ip_summed == CHECKSUM_PARTIAL) + gso_reset_checksum(seg, ~check); + else + uh->check = gso_make_checksum(seg, ~check) ? : + CSUM_MANGLED_0; - return segs; -} -EXPORT_SYMBOL_GPL(__udp_gso_segment); + seg = seg->next; + uh = udp_hdr(seg); + } -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; + /* last packet can be partial gso_size, account for that in checksum */ + newlen = htons(skb_tail_pointer(seg) - skb_transport_header(seg) + + seg->data_len); + check = csum16_add(csum16_sub(uh->check, uh->len), newlen); - if (!can_checksum_protocol(features, htons(ETH_P_IP))) - return ERR_PTR(-EIO); + uh->len = newlen; + uh->check = check; + + if (seg->ip_summed == CHECKSUM_PARTIAL) + gso_reset_checksum(seg, ~check); + else + uh->check = gso_make_checksum(seg, ~check) ? : CSUM_MANGLED_0; - return __udp_gso_segment(gso_skb, features, mss, - udp_v4_check(sizeof(struct udphdr) + mss, - iph->saddr, iph->daddr, 0)); + /* update refcount for the packet */ + if (copy_dtor) + refcount_add(sum_truesize - gso_skb->truesize, + &sk->sk_wmem_alloc); + return segs; } +EXPORT_SYMBOL_GPL(__udp_gso_segment); static struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb, netdev_features_t features) @@ -272,7 +298,7 @@ static struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb, goto out; if (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4) - return __udp4_gso_segment(skb, features); + return __udp_gso_segment(skb, features); mss = skb_shinfo(skb)->gso_size; if (unlikely(skb->len <= mss)) diff --git a/net/ipv6/udp_offload.c b/net/ipv6/udp_offload.c index f7b85b1e6b3e6d06bbeafd6ecfed025ef71143d7..03a2ff3fe1e697e752e2aa9f13703b6feaff0453 100644 --- a/net/ipv6/udp_offload.c +++ b/net/ipv6/udp_offload.c @@ -17,20 +17,6 @@ #include #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) { @@ -63,7 +49,7 @@ static struct sk_buff *udp6_ufo_fragment(struct sk_buff *skb, goto out; if (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4) - return __udp6_gso_segment(skb, features); + return __udp_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.