Skip to content

Commit

Permalink
mdnsd: add handling for legacy queries
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rthakur33 committed Feb 13, 2024
1 parent e91ed40 commit 6cf0b1e
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 35 deletions.
81 changes: 62 additions & 19 deletions dns.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -139,27 +146,51 @@ 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;
struct dns_header h = { 0 };
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;
Expand All @@ -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;
Expand All @@ -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);
}
Expand All @@ -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
Expand Down Expand Up @@ -355,44 +387,54 @@ 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);

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 = '.';
}
}
Expand All @@ -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;
};
}
Expand All @@ -416,17 +458,18 @@ 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) {
fprintf(stderr, "dropping: bad header\n");
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;
Expand All @@ -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))
Expand Down
4 changes: 2 additions & 2 deletions dns.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 3 additions & 2 deletions interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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++) {
Expand Down
22 changes: 12 additions & 10 deletions service.c
Original file line number Diff line number Diff line change
Expand Up @@ -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, "._");
Expand All @@ -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;

Expand All @@ -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;

Expand All @@ -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);
}
}

Expand All @@ -205,15 +207,15 @@ 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;
}

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);
}

Expand Down
5 changes: 3 additions & 2 deletions service.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 6cf0b1e

Please sign in to comment.