dns: rework packet API
authorFelix Fietkau <[email protected]>
Wed, 28 May 2025 19:39:36 +0000 (21:39 +0200)
committerFelix Fietkau <[email protected]>
Wed, 28 May 2025 21:11:27 +0000 (23:11 +0200)
Allow providing multiple questions/answers for different hostnames
within a single packet. Also support sending out the same packet on
multiple interfaces.

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

diff --git a/dns.c b/dns.c
index 2078d8821c226e96de28197e96a313497459ca32..ffa61ba4acd0e80c8e0081167a68d30bbbbfc67b 100644 (file)
--- a/dns.c
+++ b/dns.c
 #include "interface.h"
 
 static char name_buffer[MAX_NAME_LEN + 1];
-static char dns_buffer[MAX_NAME_LEN];
-static struct blob_buf ans_buf;
+
+static struct {
+       struct dns_header h;
+       unsigned char data[9000 - sizeof(struct dns_header)];
+} __attribute__((packed)) pkt;
+static size_t pkt_len;
+static struct dns_question *pkt_q[32];
+static unsigned int pkt_n_q;
+static unsigned char *dnptrs[255];
 
 const char*
 dns_type_string(uint16_t type)
@@ -67,121 +74,126 @@ dns_type_string(uint16_t type)
        return "N/A";
 }
 
-void
-dns_send_question(struct interface *iface, struct sockaddr *to,
-                 const char *question, int type, int multicast)
+void dns_packet_init(void)
 {
-       static struct dns_header h;
-       static struct dns_question q;
-       static struct iovec iov[] = {
-               {
-                       .iov_base = &h,
-                       .iov_len = sizeof(h),
-               },
-               {
-                       .iov_base = dns_buffer,
-               },
-               {
-                       .iov_base = &q,
-                       .iov_len = sizeof(q),
-               }
-       };
-       int len;
+       dnptrs[0] = (unsigned char *)&pkt;
+       dnptrs[1] = NULL;
+       pkt_len = 0;
+       pkt_n_q = 0;
+       memset(&pkt.h, 0, sizeof(pkt.h));
+}
 
-       h.questions = cpu_to_be16(1);
-       q.class = cpu_to_be16((multicast ? 0 : CLASS_UNICAST) | 1);
-       q.type = cpu_to_be16(type);
+static inline void *dns_packet_tail(size_t len)
+{
+       if (pkt_len + len > sizeof(pkt.data))
+               return NULL;
 
-       len = dn_comp(question, (void *) dns_buffer, sizeof(dns_buffer), NULL, NULL);
-       if (len < 1)
-               return;
+       return &pkt.data[pkt_len];
+}
 
-       iov[1].iov_len = len;
+static int
+dns_packet_add_name(const char *name)
+{
+       void *data;
+
+       data = dns_packet_tail(MAX_NAME_LEN);
+       if (!data)
+               return -1;
 
-       DBG(1, "Q <- %s %s\n", dns_type_string(type), question);
-       if (interface_send_packet(iface, to, iov, ARRAY_SIZE(iov)) < 0)
-               perror("failed to send question");
+       return dn_comp(name, data, MAX_NAME_LEN, dnptrs, dnptrs + ARRAY_SIZE(dnptrs) - 1);
 }
 
+static void *dns_packet_record_add(size_t data_len, const char *name)
+{
+       void *data;
+       int len;
 
-struct dns_reply {
-       int type;
-       struct dns_answer a;
-       uint16_t rdlength;
-       uint8_t *rdata;
-       char *buffer;
-};
+       len = dns_packet_add_name(name);
+       if (len < 1)
+               return NULL;
+
+       data = dns_packet_tail(len + data_len);
+       if (!data)
+               return NULL;
 
-static int dns_answer_cnt;
+       pkt_len += len + data_len;
 
-void
-dns_init_answer(void)
+       return data + len;
+}
+
+bool dns_packet_question(const char *name, int type)
 {
-       dns_answer_cnt = 0;
-       blob_buf_init(&ans_buf, 0);
+       struct dns_question *q;
+
+       q = dns_packet_record_add(sizeof(*q), name);
+       if (!q)
+               return false;
+
+       pkt.h.questions += cpu_to_be16(1);
+       pkt_q[pkt_n_q++] = q;
+       memset(q, 0, sizeof(*q));
+       q->class = cpu_to_be16(1);
+       q->type = cpu_to_be16(type);
+       DBG(1, "Q <- %s %s\n", dns_type_string(type), name);
+
+       return true;
 }
 
-void
-dns_add_answer(int type, const uint8_t *rdata, uint16_t rdlength, int ttl)
+void dns_packet_answer(const char *name, int type, const uint8_t *rdata, uint16_t rdlength, int ttl)
 {
-       struct blob_attr *attr;
        struct dns_answer *a;
 
-       attr = blob_new(&ans_buf, 0, sizeof(*a) + rdlength);
-       a = blob_data(attr);
+       pkt.h.flags |= cpu_to_be16(0x8400);
+
+       a = dns_packet_record_add(sizeof(*a) + rdlength, name);
+       memset(a, 0, sizeof(*a));
        a->type = cpu_to_be16(type);
        a->class = cpu_to_be16(1);
        a->ttl = cpu_to_be32(ttl);
        a->rdlength = cpu_to_be16(rdlength);
        memcpy(a + 1, rdata, rdlength);
+       DBG(1, "A <- %s %s\n", dns_type_string(be16_to_cpu(a->type)), name);
 
-       dns_answer_cnt++;
+       pkt.h.answers += cpu_to_be16(1);
 }
 
-void
-dns_send_answer(struct interface *iface, struct sockaddr *to, const char *answer)
+static void dns_question_set_multicast(struct dns_question *q, bool val)
 {
-       uint8_t buffer[256];
-       struct blob_attr *attr;
-       struct dns_header h = { 0 };
-       struct iovec *iov;
-       int answer_len, rem;
-       int n_iov = 0;
-
-       if (!dns_answer_cnt)
-               return;
-
-       h.answers = cpu_to_be16(dns_answer_cnt);
-       h.flags = cpu_to_be16(0x8400);
-
-       iov = alloca(sizeof(struct iovec) * ((dns_answer_cnt * 2) + 1));
-
-       iov[n_iov].iov_base = &h;
-       iov[n_iov].iov_len = sizeof(struct dns_header);
-       n_iov++;
-
-       answer_len = dn_comp(answer, buffer, sizeof(buffer), NULL, NULL);
-       if (answer_len < 1)
-               return;
-
-       blob_for_each_attr(attr, ans_buf.head, rem) {
-               struct dns_answer *a = blob_data(attr);
+       if (val)
+               q->class &= ~cpu_to_be16(CLASS_UNICAST);
+       else
+               q->class |= cpu_to_be16(CLASS_UNICAST);
+}
 
-               iov[n_iov].iov_base = buffer;
-               iov[n_iov].iov_len = answer_len;
-               n_iov++;
+void dns_packet_send(struct interface *iface, struct sockaddr *to, bool query, int multicast)
+{
+       struct iovec iov = {
+               .iov_base = &pkt,
+               .iov_len = sizeof(pkt.h) + pkt_len,
+       };
+       size_t i;
 
-               iov[n_iov].iov_base = blob_data(attr);
-               iov[n_iov].iov_len = blob_len(attr);
-               n_iov++;
+       if (query) {
+               if (multicast < 0)
+                       multicast = interface_multicast(iface);
 
-               DBG(1, "A <- %s %s\n", dns_type_string(be16_to_cpu(a->type)), answer);
+               for (i = 0; i < pkt_n_q; i++)
+                       dns_question_set_multicast(pkt_q[i], multicast);
        }
 
-       if (interface_send_packet(iface, to, iov, n_iov) < 0)
+       if (interface_send_packet(iface, to, &iov, 1) < 0)
                perror("failed to send answer");
 }
 
+void
+dns_send_question(struct interface *iface, struct sockaddr *to,
+                 const char *question, int type, int multicast)
+{
+       dns_packet_init();
+       dns_packet_question(question, type);
+       dns_packet_send(iface, to, true, multicast);
+}
+
 void
 dns_reply_a(struct interface *iface, struct sockaddr *to, int ttl, const char *hostname)
 {
@@ -189,24 +201,27 @@ dns_reply_a(struct interface *iface, struct sockaddr *to, int ttl, const char *h
        struct sockaddr_in *sa;
        struct sockaddr_in6 *sa6;
 
+       if (!hostname)
+               hostname = mdns_hostname_local;
+
        getifaddrs(&ifap);
 
-       dns_init_answer();
+       dns_packet_init();
        for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
                if (strcmp(ifa->ifa_name, iface->name))
                        continue;
                if (ifa->ifa_addr->sa_family == AF_INET) {
                        sa = (struct sockaddr_in *) ifa->ifa_addr;
-                       dns_add_answer(TYPE_A, (uint8_t *) &sa->sin_addr, 4, ttl);
+                       dns_packet_answer(hostname, TYPE_A, (uint8_t *) &sa->sin_addr, 4, ttl);
                }
                if (ifa->ifa_addr->sa_family == AF_INET6) {
                        sa6 = (struct sockaddr_in6 *) ifa->ifa_addr;
-                       dns_add_answer(TYPE_AAAA, (uint8_t *) &sa6->sin6_addr, 16, ttl);
+                       dns_packet_answer(hostname, TYPE_AAAA, (uint8_t *) &sa6->sin6_addr, 16, ttl);
                }
        }
-       dns_send_answer(iface, to, hostname ? hostname : mdns_hostname_local);
-
        freeifaddrs(ifap);
+
+       dns_packet_send(iface, to, 0, 0);
 }
 
 void
diff --git a/dns.h b/dns.h
index 39a1a51c3c752624a7b625a52f94236fd401099d..d6991e06423e43f0e3ec51c73a50fa4294e8cf6e 100644 (file)
--- a/dns.h
+++ b/dns.h
@@ -73,11 +73,13 @@ struct interface;
 extern int cfg_proto;
 extern int cfg_no_subnet;
 
+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_send_question(struct interface *iface, struct sockaddr *to,
                       const char *question, int type, int multicast);
-void dns_init_answer(void);
-void dns_add_answer(int type, const uint8_t *rdata, uint16_t rdlength, int ttl);
-void dns_send_answer(struct interface *iface, struct sockaddr *to, const char *answer);
 void dns_reply_a(struct interface *iface, struct sockaddr *to, int ttl, const char *hostname);
 void dns_reply_a_additional(struct interface *iface, struct sockaddr *to, int ttl);
 const char* dns_type_string(uint16_t type);
index 9d3767b3f6b8d630326ea83a17269f8f813a0be5..865309f136f7d5ef63ed90773f7d53d696119312 100644 (file)
--- a/service.c
+++ b/service.c
@@ -78,18 +78,18 @@ service_instance_name(struct service *s)
 }
 
 static void
-service_add_ptr(const char *host, int ttl)
+service_add_ptr(const char *name, const char *host, int ttl)
 {
        int len = dn_comp(host, mdns_buf, sizeof(mdns_buf), NULL, NULL);
 
        if (len < 1)
                return;
 
-       dns_add_answer(TYPE_PTR, mdns_buf, len, ttl);
+       dns_packet_answer(name, TYPE_PTR, mdns_buf, len, ttl);
 }
 
 static void
-service_add_srv(struct service *s, int ttl)
+service_add_srv(const char *name, struct service *s, int ttl)
 {
        struct dns_srv_data *sd = (struct dns_srv_data *) mdns_buf;
        int len = sizeof(*sd);
@@ -99,7 +99,7 @@ service_add_srv(struct service *s, int ttl)
                return;
 
        sd->port = cpu_to_be16(s->port);
-       dns_add_answer(TYPE_SRV, mdns_buf, len, ttl);
+       dns_packet_answer(name, TYPE_SRV, mdns_buf, len, ttl);
 }
 
 #define TOUT_LOOKUP    60
@@ -132,15 +132,12 @@ service_reply_single(struct interface *iface, struct sockaddr *to, struct servic
 
        s->t = t;
 
-       dns_init_answer();
-       service_add_ptr(service_instance_name(s), ttl);
-       dns_send_answer(iface, to, service);
-
-       dns_init_answer();
-       service_add_srv(s, ttl);
+       dns_packet_init();
+       service_add_ptr(service, service_instance_name(s), ttl);
+       service_add_srv(host, s, ttl);
        if (s->txt && s->txt_len)
-               dns_add_answer(TYPE_TXT, (uint8_t *) s->txt, s->txt_len, ttl);
-       dns_send_answer(iface, to, host);
+               dns_packet_answer(host, TYPE_TXT, (uint8_t *) s->txt, s->txt_len, ttl);
+       dns_packet_send(iface, to, 0, 0);
 }
 
 void
@@ -163,16 +160,16 @@ service_announce_services(struct interface *iface, struct sockaddr *to, int ttl)
        struct service *s;
        int count = 0;
 
-       dns_init_answer();
+       dns_packet_init();
        vlist_for_each_element(&announced_services, s, node) {
                s->t = 0;
                if (ttl) {
-                       service_add_ptr(s->service, ttl);
+                       service_add_ptr(C_DNS_SD, s->service, ttl);
                        count++;
                }
        }
        if (count)
-               dns_send_answer(iface, to, C_DNS_SD);
+               dns_packet_send(iface, to, 0, 0);
 }
 
 void