those that can be overridden by the "errorfile" directive.
No further "http-request" rules are evaluated.
+http-request do-resolve(<var>,<resolvers>,[ipv4,ipv6]) <expr> :
+
+ This action performs a DNS resolution of the output of <expr> and stores
+ the result in the variable <var>. It uses the DNS resolvers section
+ pointed by <resolvers>.
+ It is possible to choose a resolution preference using the optional
+ arguments 'ipv4' or 'ipv6'.
+ When performing the DNS resolution, the client side connection is on
+ pause waiting till the end of the resolution.
+ If an IP address can be found, it is stored into <var>. If any kind of
+ error occurs, then <var> is not set.
+ One can use this action to discover a server IP address at run time and
+ based on information found in the request (IE a Host header).
+ If this action is used to find the server's IP address (using the
+ "set-dst" action), then the server IP address in the backend must be set
+ to 0.0.0.0.
+
+ Example:
+ resolvers mydns
+ nameserver local 127.0.0.53:53
+ nameserver google 8.8.8.8:53
+ timeout retry 1s
+ hold valid 10s
+ hold nx 3s
+ hold other 3s
+ hold obsolete 0s
+ accepted_payload_size 8192
+
+ frontend fe
+ bind 10.42.0.1:80
+ http-request do-resolve(txn.myip,mydns,ipv4) hdr(Host),lower
+ http-request capture var(txn.myip) len 40
+
+ # return 503 when the variable is not set,
+ # which mean DNS resolution error
+ use_backend b_503 unless { var(txn.myip) -m found }
+
+ default_backend be
+
+ backend b_503
+ # dummy backend used to return 503.
+ # one can use the errorfile directive to send a nice
+ # 503 error page to end users
+
+ backend be
+ # rule to prevent HAProxy from reconnecting to services
+ # on the local network (forged DNS name used to scan the network)
+ http-request deny if { var(txn.myip) -m ip 127.0.0.0/8 10.0.0.0/8 }
+ http-request set-dst var(txn.myip)
+ server clear 0.0.0.0:0
+
+ NOTE: Don't forget to set the "protection" rules to ensure HAProxy won't
+ be used to scan the network or worst won't loop over itself...
+
http-request early-hint <name> <fmt> [ { if | unless } <condition> ]
This is used to build an HTTP 103 Early Hints response prior to any other one.
Several types of actions are supported :
- accept : the request is accepted
+ - do-resolve: perform a DNS resolution
- reject : the request is rejected and the connection is closed
- capture : the specified sample expression is captured
- set-priority-class <expr> | set-priority-offset <expr>
They have the same meaning as their counter-parts in "tcp-request connection"
so please refer to that section for a complete description.
+ For "do-resolve" action, please check the "http-request do-resolve"
+ configuration section.
While there is nothing mandatory about it, it is recommended to use the
track-sc0 in "tcp-request connection" rules, track-sc1 for "tcp-request
#include <common/ticks.h>
#include <common/net_helper.h>
+#include <types/action.h>
#include <types/applet.h>
#include <types/cli.h>
#include <types/global.h>
#include <types/dns.h>
#include <types/stats.h>
+#include <proto/action.h>
#include <proto/channel.h>
#include <proto/cli.h>
#include <proto/checks.h>
#include <proto/dns.h>
#include <proto/fd.h>
+#include <proto/proto_http.h>
+#include <proto/http_rules.h>
#include <proto/log.h>
+#include <proto/sample.h>
#include <proto/server.h>
#include <proto/task.h>
#include <proto/proto_udp.h>
#include <proto/proxy.h>
#include <proto/stream_interface.h>
+#include <proto/tcp_rules.h>
+#include <proto/vars.h>
struct list dns_resolvers = LIST_HEAD_INIT(dns_resolvers);
struct list dns_srvrq_list = LIST_HEAD_INIT(dns_srvrq_list);
DECLARE_POOL(dns_requester_pool, "dns_requester", sizeof(struct dns_requester));
static unsigned int resolution_uuid = 1;
+unsigned int dns_failed_resolutions = 0;
/* Returns a pointer to the resolvers matching the id <id>. NULL is returned if
* no match is found.
struct dns_resolvers *resolvers;
struct server *srv = NULL;
struct dns_srvrq *srvrq = NULL;
+ struct stream *stream = NULL;
char **hostname_dn;
int hostname_dn_len, query_type;
query_type = DNS_RTYPE_SRV;
break;
+ case OBJ_TYPE_STREAM:
+ stream = (struct stream *)requester;
+ hostname_dn = &stream->dns_ctx.hostname_dn;
+ hostname_dn_len = stream->dns_ctx.hostname_dn_len;
+ resolvers = stream->dns_ctx.parent->arg.dns.resolvers;
+ query_type = ((stream->dns_ctx.parent->arg.dns.dns_opts.family_prio == AF_INET)
+ ? DNS_RTYPE_A
+ : DNS_RTYPE_AAAA);
+ break;
default:
goto err;
}
req->requester_cb = snr_resolution_cb;
req->requester_error_cb = snr_resolution_error_cb;
}
+ else if (stream) {
+ if (stream->dns_ctx.dns_requester == NULL) {
+ if ((req = pool_alloc(dns_requester_pool)) == NULL)
+ goto err;
+ req->owner = &stream->obj_type;
+ stream->dns_ctx.dns_requester = req;
+ }
+ else
+ req = stream->dns_ctx.dns_requester;
+
+ req->requester_cb = act_resolution_cb;
+ req->requester_error_cb = act_resolution_error_cb;
+ }
else
goto err;
res->hostname_dn = __objt_dns_srvrq(req->owner)->hostname_dn;
res->hostname_dn_len = __objt_dns_srvrq(req->owner)->hostname_dn_len;
break;
+ case OBJ_TYPE_STREAM:
+ res->hostname_dn = __objt_stream(req->owner)->dns_ctx.hostname_dn;
+ res->hostname_dn_len = __objt_stream(req->owner)->dns_ctx.hostname_dn_len;
+ break;
default:
res->hostname_dn = NULL;
res->hostname_dn_len = 0;
INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
+/*
+ * Prepare <rule> for hostname resolution.
+ * Returns -1 in case of any allocation failure, 0 if not.
+ * On error, a global failure counter is also incremented.
+ */
+static int action_prepare_for_resolution(struct stream *stream, const char *hostname)
+{
+ char *hostname_dn;
+ int hostname_len, hostname_dn_len;
+ struct buffer *tmp = get_trash_chunk();
+
+ if (!hostname)
+ return 0;
+
+ hostname_len = strlen(hostname);
+ hostname_dn = tmp->area;
+ hostname_dn_len = dns_str_to_dn_label(hostname, hostname_len + 1,
+ hostname_dn, tmp->size);
+ if (hostname_dn_len == -1)
+ goto err;
+
+
+ stream->dns_ctx.hostname_dn = strdup(hostname_dn);
+ stream->dns_ctx.hostname_dn_len = hostname_dn_len;
+ if (!stream->dns_ctx.hostname_dn)
+ goto err;
+
+ return 0;
+
+ err:
+ free(stream->dns_ctx.hostname_dn); stream->dns_ctx.hostname_dn = NULL;
+ dns_failed_resolutions += 1;
+ return -1;
+}
+
+
+/*
+ * Execute the "do-resolution" action. May be called from {tcp,http}request.
+ */
+enum act_return dns_action_do_resolve(struct act_rule *rule, struct proxy *px,
+ struct session *sess, struct stream *s, int flags)
+{
+ struct connection *cli_conn;
+ struct dns_resolution *resolution;
+
+ /* we have a response to our DNS resolution */
+ if (s->dns_ctx.dns_requester && s->dns_ctx.dns_requester->resolution != NULL) {
+ resolution = s->dns_ctx.dns_requester->resolution;
+ if (resolution->step == RSLV_STEP_NONE) {
+ /* We update the variable only if we have a valid response. */
+ if (resolution->status == RSLV_STATUS_VALID) {
+ struct sample smp;
+ short ip_sin_family = 0;
+ void *ip = NULL;
+
+ dns_get_ip_from_response(&resolution->response, &rule->arg.dns.dns_opts, NULL,
+ 0, &ip, &ip_sin_family, NULL);
+
+ switch (ip_sin_family) {
+ case AF_INET:
+ smp.data.type = SMP_T_IPV4;
+ memcpy(&smp.data.u.ipv4, ip, 4);
+ break;
+ case AF_INET6:
+ smp.data.type = SMP_T_IPV6;
+ memcpy(&smp.data.u.ipv6, ip, 16);
+ break;
+ default:
+ ip = NULL;
+ }
+
+ if (ip) {
+ smp.px = px;
+ smp.sess = sess;
+ smp.strm = s;
+
+ vars_set_by_name(rule->arg.dns.varname, strlen(rule->arg.dns.varname), &smp);
+ }
+ }
+ }
+
+ free(s->dns_ctx.hostname_dn); s->dns_ctx.hostname_dn = NULL;
+ s->dns_ctx.hostname_dn_len = 0;
+ dns_unlink_resolution(s->dns_ctx.dns_requester);
+
+ pool_free(dns_requester_pool, s->dns_ctx.dns_requester);
+ s->dns_ctx.dns_requester = NULL;
+
+ return ACT_RET_CONT;
+ }
+
+ /* need to configure and start a new DNS resolution */
+ cli_conn = objt_conn(sess->origin);
+ if (cli_conn && conn_ctrl_ready(cli_conn)) {
+ struct sample *smp;
+ char *fqdn;
+
+ conn_get_from_addr(cli_conn);
+
+ smp = sample_fetch_as_type(px, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.dns.expr, SMP_T_STR);
+ if (smp == NULL)
+ return ACT_RET_CONT;
+
+ fqdn = smp->data.u.str.area;
+ if (action_prepare_for_resolution(s, fqdn) == -1) {
+ return ACT_RET_ERR;
+ }
+
+ s->dns_ctx.parent = rule;
+ dns_link_resolution(s, OBJ_TYPE_STREAM, 0);
+ dns_trigger_resolution(s->dns_ctx.dns_requester);
+ }
+ return ACT_RET_YIELD;
+}
+
+
+/* parse "do-resolve" action
+ * This action takes the following arguments:
+ * do-resolve(<varName>,<resolversSectionName>,<resolvePrefer>) <expr>
+ *
+ * - <varName> is the variable name where the result of the DNS resolution will be stored
+ * (mandatory)
+ * - <resolversSectionName> is the name of the resolvers section to use to perform the resolution
+ * (mandatory)
+ * - <resolvePrefer> can be either 'ipv4' or 'ipv6' and is the IP family we would like to resolve first
+ * (optional), defaults to ipv6
+ * - <expr> is an HAProxy expression used to fetch the name to be resolved
+ */
+enum act_parse_ret dns_parse_do_resolve(const char **args, int *orig_arg, struct proxy *px, struct act_rule *rule, char **err)
+{
+ int cur_arg;
+ struct sample_expr *expr;
+ unsigned int where;
+ const char *beg, *end;
+
+ /* orig_arg points to the first argument, but we need to analyse the command itself first */
+ cur_arg = *orig_arg - 1;
+
+ /* locate varName, which is mandatory */
+ beg = strchr(args[cur_arg], '(');
+ if (beg == NULL)
+ goto do_resolve_parse_error;
+ beg = beg + 1; /* beg should points to the first character after opening parenthesis '(' */
+ end = strchr(beg, ',');
+ if (end == NULL)
+ goto do_resolve_parse_error;
+ rule->arg.dns.varname = my_strndup(beg, end - beg);
+ if (rule->arg.dns.varname == NULL)
+ goto do_resolve_parse_error;
+
+
+ /* locate resolversSectionName, which is mandatory.
+ * Since next parameters are optional, the delimiter may be comma ','
+ * or closing parenthesis ')'
+ */
+ beg = end + 1;
+ end = strchr(beg, ',');
+ if (end == NULL)
+ end = strchr(beg, ')');
+ if (end == NULL)
+ goto do_resolve_parse_error;
+ rule->arg.dns.resolvers_id = my_strndup(beg, end - beg);
+ if (rule->arg.dns.resolvers_id == NULL)
+ goto do_resolve_parse_error;
+
+
+ /* Default priority is ipv6 */
+ rule->arg.dns.dns_opts.family_prio = AF_INET6;
+
+ /* optional arguments accepted for now:
+ * ipv4 or ipv6
+ */
+ while (*end != ')') {
+ beg = end + 1;
+ end = strchr(beg, ',');
+ if (end == NULL)
+ end = strchr(beg, ')');
+ if (end == NULL)
+ goto do_resolve_parse_error;
+
+ if (strncmp(beg, "ipv4", end - beg) == 0) {
+ rule->arg.dns.dns_opts.family_prio = AF_INET;
+ }
+ else if (strncmp(beg, "ipv6", end - beg) == 0) {
+ rule->arg.dns.dns_opts.family_prio = AF_INET6;
+ }
+ else {
+ goto do_resolve_parse_error;
+ }
+ }
+
+ cur_arg = cur_arg + 1;
+
+ expr = sample_parse_expr((char **)args, &cur_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args);
+ if (!expr)
+ goto do_resolve_parse_error;
+
+
+ where = 0;
+ if (px->cap & PR_CAP_FE)
+ where |= SMP_VAL_FE_HRQ_HDR;
+ if (px->cap & PR_CAP_BE)
+ where |= SMP_VAL_BE_HRQ_HDR;
+
+ if (!(expr->fetch->val & where)) {
+ memprintf(err,
+ "fetch method '%s' extracts information from '%s', none of which is available here",
+ args[cur_arg-1], sample_src_names(expr->fetch->use));
+ free(expr);
+ return ACT_RET_PRS_ERR;
+ }
+ rule->arg.dns.expr = expr;
+ rule->action = ACT_CUSTOM;
+ rule->action_ptr = dns_action_do_resolve;
+ *orig_arg = cur_arg;
+
+ rule->check_ptr = check_action_do_resolve;
+
+ return ACT_RET_PRS_OK;
+
+ do_resolve_parse_error:
+ free(rule->arg.dns.varname); rule->arg.dns.varname = NULL;
+ free(rule->arg.dns.resolvers_id); rule->arg.dns.resolvers_id = NULL;
+ memprintf(err, "Can't parse '%s'. Expects 'do-resolve(<varname>,<resolvers>[,<options>]) <expr>'. Available options are 'ipv4' and 'ipv6'",
+ args[cur_arg]);
+ return ACT_RET_PRS_ERR;
+}
+
+static struct action_kw_list http_req_kws = { { }, {
+ { "do-resolve", dns_parse_do_resolve, 1 },
+ { /* END */ }
+}};
+
+INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_kws);
+
+static struct action_kw_list tcp_req_cont_actions = {ILH, {
+ { "do-resolve", dns_parse_do_resolve, 1 },
+ { /* END */ }
+}};
+
+INITCALL1(STG_REGISTER, tcp_req_cont_keywords_register, &tcp_req_cont_actions);
+
+/* Check an "http-request do-resolve" action.
+ *
+ * The function returns 1 in success case, otherwise, it returns 0 and err is
+ * filled.
+ */
+int check_action_do_resolve(struct act_rule *rule, struct proxy *px, char **err)
+{
+ struct dns_resolvers *resolvers = NULL;
+
+ if (rule->arg.dns.resolvers_id == NULL) {
+ memprintf(err,"Proxy '%s': %s", px->id, "do-resolve action without resolvers");
+ return 0;
+ }
+
+ resolvers = find_resolvers_by_id(rule->arg.dns.resolvers_id);
+ if (resolvers == NULL) {
+ memprintf(err,"Can't find resolvers section '%s' for do-resolve action", rule->arg.dns.resolvers_id);
+ return 0;
+ }
+ rule->arg.dns.resolvers = resolvers;
+
+ return 1;
+}
+
REGISTER_POST_DEINIT(dns_deinit);
REGISTER_CONFIG_POSTPARSER("dns runtime resolver", dns_finalize_config);