MINOR: dns/stats: integrate dns counters in stats

Use the new stats module API to integrate the dns counters in the
standard stats. This is done in order to avoid code duplication, keep
the code related to cli out of dns and use the full possibility of the
stats function, allowing to print dns stats in csv or json format.
diff --git a/doc/management.txt b/doc/management.txt
index 151c96e..eef05b0 100644
--- a/doc/management.txt
+++ b/doc/management.txt
@@ -945,7 +945,9 @@
 Unix socket.
 
 Statistics are regroup in categories labelled as domains, corresponding to the
-multiple components of HAProxy. Only the proxy domain is available.
+multiple components of HAProxy. There are two domains avaiable: proxy and dns.
+If not specified, the proxy domain is selected. Note that only the proxy
+statistics are printed on the HTTP page.
 
 9.1. CSV format
 ---------------
diff --git a/include/haproxy/dns-t.h b/include/haproxy/dns-t.h
index 8240372..c77b97e 100644
--- a/include/haproxy/dns-t.h
+++ b/include/haproxy/dns-t.h
@@ -27,6 +27,7 @@
 #include <haproxy/connection-t.h>
 #include <haproxy/dgram-t.h>
 #include <haproxy/obj_type-t.h>
+#include <haproxy/stats-t.h>
 #include <haproxy/task-t.h>
 #include <haproxy/thread.h>
 
@@ -210,26 +211,31 @@
 	struct dgram_conn      *dgram;  /* transport layer */
 	struct sockaddr_storage addr;   /* IP address */
 
-	struct {                        /* numbers relted to this name server: */
-		long long sent;         /* - queries sent */
-		long long snd_error;    /* - sending errors */
-		long long valid;        /* - valid response */
-		long long update;       /* - valid response used to update server's IP */
-		long long cname;        /* - CNAME response requiring new resolution */
-		long long cname_error;  /* - error when resolving CNAMEs */
-		long long any_err;      /* - void response (usually because ANY qtype) */
-		long long nx;           /* - NX response */
-		long long timeout;      /* - queries which reached timeout */
-		long long refused;      /* - queries refused */
-		long long other;        /* - other type of response */
-		long long invalid;      /* - malformed DNS response */
-		long long too_big;      /* - too big response */
-		long long outdated;     /* - outdated response (server slower than the other ones) */
-		long long truncated;    /* - truncated response */
-	} counters;
+	EXTRA_COUNTERS(extra_counters);
+	struct dns_counters *counters;
+
 	struct list list;               /* nameserver chained list */
 };
 
+struct dns_counters {
+	char *id;
+	long long sent;         /* - queries sent */
+	long long snd_error;    /* - sending errors */
+	long long valid;        /* - valid response */
+	long long update;       /* - valid response used to update server's IP */
+	long long cname;        /* - CNAME response requiring new resolution */
+	long long cname_error;  /* - error when resolving CNAMEs */
+	long long any_err;      /* - void response (usually because ANY qtype) */
+	long long nx;           /* - NX response */
+	long long timeout;      /* - queries which reached timeout */
+	long long refused;      /* - queries refused */
+	long long other;        /* - other type of response */
+	long long invalid;      /* - malformed DNS response */
+	long long too_big;      /* - too big response */
+	long long outdated;     /* - outdated response (server slower than the other ones) */
+	long long truncated;    /* - truncated response */;
+};
+
 struct dns_options {
 	int family_prio; /* which IP family should the resolver use when both are returned */
 	struct {
diff --git a/include/haproxy/dns.h b/include/haproxy/dns.h
index d209778..4f2cb8b 100644
--- a/include/haproxy/dns.h
+++ b/include/haproxy/dns.h
@@ -48,5 +48,10 @@
 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);
 
+int stats_dump_dns(struct stream_interface *si,
+                   struct field *stats, size_t stats_count,
+                   struct list *stat_modules);
+void dns_stats_clear_counters(int clrall, struct list *stat_modules);
+int dns_allocate_counters(struct list *stat_modules);
 
 #endif // _HAPROXY_DNS_H
diff --git a/include/haproxy/stats-t.h b/include/haproxy/stats-t.h
index d148651..b9e255c 100644
--- a/include/haproxy/stats-t.h
+++ b/include/haproxy/stats-t.h
@@ -51,7 +51,7 @@
 #define STATS_TYPE_SV  2
 #define STATS_TYPE_SO  3
 
-#define STATS_DOMAIN  (0)               /* used for bitshifting, type of statistics, for now only proxy is available */
+#define STATS_DOMAIN  (0)               /* used for bitshifting, type of statistics: proxy or dns */
 #define STATS_PX_CAP  (8)               /* used for bitshifting, differentiate obj1 type for proxy statistics */
 
 /* HTTP stats : applet.st0 */
@@ -459,6 +459,7 @@
 	COUNTERS_BE,
 	COUNTERS_SV,
 	COUNTERS_LI,
+	COUNTERS_DNS,
 
 	COUNTERS_OFF_END
 };
@@ -490,6 +491,7 @@
 /* stats_domain is used in a flag as a 1 byte field */
 enum stats_domain {
 	STATS_DOMAIN_PROXY = 0,
+	STATS_DOMAIN_DNS,
 	STATS_DOMAIN_COUNT,
 
 	STATS_DOMAIN_MASK  = 0xff
diff --git a/src/dns.c b/src/dns.c
index c38152d..3d48426 100644
--- a/src/dns.c
+++ b/src/dns.c
@@ -36,7 +36,7 @@
 #include <haproxy/proxy.h>
 #include <haproxy/sample.h>
 #include <haproxy/server.h>
-#include <haproxy/stats-t.h>
+#include <haproxy/stats.h>
 #include <haproxy/stream_interface.h>
 #include <haproxy/task.h>
 #include <haproxy/tcp_rules.h>
@@ -57,6 +57,78 @@
 static unsigned int resolution_uuid = 1;
 unsigned int dns_failed_resolutions = 0;
 
+enum {
+	DNS_STAT_ID,
+	DNS_STAT_SND_ERROR,
+	DNS_STAT_VALID,
+	DNS_STAT_UPDATE,
+	DNS_STAT_CNAME,
+	DNS_STAT_CNAME_ERROR,
+	DNS_STAT_ANY_ERR,
+	DNS_STAT_NX,
+	DNS_STAT_TIMEOUT,
+	DNS_STAT_REFUSED,
+	DNS_STAT_OTHER,
+	DNS_STAT_INVALID,
+	DNS_STAT_TOO_BIG,
+	DNS_STAT_TRUNCATED,
+	DNS_STAT_OUTDATED,
+	DNS_STAT_END,
+};
+
+static struct name_desc dns_stats[] = {
+	[DNS_STAT_ID]          = { .name = "id",          .desc = "ID" },
+	[DNS_STAT_SND_ERROR]   = { .name = "send_error",  .desc = "Send error" },
+	[DNS_STAT_VALID]       = { .name = "valid",       .desc = "Valid" },
+	[DNS_STAT_UPDATE]      = { .name = "update",      .desc = "Update" },
+	[DNS_STAT_CNAME]       = { .name = "cname",       .desc = "CNAME" },
+	[DNS_STAT_CNAME_ERROR] = { .name = "cname_error", .desc = "CNAME error" },
+	[DNS_STAT_ANY_ERR]     = { .name = "any_err",     .desc = "Any errors" },
+	[DNS_STAT_NX]          = { .name = "nx",          .desc = "NX" },
+	[DNS_STAT_TIMEOUT]     = { .name = "timeout",     .desc = "Timeout" },
+	[DNS_STAT_REFUSED]     = { .name = "refused",     .desc = "Refused" },
+	[DNS_STAT_OTHER]       = { .name = "other",       .desc = "Other" },
+	[DNS_STAT_INVALID]     = { .name = "invalid",     .desc = "Invalid" },
+	[DNS_STAT_TOO_BIG]     = { .name = "too_big",     .desc = "Too big" },
+	[DNS_STAT_TRUNCATED]   = { .name = "truncated",   .desc = "Truncated" },
+	[DNS_STAT_OUTDATED]    = { .name = "outdated",    .desc = "Outdated" },
+};
+
+static struct dns_counters dns_counters;
+
+static void dns_fill_stats(void *d, struct field *stats)
+{
+	struct dns_counters *counters = d;
+	stats[DNS_STAT_ID]          = mkf_str(FO_CONFIG, counters->id);
+	stats[DNS_STAT_SND_ERROR]   = mkf_u64(FN_GAUGE, counters->snd_error);
+	stats[DNS_STAT_VALID]       = mkf_u64(FN_GAUGE, counters->valid);
+	stats[DNS_STAT_UPDATE]      = mkf_u64(FN_GAUGE, counters->update);
+	stats[DNS_STAT_CNAME]       = mkf_u64(FN_GAUGE, counters->cname);
+	stats[DNS_STAT_CNAME_ERROR] = mkf_u64(FN_GAUGE, counters->cname_error);
+	stats[DNS_STAT_ANY_ERR]     = mkf_u64(FN_GAUGE, counters->any_err);
+	stats[DNS_STAT_NX]          = mkf_u64(FN_GAUGE, counters->nx);
+	stats[DNS_STAT_TIMEOUT]     = mkf_u64(FN_GAUGE, counters->timeout);
+	stats[DNS_STAT_REFUSED]     = mkf_u64(FN_GAUGE, counters->refused);
+	stats[DNS_STAT_OTHER]       = mkf_u64(FN_GAUGE, counters->other);
+	stats[DNS_STAT_INVALID]     = mkf_u64(FN_GAUGE, counters->invalid);
+	stats[DNS_STAT_TOO_BIG]     = mkf_u64(FN_GAUGE, counters->too_big);
+	stats[DNS_STAT_TRUNCATED]   = mkf_u64(FN_GAUGE, counters->truncated);
+	stats[DNS_STAT_OUTDATED]    = mkf_u64(FN_GAUGE, counters->outdated);
+}
+
+static struct stats_module dns_stats_module = {
+	.name          = "dns",
+	.domain_flags  = STATS_DOMAIN_DNS << STATS_DOMAIN,
+	.fill_stats    = dns_fill_stats,
+	.stats         = dns_stats,
+	.stats_count   = DNS_STAT_END,
+	.counters      = &dns_counters,
+	.counters_size = sizeof(dns_counters),
+	.clearable     = 0,
+};
+
+INITCALL1(STG_REGISTER, stats_register_module, &dns_stats_module);
+
 /* Returns a pointer to the resolvers matching the id <id>. NULL is returned if
  * no match is found.
  */
@@ -307,7 +379,7 @@
 
 		ret = send(fd, trash.area, len, 0);
 		if (ret == len) {
-			ns->counters.sent++;
+			ns->counters->sent++;
 			resolution->nb_queries++;
 			continue;
 		}
@@ -319,7 +391,7 @@
 		}
 
 	snd_error:
-		ns->counters.snd_error++;
+		ns->counters->snd_error++;
 		resolution->nb_queries++;
 	}
 
@@ -1834,7 +1906,7 @@
 
 		/* message too big */
 		if (buflen > resolvers->accepted_payload_size) {
-			ns->counters.too_big++;
+			ns->counters->too_big++;
 			continue;
 		}
 
@@ -1843,7 +1915,7 @@
 
 		/* read the query id from the packet (16 bits) */
 		if (buf + 2 > bufend) {
-			ns->counters.invalid++;
+			ns->counters->invalid++;
 			continue;
 		}
 		query_id = dns_response_get_query_id(buf);
@@ -1852,7 +1924,7 @@
 		eb = eb32_lookup(&resolvers->query_ids, query_id);
 		if (eb == NULL) {
 			/* unknown query id means an outdated response and can be safely ignored */
-			ns->counters.outdated++;
+			ns->counters->outdated++;
 			continue;
 		}
 
@@ -1872,39 +1944,39 @@
 			case DNS_RESP_QUERY_COUNT_ERROR:
 			case DNS_RESP_WRONG_NAME:
 				res->status = RSLV_STATUS_INVALID;
-				ns->counters.invalid++;
+				ns->counters->invalid++;
 				break;
 
 			case DNS_RESP_NX_DOMAIN:
 				res->status = RSLV_STATUS_NX;
-				ns->counters.nx++;
+				ns->counters->nx++;
 				break;
 
 			case DNS_RESP_REFUSED:
 				res->status = RSLV_STATUS_REFUSED;
-				ns->counters.refused++;
+				ns->counters->refused++;
 				break;
 
 			case DNS_RESP_ANCOUNT_ZERO:
 				res->status = RSLV_STATUS_OTHER;
-				ns->counters.any_err++;
+				ns->counters->any_err++;
 				break;
 
 			case DNS_RESP_CNAME_ERROR:
 				res->status = RSLV_STATUS_OTHER;
-				ns->counters.cname_error++;
+				ns->counters->cname_error++;
 				break;
 
 			case DNS_RESP_TRUNCATED:
 				res->status = RSLV_STATUS_OTHER;
-				ns->counters.truncated++;
+				ns->counters->truncated++;
 				break;
 
 			case DNS_RESP_NO_EXPECTED_RECORD:
 			case DNS_RESP_ERROR:
 			case DNS_RESP_INTERNAL:
 				res->status = RSLV_STATUS_OTHER;
-				ns->counters.other++;
+				ns->counters->other++;
 				break;
 		}
 
@@ -1943,14 +2015,14 @@
 		query = LIST_NEXT(&res->response.query_list, struct dns_query_item *, list);
 		if (query && dns_hostname_cmp(query->name, res->hostname_dn, res->hostname_dn_len) != 0) {
 			dns_resp = DNS_RESP_WRONG_NAME;
-			ns->counters.other++;
+			ns->counters->other++;
 			goto report_res_error;
 		}
 
 		/* So the resolution succeeded */
 		res->status     = RSLV_STATUS_VALID;
 		res->last_valid = now_ms;
-		ns->counters.valid++;
+		ns->counters->valid++;
 		goto report_res_success;
 
 	report_res_error:
@@ -2032,12 +2104,13 @@
 			goto snd_error;
 		}
 
-		ns->counters.sent++;
+		ns->counters->sent++;
+
 		res->nb_queries++;
 		continue;
 
 	  snd_error:
-		ns->counters.snd_error++;
+		ns->counters->snd_error++;
 		res->nb_queries++;
 	}
 	HA_SPIN_UNLOCK(DNS_LOCK, &resolvers->lock);
@@ -2144,6 +2217,7 @@
 				fd_delete(ns->dgram->t.sock.fd);
 			free(ns->dgram);
 			LIST_DEL(&ns->list);
+			EXTRA_COUNTERS_FREE(ns->extra_counters);
 			free(ns);
 		}
 
@@ -2229,6 +2303,12 @@
 			dgram->data      = &resolve_dgram_cb;
 			dgram->t.sock.fd = -1;
 			ns->dgram        = dgram;
+
+			/* Store the ns counters pointer */
+			if (ns->extra_counters) {
+				ns->counters = EXTRA_COUNTERS_GET(ns->extra_counters, &dns_stats_module);
+				ns->counters->id = ns->id;
+			}
 		}
 
 		/* Create the task associated to the resolvers section */
@@ -2288,6 +2368,144 @@
 	dns_deinit();
 	return err_code;
 
+}
+
+static int stats_dump_dns_to_buffer(struct stream_interface *si,
+                                    struct dns_nameserver *ns,
+                                    struct field *stats, size_t stats_count,
+                                    struct list *stat_modules)
+{
+	struct appctx *appctx = __objt_appctx(si->end);
+	struct channel *rep = si_ic(si);
+	struct stats_module *mod;
+	size_t idx = 0;
+
+	memset(stats, 0, sizeof(struct field) * stats_count);
+
+	list_for_each_entry(mod, stat_modules, list) {
+		struct counters_node *counters = EXTRA_COUNTERS_GET(ns->extra_counters, mod);
+
+		mod->fill_stats(counters, stats + idx);
+		idx += mod->stats_count;
+	}
+
+	if (!stats_dump_one_line(stats, idx, appctx))
+		return 0;
+
+	if (!stats_putchk(rep, NULL, &trash))
+		goto full;
+
+	return 1;
+
+  full:
+	si_rx_room_rdy(si);
+	return 0;
+}
+
+/* Uses <appctx.ctx.stats.obj1> as a pointer to the current resolver and <obj2>
+ * as a pointer to the current nameserver.
+ */
+int stats_dump_dns(struct stream_interface *si,
+                   struct field *stats, size_t stats_count,
+                   struct list *stat_modules)
+{
+	struct appctx *appctx = __objt_appctx(si->end);
+	struct channel *rep = si_ic(si);
+	struct dns_resolvers *resolver = appctx->ctx.stats.obj1;
+	struct dns_nameserver *ns = appctx->ctx.stats.obj2;
+
+	if (!resolver)
+		resolver = LIST_NEXT(&dns_resolvers, struct dns_resolvers *, list);
+
+	/* dump resolvers */
+	list_for_each_entry_from(resolver, &dns_resolvers, list) {
+		appctx->ctx.stats.obj1 = resolver;
+
+		ns = appctx->ctx.stats.obj2 ?
+		     appctx->ctx.stats.obj2 :
+		     LIST_NEXT(&resolver->nameservers, struct dns_nameserver *, list);
+
+		list_for_each_entry_from(ns, &resolver->nameservers, list) {
+			appctx->ctx.stats.obj2 = ns;
+
+			if (buffer_almost_full(&rep->buf))
+				goto full;
+
+			if (!stats_dump_dns_to_buffer(si, ns,
+			                              stats, stats_count,
+			                              stat_modules)) {
+				return 0;
+			}
+		}
+
+		appctx->ctx.stats.obj2 = NULL;
+	}
+
+	return 1;
+
+  full:
+	si_rx_room_blk(si);
+	return 0;
+}
+
+void dns_stats_clear_counters(int clrall, struct list *stat_modules)
+{
+	struct dns_resolvers  *resolvers;
+	struct dns_nameserver *ns;
+	struct stats_module *mod;
+	void *counters;
+
+	list_for_each_entry(mod, stat_modules, list) {
+		if (!mod->clearable && !clrall)
+			continue;
+
+		list_for_each_entry(resolvers, &dns_resolvers, list) {
+			list_for_each_entry(ns, &resolvers->nameservers, list) {
+				counters = EXTRA_COUNTERS_GET(ns->extra_counters, mod);
+				memcpy(counters, mod->counters, mod->counters_size);
+			}
+		}
+	}
+
+}
+
+int dns_allocate_counters(struct list *stat_modules)
+{
+	struct stats_module *mod;
+	struct dns_resolvers *resolvers;
+	struct dns_nameserver *ns;
+
+	list_for_each_entry(resolvers, &dns_resolvers, list) {
+		list_for_each_entry(ns, &resolvers->nameservers, list) {
+			EXTRA_COUNTERS_REGISTER(&ns->extra_counters, COUNTERS_DNS,
+			                        alloc_failed);
+
+			list_for_each_entry(mod, stat_modules, list) {
+				EXTRA_COUNTERS_ADD(mod,
+				                   ns->extra_counters,
+				                   mod->counters,
+				                   mod->counters_size);
+			}
+
+			EXTRA_COUNTERS_ALLOC(ns->extra_counters, alloc_failed);
+
+			list_for_each_entry(mod, stat_modules, list) {
+				memcpy(ns->extra_counters->data + mod->counters_off[ns->extra_counters->type],
+				       mod->counters, mod->counters_size);
+
+				/* Store the ns counters pointer */
+				if (!strcmp(mod->name, "dns")) {
+					ns->counters = (struct dns_counters *)ns->extra_counters->data + mod->counters_off[COUNTERS_DNS];
+					ns->counters->id = ns->id;
+				}
+			}
+		}
+	}
+
+	return 1;
+
+alloc_failed:
+	return 0;
 }
 
 /* if an arg is found, it sets the resolvers section pointer into cli.p0 */
@@ -2338,21 +2556,21 @@
 				chunk_appendf(&trash, "Resolvers section %s\n", resolvers->id);
 				list_for_each_entry(ns, &resolvers->nameservers, list) {
 					chunk_appendf(&trash, " nameserver %s:\n", ns->id);
-					chunk_appendf(&trash, "  sent:        %lld\n", ns->counters.sent);
-					chunk_appendf(&trash, "  snd_error:   %lld\n", ns->counters.snd_error);
-					chunk_appendf(&trash, "  valid:       %lld\n", ns->counters.valid);
-					chunk_appendf(&trash, "  update:      %lld\n", ns->counters.update);
-					chunk_appendf(&trash, "  cname:       %lld\n", ns->counters.cname);
-					chunk_appendf(&trash, "  cname_error: %lld\n", ns->counters.cname_error);
-					chunk_appendf(&trash, "  any_err:     %lld\n", ns->counters.any_err);
-					chunk_appendf(&trash, "  nx:          %lld\n", ns->counters.nx);
-					chunk_appendf(&trash, "  timeout:     %lld\n", ns->counters.timeout);
-					chunk_appendf(&trash, "  refused:     %lld\n", ns->counters.refused);
-					chunk_appendf(&trash, "  other:       %lld\n", ns->counters.other);
-					chunk_appendf(&trash, "  invalid:     %lld\n", ns->counters.invalid);
-					chunk_appendf(&trash, "  too_big:     %lld\n", ns->counters.too_big);
-					chunk_appendf(&trash, "  truncated:   %lld\n", ns->counters.truncated);
-					chunk_appendf(&trash, "  outdated:    %lld\n",  ns->counters.outdated);
+					chunk_appendf(&trash, "  sent:        %lld\n", ns->counters->sent);
+					chunk_appendf(&trash, "  snd_error:   %lld\n", ns->counters->snd_error);
+					chunk_appendf(&trash, "  valid:       %lld\n", ns->counters->valid);
+					chunk_appendf(&trash, "  update:      %lld\n", ns->counters->update);
+					chunk_appendf(&trash, "  cname:       %lld\n", ns->counters->cname);
+					chunk_appendf(&trash, "  cname_error: %lld\n", ns->counters->cname_error);
+					chunk_appendf(&trash, "  any_err:     %lld\n", ns->counters->any_err);
+					chunk_appendf(&trash, "  nx:          %lld\n", ns->counters->nx);
+					chunk_appendf(&trash, "  timeout:     %lld\n", ns->counters->timeout);
+					chunk_appendf(&trash, "  refused:     %lld\n", ns->counters->refused);
+					chunk_appendf(&trash, "  other:       %lld\n", ns->counters->other);
+					chunk_appendf(&trash, "  invalid:     %lld\n", ns->counters->invalid);
+					chunk_appendf(&trash, "  too_big:     %lld\n", ns->counters->too_big);
+					chunk_appendf(&trash, "  truncated:   %lld\n", ns->counters->truncated);
+					chunk_appendf(&trash, "  outdated:    %lld\n",  ns->counters->outdated);
 				}
 				chunk_appendf(&trash, "\n");
 			}
diff --git a/src/server.c b/src/server.c
index b1656d5..4de4543 100644
--- a/src/server.c
+++ b/src/server.c
@@ -3868,7 +3868,7 @@
 
  save_ip:
 	if (nameserver) {
-		nameserver->counters.update++;
+		nameserver->counters->update++;
 		/* save the first ip we found */
 		chunk_printf(chk, "%s/%s", nameserver->resolvers->id, nameserver->id);
 	}
@@ -3882,7 +3882,7 @@
 
  invalid:
 	if (nameserver) {
-		nameserver->counters.invalid++;
+		nameserver->counters->invalid++;
 		goto update_status;
 	}
 	snr_update_srv_status(s, has_no_ip);
diff --git a/src/stats.c b/src/stats.c
index f84488a..aa3fe8b 100644
--- a/src/stats.c
+++ b/src/stats.c
@@ -265,6 +265,7 @@
 /* list of all registered stats module */
 static struct list stats_module_list[STATS_DOMAIN_COUNT] = {
 	LIST_HEAD_INIT(stats_module_list[STATS_DOMAIN_PROXY]),
+	LIST_HEAD_INIT(stats_module_list[STATS_DOMAIN_DNS]),
 };
 
 static inline uint8_t stats_get_domain(uint32_t domain)
@@ -604,6 +605,11 @@
 			              stats[ST_F_PID].u.u32);
 			break;
 
+		case STATS_DOMAIN_DNS:
+			chunk_appendf(out, "D.%d.%s:", field,
+			              stat_f[domain][field].name);
+			break;
+
 		default:
 			break;
 		}
@@ -701,6 +707,18 @@
 	              obj_type, iid, sid, pos, name, pid);
 }
 
+static void stats_print_dns_field_json(struct buffer *out,
+                                       const struct field *stat,
+                                       const char *name,
+                                       int pos)
+{
+	chunk_appendf(out,
+	              "{"
+	              "\"field\":{\"pos\":%d,\"name\":\"%s\"},",
+	              pos, name);
+}
+
+
 /* Dump all fields from <stats> into <out> using a typed "field:desc:type:value" format */
 static int stats_dump_fields_json(struct buffer *out,
                                   const struct field *stats, size_t stats_count,
@@ -734,6 +752,10 @@
 			                             stats[ST_F_IID].u.u32,
 			                             stats[ST_F_SID].u.u32,
 			                             stats[ST_F_PID].u.u32);
+		} else if (domain == STATS_DOMAIN_DNS) {
+			stats_print_dns_field_json(out, &stats[field],
+			                           stat_f[domain][field].name,
+			                           field);
 		}
 
 		if (old_len == out->data)
@@ -3052,6 +3074,7 @@
 {
 	struct appctx *appctx = __objt_appctx(si->end);
 	struct channel *rep = si_ic(si);
+	enum stats_domain domain = appctx->ctx.stats.domain;
 
 	chunk_reset(&trash);
 
@@ -3087,15 +3110,30 @@
 				goto full;
 		}
 
-		appctx->ctx.stats.obj1 = proxies_list;
+		if (domain == STATS_DOMAIN_PROXY)
+			appctx->ctx.stats.obj1 = proxies_list;
+
 		appctx->ctx.stats.px_st = STAT_PX_ST_INIT;
 		appctx->st2 = STAT_ST_LIST;
 		/* fall through */
 
 	case STAT_ST_LIST:
-		/* dump proxies */
-		if (!stats_dump_proxies(si, htx, uri))
-			return 0;
+		switch (domain) {
+		case STATS_DOMAIN_DNS:
+			if (!stats_dump_dns(si, stat_l[domain],
+			                    stat_count[domain],
+			                    &stats_module_list[domain])) {
+				return 0;
+			}
+			break;
+
+		case STATS_DOMAIN_PROXY:
+		default:
+			/* dump proxies */
+			if (!stats_dump_proxies(si, htx, uri))
+				return 0;
+			break;
+		}
 
 		appctx->st2 = STAT_ST_END;
 		/* fall through */
@@ -4194,6 +4232,8 @@
 		}
 	}
 
+	dns_stats_clear_counters(clrall, &stats_module_list[STATS_DOMAIN_DNS]);
+
 	memset(activity, 0, sizeof(activity));
 	return 1;
 }
@@ -4236,10 +4276,14 @@
 	if (!strcmp(args[arg], "domain")) {
 		++args;
 
-		if (!strcmp(args[arg], "proxy"))
+		if (!strcmp(args[arg], "proxy")) {
 			++args;
-		else
+		} else if (!strcmp(args[arg], "dns")) {
+			appctx->ctx.stats.domain = STATS_DOMAIN_DNS;
+			++args;
+		} else {
 			return cli_err(appctx, "Invalid statistics domain.\n");
+		}
 	}
 
 	if (appctx->ctx.stats.domain == STATS_DOMAIN_PROXY
@@ -4417,6 +4461,44 @@
 
 REGISTER_CONFIG_POSTPARSER("allocate-stats-px", allocate_stats_px_postcheck);
 
+static int allocate_stats_dns_postcheck(void)
+{
+	struct stats_module *mod;
+	size_t i = 0;
+	int err_code = 0;
+
+	stat_f[STATS_DOMAIN_DNS] = malloc(stat_count[STATS_DOMAIN_DNS] * sizeof(struct name_desc));
+	if (!stat_f[STATS_DOMAIN_DNS]) {
+		ha_alert("stats: cannot allocate all fields for dns statistics\n");
+		err_code |= ERR_ALERT | ERR_FATAL;
+		return err_code;
+	}
+
+	list_for_each_entry(mod, &stats_module_list[STATS_DOMAIN_DNS], list) {
+		memcpy(stat_f[STATS_DOMAIN_DNS] + i,
+		       mod->stats,
+		       mod->stats_count * sizeof(struct name_desc));
+		i += mod->stats_count;
+	}
+
+	if (!dns_allocate_counters(&stats_module_list[STATS_DOMAIN_DNS])) {
+		ha_alert("stats: cannot allocate all counters for dns statistics\n");
+		err_code |= ERR_ALERT | ERR_FATAL;
+		return err_code;
+	}
+
+	stat_l[STATS_DOMAIN_DNS] = malloc(stat_count[STATS_DOMAIN_DNS] * sizeof(struct field));
+	if (!stat_l[STATS_DOMAIN_DNS]) {
+		ha_alert("stats: cannot allocate a line for dns statistics\n");
+		err_code |= ERR_ALERT | ERR_FATAL;
+		return err_code;
+	}
+
+	return err_code;
+}
+
+REGISTER_CONFIG_POSTPARSER("allocate-stats-dns", allocate_stats_dns_postcheck);
+
 /* register cli keywords */
 static struct cli_kw_list cli_kws = {{ },{
 	{ { "clear", "counters",  NULL }, "clear counters : clear max statistics counters (add 'all' for all counters)", cli_parse_clear_counters, NULL, NULL },