CHROMIUM: tcp: Avoid merging segments on the OOO queue into a cloned SKB
authorJulius Werner <jwerner@chromium.org>
Thu, 8 Nov 2012 22:51:33 +0000 (14:51 -0800)
committerGerrit <chrome-bot@google.com>
Tue, 13 Nov 2012 06:12:25 +0000 (22:12 -0800)
TCP tries to merge socket buffers in the out-of-order queue when they
are sequential. This is generally a good thing to save memory and speed
up processing, but can lead to problems when it expands a buffer that
has previously been cloned.

We have encountered a bug with a confused/broken receive queue, that we
think might have been caused by this problem, as the smsc95xx driver
uses cloned buffers in its receive path. Thanks to edumazet for
pointing that out and suggesting this fix.

This patch makes sure that this code path only expands socket buffers
that have not been cloned and just enqueues them normally in the other
case. In upstream kernels starting with 3.5, the affected code has been
refactored into tcp_try_coalesce with commit 1402d36, which has later
been patched to fix this issue in commit 923dd34. The fix has not been
backported into the upstream 3.4 kernel.

BUG=chromium-os:35827
TEST=None

Change-Id: I2cdf09946a3cbb0cbff35b1ac09313352b8f1e94
Signed-off-by: Julius Werner <jwerner@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/37666
Reviewed-by: Eric Dumazet <edumazet@chromium.org>
Reviewed-by: Sameer Nanda <snanda@chromium.org>
net/ipv4/tcp_input.c

index 257b617..9f8f68c 100644 (file)
@@ -4496,7 +4496,9 @@ static void tcp_data_queue_ofo(struct sock *sk, struct sk_buff *skb)
                 * to avoid future tcp_collapse_ofo_queue(),
                 * probably the most expensive function in tcp stack.
                 */
-               if (skb->len <= skb_tailroom(skb1) && !tcp_hdr(skb)->fin) {
+               if (skb->len <= skb_tailroom(skb1) &&
+                   !tcp_hdr(skb)->fin &&
+                   !skb_cloned(skb1)) {
                        NET_INC_STATS_BH(sock_net(sk),
                                         LINUX_MIB_TCPRCVCOALESCE);
                        BUG_ON(skb_copy_bits(skb, 0,