From df3c2c13ac6d6b9f980d283f483ae8a02d4dc810 Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Thu, 17 Mar 2022 17:50:55 +0100 Subject: [PATCH 1/7] Also consider authoritative entries when listening Don't skip over authoritative entries when receiving entries, otherwise we'll never send A or AAAA records. --- src/mdns.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mdns.c b/src/mdns.c index 198654c..1af9636 100644 --- a/src/mdns.c +++ b/src/mdns.c @@ -758,7 +758,7 @@ mdns_listen_probe_network(const struct mdns_ctx *ctx, const char *const names[], continue; } - if (ahdr.num_ans_rr + ahdr.num_add_rr == 0) + if (ahdr.num_ans_rr + ahdr.num_auth_rr + ahdr.num_add_rr == 0) { mdns_free(entries); continue; From 3809533103219227cb81f58805efdb189033673a Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Thu, 17 Mar 2022 17:51:47 +0100 Subject: [PATCH 2/7] Fix callback not being called when hostname case doesn't match As per https://www.rfc-editor.org/rfc/rfc952, "[n]o distinction is made between upper and lower case" in hostnames, so check whether the requested name matches the records we receive in a case insensitive way. --- src/mdns.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/mdns.c b/src/mdns.c index 1af9636..041c904 100644 --- a/src/mdns.c +++ b/src/mdns.c @@ -729,6 +729,12 @@ strrcmp(const char *s, const char *sub) return (strncmp(s + m - n, sub, n)); } +static bool +is_host_address_rr_type(enum rr_type type) +{ + return (type == RR_A || type == RR_AAAA); +} + static int mdns_listen_probe_network(const struct mdns_ctx *ctx, const char *const names[], unsigned int nb_names, mdns_listen_callback callback, @@ -769,6 +775,10 @@ mdns_listen_probe_network(const struct mdns_ctx *ctx, const char *const names[], if (!strrcmp(entry->name, names[i])) { callback(p_cookie, r, entries); break; + } else if (is_host_address_rr_type(entry->type) && + !strcasecmp (entry->name, names[i])) { + callback(p_cookie, r, entries); + break; } } } From c530442dec3d069fc4fc6916c153b7f6376b9a21 Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Mon, 21 Mar 2022 00:25:55 +0100 Subject: [PATCH 3/7] Make sure that looked up hostnames have 2 labels Allow foo.local not foo.bar.local, as per draft-cheshire-dnsext-multicastdns.txt This is the two-label limit heuristic, which is also implemented in the default avahi confifuration: https://github.com/lathiat/nss-mdns#etcmdnsallow --- src/mdns.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/mdns.c b/src/mdns.c index 041c904..d27eb72 100644 --- a/src/mdns.c +++ b/src/mdns.c @@ -729,6 +729,23 @@ strrcmp(const char *s, const char *sub) return (strncmp(s + m - n, sub, n)); } +static bool +hostname_has_two_labels(const char *hostname) +{ + unsigned int i, num_dots; + + if (!hostname) + return false; + + for (i = 0, num_dots = 0; hostname[i] != '\0' && num_dots < 2; i++) + { + if (hostname[i] == '.' && hostname[i+1] != '\0') + num_dots++; + } + + return (num_dots == 1); +} + static bool is_host_address_rr_type(enum rr_type type) { @@ -808,6 +825,16 @@ mdns_listen(const struct mdns_ctx *ctx, const char *const names[], qns[i].name = (char *)names[i]; qns[i].type = type; qns[i].rr_class = RR_IN; + + if (is_host_address_rr_type(type)) + { + if (!hostname_has_two_labels(qns[i].name)) + { + free(qns); + return (MDNS_ERROR); + } + } + if (i + 1 < nb_names) qns[i].next = &qns[i+1]; } From 59c60907930aadfdd226a2ee9fc37184c212b4ba Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Mon, 21 Mar 2022 00:57:40 +0100 Subject: [PATCH 4/7] Make sure that looked up hostnames end in .local or .local. As implemented in the default avahi configuration: https://github.com/lathiat/nss-mdns#etcmdnsallow " If the request does not end with .local or .local., it is rejected. Example: example.test is rejected. " --- src/mdns.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mdns.c b/src/mdns.c index d27eb72..85e5527 100644 --- a/src/mdns.c +++ b/src/mdns.c @@ -828,7 +828,8 @@ mdns_listen(const struct mdns_ctx *ctx, const char *const names[], if (is_host_address_rr_type(type)) { - if (!hostname_has_two_labels(qns[i].name)) + if (!hostname_has_two_labels(qns[i].name) || + !(!strrcmp(qns[i].name, ".local") || !strrcmp(qns[i].name, ".local."))) { free(qns); return (MDNS_ERROR); From efc3ae03d12b566902848884661972979a45a638 Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Mon, 21 Mar 2022 00:33:45 +0100 Subject: [PATCH 5/7] Allow looking up hostnames ending in .local and .local. --- src/mdns.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/mdns.c b/src/mdns.c index 85e5527..96e4574 100644 --- a/src/mdns.c +++ b/src/mdns.c @@ -752,6 +752,19 @@ is_host_address_rr_type(enum rr_type type) return (type == RR_A || type == RR_AAAA); } +/* A lookup of 'foo.local.' or 'foo.local' will + * match a result of 'foo.local' */ +static bool +hostname_match(const char *hostname, const char *match) +{ + unsigned int len; + + len = strlen(hostname); + if (hostname[len-1] == '.') + len--; + return (!strncasecmp (hostname, match, len)); +} + static int mdns_listen_probe_network(const struct mdns_ctx *ctx, const char *const names[], unsigned int nb_names, mdns_listen_callback callback, @@ -793,7 +806,7 @@ mdns_listen_probe_network(const struct mdns_ctx *ctx, const char *const names[], callback(p_cookie, r, entries); break; } else if (is_host_address_rr_type(entry->type) && - !strcasecmp (entry->name, names[i])) { + hostname_match(names[i], entry->name)) { callback(p_cookie, r, entries); break; } From b7a403d1b6e4a1e46019f27ace9de1363a273c74 Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Thu, 17 Mar 2022 17:44:03 +0100 Subject: [PATCH 6/7] examples: Add a hostname resolution example Prints the IPv4, followed by the IPv6 address for the requested hostname, and exits. Closes: #41 --- examples/host-lookup.c | 114 +++++++++++++++++++++++++++++++++++++++++ examples/meson.build | 1 + 2 files changed, 115 insertions(+) create mode 100644 examples/host-lookup.c diff --git a/examples/host-lookup.c b/examples/host-lookup.c new file mode 100644 index 0000000..f4ff694 --- /dev/null +++ b/examples/host-lookup.c @@ -0,0 +1,114 @@ +/* + * Copyright © 2014-2015 VideoLabs SAS + * Copyright © 2021 Red Hat Inc + * + * Authors: Jonathan Calmels + * Bastien Nocera + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include + +#include "compat.h" + +#ifdef HAVE_UNISTD_H +#include +#endif + +#define TIMEOUT 1 + +static bool stopflag = false; +static time_t start_time; + +static double get_elapsed(void) +{ + time_t t; + + t = time(NULL); + return difftime(t, start_time); +} + +static bool stop(void *p_cookie) +{ + double elapsed; + if (stopflag) + return stopflag; + elapsed = get_elapsed(); + return elapsed >= (double) TIMEOUT; +} + +static void callback(void *p_cookie, int status, const struct rr_entry *entries) +{ + struct rr_entry *entry; + char *hostname = p_cookie; + char err[128]; + + if (status < 0) { + mdns_strerror(status, err, sizeof(err)); + fprintf(stderr, "error: %s\n", err); + return; + } + entry = (struct rr_entry *) entries; + while (entry) { + if (entry->type == RR_A) + printf("%s resolves to IPv4 address %s\n", hostname, entry->data.A.addr_str); + if (entry->type == RR_AAAA) + printf("%s resolves to IPv6 address %s\n", hostname, entry->data.AAAA.addr_str); + entry = entry->next; + } + stopflag = true; +} + +int main(int i_argc, char *ppsz_argv[]) +{ + int r = 0; + char err[128]; + struct mdns_ctx *ctx; + const char **ppsz_names; + int i_nb_names; + + if (i_argc <= 1) + { + fprintf(stderr, "Usage: %s [HOSTNAME]\n", ppsz_argv[0]); + return (1); + } + + ppsz_names = (const char **) &ppsz_argv[1]; + i_nb_names = i_argc - 1; + + if ((r = mdns_init(&ctx, NULL, MDNS_PORT)) < 0) + goto err; + start_time = time(NULL); + if ((r = mdns_listen(ctx, ppsz_names, i_nb_names, RR_A, TIMEOUT, stop, + callback, ppsz_argv[1])) < 0) + goto err; + stopflag = false; + start_time = time(NULL); + if ((r = mdns_listen(ctx, ppsz_names, i_nb_names, RR_AAAA, TIMEOUT, stop, + callback, ppsz_argv[1])) < 0) + goto err; +err: + if (r < 0) { + mdns_strerror(r, err, sizeof(err)); + fprintf(stderr, "fatal: %s\n", err); + } + mdns_destroy(ctx); + return (0); +} diff --git a/examples/meson.build b/examples/meson.build index d3eb0cc..5b461ea 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -11,4 +11,5 @@ examples_kwargs = { executable('listen', 'main.c', kwargs: examples_kwargs) executable('announce', 'announce.c', kwargs: examples_kwargs) +executable('host-lookup', 'host-lookup.c', kwargs: examples_kwargs) From b1dbacfc286542e8ce8ad76b62245585fa8142e8 Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Sat, 1 Jul 2023 14:49:11 +0200 Subject: [PATCH 7/7] Only call callback when valid entries were received Our network listening code might receive packets that were for a different request than the one we made. Make sure that we only call the callback when valid entries were received, and make it possible for clients to skip over invalid entries. --- examples/host-lookup.c | 11 +++++++---- include/microdns/rr.h | 1 + src/mdns.c | 20 +++++++++++++------- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/examples/host-lookup.c b/examples/host-lookup.c index f4ff694..b1f5361 100644 --- a/examples/host-lookup.c +++ b/examples/host-lookup.c @@ -67,10 +67,13 @@ static void callback(void *p_cookie, int status, const struct rr_entry *entries) } entry = (struct rr_entry *) entries; while (entry) { - if (entry->type == RR_A) - printf("%s resolves to IPv4 address %s\n", hostname, entry->data.A.addr_str); - if (entry->type == RR_AAAA) - printf("%s resolves to IPv6 address %s\n", hostname, entry->data.AAAA.addr_str); + if (entry->valid) + { + if (entry->type == RR_A) + printf("%s resolves to IPv4 address %s\n", hostname, entry->data.A.addr_str); + else if (entry->type == RR_AAAA) + printf("%s resolves to IPv6 address %s\n", hostname, entry->data.AAAA.addr_str); + } entry = entry->next; } stopflag = true; diff --git a/include/microdns/rr.h b/include/microdns/rr.h index 6ff3311..1bb7f51 100644 --- a/include/microdns/rr.h +++ b/include/microdns/rr.h @@ -95,6 +95,7 @@ struct rr_entry { /* Answers only */ uint32_t ttl; + uint16_t valid : 1; // whether the answer is valid for the request uint16_t data_len; union rr_data data; diff --git a/src/mdns.c b/src/mdns.c index 96e4574..5231f37 100644 --- a/src/mdns.c +++ b/src/mdns.c @@ -767,8 +767,8 @@ hostname_match(const char *hostname, const char *match) static int mdns_listen_probe_network(const struct mdns_ctx *ctx, const char *const names[], - unsigned int nb_names, mdns_listen_callback callback, - void *p_cookie) + unsigned int nb_names, enum rr_type type, + mdns_listen_callback callback, void *p_cookie) { struct mdns_hdr ahdr = {0}; struct rr_entry *entries; @@ -785,6 +785,8 @@ mdns_listen_probe_network(const struct mdns_ctx *ctx, const char *const names[], return r; } for (size_t i = 0; i < ctx->nb_conns; ++i) { + bool has_valid_entries = false; + if ((pfd[i].revents & POLLIN) == 0) continue; r = mdns_recv(&ctx->conns[i], &ahdr, &entries); @@ -801,17 +803,21 @@ mdns_listen_probe_network(const struct mdns_ctx *ctx, const char *const names[], } for (struct rr_entry *entry = entries; entry; entry = entry->next) { + if (entry->type != type) + continue; for (unsigned int i = 0; i < nb_names; ++i) { if (!strrcmp(entry->name, names[i])) { - callback(p_cookie, r, entries); - break; + entry->valid = true; + has_valid_entries = true; } else if (is_host_address_rr_type(entry->type) && hostname_match(names[i], entry->name)) { - callback(p_cookie, r, entries); - break; + entry->valid = true; + has_valid_entries = true; } } } + if (has_valid_entries) + callback(p_cookie, r, entries); mdns_free(entries); } return 0; @@ -870,7 +876,7 @@ mdns_listen(const struct mdns_ctx *ctx, const char *const names[], } t1 = t2; } - mdns_listen_probe_network(ctx, names, nb_names, callback, p_cookie); + mdns_listen_probe_network(ctx, names, nb_names, type, callback, p_cookie); } free(qns); return (0);