From 90d2f157f2721e1920e38a7bc8004d1400c57b88 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Sat, 27 Dec 2025 16:45:09 +0100 Subject: [PATCH] MINOR: net_helper: add sample converters to decode ethernet frames 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 | 2 +- doc/configuration.txt | 62 +++++++++++++++++-- src/net_helper.c | 134 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 191 insertions(+), 7 deletions(-) create mode 100644 src/net_helper.c diff --git a/Makefile b/Makefile index 90e791f39..c4d1d0b72 100644 --- 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 \ diff --git a/doc/configuration.txt b/doc/configuration.txt index 090cf7f8a..5612d0d20 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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([]) 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 index 000000000..fbb1e3255 --- /dev/null +++ b/src/net_helper.c @@ -0,0 +1,134 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*****************************************************/ +/* 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 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);