summaryrefslogtreecommitdiffstats
path: root/dev
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:35:11 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:35:11 +0000
commitda76459dc21b5af2449af2d36eb95226cb186ce2 (patch)
tree542ebb3c1e796fac2742495b8437331727bbbfa0 /dev
parentInitial commit. (diff)
downloadhaproxy-upstream.tar.xz
haproxy-upstream.zip
Adding upstream version 2.6.12.upstream/2.6.12upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dev')
-rw-r--r--dev/base64/base64rev-gen.c70
-rw-r--r--dev/coccinelle/bug_on.cocci7
-rw-r--r--dev/coccinelle/cs_endp_flags.cocci76
-rw-r--r--dev/coccinelle/endp_flags.cocci76
-rw-r--r--dev/coccinelle/ha_free.cocci6
-rw-r--r--dev/coccinelle/ist.cocci86
-rw-r--r--dev/coccinelle/realloc_leak.cocci6
-rw-r--r--dev/coccinelle/strcmp.cocci309
-rw-r--r--dev/coccinelle/xalloc_cast.cocci11
-rw-r--r--dev/coccinelle/xalloc_size.cocci41
-rw-r--r--dev/flags/README12
-rw-r--r--dev/flags/flags.c496
-rwxr-xr-xdev/flags/show-fd-to-flags.sh2
-rw-r--r--dev/haring/README4
-rw-r--r--dev/haring/haring.c271
-rw-r--r--dev/hpack/README4
-rw-r--r--dev/hpack/decode.c215
-rw-r--r--dev/hpack/gen-enc.c205
-rw-r--r--dev/hpack/gen-rht.c369
-rw-r--r--dev/plug_qdisc/README59
-rw-r--r--dev/plug_qdisc/plug_qdisc.c86
-rw-r--r--dev/poll/Makefile11
-rw-r--r--dev/poll/poll.c365
-rw-r--r--dev/tcploop/Makefile11
-rw-r--r--dev/tcploop/tcploop.c956
-rwxr-xr-xdev/trace/trace.awk78
-rw-r--r--dev/udp/udp-perturb.c519
27 files changed, 4351 insertions, 0 deletions
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/coccinelle/bug_on.cocci b/dev/coccinelle/bug_on.cocci
new file mode 100644
index 0000000..3837879
--- /dev/null
+++ b/dev/coccinelle/bug_on.cocci
@@ -0,0 +1,7 @@
+@@
+expression E;
+@@
+
+- if (E)
+- ABORT_NOW();
++ BUG_ON(E);
diff --git a/dev/coccinelle/cs_endp_flags.cocci b/dev/coccinelle/cs_endp_flags.cocci
new file mode 100644
index 0000000..639d321
--- /dev/null
+++ b/dev/coccinelle/cs_endp_flags.cocci
@@ -0,0 +1,76 @@
+@@
+struct conn_stream *cs;
+expression e;
+@@
+(
+- (cs->endp->flags & (e))
++ sc_ep_test(cs, e)
+|
+- (cs->endp->flags & e)
++ sc_ep_test(cs, e)
+|
+- cs->endp->flags & (e)
++ sc_ep_test(cs, e)
+|
+- cs->endp->flags & e
++ sc_ep_test(cs, e)
+)
+
+@@
+struct conn_stream *cs;
+expression e;
+@@
+(
+- cs->endp->flags |= (e)
++ sc_ep_set(cs, e)
+|
+- cs->endp->flags |= e
++ sc_ep_set(cs, e)
+)
+
+@@
+struct conn_stream *cs;
+expression e;
+@@
+(
+- cs->endp->flags &= ~(e)
++ sc_ep_clr(cs, e)
+|
+- cs->endp->flags &= (e)
++ sc_ep_clr(cs, ~e)
+|
+- cs->endp->flags &= ~e
++ sc_ep_clr(cs, e)
+|
+- cs->endp->flags &= e
++ sc_ep_clr(cs, ~e)
+)
+
+@@
+struct conn_stream *cs;
+@@
+- cs->endp->flags = 0
++ sc_ep_zero(cs)
+
+@@
+struct conn_stream *cs;
+expression e;
+@@
+(
+- cs->endp->flags = (e)
++ sc_ep_setall(cs, e)
+|
+- cs->endp->flags = e
++ sc_ep_setall(cs, e)
+)
+
+@@
+struct conn_stream *cs;
+@@
+(
+- (cs->endp->flags)
++ sc_ep_get(cs)
+|
+- cs->endp->flags
++ sc_ep_get(cs)
+)
diff --git a/dev/coccinelle/endp_flags.cocci b/dev/coccinelle/endp_flags.cocci
new file mode 100644
index 0000000..fceda27
--- /dev/null
+++ b/dev/coccinelle/endp_flags.cocci
@@ -0,0 +1,76 @@
+@@
+struct cs_endpoint *endp;
+expression e;
+@@
+(
+- (endp->flags & (e))
++ se_fl_test(endp, e)
+|
+- (endp->flags & e)
++ se_fl_test(endp, e)
+|
+- endp->flags & (e)
++ se_fl_test(endp, e)
+|
+- endp->flags & e
++ se_fl_test(endp, e)
+)
+
+@@
+struct cs_endpoint *endp;
+expression e;
+@@
+(
+- endp->flags |= (e)
++ se_fl_set(endp, e)
+|
+- endp->flags |= e
++ se_fl_set(endp, e)
+)
+
+@@
+struct cs_endpoint *endp;
+expression e;
+@@
+(
+- endp->flags &= ~(e)
++ se_fl_clr(endp, e)
+|
+- endp->flags &= (e)
++ se_fl_clr(endp, ~e)
+|
+- endp->flags &= ~e
++ se_fl_clr(endp, e)
+|
+- endp->flags &= e
++ se_fl_clr(endp, ~e)
+)
+
+@@
+struct cs_endpoint *endp;
+@@
+- endp->flags = 0
++ se_fl_zero(endp)
+
+@@
+struct cs_endpoint *endp;
+expression e;
+@@
+(
+- endp->flags = (e)
++ se_fl_setall(endp, e)
+|
+- endp->flags = e
++ se_fl_setall(endp, e)
+)
+
+@@
+struct cs_endpoint *endp;
+@@
+(
+- (endp->flags)
++ se_fl_get(endp)
+|
+- endp->flags
++ se_fl_get(endp)
+)
diff --git a/dev/coccinelle/ha_free.cocci b/dev/coccinelle/ha_free.cocci
new file mode 100644
index 0000000..0019039
--- /dev/null
+++ b/dev/coccinelle/ha_free.cocci
@@ -0,0 +1,6 @@
+@ rule @
+expression E;
+@@
+- free(E);
+- E = NULL;
++ ha_free(&E);
diff --git a/dev/coccinelle/ist.cocci b/dev/coccinelle/ist.cocci
new file mode 100644
index 0000000..acde626
--- /dev/null
+++ b/dev/coccinelle/ist.cocci
@@ -0,0 +1,86 @@
+@@
+struct ist i;
+expression p, l;
+@@
+
+(
+- i.ptr = p;
+- i.len = strlen(i.ptr);
++ i = ist(p);
+|
+- i.ptr = p;
+- i.len = l;
++ i = ist2(p, l);
+)
+
+@@
+@@
+
+- ist2(NULL, 0)
++ IST_NULL
+
+@@
+struct ist i;
+expression e;
+@@
+
+- i.ptr += e;
+- i.len -= e;
++ i = istadv(i, e);
+
+@@
+struct ist i;
+@@
+
+- i = istadv(i, 1);
++ i = istnext(i);
+
+@@
+struct ist i;
+@@
+
+- i.ptr++;
+- i.len--;
++ i = istnext(i);
+
+@@
+struct ist i;
+@@
+
+- (\(i.ptr\|istptr(i)\) + \(i.len\|istlen(i)\))
++ istend(i)
+
+@@
+struct ist i;
+expression e;
+@@
+
+- if (\(i.len\|istlen(i)\) > e) { i.len = e; }
++ i = isttrim(i, e);
+
+@@
+struct ist i;
+struct buffer *b;
+@@
+
+- chunk_memcat(b, \(i.ptr\|istptr(i)\) , \(i.len\|istlen(i)\));
++ chunk_istcat(b, i);
+
+@@
+struct ist i;
+@@
+
+- i.ptr != NULL
++ isttest(i)
+
+@@
+char *s;
+@@
+
+(
+- ist2(s, strlen(s))
++ ist(s)
+|
+- ist2(strdup(s), strlen(s))
++ ist(strdup(s))
+)
diff --git a/dev/coccinelle/realloc_leak.cocci b/dev/coccinelle/realloc_leak.cocci
new file mode 100644
index 0000000..c201b80
--- /dev/null
+++ b/dev/coccinelle/realloc_leak.cocci
@@ -0,0 +1,6 @@
+@@
+expression E;
+expression F;
+@@
+
+* E = realloc(E, F);
diff --git a/dev/coccinelle/strcmp.cocci b/dev/coccinelle/strcmp.cocci
new file mode 100644
index 0000000..f6064bf
--- /dev/null
+++ b/dev/coccinelle/strcmp.cocci
@@ -0,0 +1,309 @@
+@@
+statement S;
+expression E;
+expression F;
+@@
+
+ if (
+(
+dns_hostname_cmp
+|
+eb_memcmp
+|
+memcmp
+|
+strcasecmp
+|
+strcmp
+|
+strncasecmp
+|
+strncmp
+)
+- (E, F)
++ (E, F) != 0
+ )
+(
+ S
+|
+ { ... }
+)
+
+@@
+statement S;
+expression E;
+expression F;
+@@
+
+ if (
+- !
+(
+dns_hostname_cmp
+|
+eb_memcmp
+|
+memcmp
+|
+strcasecmp
+|
+strcmp
+|
+strncasecmp
+|
+strncmp
+)
+- (E, F)
++ (E, F) == 0
+ )
+(
+ S
+|
+ { ... }
+)
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+
+(
+G &&
+(
+dns_hostname_cmp
+|
+eb_memcmp
+|
+memcmp
+|
+strcasecmp
+|
+strcmp
+|
+strncasecmp
+|
+strncmp
+)
+- (E, F)
++ (E, F) != 0
+)
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+
+(
+G ||
+(
+dns_hostname_cmp
+|
+eb_memcmp
+|
+memcmp
+|
+strcasecmp
+|
+strcmp
+|
+strncasecmp
+|
+strncmp
+)
+- (E, F)
++ (E, F) != 0
+)
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+
+(
+(
+dns_hostname_cmp
+|
+eb_memcmp
+|
+memcmp
+|
+strcasecmp
+|
+strcmp
+|
+strncasecmp
+|
+strncmp
+)
+- (E, F)
++ (E, F) != 0
+&& G
+)
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+
+(
+(
+dns_hostname_cmp
+|
+eb_memcmp
+|
+memcmp
+|
+strcasecmp
+|
+strcmp
+|
+strncasecmp
+|
+strncmp
+)
+- (E, F)
++ (E, F) != 0
+|| G
+)
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+
+(
+G &&
+- !
+(
+dns_hostname_cmp
+|
+eb_memcmp
+|
+memcmp
+|
+strcasecmp
+|
+strcmp
+|
+strncasecmp
+|
+strncmp
+)
+- (E, F)
++ (E, F) == 0
+)
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+
+(
+G ||
+- !
+(
+dns_hostname_cmp
+|
+eb_memcmp
+|
+memcmp
+|
+strcasecmp
+|
+strcmp
+|
+strncasecmp
+|
+strncmp
+)
+- (E, F)
++ (E, F) == 0
+)
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+
+(
+- !
+(
+dns_hostname_cmp
+|
+eb_memcmp
+|
+memcmp
+|
+strcasecmp
+|
+strcmp
+|
+strncasecmp
+|
+strncmp
+)
+- (E, F)
++ (E, F) == 0
+&& G
+)
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+
+(
+- !
+(
+dns_hostname_cmp
+|
+eb_memcmp
+|
+memcmp
+|
+strcasecmp
+|
+strcmp
+|
+strncasecmp
+|
+strncmp
+)
+- (E, F)
++ (E, F) == 0
+|| G
+)
+
+@@
+expression E;
+expression F;
+expression G;
+@@
+
+(
+- !
+(
+dns_hostname_cmp
+|
+eb_memcmp
+|
+memcmp
+|
+strcasecmp
+|
+strcmp
+|
+strncasecmp
+|
+strncmp
+)
+- (E, F)
++ (E, F) == 0
+)
diff --git a/dev/coccinelle/xalloc_cast.cocci b/dev/coccinelle/xalloc_cast.cocci
new file mode 100644
index 0000000..75baa00
--- /dev/null
+++ b/dev/coccinelle/xalloc_cast.cocci
@@ -0,0 +1,11 @@
+@@
+type T;
+@@
+
+- (T*)
+(
+malloc
+|
+calloc
+)
+ (...)
diff --git a/dev/coccinelle/xalloc_size.cocci b/dev/coccinelle/xalloc_size.cocci
new file mode 100644
index 0000000..80808e3
--- /dev/null
+++ b/dev/coccinelle/xalloc_size.cocci
@@ -0,0 +1,41 @@
+@@
+type T;
+expression E;
+expression t;
+@@
+
+(
+ t = calloc(E, sizeof(*t))
+|
+- t = calloc(E, sizeof(T))
++ t = calloc(E, sizeof(*t))
+)
+
+@@
+type T;
+T *x;
+@@
+
+ x = malloc(
+- sizeof(T)
++ sizeof(*x)
+ )
+
+@@
+type T;
+T *x;
+@@
+
+ x = calloc(1,
+- sizeof(T)
++ sizeof(*x)
+ )
+
+@@
+@@
+
+ calloc(
++ 1,
+ ...
+- ,1
+ )
diff --git a/dev/flags/README b/dev/flags/README
new file mode 100644
index 0000000..f3730c7
--- /dev/null
+++ b/dev/flags/README
@@ -0,0 +1,12 @@
+This needs to be built from the top makefile, for example :
+
+ make dev/flags/flags
+
+Then the executable is usable either one value at a time from the
+command line, either with values coming from stdin with "-" passed
+alone instead of the value.
+
+It is possible to restrict the decoding to certain fields only by
+specifying one of "ana", "chn", "conn", "sc", "si", "sierr", "strm",
+"task", or "txn" before the value.
+
diff --git a/dev/flags/flags.c b/dev/flags/flags.c
new file mode 100644
index 0000000..422f2fd
--- /dev/null
+++ b/dev/flags/flags.c
@@ -0,0 +1,496 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <haproxy/channel-t.h>
+#include <haproxy/connection-t.h>
+#include <haproxy/stconn-t.h>
+#include <haproxy/http_ana-t.h>
+#include <haproxy/stream-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_SC 0x00000008
+#define SHOW_AS_SET 0x00000010
+#define SHOW_AS_STRM 0x00000020
+#define SHOW_AS_TASK 0x00000040
+#define SHOW_AS_TXN 0x00000080
+#define SHOW_AS_SD 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", "sc", "stet", "strm", "task", "txn", "sd", };
+
+#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_WAIT_CLI);
+ 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_WAIT_CLI);
+ 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_SESS_IDLE);
+ SHOW_FLAG(f, CO_FL_RCVD_PROXY);
+ SHOW_FLAG(f, CO_FL_PRIVATE);
+ SHOW_FLAG(f, CO_FL_SSL_WAIT_HS);
+ SHOW_FLAG(f, CO_FL_ACCEPT_CIP);
+ SHOW_FLAG(f, CO_FL_ACCEPT_PROXY);
+ 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_FDLESS);
+ 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_WAIT_ROOM);
+ SHOW_FLAG(f, CO_FL_WANT_DRAIN);
+ 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_sd_flags(unsigned int f)
+{
+ printf("sd->flags = ");
+ if (!f) {
+ printf("0\n");
+ return;
+ }
+
+ SHOW_FLAG(f, SE_FL_APPLET_NEED_CONN);
+ SHOW_FLAG(f, SE_FL_HAVE_NO_DATA);
+ SHOW_FLAG(f, SE_FL_WONT_CONSUME);
+ SHOW_FLAG(f, SE_FL_WAIT_DATA);
+ SHOW_FLAG(f, SE_FL_KILL_CONN);
+ SHOW_FLAG(f, SE_FL_WAIT_FOR_HS);
+ SHOW_FLAG(f, SE_FL_WANT_ROOM);
+ SHOW_FLAG(f, SE_FL_RCV_MORE);
+ SHOW_FLAG(f, SE_FL_MAY_SPLICE);
+ SHOW_FLAG(f, SE_FL_ERR_PENDING);
+ SHOW_FLAG(f, SE_FL_ERROR);
+ SHOW_FLAG(f, SE_FL_EOS);
+ SHOW_FLAG(f, SE_FL_EOI);
+ SHOW_FLAG(f, SE_FL_WEBSOCKET);
+ SHOW_FLAG(f, SE_FL_NOT_FIRST);
+ SHOW_FLAG(f, SE_FL_SHWS);
+ SHOW_FLAG(f, SE_FL_SHWN);
+ SHOW_FLAG(f, SE_FL_SHRR);
+ SHOW_FLAG(f, SE_FL_SHRD);
+ SHOW_FLAG(f, SE_FL_ORPHAN);
+ SHOW_FLAG(f, SE_FL_DETACHED);
+ SHOW_FLAG(f, SE_FL_T_APPLET);
+ SHOW_FLAG(f, SE_FL_T_MUX);
+
+ if (f) {
+ printf("EXTRA(0x%08x)", f);
+ }
+ putchar('\n');
+}
+void show_sc_flags(unsigned int f)
+{
+ printf("sc->flags = ");
+ if (!f) {
+ printf("0\n");
+ return;
+ }
+ SHOW_FLAG(f, SC_FL_NEED_ROOM);
+ SHOW_FLAG(f, SC_FL_NEED_BUFF);
+ SHOW_FLAG(f, SC_FL_WONT_READ);
+ SHOW_FLAG(f, SC_FL_INDEP_STR);
+ SHOW_FLAG(f, SC_FL_DONT_WAKE);
+ SHOW_FLAG(f, SC_FL_NOHALF);
+ SHOW_FLAG(f, SC_FL_NOLINGER);
+ SHOW_FLAG(f, SC_FL_ISBACK);
+
+ if (f) {
+ printf("EXTRA(0x%08x)", f);
+ }
+ putchar('\n');
+}
+
+void show_strm_et(unsigned int f)
+{
+ printf("strm->et = ");
+ if (!f) {
+ printf("STRM_ET_NONE\n");
+ return;
+ }
+
+ SHOW_FLAG(f, STRM_ET_QUEUE_TO);
+ SHOW_FLAG(f, STRM_ET_QUEUE_ERR);
+ SHOW_FLAG(f, STRM_ET_QUEUE_ABRT);
+ SHOW_FLAG(f, STRM_ET_CONN_TO);
+ SHOW_FLAG(f, STRM_ET_CONN_ERR);
+ SHOW_FLAG(f, STRM_ET_CONN_ABRT);
+ SHOW_FLAG(f, STRM_ET_CONN_RES);
+ SHOW_FLAG(f, STRM_ET_CONN_OTHER);
+ SHOW_FLAG(f, STRM_ET_DATA_TO);
+ SHOW_FLAG(f, STRM_ET_DATA_ERR);
+ SHOW_FLAG(f, STRM_ET_DATA_ABRT);
+
+ 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_F_USR1);
+ SHOW_FLAG(f, TASK_F_TASKLET);
+ 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_HEAVY);
+ SHOW_FLAG(f, TASK_IN_LIST);
+ SHOW_FLAG(f, TASK_KILLED);
+ SHOW_FLAG(f, TASK_SELF_WAKING);
+ SHOW_FLAG(f, TASK_SHARED_WQ);
+ SHOW_FLAG(f, TASK_QUEUED);
+ SHOW_FLAG(f, TASK_GLOBAL);
+ 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_L7_RETRY);
+ SHOW_FLAG(f, TX_D_L7_RETRY);
+ SHOW_FLAG(f, TX_NOT_FIRST);
+ SHOW_FLAG(f, TX_USE_PX_CONN);
+ SHOW_FLAG(f, TX_CACHE_HAS_SEC_KEY);
+ SHOW_FLAG(f, TX_CON_WANT_TUN);
+
+ SHOW_FLAG(f, TX_CACHE_IGNORE);
+ 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);
+ SHOW_FLAG(f, TX_CONST_REPLY);
+
+ 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_SRC_ADDR);
+ SHOW_FLAG(f, SF_WEBSOCKET);
+ SHOW_FLAG(f, SF_SRV_REUSED_ANTICIPATED);
+ 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_CONN_EXP);
+ 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_ASSIGNED);
+ SHOW_FLAG(f, SF_DIRECT);
+
+ if (f) {
+ printf("EXTRA(0x%08x)", f);
+ }
+ putchar('\n');
+}
+
+void usage_exit(const char *name)
+{
+ int word, nbword;
+
+ fprintf(stderr, "Usage: %s [", name);
+
+ nbword = sizeof(show_as_words) / sizeof(*show_as_words);
+ for (word = 0; word < nbword; word++)
+ fprintf(stderr, "%s%s", word ? "|" : "", show_as_words[word]);
+ fprintf(stderr, "]* { [+-][0x]value* | - }\n");
+ 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++;
+ *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_SC) show_sc_flags(flags);
+ if (show_as & SHOW_AS_SD) show_sd_flags(flags);
+ if (show_as & SHOW_AS_SET) show_strm_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/haring/README b/dev/haring/README
new file mode 100644
index 0000000..69d08a8
--- /dev/null
+++ b/dev/haring/README
@@ -0,0 +1,4 @@
+This needs to be built from the top makefile, for example :
+
+ make dev/haring/haring
+
diff --git a/dev/haring/haring.c b/dev/haring/haring.c
new file mode 100644
index 0000000..53352cb
--- /dev/null
+++ b/dev/haring/haring.c
@@ -0,0 +1,271 @@
+/*
+ * post-mortem ring reader for haproxy
+ *
+ * Copyright (C) 2022 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/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <haproxy/api.h>
+#include <haproxy/buf.h>
+#include <haproxy/ring.h>
+
+int force = 0; // force access to a different layout
+int lfremap = 0; // remap LF in traces
+int repair = 0; // repair file
+
+
+/* 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]* <file>\n"
+ "\n"
+ "options :\n"
+ " -f : force accessing a non-matching layout for 'ring struct'\n"
+ " -l : replace LF in contents with CR VT\n"
+ " -r : \"repair\" corrupted file (actively search for message boundaries)\n"
+ "\n"
+ "", arg0);
+}
+
+/* This function dumps all events from the ring whose pointer is in <p0> into
+ * the appctx's output buffer, and takes from <o0> the seek offset into the
+ * buffer's history (0 for oldest known event). It looks at <i0> for boolean
+ * options: bit0 means it must wait for new data or any key to be pressed. Bit1
+ * means it must seek directly to the end to wait for new contents. It returns
+ * 0 if the output buffer or events are missing is full and it needs to be
+ * called again, otherwise non-zero. It is meant to be used with
+ * cli_release_show_ring() to clean up.
+ */
+int dump_ring(struct ring *ring, size_t ofs, int flags)
+{
+ struct buffer buf;
+ uint64_t msg_len = 0;
+ size_t len, cnt;
+ const char *blk1 = NULL, *blk2 = NULL, *p;
+ size_t len1 = 0, len2 = 0, bl;
+
+ /* Explanation: the storage area in the writing process starts after
+ * the end of the structure. Since the whole area is mmapped(), we know
+ * it starts at 0 mod 4096, hence the buf->area pointer's 12 LSB point
+ * to the relative offset of the storage area. As there will always be
+ * users using the wrong version of the tool with a dump, we need to
+ * run a few checks first. After that we'll create our own buffer
+ * descriptor matching that area.
+ */
+ if ((((long)ring->buf.area) & 4095) != sizeof(*ring)) {
+ if (!force) {
+ fprintf(stderr, "FATAL: header in file is %ld bytes long vs %ld expected!\n",
+ (((long)ring->buf.area) & 4095),
+ (long)sizeof(*ring));
+ exit(1);
+ }
+ else {
+ fprintf(stderr, "WARNING: header in file is %ld bytes long vs %ld expected!\n",
+ (((long)ring->buf.area) & 4095),
+ (long)sizeof(*ring));
+ }
+ /* maybe we could emit a warning at least ? */
+ }
+
+ /* Now make our own buffer pointing to that area */
+ buf = b_make(((void *)ring + (((long)ring->buf.area) & 4095)),
+ ring->buf.size, ring->buf.head, ring->buf.data);
+
+ /* explanation for the initialization below: it would be better to do
+ * this in the parsing function but this would occasionally result in
+ * dropped events because we'd take a reference on the oldest message
+ * and keep it while being scheduled. Thus instead let's take it the
+ * first time we enter here so that we have a chance to pass many
+ * existing messages before grabbing a reference to a location. This
+ * value cannot be produced after initialization.
+ */
+ if (unlikely(ofs == ~0)) {
+ ofs = 0;
+
+ /* going to the end means looking at tail-1 */
+ if (flags & RING_WF_SEEK_NEW)
+ ofs += b_data(&buf) - 1;
+
+ //HA_ATOMIC_INC(b_peek(&buf, ofs));
+ ofs += ring->ofs;
+ }
+
+ while (1) {
+ //HA_RWLOCK_RDLOCK(LOGSRV_LOCK, &ring->lock);
+
+ /* we were already there, adjust the offset to be relative to
+ * the buffer's head and remove us from the counter.
+ */
+ ofs -= ring->ofs;
+ if (ofs >= buf.size) {
+ fprintf(stderr, "FATAL error at %d\n", __LINE__);
+ return 1;
+ }
+ //HA_ATOMIC_DEC(b_peek(&buf, ofs));
+
+ /* in this loop, ofs always points to the counter byte that precedes
+ * the message so that we can take our reference there if we have to
+ * stop before the end.
+ */
+ while (ofs + 1 < b_data(&buf)) {
+ if (unlikely(repair && *b_peek(&buf, ofs))) {
+ /* in repair mode we consider that we could have landed
+ * in the middle of a message so we skip all bytes till
+ * the next zero.
+ */
+ ofs++;
+ continue;
+ }
+ cnt = 1;
+ len = b_peek_varint(&buf, ofs + cnt, &msg_len);
+ if (!len)
+ break;
+ cnt += len;
+
+ if (msg_len + ofs + cnt + 1 > buf.data) {
+ fprintf(stderr, "FATAL error at %d\n", __LINE__);
+ return 1;
+ }
+
+ len = b_getblk_nc(&buf, &blk1, &len1, &blk2, &len2, ofs + cnt, msg_len);
+ if (!lfremap) {
+ if (len > 0 && len1)
+ fwrite(blk1, len1, 1, stdout);
+ if (len > 1 && len2)
+ fwrite(blk2, len2, 1, stdout);
+ } else {
+ while (len > 0) {
+ for (; len1; p++) {
+ p = memchr(blk1, '\n', len1);
+ if (!p || p > blk1) {
+ bl = p ? p - blk1 : len1;
+ fwrite(blk1, bl, 1, stdout);
+ blk1 += bl;
+ len1 -= bl;
+ }
+
+ if (p) {
+ putchar('\r');
+ putchar('\v');
+ blk1++;
+ len1--;
+ }
+ }
+ len--;
+ blk1 = blk2;
+ len1 = len2;
+ }
+ }
+
+ putchar('\n');
+
+ ofs += cnt + msg_len;
+ }
+
+ //HA_ATOMIC_INC(b_peek(&buf, ofs));
+ ofs += ring->ofs;
+ //HA_RWLOCK_RDUNLOCK(LOGSRV_LOCK, &ring->lock);
+
+ if (!(flags & RING_WF_WAIT_MODE))
+ break;
+
+ /* pause 10ms before checking for new stuff */
+ usleep(10000);
+ }
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ struct ring *ring;
+ struct stat statbuf;
+ const char *arg0;
+ int fd;
+
+ arg0 = argv[0];
+ while (argc > 1 && argv[1][0] == '-') {
+ argc--; argv++;
+ if (strcmp(argv[0], "-f") == 0)
+ force = 1;
+ else if (strcmp(argv[0], "-l") == 0)
+ lfremap = 1;
+ else if (strcmp(argv[0], "-r") == 0)
+ repair = 1;
+ else if (strcmp(argv[0], "--") == 0)
+ break;
+ else
+ usage(1, arg0);
+ }
+
+ if (argc < 2)
+ usage(1, arg0);
+
+ fd = open(argv[1], O_RDONLY);
+ if (fd < 0) {
+ perror("open()");
+ return 1;
+ }
+
+ if (fstat(fd, &statbuf) < 0) {
+ perror("fstat()");
+ return 1;
+ }
+
+ ring = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ close(fd);
+
+ if (ring == MAP_FAILED) {
+ perror("mmap()");
+ return 1;
+ }
+
+ return dump_ring(ring, ~0, 0);
+}
+
+
+/*
+ * Local variables:
+ * c-indent-level: 8
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/dev/hpack/README b/dev/hpack/README
new file mode 100644
index 0000000..d7258b5
--- /dev/null
+++ b/dev/hpack/README
@@ -0,0 +1,4 @@
+This needs to be built from the top makefile, for example :
+
+ make dev/hpack/{decode,gen-enc,gen-rht}
+
diff --git a/dev/hpack/decode.c b/dev/hpack/decode.c
new file mode 100644
index 0000000..13c95c7
--- /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];
+
+THREAD_LOCAL 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
+}
+'
diff --git a/dev/udp/udp-perturb.c b/dev/udp/udp-perturb.c
new file mode 100644
index 0000000..ca8c0dc
--- /dev/null
+++ b/dev/udp/udp-perturb.c
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2010-2022 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/tcp.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <limits.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#define MAXCONN 1
+
+const int zero = 0;
+const int one = 1;
+
+struct conn {
+ struct sockaddr_storage cli_addr;
+ int fd_bck;
+};
+
+struct errmsg {
+ char *msg;
+ int size;
+ int len;
+};
+
+struct sockaddr_storage frt_addr; // listen address
+struct sockaddr_storage srv_addr; // server address
+
+#define MAXPKTSIZE 16384
+#define MAXREORDER 20
+char trash[MAXPKTSIZE];
+
+/* history buffer, to resend random packets */
+struct {
+ char buf[MAXPKTSIZE];
+ size_t len;
+} history[MAXREORDER];
+int history_idx = 0;
+unsigned int rand_rate = 0;
+unsigned int corr_rate = 0;
+unsigned int corr_span = 1;
+unsigned int corr_base = 0;
+
+struct conn conns[MAXCONN]; // sole connection for now
+int fd_frt;
+
+int nbfd = 0;
+int nbconn = MAXCONN;
+
+
+/* display the message and exit with the code */
+__attribute__((noreturn)) void die(int code, const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+ exit(code);
+}
+
+/* Xorshift RNG */
+unsigned int prng_state = ~0U/3; // half bits set, but any seed will fit
+static inline unsigned int prng(unsigned int range)
+{
+ unsigned int x = prng_state;
+
+ x ^= x << 13;
+ x ^= x >> 17;
+ x ^= x << 5;
+ prng_state = x;
+ return ((unsigned long long)x * (range - 1) + x) >> 32;
+}
+
+/* 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 errmsg *err)
+{
+ char *port_str;
+ int port;
+
+ /* look for the addr/port delimiter, it's the last colon. */
+ if ((port_str = strrchr(str, ':')) == NULL)
+ port_str = str;
+ else
+ *port_str++ = 0;
+
+ port = atoi(port_str);
+ if (port <= 0 || port > 65535) {
+ err->len = snprintf(err->msg, err->size, "Missing/invalid port number: '%s'\n", port_str);
+ return -1;
+ }
+ *port_str = 0; // present an empty address if none was set
+
+ memset(ss, 0, sizeof(*ss));
+
+ if (strrchr(str, ':') != NULL) {
+ /* IPv6 address contains ':' */
+ ss->ss_family = AF_INET6;
+ ((struct sockaddr_in6 *)ss)->sin6_port = htons(port);
+
+ if (!inet_pton(ss->ss_family, str, &((struct sockaddr_in6 *)ss)->sin6_addr)) {
+ err->len = snprintf(err->msg, err->size, "Invalid IPv6 server address: '%s'", str);
+ return -1;
+ }
+ }
+ else {
+ ss->ss_family = AF_INET;
+ ((struct sockaddr_in *)ss)->sin_port = htons(port);
+
+ 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 IPv4 server name: '%s'", str);
+ return -1;
+ }
+ ((struct sockaddr_in *)ss)->sin_addr = *(struct in_addr *) *(he->h_addr_list);
+ }
+ }
+ return 0;
+}
+
+/* returns <0 with err in case of error or the front FD */
+int create_udp_listener(struct sockaddr_storage *addr, struct errmsg *err)
+{
+ int fd;
+
+ if ((fd = socket(addr->ss_family, SOCK_DGRAM, 0)) == -1) {
+ err->len = snprintf(err->msg, err->size, "socket(): '%s'", strerror(errno));
+ goto fail;
+ }
+
+ if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
+ err->len = snprintf(err->msg, err->size, "fcntl(O_NONBLOCK): '%s'", strerror(errno));
+ goto fail;
+ }
+
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)) == -1) {
+ err->len = snprintf(err->msg, err->size, "setsockopt(SO_REUSEADDR): '%s'", strerror(errno));
+ goto fail;
+ }
+
+#ifdef SO_REUSEPORT
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char *) &one, sizeof(one)) == -1) {
+ err->len = snprintf(err->msg, err->size, "setsockopt(SO_REUSEPORT): '%s'", strerror(errno));
+ goto fail;
+ }
+#endif
+ if (bind(fd, (struct sockaddr *)addr, addr->ss_family == AF_INET6 ?
+ sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) == -1) {
+ err->len = snprintf(err->msg, err->size, "bind(): '%s'", strerror(errno));
+ goto fail;
+ }
+
+ /* the socket is ready */
+ return fd;
+
+ fail:
+ if (fd > -1)
+ close(fd);
+ fd = -1;
+ return fd;
+}
+
+/* recompute pollfds using frt_fd and scanning nbconn connections.
+ * Returns the number of FDs in the set.
+ */
+int update_pfd(struct pollfd *pfd, int frt_fd, struct conn *conns, int nbconn)
+{
+ int nbfd = 0;
+ int i;
+
+ pfd[nbfd].fd = frt_fd;
+ pfd[nbfd].events = POLLIN;
+ nbfd++;
+
+ for (i = 0; i < nbconn; i++) {
+ if (conns[i].fd_bck < 0)
+ continue;
+ pfd[nbfd].fd = conns[i].fd_bck;
+ pfd[nbfd].events = POLLIN;
+ nbfd++;
+ }
+ return nbfd;
+}
+
+/* searches a connection using fd <fd> as back connection, returns it if found
+ * otherwise NULL.
+ */
+struct conn *conn_bck_lookup(struct conn *conns, int nbconn, int fd)
+{
+ int i;
+
+ for (i = 0; i < nbconn; i++) {
+ if (conns[i].fd_bck < 0)
+ continue;
+ if (conns[i].fd_bck == fd)
+ return &conns[i];
+ }
+ return NULL;
+}
+
+/* Try to establish a connection to <sa>. Return the fd or -1 in case of error */
+int add_connection(struct sockaddr_storage *ss)
+{
+ int fd;
+
+ fd = socket(ss->ss_family, SOCK_DGRAM, 0);
+ if (fd < 0)
+ goto fail;
+
+ if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1)
+ goto fail;
+
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1)
+ goto fail;
+
+ if (connect(fd, (struct sockaddr *)ss, ss->ss_family == AF_INET6 ?
+ sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) == -1) {
+ if (errno != EINPROGRESS)
+ goto fail;
+ }
+
+ return fd;
+ fail:
+ if (fd > -1)
+ close(fd);
+ return -1;
+}
+
+/* Handle a read operation on an front FD. Will either reuse the existing
+ * connection if the source is found, or will allocate a new one, possibly
+ * replacing the oldest one. Returns <0 on error or the number of bytes
+ * transmitted.
+ */
+int handle_frt(int fd, struct pollfd *pfd, struct conn *conns, int nbconn)
+{
+ struct sockaddr_storage addr;
+ socklen_t addrlen;
+ struct conn *conn;
+ char *pktbuf = trash;
+ int ret;
+ int i;
+
+ if (rand_rate > 0) {
+ /* keep a copy of this packet */
+ history_idx++;
+ if (history_idx >= MAXREORDER)
+ history_idx = 0;
+ pktbuf = history[history_idx].buf;
+ }
+
+ addrlen = sizeof(addr);
+ ret = recvfrom(fd, pktbuf, MAXPKTSIZE, MSG_DONTWAIT | MSG_NOSIGNAL,
+ (struct sockaddr *)&addr, &addrlen);
+
+ if (rand_rate > 0) {
+ history[history_idx].len = ret; // note: we may store -1/EAGAIN
+ if (prng(100) < rand_rate) {
+ /* return a random buffer or nothing */
+ int idx = prng(MAXREORDER + 1) - 1;
+ if (idx < 0) {
+ /* pretend we didn't receive anything */
+ return 0;
+ }
+ pktbuf = history[idx].buf;
+ ret = history[idx].len;
+ if (ret < 0)
+ errno = EAGAIN;
+ }
+ }
+
+ if (ret == 0)
+ return 0;
+
+ if (ret < 0)
+ return errno == EAGAIN ? 0 : -1;
+
+ if (corr_rate > 0 && prng(100) < corr_rate) {
+ unsigned int rnd = prng(corr_span * 256); // pos and value
+ unsigned int pos = corr_base + (rnd >> 8);
+
+ if (pos < ret)
+ pktbuf[pos] ^= rnd;
+ }
+
+ conn = NULL;
+ for (i = 0; i < nbconn; i++) {
+ if (addr.ss_family != conns[i].cli_addr.ss_family)
+ continue;
+ if (memcmp(&conns[i].cli_addr, &addr,
+ (addr.ss_family == AF_INET6) ?
+ sizeof(struct sockaddr_in6) :
+ sizeof(struct sockaddr_in)) != 0)
+ continue;
+ conn = &conns[i];
+ break;
+ }
+
+ if (!conn) {
+ /* address not found, create a new conn or replace the oldest
+ * one. For now we support a single one.
+ */
+ conn = &conns[0];
+
+ memcpy(&conn->cli_addr, &addr,
+ (addr.ss_family == AF_INET6) ?
+ sizeof(struct sockaddr_in6) :
+ sizeof(struct sockaddr_in));
+
+ if (conn->fd_bck < 0) {
+ /* try to create a new connection */
+ conn->fd_bck = add_connection(&srv_addr);
+ nbfd = update_pfd(pfd, fd, conns, nbconn); // FIXME: MAXCONN instead ?
+ }
+ }
+
+ if (conn->fd_bck < 0)
+ return 0;
+
+ ret = send(conn->fd_bck, pktbuf, ret, MSG_DONTWAIT | MSG_NOSIGNAL);
+ return ret;
+}
+
+/* Handle a read operation on an FD. Close and return 0 when the read returns zero or an error */
+int handle_bck(int fd, struct pollfd *pfd, struct conn *conns, int nbconn)
+{
+ struct sockaddr_storage addr;
+ socklen_t addrlen;
+ struct conn *conn;
+ char *pktbuf = trash;
+ int ret;
+
+ if (rand_rate > 0) {
+ /* keep a copy of this packet */
+ history_idx++;
+ if (history_idx >= MAXREORDER)
+ history_idx = 0;
+ pktbuf = history[history_idx].buf;
+ }
+
+ ret = recvfrom(fd, pktbuf, MAXPKTSIZE, MSG_DONTWAIT | MSG_NOSIGNAL,
+ (struct sockaddr *)&addr, &addrlen);
+
+ if (rand_rate > 0) {
+ history[history_idx].len = ret; // note: we may store -1/EAGAIN
+ if (prng(100) < rand_rate) {
+ /* return a random buffer or nothing */
+ int idx = prng(MAXREORDER + 1) - 1;
+ if (idx < 0) {
+ /* pretend we didn't receive anything */
+ return 0;
+ }
+ pktbuf = history[idx].buf;
+ ret = history[idx].len;
+ if (ret < 0)
+ errno = EAGAIN;
+ }
+ }
+
+ if (ret == 0)
+ return 0;
+
+ if (ret < 0)
+ return errno == EAGAIN ? 0 : -1;
+
+ conn = conn_bck_lookup(conns, nbconn, fd);
+ if (!conn)
+ return 0;
+
+ ret = sendto(fd_frt, pktbuf, ret, MSG_DONTWAIT | MSG_NOSIGNAL,
+ (struct sockaddr *)&conn->cli_addr,
+ conn->cli_addr.ss_family == AF_INET6 ?
+ sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in));
+ return ret;
+}
+
+/* print the usage message for program named <name> and exit with status <status> */
+void usage(int status, const char *name)
+{
+ if (strchr(name, '/'))
+ name = strrchr(name, '/') + 1;
+ die(status,
+ "Usage: %s [-h] [options] [<laddr>:]<lport> [<saddr>:]<sport>\n"
+ "Options:\n"
+ " -h display this help\n"
+ " -r rate reorder/duplicate/lose around <rate>%% of packets\n"
+ " -s seed force initial random seed (currently %#x)\n"
+ " -c rate corrupt around <rate>%% of packets\n"
+ " -o ofs start offset of corrupted area (def: 0)\n"
+ " -w width width of the corrupted area (def: 1)\n"
+ "", name, prng_state);
+}
+
+int main(int argc, char **argv)
+{
+ struct errmsg err;
+ struct pollfd *pfd;
+ int opt;
+ int i;
+
+ err.len = 0;
+ err.size = 100;
+ err.msg = malloc(err.size);
+
+ while ((opt = getopt(argc, argv, "hr:s:c:o:w:")) != -1) {
+ switch (opt) {
+ case 'r': // rand_rate%
+ rand_rate = atoi(optarg);
+ break;
+ case 's': // seed
+ prng_state = atol(optarg);
+ break;
+ case 'c': // corruption rate
+ corr_rate = atol(optarg);
+ break;
+ case 'o': // corruption offset
+ corr_base = atol(optarg);
+ break;
+ case 'w': // corruption width
+ corr_span = atol(optarg);
+ break;
+ default: // help, anything else
+ usage(0, argv[0]);
+ }
+ }
+
+ if (argc - optind < 2)
+ usage(1, argv[0]);
+
+ if (addr_to_ss(argv[optind], &frt_addr, &err) < 0)
+ die(1, "parsing listen address: %s\n", err.msg);
+
+ if (addr_to_ss(argv[optind+1], &srv_addr, &err) < 0)
+ die(1, "parsing server address: %s\n", err.msg);
+
+ pfd = calloc(sizeof(struct pollfd), MAXCONN + 1);
+ if (!pfd)
+ die(1, "out of memory\n");
+
+ fd_frt = create_udp_listener(&frt_addr, &err);
+ if (fd_frt < 0)
+ die(1, "binding listener: %s\n", err.msg);
+
+
+ for (i = 0; i < MAXCONN; i++)
+ conns[i].fd_bck = -1;
+
+ nbfd = update_pfd(pfd, fd_frt, conns, MAXCONN);
+
+ while (1) {
+ /* listen for incoming packets */
+ int ret, i;
+
+ ret = poll(pfd, nbfd, 1000);
+ if (ret <= 0)
+ continue;
+
+ for (i = 0; ret; i++) {
+ if (!pfd[i].revents)
+ continue;
+ ret--;
+
+ if (pfd[i].fd == fd_frt) {
+ handle_frt(pfd[i].fd, pfd, conns, nbconn);
+ continue;
+ }
+
+ handle_bck(pfd[i].fd, pfd, conns, nbconn);
+ }
+ }
+}