]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: net_helper: add sample converters to decode IP packet headers
authorWilly Tarreau <w@1wt.eu>
Sat, 27 Dec 2025 16:46:44 +0000 (17:46 +0100)
committerWilly Tarreau <w@1wt.eu>
Wed, 31 Dec 2025 16:16:29 +0000 (17:16 +0100)
This adds a few converters that help decode parts of IP packets:
  - ip.data : returns the next header (typically TCP)
  - ip.df   : returns the dont-fragment flags
  - ip.dst  : returns the destination IPv4/v6 address
  - ip.hdr  : returns only the IP header
  - ip.proto: returns the upper level protocol (udp/tcp)
  - ip.src  : returns the source IPv4/v6 address
  - ip.tos  : returns the TOS / TC field
  - ip.ttl  : returns the TTL/HL value
  - ip.ver  : returns the IP version (4 or 6)

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

doc/configuration.txt
src/net_helper.c

index 5612d0d2006c35fee7b7ff48c20159e42bb31f44..dfc4a929a5e30ae247949af55fa13229a6376e6b 100644 (file)
@@ -20489,6 +20489,15 @@ htonl                                              integer      integer
 http_date([offset[,unit]])                         integer      string
 iif(true,false)                                    boolean      string
 in_table([table])                                  any          boolean
+ip.data                                            binary       binary
+ip.df                                              binary       integer
+ip.dst                                             binary       address
+ip.hdr                                             binary       binary
+ip.proto                                           binary       integer
+ip.src                                             binary       address
+ip.tos                                             binary       integer
+ip.ttl                                             binary       integer
+ip.ver                                             binary       integer
 ipmask(mask4[,mask6])                              address      address
 json([input-code])                                 string       string
 json_query(json_path[,output_type])                string       _outtype_
@@ -21066,6 +21075,81 @@ in_table([<table>])
   elements (e.g. whether or not a source IP address or an Authorization header
   was already seen).
 
+ip.data
+  This is used with an input sample representing a binary Ethernet frame, as
+  returned by "fc_saved_syn" combined with the "tcp-ss" bind option set to "1",
+  or with the output of "eth.data". It skips the IP header and any optional
+  options or extensions, and returns a block of binary data starting at the
+  transport protocol (usually TCP or UDP). See also "fc_saved_syn", "tcp-ss",
+  and "eth.data".
+
+ip.df
+  This is used with an input sample representing a binary Ethernet frame, as
+  returned by "fc_saved_syn" combined with the "tcp-ss" bind option set to "1",
+  or with the output of "eth.data". It returns integer value 1 if the DF (don't
+  fragment) flag is set in the IP header, 0 otherwise. IPv6 does not have a DF
+  flag, and doesn't fragment by default so it always returns 1. See also
+  "fc_saved_syn", "tcp-ss", and "eth.data".
+
+ip.dst
+  This is used with an input sample representing a binary Ethernet frame, as
+  returned by "fc_saved_syn" combined with the "tcp-ss" bind option set to "1",
+  or with the output of "eth.data". It returns the IPv4 or IPv6 destination
+  address from the IPv4/v6 header. See also "fc_saved_syn", "tcp-ss", and
+  "eth.data".
+
+ip.hdr
+  This is used with an input sample representing a binary Ethernet frame, as
+  returned by "fc_saved_syn" combined with the "tcp-ss" bind option set to "1",
+  or with the output of "eth.data". It returns a block of binary data starting
+  with the IP header and stopping after the last option or extension, and
+  before the transport protocol header. See also "fc_saved_syn", "tcp-ss", and
+  "eth.data".
+
+ip.proto
+  This is used with an input sample representing a binary Ethernet frame, as
+  returned by "fc_saved_syn" combined with the "tcp-ss" bind option set to "1",
+  or with the output of "eth.data". It returns the transport protocol number,
+  usually 6 for TCP or 17 for UDP. See also "fc_saved_syn", "tcp-ss", and
+  "eth.data".
+
+ip.src
+  This is used with an input sample representing a binary Ethernet frame, as
+  returned by "fc_saved_syn" combined with the "tcp-ss" bind option set to "1",
+  or with the output of "eth.data". It returns the IPv4 or IPv6 source address
+  from the IPv4/v6 header. See also "fc_saved_syn", "tcp-ss", and "eth.data".
+
+ip.tos
+  This is used with an input sample representing a binary Ethernet frame, as
+  returned by "fc_saved_syn" combined with the "tcp-ss" bind option set to "1",
+  or with the output of "eth.data". It returns an integer corresponding to the
+  value of the type-of-service (TOS) field in the IPv4 header or traffic class
+  (TC) field in the IPv6 header. Note that in the modern internet, this field
+  most often contains a DSCP (Differentiated Services Codepoint) value in the
+  6 upper bits and the two lower are either not used, or used by IP ECN. Please
+  refer to RFC2474 and RFC8436 for DSCP values, and RFC3168 for IP ECN fields.
+  See also "fc_saved_syn", "tcp-ss", and "eth.data".
+
+ip.ttl
+  This is used with an input sample representing a binary Ethernet frame, as
+  returned by "fc_saved_syn" combined with the "tcp-ss" bind option set to "1",
+  or with the output of "eth.data". This returns an integer corresponding to
+  the TTL (Time To Live) or HL (Hop Limit) field in the IPv4/IPv6 header. This
+  value is usually preset to a fixed value and decremented by each router that
+  the packet crosses. It can help infer how far a client connects from when the
+  initial value is known. Note that most modern operating systems start with an
+  initial value of 64. See also "fc_saved_syn", "tcp-ss", and "eth.data".
+
+ip.ver
+  This is used with an input sample representing a binary Ethernet frame, as
+  returned by "fc_saved_syn" combined with the "tcp-ss" bind option set to "1",
+  or with the output of "eth.data". This returns the IP version from the IP
+  header, normally either 4 or 6. Note that this doesn't check whether the
+  protocol number in the upper layer Ethernet frame matches, but since this is
+  expected to be used with valid packets, it is expected that the operating
+  system has already verified this. See also "fc_saved_syn", "tcp-ss", and
+  "eth.data".
+
 ipmask(<mask4>[,<mask6>])
   Apply a mask to an IP address, and use the result for lookups and storage.
   This can be used to make all hosts within a certain mask to share the same
@@ -23983,8 +24067,8 @@ fc_saved_syn : binary
                       mac_src=%[var(sess.syn),eth.src,hex] \
                       proto=%[var(sess.syn),eth.proto,bytes(6),be2hex(,2)] \
                       ipv4h=%[var(sess.syn),eth.data,bytes(0,12),hex] \
-                      ipv4_src=%[var(sess.syn),bytes(26,4),be2dec(.,1)] \
-                      ipv4_dst=%[var(sess.syn),bytes(30,4),be2dec(.,1)] \
+                      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)] \
@@ -23996,8 +24080,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", and
-           "eth.XXX" converters.
+  See also the "set-var" action, the "be2dec", "bytes", "hex", "eth.XXX" and
+           "ip.XXX" converters.
 
 fc_settings_streams_limit : integer
   Returns the maximum number of streams allowed on the frontend connection. For
index fbb1e3255a556d6cd77106557712f31f1c574c5f..5f22287dfa14c3e2700859ae751c88cba7e81faa 100644 (file)
@@ -119,6 +119,303 @@ static int sample_conv_eth_vlan(const struct arg *arg_p, struct sample *smp, voi
        return 0;
 }
 
+/*******************************************************/
+/* Converters used to process IPv4/IPv6 packet headers */
+/*******************************************************/
+
+/* returns the total header length for the input IP packet header (v4 or v6),
+ * including all extensions if any. It corresponds to the length to skip to
+ * find the TCP or UDP header. If data are missing or unparsable, it returns
+ * 0.
+ */
+static size_t ip_header_length(const struct sample *smp)
+{
+       size_t len;
+       uchar next;
+       uchar ver;
+
+       if (smp->data.u.str.data < 1)
+               return 0;
+
+       ver = (uchar)smp->data.u.str.area[0] >> 4;
+       if (ver == 4) {
+               len = (smp->data.u.str.area[0] & 0xF) * 4;
+               if (smp->data.u.str.data < len)
+                       return 0;
+       }
+       else if (ver == 6) {
+               if (smp->data.u.str.data < 40)
+                       return 0;
+
+               len = 40;
+               next = smp->data.u.str.area[6];
+
+               while (next != 6 && next != 17) {
+                       if (smp->data.u.str.data < len + 2)
+                               return 0;
+                       next = smp->data.u.str.area[len];
+                       len += (uchar)smp->data.u.str.area[len + 1] * 8 + 8;
+               }
+
+               if (smp->data.u.str.data < len)
+                       return 0;
+       }
+       else {
+               return 0;
+       }
+
+       return len;
+}
+
+/* returns the payload following the input IP packet header (v4 or v6) skipping
+ * all extensions if any. For IPv6, it returns the TCP or UDP next header.
+ */
+static int sample_conv_ip_data(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       size_t len;
+
+       len = ip_header_length(smp);
+       if (!len)
+               return 0;
+
+       /* advance buffer by <len> */
+       smp->data.u.str.area += len;
+       smp->data.u.str.data -= len;
+       return 1;
+}
+
+/* returns the DF (don't fragment) flag from an IPv4 header, as 0 or 1. The
+ * value is always one for IPv6 since DF is implicit.
+ */
+static int sample_conv_ip_df(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       uchar ver;
+       uchar df;
+
+       if (smp->data.u.str.data < 1)
+               return 0;
+
+       ver = (uchar)smp->data.u.str.area[0] >> 4;
+       if (ver == 4) {
+               if (smp->data.u.str.data < 6)
+                       return 0;
+               df = !!(smp->data.u.str.area[6] & 0x40);
+       }
+       else if (ver == 6) {
+               df = 1;
+       }
+       else {
+               return 0;
+       }
+
+       smp->data.u.sint = df;
+       smp->data.type = SMP_T_SINT;
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
+/* returns the IP DST address found in an input IP packet header (v4 or v6). */
+static int sample_conv_ip_dst(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       uchar ver;
+
+       if (smp->data.u.str.data < 1)
+               return 0;
+
+       ver = (uchar)smp->data.u.str.area[0] >> 4;
+       if (ver == 4) {
+               if (smp->data.u.str.data < 20)
+                       return 0;
+
+                smp->data.u.ipv4.s_addr = read_u32(smp->data.u.str.area + 16);
+                smp->data.type = SMP_T_IPV4;
+       }
+       else if (ver == 6) {
+               if (smp->data.u.str.data < 40)
+                       return 0;
+
+                memcpy(&smp->data.u.ipv6, smp->data.u.str.area + 24, 16);
+                smp->data.type = SMP_T_IPV6;
+       }
+       else {
+               return 0;
+       }
+
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
+/* returns the IP header only for an input IP packet header (v4 or v6), including
+ * all extensions if any. For IPv6, it includes every extension before TCP/UDP.
+ */
+static int sample_conv_ip_hdr(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       size_t len;
+
+       len = ip_header_length(smp);
+       if (!len)
+               return 0;
+
+       /* truncate buffer to <len> */
+       smp->data.u.str.data = len;
+       return 1;
+}
+
+/* returns the upper layer protocol number (TCP/UDP) for an input IP packet
+ * header (v4 or v6).
+ */
+static int sample_conv_ip_proto(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       size_t len;
+       uchar next;
+       uchar ver;
+
+       if (smp->data.u.str.data < 1)
+               return 0;
+
+       ver = (uchar)smp->data.u.str.area[0] >> 4;
+       if (ver == 4) {
+               if (smp->data.u.str.data < 10)
+                       return 0;
+               next = smp->data.u.str.area[9];
+       }
+       else if (ver == 6) {
+               /* skip all extensions */
+               if (smp->data.u.str.data < 40)
+                       return 0;
+
+               len = 40;
+               next = smp->data.u.str.area[6];
+
+               while (next != 6 && next != 17) {
+                       if (smp->data.u.str.data < len + 2)
+                               return 0;
+                       next = smp->data.u.str.area[len];
+                       len += (uchar)smp->data.u.str.area[len + 1] * 8 + 8;
+               }
+
+               if (smp->data.u.str.data < len)
+                       return 0;
+       }
+       else {
+               return 0;
+       }
+
+       /* protocol number is in <next> */
+       smp->data.u.sint = next;
+       smp->data.type = SMP_T_SINT;
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
+/* returns the IP SRC address found in an input IP packet header (v4 or v6). */
+static int sample_conv_ip_src(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       uchar ver;
+
+       if (smp->data.u.str.data < 1)
+               return 0;
+
+       ver = (uchar)smp->data.u.str.area[0] >> 4;
+       if (ver == 4) {
+               if (smp->data.u.str.data < 20)
+                       return 0;
+
+                smp->data.u.ipv4.s_addr = read_u32(smp->data.u.str.area + 12);
+                smp->data.type = SMP_T_IPV4;
+       }
+       else if (ver == 6) {
+               if (smp->data.u.str.data < 40)
+                       return 0;
+
+                memcpy(&smp->data.u.ipv6, smp->data.u.str.area + 8, 16);
+                smp->data.type = SMP_T_IPV6;
+       }
+       else {
+               return 0;
+       }
+
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
+/* returns the IP TOS/TC field found in an input IP packet header (v4 or v6). */
+static int sample_conv_ip_tos(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       uchar ver;
+
+       if (smp->data.u.str.data < 1)
+               return 0;
+
+       ver = (uchar)smp->data.u.str.area[0] >> 4;
+       if (ver == 4) {
+               /* TOS field is at offset 1 */
+               if (smp->data.u.str.data < 2)
+                       return 0;
+
+               smp->data.u.sint = (uchar)smp->data.u.str.area[1];
+       }
+       else if (ver == 6) {
+               /* TOS field is between offset 0 and 1 */
+               if (smp->data.u.str.data < 2)
+                       return 0;
+
+               smp->data.u.sint = (uchar)(read_n16(smp->data.u.str.area) >> 4);
+       }
+       else {
+               return 0;
+       }
+
+       /* OK we have the value in data.u.sint */
+       smp->data.type = SMP_T_SINT;
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
+/* returns the IP TTL/HL field found in an input IP packet header (v4 or v6). */
+static int sample_conv_ip_ttl(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       uchar ver;
+
+       if (smp->data.u.str.data < 1)
+               return 0;
+
+       ver = (uchar)smp->data.u.str.area[0] >> 4;
+       if (ver == 4) {
+               if (smp->data.u.str.data < 20)
+                       return 0;
+
+               smp->data.u.sint = (uchar)smp->data.u.str.area[8];
+       }
+       else if (ver == 6) {
+               if (smp->data.u.str.data < 40)
+                       return 0;
+
+               smp->data.u.sint = (uchar)smp->data.u.str.area[7];
+       }
+       else {
+               return 0;
+       }
+
+       /* OK we have the value in data.u.sint */
+       smp->data.type = SMP_T_SINT;
+       smp->flags &= ~SMP_F_CONST;
+       return 1;
+}
+
+/* returns the IP version found in an input IP packet header (v4 or v6). */
+static int sample_conv_ip_ver(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       if (smp->data.u.str.data < 1)
+               return 0;
+
+       smp->data.u.sint = (uchar)smp->data.u.str.area[0] >> 4;
+       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, {
        { "eth.data",           sample_conv_eth_data,           0,      NULL,      SMP_T_BIN,  SMP_T_BIN  },
@@ -128,6 +425,16 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, {
        { "eth.src",            sample_conv_eth_src,            0,      NULL,      SMP_T_BIN,  SMP_T_BIN  },
        { "eth.vlan",           sample_conv_eth_vlan,           0,      NULL,      SMP_T_BIN,  SMP_T_SINT },
 
+       { "ip.data",            sample_conv_ip_data,            0,      NULL,      SMP_T_BIN,  SMP_T_BIN  },
+       { "ip.df",              sample_conv_ip_df,              0,      NULL,      SMP_T_BIN,  SMP_T_SINT },
+       { "ip.dst",             sample_conv_ip_dst,             0,      NULL,      SMP_T_BIN,  SMP_T_ADDR },
+       { "ip.hdr",             sample_conv_ip_hdr,             0,      NULL,      SMP_T_BIN,  SMP_T_BIN  },
+       { "ip.proto",           sample_conv_ip_proto,           0,      NULL,      SMP_T_BIN,  SMP_T_SINT },
+       { "ip.src",             sample_conv_ip_src,             0,      NULL,      SMP_T_BIN,  SMP_T_ADDR },
+       { "ip.tos",             sample_conv_ip_tos,             0,      NULL,      SMP_T_BIN,  SMP_T_SINT },
+       { "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 },
+
        { NULL, NULL, 0, 0, 0 },
 }};