]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: net_helper: add sample converters to decode ethernet frames
authorWilly Tarreau <w@1wt.eu>
Sat, 27 Dec 2025 15:45:09 +0000 (16:45 +0100)
committerWilly Tarreau <w@1wt.eu>
Wed, 31 Dec 2025 16:15:36 +0000 (17:15 +0100)
This adds a few converters that help decode parts of ethernet frame
headers:
  - eth.data : returns the next header (typically IP)
  - eth.dst  : returns the destination MAC address
  - eth.hdr  : returns only the ethernet header
  - eth.proto: returns the ethernet proto
  - eth.src  : returns the source MAC address
  - eth.vlan : returns the VLAN ID when present

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

Makefile
doc/configuration.txt
src/net_helper.c [new file with mode: 0644]

index 90e791f398cdc90947eb551d9387cab3e39dae0c..c4d1d0b720c5daaf99d012bc470809c2d372077b 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -992,7 +992,7 @@ OBJS += src/mux_h2.o src/mux_h1.o src/mux_fcgi.o src/log.o          \
         src/cfgcond.o src/proto_udp.o src/lb_fwlc.o src/ebmbtree.o     \
         src/proto_uxdg.o src/cfgdiag.o src/sock_unix.o src/sha1.o      \
         src/lb_fas.o src/clock.o src/sock_inet.o src/ev_select.o       \
-        src/lb_map.o src/shctx.o src/hpack-dec.o                       \
+        src/lb_map.o src/shctx.o src/hpack-dec.o src/net_helper.o       \
         src/arg.o src/signal.o src/fix.o src/dynbuf.o src/guid.o       \
         src/cfgparse-tcp.o src/lb_ss.o src/chunk.o src/counters.o      \
         src/cfgparse-unix.o src/regex.o src/fcgi.o src/uri_auth.o      \
index 090cf7f8a22ba18ee21d22daf277a1c957950f16..5612d0d2006c35fee7b7ff48c20159e42bb31f44 100644 (file)
@@ -20471,6 +20471,12 @@ debug([prefix][,destination])                       any          same
 digest(algorithm)                                  binary       binary
 div(value)                                         integer      integer
 djb2([avalanche])                                  binary       integer
+eth.data                                           binary       binary
+eth.dst                                            binary       binary
+eth.hdr                                            binary       binary
+eth.proto                                          binary       integer
+eth.src                                            binary       binary
+eth.vlan                                           binary       integer
 even                                               integer      boolean
 field(index,delimiters[,count])                    string       string
 fix_is_valid                                       binary       boolean
@@ -20890,6 +20896,48 @@ djb2([<avalanche>])
   32-bit hash is trivial to break. See also "crc32", "sdbm", "wt6", "crc32c",
   and the "hash-type" directive.
 
+eth.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 "2".
+  It skips all the Ethernet header including possible VLANs and returns a block
+  of binary data starting at the layer 3 protocol (usually IPv4 or IPv6). See
+  also "fc_saved_syn" and "tcp-ss".
+
+eth.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 "2".
+  It returns the 6 bytes of the Ethernet header corresponding to the
+  destination address of the frame, as a binary block. See also "fc_saved_syn"
+  and "tcp-ss".
+
+eth.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 "2".
+  It trims anything past the Ethernet header but keeps possible VLANs, and
+  returns this header as a block of binary data. See also "fc_saved_syn" and
+  "tcp-ss".
+
+eth.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 "2".
+  It returns the protocol number (also known as EtherType) found in a Ethernet
+  header after any optional VLAN as an integer value. It should normally be
+  either 0x800 for IPv4 or 0x86DD for IPv6. See also "fc_saved_syn" and
+  "tcp-ss".
+
+eth.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 "2".
+  It returns the 6 bytes of the Ethernet header corresponding to the source
+  address of the frame, as a binary block. See also "fc_saved_syn" and
+  "tcp-ss".
+
+eth.vlan
+  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 "2".
+  It returns the last VLAN ID found in a Ethernet header as an integer value.
+  See also "fc_saved_syn" and "tcp-ss".
+
 even
   Returns a boolean TRUE if the input value of type signed integer is even
   otherwise returns FALSE. It is functionally equivalent to "not,and(1),bool".
@@ -23921,7 +23969,8 @@ fc_saved_syn : binary
     0204FFC40402080A9C231D680000000001030307 # MSS=65476, TS, SACK, WSCALE 7
 
   The "bytes()" converter helps extract specific fields from the packet. The
-  be2dec() also permits to read chunks and emit them in integer form.
+  be2dec() also permits to read chunks and emit them in integer form. For more
+  accurate extraction, please refer to the "eth.XXX" converters.
 
   Example with IPv4 input:
 
@@ -23930,10 +23979,10 @@ fc_saved_syn : binary
         bind :4445 tcp-ss 2
         tcp-request connection set-var(sess.syn) fc_saved_syn
         http-request return status 200 content-type text/plain lf-string \
-                     "mac_dst=%[var(sess.syn),bytes(0,6),hex] \
-                      mac_src=%[var(sess.syn),bytes(6,6),hex] \
-                      proto=%[var(sess.syn),bytes(12,2),hex] \
-                      ipv4h=%[var(sess.syn),bytes(14,12),hex] \
+                     "mac_dst=%[var(sess.syn),eth.dst,hex] \
+                      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)] \
                       tcp_spt=%[var(sess.syn),bytes(34,2),be2dec(,2)] \
@@ -23947,7 +23996,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" and "hex" converters.
+  See also the "set-var" action, the "be2dec", "bytes", "hex", and
+           "eth.XXX" converters.
 
 fc_settings_streams_limit : integer
   Returns the maximum number of streams allowed on the frontend connection. For
diff --git a/src/net_helper.c b/src/net_helper.c
new file mode 100644 (file)
index 0000000..fbb1e32
--- /dev/null
@@ -0,0 +1,134 @@
+#include <string.h>
+#include <stdio.h>
+
+#include <haproxy/api.h>
+#include <haproxy/arg.h>
+#include <haproxy/buf.h>
+#include <haproxy/cfgparse.h>
+#include <haproxy/chunk.h>
+#include <haproxy/errors.h>
+#include <haproxy/global.h>
+#include <haproxy/net_helper.h>
+#include <haproxy/sample.h>
+
+/*****************************************************/
+/* Converters used to process Ethernet frame headers */
+/*****************************************************/
+
+/* returns only the data part of an input ethernet frame header, skipping any
+ * possible VLAN header. This is typically used to return the beginning of the
+ * IP packet.
+ */
+static int sample_conv_eth_data(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       size_t idx;
+
+       for (idx = 12; idx + 2 < smp->data.u.str.data; idx += 4) {
+               if (read_n16(smp->data.u.str.area + idx) != 0x8100) {
+                       smp->data.u.str.area += idx + 2;
+                       smp->data.u.str.data -= idx + 2;
+                       return 1;
+               }
+       }
+       /* incomplete header */
+       return 0;
+}
+
+/* returns the 6 bytes of MAC DST address of an input ethernet frame header */
+static int sample_conv_eth_dst(const struct arg *arg_p, struct sample *smp, void *private)
+{
+
+       if (smp->data.u.str.data < 6)
+               return 0;
+
+       smp->data.u.str.data = 6; // output length is 6
+       return 1;
+}
+
+/* returns only the ethernet header for an input ethernet frame header,
+ * including any possible VLAN headers, but stopping before data.
+ */
+static int sample_conv_eth_hdr(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       size_t idx;
+
+       for (idx = 12; idx + 2 < smp->data.u.str.data; idx += 4) {
+               if (read_n16(smp->data.u.str.area + idx) != 0x8100) {
+                       smp->data.u.str.data = idx + 2;
+                       return 1;
+               }
+       }
+       /* incomplete header */
+       return 0;
+}
+
+/* returns the ethernet protocol of an input ethernet frame header, skipping
+ * any VLAN tag.
+ */
+static int sample_conv_eth_proto(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       ushort proto;
+       size_t idx;
+
+       for (idx = 12; idx + 2 < smp->data.u.str.data; idx += 4) {
+               proto = read_n16(smp->data.u.str.area + idx);
+               if (proto != 0x8100) {
+                       smp->data.u.sint = proto;
+                       smp->data.type = SMP_T_SINT;
+                       smp->flags &= ~SMP_F_CONST;
+                       return 1;
+               }
+       }
+       /* incomplete header */
+       return 0;
+}
+
+/* returns the 6 bytes of MAC SRC address of an input ethernet frame header */
+static int sample_conv_eth_src(const struct arg *arg_p, struct sample *smp, void *private)
+{
+
+       if (smp->data.u.str.data < 12)
+               return 0;
+
+       smp->data.u.str.area += 6; // src is at address 6
+       smp->data.u.str.data  = 6; // output length is 6
+       return 1;
+}
+
+/* returns the last VLAN ID seen in an input ethernet frame header, if any.
+ * Note that VLAN ID 0 is considered as absence of VLAN.
+ */
+static int sample_conv_eth_vlan(const struct arg *arg_p, struct sample *smp, void *private)
+{
+       ushort vlan = 0;
+       size_t idx;
+
+       for (idx = 12; idx + 2 < smp->data.u.str.data; idx += 4) {
+               if (read_n16(smp->data.u.str.area + idx) != 0x8100) {
+                       smp->data.u.sint = vlan;
+                       smp->data.type = SMP_T_SINT;
+                       smp->flags &= ~SMP_F_CONST;
+                       return !!vlan;
+               }
+               if (idx + 4 < smp->data.u.str.data)
+                       break;
+
+               vlan = read_n16(smp->data.u.str.area + idx + 2) & 0xfff;
+       }
+       /* incomplete header */
+       return 0;
+}
+
+/* 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  },
+       { "eth.dst",            sample_conv_eth_dst,            0,      NULL,      SMP_T_BIN,  SMP_T_BIN  },
+       { "eth.hdr",            sample_conv_eth_hdr,            0,      NULL,      SMP_T_BIN,  SMP_T_BIN  },
+       { "eth.proto",          sample_conv_eth_proto,          0,      NULL,      SMP_T_BIN,  SMP_T_SINT },
+       { "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 },
+
+       { NULL, NULL, 0, 0, 0 },
+}};
+
+INITCALL1(STG_REGISTER, sample_register_convs, &sample_conv_kws);