diff --git a/doc/configuration.txt b/doc/configuration.txt index fd882e9b0..5e619a7fe 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -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 @@ -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 + # [debug] test: type=bool <1> + + See also: "rfc7239_nn" + rtrim() Skips any characters from from the end of the string representation of the input sample. diff --git a/src/http_ext.c b/src/http_ext.c index 3367e3884..15c6683e9 100644 --- a/src/http_ext.c +++ b/src/http_ext.c @@ -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 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 }, }};