MINOR: net_helper: add sample converters to decode IP packet headers

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.
This commit is contained in:
Willy Tarreau 2025-12-27 17:46:44 +01:00
parent 90d2f157f2
commit e0a7a7ca43
2 changed files with 395 additions and 4 deletions

View 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

View 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 },
}};