From a10d76d4db329caefd37479188cac193c3389505 Mon Sep 17 00:00:00 2001 From: Mohd Husaam Mehdi Date: Thu, 13 Feb 2025 14:59:18 +0530 Subject: [PATCH 1/2] dns: save question section when parsing DNS question For example, in case the query is of the type "One-Shot Multicast DNS Queries", the questions also need to be sent along with the answer, like a conventional DNS response (RFC 6762 Section 6.7). Therefore, we save the question section while parsing DNS question. Signed-off-by: Mohd Husaam Mehdi --- announce.c | 4 ++-- dns.c | 63 +++++++++++++++++++++++++++++++++++++++++------------ dns.h | 4 ++-- interface.c | 4 ++-- service.c | 24 ++++++++++---------- service.h | 4 ++-- 6 files changed, 70 insertions(+), 33 deletions(-) diff --git a/announce.c b/announce.c index a562277..6e517cb 100644 --- a/announce.c +++ b/announce.c @@ -65,9 +65,9 @@ announce_timer(struct uloop_timeout *timeout) /* Fall through */ case STATE_ANNOUNCE: - dns_reply_a(iface, NULL, announce_ttl, NULL); + dns_reply_a(iface, NULL, announce_ttl, NULL, NULL, 0); dns_reply_a_additional(iface, NULL, announce_ttl); - service_announce_services(iface, NULL, announce_ttl); + service_announce_services(iface, NULL, announce_ttl, NULL, 0); uloop_timeout_set(timeout, announce_ttl * 800); break; } diff --git a/dns.c b/dns.c index 2078d88..62276e6 100644 --- a/dns.c +++ b/dns.c @@ -43,6 +43,9 @@ static char name_buffer[MAX_NAME_LEN + 1]; static char dns_buffer[MAX_NAME_LEN]; static struct blob_buf ans_buf; +static struct dns_header* +dns_consume_header(uint8_t **data, int *len); + const char* dns_type_string(uint16_t type) { @@ -139,7 +142,7 @@ 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) +dns_send_answer(struct interface *iface, struct sockaddr *to, const char *answer, uint8_t *orig_buffer, int orig_len) { uint8_t buffer[256]; struct blob_attr *attr; @@ -147,19 +150,44 @@ dns_send_answer(struct interface *iface, struct sockaddr *to, const char *answer struct iovec *iov; int answer_len, rem; int n_iov = 0; + uint16_t q_cnt = 0; + struct dns_header *orig_h; + uint8_t *b = orig_buffer; + int rlen = orig_len; if (!dns_answer_cnt) return; + if (orig_buffer != NULL) { + orig_h = dns_consume_header(&b, &rlen); + if (!orig_h) { + fprintf(stderr, "dropping: bad header\n"); + return; + } + + q_cnt = orig_h->questions; + h.questions = cpu_to_be16(orig_h->questions); + h.id = cpu_to_be16(orig_h->id); + } + 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 = alloca(sizeof(struct iovec) * ((dns_answer_cnt * 2) + 1 + q_cnt)); iov[n_iov].iov_base = &h; iov[n_iov].iov_len = sizeof(struct dns_header); n_iov++; + /* if the answer is in reply to a question, then copy the question in answer + * so that the dns format is correct, as per section 6.7 of rfc 6762 */ + if (orig_buffer != NULL) { + /* after consuming the header above, b now points to query section */ + iov[n_iov].iov_base = b; + iov[n_iov].iov_len = rlen; + n_iov++; + } + answer_len = dn_comp(answer, buffer, sizeof(buffer), NULL, NULL); if (answer_len < 1) return; @@ -183,7 +211,7 @@ 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) +dns_reply_a(struct interface *iface, struct sockaddr *to, int ttl, const char *hostname, uint8_t *orig_buffer, int orig_len) { struct ifaddrs *ifap, *ifa; struct sockaddr_in *sa; @@ -204,7 +232,8 @@ dns_reply_a(struct interface *iface, struct sockaddr *to, int ttl, const char *h dns_add_answer(TYPE_AAAA, (uint8_t *) &sa6->sin6_addr, 16, ttl); } } - dns_send_answer(iface, to, hostname ? hostname : mdns_hostname_local); + + dns_send_answer(iface, to, hostname ? hostname : mdns_hostname_local, orig_buffer, orig_len); freeifaddrs(ifap); } @@ -215,7 +244,7 @@ dns_reply_a_additional(struct interface *iface, struct sockaddr *to, int ttl) struct hostname *h; vlist_for_each_element(&hostnames, h, node) - dns_reply_a(iface, to, ttl, h->hostname); + dns_reply_a(iface, to, ttl, h->hostname, NULL, 0); } static int @@ -355,7 +384,8 @@ static int parse_answer(struct interface *iface, struct sockaddr *from, } static void -parse_question(struct interface *iface, struct sockaddr *from, char *name, struct dns_question *q) +parse_question(struct interface *iface, struct sockaddr *from, char *name, struct dns_question *q, + uint8_t *orig_buffer, int orig_len, uint16_t port) { int is_unicast = (q->class & CLASS_UNICAST) != 0; struct sockaddr *to = NULL; @@ -374,25 +404,25 @@ parse_question(struct interface *iface, struct sockaddr *from, char *name, struc switch (q->type) { case TYPE_ANY: if (!strcmp(name, mdns_hostname_local)) { - dns_reply_a(iface, to, announce_ttl, NULL); + dns_reply_a(iface, to, announce_ttl, NULL, orig_buffer, orig_len); 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, orig_buffer, orig_len); } break; case TYPE_PTR: if (!strcmp(name, C_DNS_SD)) { - service_announce_services(iface, to, announce_ttl); + service_announce_services(iface, to, announce_ttl, orig_buffer, orig_len); } else { if (name[0] == '_') { - service_reply(iface, to, NULL, name, announce_ttl, is_unicast); + service_reply(iface, to, NULL, name, announce_ttl, is_unicast, orig_buffer, orig_len); } 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); + service_reply(iface, to, name, dot + 1, announce_ttl, is_unicast, orig_buffer, orig_len); *dot = '.'; } } @@ -405,13 +435,13 @@ parse_question(struct interface *iface, struct sockaddr *from, char *name, struc if (host) *host = '\0'; if (!strcmp(umdns_host_label, name)) { - dns_reply_a(iface, to, announce_ttl, NULL); + dns_reply_a(iface, to, announce_ttl, NULL, orig_buffer, orig_len); } else { if (host) *host = '.'; vlist_for_each_element(&hostnames, h, node) if (!strcmp(h->hostname, name)) - dns_reply_a(iface, to, announce_ttl, h->hostname); + dns_reply_a(iface, to, announce_ttl, h->hostname, NULL, 0); } break; }; @@ -423,6 +453,11 @@ dns_handle_packet(struct interface *iface, struct sockaddr *from, uint16_t port, struct dns_header *h; uint8_t *b = buffer; int rlen = len; + uint8_t orig_buffer[len]; + + /* make a copy of the original buffer since it might be needed to construct the answer + * in case the query is received from a one-shot multicast dns querier */ + memcpy(orig_buffer, buffer, len); h = dns_consume_header(&b, &rlen); if (!h) { @@ -450,7 +485,7 @@ dns_handle_packet(struct interface *iface, struct sockaddr *from, uint16_t port, } if (!(h->flags & FLAG_RESPONSE)) - parse_question(iface, from, name, q); + parse_question(iface, from, name, q, orig_buffer, len, port); } if (!(h->flags & FLAG_RESPONSE)) diff --git a/dns.h b/dns.h index 39a1a51..26c5d77 100644 --- a/dns.h +++ b/dns.h @@ -77,8 +77,8 @@ 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_send_answer(struct interface *iface, struct sockaddr *to, const char *answer, uint8_t *orig_buffer, int orig_len); +void dns_reply_a(struct interface *iface, struct sockaddr *to, int ttl, const char *hostname, uint8_t *orig_buffer, int orig_len); void dns_reply_a_additional(struct interface *iface, struct sockaddr *to, int ttl); const char* dns_type_string(uint16_t type); void dns_handle_packet(struct interface *iface, struct sockaddr *s, uint16_t port, uint8_t *buf, int len); diff --git a/interface.c b/interface.c index 944666f..0b949be 100644 --- a/interface.c +++ b/interface.c @@ -653,9 +653,9 @@ void interface_shutdown(void) vlist_for_each_element(&interfaces, iface, node) if (interface_multicast(iface)) { - dns_reply_a(iface, NULL, 0, NULL); + dns_reply_a(iface, NULL, 0, NULL, NULL, 0); dns_reply_a_additional(iface, NULL, 0); - service_announce_services(iface, NULL, 0); + service_announce_services(iface, NULL, 0, NULL, 0); } for (size_t i = 0; i < ARRAY_SIZE(ufd); i++) { diff --git a/service.c b/service.c index 9d3767b..cbf2fbb 100644 --- a/service.c +++ b/service.c @@ -118,7 +118,8 @@ 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, + uint8_t *orig_buffer, int orig_len) { const char *host = service_instance_name(s); char *service = strstr(host, "._"); @@ -134,17 +135,18 @@ service_reply_single(struct interface *iface, struct sockaddr *to, struct servic dns_init_answer(); service_add_ptr(service_instance_name(s), ttl); - dns_send_answer(iface, to, service); + dns_send_answer(iface, to, service, orig_buffer, orig_len); dns_init_answer(); service_add_srv(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_send_answer(iface, to, host, orig_buffer, orig_len); } 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, + uint8_t *orig_buffer, int orig_len) { struct service *s; @@ -153,12 +155,12 @@ 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, orig_buffer, orig_len); } } void -service_announce_services(struct interface *iface, struct sockaddr *to, int ttl) +service_announce_services(struct interface *iface, struct sockaddr *to, int ttl, uint8_t *orig_buffer, int orig_len) { struct service *s; int count = 0; @@ -172,7 +174,7 @@ service_announce_services(struct interface *iface, struct sockaddr *to, int ttl) } } if (count) - dns_send_answer(iface, to, C_DNS_SD); + dns_send_answer(iface, to, C_DNS_SD, orig_buffer, orig_len); } void @@ -187,7 +189,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, NULL, 0); } return; } @@ -195,7 +197,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, NULL, 0); free(s); } @@ -209,14 +211,14 @@ hostname_update(struct vlist_tree *tree, struct vlist_node *node_new, if (!node_old) { h = container_of(node_new, struct hostname, node); vlist_for_each_element(&interfaces, iface, node) - dns_reply_a(iface, NULL, announce_ttl, h->hostname); + dns_reply_a(iface, NULL, announce_ttl, h->hostname, NULL, 0); return; } h = container_of(node_old, struct hostname, node); if (!node_new) vlist_for_each_element(&interfaces, iface, node) - dns_reply_a(iface, NULL, 0, h->hostname); + dns_reply_a(iface, NULL, 0, h->hostname, NULL, 0); free(h); } diff --git a/service.h b/service.h index 6cae5bb..8f641bc 100644 --- a/service.h +++ b/service.h @@ -41,8 +41,8 @@ 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_announce_services(struct interface *iface, struct sockaddr *to, int ttl); +extern void service_reply(struct interface *iface, struct sockaddr *to, const char *instance, const char *service_domain, int ttl, int force, uint8_t *orig_buffer, int orig_len); +extern void service_announce_services(struct interface *iface, struct sockaddr *to, int ttl, uint8_t *orig_buffer, int orig_len); extern void service_update(struct vlist_tree *tree, struct vlist_node *node_new, struct vlist_node *node_old); #endif From f0576431739ad157f73df16425ec18d455d5b22e Mon Sep 17 00:00:00 2001 From: Mohd Husaam Mehdi Date: Thu, 13 Feb 2025 16:39:04 +0530 Subject: [PATCH 2/2] dns: add support for Legacy Unicast Responses As per RFC 6762 Section 6.7 (Legacy Unicast Responses) "If the source UDP port in a received Multicast DNS query is not port 5353, this indicates that the querier originating the query is a simple resolver such as described in Section 5.1, "One-Shot Multicast DNS Queries", which does not fully implement all of Multicast DNS. In this case, the Multicast DNS responder MUST send a UDP response directly back to the querier, via unicast, to the query packet's source IP address and port. This unicast response MUST be a conventional unicast response as would be generated by a conventional Unicast DNS server; for example, it MUST repeat the query ID and the question given in the query message." Therefore, umdns should not ignore DNS questions coming from non- multicast sources, and also provide question section which is not usually provided in mDNS responses. Signed-off-by: Mohd Husaam Mehdi --- dns.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/dns.c b/dns.c index 62276e6..9c7e459 100644 --- a/dns.c +++ b/dns.c @@ -393,10 +393,17 @@ parse_question(struct interface *iface, struct sockaddr *from, char *name, struc char *host; /* TODO: Multicast if more than one quarter of TTL has passed */ - if (is_unicast) { + if (is_unicast || port != MCAST_PORT) { to = from; if (interface_multicast(iface)) iface = interface_get(iface->name, iface->type | SOCKTYPE_BIT_UNICAST); + } else { + /* if the query is from multicast port, no need for original buffer + * while responding as per rfc 6762, section 6: + * Multicast DNS responses MUST NOT contain any questions in the + * Question Section. */ + orig_buffer = NULL; + orig_len = 0; } DBG(1, "Q -> %s %s\n", dns_type_string(q->type), name); @@ -465,10 +472,6 @@ dns_handle_packet(struct interface *iface, struct sockaddr *from, uint16_t port, return; } - if (h->questions && !interface_multicast(iface) && port != MCAST_PORT) - /* silently drop unicast questions that dont originate from port 5353 */ - return; - while (h->questions-- > 0) { char *name = dns_consume_name(buffer, len, &b, &rlen); struct dns_question *q;