ipv6: update skb->csum when CE mark is propagated
authorEric Dumazet <edumazet@google.com>
Fri, 15 Jan 2016 12:56:56 +0000 (04:56 -0800)
committerBen Hutchings <ben@decadent.org.uk>
Sat, 30 Apr 2016 22:05:25 +0000 (00:05 +0200)
[ Upstream commit 34ae6a1aa0540f0f781dd265366036355fdc8930 ]

When a tunnel decapsulates the outer header, it has to comply
with RFC 6080 and eventually propagate CE mark into inner header.

It turns out IP6_ECN_set_ce() does not correctly update skb->csum
for CHECKSUM_COMPLETE packets, triggering infamous "hw csum failure"
messages and stack traces.

Signed-off-by: Eric Dumazet <edumazet@google.com>
Acked-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
[bwh: Backported to 3.2:
 - Adjust context
 - Add skb argument to other callers of IP6_ECN_set_ce()]
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
include/net/inet_ecn.h
net/ipv4/ip_gre.c
net/ipv6/ip6_tunnel.c
net/ipv6/sit.c
net/ipv6/xfrm6_mode_tunnel.c

index 2fa1469..18f2d10 100644 (file)
@@ -109,11 +109,24 @@ static inline void ipv4_copy_dscp(unsigned int dscp, struct iphdr *inner)
 
 struct ipv6hdr;
 
-static inline int IP6_ECN_set_ce(struct ipv6hdr *iph)
+/* Note:
+ * IP_ECN_set_ce() has to tweak IPV4 checksum when setting CE,
+ * meaning both changes have no effect on skb->csum if/when CHECKSUM_COMPLETE
+ * In IPv6 case, no checksum compensates the change in IPv6 header,
+ * so we have to update skb->csum.
+ */
+static inline int IP6_ECN_set_ce(struct sk_buff *skb, struct ipv6hdr *iph)
 {
+       __be32 from, to;
+
        if (INET_ECN_is_not_ect(ipv6_get_dsfield(iph)))
                return 0;
-       *(__be32*)iph |= htonl(INET_ECN_CE << 20);
+
+       from = *(__be32 *)iph;
+       to = from | htonl(INET_ECN_CE << 20);
+       *(__be32 *)iph = to;
+       if (skb->ip_summed == CHECKSUM_COMPLETE)
+               skb->csum = csum_add(csum_sub(skb->csum, from), to);
        return 1;
 }
 
@@ -138,7 +151,7 @@ static inline int INET_ECN_set_ce(struct sk_buff *skb)
 
        case cpu_to_be16(ETH_P_IPV6):
                if (skb->network_header + sizeof(struct ipv6hdr) <= skb->tail)
-                       return IP6_ECN_set_ce(ipv6_hdr(skb));
+                       return IP6_ECN_set_ce(skb, ipv6_hdr(skb));
                break;
        }
 
index 5f28fab..601254e 100644 (file)
@@ -535,7 +535,7 @@ static inline void ipgre_ecn_decapsulate(const struct iphdr *iph, struct sk_buff
                if (skb->protocol == htons(ETH_P_IP)) {
                        IP_ECN_set_ce(ip_hdr(skb));
                } else if (skb->protocol == htons(ETH_P_IPV6)) {
-                       IP6_ECN_set_ce(ipv6_hdr(skb));
+                       IP6_ECN_set_ce(skb, ipv6_hdr(skb));
                }
        }
 }
index ae32ff7..1828c27 100644 (file)
@@ -689,7 +689,7 @@ static void ip6ip6_dscp_ecn_decapsulate(const struct ip6_tnl *t,
                ipv6_copy_dscp(ipv6_get_dsfield(ipv6h), ipv6_hdr(skb));
 
        if (INET_ECN_is_ce(ipv6_get_dsfield(ipv6h)))
-               IP6_ECN_set_ce(ipv6_hdr(skb));
+               IP6_ECN_set_ce(skb, ipv6_hdr(skb));
 }
 
 /* called with rcu_read_lock() */
index 52ce196..1dbd1d1 100644 (file)
@@ -553,7 +553,7 @@ out:
 static inline void ipip6_ecn_decapsulate(const struct iphdr *iph, struct sk_buff *skb)
 {
        if (INET_ECN_is_ce(iph->tos))
-               IP6_ECN_set_ce(ipv6_hdr(skb));
+               IP6_ECN_set_ce(skb, ipv6_hdr(skb));
 }
 
 static int ipip6_rcv(struct sk_buff *skb)
index 23ecd68..e33c9d7 100644 (file)
@@ -24,7 +24,7 @@ static inline void ipip6_ecn_decapsulate(struct sk_buff *skb)
        struct ipv6hdr *inner_iph = ipipv6_hdr(skb);
 
        if (INET_ECN_is_ce(ipv6_get_dsfield(outer_iph)))
-               IP6_ECN_set_ce(inner_iph);
+               IP6_ECN_set_ce(skb, inner_iph);
 }
 
 /* Add encapsulation header.