Skip to content

Commit

Permalink
MINOR: http_ext: implement rfc7239_{nn,np} converters
Browse files Browse the repository at this point in the history
"option forwarded" provides a convenient way to automatically insert
rfc7239 forwarded header to requests sent to servers.

On the other hand, manually crafting the header is quite complicated due
to specific formatting rules that must be followed as per rfc7239.
However, sometimes it may be necessary to craft the header manually, for
instance if it has to be conditional or based on parameters that "option
forwarded" doesn't provide. To ease this task, in this patch we implement
rfc7239_nn and rfc7239_np which are respectively meant to craft nodename:
nodeport values, specifically intended to manually build rfc7239 'for'
and 'by' header fields while ensuring rfc7239 compliancy.

Example:
  # build RFC-compliant 7239 header:
  http-request set-var-fmt(txn.forwarded) "for=\"%[ipv6(::1),rfc7239_nn]:%[str(8888),rfc7239_np]\";host=\"haproxy.org\";proto=http"
  # check RFC-compliancy:
  http-request set-var(txn.test) "var(txn.forwarded),debug(ok,stderr),rfc7239_is_valid,debug(ok,stderr)"
  #  stderr output:
  #    [debug] ok: type=str <for="[::1]:_8888";host="haproxy.org";proto=http>
  #    [debug] ok: type=bool <1>

See documentation for more info and examples.
  • Loading branch information
Darlelet committed Oct 17, 2024
1 parent 45cbbdc commit d28d016
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 0 deletions.
48 changes: 48 additions & 0 deletions doc/configuration.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19760,6 +19760,8 @@ rfc7239_field(field) string string
rfc7239_is_valid string boolean
rfc7239_n2nn string address / str
rfc7239_n2np string integer / str
rfc7239_nn address/str string
rfc7239_np integer/str string
rtrim(chars) string string
sdbm([avalanche]) binary integer
secure_memcmp(var) string boolean
Expand Down Expand Up @@ -20838,6 +20840,52 @@ rfc7239_n2np
#input: "_name:_port"
# output: "_port" (string)

rfc7239_nn
Converts provided address / string input into RFC7239-compliant node name.
It may be used to manually build 'for' or 'by' 7239 header fields.

When provided input is string, it will be automatically prefixed with '_'
char to represent obfuscated identifier. String must comply with RFC7239
charset. If string is empty, it will be converter to "unknown" identifier.

Example:
#input: ipv6(ab:cd:ff:ff:ff:ff:ff:ff)
# output: "[ab:cd:ff:ff:ff:ff:ff:ff]"
#input: str(test)
# output: "_test"
#input: str()
# output: "unknown"

See also: "rfc7239_np"

rfc7239_np
Converts provided unsigned integer / string input into RFC7239-compliant node
port. It may be used to manually build 'for' or 'by' 7239 header fields.

When provided input is string, it will be automatically prefixed with '_'
char to represent obfuscated identifier. String must comply with RFC7239
charset and cannot be empty.

Example:
#input: int(12)
# output: "12"
#input: str(test)
# output: "_test"

# build 'for' forwarded header field
http-request set-var-fmt(txn.test) "for=\"%[ipv6(::1),rfc7239_nn]:%[int(8080),rfc7239_np]\";"
# output: "for=\"[::1]:8080\";"

# build RFC-compliant 7239 header:
http-request set-var-fmt(txn.forwarded) "for=\"%[ipv6(::1),rfc7239_nn]:%[str(8888),rfc7239_np]\";host=\"haproxy.org\";proto=http"
# check RFC-compliancy:
http-request set-var(txn.test) "var(txn.forwarded),debug(test,stderr),rfc7239_is_valid,debug(test,stderr)"
# stderr output:
# [debug] test: type=str <for="[::1]:_8888";host="haproxy.org";proto=http>
# [debug] test: type=bool <1>

See also: "rfc7239_nn"

rtrim(<chars>)
Skips any characters from <chars> from the end of the string representation
of the input sample.
Expand Down
106 changes: 106 additions & 0 deletions src/http_ext.c
Original file line number Diff line number Diff line change
Expand Up @@ -1881,12 +1881,118 @@ static int sample_conv_7239_n2np(const struct arg *args, struct sample *smp, voi
return 1;
}

/*
* input: ipv4 address, ipv6 address or str (empty string will result in
* "unknown" indentifier, else string will be translated to _obfs
* indentifier, prefixed by '_'. Must comply with RFC7239 charset)
*
* output: rfc7239-compliant forwarded header nodename
*/
static int sample_conv_7239_nn(const struct arg *args, struct sample *smp, void *private)
{
struct buffer *trash = get_trash_chunk();

switch (smp->data.type) {
case SMP_T_IPV4:
{
unsigned char *pn = (unsigned char *)&(smp->data.u.ipv4);

chunk_printf(trash, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]);
break;
}
case SMP_T_IPV6:
_7239_print_ip6(trash, &smp->data.u.ipv6, 1);
break;
case SMP_T_STR:
case_str:
{
struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data);

if (!istlen(validate_n)) {
// empty -> unknown
chunk_printf(trash, "unknown");
break;
}

if (!(http_7239_extract_obfs(&validate_n, NULL) && !istlen(validate_n)))
return 0; /* invalid input */
// output with '_' prefix
chunk_printf(trash, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area);
break;
}
default:
{
if (sample_casts[smp->data.type][SMP_T_STR] &&
sample_casts[smp->data.type][SMP_T_STR](smp))
goto case_str;
return 0; /* unexpected */
}

}

smp->data.u.str = *trash;
smp->data.type = SMP_T_STR;
smp->flags &= ~SMP_F_CONST;

return 1;
}

/*
* input: unsigned integer or str (string will be translated to _obfs
* indentifier, prefixed by '_'. Must comply with RFC7239 charset)
*
* output: rfc7239-compliant forwarded header nodeport
*/
static int sample_conv_7239_np(const struct arg *args, struct sample *smp, void *private)
{
struct buffer *trash = get_trash_chunk();

switch (smp->data.type) {
case SMP_T_SINT:
{
chunk_printf(trash, "%u", (unsigned int)smp->data.u.sint);
break;
}
case SMP_T_STR:
case_str:
{
struct ist validate_n = ist2(smp->data.u.str.area, smp->data.u.str.data);

if (!istlen(validate_n))
return 0;

if (!(http_7239_extract_obfs(&validate_n, NULL) && !istlen(validate_n)))
return 0; /* invalid input */
// output with '_' prefix
chunk_printf(trash, "_%.*s", (int)smp->data.u.str.data, smp->data.u.str.area);
break;
}
default:
{
if (sample_casts[smp->data.type][SMP_T_STR] &&
sample_casts[smp->data.type][SMP_T_STR](smp))
goto case_str;
return 0; /* unexpected */
}

}

smp->data.u.str = *trash;
smp->data.type = SMP_T_STR;
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, {
{ "rfc7239_is_valid", sample_conv_7239_valid, 0, NULL, SMP_T_STR, SMP_T_BOOL},
{ "rfc7239_field", sample_conv_7239_field, ARG1(1,STR), NULL, SMP_T_STR, SMP_T_STR},
{ "rfc7239_n2nn", sample_conv_7239_n2nn, 0, NULL, SMP_T_STR, SMP_T_ANY},
{ "rfc7239_n2np", sample_conv_7239_n2np, 0, NULL, SMP_T_STR, SMP_T_ANY},
{ "rfc7239_nn", sample_conv_7239_nn, 0, NULL, SMP_T_ANY, SMP_T_STR},
{ "rfc7239_np", sample_conv_7239_np, 0, NULL, SMP_T_ANY, SMP_T_STR},
{ NULL, NULL, 0, 0, 0 },
}};

Expand Down

0 comments on commit d28d016

Please sign in to comment.