MINOR: net_helper: add sample converters to decode TCP headers

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.
This commit is contained in:
Willy Tarreau
2025-12-27 18:56:00 +01:00
parent e0a7a7ca43
commit 6e46d1345b
2 changed files with 347 additions and 6 deletions

View 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

View 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 },
}};