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

Reply via email to