@@ -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)
{
@@ -188,19 +188,23 @@ 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 sk_buff *seg, *segs = ERR_PTR(-EINVAL);
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;
+ __sum16 check;
+ __be16 newlen;
+ mss = skb_shinfo(gso_skb)->gso_size;
if (gso_skb->len <= sizeof(*uh) + mss)
- return ERR_PTR(-EINVAL);
+ goto out;
+
+ if (!pskb_may_pull(gso_skb, sizeof(*uh)))
+ goto out;
- 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 */
@@ -213,24 +217,42 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
return segs;
}
- for (seg = segs; seg; seg = seg->next) {
- uh = udp_hdr(seg);
- uh->len = htons(seg->len - hdrlen);
- uh->check = check;
+ seg = segs;
+ uh = udp_hdr(seg);
- /* last packet can be partial gso_size */
- if (!seg->next)
- csum_replace2(&uh->check, htons(mss),
- htons(seg->len - hdrlen - sizeof(*uh)));
+ /* compute checksum adjustment based on old length versus new */
+ newlen = htons(sizeof(*uh) + mss);
+ check = ~csum_fold((__force __wsum)((__force u32)uh->check +
+ ((__force u32)uh->len ^ 0xFFFF) +
+ (__force u32)newlen));
- uh->check = ~uh->check;
+ for (;;) {
seg->destructor = sock_wfree;
seg->sk = sk;
sum_truesize += seg->truesize;
+
+ if (!seg->next)
+ break;
+
+ uh->len = newlen;
+ uh->check = check;
+
+ seg = seg->next;
+ uh = udp_hdr(seg);
}
- refcount_add(sum_truesize - gso_skb->truesize, &sk->sk_wmem_alloc);
+ /* 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 = ~csum_fold((__force __wsum)((__force u32)uh->check +
+ ((__force u32)uh->len ^ 0xFFFF) +
+ (__force u32)newlen));
+ uh->len = newlen;
+ uh->check = check;
+ /* update refcount for the packet */
+ refcount_add(sum_truesize - gso_skb->truesize, &sk->sk_wmem_alloc);
+out:
return segs;
}
EXPORT_SYMBOL_GPL(__udp_gso_segment);
@@ -238,15 +260,10 @@ struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
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));
+ return __udp_gso_segment(gso_skb, features);
}
static struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb,
@@ -20,15 +20,10 @@
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));
+ return __udp_gso_segment(gso_skb, features);
}
static struct sk_buff *udp6_ufo_fragment(struct sk_buff *skb,