[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>
Este cometimento está contido em:

cometido por
David S. Miller

ascendente
89114afd43
cometimento
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,
|
||||
};
|
||||
|
||||
|
@@ -552,6 +552,24 @@ static void tcp_v6_send_check(struct sock *sk, int len, struct sk_buff *skb)
|
||||
}
|
||||
}
|
||||
|
||||
static int tcp_v6_gso_send_check(struct sk_buff *skb)
|
||||
{
|
||||
struct ipv6hdr *ipv6h;
|
||||
struct tcphdr *th;
|
||||
|
||||
if (!pskb_may_pull(skb, sizeof(*th)))
|
||||
return -EINVAL;
|
||||
|
||||
ipv6h = skb->nh.ipv6h;
|
||||
th = skb->h.th;
|
||||
|
||||
th->check = 0;
|
||||
th->check = ~csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr, skb->len,
|
||||
IPPROTO_TCP, 0);
|
||||
skb->csum = offsetof(struct tcphdr, check);
|
||||
skb->ip_summed = CHECKSUM_HW;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tcp_v6_send_reset(struct sk_buff *skb)
|
||||
{
|
||||
@@ -1603,6 +1621,7 @@ struct proto tcpv6_prot = {
|
||||
static struct inet6_protocol tcpv6_protocol = {
|
||||
.handler = tcp_v6_rcv,
|
||||
.err_handler = tcp_v6_err,
|
||||
.gso_send_check = tcp_v6_gso_send_check,
|
||||
.gso_segment = tcp_tso_segment,
|
||||
.flags = INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL,
|
||||
};
|
||||
|
Criar uma nova questão referindo esta
Bloquear um utilizador