Blame SOURCES/haproxy-buffer-slow-realign.patch

349717
From 039ee0a1f892c7a6445ad976ced57d395d2d4b43 Mon Sep 17 00:00:00 2001
349717
From: Willy Tarreau <w@1wt.eu>
349717
Date: Thu, 2 Jul 2015 12:50:23 +0200
349717
Subject: [PATCH] BUG/MAJOR: buffers: make the buffer_slow_realign() function
349717
 respect output data
349717
349717
The function buffer_slow_realign() was initially designed for requests
349717
only and did not consider pending outgoing data. This causes a problem
349717
when called on responses where data remain in the buffer, which may
349717
happen with pipelined requests when the client is slow to read data.
349717
349717
The user-visible effect is that if less than <maxrewrite> bytes are
349717
present in the buffer from a previous response and these bytes cross
349717
the <maxrewrite> boundary close to the end of the buffer, then a new
349717
response will cause a realign and will destroy these pending data and
349717
move the pointer to what's believed to contain pending output data.
349717
Thus the client receives the crap that lies in the buffer instead of
349717
the original output bytes.
349717
349717
This new implementation now properly realigns everything including the
349717
outgoing data which are moved to the end of the buffer while the input
349717
data are moved to the beginning.
349717
349717
This implementation still uses a buffer-to-buffer copy which is not
349717
optimal in terms of performance and which should be replaced by a
349717
buffer switch later.
349717
349717
Prior to this patch, the following script would return different hashes
349717
on each round when run from a 100 Mbps-connected machine :
349717
349717
  i=0
349717
  while usleep 100000; do
349717
    echo round $((i++))
349717
    set -- $(nc6 0 8001 < 1kreq5k.txt | grep -v '^[0-9A-Z]' | md5sum)
349717
    if [ "$1" != "3861afbb6566cd48740ce01edc426020" ]; then echo $1;break;fi
349717
  done
349717
349717
The file contains 1000 times this request with "Connection: close" on the
349717
last one :
349717
349717
  GET /?s=5k&R=1 HTTP/1.1
349717
349717
The config is very simple :
349717
349717
  global
349717
        tune.bufsize 16384
349717
        tune.maxrewrite 8192
349717
349717
  defaults
349717
        mode http
349717
        timeout client 10s
349717
        timeout server 5s
349717
        timeout connect 3s
349717
349717
  listen px
349717
        bind :8001
349717
        option http-server-close
349717
        server s1 127.0.0.1:8000
349717
349717
And httpterm-1.7.2 is used as the server on port 8000.
349717
349717
After the fix, 1 million requests were sent and all returned the same
349717
contents.
349717
349717
Many thanks to Charlie Smurthwaite of atechmedia.com for his precious
349717
help on this issue, which would not have been diagnosed without his
349717
very detailed traces and numerous tests.
349717
349717
The patch must be backported to 1.5 which is where the bug was introduced.
349717
(cherry picked from commit 27187ab56a2f1104818c2f21c5139c1edd8b838f)
349717
---
349717
 src/buffer.c | 49 +++++++++++++++++++++++++++++--------------------
349717
 1 file changed, 29 insertions(+), 20 deletions(-)
349717
349717
diff --git a/src/buffer.c b/src/buffer.c
349717
index 91bee63..f5c8e1d 100644
349717
--- a/src/buffer.c
349717
+++ b/src/buffer.c
349717
@@ -102,30 +102,39 @@ int buffer_insert_line2(struct buffer *b, char *pos, const char *str, int len)
349717
 	return delta;
349717
 }
349717
 
349717
-/* This function realigns input data in a possibly wrapping buffer so that it
349717
- * becomes contiguous and starts at the beginning of the buffer area. The
349717
- * function may only be used when the buffer's output is empty.
349717
+/* This function realigns a possibly wrapping buffer so that the input part is
349717
+ * contiguous and starts at the beginning of the buffer and the output part
349717
+ * ends at the end of the buffer. This provides the best conditions since it
349717
+ * allows the largest inputs to be processed at once and ensures that once the
349717
+ * output data leaves, the whole buffer is available at once.
349717
  */
349717
 void buffer_slow_realign(struct buffer *buf)
349717
 {
349717
-	/* two possible cases :
349717
-	 *   - the buffer is in one contiguous block, we move it in-place
349717
-	 *   - the buffer is in two blocks, we move it via the swap_buffer
349717
-	 */
349717
-	if (buf->i) {
349717
-		int block1 = buf->i;
349717
-		int block2 = 0;
349717
-		if (buf->p + buf->i > buf->data + buf->size) {
349717
-			/* non-contiguous block */
349717
-			block1 = buf->data + buf->size - buf->p;
349717
-			block2 = buf->p + buf->i - (buf->data + buf->size);
349717
-		}
349717
-		if (block2)
349717
-			memcpy(swap_buffer, buf->data, block2);
349717
-		memmove(buf->data, buf->p, block1);
349717
-		if (block2)
349717
-			memcpy(buf->data + block1, swap_buffer, block2);
349717
+	int block1 = buf->o;
349717
+	int block2 = 0;
349717
+
349717
+	/* process output data in two steps to cover wrapping */
349717
+	if (block1 > buf->p - buf->data) {
349717
+		block2 = buf->p - buf->data;
349717
+		block1 -= block2;
349717
+	}
349717
+	memcpy(swap_buffer + buf->size - buf->o, bo_ptr(buf), block1);
349717
+	memcpy(swap_buffer + buf->size - block2, buf->data, block2);
349717
+
349717
+	/* process input data in two steps to cover wrapping */
349717
+	block1 = buf->i;
349717
+	block2 = 0;
349717
+
349717
+	if (block1 > buf->data + buf->size - buf->p) {
349717
+		block1 = buf->data + buf->size - buf->p;
349717
+		block2 = buf->i - block1;
349717
 	}
349717
+	memcpy(swap_buffer, bi_ptr(buf), block1);
349717
+	memcpy(swap_buffer + block1, buf->data, block2);
349717
+
349717
+	/* reinject changes into the buffer */
349717
+	memcpy(buf->data, swap_buffer, buf->i);
349717
+	memcpy(buf->data + buf->size - buf->o, swap_buffer + buf->size - buf->o, buf->o);
349717
 
349717
 	buf->p = buf->data;
349717
 }
349717
-- 
349717
1.8.1.4
349717