dns: add cache/queue for outgoing queries
authorFelix Fietkau <[email protected]>
Wed, 28 May 2025 21:48:18 +0000 (23:48 +0200)
committerFelix Fietkau <[email protected]>
Wed, 28 May 2025 21:48:19 +0000 (23:48 +0200)
Batch outgoing questions and drop redundant ones.

Signed-off-by: Felix Fietkau <[email protected]>
cache.c
dns.c
dns.h

diff --git a/cache.c b/cache.c
index 51eb707848e9856ecebd635c60ebc795feb03a7c..0ccac4952e7b461fa6c9e97d6264ac3a977ca62a 100644 (file)
--- a/cache.c
+++ b/cache.c
@@ -143,26 +143,17 @@ void
 cache_update(void)
 {
        struct cache_service *s;
-       int count = 2;
 
-       dns_packet_init();
-       dns_packet_question(C_DNS_SD, TYPE_ANY);
-       dns_packet_question(C_DNS_SD, TYPE_PTR);
+       dns_query(C_DNS_SD, TYPE_ANY);
+       dns_query(C_DNS_SD, TYPE_PTR);
        avl_for_each_element(&services, s, avl) {
                if (s->host) {
-                       dns_packet_question(s->host, TYPE_A);
-                       dns_packet_question(s->host, TYPE_AAAA);
+                       dns_query(s->host, TYPE_A);
+                       dns_query(s->host, TYPE_AAAA);
                } else {
-                       dns_packet_question(s->entry, TYPE_PTR);
+                       dns_query(s->entry, TYPE_PTR);
                }
-               if (++count < 16)
-                       continue;
-               dns_packet_broadcast();
-               count = 0;
        }
-
-       if (count)
-               dns_packet_broadcast();
 }
 
 static struct cache_service*
@@ -203,14 +194,12 @@ cache_service(struct interface *iface, char *entry, int hlen, int ttl)
                s->avl.key = type;
        avl_insert(&services, &s->avl);
 
-       dns_packet_init();
        if (hlen) {
-               dns_packet_question(s->host, TYPE_A);
-               dns_packet_question(s->host, TYPE_AAAA);
+               dns_query(s->host, TYPE_A);
+               dns_query(s->host, TYPE_AAAA);
        } else {
-               dns_packet_question(entry, TYPE_PTR);
+               dns_query(entry, TYPE_PTR);
        }
-       dns_packet_broadcast();
 
        return s;
 }
diff --git a/dns.c b/dns.c
index 715125b8b782bf6016ac5dcda9509a48d33bd0bf..7c24ddc8be4de9bbeefbaad87cc7e80d0c57d2bd 100644 (file)
--- a/dns.c
+++ b/dns.c
@@ -31,6 +31,7 @@
 #include <libubox/uloop.h>
 #include <libubox/usock.h>
 #include <libubox/utils.h>
+#include <libubox/avl-cmp.h>
 
 #include "announce.h"
 #include "util.h"
 #include "service.h"
 #include "interface.h"
 
+#define QUERY_BATCH_SIZE       16
+
+struct query_entry {
+       struct avl_node node;
+       uint16_t type;
+       char name[];
+};
+
+static AVL_TREE(queries, avl_strcmp, true, NULL);
 static char name_buffer[MAX_NAME_LEN + 1];
 
 static struct {
@@ -185,7 +195,7 @@ void dns_packet_send(struct interface *iface, struct sockaddr *to, bool query, i
                perror("failed to send answer");
 }
 
-void dns_packet_broadcast(void)
+static void dns_packet_broadcast(void)
 {
        struct interface *iface;
 
@@ -202,6 +212,58 @@ dns_send_question(struct interface *iface, struct sockaddr *to,
        dns_packet_send(iface, to, true, multicast);
 }
 
+static void
+dns_query_pending(struct uloop_timeout *t)
+{
+       struct query_entry *e, *tmp;
+       int count = 0;
+
+       dns_packet_init();
+       avl_remove_all_elements(&queries, e, node, tmp) {
+               dns_packet_question(e->name, e->type);
+               free(e);
+
+               if (++count < QUERY_BATCH_SIZE)
+                       continue;
+
+               count = 0;
+               dns_packet_broadcast();
+       }
+
+       if (count)
+               dns_packet_broadcast();
+}
+
+void dns_query(const char *name, uint16_t type)
+{
+       static struct uloop_timeout timer = {
+               .cb = dns_query_pending
+       };
+       struct query_entry *e;
+
+       e = avl_find_element(&queries, name, e, node);
+       while (e) {
+               if (e->type == type)
+                       return;
+
+               e = avl_next_element(e, node);
+               if (strcmp(e->name, name) != 0)
+                       break;
+       }
+
+       e = calloc(1, sizeof(*e) + strlen(name) + 1);
+       e->type = type;
+       e->node.key = e->name;
+       strcpy(e->name, name);
+       avl_insert(&queries, &e->node);
+
+       if (queries.count > QUERY_BATCH_SIZE)
+               timer.cb(&timer);
+
+       if (!timer.pending)
+               uloop_timeout_set(&timer, 100);
+}
+
 void
 dns_reply_a(struct interface *iface, struct sockaddr *to, int ttl, const char *hostname)
 {
diff --git a/dns.h b/dns.h
index 65efcf7be1a89b917307957b9f4ef4b734294d66..fde049f8c40ad101acc0e49bf3e0fe10f3941ea3 100644 (file)
--- a/dns.h
+++ b/dns.h
@@ -77,7 +77,8 @@ void dns_packet_init(void);
 bool dns_packet_question(const char *name, int type);
 void dns_packet_answer(const char *name, int type, const uint8_t *rdata, uint16_t rdlength, int ttl);
 void dns_packet_send(struct interface *iface, struct sockaddr *to, bool query, int multicast);
-void dns_packet_broadcast(void);
+
+void dns_query(const char *name, uint16_t type);
 
 void dns_send_question(struct interface *iface, struct sockaddr *to,
                       const char *question, int type, int multicast);