]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: net_helper: extend the ip.fp output with an option presence mask
authorWilly Tarreau <w@1wt.eu>
Mon, 9 Feb 2026 08:06:38 +0000 (09:06 +0100)
committerWilly Tarreau <w@1wt.eu>
Mon, 9 Feb 2026 08:18:04 +0000 (09:18 +0100)
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).

doc/configuration.txt
src/net_helper.c

index b207e2c445c71935ddf4c02ab1dd30d95fb4df28..25fbdea7804f8b3514f7aa96835f427c32acae63 100644 (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
index c2cbde5a61fb3a2f2016e33f7b02f59a1554a2d9..949b0335f5b03de18a54bf92d55cded079081e2a 100644 (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 */
+               opt = smp->data.u.str.area[ofs];
+
                if (mode & 2) // mode & 2: append tcp.options_list
-                       trash->area[trash->data++] = smp->data.u.str.area[ofs];
+                       trash->area[trash->data++] = opt;
 
-               if (smp->data.u.str.area[ofs] == 2 /* MSS */) {
+               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;