summaryrefslogtreecommitdiffstats
path: root/dev
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:18:05 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:18:05 +0000
commitb46aad6df449445a9fc4aa7b32bd40005438e3f7 (patch)
tree751aa858ca01f35de800164516b298887382919d /dev
parentInitial commit. (diff)
downloadhaproxy-b46aad6df449445a9fc4aa7b32bd40005438e3f7.tar.xz
haproxy-b46aad6df449445a9fc4aa7b32bd40005438e3f7.zip
Adding upstream version 2.9.5.upstream/2.9.5
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.c157
-rwxr-xr-xdev/flags/show-fd-to-flags.sh2
-rwxr-xr-xdev/flags/show-sess-to-flags.sh209
-rwxr-xr-xdev/h2/mkhdr.sh151
-rw-r--r--dev/haring/README9
-rw-r--r--dev/haring/haring.c266
-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/Makefile13
-rw-r--r--dev/poll/poll.c445
-rw-r--r--dev/qpack/decode.c171
-rw-r--r--dev/sslkeylogger/sslkeylogger.lua47
-rw-r--r--dev/tcploop/Makefile13
-rw-r--r--dev/tcploop/tcploop.c1055
-rwxr-xr-xdev/trace/trace.awk78
-rw-r--r--dev/udp/udp-perturb.c527
31 files changed, 4781 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..65af237
--- /dev/null
+++ b/dev/flags/flags.c
@@ -0,0 +1,157 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+/* make the include files below expose their flags */
+#define HA_EXPOSE_FLAGS
+
+#include <haproxy/channel-t.h>
+#include <haproxy/connection-t.h>
+#include <haproxy/fd-t.h>
+#include <haproxy/http_ana-t.h>
+#include <haproxy/htx-t.h>
+#include <haproxy/mux_fcgi-t.h>
+#include <haproxy/mux_h2-t.h>
+#include <haproxy/mux_h1-t.h>
+#include <haproxy/stconn-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
+#define SHOW_AS_HSL 0x00000200
+#define SHOW_AS_HTX 0x00000400
+#define SHOW_AS_HMSG 0x00000800
+#define SHOW_AS_FD 0x00001000
+#define SHOW_AS_H2C 0x00002000
+#define SHOW_AS_H2S 0x00004000
+#define SHOW_AS_H1C 0x00008000
+#define SHOW_AS_H1S 0x00010000
+#define SHOW_AS_FCONN 0x00020000
+#define SHOW_AS_FSTRM 0x00040000
+
+// 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", "hsl", "htx", "hmsg", "fd", "h2c", "h2s", "h1c", "h1s", "fconn", "fstrm"};
+
+/* will be sufficient for even largest flag names */
+static char buf[4096];
+static size_t bsz = sizeof(buf);
+
+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 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) printf("chn->ana = %s\n", (chn_show_analysers(buf, bsz, " | ", flags), buf));
+ if (show_as & SHOW_AS_CHN) printf("chn->flags = %s\n", (chn_show_flags (buf, bsz, " | ", flags), buf));
+ if (show_as & SHOW_AS_CONN) printf("conn->flags = %s\n", (conn_show_flags (buf, bsz, " | ", flags), buf));
+ if (show_as & SHOW_AS_SC) printf("sc->flags = %s\n", (sc_show_flags (buf, bsz, " | ", flags), buf));
+ if (show_as & SHOW_AS_SD) printf("sd->flags = %s\n", (se_show_flags (buf, bsz, " | ", flags), buf));
+ if (show_as & SHOW_AS_SET) printf("strm->et = %s\n", (strm_et_show_flags(buf, bsz, " | ", flags), buf));
+ if (show_as & SHOW_AS_STRM) printf("strm->flags = %s\n", (strm_show_flags (buf, bsz, " | ", flags), buf));
+ if (show_as & SHOW_AS_TASK) printf("task->state = %s\n", (task_show_state (buf, bsz, " | ", flags), buf));
+ if (show_as & SHOW_AS_TXN) printf("txn->flags = %s\n", (txn_show_flags (buf, bsz, " | ", flags), buf));
+ if (show_as & SHOW_AS_HSL) printf("sl->flags = %s\n", (hsl_show_flags (buf, bsz, " | ", flags), buf));
+ if (show_as & SHOW_AS_HTX) printf("htx->flags = %s\n", (htx_show_flags (buf, bsz, " | ", flags), buf));
+ if (show_as & SHOW_AS_HMSG) printf("hmsg->flags = %s\n", (hmsg_show_flags (buf, bsz, " | ", flags), buf));
+ if (show_as & SHOW_AS_FD) printf("fd->flags = %s\n", (fd_show_flags (buf, bsz, " | ", flags), buf));
+ if (show_as & SHOW_AS_H2C) printf("h2c->flags = %s\n", (h2c_show_flags (buf, bsz, " | ", flags), buf));
+ if (show_as & SHOW_AS_H2S) printf("h2s->flags = %s\n", (h2s_show_flags (buf, bsz, " | ", flags), buf));
+ if (show_as & SHOW_AS_H1C) printf("h1c->flags = %s\n", (h1c_show_flags (buf, bsz, " | ", flags), buf));
+ if (show_as & SHOW_AS_H1S) printf("h1s->flags = %s\n", (h1s_show_flags (buf, bsz, " | ", flags), buf));
+ if (show_as & SHOW_AS_FCONN) printf("fconn->flags = %s\n",(fconn_show_flags (buf, bsz, " | ", flags), buf));
+ if (show_as & SHOW_AS_FSTRM) printf("fstrm->flags = %s\n",(fstrm_show_flags (buf, bsz, " | ", flags), buf));
+ }
+ 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/flags/show-sess-to-flags.sh b/dev/flags/show-sess-to-flags.sh
new file mode 100755
index 0000000..79003a4
--- /dev/null
+++ b/dev/flags/show-sess-to-flags.sh
@@ -0,0 +1,209 @@
+#!/usr/bin/env bash
+
+# This script is used to resolve various flags that appear on "show sess all".
+# All identified ones will be appended at the end, with a short name and their
+# value, followed by either the value resolved by "flags" when it's found, or
+# by the copy-pastable command to use to resolve them. The path to FLAGS is
+# searched in this order: 1) $FLAGS, 2) in the path, 3) dev/flags/flags, 4)
+# in the same directory as the script.
+#
+# This script is horrendous, but it's not a reason for making it even more
+# disgusting. The big regex flag mapping mess at the end is readable on a
+# large screen and it's easier to spot mistakes using this aligned format,
+# so please preserve this as much as possible and avoid multi-line formats.
+#
+# The append_* functions provide different variants that are still commented
+# out. It's mostly a matter of taste, they're equivalent.
+#
+# Usage: socat /path/to/socket - <<< "show sess all" | ./$0 > output
+#
+# options:
+# --color=never, --no-color: never colorize output
+# --color=always: always colorize output (default: only on terminal)
+
+# look for "flags in path then in dev/flags/flags then next to the script"
+FLAGS="${FLAGS:-$(command -v flags)}"
+if [ -z "$FLAGS" ]; then
+ if [ -e dev/flags/flags ]; then
+ FLAGS=dev/flags/flags;
+ elif [ -e "${0%/*}/flags" ]; then
+ FLAGS="${0%/*}/flags"
+ else
+ # OK still not found,let's write a copy-pastable command
+ FLAGS="echo ./flags"
+ fi
+fi
+
+HTTP_METH=( "OPTIONS" "GET" "HEAD" "POST" "PUT" "DELETE" "TRACE" "CONNECT" "OTHER" )
+out=( )
+decode=( )
+
+# returns str $2 and $3 concatenated with enough spaces in between so that the
+# total size doesn't exceed $1 chars, but always inserts at least one space.
+justify() {
+ local pad=" "
+ local str
+
+ while str="${2}${pad}${3}" && [ ${#str} -le $1 ]; do
+ pad="${pad} "
+ done
+ echo -n "$str"
+}
+
+# remove spaces at the beginning and end in "$1"
+trim() {
+ while [ -n "$1" -a -z "${1## *}" ]; do
+ set -- "${1# }"
+ done
+ while [ -n "$1" -a -z "${1%%* }" ]; do
+ set -- "${1% }"
+ done
+ echo -n "$1"
+}
+
+# pass $1=ctx name, $2=argname, $3=value, append the decoded line to decode[]
+append_flag() {
+ set -- "$1" "$2" "$(printf "%#x" $3)"
+ #decode[${#decode[@]}]="$1=$3 [ $(set -- $($FLAGS $2 $3 | cut -f2- -d=); echo $*) ]"
+ #decode[${#decode[@]}]="$(printf "%-14s %10s %s" $1 $3 "$(set -- $($FLAGS $2 $3 | cut -f2- -d=); echo $*)")"
+ #decode[${#decode[@]}]="$(justify 22 "$1" "$3") $(set -- $($FLAGS $2 $3 | cut -f2- -d=); echo $*)"
+ decode[${#decode[@]}]="$(justify 22 "$1" "$3") $(set -- $($FLAGS $2 $3 | cut -f2- -d= | tr -d '|'); echo "$*")"
+ #decode[${#decode[@]}]="$(justify 22 "$1" "$3") $(set -- $($FLAGS $2 $(printf "%#x" $3) | cut -f2- -d= | tr -d '|'); echo "$*")"
+ #decode[${#decode[@]}]="$(justify 22 "$1" "$3") $(trim "$($FLAGS $2 $3 | cut -f2- -d= | tr -d '|')")"
+ #decode[${#decode[@]}]="$(justify 22 "$1" "$3") $(trim "$($FLAGS $2 $3 | cut -f2- -d= | tr -d ' ')")"
+}
+
+# pass $1=ctx name, $2=value, $3=decoded value
+append_str() {
+ #decode[${#decode[@]}]="$1=$2 [ $3 ]"
+ #decode[${#decode[@]}]="$(printf "%-14s %10s %s" $1 $2 $3)"
+ decode[${#decode[@]}]="$(justify 22 "$1" "$2") $(trim $3)"
+}
+
+# dump and reset the buffers
+dump_and_reset() {
+ local line
+
+ line=0
+ while [ $line -lt ${#out[@]} ]; do
+ if [ -n "$COLOR" ]; then
+ # highlight name=value for values made of upper case letters
+ echo "${out[$line]}" | \
+ sed -e 's,\(^0x.*\),\x1b[1;37m\1\x1b[0m,g' \
+ -e 's,\([^ ,=]*\)=\([A-Z][^:, ]*\),\x1b[1;36m\1\x1b[0m=\x1b[1;33m\2\x1b[0m,g'
+
+ else
+ echo "${out[$line]}"
+ fi
+ ((line++))
+ done
+
+ [ ${#decode[@]} -eq 0 ] || echo " -----------------------------------"
+
+ line=0
+ while [ $line -lt ${#decode[@]} ]; do
+ echo " ${decode[$line]}"
+ ((line++, total++))
+ done
+
+ [ ${#decode[@]} -eq 0 ] || echo " -----------------------------------"
+
+ decode=( )
+ out=( )
+}
+
+### main entry point
+
+if [ -t 1 ]; then
+ # terminal on stdout, enable color by default
+ COLOR=1
+else
+ COLOR=
+fi
+
+if [ "$1" == "--no-color" -o "$1" == "--color=never" ]; then
+ shift
+ COLOR=
+elif [ "$1" == "--color=always" ]; then
+ shift
+ COLOR=1
+fi
+
+ctx=strm
+while read -r; do
+ [ "$REPLY" != "EOF" ] || break # for debugging
+
+ if [[ "$REPLY" =~ ^[[:blank:]]*task= ]]; then
+ ctx=task;
+ elif [[ "$REPLY" =~ ^[[:blank:]]*txn= ]]; then
+ ctx=txn;
+ elif [[ "$REPLY" =~ ^[[:blank:]]*scf= ]]; then
+ ctx=scf;
+ elif [[ "$REPLY" =~ ^[[:blank:]]*co0= ]]; then
+ ctx=cof;
+ elif [[ "$REPLY" =~ ^[[:blank:]]*app0= ]]; then
+ ctx=appf;
+ elif [[ "$REPLY" =~ ^[[:blank:]]*req= ]]; then
+ ctx=req;
+ elif [[ "$REPLY" =~ ^[[:blank:]]*scb= ]]; then
+ ctx=scb;
+ elif [[ "$REPLY" =~ ^[[:blank:]]*co1= ]]; then
+ ctx=cob;
+ elif [[ "$REPLY" =~ ^[[:blank:]]*app1= ]]; then
+ ctx=appb;
+ elif [[ "$REPLY" =~ ^[[:blank:]]*res= ]]; then
+ ctx=res;
+ elif [[ "$REPLY" =~ ^0x ]]; then
+ # here we dump what we have and we reset
+ dump_and_reset
+ ctx=strm;
+ fi
+
+ if [ $ctx = strm ]; then
+ ! [[ "$REPLY" =~ [[:blank:]]flags=([0-9a-fx]*) ]] || append_flag strm.flg strm "${BASH_REMATCH[1]}"
+ elif [ $ctx = task ]; then
+ ! [[ "$REPLY" =~ \(state=([0-9a-fx]*) ]] || append_flag task.state task "${BASH_REMATCH[1]}"
+ elif [ $ctx = txn ]; then
+ ! [[ "$REPLY" =~ [[:blank:]]meth=([^[:blank:]]*) ]] || append_str txn.meth "${BASH_REMATCH[1]}" "${HTTP_METH[$((${BASH_REMATCH[1]}))]}"
+ ! [[ "$REPLY" =~ [[:blank:]]flags=([0-9a-fx]*) ]] || append_flag txn.flg txn "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]req\.f=([0-9a-fx]*) ]] || append_flag txn.req.flg hmsg "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]rsp\.f=([0-9a-fx]*) ]] || append_flag txn.rsp.flg hmsg "${BASH_REMATCH[1]}"
+ elif [ $ctx = scf ]; then
+ ! [[ "$REPLY" =~ [[:blank:]]flags=([0-9a-fx]*) ]] || append_flag f.sc.flg sc "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]endp=[[:alnum:]]*,[[:alnum:]]*,([0-9a-fx]*) ]] || append_flag f.sc.sd.flg sd "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]h1s.*\.sd\.flg=([0-9a-fx]*) ]] || append_flag f.h1s.sd.flg sd "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]h1s\.flg=([0-9a-fx]*) ]] || append_flag f.h1s.flg h1s "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]h1c\.flg=([0-9a-fx]*) ]] || append_flag f.h1c.flg h1c "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ ^[[:blank:]]*\.sc=.*\.flg=.*\.app=.*\.sd=[^=]*\.flg=([0-9a-fx]*) ]] || append_flag f.h2s.sd.flg sd "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]h2s.*\.flg=([0-9a-fx]*) ]] || append_flag f.h2s.flg h2s "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]h2c.*\.flg=([0-9a-fx]*) ]] || append_flag f.h2c.flg h2c "${BASH_REMATCH[1]}"
+ elif [ $ctx = cof ]; then
+ ! [[ "$REPLY" =~ [[:blank:]]flags=([0-9a-fx]*) ]] || append_flag f.co.flg conn "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]fd.state=([0-9a-fx]*) ]] || append_flag f.co.fd.st fd 0x"${BASH_REMATCH[1]#0x}"
+ elif [ $ctx = req ]; then
+ ! [[ "$REPLY" =~ [[:blank:]]\(f=([0-9a-fx]*) ]] || append_flag req.flg chn "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]an=([0-9a-fx]*) ]] || append_flag req.ana ana "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]htx.*flags=([0-9a-fx]*) ]] || append_flag req.htx.flg htx "${BASH_REMATCH[1]}"
+ elif [ $ctx = scb ]; then
+ ! [[ "$REPLY" =~ [[:blank:]]flags=([0-9a-fx]*) ]] || append_flag b.sc.flg sc "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]endp=[[:alnum:]]*,[[:alnum:]]*,([0-9a-fx]*) ]] || append_flag b.sc.sd.flg sd "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]h1s.*\.sd\.flg=([0-9a-fx]*) ]] || append_flag b.h1s.sd.flg sd "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]h1s\.flg=([0-9a-fx]*) ]] || append_flag b.h1s.flg h1s "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]h1c\.flg=([0-9a-fx]*) ]] || append_flag b.h1c.flg h1c "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ ^[[:blank:]]*\.sc=.*\.flg=.*\.app=.*\.sd=[^=]*\.flg=([0-9a-fx]*) ]] || append_flag b.h2s.sd.flg sd "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]h2s.*\.flg=([0-9a-fx]*) ]] || append_flag b.h2s.flg h2s "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]h2c.*\.flg=([0-9a-fx]*) ]] || append_flag b.h2c.flg h2c "${BASH_REMATCH[1]}"
+ elif [ $ctx = cob ]; then
+ ! [[ "$REPLY" =~ [[:blank:]]flags=([0-9a-fx]*) ]] || append_flag b.co.flg conn "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]fd.state=([0-9a-fx]*) ]] || append_flag b.co.fd.st fd "${BASH_REMATCH[1]}"
+ elif [ $ctx = res ]; then
+ ! [[ "$REPLY" =~ [[:blank:]]\(f=([0-9a-fx]*) ]] || append_flag res.flg chn "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]an=([0-9a-fx]*) ]] || append_flag res.ana ana "${BASH_REMATCH[1]}"
+ ! [[ "$REPLY" =~ [[:blank:]]htx.*flags=([0-9a-fx]*) ]] || append_flag res.htx.flg htx "${BASH_REMATCH[1]}"
+ fi
+
+ out[${#out[@]}]="$REPLY"
+done
+
+# dump the last stream
+dump_and_reset
diff --git a/dev/h2/mkhdr.sh b/dev/h2/mkhdr.sh
new file mode 100755
index 0000000..4d129fa
--- /dev/null
+++ b/dev/h2/mkhdr.sh
@@ -0,0 +1,151 @@
+#!/usr/bin/env bash
+
+# Usage: mkhdr -l <len> -t <type> -f <flags> -sid <sid> > hdr.bin
+# All fields are optional. 0 assumed when absent.
+
+USAGE=\
+"Usage: %s [-l <len> ] [-t <type>] [-f <flags>] [-i <sid>] [ -d <data> ] > hdr.bin
+ Numbers are decimal or 0xhex. Not set=0. If <data> is passed, it points
+ to a file that is read and chunked into frames of <len> bytes.
+
+Supported symbolic types (case insensitive prefix match):
+ DATA (0x00) PUSH_PROMISE (0x05)
+ HEADERS (0x01) PING (0x06)
+ PRIORITY (0x02) GOAWAY (0x07)
+ RST_STREAM (0x03) WINDOW_UPDATE (0x08)
+ SETTINGS (0x04) CONTINUATION (0x09)
+
+Supported symbolic flags (case insensitive prefix match):
+ ES (0x01) PAD (0x08)
+ EH (0x04) PRIO (0x20)
+
+"
+
+LEN=
+TYPE=
+FLAGS=
+ID=
+
+die() {
+ [ "$#" -eq 0 ] || echo "$*" >&2
+ exit 1
+}
+
+quit() {
+ [ "$#" -eq 0 ] || echo "$*"
+ exit 0
+}
+
+# print usage with $1 as the cmd name
+usage() {
+ printf "$USAGE" "$1";
+}
+
+# Send frame made of $1 $2 $3 $4 to stdout.
+# Usage: mkframe <len> <type> <flags> <id>
+mkframe() {
+ local L="${1:-0}"
+ local T="${2:-0}"
+ local F="${3:-0}"
+ local I="${4:-0}"
+ local t f
+
+ # get the first match in this order
+ for t in DATA:0x00 HEADERS:0x01 RST_STREAM:0x03 SETTINGS:0x04 PING:0x06 \
+ GOAWAY:0x07 WINDOW_UPDATE:0x08 CONTINUATION:0x09 PRIORITY:0x02 \
+ PUSH_PROMISE:0x05; do
+ if [ -z "${t##${T^^*}*}" ]; then
+ T="${t##*:}"
+ break
+ fi
+ done
+
+ if [ -n "${T##[0-9]*}" ]; then
+ echo "Unknown type '$T'" >&2
+ usage "${0##*}"
+ die
+ fi
+
+ # get the first match in this order
+ for f in ES:0x01 EH:0x04 PAD:0x08 PRIO:0x20; do
+ if [ -z "${f##${F^^*}*}" ]; then
+ F="${f##*:}"
+ fi
+ done
+
+ if [ -n "${F##[0-9]*}" ]; then
+ echo "Unknown type '$T'" >&2
+ usage "${0##*}"
+ die
+ fi
+
+ L=$(( L )); T=$(( T )); F=$(( F )); I=$(( I ))
+
+ L0=$(( (L >> 16) & 255 )); L0=$(printf "%02x" $L0)
+ L1=$(( (L >> 8) & 255 )); L1=$(printf "%02x" $L1)
+ L2=$(( (L >> 0) & 255 )); L2=$(printf "%02x" $L2)
+
+ T0=$(( (T >> 0) & 255 )); T0=$(printf "%02x" $T0)
+ F0=$(( (F >> 0) & 255 )); F0=$(printf "%02x" $F0)
+
+ I0=$(( (I >> 24) & 127 )); I0=$(printf "%02x" $I0)
+ I1=$(( (I >> 16) & 255 )); I1=$(printf "%02x" $I1)
+ I2=$(( (I >> 8) & 255 )); I2=$(printf "%02x" $I2)
+ I3=$(( (I >> 0) & 255 )); I3=$(printf "%02x" $I3)
+
+ printf "\x$L0\x$L1\x$L2\x$T0\x$F0\x$I0\x$I1\x$I2\x$I3"
+}
+
+## main
+
+if [ $# -le 1 ]; then
+ usage "${0##*}"
+ die
+fi
+
+while [ -n "$1" -a -z "${1##-*}" ]; do
+ case "$1" in
+ -l) LEN="$2" ; shift 2 ;;
+ -t) TYPE="$2" ; shift 2 ;;
+ -f) FLAGS="$2" ; shift 2 ;;
+ -i) ID="$2" ; shift 2 ;;
+ -d) DATA="$2" ; shift 2 ;;
+ -h|--help) usage "${0##*}"; quit;;
+ *) usage "${0##*}"; die ;;
+ esac
+done
+
+if [ $# -gt 0 ]; then
+ usage "${0##*}"
+ die
+fi
+
+# default values for LEN and ID
+LEN=${LEN:-0};
+if [ -n "${LEN##[0-9]*}" ]; then
+ echo "Unparsable length '$LEN'" >&2
+ usage "${0##*}"
+ die
+fi
+
+ID=${ID:-0};
+if [ -n "${ID##[0-9]*}" ]; then
+ echo "Unparsable stream ID '$ID'" >&2
+ usage "${0##*}"
+ die
+fi
+
+if [ -z "$DATA" ]; then
+ mkframe "$LEN" "$TYPE" "$FLAGS" "$ID"
+else
+ # read file $DATA in <LEN> chunks and send it in multiple frames
+ # advertising their respective lengths.
+ [ $LEN -gt 0 ] || LEN=16384
+
+ while read -rN "$LEN" payload || [ ${#payload} -gt 0 ]; do
+ mkframe "${#payload}" "$TYPE" "$FLAGS" "$ID"
+ echo -n "$payload"
+ done < "$DATA"
+fi
+
+exit 0
diff --git a/dev/haring/README b/dev/haring/README
new file mode 100644
index 0000000..5205cf2
--- /dev/null
+++ b/dev/haring/README
@@ -0,0 +1,9 @@
+This needs to be built from the top makefile, for example :
+
+ make dev/haring/haring
+
+If HAProxy is built with special options such -DDEBUG_THREAD or with
+multi-threading support enabled (which changes the ring's header size),
+it can be worth reusing the same build options for haring, usually they
+will remain compatible, and will simplify the handling of different file
+layouts, at the expense of dragging more dependencies into the executable.
diff --git a/dev/haring/haring.c b/dev/haring/haring.c
new file mode 100644
index 0000000..ee7e1aa
--- /dev/null
+++ b/dev/haring/haring.c
@@ -0,0 +1,266 @@
+/*
+ * 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.
+ */
+/* we do not implement BUG_ON() */
+#undef DEBUG_STRICT
+
+#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 */
+ ofs = (flags & RING_WF_SEEK_NEW) ? buf.data - 1 : 0;
+
+ //HA_ATOMIC_INC(b_peek(&buf, ofs));
+ }
+
+ while (1) {
+ //HA_RWLOCK_RDLOCK(RING_LOCK, &ring->lock);
+
+ 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));
+ //HA_RWLOCK_RDUNLOCK(RING_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..0247099
--- /dev/null
+++ b/dev/poll/Makefile
@@ -0,0 +1,13 @@
+include ../../include/make/verbose.mk
+
+CC = cc
+OPTIMIZE = -O2 -g
+DEFINE =
+INCLUDE =
+OBJS = poll
+
+poll: poll.c
+ $(cmd_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..022c039
--- /dev/null
+++ b/dev/poll/poll.c
@@ -0,0 +1,445 @@
+#define _GNU_SOURCE // for POLLRDHUP
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#ifdef __linux__
+#include <sys/epoll.h>
+#endif
+
+#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;
+int connected = 0;
+int use_epoll = 0;
+struct sockaddr_in saddr, caddr;
+socklen_t salen, calen;
+
+static inline const char *side(int fd)
+{
+ if (fd == lfd)
+ return "l";
+ if (fd == sfd)
+ return "s";
+ if (fd == cfd)
+ return "c";
+ return "?";
+}
+
+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"
+ " -e use epoll instead of poll\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"
+ " con connect to listener, implicit before first -c/-s\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(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret));
+}
+
+void do_con(int fd)
+{
+ int ret;
+
+ ret = connect(cfd, (const struct sockaddr*)&saddr, salen);
+ if (verbose)
+ printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret));
+ connected = 1;
+}
+
+void do_snd(int fd)
+{
+ int ret;
+
+ ret = send(fd, "foo", 3, MSG_NOSIGNAL|MSG_DONTWAIT);
+ if (verbose)
+ printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), 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(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), 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(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), 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(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), 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(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), 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(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), 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(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), 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(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret));
+}
+
+void do_clo(int fd)
+{
+ int ret;
+
+ ret = close(fd);
+ if (verbose)
+ printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s\n", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret));
+}
+
+void do_pol(int fd)
+{
+ struct pollfd fds = { .fd = fd, .events = POLLIN|POLLOUT|POLLRDHUP, .revents=0 };
+ int flags, flag;
+ int ret;
+
+#ifdef __linux__
+ while (use_epoll) {
+ struct epoll_event evt;
+ static int epoll_fd = -1;
+
+ if (epoll_fd == -1)
+ epoll_fd = epoll_create(1024);
+ if (epoll_fd == -1)
+ break;
+ evt.events = EPOLLIN | EPOLLOUT | EPOLLRDHUP;
+ evt.data.fd = fd;
+ epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &evt);
+ ret = epoll_wait(epoll_fd, &evt, 1, 0);
+
+ if (verbose) {
+ printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s ev=%#x ", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret), ret > 0 ? evt.events : 0);
+ if (ret > 0 && evt.events) {
+ putchar('(');
+
+ for (flags = evt.events; flags; flags ^= flag) {
+ flag = flags ^ (flags & (flags - 1)); // keep lowest bit only
+ switch (flag) {
+ case EPOLLIN: printf("IN"); break;
+ case EPOLLOUT: printf("OUT"); break;
+ case EPOLLPRI: printf("PRI"); break;
+ case EPOLLHUP: printf("HUP"); break;
+ case EPOLLERR: printf("ERR"); break;
+ case EPOLLRDHUP: printf("RDHUP"); break;
+ default: printf("???[%#x]", flag); break;
+ }
+ if (flags ^ flag)
+ putchar(' ');
+ }
+ putchar(')');
+ }
+ putchar('\n');
+ }
+
+ evt.data.fd = fd;
+ epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &evt);
+ return;
+ }
+#endif
+ ret = poll(&fds, 1, 0);
+ if (verbose) {
+ printf("cmd #%d stp #%d: %s(%s=%d): ret=%d%s ev=%#x ", cmd, cmdstep, __FUNCTION__ + 3, side(fd), fd, ret, get_errno(ret), ret > 0 ? fds.revents : 0);
+ if (ret > 0 && fds.revents) {
+ 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)");
+
+ 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 'e' :
+ use_epoll = 1;
+ break;
+ case 'c' :
+ cmd++; cmdstep = 0;
+ if (!connected) {
+ do_con(cfd);
+ /* connection is pending in accept queue, accept() will either be
+ * explicit with "-l acc" below, or implicit on "-s <cmd>"
+ */
+ }
+ fd = cfd;
+ break;
+ case 's' :
+ cmd++; cmdstep = 0;
+ if (!connected)
+ do_con(cfd);
+ 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, "con") == 0) {
+ do_con(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/qpack/decode.c b/dev/qpack/decode.c
new file mode 100644
index 0000000..b2d233a
--- /dev/null
+++ b/dev/qpack/decode.c
@@ -0,0 +1,171 @@
+/*
+ * QPACK stream decoder. Decode a series of hex codes on stdin using one line
+ * per H3 HEADERS frame. Silently skip spaces, tabs, CR, '-' and ','.
+ *
+ * Compilation via Makefile
+ *
+ * Example run:
+ * echo 0000d1d7508b089d5c0b8170dc101a699fc15f5085ed6989397f | ./dev/qpack/decode
+ */
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define MAX_RQ_SIZE 65536
+#define MAX_HDR_NUM 1000
+
+#define QPACK_STANDALONE
+
+#define USE_OPENSSL
+#define USE_QUIC
+
+#include <haproxy/buf-t.h>
+#include <haproxy/http-hdr-t.h>
+#include <haproxy/qpack-dec.h>
+#include <haproxy/qpack-tbl.h>
+
+char line[MAX_RQ_SIZE * 3 + 3];
+uint8_t bin[MAX_RQ_SIZE];
+
+char tmp_buf[MAX_RQ_SIZE];
+struct buffer buf = { .area = tmp_buf, .data = 0, .size = sizeof(tmp_buf) };
+
+#define DEBUG_QPACK
+#include "../src/hpack-huff.c"
+#include "../src/qpack-dec.c"
+#include "../src/qpack-tbl.c"
+
+/* define to compile with BUG_ON/ABORT_NOW statements */
+void ha_backtrace_to_stderr(void)
+{
+}
+
+/* taken from dev/hpack/decode.c */
+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;
+}
+
+/* taken from src/tools.c */
+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);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ struct http_hdr hdrs[MAX_HDR_NUM];
+ int len, outlen, hdr_idx;
+
+ do {
+ if (!fgets(line, sizeof(line), stdin))
+ break;
+
+ if ((len = hex2bin(line, bin, MAX_RQ_SIZE)) < 0)
+ break;
+
+ outlen = qpack_decode_fs(bin, len, &buf, hdrs,
+ sizeof(hdrs) / sizeof(hdrs[0]));
+ if (outlen < 0) {
+ fprintf(stderr, "QPACK decoding failed: %d\n", outlen);
+ continue;
+ }
+
+ hdr_idx = 0;
+ fprintf(stderr, "<<< Found %d headers:\n", outlen);
+ while (1) {
+ if (isteq(hdrs[hdr_idx].n, ist("")))
+ break;
+
+ fprintf(stderr, "%.*s: %.*s\n",
+ (int)hdrs[hdr_idx].n.len, hdrs[hdr_idx].n.ptr,
+ (int)hdrs[hdr_idx].v.len, hdrs[hdr_idx].v.ptr);
+
+ ++hdr_idx;
+ }
+ } while (1);
+
+ return EXIT_SUCCESS;
+}
diff --git a/dev/sslkeylogger/sslkeylogger.lua b/dev/sslkeylogger/sslkeylogger.lua
new file mode 100644
index 0000000..e67bf77
--- /dev/null
+++ b/dev/sslkeylogger/sslkeylogger.lua
@@ -0,0 +1,47 @@
+--[[
+ This script can be used to decipher SSL traffic coming through haproxy. It
+ must first be loaded in the global section of haproxy configuration with
+ TLS keys logging activated :
+
+ tune.ssl.keylog on
+ lua-load sslkeylogger.lua
+
+ Then a http-request rule can be inserted for the desired frontend :
+ http-request lua.sslkeylog <path_to_keylog_file>
+
+ The generated keylog file can then be injected into wireshark to decipher a
+ network capture.
+]]
+
+local function sslkeylog(txn, filename)
+ local fields = {
+ CLIENT_EARLY_TRAFFIC_SECRET = function() return txn.f:ssl_fc_client_early_traffic_secret() end,
+ CLIENT_HANDSHAKE_TRAFFIC_SECRET = function() return txn.f:ssl_fc_client_handshake_traffic_secret() end,
+ SERVER_HANDSHAKE_TRAFFIC_SECRET = function() return txn.f:ssl_fc_server_handshake_traffic_secret() end,
+ CLIENT_TRAFFIC_SECRET_0 = function() return txn.f:ssl_fc_client_traffic_secret_0() end,
+ SERVER_TRAFFIC_SECRET_0 = function() return txn.f:ssl_fc_server_traffic_secret_0() end,
+ EXPORTER_SECRET = function() return txn.f:ssl_fc_exporter_secret() end,
+ EARLY_EXPORTER_SECRET = function() return txn.f:ssl_fc_early_exporter_secret() end
+ }
+
+ local client_random = txn.c:hex(txn.f:ssl_fc_client_random())
+
+ -- ensure that a key is written only once by using a session variable
+ if not txn:get_var('sess.sslkeylogdone') then
+ local file, err = io.open(filename, 'a')
+ if file then
+ for fieldname, fetch in pairs(fields) do
+ if fetch() then
+ file:write(string.format('%s %s %s\n', fieldname, client_random, fetch()))
+ end
+ end
+ file:close()
+ else
+ core.Warning("Cannot open SSL log file: " .. err .. ".")
+ end
+
+ txn:set_var('sess.sslkeylogdone', true)
+ end
+end
+
+core.register_action('sslkeylog', { 'http-req' }, sslkeylog, 1)
diff --git a/dev/tcploop/Makefile b/dev/tcploop/Makefile
new file mode 100644
index 0000000..6d0a0c2
--- /dev/null
+++ b/dev/tcploop/Makefile
@@ -0,0 +1,13 @@
+include ../../include/make/verbose.mk
+
+CC = gcc
+OPTIMIZE = -O2 -g
+DEFINE =
+INCLUDE =
+OBJS = tcploop
+
+tcploop: tcploop.c
+ $(cmd_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..c091f10
--- /dev/null
+++ b/dev/tcploop/tcploop.c
@@ -0,0 +1,1055 @@
+/*
+ * 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.
+ */
+
+#define _GNU_SOURCE // for POLLRDHUP
+#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/un.h>
+#include <sys/wait.h>
+
+#ifdef __linux__
+#include <sys/epoll.h>
+#endif
+
+#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>
+
+/* for OSes which don't have it */
+#ifndef POLLRDHUP
+#define POLLRDHUP 0
+#endif
+
+#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 use_epoll;
+static int pid;
+static int sock_type = SOCK_STREAM;
+static int sock_proto = IPPROTO_TCP;
+
+
+/* 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"
+ " -u : use UDP instead of TCP (limited)\n"
+ " -U : use UNIX instead of TCP (limited, addr must have one '/')\n"
+ " -t|-tt|-ttt : show time (msec / relative / absolute)\n"
+ " -e : use epoll instead of poll on Linux\n"
+ "actions :\n"
+ " A[<count>] : Accepts <count> incoming sockets and closes count-1\n"
+ " Note: fd=accept(fd)\n"
+ " B[[ip]:port] : Bind a new socket to ip:port or default one if unspecified.\n"
+ " Note: fd=socket,bind(fd)\n"
+ " C[[ip]:port] : Connects to ip:port or default ones if unspecified.\n"
+ " Note: fd=socket,connect(fd)\n"
+ " D : Disconnect (connect to AF_UNSPEC)\n"
+ " E[<size>] : Echo this amount of bytes. 0=infinite. unset=any amount.\n"
+ " F : FIN : shutdown(SHUT_WR)\n"
+ " G : disable lingering\n"
+ " I : wait for Input data to be present (POLLIN)\n"
+ " J : Jump back to oldest post-fork/post-accept action\n"
+ " K : kill the connection and go on with next operation\n"
+ " L[<backlog>] : Listens to ip:port and optionally sets backlog\n"
+ " Note: fd=socket,bind(fd),listen(fd)\n"
+ " N<max> : fork New process, limited to <max> concurrent (default 1)\n"
+ " O : wait for Output queue to be empty (POLLOUT + TIOCOUTQ)\n"
+ " P[<time>] : Pause for <time> ms (100 by default)\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"
+ " T : set TCP_NODELAY\n"
+ " W[<time>] : Wait for any event on the socket, maximum <time> ms\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"
+ " r : shutr : shutdown(SHUT_RD) (pauses a listener or ends recv)\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(const char *str, struct sockaddr_storage *ss, struct err_msg *err)
+{
+ char *port_str;
+ int port;
+
+ memset(ss, 0, sizeof(*ss));
+
+ /* if there's a slash it's a unix socket */
+ if (strchr(str, '/')) {
+ ((struct sockaddr_un *)ss)->sun_family = AF_UNIX;
+ strncpy(((struct sockaddr_un *)ss)->sun_path, str, sizeof(((struct sockaddr_un *)ss)->sun_path) - 1);
+ ((struct sockaddr_un *)ss)->sun_path[sizeof(((struct sockaddr_un *)ss)->sun_path)] = 0;
+ return 0;
+ }
+
+ /* 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 <ms> milliseconds on fd <fd> for events <events> (POLLIN|POLLRDHUP|POLLOUT).
+ * returns poll's status.
+ */
+int wait_on_fd(int fd, int events, int ms)
+{
+ struct pollfd pollfd;
+ int ret;
+
+#ifdef __linux__
+ while (use_epoll) {
+ struct epoll_event evt;
+ static int epoll_fd = -1;
+
+ if (epoll_fd == -1)
+ epoll_fd = epoll_create(1024);
+ if (epoll_fd == -1)
+ break;
+ evt.events = ((events & POLLIN) ? EPOLLIN : 0) |
+ ((events & POLLOUT) ? EPOLLOUT : 0) |
+ ((events & POLLRDHUP) ? EPOLLRDHUP : 0);
+ evt.data.fd = fd;
+ epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &evt);
+
+ do {
+ ret = epoll_wait(epoll_fd, &evt, 1, ms);
+ } while (ret == -1 && errno == EINTR);
+
+ evt.data.fd = fd;
+ epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &evt);
+ return ret;
+ }
+#endif
+
+ do {
+ pollfd.fd = fd;
+ pollfd.events = events;
+ ret = poll(&pollfd, 1, ms);
+ } 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
+}
+
+/* Create a new TCP socket for either listening or connecting */
+int tcp_socket(sa_family_t fam)
+{
+ int sock;
+
+ sock = socket(fam, sock_type, sock_proto);
+ if (sock < 0) {
+ perror("socket()");
+ return -1;
+ }
+
+ return sock;
+}
+
+/* Try to bind to local address <sa>. Return the fd or -1 in case of error.
+ * Supports being passed NULL for arg if none has to be passed.
+ */
+int tcp_bind(int sock, const struct sockaddr_storage *sa, const char *arg)
+{
+ struct sockaddr_storage conn_addr;
+
+ if (arg && arg[1]) {
+ struct err_msg err;
+
+ if (addr_to_ss(arg + 1, &conn_addr, &err) < 0)
+ die(1, "%s\n", err.msg);
+ sa = &conn_addr;
+ }
+
+
+ if (sock < 0) {
+ sock = tcp_socket(sa->ss_family);
+ if (sock < 0)
+ return sock;
+ }
+
+ 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;
+ }
+
+ return sock;
+ fail:
+ close(sock);
+ return -1;
+}
+
+/* Try to listen to address <sa>. Return the fd or -1 in case of error */
+int tcp_listen(int sock, const struct sockaddr_storage *sa, const char *arg)
+{
+ int backlog;
+
+ if (sock < 0) {
+ sock = tcp_bind(sock, sa, NULL);
+ if (sock < 0)
+ return sock;
+ }
+
+ 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);
+ 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(int sock, const struct sockaddr_storage *sa, const char *arg)
+{
+ struct sockaddr_storage conn_addr;
+
+ if (arg[1]) {
+ struct err_msg err;
+
+ if (addr_to_ss(arg + 1, &conn_addr, &err) < 0)
+ die(1, "%s\n", err.msg);
+ sa = &conn_addr;
+ }
+
+ if (sock < 0) {
+ sock = tcp_socket(sa->ss_family);
+ if (sock < 0)
+ return sock;
+ }
+
+ 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 | POLLRDHUP, 1000));
+ 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, 1000));
+ 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 | POLLRDHUP, 1000));
+ 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, 1000));
+ 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)
+{
+ 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 */
+ ret = wait_on_fd(sock, POLLIN | POLLRDHUP | POLLOUT, delay);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/* waits for the input data to be present */
+int tcp_wait_in(int sock, const char *arg)
+{
+ int ret;
+
+ ret = wait_on_fd(sock, POLLIN | POLLRDHUP, 1000);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/* waits for the output queue to be empty */
+int tcp_wait_out(int sock, const char *arg)
+{
+ int ret;
+
+ ret = wait_on_fd(sock, POLLOUT, 1000);
+ if (ret < 0)
+ return ret;
+
+ /* 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 default_addr;
+ 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], "-e") == 0)
+ use_epoll = 1;
+ else if (strcmp(argv[0], "-v") == 0)
+ verbose ++;
+ else if (strcmp(argv[0], "-u") == 0) {
+ sock_type = SOCK_DGRAM;
+ sock_proto = IPPROTO_UDP;
+ }
+ else if (strcmp(argv[0], "-U") == 0) {
+ sock_proto = 0;
+ }
+ 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], &default_addr, &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':
+ sock = tcp_listen(sock, &default_addr, argv[arg]);
+ if (sock < 0)
+ die(1, "Fatal: tcp_listen() failed.\n");
+ break;
+
+ case 'B':
+ /* silently ignore existing connections */
+ sock = tcp_bind(sock, &default_addr, argv[arg]);
+ if (sock < 0)
+ die(1, "Fatal: tcp_connect() failed.\n");
+ dolog("connect\n");
+ break;
+
+ case 'C':
+ sock = tcp_connect(sock, &default_addr, 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..55d1773
--- /dev/null
+++ b/dev/udp/udp-perturb.c
@@ -0,0 +1,527 @@
+/*
+ * 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;
+}
+
+/* Corrupt <buf> buffer with <buflen> as length if required */
+static void pktbuf_apply_corruption(char *buf, size_t buflen)
+{
+ 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 < buflen)
+ buf[pos] ^= rnd;
+ }
+}
+
+/* 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;
+
+ pktbuf_apply_corruption(pktbuf, ret);
+
+ 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;
+
+ pktbuf_apply_corruption(pktbuf, ret);
+
+ 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(MAXCONN + 1, sizeof(struct pollfd));
+ 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);
+ }
+ }
+}