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.
This commit is contained in:
Willy Tarreau 2025-12-27 16:45:09 +01:00
parent 933cb76461
commit 90d2f157f2
3 changed files with 191 additions and 7 deletions

View file

@ -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 \

View 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

134
src/net_helper.c Normal file
View file

@ -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);