Hi Willy,

Please find attached to this email a set of 4 patches which add a new HTTP
action that can use a dns resolver section to perform a DNS resolution
based on the output of a fetch.
The use case is split DNS situations or with highly dynamic environment
where servers behind HAProxy are just ephemeral services.

Baptiste
From c3baea8c50a7dcbe4557c4a578fcbd252ffb7c56 Mon Sep 17 00:00:00 2001
From: Baptiste Assmann <bed...@gmail.com>
Date: Tue, 30 Jan 2018 08:10:20 +0100
Subject: [PATCH 3/4] MINOR: obj_type: new object type for struct stream

This patch creates a new obj_type for the struct stream in HAProxy.
---
 include/proto/obj_type.h | 13 +++++++++++++
 include/types/obj_type.h |  1 +
 include/types/stream.h   |  1 +
 3 files changed, 15 insertions(+)

diff --git a/include/proto/obj_type.h b/include/proto/obj_type.h
index 47273ca..19865bb 100644
--- a/include/proto/obj_type.h
+++ b/include/proto/obj_type.h
@@ -30,6 +30,7 @@
 #include <types/obj_type.h>
 #include <types/proxy.h>
 #include <types/server.h>
+#include <types/stream.h>
 #include <types/stream_interface.h>
 
 static inline enum obj_type obj_type(enum obj_type *t)
@@ -158,6 +159,18 @@ static inline struct dns_srvrq *objt_dns_srvrq(enum obj_type *t)
 	return __objt_dns_srvrq(t);
 }
 
+static inline struct stream *__objt_stream(enum obj_type *t)
+{
+	return container_of(t, struct stream, obj_type);
+}
+
+static inline struct stream *objt_stream(enum obj_type *t)
+{
+	if (!t || *t != OBJ_TYPE_STREAM)
+		return NULL;
+	return __objt_stream(t);
+}
+
 static inline void *obj_base_ptr(enum obj_type *t)
 {
 	switch (obj_type(t)) {
diff --git a/include/types/obj_type.h b/include/types/obj_type.h
index e141d69..9410718 100644
--- a/include/types/obj_type.h
+++ b/include/types/obj_type.h
@@ -41,6 +41,7 @@ enum obj_type {
 	OBJ_TYPE_CONN,         /* object is a struct connection */
 	OBJ_TYPE_SRVRQ,        /* object is a struct dns_srvrq */
 	OBJ_TYPE_CS,           /* object is a struct conn_stream */
+	OBJ_TYPE_STREAM,       /* object is a struct stream */
 	OBJ_TYPE_ENTRIES       /* last one : number of entries */
 } __attribute__((packed)) ;
 
diff --git a/include/types/stream.h b/include/types/stream.h
index 5e854c5..02eacd9 100644
--- a/include/types/stream.h
+++ b/include/types/stream.h
@@ -119,6 +119,7 @@ struct strm_logs {
 };
 
 struct stream {
+	enum obj_type obj_type;         /* object type == OBJ_TYPE_STREAM */
 	int flags;                      /* some flags describing the stream */
 	unsigned int uniq_id;           /* unique ID used for the traces */
 	enum obj_type *target;          /* target to use for this stream */
-- 
2.7.4

From 7f4b2ae2e0a98efd2fa162e906c4bb641732ae98 Mon Sep 17 00:00:00 2001
From: Baptiste Assmann <bed...@gmail.com>
Date: Tue, 30 Jan 2018 08:08:04 +0100
Subject: [PATCH 2/4] MINOR: dns: move callback affection in
 dns_link_resolution()

In dns.c, dns_link_resolution(), each type of dns requester is managed
separately, that said, the callback function is affected globaly (and
points to server type callbacks only).
This design prevents the addition of new dns requester type and this
patch aims at fixing this limitation: now, the callback setting is done
directly into the portion of code dedicated to each requester type.
---
 src/dns.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/dns.c b/src/dns.c
index f39f3ff..8ac5024 100644
--- a/src/dns.c
+++ b/src/dns.c
@@ -1397,6 +1397,9 @@ int dns_link_resolution(void *requester, int requester_type, int requester_locke
 			req = srv->dns_requester;
 		if (!requester_locked)
 			HA_SPIN_UNLOCK(SERVER_LOCK, &srv->lock);
+
+		req->requester_cb       = snr_resolution_cb;
+		req->requester_error_cb = snr_resolution_error_cb;
 	}
 	else if (srvrq) {
 		if (srvrq->dns_requester == NULL) {
@@ -1407,13 +1410,14 @@ int dns_link_resolution(void *requester, int requester_type, int requester_locke
 		}
 		else
 			req = srvrq->dns_requester;
+
+		req->requester_cb       = snr_resolution_cb;
+		req->requester_error_cb = snr_resolution_error_cb;
 	}
 	else
 		goto err;
 
 	req->resolution         = res;
-	req->requester_cb       = snr_resolution_cb;
-	req->requester_error_cb = snr_resolution_error_cb;
 
 	LIST_ADDQ(&res->requesters, &req->list);
 	return 0;
-- 
2.7.4

From 077ea8af588e0f0ac2ac4070d514e27c6dac57c9 Mon Sep 17 00:00:00 2001
From: Baptiste Assmann <bed...@gmail.com>
Date: Mon, 21 Jan 2019 08:34:50 +0100
Subject: [PATCH 4/4] MINOR: action: new 'http-request do-resolve' action

The 'do-resolve' action is an http-request action which allows to run
DNS resolution at run time in HAProxy.
The name to be resolved can be picked up in the request sent by the
client and the result of the resolution is stored in a variable.
The time the resolution is being performed, the request is on pause.
If the resolution can't provide a suitable result, then the variable
will be empty. It's up to the admin to take decisions based on this
statement (return 503 to prevent loops).

Read carefully the documentation concerning this feature, to ensure your
setup is secure and safe to be used in production.
---
 doc/configuration.txt  |  54 +++++++++-
 include/proto/action.h |   3 +
 include/proto/dns.h    |   2 +
 include/types/action.h |   8 ++
 include/types/stream.h |  10 ++
 src/action.c           |  34 +++++++
 src/cfgparse.c         |  18 ++++
 src/dns.c              | 266 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/proto_http.c       |   9 +-
 src/stream.c           |  11 ++
 10 files changed, 407 insertions(+), 8 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 2a7efe9..0155274 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -4064,7 +4064,6 @@ http-check send-state
 
   See also : "option httpchk", "http-check disable-on-404"
 
-
 http-request <action> [options...] [ { if | unless } <condition> ]
   Access control for Layer 7 requests
 
@@ -4219,6 +4218,59 @@ http-request deny [deny_status <status>] [ { if | unless } <condition> ]
   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.
diff --git a/include/proto/action.h b/include/proto/action.h
index 19312db..d0b2c5d 100644
--- a/include/proto/action.h
+++ b/include/proto/action.h
@@ -24,6 +24,9 @@
 
 #include <types/action.h>
 
+int act_resolution_cb(struct dns_requester *requester, struct dns_nameserver *nameserver);
+int act_resolution_error_cb(struct dns_requester *requester, int error_code);
+
 static inline struct action_kw *action_lookup(struct list *keywords, const char *kw)
 {
 	struct action_kw_list *kw_list;
diff --git a/include/proto/dns.h b/include/proto/dns.h
index 3ad79c3..f3e8ab8 100644
--- a/include/proto/dns.h
+++ b/include/proto/dns.h
@@ -22,6 +22,7 @@
 #ifndef _PROTO_DNS_H
 #define _PROTO_DNS_H
 
+#include <types/action.h>
 #include <types/dns.h>
 
 extern struct list dns_resolvers;
@@ -43,6 +44,7 @@ int dns_get_ip_from_response(struct dns_response_packet *dns_p,
 int dns_link_resolution(void *requester, int requester_type, int requester_locked);
 void dns_unlink_resolution(struct dns_requester *requester);
 void dns_trigger_resolution(struct dns_requester *requester);
+enum act_parse_ret dns_parse_do_resolve(const char **args, int *orig_arg, struct proxy *px, struct act_rule *rule, char **err);
 
 
 #endif // _PROTO_DNS_H
diff --git a/include/types/action.h b/include/types/action.h
index 0367ea9..d44641c 100644
--- a/include/types/action.h
+++ b/include/types/action.h
@@ -78,6 +78,7 @@ enum act_name {
 	ACT_HTTP_DEL_MAP,
 	ACT_HTTP_SET_MAP,
 	ACT_HTTP_EARLY_HINT,
+	ACT_HTTP_DO_RESOLVE,
 
 	/* http request actions. */
 	ACT_HTTP_REQ_TARPIT,
@@ -108,6 +109,13 @@ struct act_rule {
 	struct applet applet;                  /* used for the applet registration. */
 	union {
 		struct {
+			struct sample_expr *expr;
+			char *varname;
+			char *resolvers_id;
+			struct dns_resolvers *resolvers;
+			struct dns_options dns_opts;
+		} dns;                        /* dns resolution */
+		struct {
 			char *realm;
 		} auth;                        /* arg used by "auth" */
 		struct {
diff --git a/include/types/stream.h b/include/types/stream.h
index 02eacd9..26a5e4a 100644
--- a/include/types/stream.h
+++ b/include/types/stream.h
@@ -179,6 +179,16 @@ struct stream {
 	struct list *current_rule_list;         /* this is used to store the current executed rule list. */
 	void *current_rule;                     /* this is used to store the current rule to be resumed. */
 	struct hlua *hlua;                      /* lua runtime context */
+
+	/* Context */
+	union {
+		struct {
+			struct dns_requester *dns_requester;
+			char *hostname_dn;
+			int hostname_dn_len;
+			struct act_rule *parent;
+		} dns;
+	} ctx;
 };
 
 #endif /* _TYPES_STREAM_H */
diff --git a/src/action.c b/src/action.c
index 54d27a0..6bc61a9 100644
--- a/src/action.c
+++ b/src/action.c
@@ -16,8 +16,10 @@
 #include <common/standard.h>
 
 #include <proto/action.h>
+#include <proto/obj_type.h>
 #include <proto/proxy.h>
 #include <proto/stick_table.h>
+#include <proto/task.h>
 
 
 /* Find and check the target table used by an action ACT_ACTION_TRK_*. This
@@ -62,3 +64,35 @@ int check_trk_action(struct act_rule *rule, struct proxy *px, char **err)
 	return 1;
 }
 
+int act_resolution_cb(struct dns_requester *requester, struct dns_nameserver *nameserver)
+{
+	struct stream *stream = NULL;
+
+	if (requester->resolution == NULL)
+		return 0;
+
+	stream = objt_stream(requester->owner);
+	if (stream == NULL)
+		return 0;
+
+	task_wakeup(stream->task, TASK_WOKEN_MSG);
+
+	return 0;
+}
+
+int act_resolution_error_cb(struct dns_requester *requester, int error_code)
+{
+	struct stream *stream = NULL;
+
+	if (requester->resolution == NULL)
+		return 0;
+
+	stream = objt_stream(requester->owner);
+	if (stream == NULL)
+		return 0;
+
+	task_wakeup(stream->task, TASK_WOKEN_MSG);
+
+	return 0;
+}
+
diff --git a/src/cfgparse.c b/src/cfgparse.c
index a12c4ee..8f9200f 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -2729,6 +2729,24 @@ int check_config_validity()
 				free(err);
 				cfgerr++;
 			}
+			/* link do-resolve HTTP action to its resolvers section */
+			if (arule->action == ACT_HTTP_DO_RESOLVE) {
+				struct dns_resolvers *resolvers = NULL;
+
+				if (arule->arg.dns.resolvers_id == NULL) {
+					ha_alert("Proxy '%s': %s.\n", curproxy->id, "do-resolve action without resolvers");
+					cfgerr++;
+					err_code |= ERR_FATAL;
+				}
+				else if ((resolvers = find_resolvers_by_id(arule->arg.dns.resolvers_id)) == NULL) {
+					ha_alert("Proxy '%s': %s.\n", curproxy->id, "resolvers of do-resolve action is unknown");
+					cfgerr++;
+					err_code |= ERR_FATAL;
+				}
+
+				arule->arg.dns.resolvers = resolvers;
+			}
+
 		}
 
 		/* check validity for 'http-response' layer 7 rules */
diff --git a/src/dns.c b/src/dns.c
index 8ac5024..09dec38 100644
--- a/src/dns.c
+++ b/src/dns.c
@@ -26,23 +26,29 @@
 #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/vars.h>
 
 struct list dns_resolvers  = LIST_HEAD_INIT(dns_resolvers);
 struct list dns_srvrq_list = LIST_HEAD_INIT(dns_srvrq_list);
@@ -1351,6 +1357,7 @@ int dns_link_resolution(void *requester, int requester_type, int requester_locke
 	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;
 
@@ -1373,6 +1380,15 @@ int dns_link_resolution(void *requester, int requester_type, int requester_locke
 			query_type      = DNS_RTYPE_SRV;
 			break;
 
+		case OBJ_TYPE_STREAM:
+			stream          = (struct stream *)requester;
+			hostname_dn     = &stream->ctx.dns.hostname_dn;
+			hostname_dn_len = stream->ctx.dns.hostname_dn_len;
+			resolvers       = stream->ctx.dns.parent->arg.dns.resolvers;
+			query_type      = ((stream->ctx.dns.parent->arg.dns.dns_opts.family_prio == AF_INET)
+					   ? DNS_RTYPE_A
+					   : DNS_RTYPE_AAAA);
+			break;
 		default:
 			goto err;
 	}
@@ -1414,6 +1430,19 @@ int dns_link_resolution(void *requester, int requester_type, int requester_locke
 		req->requester_cb       = snr_resolution_cb;
 		req->requester_error_cb = snr_resolution_error_cb;
 	}
+	else if (stream) {
+		if (stream->ctx.dns.dns_requester == NULL) {
+			if ((req = pool_alloc(dns_requester_pool)) == NULL)
+				goto err;
+			req->owner           = &stream->obj_type;
+			stream->ctx.dns.dns_requester = req;
+		}
+		else
+			req = stream->ctx.dns.dns_requester;
+
+		req->requester_cb       = act_resolution_cb;
+		req->requester_error_cb = act_resolution_error_cb;
+	}
 	else
 		goto err;
 
@@ -1463,6 +1492,10 @@ void dns_unlink_resolution(struct dns_requester *requester)
 			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)->ctx.dns.hostname_dn;
+			res->hostname_dn_len = __objt_stream(req->owner)->ctx.dns.hostname_dn_len;
+			break;
 		default:
 			res->hostname_dn     = NULL;
 			res->hostname_dn_len = 0;
@@ -2071,5 +2104,238 @@ static struct cli_kw_list cli_kws = {{ }, {
 
 INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
 
+/*
+ * Prepare <rule> for hostname resolution.
+ * Returns -1 in case of any allocation failure, 0 if not.
+ */
+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->ctx.dns.hostname_dn     = strdup(hostname_dn);
+	stream->ctx.dns.hostname_dn_len = hostname_dn_len;
+	if (!stream->ctx.dns.hostname_dn)
+		goto err;
+
+	return 0;
+
+ err:
+	free(stream->ctx.dns.hostname_dn); stream->ctx.dns.hostname_dn = NULL;
+	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->ctx.dns.dns_requester && s->ctx.dns.dns_requester->resolution != NULL) {
+		resolution = s->ctx.dns.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->ctx.dns.hostname_dn); s->ctx.dns.hostname_dn = NULL;
+		s->ctx.dns.hostname_dn_len = 0;
+		dns_unlink_resolution(s->ctx.dns.dns_requester);
+
+		pool_free(dns_requester_pool, s->ctx.dns.dns_requester);
+		s->ctx.dns.dns_requester = NULL;
+
+		return ACT_RET_CONT;
+	}
+
+	/* need to configure and start a new DNS resolution */
+	if ((cli_conn = objt_conn(sess->origin)) && conn_ctrl_ready(cli_conn)) {
+		struct sample *smp;
+
+		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) {
+			char *fqdn;
+
+			fqdn = smp->data.u.str.area;
+			if (action_prepare_for_resolution(s, fqdn) == -1) {
+				ha_alert("Can't create DNS resolution for server 'http request action'\n");
+				return ACT_RET_ERR;
+			}
+
+			s->ctx.dns.parent = rule;
+			dns_link_resolution(s, OBJ_TYPE_STREAM, 0);
+			dns_trigger_resolution(s->ctx.dns.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);
+
+
+	/* locate resolversSectionName, which is mandatory.
+	 * Since next parameters are optional, the delimiter may be comma ','
+	 * or closing parenthesis ')'
+	 */
+	beg = end + 1;
+	if (beg == '\0')
+		goto do_resolve_parse_error;
+	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.varname || !rule->arg.dns.resolvers_id)
+		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;
+		if (beg == '\0')
+			goto do_resolve_parse_error;
+		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_HTTP_DO_RESOLVE;
+	rule->action_ptr = dns_action_do_resolve;
+	*orig_arg = cur_arg;
+
+	return ACT_RET_PRS_OK;
+
+ do_resolve_parse_error:
+	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);
+
 REGISTER_POST_DEINIT(dns_deinit);
 REGISTER_CONFIG_POSTPARSER("dns runtime resolver", dns_finalize_config);
diff --git a/src/proto_http.c b/src/proto_http.c
index 245fa98..e72a714 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -54,6 +54,7 @@
 #include <proto/checks.h>
 #include <proto/cli.h>
 #include <proto/compression.h>
+#include <proto/dns.h>
 #include <proto/stats.h>
 #include <proto/fd.h>
 #include <proto/filters.h>
@@ -1766,6 +1767,7 @@ resume_execution:
 				goto end;
 			}
 			break;
+		case ACT_HTTP_DO_RESOLVE:
 		case ACT_CUSTOM:
 			if ((s->req.flags & CF_READ_ERROR) ||
 			    ((s->req.flags & (CF_SHUTR|CF_READ_NULL)) &&
@@ -7369,13 +7371,6 @@ int http_replace_req_line(int action, const char *replace, int len,
 		cur_ptr = ci_head(&s->req);
 		cur_end = cur_ptr + txn->req.sl.rq.m_l;
 
-		/* adjust req line offsets and lengths */
-		delta = len - offset - (cur_end - cur_ptr);
-		txn->req.sl.rq.m_l += delta;
-		txn->req.sl.rq.u   += delta;
-		txn->req.sl.rq.v   += delta;
-		break;
-
 	case 1: // path
 		cur_ptr = http_txn_get_path(txn);
 		if (!cur_ptr)
diff --git a/src/stream.c b/src/stream.c
index 2918589..b83ddef 100644
--- a/src/stream.c
+++ b/src/stream.c
@@ -39,6 +39,7 @@
 #include <proto/checks.h>
 #include <proto/cli.h>
 #include <proto/connection.h>
+#include <proto/dns.h>
 #include <proto/stats.h>
 #include <proto/fd.h>
 #include <proto/filters.h>
@@ -85,6 +86,7 @@ int stream_create_from_cs(struct conn_stream *cs)
 	if (strm == NULL)
 		return -1;
 
+	strm->obj_type = OBJ_TYPE_STREAM;
 	task_wakeup(strm->task, TASK_WOKEN_INIT);
 	return 0;
 }
@@ -2606,6 +2608,15 @@ redo:
 	/* update time stats for this stream */
 	stream_update_time_stats(s);
 
+	if (s->ctx.dns.dns_requester) {
+		free(s->ctx.dns.hostname_dn); s->ctx.dns.hostname_dn = NULL;
+		s->ctx.dns.hostname_dn_len = 0;
+		dns_unlink_resolution(s->ctx.dns.dns_requester);
+
+		pool_free(dns_requester_pool, s->ctx.dns.dns_requester);
+		s->ctx.dns.dns_requester = NULL;
+	}
+
 	/* the task MUST not be in the run queue anymore */
 	stream_free(s);
 	task_delete(t);
-- 
2.7.4

From 8d0634bfd1dafb185d3704710845d809b89ec382 Mon Sep 17 00:00:00 2001
From: Baptiste Assmann <bed...@gmail.com>
Date: Mon, 21 Jan 2019 08:18:09 +0100
Subject: [PATCH 1/4] MINOR: dns: dns_requester structures are now in a memory
 pool

dns_requester structure can be allocated at run time when servers get
associated to DNS resolution (this happens when SRV records are used in
conjunction with service discovery).
Well, this memory allocation is safer if managed in an HAProxy pool,
furthermore with upcoming HTTP action which can perform DNS resolution
at runtime.

This patch moves the memory management of the dns_requester structure
into its own pool.
---
 include/types/dns.h | 2 ++
 src/dns.c           | 5 +++--
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/include/types/dns.h b/include/types/dns.h
index 3d022d4..81cd6d2 100644
--- a/include/types/dns.h
+++ b/include/types/dns.h
@@ -34,6 +34,8 @@
 #include <types/server.h>
 #include <types/task.h>
 
+extern struct pool_head *dns_requester_pool;
+
 /*DNS maximum values */
 /*
  * Maximum issued from RFC:
diff --git a/src/dns.c b/src/dns.c
index 1d91e43..f39f3ff 100644
--- a/src/dns.c
+++ b/src/dns.c
@@ -51,6 +51,7 @@ static THREAD_LOCAL int64_t dns_query_id_seed = 0; /* random seed */
 
 DECLARE_STATIC_POOL(dns_answer_item_pool, "dns_answer_item", sizeof(struct dns_answer_item));
 DECLARE_STATIC_POOL(dns_resolution_pool,  "dns_resolution",  sizeof(struct dns_resolution));
+DECLARE_POOL(dns_requester_pool,  "dns_requester",  sizeof(struct dns_requester));
 
 static unsigned int resolution_uuid = 1;
 
@@ -1384,7 +1385,7 @@ int dns_link_resolution(void *requester, int requester_type, int requester_locke
 		if (!requester_locked)
 			HA_SPIN_LOCK(SERVER_LOCK, &srv->lock);
 		if (srv->dns_requester == NULL) {
-			if ((req = calloc(1, sizeof(*req))) == NULL) {
+			if ((req = pool_alloc(dns_requester_pool)) == NULL) {
 				if (!requester_locked)
 					HA_SPIN_UNLOCK(SERVER_LOCK, &srv->lock);
 				goto err;
@@ -1399,7 +1400,7 @@ int dns_link_resolution(void *requester, int requester_type, int requester_locke
 	}
 	else if (srvrq) {
 		if (srvrq->dns_requester == NULL) {
-			if ((req = calloc(1, sizeof(*req))) == NULL)
+			if ((req = pool_alloc(dns_requester_pool)) == NULL)
 				goto err;
 			req->owner           = &srvrq->obj_type;
 			srvrq->dns_requester = req;
-- 
2.7.4

Reply via email to