From 6cf0b1e122d72b028e4b2cef8c151cacdd321733 Mon Sep 17 00:00:00 2001 From: Rahul Thakur Date: Tue, 13 Feb 2024 12:11:46 +0530 Subject: [PATCH] mdnsd: add handling for legacy queries Following are taken care of: - in the reply to a legacy query, send unicast udp to same port from which the packet is received. - copy the query from question message in reply. - use the same transaction id as in question. --- dns.c | 81 ++++++++++++++++++++++++++++++++++++++++------------- dns.h | 4 +-- interface.c | 5 ++-- service.c | 22 ++++++++------- service.h | 5 ++-- 5 files changed, 82 insertions(+), 35 deletions(-) diff --git a/dns.c b/dns.c index 2b5a390..67082f1 100644 --- a/dns.c +++ b/dns.c @@ -43,6 +43,13 @@ 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); +static char * +dns_consume_name(const uint8_t *base, int blen, uint8_t **data, int *len); +static struct dns_question* +dns_consume_question(uint8_t **data, int *len); + const char* dns_type_string(uint16_t type) { @@ -139,7 +146,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 +154,43 @@ 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, the 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; @@ -178,12 +209,13 @@ dns_send_answer(struct interface *iface, struct sockaddr *to, const char *answer DBG(1, "A <- %s %s\n", dns_type_string(be16_to_cpu(a->type)), answer); } + if (interface_send_packet(iface, to, iov, n_iov) < 0) perror("failed to send 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 +236,7 @@ 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 +247,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,16 +387,26 @@ 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) { struct sockaddr *to = NULL; char *host; /* TODO: Multicast if more than one quarter of TTL has passed */ - if (q->class & CLASS_UNICAST) { + if ((q->class & CLASS_UNICAST) || port != MCAST_PORT) { + /* As per rfc 6762 section 6.7, if source port is not multicast port, + * then the response should be unicast udp to the source 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); @@ -372,27 +414,27 @@ 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); + service_reply(iface, to, NULL, NULL, announce_ttl, orig_buffer, orig_len); } break; case TYPE_PTR: if (!strcmp(name, C_DNS_SD)) { - 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_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); + service_reply(iface, to, NULL, name, announce_ttl, 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); + service_reply(iface, to, name, dot + 1, announce_ttl, orig_buffer, orig_len); *dot = '.'; } } @@ -405,7 +447,7 @@ 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); break; }; } @@ -416,6 +458,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) { @@ -423,10 +470,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; @@ -443,7 +486,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 ad25b39..e591fb4 100644 --- a/interface.c +++ b/interface.c @@ -83,6 +83,7 @@ interface_send_packet4(struct interface *iface, struct sockaddr_in *to, struct i if (to) fprintf(stderr, "Ignoring IPv4 address for multicast interface\n"); } else { + a.sin_port = to->sin_port; a.sin_addr.s_addr = to->sin_addr.s_addr; } @@ -652,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 b693b31..71c0666 100644 --- a/service.c +++ b/service.c @@ -138,7 +138,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, + struct dns_question *q, uint8_t *orig_buffer, int orig_len) { const char *host = service_instance_name(s); char *service = strstr(host, "._"); @@ -154,17 +155,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) +service_reply(struct interface *iface, struct sockaddr *to, const char *instance, const char *service_domain, int ttl, + uint8_t *orig_buffer, int orig_len) { struct service *s; @@ -173,12 +175,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, 0); + service_reply_single(iface, to, s, ttl, 0, 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; @@ -187,9 +189,9 @@ service_announce_services(struct interface *iface, struct sockaddr *to, int ttl) if (ttl) { dns_init_answer(); service_add_ptr(s->service, ttl); - dns_send_answer(iface, to, C_DNS_SD); + dns_send_answer(iface, to, C_DNS_SD, orig_buffer, orig_len); } - service_reply_single(iface, to, s, ttl, 0); + service_reply_single(iface, to, s, ttl, 0, NULL, 0); } } @@ -205,7 +207,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, NULL, 0); } return; } @@ -213,7 +215,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); } diff --git a/service.h b/service.h index 9dea1f6..f623ac2 100644 --- a/service.h +++ b/service.h @@ -23,7 +23,8 @@ extern struct vlist_tree hostnames; 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); -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, + 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); #endif