]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: net_helper: add sample converters to decode TCP headers
authorWilly Tarreau <w@1wt.eu>
Sat, 27 Dec 2025 17:56:00 +0000 (18:56 +0100)
committerWilly Tarreau <w@1wt.eu>
Wed, 31 Dec 2025 16:17:23 +0000 (17:17 +0100)
This adds the following converters, used to decode fields
in an incoming tcp header:

   tcp.dst, tcp.flags, tcp.seq, tcp.src, tcp.win,
   tcp.options.mss, tcp.options.tsopt, tcp.options.tsval,
   tcp.options.wscale, tcp.options_list,

These can be used with the tcp-ss bind option. The doc was updated
accordingly.

doc/configuration.txt
src/net_helper.c

index dfc4a929a5e30ae247949af55fa13229a6376e6b..3c6d8fd6760f3132ef4674a251075d861544edc8 100644 (file)
@@ -20583,6 +20583,18 @@ table_server_id([table])                           any          integer
 table_sess_cnt([table])                            any          integer
 table_sess_rate([table])                           any          integer
 table_trackers([table])                            any          integer
+tcp.dst                                            binary       integer
+tcp.flags                                          binary       integer
+tcp.options.mss                                    binary       integer
+tcp.options.sack                                   binary       integer
+tcp.options.tsopt                                  binary       integer
+tcp.options.tsval                                  binary       integer
+tcp.options.wscale                                 binary       integer
+tcp.options.wsopt                                  binary       integer
+tcp.options_list                                   binary       binary
+tcp.seq                                            binary       integer
+tcp.src                                            binary       integer
+tcp.win                                            binary       integer
 ub64dec                                            string       string
 ub64enc                                            string       string
 ungrpc(field_number[,field_type])                  binary       binary / int
@@ -22359,6 +22371,88 @@ table_trackers([<table>])
   concurrent connections there are from a given address for example. See also
   the sc_trackers sample fetch keyword.
 
+tcp.dst
+  This is used with an input sample representing a binary TCP header, as
+  returned by "ip.data". It returns an integer representing the destination
+  port present in the TCP header. See also "fc_saved_syn", "tcp-ss", and
+  "ip.data".
+
+tcp.flags
+  This is used with an input sample representing a binary TCP header, as
+  returned by "ip.data". It returns an integer representing the TCP flags
+  from this TCP header. All 8 flags from FIN to CWR are retrieved. Each flag
+  may be tested using the "and()" converter. Please refer to RFC9293 for the
+  value of each flag. See also "fc_saved_syn", "tcp-ss", and "ip.data".
+
+tcp.options.mss
+  This is used with an input sample representing a binary TCP header, as
+  returned by "ip.data". It looks for a TCP option of kind "MSS", and if found,
+  it returns an integer value corresponding to the advertised value in that
+  option, otherwise zero. The MSS is the Maximum Segment Size and indicates the
+  largest segment the peer may receive, in bytes. See also "fc_saved_syn",
+  "tcp-ss", and "ip.data".
+
+tcp.options.sack
+  This is used with an input sample representing a binary TCP header, as
+  returned by "ip.data". It looks for a TCP option of kind "Sack-Permitted",
+  and if found, returns 1, otherwise zero. See also "fc_saved_syn", "tcp-ss",
+  and "ip.data".
+
+tcp.options.tsopt
+  This is used with an input sample representing a binary TCP header, as
+  returned by "ip.data". It looks for a TCP option of kind "Timestamp", and if
+  found, returns 1, otherwise zero. See also "fc_saved_syn", "tcp-ss", and
+  "ip.data".
+
+tcp.options.tsval
+  This is used with an input sample representing a binary TCP header, as
+  returned by "ip.data". It looks for a TCP option of kind "Timestamp", and if
+  found, returns the timestamp value emitted by the peer, otherwise does not
+  return anything. Note that timestamps are 32-bit unsigned values with no
+  particular unit that only the peer decides on, and timestamps are expected to
+  be independent between different connections. See also "fc_saved_syn",
+  "tcp-ss", and "ip.data".
+
+tcp.options.wscale
+  This is used with an input sample representing a binary TCP header, as
+  returned by "ip.data". It looks for a TCP option of kind "Window Scale", and
+  if found, returns the window scaling value emitted by the peer, otherwise
+  zero. Note that values are not expected to be beyond 14 though no technical
+  limitation prevents them from being sent. In order to detect if the window
+  scale option was used, please use "tcp.options.wsopt". See also "tcp-ss",
+  "fc_saved_syn", "ip.data", and "tcp.options.wsopt".
+
+tcp.options.wsopt
+  This is used with an input sample representing a binary TCP header, as
+  returned by "ip.data". It looks for a TCP option of kind "Window Scale", and
+  if found, returns 1 otherwise 0. See also "fc_saved_syn", "tcp-ss", "ip.data"
+  "tcp.options.wscale".
+
+tcp.options_list
+  This is used with an input sample representing a binary TCP header, as
+  returned by "ip.data". It builds a binary sequence of all TCP option kinds in
+  the same order as they appear in the TCP header. It can produce from 0 to 60
+  bytes (in the worst case). The End-of-options is not emitted. See also
+  "fc_saved_syn", "tcp-ss", and "ip.data".
+
+tcp.seq
+  This is used with an input sample representing a binary TCP header, as
+  returned by "ip.data". It returns an integer representing the sequence number
+  used by the peer in the TCP header. Sequence numbers are 32-bit unsigned
+  values. See also "fc_saved_syn", "tcp-ss", and "ip.data".
+
+tcp.src
+  This is used with an input sample representing a binary TCP header, as
+  returned by "ip.data". It returns an integer representing the source port
+  present in the TCP header. See also "fc_saved_syn", "tcp-ss", and "ip.data".
+
+tcp.win
+  This is used with an input sample representing a binary TCP header, as
+  returned by "ip.data". It returns an integer representing the window size
+  advertised by the peer in the TCP header. The value is provided as-is, as a
+  16-bit unsigned quantity, without applying the window scaling factor. See
+  also "fc_saved_syn", "tcp-ss", and "ip.data".
+
 ub64dec
   This converter is the base64url variant of b64dec converter. base64url
   encoding is the "URL and Filename Safe Alphabet" variant of base64 encoding.
@@ -24069,10 +24163,10 @@ fc_saved_syn : binary
                       ipv4h=%[var(sess.syn),eth.data,bytes(0,12),hex] \
                       ipv4_src=%[var(sess.syn),eth.data,ip.src] \
                       ipv4_dst=%[var(sess.syn),eth.data,ip.dst] \
-                      tcp_spt=%[var(sess.syn),bytes(34,2),be2dec(,2)] \
-                      tcp_dpt=%[var(sess.syn),bytes(36,2),be2dec(,2)] \
-                      tcp_win=%[var(sess.syn),bytes(48,2),be2dec(,2)] \
-                      tcp_opt=%[var(sess.syn),bytes(54),hex]\n"
+                      tcp_spt=%[var(sess.syn),eth.data,ip.data,tcp.src] \
+                      tcp_dpt=%[var(sess.syn),eth.data,ip.data,tcp.dst] \
+                      tcp_win=%[var(sess.syn),eth.data,ip.data,tcp.win] \
+                      tcp_opt=%[var(sess.syn),eth.data,ip.data,bytes(20),hex]\n"
 
     $ curl '0:4445'
     mac_dst=000000000000 mac_src=000000000000 proto=0800 \
@@ -24080,8 +24174,8 @@ fc_saved_syn : binary
     tcp_spt=43970 tcp_dpt=4445 tcp_win=65495 \
     tcp_opt=0204FFD70402080A01DC0D410000000001030307
 
-  See also the "set-var" action, the "be2dec", "bytes", "hex", "eth.XXX" and
-           "ip.XXX" converters.
+  See also the "set-var" action, the "be2dec", "bytes", "hex", "eth.XXX",
+           "ip.XXX", and "tcp.XXX" converters.
 
 fc_settings_streams_limit : integer
   Returns the maximum number of streams allowed on the frontend connection. For
index 5f22287dfa14c3e2700859ae751c88cba7e81faa..415f7eebda198e146ae981f4ea7104c175912b5a 100644 (file)
@@ -415,6 +415,240 @@ static int sample_conv_ip_ver(const struct arg *arg_p, struct sample *smp, void
        return 1;
 }
 
+/******************************************/
+/* Converters used to process TCP headers */
+/******************************************/
+
+/* returns the TCP header length in bytes if complete, otherwise zero */
+static int tcp_fullhdr_length(const struct sample *smp)
+{
+       size_t ofs;
+
+       if (smp->data.u.str.data < 20)
+               return 0;
+
+       /* check that header is complete */
+       ofs = ((uchar)smp->data.u.str.area[12] >> 4) * 4;
+       if (ofs < 20 || smp->data.u.str.data < ofs)
+               return 0;
+       return ofs;
+}
+
+/* returns the offset in the input TCP header where option kind <opt> is first
+ * seen, otherwise 0 if not found. NOP and END cannot be searched.
+ */
+static size_t tcp_fullhdr_find_opt(const struct sample *smp, uint8_t opt)
+{
+       size_t len = tcp_fullhdr_length(smp);
+       size_t next = 20, curr;
+
+       while ((curr = next) < len) {
+               if (smp->data.u.str.area[next] == 0) // kind0=end of options
+                       break;
+               /* kind1 = NOP and is a single byte, others have a length field */
+               next += (smp->data.u.str.area[next] == 1) ? 1 : smp->data.u.str.area[next + 1];
+               if (smp->data.u.str.area[curr] == opt && next <= len)
+                       return curr;
+       }
+
+       return 0;
+}
+
+/* returns the destination port field found in an input TCP header */
+static int sample_conv_tcp_dst(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       if (smp->data.u.str.data < 20)
+               return 0;
+
+       smp->data.u.sint = read_n16(smp->data.u.str.area + 2);
+       smp->data.type = SMP_T_SINT;
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
+/* returns the flags field found in an input TCP header */
+static int sample_conv_tcp_flags(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       if (smp->data.u.str.data < 20)
+               return 0;
+
+       smp->data.u.sint = (uchar)smp->data.u.str.area[13];
+       smp->data.type = SMP_T_SINT;
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
+/* returns the MSS value of an input TCP header, or 0 if absent. Returns
+ * nothing if the header is incomplete.
+ */
+static int sample_conv_tcp_options_mss(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       size_t len = tcp_fullhdr_length(smp);
+       size_t ofs = tcp_fullhdr_find_opt(smp, 2 /* MSS */);
+
+       if (!len)
+               return 0;
+
+       smp->data.u.sint = ofs ? read_n16(smp->data.u.str.area + ofs + 2) : 0;
+       smp->data.type = SMP_T_SINT;
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
+/* returns 1 if the SackPerm option is present in an input TCP header,
+ * otherwise 0. Returns nothing if the header is incomplete.
+ */
+static int sample_conv_tcp_options_sack(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       size_t len = tcp_fullhdr_length(smp);
+       size_t ofs = tcp_fullhdr_find_opt(smp, 4 /* sackperm */);
+
+       if (!len)
+               return 0;
+
+       smp->data.u.sint = !!ofs;
+       smp->data.type = SMP_T_SINT;
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
+/* returns 1 if the TimeStamp option is present in an input TCP header,
+ * otherwise 0. Returns nothing if the header is incomplete.
+ */
+static int sample_conv_tcp_options_tsopt(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       size_t len = tcp_fullhdr_length(smp);
+       size_t ofs = tcp_fullhdr_find_opt(smp, 8 /* TS */);
+
+       if (!len)
+               return 0;
+
+       smp->data.u.sint = !!ofs;
+       smp->data.type = SMP_T_SINT;
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
+/* returns the TSval value in the TimeStamp option found in an input TCP
+ * header, if found, otherwise 0. Returns nothing if the header is incomplete
+ * (see also tsopt).
+ */
+static int sample_conv_tcp_options_tsval(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       size_t len = tcp_fullhdr_length(smp);
+       size_t ofs = tcp_fullhdr_find_opt(smp, 8 /* TS */);
+
+       if (!len)
+               return 0;
+
+       smp->data.u.sint = ofs ? read_n32(smp->data.u.str.area + ofs + 2) : 0;
+       smp->data.type = SMP_T_SINT;
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
+/* returns the window scaling shift count from an input TCP header, otherwise 0
+ * if option not found (see also wsopt). Returns nothing if the header is
+ * incomplete.
+ */
+static int sample_conv_tcp_options_wscale(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       size_t len = tcp_fullhdr_length(smp);
+       size_t ofs = tcp_fullhdr_find_opt(smp, 3 /* wscale */);
+
+       if (!len)
+               return 0;
+
+       smp->data.u.sint = ofs ? (uchar)smp->data.u.str.area[ofs + 2] : 0;
+       smp->data.type = SMP_T_SINT;
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
+/* returns 1 if the WScale option is present in an input TCP header,
+ * otherwise 0. Returns nothing if the header is incomplete.
+ */
+static int sample_conv_tcp_options_wsopt(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       size_t len = tcp_fullhdr_length(smp);
+       size_t ofs = tcp_fullhdr_find_opt(smp, 3 /* wscale */);
+
+       if (!len)
+               return 0;
+
+       smp->data.u.sint = !!ofs;
+       smp->data.type = SMP_T_SINT;
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
+/* returns only the TCP options kinds of an input TCP header, as a binary
+ * block of one byte per option.
+ */
+static int sample_conv_tcp_options_list(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       struct buffer *trash = get_trash_chunk();
+       size_t len = tcp_fullhdr_length(smp);
+       size_t ofs = 20;
+
+       if (!len)
+               return 0;
+
+       while (ofs < len) {
+               if (smp->data.u.str.area[ofs] == 0) // kind0=end of options
+                       break;
+               trash->area[trash->data++] = smp->data.u.str.area[ofs];
+               /* kind1 = NOP and is a single byte, others have a length field */
+               if (smp->data.u.str.area[ofs] == 1)
+                       ofs++;
+               else if (ofs + 1 <= len)
+                       ofs += smp->data.u.str.area[ofs + 1];
+               else
+                       break;
+       }
+
+       /* returns a binary block of 1 byte per option */
+       smp->data.u.str = *trash;
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
+/* returns the sequence number field found in an input TCP header */
+static int sample_conv_tcp_seq(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       if (smp->data.u.str.data < 20)
+               return 0;
+
+       smp->data.u.sint = read_n32(smp->data.u.str.area + 4);
+       smp->data.type = SMP_T_SINT;
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
+/* returns the source port field found in an input TCP header */
+static int sample_conv_tcp_src(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       if (smp->data.u.str.data < 20)
+               return 0;
+
+       smp->data.u.sint = read_n16(smp->data.u.str.area);
+       smp->data.type = SMP_T_SINT;
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
+/* returns the window field found in an input TCP header */
+static int sample_conv_tcp_win(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       if (smp->data.u.str.data < 20)
+               return 0;
+
+       smp->data.u.sint = read_n16(smp->data.u.str.area + 14);
+       smp->data.type = SMP_T_SINT;
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
 
 /* Note: must not be declared <const> as its list will be overwritten */
 static struct sample_conv_kw_list sample_conv_kws = {ILH, {
@@ -435,6 +669,19 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, {
        { "ip.ttl",             sample_conv_ip_ttl,             0,      NULL,      SMP_T_BIN,  SMP_T_SINT },
        { "ip.ver",             sample_conv_ip_ver,             0,      NULL,      SMP_T_BIN,  SMP_T_SINT },
 
+       { "tcp.dst",            sample_conv_tcp_dst,            0,      NULL,      SMP_T_BIN,  SMP_T_SINT },
+       { "tcp.flags",          sample_conv_tcp_flags,          0,      NULL,      SMP_T_BIN,  SMP_T_SINT },
+       { "tcp.options.mss",    sample_conv_tcp_options_mss,    0,      NULL,      SMP_T_BIN,  SMP_T_SINT },
+       { "tcp.options.sack",   sample_conv_tcp_options_sack,   0,      NULL,      SMP_T_BIN,  SMP_T_SINT },
+       { "tcp.options.tsopt",  sample_conv_tcp_options_tsopt,  0,      NULL,      SMP_T_BIN,  SMP_T_SINT },
+       { "tcp.options.tsval",  sample_conv_tcp_options_tsval,  0,      NULL,      SMP_T_BIN,  SMP_T_SINT },
+       { "tcp.options.wscale", sample_conv_tcp_options_wscale, 0,      NULL,      SMP_T_BIN,  SMP_T_SINT },
+       { "tcp.options.wsopt",  sample_conv_tcp_options_wsopt,  0,      NULL,      SMP_T_BIN,  SMP_T_SINT },
+       { "tcp.options_list",   sample_conv_tcp_options_list,   0,      NULL,      SMP_T_BIN,  SMP_T_BIN  },
+       { "tcp.seq",            sample_conv_tcp_seq,            0,      NULL,      SMP_T_BIN,  SMP_T_SINT },
+       { "tcp.src",            sample_conv_tcp_src,            0,      NULL,      SMP_T_BIN,  SMP_T_SINT },
+       { "tcp.win",            sample_conv_tcp_win,            0,      NULL,      SMP_T_BIN,  SMP_T_SINT },
+
        { NULL, NULL, 0, 0, 0 },
 }};