1 /*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2 
3     This program is free software: you can redistribute it and/or modify
4     it under the terms of the GNU General Public License as published by
5     the Free Software Foundation, either version 3 of the License, or
6     (at your option) any later version.
7 
8     This program is distributed in the hope that it will be useful,
9     but WITHOUT ANY WARRANTY; without even the implied warranty of
10     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11     GNU General Public License for more details.
12 
13     You should have received a copy of the GNU General Public License
14     along with this program.  If not, see <https://www.gnu.org/licenses/>.
15  */
16 
17 #include "contrib/macros.h"
18 #include "contrib/wire_ctx.h"
19 #include "knot/include/module.h"
20 #include "knot/nameserver/xfr.h" // Dependency on qdata->extra!
21 
22 #define MOD_PROTOCOL	"\x10""request-protocol"
23 #define MOD_OPERATION	"\x10""server-operation"
24 #define MOD_REQ_BYTES	"\x0D""request-bytes"
25 #define MOD_RESP_BYTES	"\x0E""response-bytes"
26 #define MOD_EDNS	"\x0D""edns-presence"
27 #define MOD_FLAG	"\x0D""flag-presence"
28 #define MOD_RCODE	"\x0D""response-code"
29 #define MOD_REQ_EOPT	"\x13""request-edns-option"
30 #define MOD_RESP_EOPT	"\x14""response-edns-option"
31 #define MOD_NODATA	"\x0C""reply-nodata"
32 #define MOD_QTYPE	"\x0A""query-type"
33 #define MOD_QSIZE	"\x0A""query-size"
34 #define MOD_RSIZE	"\x0A""reply-size"
35 
36 #define OTHER		"other"
37 
38 const yp_item_t stats_conf[] = {
39 	{ MOD_PROTOCOL,   YP_TBOOL, YP_VBOOL = { true } },
40 	{ MOD_OPERATION,  YP_TBOOL, YP_VBOOL = { true } },
41 	{ MOD_REQ_BYTES,  YP_TBOOL, YP_VBOOL = { true } },
42 	{ MOD_RESP_BYTES, YP_TBOOL, YP_VBOOL = { true } },
43 	{ MOD_EDNS,       YP_TBOOL, YP_VNONE },
44 	{ MOD_FLAG,       YP_TBOOL, YP_VNONE },
45 	{ MOD_RCODE,      YP_TBOOL, YP_VBOOL = { true } },
46 	{ MOD_REQ_EOPT,   YP_TBOOL, YP_VNONE },
47 	{ MOD_RESP_EOPT,  YP_TBOOL, YP_VNONE },
48 	{ MOD_NODATA,     YP_TBOOL, YP_VNONE },
49 	{ MOD_QTYPE,      YP_TBOOL, YP_VNONE },
50 	{ MOD_QSIZE,      YP_TBOOL, YP_VNONE },
51 	{ MOD_RSIZE,      YP_TBOOL, YP_VNONE },
52 	{ NULL }
53 };
54 
55 enum {
56 	CTR_PROTOCOL,
57 	CTR_OPERATION,
58 	CTR_REQ_BYTES,
59 	CTR_RESP_BYTES,
60 	CTR_EDNS,
61 	CTR_FLAG,
62 	CTR_RCODE,
63 	CTR_REQ_EOPT,
64 	CTR_RESP_EOPT,
65 	CTR_NODATA,
66 	CTR_QTYPE,
67 	CTR_QSIZE,
68 	CTR_RSIZE,
69 };
70 
71 typedef struct {
72 	bool protocol;
73 	bool operation;
74 	bool req_bytes;
75 	bool resp_bytes;
76 	bool edns;
77 	bool flag;
78 	bool rcode;
79 	bool req_eopt;
80 	bool resp_eopt;
81 	bool nodata;
82 	bool qtype;
83 	bool qsize;
84 	bool rsize;
85 } stats_t;
86 
87 typedef struct {
88 	yp_name_t *conf_name;
89 	size_t conf_offset;
90 	uint32_t count;
91 	knotd_mod_idx_to_str_f fcn;
92 } ctr_desc_t;
93 
94 enum {
95 	OPERATION_QUERY = 0,
96 	OPERATION_UPDATE,
97 	OPERATION_NOTIFY,
98 	OPERATION_AXFR,
99 	OPERATION_IXFR,
100 	OPERATION_INVALID,
101 	OPERATION__COUNT
102 };
103 
operation_to_str(uint32_t idx,uint32_t count)104 static char *operation_to_str(uint32_t idx, uint32_t count)
105 {
106 	switch (idx) {
107 	case OPERATION_QUERY:   return strdup("query");
108 	case OPERATION_UPDATE:  return strdup("update");
109 	case OPERATION_NOTIFY:  return strdup("notify");
110 	case OPERATION_AXFR:    return strdup("axfr");
111 	case OPERATION_IXFR:    return strdup("ixfr");
112 	case OPERATION_INVALID: return strdup("invalid");
113 	default:                assert(0); return NULL;
114 	}
115 }
116 
117 enum {
118 	PROTOCOL_UDP4 = 0,
119 	PROTOCOL_TCP4,
120 	PROTOCOL_UDP6,
121 	PROTOCOL_TCP6,
122 	PROTOCOL_UDP4_XDP,
123 	PROTOCOL_TCP4_XDP,
124 	PROTOCOL_UDP6_XDP,
125 	PROTOCOL_TCP6_XDP,
126 	PROTOCOL__COUNT
127 };
128 
protocol_to_str(uint32_t idx,uint32_t count)129 static char *protocol_to_str(uint32_t idx, uint32_t count)
130 {
131 	switch (idx) {
132 	case PROTOCOL_UDP4:     return strdup("udp4");
133 	case PROTOCOL_TCP4:     return strdup("tcp4");
134 	case PROTOCOL_UDP6:     return strdup("udp6");
135 	case PROTOCOL_TCP6:     return strdup("tcp6");
136 	case PROTOCOL_UDP4_XDP: return strdup("udp4-xdp");
137 	case PROTOCOL_TCP4_XDP: return strdup("tcp4-xdp");
138 	case PROTOCOL_UDP6_XDP: return strdup("udp6-xdp");
139 	case PROTOCOL_TCP6_XDP: return strdup("tcp6-xdp");
140 	default:                assert(0); return NULL;
141 	}
142 }
143 
144 enum {
145 	REQ_BYTES_QUERY = 0,
146 	REQ_BYTES_UPDATE,
147 	REQ_BYTES_OTHER,
148 	REQ_BYTES__COUNT
149 };
150 
req_bytes_to_str(uint32_t idx,uint32_t count)151 static char *req_bytes_to_str(uint32_t idx, uint32_t count)
152 {
153 	switch (idx) {
154 	case REQ_BYTES_QUERY:  return strdup("query");
155 	case REQ_BYTES_UPDATE: return strdup("update");
156 	case REQ_BYTES_OTHER:  return strdup(OTHER);
157 	default:               assert(0); return NULL;
158 	}
159 }
160 
161 enum {
162 	RESP_BYTES_REPLY = 0,
163 	RESP_BYTES_TRANSFER,
164 	RESP_BYTES_OTHER,
165 	RESP_BYTES__COUNT
166 };
167 
resp_bytes_to_str(uint32_t idx,uint32_t count)168 static char *resp_bytes_to_str(uint32_t idx, uint32_t count)
169 {
170 	switch (idx) {
171 	case RESP_BYTES_REPLY:    return strdup("reply");
172 	case RESP_BYTES_TRANSFER: return strdup("transfer");
173 	case RESP_BYTES_OTHER:    return strdup(OTHER);
174 	default:                  assert(0); return NULL;
175 	}
176 }
177 
178 enum {
179 	EDNS_REQ = 0,
180 	EDNS_RESP,
181 	EDNS__COUNT
182 };
183 
edns_to_str(uint32_t idx,uint32_t count)184 static char *edns_to_str(uint32_t idx, uint32_t count)
185 {
186 	switch (idx) {
187 	case EDNS_REQ:  return strdup("request");
188 	case EDNS_RESP: return strdup("response");
189 	default:        assert(0); return NULL;
190 	}
191 }
192 
193 enum {
194 	FLAG_DO = 0,
195 	FLAG_TC,
196 	FLAG__COUNT
197 };
198 
flag_to_str(uint32_t idx,uint32_t count)199 static char *flag_to_str(uint32_t idx, uint32_t count)
200 {
201 	switch (idx) {
202 	case FLAG_TC: return strdup("TC");
203 	case FLAG_DO: return strdup("DO");
204 	default:      assert(0); return NULL;
205 	}
206 }
207 
208 enum {
209 	NODATA_A = 0,
210 	NODATA_AAAA,
211 	NODATA_OTHER,
212 	NODATA__COUNT
213 };
214 
nodata_to_str(uint32_t idx,uint32_t count)215 static char *nodata_to_str(uint32_t idx, uint32_t count)
216 {
217 	switch (idx) {
218 	case NODATA_A:     return strdup("A");
219 	case NODATA_AAAA:  return strdup("AAAA");
220 	case NODATA_OTHER: return strdup(OTHER);
221 	default:           assert(0); return NULL;
222 	}
223 }
224 
225 #define RCODE_BADSIG	15 // Unassigned code internally used for BADSIG.
226 #define RCODE_OTHER	(KNOT_RCODE_BADCOOKIE + 1) // Other RCODES.
227 
rcode_to_str(uint32_t idx,uint32_t count)228 static char *rcode_to_str(uint32_t idx, uint32_t count)
229 {
230 	const knot_lookup_t *rcode = NULL;
231 
232 	switch (idx) {
233 	case RCODE_BADSIG:
234 		rcode = knot_lookup_by_id(knot_tsig_rcode_names, KNOT_RCODE_BADSIG);
235 		break;
236 	case RCODE_OTHER:
237 		return strdup(OTHER);
238 	default:
239 		rcode = knot_lookup_by_id(knot_rcode_names, idx);
240 		break;
241 	}
242 
243 	if (rcode != NULL) {
244 		return strdup(rcode->name);
245 	} else {
246 		return NULL;
247 	}
248 }
249 
250 #define EOPT_OTHER		(KNOT_EDNS_MAX_OPTION_CODE + 1)
251 #define req_eopt_to_str		eopt_to_str
252 #define resp_eopt_to_str	eopt_to_str
253 
eopt_to_str(uint32_t idx,uint32_t count)254 static char *eopt_to_str(uint32_t idx, uint32_t count)
255 {
256 	if (idx >= EOPT_OTHER) {
257 		return strdup(OTHER);
258 	}
259 
260 	char str[32];
261 	if (knot_opt_code_to_string(idx, str, sizeof(str)) < 0) {
262 		return NULL;
263 	} else {
264 		return strdup(str);
265 	}
266 }
267 
268 enum {
269 	QTYPE_OTHER  =   0,
270 	QTYPE_MIN1   =   1,
271 	QTYPE_MAX1   =  65,
272 	QTYPE_MIN2   =  99,
273 	QTYPE_MAX2   = 110,
274 	QTYPE_MIN3   = 255,
275 	QTYPE_MAX3   = 260,
276 	QTYPE_SHIFT2 = QTYPE_MIN2 - QTYPE_MAX1 - 1,
277 	QTYPE_SHIFT3 = QTYPE_SHIFT2 + QTYPE_MIN3 - QTYPE_MAX2 - 1,
278 	QTYPE__COUNT = QTYPE_MAX3 - QTYPE_SHIFT3 + 1
279 };
280 
qtype_to_str(uint32_t idx,uint32_t count)281 static char *qtype_to_str(uint32_t idx, uint32_t count)
282 {
283 	if (idx == QTYPE_OTHER) {
284 		return strdup(OTHER);
285 	}
286 
287 	uint16_t qtype;
288 
289 	if (idx <= QTYPE_MAX1) {
290 		qtype = idx;
291 		assert(qtype >= QTYPE_MIN1 && qtype <= QTYPE_MAX1);
292 	} else if (idx <= QTYPE_MAX2 - QTYPE_SHIFT2) {
293 		qtype = idx + QTYPE_SHIFT2;
294 		assert(qtype >= QTYPE_MIN2 && qtype <= QTYPE_MAX2);
295 	} else {
296 		qtype = idx + QTYPE_SHIFT3;
297 		assert(qtype >= QTYPE_MIN3 && qtype <= QTYPE_MAX3);
298 	}
299 
300 	char str[32];
301 	if (knot_rrtype_to_string(qtype, str, sizeof(str)) < 0) {
302 		return NULL;
303 	} else {
304 		return strdup(str);
305 	}
306 }
307 
308 #define BUCKET_SIZE	16
309 #define QSIZE_MAX_IDX	(288 / BUCKET_SIZE)
310 #define RSIZE_MAX_IDX	(4096 / BUCKET_SIZE)
311 
size_to_str(uint32_t idx,uint32_t count)312 static char *size_to_str(uint32_t idx, uint32_t count)
313 {
314 	char str[16];
315 
316 	int ret;
317 	if (idx < count - 1) {
318 		ret = snprintf(str, sizeof(str), "%u-%u", idx * BUCKET_SIZE,
319 		               (idx + 1) * BUCKET_SIZE - 1);
320 	} else {
321 		ret = snprintf(str, sizeof(str), "%u-65535", idx * BUCKET_SIZE);
322 	}
323 
324 	if (ret <= 0 || (size_t)ret >= sizeof(str)) {
325 		return NULL;
326 	} else {
327 		return strdup(str);
328 	}
329 }
330 
qsize_to_str(uint32_t idx,uint32_t count)331 static char *qsize_to_str(uint32_t idx, uint32_t count)
332 {
333 	return size_to_str(idx, count);
334 }
335 
rsize_to_str(uint32_t idx,uint32_t count)336 static char *rsize_to_str(uint32_t idx, uint32_t count)
337 {
338 	return size_to_str(idx, count);
339 }
340 
341 static const ctr_desc_t ctr_descs[] = {
342 	#define item(macro, name, count) \
343 		[CTR_##macro] = { MOD_##macro, offsetof(stats_t, name), (count), name##_to_str }
344 	item(PROTOCOL,   protocol,   PROTOCOL__COUNT),
345 	item(OPERATION,  operation,  OPERATION__COUNT),
346 	item(REQ_BYTES,  req_bytes,  REQ_BYTES__COUNT),
347 	item(RESP_BYTES, resp_bytes, RESP_BYTES__COUNT),
348 	item(EDNS,       edns,       EDNS__COUNT),
349 	item(FLAG,       flag,       FLAG__COUNT),
350 	item(RCODE,      rcode,      RCODE_OTHER + 1),
351 	item(REQ_EOPT,   req_eopt,   EOPT_OTHER + 1),
352 	item(RESP_EOPT,  resp_eopt,  EOPT_OTHER + 1),
353 	item(NODATA,     nodata,     NODATA__COUNT),
354 	item(QTYPE,      qtype,      QTYPE__COUNT),
355 	item(QSIZE,      qsize,      QSIZE_MAX_IDX + 1),
356 	item(RSIZE,      rsize,      RSIZE_MAX_IDX + 1),
357 	{ NULL }
358 };
359 
incr_edns_option(knotd_mod_t * mod,unsigned thr_id,const knot_pkt_t * pkt,unsigned ctr_name)360 static void incr_edns_option(knotd_mod_t *mod, unsigned thr_id, const knot_pkt_t *pkt, unsigned ctr_name)
361 {
362 	if (!knot_pkt_has_edns(pkt)) {
363 		return;
364 	}
365 
366 	knot_rdata_t *rdata = pkt->opt_rr->rrs.rdata;
367 	if (rdata == NULL || rdata->len == 0) {
368 		return;
369 	}
370 
371 	wire_ctx_t wire = wire_ctx_init_const(rdata->data, rdata->len);
372 	while (wire_ctx_available(&wire) > 0) {
373 		uint16_t opt_code = wire_ctx_read_u16(&wire);
374 		uint16_t opt_len = wire_ctx_read_u16(&wire);
375 		wire_ctx_skip(&wire, opt_len);
376 		if (wire.error != KNOT_EOK) {
377 			break;
378 		}
379 		knotd_mod_stats_incr(mod, thr_id, ctr_name, MIN(opt_code, EOPT_OTHER), 1);
380 	}
381 }
382 
update_counters(knotd_state_t state,knot_pkt_t * pkt,knotd_qdata_t * qdata,knotd_mod_t * mod)383 static knotd_state_t update_counters(knotd_state_t state, knot_pkt_t *pkt,
384                                      knotd_qdata_t *qdata, knotd_mod_t *mod)
385 {
386 	assert(pkt && qdata);
387 
388 	stats_t *stats = knotd_mod_ctx(mod);
389 
390 	uint16_t operation;
391 	unsigned xfr_packets = 0;
392 	unsigned tid = qdata->params->thread_id;
393 
394 	// Get the server operation.
395 	switch (qdata->type) {
396 	case KNOTD_QUERY_TYPE_NORMAL:
397 		operation = OPERATION_QUERY;
398 		break;
399 	case KNOTD_QUERY_TYPE_UPDATE:
400 		operation = OPERATION_UPDATE;
401 		break;
402 	case KNOTD_QUERY_TYPE_NOTIFY:
403 		operation = OPERATION_NOTIFY;
404 		break;
405 	case KNOTD_QUERY_TYPE_AXFR:
406 		operation = OPERATION_AXFR;
407 		if (qdata->extra->ext != NULL) {
408 			xfr_packets = ((struct xfr_proc *)qdata->extra->ext)->stats.messages;
409 		}
410 		break;
411 	case KNOTD_QUERY_TYPE_IXFR:
412 		operation = OPERATION_IXFR;
413 		if (qdata->extra->ext != NULL) {
414 			xfr_packets = ((struct xfr_proc *)qdata->extra->ext)->stats.messages;
415 		}
416 		break;
417 	default:
418 		operation = OPERATION_INVALID;
419 		break;
420 	}
421 
422 	// Count request bytes.
423 	if (stats->req_bytes) {
424 		switch (operation) {
425 		case OPERATION_QUERY:
426 			knotd_mod_stats_incr(mod, tid, CTR_REQ_BYTES, REQ_BYTES_QUERY,
427 			                     knot_pkt_size(qdata->query));
428 			break;
429 		case OPERATION_UPDATE:
430 			knotd_mod_stats_incr(mod, tid, CTR_REQ_BYTES, REQ_BYTES_UPDATE,
431 			                     knot_pkt_size(qdata->query));
432 			break;
433 		default:
434 			if (xfr_packets <= 1) {
435 				knotd_mod_stats_incr(mod, tid, CTR_REQ_BYTES, REQ_BYTES_OTHER,
436 				                     knot_pkt_size(qdata->query));
437 			}
438 			break;
439 		}
440 	}
441 
442 	// Count response bytes.
443 	if (stats->resp_bytes && state != KNOTD_STATE_NOOP) {
444 		switch (operation) {
445 		case OPERATION_QUERY:
446 			knotd_mod_stats_incr(mod, tid, CTR_RESP_BYTES, RESP_BYTES_REPLY,
447 			                     knot_pkt_size(pkt));
448 			break;
449 		case OPERATION_AXFR:
450 		case OPERATION_IXFR:
451 			knotd_mod_stats_incr(mod, tid, CTR_RESP_BYTES, RESP_BYTES_TRANSFER,
452 			                     knot_pkt_size(pkt));
453 			break;
454 		default:
455 			knotd_mod_stats_incr(mod, tid, CTR_RESP_BYTES, RESP_BYTES_OTHER,
456 			                     knot_pkt_size(pkt));
457 			break;
458 		}
459 	}
460 
461 	// Get the extended response code.
462 	uint16_t rcode = qdata->rcode;
463 	if (qdata->rcode_tsig != KNOT_RCODE_NOERROR) {
464 		rcode = qdata->rcode_tsig;
465 	}
466 
467 	// Count the response code.
468 	if (stats->rcode && state != KNOTD_STATE_NOOP) {
469 		if (xfr_packets <= 1 || rcode != KNOT_RCODE_NOERROR) {
470 			if (xfr_packets > 1) {
471 				assert(rcode != KNOT_RCODE_NOERROR);
472 				// Ignore the leading XFR message NOERROR.
473 				knotd_mod_stats_decr(mod, tid, CTR_RCODE,
474 				                     KNOT_RCODE_NOERROR, 1);
475 			}
476 
477 			if (qdata->rcode_tsig == KNOT_RCODE_BADSIG) {
478 				knotd_mod_stats_incr(mod, tid, CTR_RCODE, RCODE_BADSIG, 1);
479 			} else {
480 				knotd_mod_stats_incr(mod, tid, CTR_RCODE,
481 				                     MIN(rcode, RCODE_OTHER), 1);
482 			}
483 		}
484 	}
485 
486 	// Return if non-first transfer message.
487 	if (xfr_packets > 1) {
488 		return state;
489 	}
490 
491 	// Count the server opearation.
492 	if (stats->operation) {
493 		knotd_mod_stats_incr(mod, tid, CTR_OPERATION, operation, 1);
494 	}
495 
496 	// Count the request protocol.
497 	if (stats->protocol) {
498 		bool xdp = qdata->params->xdp_msg != NULL;
499 		if (knotd_qdata_remote_addr(qdata)->ss_family == AF_INET) {
500 			if (qdata->params->flags & KNOTD_QUERY_FLAG_LIMIT_SIZE) {
501 				if (xdp) {
502 					knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
503 					                     PROTOCOL_UDP4_XDP, 1);
504 				} else {
505 					knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
506 					                     PROTOCOL_UDP4, 1);
507 				}
508 			} else {
509 				if (xdp) {
510 					knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
511 					                     PROTOCOL_TCP4_XDP, 1);
512 				} else {
513 					knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
514 					                     PROTOCOL_TCP4, 1);
515 				}
516 			}
517 		} else {
518 			if (qdata->params->flags & KNOTD_QUERY_FLAG_LIMIT_SIZE) {
519 				if (xdp) {
520 					knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
521 					                     PROTOCOL_UDP6_XDP, 1);
522 				} else {
523 					knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
524 					                     PROTOCOL_UDP6, 1);
525 				}
526 			} else {
527 				if (xdp) {
528 					knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
529 					                     PROTOCOL_TCP6_XDP, 1);
530 				} else {
531 					knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
532 					                     PROTOCOL_TCP6, 1);
533 				}
534 			}
535 		}
536 	}
537 
538 	// Count EDNS occurrences.
539 	if (stats->edns) {
540 		if (knot_pkt_has_edns(qdata->query)) {
541 			knotd_mod_stats_incr(mod, tid, CTR_EDNS, EDNS_REQ, 1);
542 		}
543 		if (knot_pkt_has_edns(pkt) && state != KNOTD_STATE_NOOP) {
544 			knotd_mod_stats_incr(mod, tid, CTR_EDNS, EDNS_RESP, 1);
545 		}
546 	}
547 
548 	// Count interesting message header flags.
549 	if (stats->flag) {
550 		if (state != KNOTD_STATE_NOOP && knot_wire_get_tc(pkt->wire)) {
551 			knotd_mod_stats_incr(mod, tid, CTR_FLAG, FLAG_TC, 1);
552 		}
553 		if (knot_pkt_has_dnssec(pkt)) {
554 			knotd_mod_stats_incr(mod, tid, CTR_FLAG, FLAG_DO, 1);
555 		}
556 	}
557 
558 	// Count EDNS options.
559 	if (stats->req_eopt) {
560 		incr_edns_option(mod, tid, qdata->query, CTR_REQ_EOPT);
561 	}
562 	if (stats->resp_eopt) {
563 		incr_edns_option(mod, tid, pkt, CTR_RESP_EOPT);
564 	}
565 
566 	// Return if not query operation.
567 	if (operation != OPERATION_QUERY) {
568 		return state;
569 	}
570 
571 	// Count NODATA reply (RFC 2308, Section 2.2).
572 	if (stats->nodata && rcode == KNOT_RCODE_NOERROR && state != KNOTD_STATE_NOOP &&
573 	    knot_wire_get_ancount(pkt->wire) == 0 && !knot_wire_get_tc(pkt->wire) &&
574 	    (knot_wire_get_nscount(pkt->wire) == 0 ||
575 	     knot_pkt_rr(knot_pkt_section(pkt, KNOT_AUTHORITY), 0)->type == KNOT_RRTYPE_SOA)) {
576 		switch (knot_pkt_qtype(qdata->query)) {
577 		case KNOT_RRTYPE_A:
578 			knotd_mod_stats_incr(mod, tid, CTR_NODATA, NODATA_A, 1);
579 			break;
580 		case KNOT_RRTYPE_AAAA:
581 			knotd_mod_stats_incr(mod, tid, CTR_NODATA, NODATA_AAAA, 1);
582 			break;
583 		default:
584 			knotd_mod_stats_incr(mod, tid, CTR_NODATA, NODATA_OTHER, 1);
585 			break;
586 		}
587 	}
588 
589 	// Count the query type.
590 	if (stats->qtype) {
591 		uint16_t qtype = knot_pkt_qtype(qdata->query);
592 
593 		uint16_t idx;
594 		switch (qtype) {
595 		case QTYPE_MIN1 ... QTYPE_MAX1: idx = qtype; break;
596 		case QTYPE_MIN2 ... QTYPE_MAX2: idx = qtype - QTYPE_SHIFT2; break;
597 		case QTYPE_MIN3 ... QTYPE_MAX3: idx = qtype - QTYPE_SHIFT3; break;
598 		default:                        idx = QTYPE_OTHER; break;
599 		}
600 
601 		knotd_mod_stats_incr(mod, tid, CTR_QTYPE, idx, 1);
602 	}
603 
604 	// Count the query size.
605 	if (stats->qsize) {
606 		uint64_t idx = knot_pkt_size(qdata->query) / BUCKET_SIZE;
607 		knotd_mod_stats_incr(mod, tid, CTR_QSIZE, MIN(idx, QSIZE_MAX_IDX), 1);
608 	}
609 
610 	// Count the reply size.
611 	if (stats->rsize && state != KNOTD_STATE_NOOP) {
612 		uint64_t idx = knot_pkt_size(pkt) / BUCKET_SIZE;
613 		knotd_mod_stats_incr(mod, tid, CTR_RSIZE, MIN(idx, RSIZE_MAX_IDX), 1);
614 	}
615 
616 	return state;
617 }
618 
stats_load(knotd_mod_t * mod)619 int stats_load(knotd_mod_t *mod)
620 {
621 	stats_t *stats = calloc(1, sizeof(*stats));
622 	if (stats == NULL) {
623 		return KNOT_ENOMEM;
624 	}
625 
626 	for (const ctr_desc_t *desc = ctr_descs; desc->conf_name != NULL; desc++) {
627 		knotd_conf_t conf = knotd_conf_mod(mod, desc->conf_name);
628 		bool enabled = conf.single.boolean;
629 
630 		// Initialize corresponding configuration item.
631 		*(bool *)((uint8_t *)stats + desc->conf_offset) = enabled;
632 
633 		int ret = knotd_mod_stats_add(mod, enabled ? desc->conf_name + 1 : NULL,
634 		                              enabled ? desc->count : 1, desc->fcn);
635 		if (ret != KNOT_EOK) {
636 			free(stats);
637 			return ret;
638 		}
639 	}
640 
641 	knotd_mod_ctx_set(mod, stats);
642 
643 	return knotd_mod_hook(mod, KNOTD_STAGE_END, update_counters);
644 }
645 
stats_unload(knotd_mod_t * mod)646 void stats_unload(knotd_mod_t *mod)
647 {
648 	free(knotd_mod_ctx(mod));
649 }
650 
651 KNOTD_MOD_API(stats, KNOTD_MOD_FLAG_SCOPE_ANY | KNOTD_MOD_FLAG_OPT_CONF,
652               stats_load, stats_unload, stats_conf, NULL);
653