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'.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 4fb9033..8323737 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -4267,6 +4267,60 @@
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.
@@ -9804,6 +9858,7 @@
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>
@@ -9820,6 +9875,8 @@
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_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 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 @@
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 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 @@
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 a0da346..30fc93c 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_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 @@
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 @@
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 @@
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 @@
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;
@@ -2070,5 +2105,271 @@
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 695ff7e..8329324 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 @@
[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 @@
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 7740289..af15d47 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 @@
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) {
@@ -418,6 +420,15 @@
s->txn = NULL;
}
+ 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;
+ }
+
flt_stream_stop(s);
flt_stream_release(s, 0);