[NET] gso: Fix up GSO packets with broken checksums
Certain subsystems in the stack (e.g., netfilter) can break the partial checksum on GSO packets. Until they're fixed, this patch allows this to work by recomputing the partial checksums through the GSO mechanism. Once they've all been converted to update the partial checksum instead of clearing it, this workaround can be removed. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
committed by
David S. Miller
parent
89114afd43
commit
a430a43d08
@@ -57,12 +57,71 @@
|
||||
|
||||
DEFINE_SNMP_STAT(struct ipstats_mib, ipv6_statistics) __read_mostly;
|
||||
|
||||
static struct inet6_protocol *ipv6_gso_pull_exthdrs(struct sk_buff *skb,
|
||||
int proto)
|
||||
{
|
||||
struct inet6_protocol *ops = NULL;
|
||||
|
||||
for (;;) {
|
||||
struct ipv6_opt_hdr *opth;
|
||||
int len;
|
||||
|
||||
if (proto != NEXTHDR_HOP) {
|
||||
ops = rcu_dereference(inet6_protos[proto]);
|
||||
|
||||
if (unlikely(!ops))
|
||||
break;
|
||||
|
||||
if (!(ops->flags & INET6_PROTO_GSO_EXTHDR))
|
||||
break;
|
||||
}
|
||||
|
||||
if (unlikely(!pskb_may_pull(skb, 8)))
|
||||
break;
|
||||
|
||||
opth = (void *)skb->data;
|
||||
len = opth->hdrlen * 8 + 8;
|
||||
|
||||
if (unlikely(!pskb_may_pull(skb, len)))
|
||||
break;
|
||||
|
||||
proto = opth->nexthdr;
|
||||
__skb_pull(skb, len);
|
||||
}
|
||||
|
||||
return ops;
|
||||
}
|
||||
|
||||
static int ipv6_gso_send_check(struct sk_buff *skb)
|
||||
{
|
||||
struct ipv6hdr *ipv6h;
|
||||
struct inet6_protocol *ops;
|
||||
int err = -EINVAL;
|
||||
|
||||
if (unlikely(!pskb_may_pull(skb, sizeof(*ipv6h))))
|
||||
goto out;
|
||||
|
||||
ipv6h = skb->nh.ipv6h;
|
||||
__skb_pull(skb, sizeof(*ipv6h));
|
||||
err = -EPROTONOSUPPORT;
|
||||
|
||||
rcu_read_lock();
|
||||
ops = ipv6_gso_pull_exthdrs(skb, ipv6h->nexthdr);
|
||||
if (likely(ops && ops->gso_send_check)) {
|
||||
skb->h.raw = skb->data;
|
||||
err = ops->gso_send_check(skb);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb, int features)
|
||||
{
|
||||
struct sk_buff *segs = ERR_PTR(-EINVAL);
|
||||
struct ipv6hdr *ipv6h;
|
||||
struct inet6_protocol *ops;
|
||||
int proto;
|
||||
|
||||
if (unlikely(skb_shinfo(skb)->gso_type &
|
||||
~(SKB_GSO_UDP |
|
||||
@@ -76,42 +135,15 @@ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb, int features)
|
||||
goto out;
|
||||
|
||||
ipv6h = skb->nh.ipv6h;
|
||||
proto = ipv6h->nexthdr;
|
||||
__skb_pull(skb, sizeof(*ipv6h));
|
||||
segs = ERR_PTR(-EPROTONOSUPPORT);
|
||||
|
||||
rcu_read_lock();
|
||||
for (;;) {
|
||||
struct ipv6_opt_hdr *opth;
|
||||
int len;
|
||||
|
||||
if (proto != NEXTHDR_HOP) {
|
||||
ops = rcu_dereference(inet6_protos[proto]);
|
||||
|
||||
if (unlikely(!ops))
|
||||
goto unlock;
|
||||
|
||||
if (!(ops->flags & INET6_PROTO_GSO_EXTHDR))
|
||||
break;
|
||||
}
|
||||
|
||||
if (unlikely(!pskb_may_pull(skb, 8)))
|
||||
goto unlock;
|
||||
|
||||
opth = (void *)skb->data;
|
||||
len = opth->hdrlen * 8 + 8;
|
||||
|
||||
if (unlikely(!pskb_may_pull(skb, len)))
|
||||
goto unlock;
|
||||
|
||||
proto = opth->nexthdr;
|
||||
__skb_pull(skb, len);
|
||||
}
|
||||
|
||||
skb->h.raw = skb->data;
|
||||
if (likely(ops->gso_segment))
|
||||
ops = ipv6_gso_pull_exthdrs(skb, ipv6h->nexthdr);
|
||||
if (likely(ops && ops->gso_segment)) {
|
||||
skb->h.raw = skb->data;
|
||||
segs = ops->gso_segment(skb, features);
|
||||
|
||||
unlock:
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
if (unlikely(IS_ERR(segs)))
|
||||
@@ -130,6 +162,7 @@ out:
|
||||
static struct packet_type ipv6_packet_type = {
|
||||
.type = __constant_htons(ETH_P_IPV6),
|
||||
.func = ipv6_rcv,
|
||||
.gso_send_check = ipv6_gso_send_check,
|
||||
.gso_segment = ipv6_gso_segment,
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user