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]>
---
 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
+])
+
 OVN_CLEANUP([hv1])
 
 AT_CLEANUP
-- 
2.52.0

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to