MINOR: net_helper: extend the ip.fp output with an option presence mask

Emeric suggested that it's sometimes convenient to instantly know if a
client has advertised support for window scaling or timestamps for
example. While the info is present in the TCP options output, it's hard
to extract since it respects the options order.

So here we're extending the 56-bit fingerprint with 8 extra bits that
indicate the presence of options 2..8, and any option above 9 for the
last bit. In practice this is sufficient since higher options are not
commonly used. Also TCP option 5 is normally not sent on the SYN (SACK,
only SACK_perm is sent), and echo options 6 & 7 are no longer used
(replaced with timestamps). These fields might be repurposed in the
future if some more meaningful options are to be mapped (e.g. MPTCP,
TFO cookie, auth).
This commit is contained in:
Willy Tarreau
2026-02-09 09:06:38 +01:00
parent a1db464c3e
commit ecffaa6d5a
2 changed files with 29 additions and 13 deletions

View File

@@ -21239,9 +21239,9 @@ ip.fp([<mode>])
can be used to distinguish between multiple apparently identical hosts. The
real-world use case is to refine the identification of misbehaving hosts
between a shared IP address to avoid blocking legitimate users when only one
is misbehaving and needs to be blocked. The converter builds a 7-byte binary
block based on the input. The bytes of the fingerprint are arranged like
this:
is misbehaving and needs to be blocked. The converter builds a 8-byte minimum
binary block based on the input. The bytes of the fingerprint are arranged
like this:
- byte 0: IP TOS field (see ip.tos)
- byte 1:
- bit 7: IPv6 (1) / IPv4 (0)
@@ -21256,10 +21256,13 @@ ip.fp([<mode>])
- bits 3..0: TCP window scaling + 1 (1..15) / 0 (no WS advertised)
- byte 3..4: tcp.win
- byte 5..6: tcp.options.mss, or zero if absent
- byte 7: 1 bit per present TCP option, with options 2 to 8 being mapped to
bits 0..6 respectively, and bit 7 indicating the presence of any
option from 9 to 255.
The <mode> argument permits to append more information to the fingerprint. By
default, when the <mode> argument is not set or is zero, the fingerprint is
solely made of the 7 bytes described above. If <mode> is specified as another
solely made of the 8 bytes described above. If <mode> is specified as another
value, it then corresponds to the sum of the following values, and the
respective components will be concatenated to the fingerprint, in the order
below:
@@ -21269,7 +21272,7 @@ ip.fp([<mode>])
- 4: the source IP address is appended to the fingerprint, which adds
4 bytes for IPv4 and 16 for IPv6.
Example: make a 12..24 bytes fingerprint using the base FP, the TTL and the
Example: make a 13..25 bytes fingerprint using the base FP, the TTL and the
source address (1+4=5):
frontend test

View File

@@ -652,8 +652,8 @@ static int sample_conv_tcp_win(const struct arg *arg_p, struct sample *smp, void
/* Builds a binary fingerprint of the IP+TCP input contents that are supposed
* to rely essentially on the client stack's settings. This can be used for
* example to selectively block bad behaviors at one IP address without
* blocking others. The resulting fingerprint is a binary block of 56 to 376
* bytes long (56 being the fixed part and the rest depending on the provided
* blocking others. The resulting fingerprint is a binary block of 64 to 384
* bits long (64 being the fixed part and the rest depending on the provided
* TCP extensions).
*/
static int sample_conv_ip_fp(const struct arg *arg_p, struct sample *smp, void *private)
@@ -668,6 +668,7 @@ static int sample_conv_ip_fp(const struct arg *arg_p, struct sample *smp, void *
uchar tcpflags;
uchar tcplen;
uchar tcpws;
uchar opts;
ushort pktlen;
ushort tcpwin;
ushort tcpmss;
@@ -719,8 +720,8 @@ static int sample_conv_ip_fp(const struct arg *arg_p, struct sample *smp, void *
else
return 0;
/* prepare trash to contain at least 7 bytes */
trash->data = 7;
/* prepare trash to contain at least 8 bytes */
trash->data = 8;
/* store the TOS in the FP's first byte */
trash->area[0] = iptos;
@@ -763,9 +764,11 @@ static int sample_conv_ip_fp(const struct arg *arg_p, struct sample *smp, void *
(tcpflags >> 6 << 0); // CWR, ECE
tcpmss = tcpws = 0;
opts = 0;
ofs = 20;
while (ofs < tcplen) {
size_t next;
uchar opt;
if (smp->data.u.str.area[ofs] == 0) // kind0=end of options
break;
@@ -782,17 +785,24 @@ static int sample_conv_ip_fp(const struct arg *arg_p, struct sample *smp, void *
break;
/* option is complete, take a copy of it */
if (mode & 2) // mode & 2: append tcp.options_list
trash->area[trash->data++] = smp->data.u.str.area[ofs];
opt = smp->data.u.str.area[ofs];
if (smp->data.u.str.area[ofs] == 2 /* MSS */) {
if (mode & 2) // mode & 2: append tcp.options_list
trash->area[trash->data++] = opt;
if (opt == 2 /* MSS */) {
tcpmss = read_n16(smp->data.u.str.area + ofs + 2);
}
else if (smp->data.u.str.area[ofs] == 3 /* WS */) {
else if (opt == 3 /* WS */) {
tcpws = (uchar)smp->data.u.str.area[ofs + 2];
/* output from 1 to 15, thus 0=not found */
tcpws = tcpws > 14 ? 15 : tcpws + 1;
}
/* keep a presence mask of opts 2..8 and others */
if (opt >= 2)
opts |= 1 << (opt < 9 ? opt - 2 : 7);
ofs = next;
}
@@ -803,6 +813,9 @@ static int sample_conv_ip_fp(const struct arg *arg_p, struct sample *smp, void *
write_n16(trash->area + 3, tcpwin);
write_n16(trash->area + 5, tcpmss);
/* the the bit mask of present options */
trash->area[7] = opts;
/* mode 4: append source IP address */
if (mode & 4) {
iplen = (ipver == 4) ? 4 : 16;