CONTRIB: move some dev-specific tools to dev/

The following directories were moved from contrib/ to dev/ to make their
use case a bit clearer. In short, only developers are expected to ever
go there. The makefile was updated to build and clean from these ones.

base64/  flags/  hpack/  plug_qdisc/  poll/  tcploop/  trace/
diff --git a/dev/base64/base64rev-gen.c b/dev/base64/base64rev-gen.c
new file mode 100644
index 0000000..faffc87
--- /dev/null
+++ b/dev/base64/base64rev-gen.c
@@ -0,0 +1,70 @@
+/*
+ * base64rev generator
+ *
+ * Copyright 2009-2010 Krzysztof Piotr Oledzki <ole@ans.pl>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <stdio.h>
+
+const char base64tab[65]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+char base64rev[128];
+
+#define base '#'	/* arbitrary chosen base value */
+#define B64MAX	64
+#define B64PADV B64MAX
+
+int main() {
+	char *p, c;
+	int i, min = 255, max = 0;
+
+	for (i = 0; i < sizeof(base64rev); i++)
+		base64rev[i] = base;
+
+	for (i = 0;  i < B64MAX; i++) {
+		c = base64tab[i];
+
+		if (min > c)
+			min = c;
+
+		if (max < c)
+			max = c;
+	}
+
+	for (i = 0;  i < B64MAX; i++) {
+		c = base64tab[i];
+
+		if (base+i+1 > 127) {
+			printf("Wrong base value @%d\n", i);
+			return 1;
+		}
+
+		base64rev[c - min] = base+i+1;
+	}
+
+	base64rev['=' - min] = base + B64PADV;
+
+	base64rev[max - min + 1] = '\0';
+
+	printf("#define B64BASE '%c'\n", base);
+	printf("#define B64CMIN '%c'\n", min);
+	printf("#define B64CMAX '%c'\n", max);
+	printf("#define B64PADV %u\n", B64PADV);
+
+	p = base64rev;
+	printf("const char base64rev[]=\"");
+	for (p = base64rev; *p; p++) {
+		if (*p == '\\')
+			printf("\\%c", *p);
+		else
+			printf("%c", *p);
+	}
+	printf("\"\n");
+
+	return 0;
+}
diff --git a/dev/flags/Makefile b/dev/flags/Makefile
new file mode 100644
index 0000000..9171eaf
--- /dev/null
+++ b/dev/flags/Makefile
@@ -0,0 +1,14 @@
+INCLUDE  = -I../../include
+
+CC       = gcc
+OPTIMIZE = -O2
+DEFINE   =
+OBJS     = flags
+
+all: $(OBJS)
+
+flags: flags.c
+	$(CC) $(OPTIMIZE) $(DEFINE) $(INCLUDE) -o $@ $^
+
+clean:
+	rm -f $(OBJS) *.[oas] *~
diff --git a/dev/flags/flags.c b/dev/flags/flags.c
new file mode 100644
index 0000000..669bd09
--- /dev/null
+++ b/dev/flags/flags.c
@@ -0,0 +1,471 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <haproxy/channel-t.h>
+#include <haproxy/connection-t.h>
+#include <haproxy/http_ana-t.h>
+#include <haproxy/stream-t.h>
+#include <haproxy/stream_interface-t.h>
+#include <haproxy/task-t.h>
+
+// 1 bit per flag, no hole permitted here
+#define SHOW_AS_ANA   0x00000001
+#define SHOW_AS_CHN   0x00000002
+#define SHOW_AS_CONN  0x00000004
+#define SHOW_AS_CS    0x00000008
+#define SHOW_AS_SI    0x00000010
+#define SHOW_AS_SIET  0x00000020
+#define SHOW_AS_STRM  0x00000040
+#define SHOW_AS_TASK  0x00000080
+#define SHOW_AS_TXN   0x00000100
+
+// command line names, must be in exact same order as the SHOW_AS_* flags above
+// so that show_as_words[i] matches flag 1U<<i.
+const char *show_as_words[] = { "ana", "chn", "conn", "cs", "si", "siet", "strm", "task", "txn", };
+
+#define SHOW_FLAG(f,n)					\
+	do {				 		\
+		if (!((f) & (n))) break; 		\
+		(f) &= ~(n);				\
+		printf(#n"%s", (f) ? " | " : "");	\
+	} while (0)
+
+unsigned int get_show_as(const char *word)
+{
+	int w = 0;
+
+	while (1) {
+		if (w == sizeof(show_as_words) / sizeof(*show_as_words))
+			return 0;
+		if (strcmp(word, show_as_words[w]) == 0)
+			return 1U << w;
+		w++;
+	}
+}
+
+void show_chn_ana(unsigned int f)
+{
+	printf("chn->ana    = ");
+
+	if (!f) {
+		printf("0\n");
+		return;
+	}
+
+	SHOW_FLAG(f, AN_REQ_FLT_START_FE);
+	SHOW_FLAG(f, AN_REQ_INSPECT_FE);
+	SHOW_FLAG(f, AN_REQ_WAIT_HTTP);
+	SHOW_FLAG(f, AN_REQ_HTTP_BODY);
+	SHOW_FLAG(f, AN_REQ_HTTP_PROCESS_FE);
+	SHOW_FLAG(f, AN_REQ_SWITCHING_RULES);
+	SHOW_FLAG(f, AN_REQ_FLT_START_BE);
+	SHOW_FLAG(f, AN_REQ_INSPECT_BE);
+	SHOW_FLAG(f, AN_REQ_HTTP_PROCESS_BE);
+	SHOW_FLAG(f, AN_REQ_HTTP_TARPIT);
+	SHOW_FLAG(f, AN_REQ_SRV_RULES);
+	SHOW_FLAG(f, AN_REQ_HTTP_INNER);
+	SHOW_FLAG(f, AN_REQ_PRST_RDP_COOKIE);
+	SHOW_FLAG(f, AN_REQ_STICKING_RULES);
+	SHOW_FLAG(f, AN_REQ_FLT_HTTP_HDRS);
+	SHOW_FLAG(f, AN_REQ_HTTP_XFER_BODY);
+	SHOW_FLAG(f, AN_REQ_FLT_XFER_DATA);
+	SHOW_FLAG(f, AN_REQ_FLT_END);
+
+	SHOW_FLAG(f, AN_RES_FLT_START_FE);
+	SHOW_FLAG(f, AN_RES_FLT_START_BE);
+	SHOW_FLAG(f, AN_RES_INSPECT);
+	SHOW_FLAG(f, AN_RES_WAIT_HTTP);
+	SHOW_FLAG(f, AN_RES_STORE_RULES);
+	SHOW_FLAG(f, AN_RES_HTTP_PROCESS_FE);
+	SHOW_FLAG(f, AN_RES_HTTP_PROCESS_BE);
+	SHOW_FLAG(f, AN_RES_FLT_HTTP_HDRS);
+	SHOW_FLAG(f, AN_RES_HTTP_XFER_BODY);
+	SHOW_FLAG(f, AN_RES_FLT_XFER_DATA);
+	SHOW_FLAG(f, AN_RES_FLT_END);
+
+	if (f) {
+		printf("EXTRA(0x%08x)", f);
+	}
+	putchar('\n');
+}
+
+void show_chn_flags(unsigned int f)
+{
+	printf("chn->flags  = ");
+
+	if (!f) {
+		printf("0\n");
+		return;
+	}
+
+	SHOW_FLAG(f, CF_ISRESP);
+	SHOW_FLAG(f, CF_EOI);
+	SHOW_FLAG(f, CF_FLT_ANALYZE);
+	SHOW_FLAG(f, CF_WAKE_ONCE);
+	SHOW_FLAG(f, CF_NEVER_WAIT);
+	SHOW_FLAG(f, CF_SEND_DONTWAIT);
+	SHOW_FLAG(f, CF_EXPECT_MORE);
+	SHOW_FLAG(f, CF_DONT_READ);
+	SHOW_FLAG(f, CF_AUTO_CONNECT);
+	SHOW_FLAG(f, CF_READ_DONTWAIT);
+	SHOW_FLAG(f, CF_KERN_SPLICING);
+	SHOW_FLAG(f, CF_READ_ATTACHED);
+	SHOW_FLAG(f, CF_ANA_TIMEOUT);
+	SHOW_FLAG(f, CF_WROTE_DATA);
+	SHOW_FLAG(f, CF_STREAMER_FAST);
+	SHOW_FLAG(f, CF_STREAMER);
+	SHOW_FLAG(f, CF_AUTO_CLOSE);
+	SHOW_FLAG(f, CF_SHUTW_NOW);
+	SHOW_FLAG(f, CF_SHUTW);
+	SHOW_FLAG(f, CF_WAKE_WRITE);
+	SHOW_FLAG(f, CF_WRITE_ERROR);
+	SHOW_FLAG(f, CF_WRITE_TIMEOUT);
+	SHOW_FLAG(f, CF_WRITE_PARTIAL);
+	SHOW_FLAG(f, CF_WRITE_NULL);
+	SHOW_FLAG(f, CF_READ_NOEXP);
+	SHOW_FLAG(f, CF_SHUTR_NOW);
+	SHOW_FLAG(f, CF_SHUTR);
+	SHOW_FLAG(f, CF_READ_ERROR);
+	SHOW_FLAG(f, CF_READ_TIMEOUT);
+	SHOW_FLAG(f, CF_READ_PARTIAL);
+	SHOW_FLAG(f, CF_READ_NULL);
+
+	if (f) {
+		printf("EXTRA(0x%08x)", f);
+	}
+	putchar('\n');
+}
+
+void show_conn_flags(unsigned int f)
+{
+	printf("conn->flags = ");
+	if (!f) {
+		printf("0\n");
+		return;
+	}
+
+	SHOW_FLAG(f, CO_FL_XPRT_TRACKED);
+	SHOW_FLAG(f, CO_FL_RCVD_PROXY);
+	SHOW_FLAG(f, CO_FL_PRIVATE);
+	SHOW_FLAG(f, CO_FL_ACCEPT_CIP);
+	SHOW_FLAG(f, CO_FL_ACCEPT_PROXY);
+	SHOW_FLAG(f, CO_FL_SSL_WAIT_HS);
+	SHOW_FLAG(f, CO_FL_SEND_PROXY);
+	SHOW_FLAG(f, CO_FL_WAIT_L6_CONN);
+	SHOW_FLAG(f, CO_FL_WAIT_L4_CONN);
+	SHOW_FLAG(f, CO_FL_ERROR);
+	SHOW_FLAG(f, CO_FL_SOCK_WR_SH);
+	SHOW_FLAG(f, CO_FL_SOCK_RD_SH);
+	SHOW_FLAG(f, CO_FL_SOCKS4_RECV);
+	SHOW_FLAG(f, CO_FL_SOCKS4_SEND);
+	SHOW_FLAG(f, CO_FL_EARLY_DATA);
+	SHOW_FLAG(f, CO_FL_EARLY_SSL_HS);
+	SHOW_FLAG(f, CO_FL_ADDR_TO_SET);
+	SHOW_FLAG(f, CO_FL_ADDR_FROM_SET);
+	SHOW_FLAG(f, CO_FL_WAIT_ROOM);
+	SHOW_FLAG(f, CO_FL_XPRT_READY);
+	SHOW_FLAG(f, CO_FL_CTRL_READY);
+	SHOW_FLAG(f, CO_FL_IDLE_LIST);
+	SHOW_FLAG(f, CO_FL_SAFE_LIST);
+
+	if (f) {
+		printf("EXTRA(0x%08x)", f);
+	}
+	putchar('\n');
+}
+void show_cs_flags(unsigned int f)
+{
+	printf("cs->flags = ");
+	if (!f) {
+		printf("0\n");
+		return;
+	}
+	SHOW_FLAG(f, CS_FL_NOT_FIRST);
+	SHOW_FLAG(f, CS_FL_KILL_CONN);
+	SHOW_FLAG(f, CS_FL_WAIT_FOR_HS);
+	SHOW_FLAG(f, CS_FL_EOI);
+	SHOW_FLAG(f, CS_FL_EOS);
+	SHOW_FLAG(f, CS_FL_ERR_PENDING);
+	SHOW_FLAG(f, CS_FL_WANT_ROOM);
+	SHOW_FLAG(f, CS_FL_RCV_MORE);
+	SHOW_FLAG(f, CS_FL_ERROR);
+	SHOW_FLAG(f, CS_FL_SHWS);
+	SHOW_FLAG(f, CS_FL_SHWN);
+	SHOW_FLAG(f, CS_FL_SHRR);
+	SHOW_FLAG(f, CS_FL_SHRD);
+
+	if (f) {
+		printf("EXTRA(0x%08x)", f);
+	}
+	putchar('\n');
+}
+
+void show_si_et(unsigned int f)
+{
+	printf("si->et      = ");
+	if (!f) {
+		printf("SI_ET_NONE\n");
+		return;
+	}
+
+	SHOW_FLAG(f, SI_ET_QUEUE_TO);
+	SHOW_FLAG(f, SI_ET_QUEUE_ERR);
+	SHOW_FLAG(f, SI_ET_QUEUE_ABRT);
+	SHOW_FLAG(f, SI_ET_CONN_TO);
+	SHOW_FLAG(f, SI_ET_CONN_ERR);
+	SHOW_FLAG(f, SI_ET_CONN_ABRT);
+	SHOW_FLAG(f, SI_ET_CONN_RES);
+	SHOW_FLAG(f, SI_ET_CONN_OTHER);
+	SHOW_FLAG(f, SI_ET_DATA_TO);
+	SHOW_FLAG(f, SI_ET_DATA_ERR);
+	SHOW_FLAG(f, SI_ET_DATA_ABRT);
+
+	if (f) {
+		printf("EXTRA(0x%08x)", f);
+	}
+	putchar('\n');
+}
+
+void show_si_flags(unsigned int f)
+{
+	printf("si->flags   = ");
+	if (!f) {
+		printf("SI_FL_NONE\n");
+		return;
+	}
+
+	SHOW_FLAG(f, SI_FL_EXP);
+	SHOW_FLAG(f, SI_FL_ERR);
+	SHOW_FLAG(f, SI_FL_RXBLK_ROOM);
+	SHOW_FLAG(f, SI_FL_WAIT_DATA);
+	SHOW_FLAG(f, SI_FL_ISBACK);
+	SHOW_FLAG(f, SI_FL_DONT_WAKE);
+	SHOW_FLAG(f, SI_FL_INDEP_STR);
+	SHOW_FLAG(f, SI_FL_NOLINGER);
+	SHOW_FLAG(f, SI_FL_NOHALF);
+	SHOW_FLAG(f, SI_FL_SRC_ADDR);
+	SHOW_FLAG(f, SI_FL_WANT_GET);
+	SHOW_FLAG(f, SI_FL_CLEAN_ABRT);
+	SHOW_FLAG(f, SI_FL_RXBLK_CHAN);
+	SHOW_FLAG(f, SI_FL_RXBLK_BUFF);
+	SHOW_FLAG(f, SI_FL_RXBLK_ROOM);
+	SHOW_FLAG(f, SI_FL_RXBLK_SHUT);
+	SHOW_FLAG(f, SI_FL_RX_WAIT_EP);
+	SHOW_FLAG(f, SI_FL_L7_RETRY);
+	SHOW_FLAG(f, SI_FL_D_L7_RETRY);
+
+	if (f) {
+		printf("EXTRA(0x%08x)", f);
+	}
+	putchar('\n');
+}
+
+void show_task_state(unsigned int f)
+{
+	printf("task->state = ");
+
+	if (!f) {
+		printf("TASK_SLEEPING\n");
+		return;
+	}
+
+	SHOW_FLAG(f, TASK_WOKEN_OTHER);
+	SHOW_FLAG(f, TASK_WOKEN_RES);
+	SHOW_FLAG(f, TASK_WOKEN_MSG);
+	SHOW_FLAG(f, TASK_WOKEN_SIGNAL);
+	SHOW_FLAG(f, TASK_WOKEN_IO);
+	SHOW_FLAG(f, TASK_WOKEN_TIMER);
+	SHOW_FLAG(f, TASK_WOKEN_INIT);
+	SHOW_FLAG(f, TASK_RUNNING);
+
+	if (f) {
+		printf("EXTRA(0x%08x)", f);
+	}
+	putchar('\n');
+}
+
+void show_txn_flags(unsigned int f)
+{
+	printf("txn->flags  = ");
+
+	if (!f) {
+		printf("0\n");
+		return;
+	}
+
+	SHOW_FLAG(f, TX_NOT_FIRST);
+	SHOW_FLAG(f, TX_USE_PX_CONN);
+	SHOW_FLAG(f, TX_CON_WANT_TUN);
+
+	SHOW_FLAG(f, TX_CACHE_COOK);
+	SHOW_FLAG(f, TX_CACHEABLE);
+	SHOW_FLAG(f, TX_SCK_PRESENT);
+
+	//printf("%s", f ? "" : " | ");
+	switch (f & TX_SCK_MASK) {
+	case TX_SCK_NONE:                        f &= ~TX_SCK_MASK ; /*printf("TX_SCK_NONE%s",     f ? " | " : "");*/ break;
+	case TX_SCK_FOUND:                       f &= ~TX_SCK_MASK ; printf("TX_SCK_FOUND%s",    f ? " | " : ""); break;
+	case TX_SCK_DELETED:                     f &= ~TX_SCK_MASK ; printf("TX_SCK_DELETED%s",  f ? " | " : ""); break;
+	case TX_SCK_INSERTED:                    f &= ~TX_SCK_MASK ; printf("TX_SCK_INSERTED%s", f ? " | " : ""); break;
+	case TX_SCK_REPLACED:                    f &= ~TX_SCK_MASK ; printf("TX_SCK_REPLACED%s", f ? " | " : ""); break;
+	case TX_SCK_UPDATED:                     f &= ~TX_SCK_MASK ; printf("TX_SCK_UPDATED%s",  f ? " | " : ""); break;
+	default: printf("TX_SCK_MASK(%02x)", f); f &= ~TX_SCK_MASK ; printf("%s",                f ? " | " : ""); break;
+	}
+
+	//printf("%s", f ? "" : " | ");
+	switch (f & TX_CK_MASK) {
+	case TX_CK_NONE:                        f &= ~TX_CK_MASK ; /*printf("TX_CK_NONE%s",    f ? " | " : "");*/ break;
+	case TX_CK_INVALID:                     f &= ~TX_CK_MASK ; printf("TX_CK_INVALID%s", f ? " | " : ""); break;
+	case TX_CK_DOWN:                        f &= ~TX_CK_MASK ; printf("TX_CK_DOWN%s",    f ? " | " : ""); break;
+	case TX_CK_VALID:                       f &= ~TX_CK_MASK ; printf("TX_CK_VALID%s",   f ? " | " : ""); break;
+	case TX_CK_EXPIRED:                     f &= ~TX_CK_MASK ; printf("TX_CK_EXPIRED%s", f ? " | " : ""); break;
+	case TX_CK_OLD:                         f &= ~TX_CK_MASK ; printf("TX_CK_OLD%s",     f ? " | " : ""); break;
+	case TX_CK_UNUSED:                      f &= ~TX_CK_MASK ; printf("TX_CK_UNUSED%s",  f ? " | " : ""); break;
+	default: printf("TX_CK_MASK(%02x)", f); f &= ~TX_CK_MASK ; printf("%s",              f ? " | " : ""); break;
+	}
+
+	SHOW_FLAG(f, TX_CLTARPIT);
+
+	if (f) {
+		printf("EXTRA(0x%08x)", f);
+	}
+	putchar('\n');
+}
+
+void show_strm_flags(unsigned int f)
+{
+	printf("strm->flags = ");
+
+	if (!f) {
+		printf("0\n");
+		return;
+	}
+
+	SHOW_FLAG(f, SF_SRV_REUSED);
+	SHOW_FLAG(f, SF_IGNORE_PRST);
+
+	//printf("%s", f ? "" : " | ");
+	switch (f & SF_FINST_MASK) {
+	case SF_FINST_R: f &= ~SF_FINST_MASK ; printf("SF_FINST_R%s", f ? " | " : ""); break;
+	case SF_FINST_C: f &= ~SF_FINST_MASK ; printf("SF_FINST_C%s", f ? " | " : ""); break;
+	case SF_FINST_H: f &= ~SF_FINST_MASK ; printf("SF_FINST_H%s", f ? " | " : ""); break;
+	case SF_FINST_D: f &= ~SF_FINST_MASK ; printf("SF_FINST_D%s", f ? " | " : ""); break;
+	case SF_FINST_L: f &= ~SF_FINST_MASK ; printf("SF_FINST_L%s", f ? " | " : ""); break;
+	case SF_FINST_Q: f &= ~SF_FINST_MASK ; printf("SF_FINST_Q%s", f ? " | " : ""); break;
+	case SF_FINST_T: f &= ~SF_FINST_MASK ; printf("SF_FINST_T%s", f ? " | " : ""); break;
+	}
+
+	switch (f & SF_ERR_MASK) {
+	case SF_ERR_LOCAL:    f &= ~SF_ERR_MASK ; printf("SF_ERR_LOCAL%s",    f ? " | " : ""); break;
+	case SF_ERR_CLITO:    f &= ~SF_ERR_MASK ; printf("SF_ERR_CLITO%s",    f ? " | " : ""); break;
+	case SF_ERR_CLICL:    f &= ~SF_ERR_MASK ; printf("SF_ERR_CLICL%s",    f ? " | " : ""); break;
+	case SF_ERR_SRVTO:    f &= ~SF_ERR_MASK ; printf("SF_ERR_SRVTO%s",    f ? " | " : ""); break;
+	case SF_ERR_SRVCL:    f &= ~SF_ERR_MASK ; printf("SF_ERR_SRVCL%s",    f ? " | " : ""); break;
+	case SF_ERR_PRXCOND:  f &= ~SF_ERR_MASK ; printf("SF_ERR_PRXCOND%s",  f ? " | " : ""); break;
+	case SF_ERR_RESOURCE: f &= ~SF_ERR_MASK ; printf("SF_ERR_RESOURCE%s", f ? " | " : ""); break;
+	case SF_ERR_INTERNAL: f &= ~SF_ERR_MASK ; printf("SF_ERR_INTERNAL%s", f ? " | " : ""); break;
+	case SF_ERR_DOWN:     f &= ~SF_ERR_MASK ; printf("SF_ERR_DOWN%s",     f ? " | " : ""); break;
+	case SF_ERR_KILLED:   f &= ~SF_ERR_MASK ; printf("SF_ERR_KILLED%s",   f ? " | " : ""); break;
+	case SF_ERR_UP:       f &= ~SF_ERR_MASK ; printf("SF_ERR_UP%s",       f ? " | " : ""); break;
+	case SF_ERR_CHK_PORT: f &= ~SF_ERR_MASK ; printf("SF_ERR_CHK_PORT%s",       f ? " | " : ""); break;
+	}
+
+	SHOW_FLAG(f, SF_HTX);
+	SHOW_FLAG(f, SF_REDIRECTABLE);
+	SHOW_FLAG(f, SF_IGNORE);
+	SHOW_FLAG(f, SF_REDISP);
+	SHOW_FLAG(f, SF_CURR_SESS);
+	SHOW_FLAG(f, SF_MONITOR);
+	SHOW_FLAG(f, SF_FORCE_PRST);
+	SHOW_FLAG(f, SF_BE_ASSIGNED);
+	SHOW_FLAG(f, SF_ADDR_SET);
+	SHOW_FLAG(f, SF_ASSIGNED);
+	SHOW_FLAG(f, SF_DIRECT);
+
+	if (f) {
+		printf("EXTRA(0x%08x)", f);
+	}
+	putchar('\n');
+}
+
+void usage_exit(const char *name)
+{
+	fprintf(stderr, "Usage: %s [ana|chn|conn|cs|si|sierr|strm|task|txn]* { [+-][0x]value* | - }\n", name);
+	exit(1);
+}
+
+int main(int argc, char **argv)
+{
+	unsigned int flags;
+	unsigned int show_as = 0;
+	unsigned int f;
+	const char *name = argv[0];
+	char line[20];
+	char *value;
+	int multi = 0;
+	int use_stdin = 0;
+	char *err;
+
+	while (argc > 0) {
+		argv++; argc--;
+		if (argc < 1)
+			usage_exit(name);
+
+		f = get_show_as(argv[0]);
+		if (!f)
+			break;
+		show_as |= f;
+	}
+
+	if (!show_as)
+		show_as = ~0U;
+
+	if (argc > 1)
+		multi = 1;
+
+	if (strcmp(argv[0], "-") == 0)
+		use_stdin = 1;
+
+	while (argc > 0) {
+		if (use_stdin) {
+			value = fgets(line, sizeof(line), stdin);
+			if (!value)
+				break;
+
+			/* skip common leading delimiters that slip from copy-paste */
+			while (*value == ' ' || *value == '\t' || *value == ':' || *value == '=')
+				value++;
+
+			/* stop at the end of the number and trim any C suffix like "UL" */
+			err = value;
+			while (*err == '-' || *err == '+' ||
+			       (isalnum((unsigned char)*err) && toupper((unsigned char)*err) != 'U' && toupper((unsigned char)*err) != 'L'))
+				err++;
+			if (err)
+				*err = 0;
+		} else {
+			value = argv[0];
+			argv++; argc--;
+		}
+
+		flags = strtoul(value, &err, 0);
+		if (!*value || *err) {
+			fprintf(stderr, "Unparsable value: <%s>\n", value);
+			usage_exit(name);
+		}
+
+		if (multi || use_stdin)
+			printf("### 0x%08x:\n", flags);
+
+		if (show_as & SHOW_AS_ANA)   show_chn_ana(flags);
+		if (show_as & SHOW_AS_CHN)   show_chn_flags(flags);
+		if (show_as & SHOW_AS_CONN)  show_conn_flags(flags);
+		if (show_as & SHOW_AS_CS)    show_cs_flags(flags);
+		if (show_as & SHOW_AS_SI)    show_si_flags(flags);
+		if (show_as & SHOW_AS_SIET)  show_si_et(flags);
+		if (show_as & SHOW_AS_STRM)  show_strm_flags(flags);
+		if (show_as & SHOW_AS_TASK)  show_task_state(flags);
+		if (show_as & SHOW_AS_TXN)   show_txn_flags(flags);
+	}
+	return 0;
+}
diff --git a/dev/flags/show-fd-to-flags.sh b/dev/flags/show-fd-to-flags.sh
new file mode 100755
index 0000000..29757c3
--- /dev/null
+++ b/dev/flags/show-fd-to-flags.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+awk '{print $12}' | grep cflg= | sort | uniq -c | sort -nr | while read a b; do c=${b##*=}; d=$(${0%/*}/flags conn $c);d=${d##*= }; printf "%6d %s    %s\n" $a "$b" "$d";done
diff --git a/dev/hpack/Makefile b/dev/hpack/Makefile
new file mode 100644
index 0000000..1c6448b
--- /dev/null
+++ b/dev/hpack/Makefile
@@ -0,0 +1,9 @@
+CFLAGS = -O2 -Wall -g -I../../include -fwrapv -fno-strict-aliasing
+OBJS = gen-rht gen-enc decode
+
+all: $(OBJS)
+
+%: %.c
+
+clean:
+	-rm -vf $(OBJS) *.o *.a *~
diff --git a/dev/hpack/decode.c b/dev/hpack/decode.c
new file mode 100644
index 0000000..ae82512
--- /dev/null
+++ b/dev/hpack/decode.c
@@ -0,0 +1,215 @@
+/*
+ * HPACK stream decoder. Takes a series of hex codes on stdin using one line
+ * per HEADERS frame. Spaces, tabs, CR, '-' and ',' are silently skipped.
+ * e.g. :
+ *   echo 82864188f439ce75c875fa5784 | dev/hpack/decode
+ *
+ * The DHT size may optionally be changed in argv[1].
+ *
+ * Build like this :
+ *    gcc -I../../include -O0 -g -fno-strict-aliasing -fwrapv \
+ *        -o decode decode.c
+ */
+
+#define HPACK_STANDALONE
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <haproxy/chunk.h>
+#include <haproxy/hpack-dec.h>
+
+#define MAX_RQ_SIZE 65536
+#define MAX_HDR_NUM 1000
+
+char hex[MAX_RQ_SIZE*3+3]; // enough for "[ XX]* <CR> <LF> \0"
+uint8_t buf[MAX_RQ_SIZE];
+
+char trash_buf[MAX_RQ_SIZE];
+char tmp_buf[MAX_RQ_SIZE];
+
+struct buffer trash = { .area = trash_buf, .data = 0, .size = sizeof(trash_buf) };
+struct buffer tmp   = { .area = tmp_buf,   .data = 0, .size = sizeof(tmp_buf)   };
+
+/* displays a <len> long memory block at <buf>, assuming first byte of <buf>
+ * has address <baseaddr>. String <pfx> may be placed as a prefix in front of
+ * each line. It may be NULL if unused. The output is emitted to file <out>.
+ */
+void debug_hexdump(FILE *out, const char *pfx, const char *buf,
+                   unsigned int baseaddr, int len)
+{
+	unsigned int i;
+	int b, j;
+
+	for (i = 0; i < (len + (baseaddr & 15)); i += 16) {
+		b = i - (baseaddr & 15);
+		fprintf(out, "%s%08x: ", pfx ? pfx : "", i + (baseaddr & ~15));
+		for (j = 0; j < 8; j++) {
+			if (b + j >= 0 && b + j < len)
+				fprintf(out, "%02x ", (unsigned char)buf[b + j]);
+			else
+				fprintf(out, "   ");
+		}
+
+		if (b + j >= 0 && b + j < len)
+			fputc('-', out);
+		else
+			fputc(' ', out);
+
+		for (j = 8; j < 16; j++) {
+			if (b + j >= 0 && b + j < len)
+				fprintf(out, " %02x", (unsigned char)buf[b + j]);
+			else
+				fprintf(out, "   ");
+		}
+
+		fprintf(out, "   ");
+		for (j = 0; j < 16; j++) {
+			if (b + j >= 0 && b + j < len) {
+				if (isprint((unsigned char)buf[b + j]))
+					fputc((unsigned char)buf[b + j], out);
+				else
+					fputc('.', out);
+			}
+			else
+				fputc(' ', out);
+		}
+		fputc('\n', out);
+	}
+}
+
+/* enable DEBUG_HPACK to show each individual hpack code */
+#define DEBUG_HPACK
+#include "../src/hpack-huff.c"
+#include "../src/hpack-tbl.c"
+#include "../src/hpack-dec.c"
+
+/* display the message and exit with the code */
+__attribute__((noreturn)) void die(int code, const char *format, ...)
+{
+	va_list args;
+
+	if (format) {
+		va_start(args, format);
+		vfprintf(stderr, format, args);
+		va_end(args);
+	}
+	exit(code);
+}
+
+/* reads <hex> and stops at the first LF, '#' or \0. Converts from hex to
+ * binary, ignoring spaces, tabs, CR, "-" and ','. The output is sent into
+ * <bin> for no more than <size> bytes. The number of bytes placed there is
+ * returned, or a negative value in case of parsing error.
+ */
+int hex2bin(const char *hex, uint8_t *bin, int size)
+{
+	int a, b, c;
+	uint8_t code;
+	int len = 0;
+
+	a = b = -1;
+
+	for (; *hex; hex++) {
+		c = *hex;
+		if (c == ' ' || c == '\t' || c == '\r' ||
+		    c == '-' || c == ',')
+			continue;
+
+		if (c == '\n' || c == '#')
+			break;
+
+		if (c >= '0' && c <= '9')
+			c -= '0';
+		else if (c >= 'a' && c <= 'f')
+			c -= 'a' - 10;
+		else if (c >= 'A' && c <= 'F')
+			c -= 'A' - 10;
+		else
+			return -1;
+
+		if (a == -1)
+			a = c;
+		else
+			b = c;
+
+		if (b == -1)
+			continue;
+
+		code = (a << 4) | b;
+		a = b = -1;
+		if (len >= size)
+			return -2;
+
+		bin[len] = code;
+		len++;
+	}
+	if (a >= 0 || b >= 0)
+		return -3;
+	return len;
+}
+
+int main(int argc, char **argv)
+{
+	struct hpack_dht *dht;
+	struct http_hdr list[MAX_HDR_NUM];
+	struct pool_head pool;
+	int outlen;
+	int dht_size = 4096;
+	int len, idx;
+	int line;
+
+	/* first arg: dht size */
+	if (argc > 1) {
+		dht_size = atoi(argv[1]);
+		argv++;	argc--;
+	}
+
+	pool.size = dht_size;
+	pool_head_hpack_tbl = &pool;
+	dht = hpack_dht_alloc();
+	if (!dht) {
+		die(1, "cannot initialize dht\n");
+		return 1;
+	}
+
+	for (line = 1; fgets(hex, sizeof(hex), stdin); line++) {
+		len = hex2bin(hex, buf, sizeof(buf));
+		if (len <= 0)
+			continue;
+		printf("###### line %d : frame len=%d #######\n", line, len);
+		debug_hexdump(stdout, "   ", (const char *)buf, 0, len);
+
+		outlen = hpack_decode_frame(dht, buf, len, list,
+					    sizeof(list)/sizeof(list[0]), &tmp);
+		if (outlen <= 0) {
+			printf("   HPACK decoding failed: %d\n", outlen);
+			continue;
+		}
+
+		printf("<<< Found %d headers :\n", outlen);
+		for (idx = 0; idx < outlen - 1; idx++) {
+			//printf("      \e[1;34m%s\e[0m: ",
+			//       list[idx].n.ptr ? istpad(trash.str, list[idx].n).ptr : h2_phdr_to_str(list[idx].n.len));
+
+			//printf("\e[1;35m%s\e[0m\n", istpad(trash.str, list[idx].v).ptr);
+
+			printf("      %s: ", list[idx].n.ptr ?
+			       istpad(trash.area, list[idx].n).ptr :
+			       h2_phdr_to_str(list[idx].n.len));
+
+			printf("%s [n=(%p,%d) v=(%p,%d)]\n",
+			       istpad(trash.area, list[idx].v).ptr,
+			       list[idx].n.ptr, (int)list[idx].n.len, list[idx].v.ptr, (int)list[idx].v.len);
+		}
+		puts(">>>");
+#ifdef DEBUG_HPACK
+		printf("<<=== DHT dump [ptr=%p]:\n", dht);
+		hpack_dht_dump(stdout, dht);
+		puts("===>>");
+#endif
+	}
+	return 0;
+}
diff --git a/dev/hpack/gen-enc.c b/dev/hpack/gen-enc.c
new file mode 100644
index 0000000..3fc5ef9
--- /dev/null
+++ b/dev/hpack/gen-enc.c
@@ -0,0 +1,205 @@
+/*
+ * HPACK encoding table generator. It produces a stream of
+ * <len><idx><name> and a table pointing to the first <len> of each series.
+ * The end of the stream is marked by <len>=0. In parallel, a length-indexed
+ * table is built to access the first entry of each length.
+ *
+ * Build like this :
+ *    gcc -I../../include -o gen-enc gen-enc.c
+ */
+#define HPACK_STANDALONE
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <import/ist.h>
+#include <haproxy/hpack-tbl-t.h>
+#include "../../src/hpack-tbl.c"
+
+struct idxhdr {
+	const char *ptr;
+	int len;
+	int idx;
+};
+
+struct idxhdr idxhdr[HPACK_SHT_SIZE];
+static int positions[32];
+static char known_hdr[1024];
+
+/* preferred ordering of headers of similar size. Those not mentioned will be
+ * less prioritized.
+ */
+const struct {
+	const char *name;
+	const int rank;
+} ranks[] = {
+	{ .name = "age", .rank = 1 },
+	{ .name = "via", .rank = 2 },
+
+	{ .name = "date", .rank = 1 },
+	{ .name = "host", .rank = 2 },
+
+	{ .name = "accept", .rank = 1 },
+	{ .name = "server", .rank = 2 },
+	{ .name = "cookie", .rank = 3 },
+
+	{ .name = "referer", .rank = 1 },
+	{ .name = "expires", .rank = 2 },
+
+	{ .name = "location", .rank = 1 },
+
+	{ .name = "user-agent", .rank = 1 },
+	{ .name = "set-cookie", .rank = 2 },
+
+	{ .name = "content-type", .rank = 1 },
+
+	{ .name = "cache-control", .rank = 1 },
+	{ .name = "last-modified", .rank = 2 },
+	{ .name = "accept-ranges", .rank = 3 },
+	{ .name = "if-none-match", .rank = 4 },
+
+	{ .name = "content-length", .rank = 1 },
+
+	{ .name = "accept-encoding", .rank = 1 },
+	{ .name = "accept-language", .rank = 2 },
+
+	{ .name = "content-encoding", .rank = 1 },
+
+	{ .name = "transfer-encoding", .rank = 1 },
+	{ .name = "if-modified-since", .rank = 2 },
+
+	{ .name = "content-disposition", .rank = 1 },
+};
+
+/* returns the rank of header <name> or 255 if not found */
+int get_hdr_rank(const char *name)
+{
+	int i;
+
+	for (i = 0; i < sizeof(ranks) / sizeof(ranks[0]); i++) {
+		if (strcmp(ranks[i].name, name) == 0)
+			return ranks[i].rank;
+	}
+	return 255;
+}
+
+/* sorts first on the length, second on the name, and third on the idx, so that
+ * headers which appear with multiple occurrences are always met first.
+ */
+int cmp_idx(const void *l, const void *r)
+{
+	const struct idxhdr *a = l, *b = r;
+	int ranka, rankb;
+	int ret;
+
+	if (a->len < b->len)
+		return -1;
+	else if (a->len > b->len)
+		return 1;
+
+	ranka = get_hdr_rank(a->ptr);
+	rankb = get_hdr_rank(b->ptr);
+
+	if (ranka < rankb)
+		return -1;
+	else if (ranka > rankb)
+		return 1;
+
+	/* same rank, check for duplicates and use index */
+	ret = strcmp(a->ptr, b->ptr);
+	if (ret != 0)
+		return ret;
+
+	if (a->idx < b->idx)
+		return -1;
+	else if (a->idx > b->idx)
+		return 1;
+	else
+		return 0;
+}
+
+int main(int argc, char **argv)
+{
+	int pos;
+	int prev;
+	int len;
+	int i;
+
+	for (len = 0; len < 32; len++)
+		positions[len] = -1;
+
+	for (i = 0; i < HPACK_SHT_SIZE; i++) {
+		idxhdr[i].ptr = hpack_sht[i].n.ptr;
+		idxhdr[i].len = hpack_sht[i].n.len;
+		idxhdr[i].idx = i;
+	}
+
+	/* sorts all header names by length first, then by name, and finally by
+	 * idx so that we meet smaller headers first, that within a length they
+	 * appear in frequency order, and that multiple occurrences appear with
+	 * the smallest index first.
+	 */
+	qsort(&idxhdr[1], HPACK_SHT_SIZE - 1, sizeof(idxhdr[0]), cmp_idx);
+
+	pos = 0;
+	prev = -1;
+	for (i = 1; i < HPACK_SHT_SIZE; i++) {
+		len = idxhdr[i].len;
+		if (len > 31) {
+			//printf("skipping %s (len=%d)\n", idxhdr[i].ptr, idxhdr[i].len);
+			continue;
+		}
+
+		/* first occurrence of this length? */
+		if (positions[len] == -1)
+			positions[len] = pos;
+		else if (prev >= 0 &&
+			 memcmp(&known_hdr[prev] + 2, idxhdr[i].ptr, len) == 0) {
+			/* duplicate header field */
+			continue;
+		}
+
+		/* store <len> <idx> <name> in the output array */
+
+		if (pos + 1 + len + 2 >= sizeof(known_hdr))
+			abort();
+
+		prev = pos;
+		known_hdr[pos++] = len;
+		known_hdr[pos++] = idxhdr[i].idx;
+		memcpy(&known_hdr[pos], idxhdr[i].ptr, len);
+		pos += len;
+		//printf("%d %d %s\n", len, idxhdr[i].idx, idxhdr[i].ptr);
+	}
+
+	if (pos + 1 >= sizeof(known_hdr))
+		abort();
+	known_hdr[pos++] = 0; // size zero ends the stream
+
+	printf("const char hpack_enc_stream[%d] = {\n", pos);
+	for (i = 0; i < pos; i++) {
+		if ((i & 7) == 0)
+			printf("\t /* % 4d: */", i);
+
+		printf(" 0x%02x,", known_hdr[i]);
+
+		if ((i & 7) == 7 || (i == pos - 1))
+			putchar('\n');
+	}
+	printf("};\n\n");
+
+	printf("const signed short hpack_pos_len[32] = {\n");
+	for (i = 0; i < 32; i++) {
+		if ((i & 7) == 0)
+			printf("\t /* % 4d: */", i);
+
+		printf(" % 4d,", positions[i]);
+
+		if ((i & 7) == 7 || (i == pos - 1))
+			putchar('\n');
+	}
+	printf("};\n\n");
+	return 0;
+}
diff --git a/dev/hpack/gen-rht.c b/dev/hpack/gen-rht.c
new file mode 100644
index 0000000..4260ffb
--- /dev/null
+++ b/dev/hpack/gen-rht.c
@@ -0,0 +1,369 @@
+/* Reverse Huffman table generator for HPACK decoder - 2017-05-19 Willy Tarreau
+ *
+ * rht_bit31_24[256]   is indexed on bits 31..24 when < 0xfe
+ * rht_bit24_17[256]   is indexed on bits 24..17 when 31..24 >= 0xfe
+ * rht_bit15_11_fe[32] is indexed on bits 15..11 when 24..17 == 0xfe
+ * rht_bit15_8[256]    is indexed on bits 15..8 when 24..17 == 0xff
+ * rht_bit11_4[256]    is indexed on bits 11..4 when 15..8 == 0xff
+ * when 11..4 == 0xff, 3..2 provide the following mapping :
+ *   00 => 0x0a, 01 => 0x0d, 10 => 0x16, 11 => EOS
+ */
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* from RFC7541 Appendix B */
+static const struct huff {
+	uint32_t c; /* code point */
+	int b;      /* bits */
+} ht[257] = {
+	[0] = { .c = 0x00001ff8, .b = 13 },
+	[1] = { .c = 0x007fffd8, .b = 23 },
+	[2] = { .c = 0x0fffffe2, .b = 28 },
+	[3] = { .c = 0x0fffffe3, .b = 28 },
+	[4] = { .c = 0x0fffffe4, .b = 28 },
+	[5] = { .c = 0x0fffffe5, .b = 28 },
+	[6] = { .c = 0x0fffffe6, .b = 28 },
+	[7] = { .c = 0x0fffffe7, .b = 28 },
+	[8] = { .c = 0x0fffffe8, .b = 28 },
+	[9] = { .c = 0x00ffffea, .b = 24 },
+	[10] = { .c = 0x3ffffffc, .b = 30 },
+	[11] = { .c = 0x0fffffe9, .b = 28 },
+	[12] = { .c = 0x0fffffea, .b = 28 },
+	[13] = { .c = 0x3ffffffd, .b = 30 },
+	[14] = { .c = 0x0fffffeb, .b = 28 },
+	[15] = { .c = 0x0fffffec, .b = 28 },
+	[16] = { .c = 0x0fffffed, .b = 28 },
+	[17] = { .c = 0x0fffffee, .b = 28 },
+	[18] = { .c = 0x0fffffef, .b = 28 },
+	[19] = { .c = 0x0ffffff0, .b = 28 },
+	[20] = { .c = 0x0ffffff1, .b = 28 },
+	[21] = { .c = 0x0ffffff2, .b = 28 },
+	[22] = { .c = 0x3ffffffe, .b = 30 },
+	[23] = { .c = 0x0ffffff3, .b = 28 },
+	[24] = { .c = 0x0ffffff4, .b = 28 },
+	[25] = { .c = 0x0ffffff5, .b = 28 },
+	[26] = { .c = 0x0ffffff6, .b = 28 },
+	[27] = { .c = 0x0ffffff7, .b = 28 },
+	[28] = { .c = 0x0ffffff8, .b = 28 },
+	[29] = { .c = 0x0ffffff9, .b = 28 },
+	[30] = { .c = 0x0ffffffa, .b = 28 },
+	[31] = { .c = 0x0ffffffb, .b = 28 },
+	[32] = { .c = 0x00000014, .b =  6 },
+	[33] = { .c = 0x000003f8, .b = 10 },
+	[34] = { .c = 0x000003f9, .b = 10 },
+	[35] = { .c = 0x00000ffa, .b = 12 },
+	[36] = { .c = 0x00001ff9, .b = 13 },
+	[37] = { .c = 0x00000015, .b =  6 },
+	[38] = { .c = 0x000000f8, .b =  8 },
+	[39] = { .c = 0x000007fa, .b = 11 },
+	[40] = { .c = 0x000003fa, .b = 10 },
+	[41] = { .c = 0x000003fb, .b = 10 },
+	[42] = { .c = 0x000000f9, .b =  8 },
+	[43] = { .c = 0x000007fb, .b = 11 },
+	[44] = { .c = 0x000000fa, .b =  8 },
+	[45] = { .c = 0x00000016, .b =  6 },
+	[46] = { .c = 0x00000017, .b =  6 },
+	[47] = { .c = 0x00000018, .b =  6 },
+	[48] = { .c = 0x00000000, .b =  5 },
+	[49] = { .c = 0x00000001, .b =  5 },
+	[50] = { .c = 0x00000002, .b =  5 },
+	[51] = { .c = 0x00000019, .b =  6 },
+	[52] = { .c = 0x0000001a, .b =  6 },
+	[53] = { .c = 0x0000001b, .b =  6 },
+	[54] = { .c = 0x0000001c, .b =  6 },
+	[55] = { .c = 0x0000001d, .b =  6 },
+	[56] = { .c = 0x0000001e, .b =  6 },
+	[57] = { .c = 0x0000001f, .b =  6 },
+	[58] = { .c = 0x0000005c, .b =  7 },
+	[59] = { .c = 0x000000fb, .b =  8 },
+	[60] = { .c = 0x00007ffc, .b = 15 },
+	[61] = { .c = 0x00000020, .b =  6 },
+	[62] = { .c = 0x00000ffb, .b = 12 },
+	[63] = { .c = 0x000003fc, .b = 10 },
+	[64] = { .c = 0x00001ffa, .b = 13 },
+	[65] = { .c = 0x00000021, .b =  6 },
+	[66] = { .c = 0x0000005d, .b =  7 },
+	[67] = { .c = 0x0000005e, .b =  7 },
+	[68] = { .c = 0x0000005f, .b =  7 },
+	[69] = { .c = 0x00000060, .b =  7 },
+	[70] = { .c = 0x00000061, .b =  7 },
+	[71] = { .c = 0x00000062, .b =  7 },
+	[72] = { .c = 0x00000063, .b =  7 },
+	[73] = { .c = 0x00000064, .b =  7 },
+	[74] = { .c = 0x00000065, .b =  7 },
+	[75] = { .c = 0x00000066, .b =  7 },
+	[76] = { .c = 0x00000067, .b =  7 },
+	[77] = { .c = 0x00000068, .b =  7 },
+	[78] = { .c = 0x00000069, .b =  7 },
+	[79] = { .c = 0x0000006a, .b =  7 },
+	[80] = { .c = 0x0000006b, .b =  7 },
+	[81] = { .c = 0x0000006c, .b =  7 },
+	[82] = { .c = 0x0000006d, .b =  7 },
+	[83] = { .c = 0x0000006e, .b =  7 },
+	[84] = { .c = 0x0000006f, .b =  7 },
+	[85] = { .c = 0x00000070, .b =  7 },
+	[86] = { .c = 0x00000071, .b =  7 },
+	[87] = { .c = 0x00000072, .b =  7 },
+	[88] = { .c = 0x000000fc, .b =  8 },
+	[89] = { .c = 0x00000073, .b =  7 },
+	[90] = { .c = 0x000000fd, .b =  8 },
+	[91] = { .c = 0x00001ffb, .b = 13 },
+	[92] = { .c = 0x0007fff0, .b = 19 },
+	[93] = { .c = 0x00001ffc, .b = 13 },
+	[94] = { .c = 0x00003ffc, .b = 14 },
+	[95] = { .c = 0x00000022, .b =  6 },
+	[96] = { .c = 0x00007ffd, .b = 15 },
+	[97] = { .c = 0x00000003, .b =  5 },
+	[98] = { .c = 0x00000023, .b =  6 },
+	[99] = { .c = 0x00000004, .b =  5 },
+	[100] = { .c = 0x00000024, .b =  6 },
+	[101] = { .c = 0x00000005, .b =  5 },
+	[102] = { .c = 0x00000025, .b =  6 },
+	[103] = { .c = 0x00000026, .b =  6 },
+	[104] = { .c = 0x00000027, .b =  6 },
+	[105] = { .c = 0x00000006, .b =  5 },
+	[106] = { .c = 0x00000074, .b =  7 },
+	[107] = { .c = 0x00000075, .b =  7 },
+	[108] = { .c = 0x00000028, .b =  6 },
+	[109] = { .c = 0x00000029, .b =  6 },
+	[110] = { .c = 0x0000002a, .b =  6 },
+	[111] = { .c = 0x00000007, .b =  5 },
+	[112] = { .c = 0x0000002b, .b =  6 },
+	[113] = { .c = 0x00000076, .b =  7 },
+	[114] = { .c = 0x0000002c, .b =  6 },
+	[115] = { .c = 0x00000008, .b =  5 },
+	[116] = { .c = 0x00000009, .b =  5 },
+	[117] = { .c = 0x0000002d, .b =  6 },
+	[118] = { .c = 0x00000077, .b =  7 },
+	[119] = { .c = 0x00000078, .b =  7 },
+	[120] = { .c = 0x00000079, .b =  7 },
+	[121] = { .c = 0x0000007a, .b =  7 },
+	[122] = { .c = 0x0000007b, .b =  7 },
+	[123] = { .c = 0x00007ffe, .b = 15 },
+	[124] = { .c = 0x000007fc, .b = 11 },
+	[125] = { .c = 0x00003ffd, .b = 14 },
+	[126] = { .c = 0x00001ffd, .b = 13 },
+	[127] = { .c = 0x0ffffffc, .b = 28 },
+	[128] = { .c = 0x000fffe6, .b = 20 },
+	[129] = { .c = 0x003fffd2, .b = 22 },
+	[130] = { .c = 0x000fffe7, .b = 20 },
+	[131] = { .c = 0x000fffe8, .b = 20 },
+	[132] = { .c = 0x003fffd3, .b = 22 },
+	[133] = { .c = 0x003fffd4, .b = 22 },
+	[134] = { .c = 0x003fffd5, .b = 22 },
+	[135] = { .c = 0x007fffd9, .b = 23 },
+	[136] = { .c = 0x003fffd6, .b = 22 },
+	[137] = { .c = 0x007fffda, .b = 23 },
+	[138] = { .c = 0x007fffdb, .b = 23 },
+	[139] = { .c = 0x007fffdc, .b = 23 },
+	[140] = { .c = 0x007fffdd, .b = 23 },
+	[141] = { .c = 0x007fffde, .b = 23 },
+	[142] = { .c = 0x00ffffeb, .b = 24 },
+	[143] = { .c = 0x007fffdf, .b = 23 },
+	[144] = { .c = 0x00ffffec, .b = 24 },
+	[145] = { .c = 0x00ffffed, .b = 24 },
+	[146] = { .c = 0x003fffd7, .b = 22 },
+	[147] = { .c = 0x007fffe0, .b = 23 },
+	[148] = { .c = 0x00ffffee, .b = 24 },
+	[149] = { .c = 0x007fffe1, .b = 23 },
+	[150] = { .c = 0x007fffe2, .b = 23 },
+	[151] = { .c = 0x007fffe3, .b = 23 },
+	[152] = { .c = 0x007fffe4, .b = 23 },
+	[153] = { .c = 0x001fffdc, .b = 21 },
+	[154] = { .c = 0x003fffd8, .b = 22 },
+	[155] = { .c = 0x007fffe5, .b = 23 },
+	[156] = { .c = 0x003fffd9, .b = 22 },
+	[157] = { .c = 0x007fffe6, .b = 23 },
+	[158] = { .c = 0x007fffe7, .b = 23 },
+	[159] = { .c = 0x00ffffef, .b = 24 },
+	[160] = { .c = 0x003fffda, .b = 22 },
+	[161] = { .c = 0x001fffdd, .b = 21 },
+	[162] = { .c = 0x000fffe9, .b = 20 },
+	[163] = { .c = 0x003fffdb, .b = 22 },
+	[164] = { .c = 0x003fffdc, .b = 22 },
+	[165] = { .c = 0x007fffe8, .b = 23 },
+	[166] = { .c = 0x007fffe9, .b = 23 },
+	[167] = { .c = 0x001fffde, .b = 21 },
+	[168] = { .c = 0x007fffea, .b = 23 },
+	[169] = { .c = 0x003fffdd, .b = 22 },
+	[170] = { .c = 0x003fffde, .b = 22 },
+	[171] = { .c = 0x00fffff0, .b = 24 },
+	[172] = { .c = 0x001fffdf, .b = 21 },
+	[173] = { .c = 0x003fffdf, .b = 22 },
+	[174] = { .c = 0x007fffeb, .b = 23 },
+	[175] = { .c = 0x007fffec, .b = 23 },
+	[176] = { .c = 0x001fffe0, .b = 21 },
+	[177] = { .c = 0x001fffe1, .b = 21 },
+	[178] = { .c = 0x003fffe0, .b = 22 },
+	[179] = { .c = 0x001fffe2, .b = 21 },
+	[180] = { .c = 0x007fffed, .b = 23 },
+	[181] = { .c = 0x003fffe1, .b = 22 },
+	[182] = { .c = 0x007fffee, .b = 23 },
+	[183] = { .c = 0x007fffef, .b = 23 },
+	[184] = { .c = 0x000fffea, .b = 20 },
+	[185] = { .c = 0x003fffe2, .b = 22 },
+	[186] = { .c = 0x003fffe3, .b = 22 },
+	[187] = { .c = 0x003fffe4, .b = 22 },
+	[188] = { .c = 0x007ffff0, .b = 23 },
+	[189] = { .c = 0x003fffe5, .b = 22 },
+	[190] = { .c = 0x003fffe6, .b = 22 },
+	[191] = { .c = 0x007ffff1, .b = 23 },
+	[192] = { .c = 0x03ffffe0, .b = 26 },
+	[193] = { .c = 0x03ffffe1, .b = 26 },
+	[194] = { .c = 0x000fffeb, .b = 20 },
+	[195] = { .c = 0x0007fff1, .b = 19 },
+	[196] = { .c = 0x003fffe7, .b = 22 },
+	[197] = { .c = 0x007ffff2, .b = 23 },
+	[198] = { .c = 0x003fffe8, .b = 22 },
+	[199] = { .c = 0x01ffffec, .b = 25 },
+	[200] = { .c = 0x03ffffe2, .b = 26 },
+	[201] = { .c = 0x03ffffe3, .b = 26 },
+	[202] = { .c = 0x03ffffe4, .b = 26 },
+	[203] = { .c = 0x07ffffde, .b = 27 },
+	[204] = { .c = 0x07ffffdf, .b = 27 },
+	[205] = { .c = 0x03ffffe5, .b = 26 },
+	[206] = { .c = 0x00fffff1, .b = 24 },
+	[207] = { .c = 0x01ffffed, .b = 25 },
+	[208] = { .c = 0x0007fff2, .b = 19 },
+	[209] = { .c = 0x001fffe3, .b = 21 },
+	[210] = { .c = 0x03ffffe6, .b = 26 },
+	[211] = { .c = 0x07ffffe0, .b = 27 },
+	[212] = { .c = 0x07ffffe1, .b = 27 },
+	[213] = { .c = 0x03ffffe7, .b = 26 },
+	[214] = { .c = 0x07ffffe2, .b = 27 },
+	[215] = { .c = 0x00fffff2, .b = 24 },
+	[216] = { .c = 0x001fffe4, .b = 21 },
+	[217] = { .c = 0x001fffe5, .b = 21 },
+	[218] = { .c = 0x03ffffe8, .b = 26 },
+	[219] = { .c = 0x03ffffe9, .b = 26 },
+	[220] = { .c = 0x0ffffffd, .b = 28 },
+	[221] = { .c = 0x07ffffe3, .b = 27 },
+	[222] = { .c = 0x07ffffe4, .b = 27 },
+	[223] = { .c = 0x07ffffe5, .b = 27 },
+	[224] = { .c = 0x000fffec, .b = 20 },
+	[225] = { .c = 0x00fffff3, .b = 24 },
+	[226] = { .c = 0x000fffed, .b = 20 },
+	[227] = { .c = 0x001fffe6, .b = 21 },
+	[228] = { .c = 0x003fffe9, .b = 22 },
+	[229] = { .c = 0x001fffe7, .b = 21 },
+	[230] = { .c = 0x001fffe8, .b = 21 },
+	[231] = { .c = 0x007ffff3, .b = 23 },
+	[232] = { .c = 0x003fffea, .b = 22 },
+	[233] = { .c = 0x003fffeb, .b = 22 },
+	[234] = { .c = 0x01ffffee, .b = 25 },
+	[235] = { .c = 0x01ffffef, .b = 25 },
+	[236] = { .c = 0x00fffff4, .b = 24 },
+	[237] = { .c = 0x00fffff5, .b = 24 },
+	[238] = { .c = 0x03ffffea, .b = 26 },
+	[239] = { .c = 0x007ffff4, .b = 23 },
+	[240] = { .c = 0x03ffffeb, .b = 26 },
+	[241] = { .c = 0x07ffffe6, .b = 27 },
+	[242] = { .c = 0x03ffffec, .b = 26 },
+	[243] = { .c = 0x03ffffed, .b = 26 },
+	[244] = { .c = 0x07ffffe7, .b = 27 },
+	[245] = { .c = 0x07ffffe8, .b = 27 },
+	[246] = { .c = 0x07ffffe9, .b = 27 },
+	[247] = { .c = 0x07ffffea, .b = 27 },
+	[248] = { .c = 0x07ffffeb, .b = 27 },
+	[249] = { .c = 0x0ffffffe, .b = 28 },
+	[250] = { .c = 0x07ffffec, .b = 27 },
+	[251] = { .c = 0x07ffffed, .b = 27 },
+	[252] = { .c = 0x07ffffee, .b = 27 },
+	[253] = { .c = 0x07ffffef, .b = 27 },
+	[254] = { .c = 0x07fffff0, .b = 27 },
+	[255] = { .c = 0x03ffffee, .b = 26 },
+	[256] = { .c = 0x3fffffff, .b = 30 }, /* EOS */
+};
+
+
+int main(int argc, char **argv)
+{
+	uint32_t c, i, j;
+
+	/* fill first byte */
+	printf("struct rht rht_bit31_24[256] = {\n");
+	for (j = 0; j < 256; j++) {
+		for (i = 0; i < sizeof(ht)/sizeof(ht[0]); i++) {
+			if (ht[i].b > 8)
+				continue;
+			c = ht[i].c << (32 - ht[i].b);
+
+			if (((c ^ (j << 24)) & -(1 << (32 - ht[i].b)) & 0xff000000) == 0) {
+				printf("\t[0x%02x] = { .c = 0x%02x, .l = %d },\n", j, i, ht[i].b);
+				break;
+			}
+		}
+	}
+	printf("};\n\n");
+
+	printf("struct rht rht_bit24_17[256] = {\n");
+	for (j = 0; j < 256; j++) {
+		for (i = 0; i < sizeof(ht)/sizeof(ht[0]); i++) {
+			if (ht[i].b <= 8 || ht[i].b > 16)
+				continue;
+			c = ht[i].c << (32 - ht[i].b);
+
+			if (((c ^ (j << 17)) & -(1 << (32 - ht[i].b)) & 0x01fe0000) == 0) {
+				printf("\t[0x%02x] = { .c = 0x%02x, .l = %d },\n", j, i, ht[i].b);
+				break;
+			}
+		}
+	}
+	printf("};\n\n");
+
+	printf("struct rht rht_bit15_11_fe[32] = {\n");
+	for (j = 0; j < 32; j++) {
+		for (i = 0; i < sizeof(ht)/sizeof(ht[0]); i++) {
+			if (ht[i].b <= 16 || ht[i].b > 21)
+				continue;
+			c = ht[i].c << (32 - ht[i].b);
+			if ((c & 0x00ff0000) != 0x00fe0000)
+				continue;
+
+			if (((c ^ (j << 11)) & -(1 << (32 - ht[i].b)) & 0x0000f800) == 0) {
+				printf("\t[0x%02x] = { .c = 0x%02x, .l = %d },\n", j, i, ht[i].b);
+				break;
+			}
+		}
+	}
+	printf("};\n\n");
+
+	printf("struct rht rht_bit15_8[256] = {\n");
+	for (j = 0; j < 256; j++) {
+		for (i = 0; i < sizeof(ht)/sizeof(ht[0]); i++) {
+			if (ht[i].b <= 16 || ht[i].b > 24)
+				continue;
+			c = ht[i].c << (32 - ht[i].b);
+			if ((c & 0x00ff0000) != 0x00ff0000)
+				continue;
+
+			if (((c ^ (j << 8)) & -(1 << (32 - ht[i].b)) & 0x0000ff00) == 0) {
+				printf("\t[0x%02x] = { .c = 0x%02x, .l = %d },\n", j, i, ht[i].b);
+				break;
+			}
+		}
+	}
+	printf("};\n\n");
+
+	printf("struct rht rht_bit11_4[256] = {\n");
+	/* fill fourth byte after 0xff 0xff 0xf6-0xff. Only 0xfffffffx are not distinguished */
+	for (j = 0; j < 256; j++) {
+		for (i = 0; i < sizeof(ht)/sizeof(ht[0]); i++) {
+			if (ht[i].b <= 24)
+				continue;
+			c = ht[i].c << (32 - ht[i].b);
+
+			if (((c ^ (j << 4)) & -(1 << (32 - ht[i].b)) & 0x00000ff0) == 0) {
+				//printf("\tj=%02x i=%02x c=%08x l=%d c/l=%08x j/l=%08x xor=%08x\n", j, i, c, ht[i].b, c & -(1 << (32 - ht[i].b)), ((j << 4) & -(1 << (32 - ht[i].b))), (c ^ (j << 4)) & -(1 << (32 - ht[i].b)));
+				printf("\t[0x%02x] = { .c = 0x%02x, .l = %d },\n", j, i, ht[i].b);
+				break;
+			}
+		}
+	}
+	printf("\t/* Note, when l==30, bits 3..2 give 00:0x0a, 01:0x0d, 10:0x16, 11:EOS */\n");
+	printf("};\n\n");
+	return 0;
+}
diff --git a/dev/plug_qdisc/README b/dev/plug_qdisc/README
new file mode 100644
index 0000000..ccc9bd0
--- /dev/null
+++ b/dev/plug_qdisc/README
@@ -0,0 +1,59 @@
+  ** Plug queueing disciplines **
+
+  The 'plug' qdisc type is not documented. It is even not supported
+  by traffic shaping tools like 'tc' from iproute2 package.
+
+  Such qdiscs have already been used by Yelp engineers but outside
+  of haproxy with libnl-utils tools (especially nl-qdisc-* tools)
+  to implement a workaround and make haproxy reloads work.
+
+  Indeed with such plug qdiscs coupled with iptables configurations
+  we are able to temporarily bufferize IP packets and to release them as
+  needed. So, they may be very useful to "synchronize" TCP sessions
+  or at higher level to put network applications in states approaching
+  the ones suspected to occur during bugs. Furthermore to be sure
+  to produce a correct bug fix, it may be useful to reproduce
+  as mush as needed such painful bugs. This is where plug qdiscs
+  may be useful.
+
+  To have an idea about how to use plug qdisc on the command line I highly recommend to
+  read Willy Tarreau blog here:
+
+  https://www.haproxy.com/blog/truly-seamless-reloads-with-haproxy-no-more-hacks/
+
+  which refers to this other one from Yelp:
+
+  https://engineeringblog.yelp.com/2015/04/true-zero-downtime-haproxy-reloads.html
+
+  The code found in plug_qdisc.c file already helped in fixing a painful bug hard to
+  fix because hard to reproduce. To use the API it exports this is quite easy:
+
+    - First your program must call plug_disc_attach() to create if not already created
+      a plug qdisc and use it (must be done during your application own already existing
+      initializations).
+      Note that this function calls plug_qdisc_release_indefinite_buffer() so that to
+      release already buffered packets before you start your application,
+
+    - then call plug_qdisc_plug_buffer() to start buffering packets incoming to your
+      plug qdisc. So they won't be delivered to your application,
+
+    - then call plug_qdisc_release_indefinite_buffer() to stop buffering the packets
+      incoming to your plug qdisc and release those already buffered.
+      So, that to be deliver them to your application.
+
+  This code is short and simple. But uses several libraries especially libnl-route module
+  part of libnl library. To compile haproxy and make it use the plug_qdisc.c code we had
+  to link it against several libnl3 library modules like that:
+
+     -lnl-genl-3 -lnl-route-3 -lnl-3 -lnl-cli-3
+
+
+  - Some references:
+    Libnl API documentation may be found here:
+    https://www.infradead.org/~tgr/libnl/doc/api/index.html
+
+    Kernel sources:
+    http://elixir.free-electrons.com/linux/latest/source/net/sched/sch_plug.c
+
+    Nice website about traffic shaping with queuing disciplines:
+    http://wiki.linuxwall.info/doku.php/en:ressources:dossiers:networking:traffic_control
diff --git a/dev/plug_qdisc/plug_qdisc.c b/dev/plug_qdisc/plug_qdisc.c
new file mode 100644
index 0000000..bc47f5d
--- /dev/null
+++ b/dev/plug_qdisc/plug_qdisc.c
@@ -0,0 +1,86 @@
+#include <inttypes.h>
+#include <netlink/cache.h>
+#include <netlink/cli/utils.h>
+#include <netlink/cli/tc.h>
+#include <netlink/cli/qdisc.h>
+#include <netlink/cli/link.h>
+#include <netlink/route/qdisc/plug.h>
+
+/*
+ * XXX Please, first note that this code is not safe. XXX
+ * It was developed fast so that to reproduce a bug.
+ * You will certainly have to adapt it to your application.
+ * But at least it gives an idea about how to programmatically use plug
+ * queueing disciplines.
+ */
+
+static struct nl_sock *nl_sock;
+static struct nl_cache *link_cache;
+static struct rtnl_qdisc *qdisc;
+static struct rtnl_tc *tc;
+
+static int qdisc_init(void)
+{
+	nl_sock = nl_cli_alloc_socket();
+	nl_cli_connect(nl_sock, NETLINK_ROUTE);
+	link_cache = nl_cli_link_alloc_cache(nl_sock);
+	qdisc = nl_cli_qdisc_alloc();
+	tc = (struct rtnl_tc *)qdisc;
+
+	return 0;
+}
+
+/* Stop buffering and release all buffered and incoming 'qdisc'
+ * queueing discipline traffic.
+ */
+int plug_qdisc_release_indefinite_buffer(void)
+{
+	rtnl_qdisc_plug_release_indefinite(qdisc);
+	return rtnl_qdisc_add(nl_sock, qdisc, 0);
+}
+
+/* Start buffering incoming 'qdisc' queueing discipline traffic. */
+int plug_qdisc_plug_buffer(void)
+{
+	rtnl_qdisc_plug_buffer(qdisc);
+	return rtnl_qdisc_add(nl_sock, qdisc, 0);
+}
+
+/* Create a plug qdisc attached to 'device' network device with 'parent'
+ * as parent, with 'id' as ID and 'limit' as buffer size.
+ * This is equivalent to use nl-qdisc-add tool like that:
+ *  $ nl-qdisc-add --dev=<device> --parent=<parent> --id=<id> plug --limit <limit>
+ *  $ nl-qdisc-add --dev=<device> --parent=<parent> --id=<id> --update plug --release-indefinite
+ */
+int plug_qdisc_attach(char *device, char *parent, char *id, uint32_t limit)
+{
+	int ret;
+
+	if (!tc && qdisc_init() == -1)
+		return -1;
+
+	nl_cli_tc_parse_dev(tc, link_cache, device);
+	nl_cli_tc_parse_parent(tc, parent);
+	if (!rtnl_tc_get_ifindex(tc))
+		return -1;
+
+	if (!rtnl_tc_get_parent(tc))
+		return -1;
+	if (id)
+		nl_cli_tc_parse_handle(tc, id, 1);
+
+	rtnl_tc_set_kind(tc, "plug");
+	if (limit)
+		rtnl_qdisc_plug_set_limit(qdisc, limit);
+
+	ret = rtnl_qdisc_add(nl_sock, qdisc, NLM_F_CREATE);
+	if (ret < 0) {
+		fprintf(stderr, "Could add attach qdisc: %s\n", nl_geterror(ret));
+		return -1;
+	}
+	/* Release buffer. */
+	plug_qdisc_release_indefinite_buffer();
+
+	return 0;
+}
+
diff --git a/dev/poll/Makefile b/dev/poll/Makefile
new file mode 100644
index 0000000..fdee514
--- /dev/null
+++ b/dev/poll/Makefile
@@ -0,0 +1,11 @@
+CC       = cc
+OPTIMIZE = -O2 -g
+DEFINE   =
+INCLUDE  =
+OBJS     = poll
+
+poll: poll.c
+	$(CC) $(OPTIMIZE) $(DEFINE) $(INCLUDE) -o $@ $^
+
+clean:
+	rm -f $(OBJS) *.[oas] *~
diff --git a/dev/poll/poll.c b/dev/poll/poll.c
new file mode 100644
index 0000000..55f922a
--- /dev/null
+++ b/dev/poll/poll.c
@@ -0,0 +1,365 @@
+#define _GNU_SOURCE // for POLLRDHUP
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* for OSes which don't have it */
+#ifndef POLLRDHUP
+#define POLLRDHUP 0
+#endif
+
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL 0
+#endif
+#ifndef MSG_MORE
+#define MSG_MORE 0
+#endif
+
+int verbose = 0;
+int cmd = 0;
+int cmdstep = 0;
+int zero = 0;
+int one  = 1;
+int lfd = -1;
+int cfd = -1;
+int sfd = -1;
+struct sockaddr_in saddr, caddr;
+socklen_t salen, calen;
+
+void usage(const char *arg0)
+{
+	printf("Usage: %s [ arg [<action>[,...]] ] ...\n"
+	       "args:\n"
+	       "    -h            display this help\n"
+	       "    -v            verbose mode (shows ret values)\n"
+	       "    -c <actions>  perform <action> on client side socket\n"
+	       "    -s <actions>  perform <action> on server side socket\n"
+	       "    -l <actions>  perform <action> on listening socket\n"
+	       "\n"
+	       "actions for -c/-s/-l (multiple may be delimited by commas) :\n"
+	       "    acc           accept on listener, implicit before first -s\n"
+	       "    snd           send a few bytes of data\n"
+	       "    mor           send a few bytes of data with MSG_MORE\n"
+	       "    rcv           receive a few bytes of data\n"
+	       "    drn           drain: receive till zero\n"
+	       "    shr           SHUT_RD : shutdown read side\n"
+	       "    shw           SHUT_WR : shutdown write side\n"
+	       "    shb           SHUT_RDWR : shutdown both sides\n"
+	       "    lin           disable lingering on the socket\n"
+	       "    clo           close the file descriptor\n"
+	       "    pol           poll() for any event\n"
+	       "\n", arg0);
+}
+
+void die(const char *msg)
+{
+	if (msg)
+		fprintf(stderr, "%s\n", msg);
+	exit(1);
+}
+
+const char *get_errno(int ret)
+{
+	static char errmsg[100];
+
+	if (ret >= 0)
+		return "";
+
+	snprintf(errmsg, sizeof(errmsg), " (%s)", strerror(errno));
+	return errmsg;
+}
+
+void do_acc(int fd)
+{
+	int ret;
+
+	calen = sizeof(caddr);
+	ret = accept(lfd, (struct sockaddr*)&caddr, &calen);
+	if (sfd < 0)
+		sfd = ret;
+	if (verbose)
+		printf("cmd #%d stp #%d: %s(%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__, fd, ret, get_errno(ret));
+}
+
+void do_snd(int fd)
+{
+	int ret;
+
+	ret = send(fd, "foo", 3, MSG_NOSIGNAL|MSG_DONTWAIT);
+	if (verbose)
+		printf("cmd #%d stp #%d: %s(%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__, fd, ret, get_errno(ret));
+}
+
+void do_mor(int fd)
+{
+	int ret;
+
+	ret = send(fd, "foo", 3, MSG_NOSIGNAL|MSG_DONTWAIT|MSG_MORE);
+	if (verbose)
+		printf("cmd #%d stp #%d: %s(%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__, fd, ret, get_errno(ret));
+}
+
+void do_rcv(int fd)
+{
+	char buf[10];
+	int ret;
+
+	ret = recv(fd, buf, sizeof(buf), MSG_DONTWAIT);
+	if (verbose)
+		printf("cmd #%d stp #%d: %s(%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__, fd, ret, get_errno(ret));
+}
+
+void do_drn(int fd)
+{
+	char buf[16384];
+	int total = -1;
+	int ret;
+
+	while (1) {
+		ret = recv(fd, buf, sizeof(buf), 0);
+		if (ret <= 0)
+			break;
+		if (total < 0)
+			total = 0;
+		total += ret;
+	}
+
+	if (verbose)
+		printf("cmd #%d stp #%d: %s(%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__, fd, total, get_errno(ret));
+}
+
+void do_shr(int fd)
+{
+	int ret;
+
+	ret = shutdown(fd, SHUT_RD);
+	if (verbose)
+		printf("cmd #%d stp #%d: %s(%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__, fd, ret, get_errno(ret));
+}
+
+void do_shw(int fd)
+{
+	int ret;
+
+	ret = shutdown(fd, SHUT_WR);
+	if (verbose)
+		printf("cmd #%d stp #%d: %s(%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__, fd, ret, get_errno(ret));
+}
+
+void do_shb(int fd)
+{
+	int ret;
+
+	ret = shutdown(fd, SHUT_RDWR);
+	if (verbose)
+		printf("cmd #%d stp #%d: %s(%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__, fd, ret, get_errno(ret));
+}
+
+void do_lin(int fd)
+{
+	struct linger nolinger = { .l_onoff = 1, .l_linger = 0 };
+	int ret;
+
+	ret = setsockopt(fd, SOL_SOCKET, SO_LINGER, &nolinger, sizeof(nolinger));
+	if (verbose)
+		printf("cmd #%d stp #%d: %s(%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__, fd, ret, get_errno(ret));
+}
+
+void do_clo(int fd)
+{
+	int ret;
+
+	ret = close(fd);
+	if (verbose)
+		printf("cmd #%d stp #%d: %s(%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__, fd, ret, get_errno(ret));
+}
+
+void do_pol(int fd)
+{
+	struct pollfd fds = { .fd = fd, .events = POLLIN|POLLOUT|POLLRDHUP, .revents=0 };
+	int ret;
+
+	ret = poll(&fds, 1, 0);
+	if (verbose) {
+		printf("cmd #%d stp #%d: %s(%d): ret=%d%s ev=%#x ", cmd, cmdstep, __FUNCTION__, fd, ret, get_errno(ret), ret > 0 ? fds.revents : 0);
+		if (ret > 0 && fds.revents) {
+			int flags, flag;
+			putchar('(');
+
+			for (flags = fds.revents; flags; flags ^= flag) {
+				flag = flags ^ (flags & (flags - 1)); // keep lowest bit only
+				switch (flag) {
+				case POLLIN: printf("IN"); break;
+				case POLLOUT: printf("OUT"); break;
+				case POLLPRI: printf("PRI"); break;
+				case POLLHUP: printf("HUP"); break;
+				case POLLERR: printf("ERR"); break;
+				case POLLNVAL: printf("NVAL"); break;
+#if POLLRDHUP
+				case POLLRDHUP: printf("RDHUP"); break;
+#endif
+				default: printf("???[%#x]", flag); break;
+				}
+				if (flags ^ flag)
+					putchar(' ');
+			}
+			putchar(')');
+		}
+		putchar('\n');
+	}
+}
+
+int main(int argc, char **argv)
+{
+	const char *arg0;
+	char *word, *next;
+	int fd;
+
+	/* listener */
+	lfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+	if (lfd < 0)
+		die("socket(l)");
+
+	setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+
+	memset(&saddr, 0, sizeof(saddr));
+        saddr.sin_family = AF_INET;
+        saddr.sin_port = htons(0);
+	salen = sizeof(saddr);
+
+	if (bind(lfd, (struct sockaddr *)&saddr, salen) < 0)
+		die("bind()");
+
+	if (listen(lfd, 1000) < 0)
+		die("listen()");
+
+	if (getsockname(lfd, (struct sockaddr *)&saddr, &salen) < 0)
+		die("getsockname()");
+
+
+	/* client */
+	cfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
+	if (cfd < 0)
+		die("socket(c)");
+
+        if (connect(cfd, (const struct sockaddr*)&saddr, salen) == -1)
+		die("connect()");
+
+	/* connection is pending in accept queue, accept() will either be
+	 * explicit with "-l acc" below, or implicit on "-s <cmd>"
+	 */
+
+	arg0 = argv[0];
+	if (argc < 2) {
+		usage(arg0);
+		exit(1);
+	}
+
+	write(1, "#### BEGIN ####\n", 16); // add a visible delimiter in the traces
+
+	while (argc > 1) {
+		argc--; argv++;
+		if (**argv != '-') {
+			usage(arg0);
+			exit(1);
+		}
+
+		fd = -1;
+		switch (argv[0][1]) {
+		case 'h' :
+			usage(arg0);
+			exit(0);
+			break;
+		case 'v' :
+			verbose++;
+			break;
+		case 'c' :
+			cmd++; cmdstep = 0;
+			fd = cfd;
+			break;
+		case 's' :
+			cmd++; cmdstep = 0;
+			if (sfd < 0)
+				do_acc(lfd);
+			if (sfd < 0)
+				die("accept()");
+			fd = sfd;
+			break;
+		case 'l' :
+			cmd++; cmdstep = 0;
+			fd = lfd;
+			break;
+		default  : usage(arg0); exit(1); break;
+		}
+
+		if (fd >= 0) { /* an action is required */
+			if (argc < 2) {
+				usage(arg0);
+				exit(1);
+			}
+
+			for (word = argv[1]; word && *word; word = next) {
+				next = strchr(word, ',');
+				if (next)
+					*(next++) = 0;
+				cmdstep++;
+				if (strcmp(word, "acc") == 0) {
+					do_acc(fd);
+				}
+				else if (strcmp(word, "snd") == 0) {
+					do_snd(fd);
+				}
+				else if (strcmp(word, "mor") == 0) {
+					do_mor(fd);
+				}
+				else if (strcmp(word, "rcv") == 0) {
+					do_rcv(fd);
+				}
+				else if (strcmp(word, "drn") == 0) {
+					do_drn(fd);
+				}
+				else if (strcmp(word, "shb") == 0) {
+					do_shb(fd);
+				}
+				else if (strcmp(word, "shr") == 0) {
+					do_shr(fd);
+				}
+				else if (strcmp(word, "shw") == 0) {
+					do_shw(fd);
+				}
+				else if (strcmp(word, "lin") == 0) {
+					do_lin(fd);
+				}
+				else if (strcmp(word, "clo") == 0) {
+					do_clo(fd);
+				}
+				else if (strcmp(word, "pol") == 0) {
+					do_pol(fd);
+				}
+				else {
+					printf("Ignoring unknown action '%s' in step #%d of cmd #%d\n", word, cmdstep, cmd);
+				}
+			}
+			argc--; argv++;
+		}
+	}
+
+	write(1, "#### END ####\n", 14); // add a visible delimiter in the traces
+
+	if (!cmd) {
+		printf("No command was requested!\n");
+		usage(arg0);
+		exit(1);
+	}
+
+	return 0;
+}
diff --git a/dev/tcploop/Makefile b/dev/tcploop/Makefile
new file mode 100644
index 0000000..42a6259
--- /dev/null
+++ b/dev/tcploop/Makefile
@@ -0,0 +1,11 @@
+CC       = gcc
+OPTIMIZE = -O2 -g
+DEFINE   =
+INCLUDE  =
+OBJS     = tcploop
+
+tcploop: tcploop.c
+	$(CC) $(OPTIMIZE) $(DEFINE) $(INCLUDE) -o $@ $^
+
+clean:
+	rm -f $(OBJS) *.[oas] *~
diff --git a/dev/tcploop/tcploop.c b/dev/tcploop/tcploop.c
new file mode 100644
index 0000000..c7cec6f
--- /dev/null
+++ b/dev/tcploop/tcploop.c
@@ -0,0 +1,956 @@
+/*
+ * TCP client and server for bug hunting
+ *
+ * Copyright (C) 2016 Willy Tarreau <w@1wt.eu>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <netdb.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifndef MSG_MORE
+#define MSG_MORE 0
+#endif
+
+struct err_msg {
+	int size;
+	int len;
+	char msg[0];
+};
+
+const int zero = 0;
+const int one = 1;
+const struct linger nolinger = { .l_onoff = 1, .l_linger = 0 };
+
+#define TRASH_SIZE 65536
+static char trash[TRASH_SIZE];
+
+volatile int nbproc = 0;
+static struct timeval start_time;
+static int showtime;
+static int verbose;
+static int pid;
+
+
+/* display the message and exit with the code */
+__attribute__((noreturn)) void die(int code, const char *format, ...)
+{
+	va_list args;
+
+	if (format) {
+		va_start(args, format);
+		vfprintf(stderr, format, args);
+		va_end(args);
+	}
+	exit(code);
+}
+
+/* display the usage message and exit with the code */
+__attribute__((noreturn)) void usage(int code, const char *arg0)
+{
+	die(code,
+	    "Usage : %s [options]* [<ip>:]port [<action>*]\n"
+	    "\n"
+	    "options :\n"
+	    "  -v           : verbose\n"
+	    "  -t|-tt|-ttt  : show time (msec / relative / absolute)\n"
+	    "actions :\n"
+	    "  L[<backlog>] : Listens to ip:port and optionally sets backlog\n"
+	    "                 Note: fd=socket,bind(fd),listen(fd)\n"
+	    "  C            : Connects to ip:port\n"
+	    "                 Note: fd=socket,connect(fd)\n"
+	    "  D            : Disconnect (connect to AF_UNSPEC)\n"
+	    "  A[<count>]   : Accepts <count> incoming sockets and closes count-1\n"
+	    "                 Note: fd=accept(fd)\n"
+	    "  J            : Jump back to oldest post-fork/post-accept action\n"
+	    "  K            : kill the connection and go on with next operation\n"
+	    "  G            : disable lingering\n"
+	    "  T            : set TCP_NODELAY\n"
+	    "  Q            : disable TCP Quick-ack\n"
+	    "  R[<size>]    : Read this amount of bytes. 0=infinite. unset=any amount.\n"
+	    "  S[<size>]    : Send this amount of bytes. 0=infinite. unset=any amount.\n"
+	    "  S:<string>   : Send this exact string. \\r, \\n, \\t, \\\\ supported.\n"
+	    "  E[<size>]    : Echo this amount of bytes. 0=infinite. unset=any amount.\n"
+	    "  W[<time>]    : Wait for any event on the socket, maximum <time> ms\n"
+	    "  P[<time>]    : Pause for <time> ms (100 by default)\n"
+	    "  I            : wait for Input data to be present (POLLIN)\n"
+	    "  O            : wait for Output queue to be empty (POLLOUT + TIOCOUTQ)\n"
+	    "  F            : FIN : shutdown(SHUT_WR)\n"
+	    "  r            : shutr : shutdown(SHUT_RD) (pauses a listener or ends recv)\n"
+	    "  N<max>       : fork New process, limited to <max> concurrent (default 1)\n"
+	    "  X[i|o|e]* ** : execvp() next args passing socket as stdin/stdout/stderr.\n"
+	    "                 If i/o/e present, only stdin/out/err are mapped to socket.\n"
+	    "\n"
+	    "It's important to note that a single FD is used at once and that Accept\n"
+	    "replaces the listening FD with the accepted one. Thus always do it after\n"
+	    "a fork if other connections have to be accepted.\n"
+	    "\n"
+	    "After a fork, we loop back to the beginning and silently skip L/C if the\n"
+	    "main socket already exists.\n"
+	    "\n"
+	    "Example dummy HTTP request drain server :\n"
+	    "   tcploop 8001 L W N20 A R S10 [ F K ]\n"
+	    "\n"
+	    "Example large bandwidth HTTP request drain server :\n"
+	    "   tcploop 8001 L W N20 A R S0 [ F K ]\n"
+	    "\n"
+	    "Example TCP client with pauses at each step :\n"
+	    "   tcploop 8001 C T W P100 S10 O P100 R S10 O R G K\n"
+	    "\n"
+	    "Simple chargen server :\n"
+	    "   tcploop 8001 L A Xo cat /dev/zero\n"
+	    "\n"
+	    "Simple telnet server :\n"
+	    "   tcploop 8001 L W N A X /usr/sbin/in.telnetd\n"
+	    "", arg0);
+}
+
+void dolog(const char *format, ...)
+{
+	struct timeval date, tv;
+	int delay;
+	va_list args;
+
+	if (!verbose)
+		return;
+
+	if (showtime) {
+		gettimeofday(&date, NULL);
+		switch (showtime) {
+		case 1: // [msec] relative
+			delay = (date.tv_sec - start_time.tv_sec) * 1000000 + date.tv_usec - start_time.tv_usec;
+			fprintf(stderr, "[%d] ", delay / 1000);
+			break;
+		case 2: // [sec.usec] relative
+			tv.tv_usec = date.tv_usec - start_time.tv_usec;
+			tv.tv_sec  = date.tv_sec  - start_time.tv_sec;
+			if ((signed)tv.tv_sec > 0) {
+				if ((signed)tv.tv_usec < 0) {
+					tv.tv_usec += 1000000;
+					tv.tv_sec--;
+				}
+			} else if (tv.tv_sec == 0) {
+				if ((signed)tv.tv_usec < 0)
+					tv.tv_usec = 0;
+			} else {
+				tv.tv_sec = 0;
+				tv.tv_usec = 0;
+			}
+			fprintf(stderr, "[%d.%06d] ", (int)tv.tv_sec, (int)tv.tv_usec);
+			break;
+		default: // [sec.usec] absolute
+			fprintf(stderr, "[%d.%06d] ", (int)date.tv_sec, (int)date.tv_usec);
+			break;
+		}
+	}
+
+	fprintf(stderr, "%5d ", pid);
+
+	va_start(args, format);
+	vfprintf(stderr, format, args);
+	va_end(args);
+}
+
+/* convert '\n', '\t', '\r', '\\' to their respective characters */
+int unescape(char *out, int size, const char *in)
+{
+	int len;
+
+	for (len = 0; len < size && *in; in++, out++, len++) {
+		if (*in == '\\') {
+			switch (in[1]) {
+			case  'n' : *out = '\n'; in++; continue;
+			case  't' : *out = '\t'; in++; continue;
+			case  'r' : *out = '\r'; in++; continue;
+			case '\\' : *out = '\\'; in++; continue;
+			default   : break;
+			}
+		}
+		*out = *in;
+	}
+	return len;
+}
+
+struct err_msg *alloc_err_msg(int size)
+{
+	struct err_msg *err;
+
+	err = malloc(sizeof(*err) + size);
+	if (err) {
+		err->len = 0;
+		err->size = size;
+	}
+	return err;
+}
+
+void sig_handler(int sig)
+{
+	if (sig == SIGCHLD) {
+		while (waitpid(-1, NULL, WNOHANG) > 0)
+			__sync_sub_and_fetch(&nbproc, 1);
+	}
+}
+
+/* converts str in the form [[<ipv4>|<ipv6>|<hostname>]:]port to struct sockaddr_storage.
+ * Returns < 0 with err set in case of error.
+ */
+int addr_to_ss(char *str, struct sockaddr_storage *ss, struct err_msg *err)
+{
+	char *port_str;
+	int port;
+
+	memset(ss, 0, sizeof(*ss));
+
+	/* look for the addr/port delimiter, it's the last colon. If there's no
+	 * colon, it's 0:<port>.
+	 */
+	if ((port_str = strrchr(str, ':')) == NULL) {
+		port = atoi(str);
+		if (port <= 0 || port > 65535) {
+			err->len = snprintf(err->msg, err->size, "Missing/invalid port number: '%s'\n", str);
+			return -1;
+		}
+
+		ss->ss_family = AF_INET;
+		((struct sockaddr_in *)ss)->sin_port = htons(port);
+		((struct sockaddr_in *)ss)->sin_addr.s_addr = INADDR_ANY;
+		return 0;
+	}
+
+	*port_str++ = 0;
+
+	if (strrchr(str, ':') != NULL) {
+		/* IPv6 address contains ':' */
+		ss->ss_family = AF_INET6;
+		((struct sockaddr_in6 *)ss)->sin6_port = htons(atoi(port_str));
+
+		if (!inet_pton(ss->ss_family, str, &((struct sockaddr_in6 *)ss)->sin6_addr)) {
+			err->len = snprintf(err->msg, err->size, "Invalid server address: '%s'\n", str);
+			return -1;
+		}
+	}
+	else {
+		ss->ss_family = AF_INET;
+		((struct sockaddr_in *)ss)->sin_port = htons(atoi(port_str));
+
+		if (*str == '*' || *str == '\0') { /* INADDR_ANY */
+			((struct sockaddr_in *)ss)->sin_addr.s_addr = INADDR_ANY;
+			return 0;
+		}
+
+		if (!inet_pton(ss->ss_family, str, &((struct sockaddr_in *)ss)->sin_addr)) {
+			struct hostent *he = gethostbyname(str);
+
+			if (he == NULL) {
+				err->len = snprintf(err->msg, err->size, "Invalid server name: '%s'\n", str);
+				return -1;
+			}
+			((struct sockaddr_in *)ss)->sin_addr = *(struct in_addr *) *(he->h_addr_list);
+		}
+	}
+
+	return 0;
+}
+
+/* waits up to one second on fd <fd> for events <events> (POLLIN|POLLOUT).
+ * returns poll's status.
+ */
+int wait_on_fd(int fd, int events)
+{
+	struct pollfd pollfd;
+	int ret;
+
+	do {
+		pollfd.fd = fd;
+		pollfd.events = events;
+		ret = poll(&pollfd, 1, 1000);
+	} while (ret == -1 && errno == EINTR);
+
+	return ret;
+}
+
+int tcp_set_nodelay(int sock, const char *arg)
+{
+	return setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
+}
+
+int tcp_set_nolinger(int sock, const char *arg)
+{
+	return setsockopt(sock, SOL_SOCKET, SO_LINGER, (struct linger *) &nolinger, sizeof(struct linger));
+}
+
+int tcp_set_noquickack(int sock, const char *arg)
+{
+#ifdef TCP_QUICKACK
+	/* warning: do not use during connect if nothing is to be sent! */
+	return setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, &zero, sizeof(zero));
+#else
+	return 0;
+#endif
+}
+
+/* Try to listen to address <sa>. Return the fd or -1 in case of error */
+int tcp_listen(const struct sockaddr_storage *sa, const char *arg)
+{
+	int sock;
+	int backlog;
+
+	if (arg[1])
+		backlog = atoi(arg + 1);
+	else
+		backlog = 1000;
+
+	if (backlog < 0 || backlog > 65535) {
+		fprintf(stderr, "backlog must be between 0 and 65535 inclusive (was %d)\n", backlog);
+		return -1;
+	}
+
+	sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+	if (sock < 0) {
+		perror("socket()");
+		return -1;
+	}
+
+	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1) {
+		perror("setsockopt(SO_REUSEADDR)");
+		goto fail;
+	}
+
+#ifdef SO_REUSEPORT
+	if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (char *) &one, sizeof(one)) == -1) {
+		perror("setsockopt(SO_REUSEPORT)");
+		goto fail;
+	}
+#endif
+	if (bind(sock, (struct sockaddr *)sa, sa->ss_family == AF_INET6 ?
+		 sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) == -1) {
+		perror("bind");
+		goto fail;
+	}
+
+	if (listen(sock, backlog) == -1) {
+		perror("listen");
+		goto fail;
+	}
+
+	return sock;
+ fail:
+	close(sock);
+	return -1;
+}
+
+/* accepts a socket from listening socket <sock>, and returns it (or -1 in case of error) */
+int tcp_accept(int sock, const char *arg)
+{
+	int count;
+	int newsock;
+
+	if (arg[1])
+		count = atoi(arg + 1);
+	else
+		count = 1;
+
+	if (count <= 0) {
+		fprintf(stderr, "accept count must be > 0 or unset (was %d)\n", count);
+		return -1;
+	}
+
+	do {
+		newsock = accept(sock, NULL, NULL);
+		if (newsock < 0) { // TODO: improve error handling
+			if (errno == EINTR || errno == EAGAIN || errno == ECONNABORTED)
+				continue;
+			perror("accept()");
+			break;
+		}
+
+		if (count > 1)
+			close(newsock);
+		count--;
+	} while (count > 0);
+
+	fcntl(newsock, F_SETFL, O_NONBLOCK);
+	return newsock;
+}
+
+/* Try to establish a new connection to <sa>. Return the fd or -1 in case of error */
+int tcp_connect(const struct sockaddr_storage *sa, const char *arg)
+{
+	int sock;
+
+	sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+	if (sock < 0)
+		return -1;
+
+	if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1)
+		goto fail;
+
+	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1)
+		goto fail;
+
+	if (connect(sock, (const struct sockaddr *)sa, sizeof(struct sockaddr_in)) < 0) {
+		if (errno != EINPROGRESS)
+			goto fail;
+	}
+
+	return sock;
+ fail:
+	close(sock);
+	return -1;
+}
+
+/* Try to disconnect by connecting to AF_UNSPEC. Return >=0 on success, -1 in case of error */
+int tcp_disconnect(int sock)
+{
+	const struct sockaddr sa = { .sa_family = AF_UNSPEC };
+
+	return connect(sock, &sa, sizeof(sa));
+}
+
+/* receives N bytes from the socket and returns 0 (or -1 in case of a recv
+ * error, or -2 in case of an argument error). When no arg is passed, receives
+ * anything and stops. Otherwise reads the requested amount of data. 0 means
+ * read as much as possible.
+ */
+int tcp_recv(int sock, const char *arg)
+{
+	int count = -1; // stop at first read
+	int ret;
+	int max;
+
+	if (arg[1]) {
+		count = atoi(arg + 1);
+		if (count < 0) {
+			fprintf(stderr, "recv count must be >= 0 or unset (was %d)\n", count);
+			return -2;
+		}
+	}
+
+	while (1) {
+		max = (count > 0) ? count : INT_MAX;
+		if (max > sizeof(trash))
+			max = sizeof(trash);
+		ret = recv(sock, trash, max, MSG_NOSIGNAL | MSG_TRUNC);
+		if (ret < 0) {
+			if (errno == EINTR)
+				continue;
+			if (errno != EAGAIN) {
+				dolog("recv %d\n", ret);
+				return -1;
+			}
+			while (!wait_on_fd(sock, POLLIN));
+			continue;
+		}
+		dolog("recv %d\n", ret);
+		if (!ret)
+			break;
+
+		if (!count)
+			continue;
+		else if (count > 0)
+			count -= ret;
+
+		if (count <= 0)
+			break;
+	}
+
+	return 0;
+}
+
+/* Sends N bytes to the socket and returns 0 (or -1 in case of send error, -2
+ * in case of an argument error. If the byte count is not set, sends only one
+ * block. Sending zero means try to send forever. If the argument starts with
+ * ':' then whatever follows is interpreted as the payload to be sent as-is.
+ * Escaped characters '\r', '\n', '\t' and '\\' are detected and converted. In
+ * this case, blocks must be small so that send() doesn't fragment them, as
+ * they will be put into the trash and expected to be sent at once.
+ */
+int tcp_send(int sock, const char *arg)
+{
+	int count = -1; // stop after first block
+	int ret;
+
+	if (arg[1] == ':') {
+		count = unescape(trash, sizeof(trash), arg + 2);
+	} else if (arg[1]) {
+		count = atoi(arg + 1);
+		if (count < 0) {
+			fprintf(stderr, "send count must be >= 0 or unset (was %d)\n", count);
+			return -2;
+		}
+	}
+
+	while (1) {
+		ret = send(sock, trash,
+		           (count > 0) && (count < sizeof(trash)) ? count : sizeof(trash),
+		           MSG_NOSIGNAL | ((count > sizeof(trash)) ? MSG_MORE : 0));
+		if (ret < 0) {
+			if (errno == EINTR)
+				continue;
+			if (errno != EAGAIN) {
+				dolog("send %d\n", ret);
+				return -1;
+			}
+			while (!wait_on_fd(sock, POLLOUT));
+			continue;
+		}
+		dolog("send %d\n", ret);
+		if (!count)
+			continue;
+		else if (count > 0)
+			count -= ret;
+
+		if (count <= 0)
+			break;
+	}
+
+	return 0;
+}
+
+/* echoes N bytes to the socket and returns 0 (or -1 in case of error). If not
+ * set, echoes only the first block. Zero means forward forever.
+ */
+int tcp_echo(int sock, const char *arg)
+{
+	int count = -1; // echo forever
+	int ret;
+	int rcvd;
+
+	if (arg[1]) {
+		count = atoi(arg + 1);
+		if (count < 0) {
+			fprintf(stderr, "send count must be >= 0 or unset (was %d)\n", count);
+			return -1;
+		}
+	}
+
+	rcvd = 0;
+	while (1) {
+		if (rcvd <= 0) {
+			/* no data pending */
+			rcvd = recv(sock, trash, (count > 0) && (count < sizeof(trash)) ? count : sizeof(trash), MSG_NOSIGNAL);
+			if (rcvd < 0) {
+				if (errno == EINTR)
+					continue;
+				if (errno != EAGAIN) {
+					dolog("recv %d\n", rcvd);
+					return -1;
+				}
+				while (!wait_on_fd(sock, POLLIN));
+				continue;
+			}
+			dolog("recv %d\n", rcvd);
+			if (!rcvd)
+				break;
+		}
+		else {
+			/* some data still pending */
+			ret = send(sock, trash, rcvd, MSG_NOSIGNAL | ((count > rcvd) ? MSG_MORE : 0));
+			if (ret < 0) {
+				if (errno == EINTR)
+					continue;
+				if (errno != EAGAIN) {
+					dolog("send %d\n", ret);
+					return -1;
+				}
+				while (!wait_on_fd(sock, POLLOUT));
+				continue;
+			}
+			dolog("send %d\n", ret);
+			rcvd -= ret;
+			if (rcvd)
+				continue;
+
+			if (!count)
+				continue;
+			else if (count > 0)
+				count -= ret;
+
+			if (count <= 0)
+				break;
+		}
+	}
+	return 0;
+}
+
+/* waits for an event on the socket, usually indicates an accept for a
+ * listening socket and a connect for an outgoing socket.
+ */
+int tcp_wait(int sock, const char *arg)
+{
+	struct pollfd pollfd;
+	int delay = -1; // wait forever
+	int ret;
+
+	if (arg[1]) {
+		delay = atoi(arg + 1);
+		if (delay < 0) {
+			fprintf(stderr, "wait time must be >= 0 or unset (was %d)\n", delay);
+			return -1;
+		}
+	}
+
+	/* FIXME: this doesn't take into account delivered signals */
+	do {
+		pollfd.fd = sock;
+		pollfd.events = POLLIN | POLLOUT;
+		ret = poll(&pollfd, 1, delay);
+	} while (ret == -1 && errno == EINTR);
+
+	if (ret > 0 && pollfd.revents & POLLERR)
+		return -1;
+
+	return 0;
+}
+
+/* waits for the input data to be present */
+int tcp_wait_in(int sock, const char *arg)
+{
+	struct pollfd pollfd;
+	int ret;
+
+	do {
+		pollfd.fd = sock;
+		pollfd.events = POLLIN;
+		ret = poll(&pollfd, 1, 1000);
+	} while (ret == -1 && errno == EINTR);
+
+	if (ret > 0 && pollfd.revents & POLLERR)
+		return -1;
+
+	return 0;
+}
+
+/* waits for the output queue to be empty */
+int tcp_wait_out(int sock, const char *arg)
+{
+	struct pollfd pollfd;
+	int ret;
+
+	do {
+		pollfd.fd = sock;
+		pollfd.events = POLLOUT;
+		ret = poll(&pollfd, 1, 1000);
+	} while (ret == -1 && errno == EINTR);
+
+	if (ret > 0 && pollfd.revents & POLLERR)
+		return -1;
+
+	/* Now wait for data to leave the socket */
+	do {
+		if (ioctl(sock, TIOCOUTQ, &ret) < 0)
+			return -1;
+	} while (ret > 0);
+	return 0;
+}
+
+/* delays processing for <time> milliseconds, 100 by default */
+int tcp_pause(int sock, const char *arg)
+{
+	int delay = 100;
+
+	if (arg[1]) {
+		delay = atoi(arg + 1);
+		if (delay < 0) {
+			fprintf(stderr, "wait time must be >= 0 or unset (was %d)\n", delay);
+			return -1;
+		}
+	}
+
+	usleep(delay * 1000);
+	return 0;
+}
+
+/* forks another process while respecting the limit imposed in argument (1 by
+ * default). Will wait for another process to exit before creating a new one.
+ * Returns the value of the fork() syscall, ie 0 for the child, non-zero for
+ * the parent, -1 for an error.
+ */
+int tcp_fork(int sock, const char *arg)
+{
+	int max = 1;
+	int ret;
+
+	if (arg[1]) {
+		max = atoi(arg + 1);
+		if (max <= 0) {
+			fprintf(stderr, "max process must be > 0 or unset (was %d)\n", max);
+			return -1;
+		}
+	}
+
+	while (nbproc >= max)
+		poll(NULL, 0, 1000);
+
+	ret = fork();
+	if (ret > 0)
+		__sync_add_and_fetch(&nbproc, 1);
+	return ret;
+}
+
+int main(int argc, char **argv)
+{
+	struct sockaddr_storage ss;
+	struct err_msg err;
+	const char *arg0;
+	int loop_arg;
+	int arg;
+	int ret;
+	int sock;
+	int errfd;
+
+	arg0 = argv[0];
+
+	while (argc > 1 && argv[1][0] == '-') {
+		argc--; argv++;
+		if (strcmp(argv[0], "-t") == 0)
+			showtime++;
+		else if (strcmp(argv[0], "-tt") == 0)
+			showtime += 2;
+		else if (strcmp(argv[0], "-ttt") == 0)
+			showtime += 3;
+		else if (strcmp(argv[0], "-v") == 0)
+			verbose ++;
+		else if (strcmp(argv[0], "--") == 0)
+			break;
+		else
+			usage(1, arg0);
+	}
+
+	if (argc < 2)
+		usage(1, arg0);
+
+	pid = getpid();
+	signal(SIGCHLD, sig_handler);
+
+	if (addr_to_ss(argv[1], &ss, &err) < 0)
+		die(1, "%s\n", err.msg);
+
+	gettimeofday(&start_time, NULL);
+
+	sock = -1;
+	loop_arg = 2;
+	for (arg = loop_arg; arg < argc; arg++) {
+		switch (argv[arg][0]) {
+		case 'L':
+			/* silently ignore existing connections */
+			if (sock == -1)
+				sock = tcp_listen(&ss, argv[arg]);
+			if (sock < 0)
+				die(1, "Fatal: tcp_listen() failed.\n");
+			break;
+
+		case 'C':
+			/* silently ignore existing connections */
+			if (sock == -1)
+				sock = tcp_connect(&ss, argv[arg]);
+			if (sock < 0)
+				die(1, "Fatal: tcp_connect() failed.\n");
+			dolog("connect\n");
+			break;
+
+		case 'D':
+			/* silently ignore non-existing connections */
+			if (sock >= 0 && tcp_disconnect(sock) < 0)
+				die(1, "Fatal: tcp_connect() failed.\n");
+			dolog("disconnect\n");
+			break;
+
+		case 'A':
+			if (sock < 0)
+				die(1, "Fatal: tcp_accept() on non-socket.\n");
+			sock = tcp_accept(sock, argv[arg]);
+			if (sock < 0)
+				die(1, "Fatal: tcp_accept() failed.\n");
+			dolog("accept\n");
+			loop_arg = arg + 1; // cannot loop before accept()
+			break;
+
+		case 'T':
+			if (sock < 0)
+				die(1, "Fatal: tcp_set_nodelay() on non-socket.\n");
+			if (tcp_set_nodelay(sock, argv[arg]) < 0)
+				die(1, "Fatal: tcp_set_nodelay() failed.\n");
+			break;
+
+		case 'G':
+			if (sock < 0)
+				die(1, "Fatal: tcp_set_nolinger() on non-socket.\n");
+			if (tcp_set_nolinger(sock, argv[arg]) < 0)
+				die(1, "Fatal: tcp_set_nolinger() failed.\n");
+			break;
+
+		case 'Q':
+			if (sock < 0)
+				die(1, "Fatal: tcp_set_noquickack() on non-socket.\n");
+			if (tcp_set_noquickack(sock, argv[arg]) < 0)
+				die(1, "Fatal: tcp_set_noquickack() failed.\n");
+			break;
+
+		case 'R':
+			if (sock < 0)
+				die(1, "Fatal: tcp_recv() on non-socket.\n");
+			ret = tcp_recv(sock, argv[arg]);
+			if (ret < 0) {
+				if (ret == -1) // usually ECONNRESET, silently exit
+					die(0, NULL);
+				die(1, "Fatal: tcp_recv() failed.\n");
+			}
+			break;
+
+		case 'S':
+			if (sock < 0)
+				die(1, "Fatal: tcp_send() on non-socket.\n");
+			ret = tcp_send(sock, argv[arg]);
+			if (ret < 0) {
+				if (ret == -1) // usually a broken pipe, silently exit
+					die(0, NULL);
+				die(1, "Fatal: tcp_send() failed.\n");
+			}
+			break;
+
+		case 'E':
+			if (sock < 0)
+				die(1, "Fatal: tcp_echo() on non-socket.\n");
+			if (tcp_echo(sock, argv[arg]) < 0)
+				die(1, "Fatal: tcp_echo() failed.\n");
+			break;
+
+		case 'P':
+			if (tcp_pause(sock, argv[arg]) < 0)
+				die(1, "Fatal: tcp_pause() failed.\n");
+			break;
+
+		case 'W':
+			if (sock < 0)
+				die(1, "Fatal: tcp_wait() on non-socket.\n");
+			if (tcp_wait(sock, argv[arg]) < 0)
+				die(1, "Fatal: tcp_wait() failed.\n");
+			dolog("ready_any\n");
+			break;
+
+		case 'I':
+			if (sock < 0)
+				die(1, "Fatal: tcp_wait_in() on non-socket.\n");
+			if (tcp_wait_in(sock, argv[arg]) < 0)
+				die(1, "Fatal: tcp_wait_in() failed.\n");
+			dolog("ready_in\n");
+			break;
+
+		case 'O':
+			if (sock < 0)
+				die(1, "Fatal: tcp_wait_out() on non-socket.\n");
+			if (tcp_wait_out(sock, argv[arg]) < 0)
+				die(1, "Fatal: tcp_wait_out() failed.\n");
+			dolog("ready_out\n");
+			break;
+
+		case 'K':
+			if (sock < 0 || close(sock) < 0)
+				die(1, "Fatal: close() on non-socket.\n");
+			dolog("close\n");
+			sock = -1;
+			break;
+
+		case 'F':
+			/* ignore errors on shutdown() as they are common */
+			if (sock >= 0)
+				shutdown(sock, SHUT_WR);
+			dolog("shutdown(w)\n");
+			break;
+
+		case 'r':
+			/* ignore errors on shutdown() as they are common */
+			if (sock >= 0)
+				shutdown(sock, SHUT_RD);
+			dolog("shutdown(r)\n");
+			break;
+
+		case 'N':
+			ret = tcp_fork(sock, argv[arg]);
+			if (ret < 0)
+				die(1, "Fatal: fork() failed.\n");
+			if (ret > 0) {
+				/* loop back to first arg */
+				arg = loop_arg - 1;
+				continue;
+			}
+			/* OK we're in the child, let's continue */
+			pid = getpid();
+			loop_arg = arg + 1;
+			break;
+
+		case 'J': // jump back to oldest post-fork action
+			arg = loop_arg - 1;
+			continue;
+
+		case 'X': // execute command. Optionally supports redirecting only i/o/e
+			if (arg + 1 >= argc)
+				die(1, "Fatal: missing argument after %s\n", argv[arg]);
+
+			errfd = dup(2);
+			fcntl(errfd, F_SETFD, fcntl(errfd, F_GETFD, FD_CLOEXEC) | FD_CLOEXEC);
+			fcntl(sock,  F_SETFL, fcntl(sock,  F_GETFL, O_NONBLOCK) & ~O_NONBLOCK);
+			if (!argv[arg][1] || strchr(argv[arg], 'i'))
+				dup2(sock, 0);
+			if (!argv[arg][1] || strchr(argv[arg], 'o'))
+				dup2(sock, 1);
+			if (!argv[arg][1] || strchr(argv[arg], 'e'))
+				dup2(sock, 2);
+			argv += arg + 1;
+			if (execvp(argv[0], argv) == -1) {
+				int e = errno;
+
+				dup2(errfd, 2); // restore original stderr
+				close(errfd);
+				die(1, "Fatal: execvp(%s) failed : %s\n", argv[0], strerror(e));
+			}
+			break;
+		default:
+			usage(1, arg0);
+		}
+	}
+	return 0;
+}
diff --git a/dev/trace/trace.awk b/dev/trace/trace.awk
new file mode 100755
index 0000000..7b3b131
--- /dev/null
+++ b/dev/trace/trace.awk
@@ -0,0 +1,78 @@
+#!/bin/sh
+#
+# trace.awk - Fast trace symbol resolver - w@1wt.eu - 2012/05/25
+#
+# Principle: this program launches reads pointers from a trace file and if not
+# found in its cache, it passes them over a pipe to addr2line which is forked
+# in a coprocess, then stores the result in the cache.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version
+# 2 of the License, or (at your option) any later version.
+#
+# usage: $0 exec_file < trace.out
+#
+
+if [ $# -lt 1 ]; then
+  echo "Usage:   ${0##*/} exec_file < trace.out"
+  echo "Example: ${0##*/} ./haproxy < trace.out"
+  echo "Example: HAPROXY_TRACE=/dev/stdout ./haproxy -f cfg | ${0##*/} ./haproxy"
+  exit 1
+fi
+
+if [ ! -s "$1" ]; then
+  echo "$1 is not a valid executable file"
+  exit 1
+fi
+
+exec awk -v prog="$1" \
+'
+BEGIN {
+  if (cmd == "")
+    cmd=ENVIRON["ADDR2LINE"];
+  if (cmd == "")
+    cmd="addr2line";
+
+  if (prog == "")
+    prog=ENVIRON["PROG"];
+
+  cmd=cmd " -f -s -e " prog;
+
+  for (i = 1; i < 100; i++) {
+    indents[">",i] = indents[">",i-1] "->"
+    indents[">",i-1] = indents[">",i-1] " "
+    indents["<",i] = indents["<",i-1] "  "
+    indents["<",i-1] = indents["<",i-1] " "
+    indents[" ",i] = indents[" ",i-1] "##"
+    indents[" ",i-1] = indents[" ",i-1] " "
+  }
+}
+
+function getptr(ptr)
+{
+  loc=locs[ptr];
+  name=names[ptr];
+  if (loc == "" || name == "") {
+    print ptr |& cmd;
+    cmd |& getline name;
+    cmd |& getline loc;
+    names[ptr]=name
+    locs[ptr]=loc
+  }
+}
+
+{
+  # input format: <timestamp> <level> <caller> <dir> <callee> [<ret>|<args>...]
+  if ($3 == "#") { # this is a trace comment
+    printf "%s %s  ", $1, indents[" ",$2]
+    $1=""; $2=""; $3=""
+    print substr($0,4)
+    next
+  }
+  getptr($3); caller_loc=loc; caller_name=name
+  getptr($5); callee_loc=loc; callee_name=name
+  printf "%s %s  %s %s %s(%s) [%s:%s] %s [%s:%s]\n",
+    $1, indents[$4,$2], caller_name, $4, callee_name, $6, caller_loc, $3, $4, callee_loc, $5
+}
+'