diff --git a/examples/host-lookup.c b/examples/host-lookup.c new file mode 100644 index 0000000..b1f5361 --- /dev/null +++ b/examples/host-lookup.c @@ -0,0 +1,117 @@ +/* + * 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->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; +} + +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) 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 198654c..5231f37 100644 --- a/src/mdns.c +++ b/src/mdns.c @@ -729,10 +729,46 @@ 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) +{ + 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, - 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; @@ -749,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); @@ -758,20 +796,28 @@ 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; } 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)) { + entry->valid = true; + has_valid_entries = true; } } } + if (has_valid_entries) + callback(p_cookie, r, entries); mdns_free(entries); } return 0; @@ -798,6 +844,17 @@ 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) || + !(!strrcmp(qns[i].name, ".local") || !strrcmp(qns[i].name, ".local."))) + { + free(qns); + return (MDNS_ERROR); + } + } + if (i + 1 < nb_names) qns[i].next = &qns[i+1]; } @@ -819,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);