From: Eric Dumazet Date: Fri, 15 Jan 2016 12:56:56 +0000 (-0800) Subject: ipv6: update skb->csum when CE mark is propagated X-Git-Tag: v3.2.80~25 X-Git-Url: https://git.openpandora.org/cgi-bin/gitweb.cgi?p=pandora-kernel.git;a=commitdiff_plain;h=58b45f408a8821b6d9f0003c3fdfa179145c90e7 ipv6: update skb->csum when CE mark is propagated [ 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 Acked-by: Herbert Xu Signed-off-by: David S. Miller [bwh: Backported to 3.2: - Adjust context - Add skb argument to other callers of IP6_ECN_set_ce()] Signed-off-by: Ben Hutchings --- diff --git a/include/net/inet_ecn.h b/include/net/inet_ecn.h index 2fa14691869c..18f2d104b6be 100644 --- a/include/net/inet_ecn.h +++ b/include/net/inet_ecn.h @@ -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; } diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c index 5f28fabffd67..601254e4f28f 100644 --- a/net/ipv4/ip_gre.c +++ b/net/ipv4/ip_gre.c @@ -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)); } } } diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index ae32ff78ab31..1828c27ba7aa 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -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() */ diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c index 52ce1969d9b2..1dbd1d10ca9d 100644 --- a/net/ipv6/sit.c +++ b/net/ipv6/sit.c @@ -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) diff --git a/net/ipv6/xfrm6_mode_tunnel.c b/net/ipv6/xfrm6_mode_tunnel.c index 23ecd68a5e62..e33c9d7a4f85 100644 --- a/net/ipv6/xfrm6_mode_tunnel.c +++ b/net/ipv6/xfrm6_mode_tunnel.c @@ -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.