diff options
author | Eric Dumazet <edumazet@google.com> | 2016-01-15 07:56:56 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2016-01-15 15:07:23 -0500 |
commit | 34ae6a1aa0540f0f781dd265366036355fdc8930 (patch) | |
tree | 11df908434ac0da470b34a08fd254df1429c4e65 | |
parent | 113c74d83eef870e43a0d9279044e9d5435f0d07 (diff) |
ipv6: update skb->csum when CE mark is propagated
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>
-rw-r--r-- | include/net/inet_ecn.h | 19 | ||||
-rw-r--r-- | net/ipv6/xfrm6_mode_tunnel.c | 2 |
2 files changed, 17 insertions, 4 deletions
diff --git a/include/net/inet_ecn.h b/include/net/inet_ecn.h index 84b20835b736..0dc0a51da38f 100644 --- a/include/net/inet_ecn.h +++ b/include/net/inet_ecn.h | |||
@@ -111,11 +111,24 @@ static inline void ipv4_copy_dscp(unsigned int dscp, struct iphdr *inner) | |||
111 | 111 | ||
112 | struct ipv6hdr; | 112 | struct ipv6hdr; |
113 | 113 | ||
114 | static inline int IP6_ECN_set_ce(struct ipv6hdr *iph) | 114 | /* Note: |
115 | * IP_ECN_set_ce() has to tweak IPV4 checksum when setting CE, | ||
116 | * meaning both changes have no effect on skb->csum if/when CHECKSUM_COMPLETE | ||
117 | * In IPv6 case, no checksum compensates the change in IPv6 header, | ||
118 | * so we have to update skb->csum. | ||
119 | */ | ||
120 | static inline int IP6_ECN_set_ce(struct sk_buff *skb, struct ipv6hdr *iph) | ||
115 | { | 121 | { |
122 | __be32 from, to; | ||
123 | |||
116 | if (INET_ECN_is_not_ect(ipv6_get_dsfield(iph))) | 124 | if (INET_ECN_is_not_ect(ipv6_get_dsfield(iph))) |
117 | return 0; | 125 | return 0; |
118 | *(__be32*)iph |= htonl(INET_ECN_CE << 20); | 126 | |
127 | from = *(__be32 *)iph; | ||
128 | to = from | htonl(INET_ECN_CE << 20); | ||
129 | *(__be32 *)iph = to; | ||
130 | if (skb->ip_summed == CHECKSUM_COMPLETE) | ||
131 | skb->csum = csum_add(csum_sub(skb->csum, from), to); | ||
119 | return 1; | 132 | return 1; |
120 | } | 133 | } |
121 | 134 | ||
@@ -142,7 +155,7 @@ static inline int INET_ECN_set_ce(struct sk_buff *skb) | |||
142 | case cpu_to_be16(ETH_P_IPV6): | 155 | case cpu_to_be16(ETH_P_IPV6): |
143 | if (skb_network_header(skb) + sizeof(struct ipv6hdr) <= | 156 | if (skb_network_header(skb) + sizeof(struct ipv6hdr) <= |
144 | skb_tail_pointer(skb)) | 157 | skb_tail_pointer(skb)) |
145 | return IP6_ECN_set_ce(ipv6_hdr(skb)); | 158 | return IP6_ECN_set_ce(skb, ipv6_hdr(skb)); |
146 | break; | 159 | break; |
147 | } | 160 | } |
148 | 161 | ||
diff --git a/net/ipv6/xfrm6_mode_tunnel.c b/net/ipv6/xfrm6_mode_tunnel.c index f7fbdbabe50e..372855eeaf42 100644 --- a/net/ipv6/xfrm6_mode_tunnel.c +++ b/net/ipv6/xfrm6_mode_tunnel.c | |||
@@ -23,7 +23,7 @@ static inline void ipip6_ecn_decapsulate(struct sk_buff *skb) | |||
23 | struct ipv6hdr *inner_iph = ipipv6_hdr(skb); | 23 | struct ipv6hdr *inner_iph = ipipv6_hdr(skb); |
24 | 24 | ||
25 | if (INET_ECN_is_ce(XFRM_MODE_SKB_CB(skb)->tos)) | 25 | if (INET_ECN_is_ce(XFRM_MODE_SKB_CB(skb)->tos)) |
26 | IP6_ECN_set_ce(inner_iph); | 26 | IP6_ECN_set_ce(skb, inner_iph); |
27 | } | 27 | } |
28 | 28 | ||
29 | /* Add encapsulation header. | 29 | /* Add encapsulation header. |