[PATCH mdnsd] dns: support additional section and SRV/TXT queries

Erik Karlsson erik.r.karlsson at gmail.com
Fri Apr 17 04:59:12 PDT 2026


From: Erik Karlsson <erik.karlsson at iopsys.eu>

When responding to a PTR query, SRV and TXT records should be placed
in the additional section and not the answer section. When responding
to an A or AAAA query, addresses of mismatching type should be placed
in the additional section and not the answer section.

Respond to SRV and TXT queries. Only respond to PTR queries without
instance specified (instance is not included in name of PTR records).
PTR/SRV/TXT responses are always forced to yield predictable results.
Rate limiting is only kept for the legacy ANY query handling.

Fix a bug where the timeout was updated even if the response is forced
and potential NULL pointer issues.

Signed-off-by: Erik Karlsson <erik.karlsson at iopsys.eu>
---
 dns.c     | 88 ++++++++++++++++++++++++++++++++++++++++++-------------
 dns.h     |  1 +
 service.c | 37 +++++++++++++++--------
 service.h |  2 +-
 4 files changed, 93 insertions(+), 35 deletions(-)

diff --git a/dns.c b/dns.c
index 6e7ec15..c66ea74 100644
--- a/dns.c
+++ b/dns.c
@@ -150,13 +150,16 @@ bool dns_packet_question(const char *name, int type)
 	return true;
 }
 
-void dns_packet_answer(const char *name, int type, const uint8_t *rdata, uint16_t rdlength, int ttl)
+static bool dns_packet_record(const char *name, int type, const uint8_t *rdata, uint16_t rdlength, int ttl)
 {
 	struct dns_answer *a;
 
 	pkt.h.flags |= cpu_to_be16(0x8400);
 
 	a = dns_packet_record_add(sizeof(*a) + rdlength, name);
+	if (!a)
+		return false;
+
 	memset(a, 0, sizeof(*a));
 	a->type = cpu_to_be16(type);
 	a->class = cpu_to_be16(1);
@@ -165,7 +168,19 @@ void dns_packet_answer(const char *name, int type, const uint8_t *rdata, uint16_
 	memcpy(a + 1, rdata, rdlength);
 	DBG(1, "A <- %s %s\n", dns_type_string(be16_to_cpu(a->type)), name);
 
-	pkt.h.answers += cpu_to_be16(1);
+	return true;
+}
+
+void dns_packet_answer(const char *name, int type, const uint8_t *rdata, uint16_t rdlength, int ttl)
+{
+	if (dns_packet_record(name, type, rdata, rdlength, ttl))
+		pkt.h.answers += cpu_to_be16(1);
+}
+
+void dns_packet_additional(const char *name, int type, const uint8_t *rdata, uint16_t rdlength, int ttl)
+{
+	if (dns_packet_record(name, type, rdata, rdlength, ttl))
+		pkt.h.additional += cpu_to_be16(1);
 }
 
 static void dns_question_set_multicast(struct dns_question *q, bool val)
@@ -184,6 +199,9 @@ void dns_packet_send(struct interface *iface, struct sockaddr *to, bool query, i
 	};
 	size_t i;
 
+	if ((query && pkt.h.questions == 0) || (!query && pkt.h.answers == 0))
+		return;
+
 	if (query) {
 		if (multicast < 0)
 			multicast = iface->need_multicast;
@@ -265,8 +283,8 @@ void dns_query(const char *name, uint16_t type)
 		uloop_timeout_set(&timer, 100);
 }
 
-void
-dns_reply_a(struct interface *iface, struct sockaddr *to, int ttl, const char *hostname)
+static void
+dns_reply_a_qtype(struct interface *iface, struct sockaddr *to, int ttl, const char *hostname, uint16_t qtype)
 {
 	struct ifaddrs *ifap, *ifa;
 	struct sockaddr_in *sa;
@@ -281,20 +299,45 @@ dns_reply_a(struct interface *iface, struct sockaddr *to, int ttl, const char *h
 	for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
 		if (strcmp(ifa->ifa_name, iface->name))
 			continue;
-		if (ifa->ifa_addr->sa_family == AF_INET) {
+		if (ifa->ifa_addr->sa_family == AF_INET && (qtype == TYPE_ANY || qtype == TYPE_A)) {
 			sa = (struct sockaddr_in *) ifa->ifa_addr;
 			dns_packet_answer(hostname, TYPE_A, (uint8_t *) &sa->sin_addr, 4, ttl);
 		}
-		if (ifa->ifa_addr->sa_family == AF_INET6) {
+		if (ifa->ifa_addr->sa_family == AF_INET6 && (qtype == TYPE_ANY || qtype == TYPE_AAAA)) {
 			sa6 = (struct sockaddr_in6 *) ifa->ifa_addr;
 			dns_packet_answer(hostname, TYPE_AAAA, (uint8_t *) &sa6->sin6_addr, 16, ttl);
 		}
 	}
+	if (qtype == TYPE_A) {
+		for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+			if (strcmp(ifa->ifa_name, iface->name))
+				continue;
+			if (ifa->ifa_addr->sa_family == AF_INET6) {
+				sa6 = (struct sockaddr_in6 *) ifa->ifa_addr;
+				dns_packet_additional(hostname, TYPE_AAAA, (uint8_t *) &sa6->sin6_addr, 16, ttl);
+			}
+		}
+	} else if (qtype == TYPE_AAAA) {
+		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_packet_additional(hostname, TYPE_A, (uint8_t *) &sa->sin_addr, 4, ttl);
+			}
+		}
+	}
 	freeifaddrs(ifap);
 
 	dns_packet_send(iface, to, 0, 0);
 }
 
+void
+dns_reply_a(struct interface *iface, struct sockaddr *to, int ttl, const char *hostname)
+{
+	dns_reply_a_qtype(iface, to, ttl, hostname, TYPE_ANY);
+}
+
 void
 dns_reply_a_additional(struct interface *iface, struct sockaddr *to, int ttl)
 {
@@ -602,7 +645,7 @@ parse_question(struct interface *iface, struct sockaddr *from, char *name, struc
 		if (!strcasecmp(name, mdns_hostname_local)) {
 			dns_reply_a(iface, to, announce_ttl, NULL);
 			dns_reply_a_additional(iface, to, announce_ttl);
-			service_reply(iface, to, NULL, NULL, announce_ttl, is_unicast);
+			service_reply(iface, to, NULL, NULL, announce_ttl, is_unicast, q->type);
 		}
 		break;
 
@@ -634,18 +677,21 @@ parse_question(struct interface *iface, struct sockaddr *from, char *name, struc
 
 		if (!strcasecmp(name, C_DNS_SD)) {
 			service_announce_services(iface, to, announce_ttl);
-		} else {
-			if (name[0] == '_') {
-				service_reply(iface, to, NULL, name, announce_ttl, is_unicast);
-			} else {
-				/* First dot separates instance name from the rest */
-				char *dot = strchr(name, '.');
-
-				if (dot) {
-					*dot = '\0';
-					service_reply(iface, to, name, dot + 1, announce_ttl, is_unicast);
-					*dot = '.';
-				}
+		} else if (name[0] == '_') {
+			service_reply(iface, to, NULL, name, announce_ttl, 1, q->type);
+		}
+		break;
+
+	case TYPE_SRV:
+	case TYPE_TXT:
+		if (name[0] != '_') {
+			/* First dot separates instance name from the rest */
+			char *dot = strchr(name, '.');
+
+			if (dot) {
+				*dot = '\0';
+				service_reply(iface, to, name, dot + 1, announce_ttl, 1, q->type);
+				*dot = '.';
 			}
 		}
 		break;
@@ -656,13 +702,13 @@ parse_question(struct interface *iface, struct sockaddr *from, char *name, struc
 		if (host)
 			*host = '\0';
 		if (!strcasecmp(umdns_host_label, name)) {
-			dns_reply_a(iface, to, announce_ttl, NULL);
+			dns_reply_a_qtype(iface, to, announce_ttl, NULL, q->type);
 		} else {
 			if (host)
 				*host = '.';
 			vlist_for_each_element(&hostnames, h, node)
 				if (!strcasecmp(h->hostname, name))
-					dns_reply_a(iface, to, announce_ttl, h->hostname);
+					dns_reply_a_qtype(iface, to, announce_ttl, h->hostname, q->type);
 		}
 		break;
 	};
diff --git a/dns.h b/dns.h
index ed0907d..3630222 100644
--- a/dns.h
+++ b/dns.h
@@ -83,6 +83,7 @@ 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_additional(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_query(const char *name, uint16_t type);
diff --git a/service.c b/service.c
index 441ec2e..01c6f90 100644
--- a/service.c
+++ b/service.c
@@ -89,7 +89,7 @@ service_add_ptr(const char *name, const char *host, int ttl)
 }
 
 static void
-service_add_srv(const char *name, struct service *s, int ttl)
+service_add_srv(const char *name, struct service *s, int ttl, int answer)
 {
 	struct dns_srv_data *sd = (struct dns_srv_data *) mdns_buf;
 	int len = sizeof(*sd);
@@ -99,7 +99,10 @@ service_add_srv(const char *name, struct service *s, int ttl)
 		return;
 
 	sd->port = cpu_to_be16(s->port);
-	dns_packet_answer(name, TYPE_SRV, mdns_buf, len, ttl);
+	if (answer)
+		dns_packet_answer(name, TYPE_SRV, mdns_buf, len, ttl);
+	else
+		dns_packet_additional(name, TYPE_SRV, mdns_buf, len, ttl);
 }
 
 #define TOUT_LOOKUP	60
@@ -118,29 +121,37 @@ service_timeout(struct service *s)
 }
 
 static void
-service_reply_single(struct interface *iface, struct sockaddr *to, struct service *s, int ttl, int force)
+service_reply_single(struct interface *iface, struct sockaddr *to, struct service *s, int ttl, int force, uint16_t qtype)
 {
 	const char *host = service_instance_name(s);
 	char *service = strstr(host, "._");
-	time_t t = service_timeout(s);
+	time_t t = force ? 0 : service_timeout(s);
 
-	if (!force && (!s->active || !service || !t))
+	if ((!force && (!s->active || !t)) || !service)
 		return;
 
 	service++;
 
-	s->t = t;
+	if (t)
+		s->t = t;
 
 	dns_packet_init();
-	service_add_ptr(service, service_instance_name(s), ttl);
-	service_add_srv(host, s, ttl);
-	if (s->txt && s->txt_len)
+	if (qtype == TYPE_ANY || qtype == TYPE_PTR)
+		service_add_ptr(service, host, ttl);
+	if (qtype == TYPE_ANY || qtype == TYPE_SRV)
+		service_add_srv(host, s, ttl, 1);
+	if (s->txt && s->txt_len && (qtype == TYPE_ANY || qtype == TYPE_TXT))
 		dns_packet_answer(host, TYPE_TXT, (uint8_t *) s->txt, s->txt_len, ttl);
+	if (qtype == TYPE_PTR) {
+		service_add_srv(host, s, ttl, 0);
+		if (s->txt && s->txt_len)
+			dns_packet_additional(host, TYPE_TXT, (uint8_t *) s->txt, s->txt_len, ttl);
+	}
 	dns_packet_send(iface, to, 0, 0);
 }
 
 void
-service_reply(struct interface *iface, struct sockaddr *to, const char *instance, const char *service_domain, int ttl, int force)
+service_reply(struct interface *iface, struct sockaddr *to, const char *instance, const char *service_domain, int ttl, int force, uint16_t qtype)
 {
 	struct service *s;
 
@@ -149,7 +160,7 @@ service_reply(struct interface *iface, struct sockaddr *to, const char *instance
 			continue;
 		if (service_domain && strcmp(s->service, service_domain))
 			continue;
-		service_reply_single(iface, to, s, ttl, force);
+		service_reply_single(iface, to, s, ttl, force, qtype);
 	}
 }
 
@@ -183,7 +194,7 @@ service_update(struct vlist_tree *tree, struct vlist_node *node_new,
 		if (service_init_announce)
 			vlist_for_each_element(&interfaces, iface, node) {
 				s->t = 0;
-				service_reply_single(iface, NULL, s, announce_ttl, 1);
+				service_reply_single(iface, NULL, s, announce_ttl, 1, TYPE_ANY);
 			}
 		return;
 	}
@@ -191,7 +202,7 @@ service_update(struct vlist_tree *tree, struct vlist_node *node_new,
 	s = container_of(node_old, struct service, node);
 	if (!node_new && service_init_announce)
 		vlist_for_each_element(&interfaces, iface, node)
-			service_reply_single(iface, NULL, s, 0, 1);
+			service_reply_single(iface, NULL, s, 0, 1, TYPE_ANY);
 	free(s);
 }
 
diff --git a/service.h b/service.h
index 7149672..2fdc954 100644
--- a/service.h
+++ b/service.h
@@ -42,7 +42,7 @@ extern struct vlist_tree announced_services;
 
 extern void service_init(int announce);
 extern void service_cleanup(void);
-extern void service_reply(struct interface *iface, struct sockaddr *to, const char *instance, const char *service_domain, int ttl, int force);
+extern void service_reply(struct interface *iface, struct sockaddr *to, const char *instance, const char *service_domain, int ttl, int force, uint16_t qtype);
 extern void service_announce_services(struct interface *iface, struct sockaddr *to, int ttl);
 extern void service_update(struct vlist_tree *tree, struct vlist_node *node_new, struct vlist_node *node_old);
 
-- 
2.53.0




More information about the openwrt-devel mailing list