On Thu, Jan 22, 2026 at 12:04 AM Ketan Supanekar via dev < [email protected]> wrote:
> Add DNS query statistics tracking using OVS coverage counters in > ovn-controller for observability into DNS query processing. > > Tracked metrics include: > - Total queries processed > - Query types (A, AAAA, PTR, ANY, Other) > - Cache performance (hits/misses) > - Responses sent > - Error conditions (truncated, parse failures, etc.) > > Statistics can be queried using: > ovn-appctl -t ovn-controller coverage/read-counter <counter_name> > ovn-appctl -t ovn-controller coverage/show > > The implementation uses coverage counter verification into existing DNS > tests > in tests/ovn.at. > > Signed-off-by: Ketan Supanekar <[email protected]> > --- > Hello Ketan, thank you for v6. > NEWS | 5 +++++ > controller/pinctrl.c | 48 ++++++++++++++++++++++++++++++++++++++++++++ > tests/ovn.at | 28 ++++++++++++++++++++++++++ > 3 files changed, 81 insertions(+) > > diff --git a/NEWS b/NEWS > index 9883fb81d..dc9d28f2c 100644 > --- a/NEWS > +++ b/NEWS > @@ -1,5 +1,10 @@ > Post v25.09.0 > ------------- > + - Added DNS query statistics tracking in ovn-controller using OVS > coverage > + counters. Statistics can be queried using "ovn-appctl -t > ovn-controller > + coverage/read-counter <counter_name>" or "coverage/show". Tracked > metrics > + include total queries, query types (A, AAAA, PTR, ANY, Other), cache > + performance (hits/misses), responses sent, and error conditions. > - Added support for TLS Server Name Indication (SNI) with the new > --ssl-server-name option in OVN utilities and daemons. This allows > specifying the server name for SNI, which is useful when connecting > diff --git a/controller/pinctrl.c b/controller/pinctrl.c > index 6f7ae4037..ab8a0a37c 100644 > --- a/controller/pinctrl.c > +++ b/controller/pinctrl.c > @@ -391,6 +391,22 @@ COVERAGE_DEFINE(pinctrl_drop_put_vport_binding); > COVERAGE_DEFINE(pinctrl_notify_main_thread); > COVERAGE_DEFINE(pinctrl_total_pin_pkts); > > +/* DNS query statistics - thread-safe coverage counters */ > +COVERAGE_DEFINE(dns_query_total); > +COVERAGE_DEFINE(dns_query_type_a); > +COVERAGE_DEFINE(dns_query_type_aaaa); > +COVERAGE_DEFINE(dns_query_type_ptr); > +COVERAGE_DEFINE(dns_query_type_any); > +COVERAGE_DEFINE(dns_query_type_other); > +COVERAGE_DEFINE(dns_cache_hit); > +COVERAGE_DEFINE(dns_cache_miss); > +COVERAGE_DEFINE(dns_error_truncated); > +COVERAGE_DEFINE(dns_skipped_not_request); > +COVERAGE_DEFINE(dns_error_no_query); > +COVERAGE_DEFINE(dns_error_parse_failure); > +COVERAGE_DEFINE(dns_unsupported_ovn_owned); > +COVERAGE_DEFINE(dns_response_sent); > + > struct empty_lb_backends_event { > struct hmap_node hmap_node; > long long int timestamp; > @@ -3366,6 +3382,9 @@ pinctrl_handle_dns_lookup( > uint32_t success = 0; > bool send_refuse = false; > > + /* Track total DNS queries received */ > + COVERAGE_INC(dns_query_total); > + > /* Parse result field. */ > const struct mf_field *f; > enum ofperr ofperr = nx_pull_header(userdata, NULL, &f, NULL); > @@ -3392,6 +3411,7 @@ pinctrl_handle_dns_lookup( > /* Check that the packet stores at least the minimal headers. */ > if (dp_packet_l4_size(pkt_in) < (UDP_HEADER_LEN + DNS_HEADER_LEN)) { > VLOG_WARN_RL(&rl, "truncated dns packet"); > + COVERAGE_INC(dns_error_truncated); > goto exit; > } > > @@ -3399,17 +3419,20 @@ pinctrl_handle_dns_lookup( > struct dns_header const *in_dns_header = > dp_packet_get_udp_payload(pkt_in); > if (!in_dns_header) { > VLOG_WARN_RL(&rl, "truncated dns packet"); > + COVERAGE_INC(dns_error_truncated); > goto exit; > } > > /* Check if it is DNS request or not */ > if (in_dns_header->lo_flag & 0x80) { > /* It's a DNS response packet which we are not interested in */ > + COVERAGE_INC(dns_skipped_not_request); > goto exit; > } > > /* Check if at least one query request is present */ > if (!in_dns_header->qdcount) { > + COVERAGE_INC(dns_error_no_query); > goto exit; > } > > @@ -3431,6 +3454,7 @@ pinctrl_handle_dns_lookup( > uint8_t label_len = in_dns_data[idx++]; > if (in_dns_data + idx + label_len > end) { > ds_destroy(&query_name); > + COVERAGE_INC(dns_error_parse_failure); > goto exit; > } > ds_put_buffer(&query_name, (const char *) in_dns_data + idx, > label_len); > @@ -3449,6 +3473,26 @@ pinctrl_handle_dns_lookup( > } > > uint16_t query_type = ntohs(get_unaligned_be16((void *) in_dns_data)); > + > + /* Track query type statistics */ > + switch (query_type) { > + case DNS_QUERY_TYPE_A: > + COVERAGE_INC(dns_query_type_a); > + break; > + case DNS_QUERY_TYPE_AAAA: > + COVERAGE_INC(dns_query_type_aaaa); > + break; > + case DNS_QUERY_TYPE_PTR: > + COVERAGE_INC(dns_query_type_ptr); > + break; > + case DNS_QUERY_TYPE_ANY: > + COVERAGE_INC(dns_query_type_any); > + break; > + default: > + COVERAGE_INC(dns_query_type_other); > + break; > + } > + > /* Supported query types - A, AAAA, ANY and PTR */ > if (!(query_type == DNS_QUERY_TYPE_A || query_type == > DNS_QUERY_TYPE_AAAA > || query_type == DNS_QUERY_TYPE_ANY > @@ -3467,8 +3511,10 @@ pinctrl_handle_dns_lookup( > &ovn_owned); > ds_destroy(&query_name); > if (!answer_data) { > + COVERAGE_INC(dns_cache_miss); > goto exit; > } > + COVERAGE_INC(dns_cache_hit); > > > uint16_t ancount = 0; > @@ -3511,6 +3557,7 @@ pinctrl_handle_dns_lookup( > if (ovn_owned && (query_type == DNS_QUERY_TYPE_AAAA || > query_type == DNS_QUERY_TYPE_A) && !ancount) { > send_refuse = true; > + COVERAGE_INC(dns_unsupported_ovn_owned); > } > > destroy_lport_addresses(&ip_addrs); > @@ -3595,6 +3642,7 @@ pinctrl_handle_dns_lookup( > pin->packet_len = dp_packet_size(&pkt_out); > > success = 1; > + COVERAGE_INC(dns_response_sent); > exit: > if (!ofperr) { > union mf_subvalue sv; > diff --git a/tests/ovn.at b/tests/ovn.at > index 445a74ce5..d657bd2e1 100644 > --- a/tests/ovn.at > +++ b/tests/ovn.at > @@ -12136,6 +12136,34 @@ reset_pcap_file hv1-vif1 hv1/vif1 > reset_pcap_file hv1-vif2 hv1/vif2 > rm -f 1.expected > rm -f 2.expected > + > +AS_BOX([Verify DNS coverage counters]) > +# The test has sent multiple DNS queries of various types, verify counters > +# Total queries should be > 0 > +AT_CHECK([as hv1 ovn-appctl -t ovn-controller coverage/read-counter > dns_query_total | awk '{if ($1 > 0) print "ok"}'], [0], [ok > +]) > +# A record queries (multiple A queries sent) > +AT_CHECK([as hv1 ovn-appctl -t ovn-controller coverage/read-counter > dns_query_type_a | awk '{if ($1 > 0) print "ok"}'], [0], [ok > +]) > +# AAAA record queries (IPv6 queries sent) > +AT_CHECK([as hv1 ovn-appctl -t ovn-controller coverage/read-counter > dns_query_type_aaaa | awk '{if ($1 > 0) print "ok"}'], [0], [ok > +]) > +# PTR record queries (reverse DNS lookups sent) > +AT_CHECK([as hv1 ovn-appctl -t ovn-controller coverage/read-counter > dns_query_type_ptr | awk '{if ($1 > 0) print "ok"}'], [0], [ok > +]) > +# ANY record queries (vm1_ipv4_v6 queries sent) > +AT_CHECK([as hv1 ovn-appctl -t ovn-controller coverage/read-counter > dns_query_type_any | awk '{if ($1 > 0) print "ok"}'], [0], [ok > +]) > +# Cache hits (repeated queries) > +AT_CHECK([as hv1 ovn-appctl -t ovn-controller coverage/read-counter > dns_cache_hit | awk '{if ($1 > 0) print "ok"}'], [0], [ok > +]) > +# Cache misses (queries for non-existent records) > +AT_CHECK([as hv1 ovn-appctl -t ovn-controller coverage/read-counter > dns_cache_miss | awk '{if ($1 > 0) print "ok"}'], [0], [ok > +]) > +# Responses sent > +AT_CHECK([as hv1 ovn-appctl -t ovn-controller coverage/read-counter > dns_response_sent | awk '{if ($1 > 0) print "ok"}'], [0], [ok > +]) > + > The test was a bit flaky, so I have adjusted it to use OVS_WAIT_FOR_OUTPUT instead. OVN_CLEANUP([hv1]) > > AT_CLEANUP > -- > 2.52.0 > > _______________________________________________ > dev mailing list > [email protected] > https://mail.openvswitch.org/mailman/listinfo/ovs-dev > > With that change I went ahead and merged this into main, I have also added you into the AUTHORS file. Regards, Ales _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
