protobuf(field_number[,field_type]) binary binary
regsub(regex,subst[,flags]) string string
reverse string string
+reverse_dom string string
rfc7239_field(field) string string
rfc7239_is_valid string boolean
rfc7239_n2nn string address / str
# Pick a backend based on the domain suffix of the Host header:
use_backend %[req.hdr(host),lower,reverse,map_beg(/etc/haproxy/hosts.map,default)]
+reverse_dom
+ Converts a string containing an FQDN-like hostname into its reversed-label
+ form. A single trailing dot on the input is ignored. Empty labels cause the
+ converter to fail.
+
+ This converter does not lowercase its input and does not strip any port.
+ It is meant to be combined with existing converters such as "lower" or
+ "host_only" when needed.
+
+ The trailing-dot policy is intentionally left to the caller. This allows
+ callers to decide whether they want to match the apex too or only
+ subdomains.
+
+ The reversed-label form is useful for large domain maps because it turns
+ domain suffix lookups into prefix lookups, allowing the use of indexed prefix
+ matchers such as "map_beg".
+
+ Examples:
+ "example.com" -> "com.example"
+ "mail.example.com" -> "com.example.mail"
+ "example.com." -> "com.example"
+
+ # match only subdomains of example.net, not the apex
+ acl example_net_sub req.hdr(Host),host_only,reverse_dom -m beg net.example.
+
+ # match only the apex
+ acl example_net_apex req.hdr(Host),host_only,reverse_dom -i net.example
+
+ # exact-or-subdomain prefix lookup using an explicit dotted form
+ http-request set-var(txn.rev_host) req.hdr(Host),host_only,reverse_dom,concat(.)
+ use_backend %[var(txn.rev_host),map_beg(/etc/haproxy/domains.map)]
+
rfc7239_field(<field>)
Extracts a single field/parameter from RFC 7239 compliant header value input.
--- /dev/null
+com.example. example
+com.example.mail. mail
--- /dev/null
+varnishtest "reverse_dom converter test"
+
+feature ignore_unknown_macro
+
+server s1 {
+ rxreq
+ txresp -hdr "Connection: close"
+} -repeat 8 -start
+
+haproxy h1 -conf {
+ global
+ .if feature(THREAD)
+ thread-groups 1
+ .endif
+
+ defaults
+ mode http
+ timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
+ timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
+ timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
+
+ frontend fe
+ bind "fd@${fe}"
+
+ http-request set-var(txn.rev_const) str(MaIl.EXAMPLE.com),reverse_dom
+ http-request set-var(txn.rev_host) req.hdr(Host),host_only,reverse_dom if { req.hdr(Host) -m found }
+ http-request set-var(txn.rev_host_dot) var(txn.rev_host),concat(.) if { var(txn.rev_host) -m found }
+ http-request set-var(txn.route) var(txn.rev_host_dot),map_beg(${testdir}/reverse_dom.map,miss) if { var(txn.rev_host_dot) -m found }
+ http-request set-var(txn.sub_only) str(no)
+ http-request set-var(txn.sub_only) str(yes) if { var(txn.rev_host) -m beg com.example. }
+
+ http-request return status 200 hdr X-Rev-Const "%[var(txn.rev_const)]" hdr X-Rev-Host "%[var(txn.rev_host)]" hdr X-Route "%[var(txn.route)]" hdr X-Sub-Only "%[var(txn.sub_only)]"
+
+ default_backend be
+
+ backend be
+ server s1 ${s1_addr}:${s1_port}
+} -start
+
+client c1 -connect ${h1_fe_sock} {
+ txreq -url "/" -hdr "Host: example.com"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.x-rev-const == "com.EXAMPLE.MaIl"
+ expect resp.http.x-rev-host == "com.example"
+ expect resp.http.x-route == "example"
+ expect resp.http.x-sub-only == "no"
+
+ txreq -url "/" -hdr "Host: mail.example.com"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.x-rev-host == "com.example.mail"
+ expect resp.http.x-route == "mail"
+ expect resp.http.x-sub-only == "yes"
+
+ txreq -url "/" -hdr "Host: example.com."
+ rxresp
+ expect resp.status == 200
+ expect resp.http.x-rev-host == "com.example"
+ expect resp.http.x-route == "example"
+ expect resp.http.x-sub-only == "no"
+
+ txreq -url "/" -hdr "Host: localhost"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.x-rev-host == "localhost"
+ expect resp.http.x-route == "miss"
+ expect resp.http.x-sub-only == "no"
+
+ txreq -url "/" -hdr "Host: badexample.com"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.x-rev-host == "com.badexample"
+ expect resp.http.x-route == "miss"
+ expect resp.http.x-sub-only == "no"
+
+ txreq -url "/" -hdr "Host: foo..bar"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.x-rev-host == "<undef>"
+ expect resp.http.x-route == "<undef>"
+
+ txreq -url "/" -hdr "Host: .example.com"
+ rxresp
+ expect resp.status == 200
+ expect resp.http.x-rev-host == "<undef>"
+ expect resp.http.x-route == "<undef>"
+
+ txreq -url "/" -hdr "Host: ."
+ rxresp
+ expect resp.status == 200
+ expect resp.http.x-rev-host == "<undef>"
+ expect resp.http.x-route == "<undef>"
+} -run
return 1;
}
+/* Reverses the order of labels in an FQDN-like string. A single trailing dot
+ * on input is ignored. Empty labels are rejected.
+ */
+static int sample_conv_reverse_dom(const struct arg *arg_p, struct sample *smp, void *private)
+{
+ const char *input = smp->data.u.str.area;
+ struct buffer *trash;
+ int input_len = smp->data.u.str.data;
+ int out = 0;
+ int label_end;
+ int label_start;
+ int label_len;
+
+ if (!input_len)
+ return 0;
+
+ if (input[input_len - 1] == '.') {
+ input_len--;
+ if (!input_len)
+ return 0;
+ }
+
+ if (input[0] == '.')
+ return 0;
+
+ trash = get_trash_chunk_sz(input_len + 1);
+ if (!trash)
+ return 0;
+
+ label_end = input_len;
+ while (label_end > 0) {
+ label_start = label_end - 1;
+ while (label_start >= 0 && input[label_start] != '.')
+ label_start--;
+ label_start++;
+
+ if (label_start == label_end)
+ return 0;
+
+ label_len = label_end - label_start;
+ memcpy(trash->area + out, input + label_start, label_len);
+ out += label_len;
+
+ if (label_start == 0)
+ break;
+
+ trash->area[out++] = '.';
+ label_end = label_start - 1;
+ }
+
+ trash->area[out] = 0;
+ trash->data = out;
+ smp->data.u.str = *trash;
+ smp->data.type = SMP_T_STR;
+ smp->flags &= ~SMP_F_CONST;
+ return 1;
+}
+
/* takes the IPv4 mask in args[0] and an optional IPv6 mask in args[1] */
static int sample_conv_ipmask(const struct arg *args, struct sample *smp, void *private)
{
{ "param", sample_conv_param, ARG2(1,STR,STR), sample_conv_param_check, SMP_T_STR, SMP_T_STR },
{ "regsub", sample_conv_regsub, ARG3(2,REG,STR,STR), sample_conv_regsub_check, SMP_T_STR, SMP_T_STR },
{ "sha1", sample_conv_sha1, 0, NULL, SMP_T_BIN, SMP_T_BIN },
- { "strcmp", sample_conv_strcmp, ARG1(1,STR), smp_check_strcmp, SMP_T_STR, SMP_T_SINT },
- { "host_only", sample_conv_host_only, 0, NULL, SMP_T_STR, SMP_T_STR },
- { "port_only", sample_conv_port_only, 0, NULL, SMP_T_STR, SMP_T_SINT },
+ { "strcmp", sample_conv_strcmp, ARG1(1,STR), smp_check_strcmp, SMP_T_STR, SMP_T_SINT },
+ { "host_only", sample_conv_host_only, 0, NULL, SMP_T_STR, SMP_T_STR },
+ { "port_only", sample_conv_port_only, 0, NULL, SMP_T_STR, SMP_T_SINT },
{ "reverse", sample_conv_reverse, 0, NULL, SMP_T_STR, SMP_T_STR },
+ { "reverse_dom", sample_conv_reverse_dom, 0, NULL, SMP_T_STR, SMP_T_STR },
/* gRPC converters. */
{ "ungrpc", sample_conv_ungrpc, ARG2(1,PBUF_FNUM,STR), sample_conv_protobuf_check, SMP_T_BIN, SMP_T_BIN },