Hi all, Willy,

Please find attached to this email the 4 patches for the http-request
do-resolve action I submitted a few months ago.
I integrated all feedback from Willy and also now support tcp-request
content do-resolve.

Baptiste
From e96ff49ee05dbdc15dc7582349e6314dcfccb20e 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/5] 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   |  4 +++-
 3 files changed, 17 insertions(+), 1 deletion(-)

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 93a39a3..b6a3e84 100644
--- a/include/types/stream.h
+++ b/include/types/stream.h
@@ -151,7 +151,9 @@ struct stream {
 		struct stktable *table;
 	} store[8];                     /* tracked stickiness values to store */
 	int store_count;
-	/* 4 unused bytes here */
+
+	enum obj_type obj_type;         /* object type == OBJ_TYPE_STREAM */
+	/* 3 unused bytes here */
 
 	struct stkctr stkctr[MAX_SESS_STKCTR];  /* content-aware stick counters */
 
-- 
2.7.4

From bd4bf0c60a8b78555c050b4ffbd399a239de8be6 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/5] MINOR: action: new '(http-request|tcp-request content)
 do-resolve' action

The 'do-resolve' action is an http-request or tcp-request content 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.

This patch creates a global counter to track various errors reported by
the action 'do-resolve'.
---
 doc/configuration.txt  |  57 ++++++++++
 include/proto/action.h |   3 +
 include/proto/dns.h    |   4 +
 include/types/action.h |   7 ++
 include/types/stats.h  |   1 +
 include/types/stream.h |   9 ++
 src/action.c           |  34 ++++++
 src/dns.c              | 301 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/proto_http.c       |   1 +
 src/stats.c            |   3 +
 src/stream.c           |  11 ++
 11 files changed, 431 insertions(+)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 357a67e..a36103a 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -4186,6 +4186,60 @@ 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.
@@ -9723,6 +9777,7 @@ tcp-request content <action> [{if | unless} <condition>]
 
   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>
@@ -9737,6 +9792,8 @@ tcp-request content <action> [{if | unless} <condition>]
 
   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
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..7ce5a09 100644
--- a/include/proto/dns.h
+++ b/include/proto/dns.h
@@ -22,9 +22,11 @@
 #ifndef _PROTO_DNS_H
 #define _PROTO_DNS_H
 
+#include <types/action.h>
 #include <types/dns.h>
 
 extern struct list dns_resolvers;
+extern unsigned int dns_failed_resolutions;
 
 struct dns_resolvers *find_resolvers_by_id(const char *id);
 struct dns_srvrq *find_srvrq_by_name(const char *name, struct proxy *px);
@@ -43,6 +45,8 @@ 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);
+int check_action_do_resolve(struct act_rule *rule, struct proxy *px, char **err);
 
 
 #endif // _PROTO_DNS_H
diff --git a/include/types/action.h b/include/types/action.h
index 0367ea9..6c5fccf 100644
--- a/include/types/action.h
+++ b/include/types/action.h
@@ -108,6 +108,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/stats.h b/include/types/stats.h
index 043c377..648b032 100644
--- a/include/types/stats.h
+++ b/include/types/stats.h
@@ -312,6 +312,7 @@ enum info_field {
 	INF_CONNECTED_PEERS,
 	INF_DROPPED_LOGS,
 	INF_BUSY_POLLING,
+	INF_FAILED_RESOLUTIONS,
 
 	/* must always be the last one */
 	INF_TOTAL_FIELDS
diff --git a/include/types/stream.h b/include/types/stream.h
index b6a3e84..96a0345 100644
--- a/include/types/stream.h
+++ b/include/types/stream.h
@@ -180,6 +180,15 @@ 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 */
+	struct {
+		struct dns_requester *dns_requester; /* owner of the resolution */
+		char *hostname_dn;              /* hostname being resolve, in domain name format */
+		int hostname_dn_len;            /* size of hostname_dn */
+		/* 4 unused bytes here */
+		struct act_rule *parent;        /* rule which requested this resolution */
+	} dns_ctx;                              /* context information for DNS resolution */
 };
 
 #endif /* _TYPES_STREAM_H */
diff --git a/src/action.c b/src/action.c
index 7574fba..9b6dcfd 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
@@ -67,3 +69,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;
+
+	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;
+
+	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/dns.c b/src/dns.c
index 8941683..2b4a505 100644
--- a/src/dns.c
+++ b/src/dns.c
@@ -26,23 +26,30 @@
 #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);
@@ -54,6 +61,7 @@ DECLARE_STATIC_POOL(dns_resolution_pool,  "dns_resolution",  sizeof(struct dns_r
 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.
@@ -1351,6 +1359,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 +1382,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->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;
 	}
@@ -1414,6 +1432,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->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;
 
@@ -1463,6 +1494,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)->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;
@@ -2071,5 +2106,271 @@ 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.
+ * 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);
diff --git a/src/proto_http.c b/src/proto_http.c
index e68b0e2..e0622b8 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -53,6 +53,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>
diff --git a/src/stats.c b/src/stats.c
index 346c629..6965ad2 100644
--- a/src/stats.c
+++ b/src/stats.c
@@ -51,6 +51,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/freq_ctr.h>
@@ -154,6 +155,7 @@ const char *info_field_names[INF_TOTAL_FIELDS] = {
 	[INF_CONNECTED_PEERS]                = "ConnectedPeers",
 	[INF_DROPPED_LOGS]                   = "DroppedLogs",
 	[INF_BUSY_POLLING]                   = "BusyPolling",
+	[INF_FAILED_RESOLUTIONS]             = "FailedResolutions",
 };
 
 const char *stat_field_names[ST_F_TOTAL_FIELDS] = {
@@ -3657,6 +3659,7 @@ int stats_fill_info(struct field *info, int len)
 	info[INF_CONNECTED_PEERS]                = mkf_u32(0, connected_peers);
 	info[INF_DROPPED_LOGS]                   = mkf_u32(0, dropped_logs);
 	info[INF_BUSY_POLLING]                   = mkf_u32(0, !!(global.tune.options & GTUNE_BUSY_POLLING));
+	info[INF_FAILED_RESOLUTIONS]             = mkf_u32(0, dns_failed_resolutions);
 
 	return 1;
 }
diff --git a/src/stream.c b/src/stream.c
index 551f8e2..cc95389 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>
@@ -151,6 +152,7 @@ struct stream *stream_new(struct session *sess, enum obj_type *origin)
 	s->logs.bytes_in = s->logs.bytes_out = 0;
 	s->logs.prx_queue_pos = 0;  /* we get the number of pending conns before us */
 	s->logs.srv_queue_pos = 0; /* we will get this number soon */
+	s->obj_type = OBJ_TYPE_STREAM;
 
 	csinfo = si_get_cs_info(cs);
 	if (csinfo) {
@@ -2614,6 +2616,15 @@ redo:
 	/* update time stats for this stream */
 	stream_update_time_stats(s);
 
+	if (s->dns_ctx.dns_requester) {
+		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;
+	}
+
 	/* the task MUST not be in the run queue anymore */
 	stream_free(s);
 	task_delete(t);
-- 
2.7.4

From 6bdf51e7e92d4b7eeb4391d31ebf2b917e99bb96 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/5] 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 11d3472..8941683 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 a330470923432dfef61b9357528e2aa19a1c3f61 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/5] 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           | 9 +++++----
 2 files changed, 7 insertions(+), 4 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..11d3472 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;
@@ -1826,7 +1827,7 @@ static void dns_deinit(void)
 		list_for_each_entry_safe(res, resback, &resolvers->resolutions.curr, list) {
 			list_for_each_entry_safe(req, reqback, &res->requesters, list) {
 				LIST_DEL(&req->list);
-				free(req);
+				pool_free(dns_requester_pool, req);
 			}
 			dns_free_resolution(res);
 		}
@@ -1834,7 +1835,7 @@ static void dns_deinit(void)
 		list_for_each_entry_safe(res, resback, &resolvers->resolutions.wait, list) {
 			list_for_each_entry_safe(req, reqback, &res->requesters, list) {
 				LIST_DEL(&req->list);
-				free(req);
+				pool_free(dns_requester_pool, req);
 			}
 			dns_free_resolution(res);
 		}
-- 
2.7.4

Reply via email to