From 07b50523fae8735ce7c121ee3cc8ef5d7d01eaf1 Mon Sep 17 00:00:00 2001 From: Rui Paulo Date: Thu, 11 Apr 2013 17:02:33 -0700 Subject: [PATCH] Initial implementation of nss_mdns. --- include/nsswitch.h | 1 + lib/nss_mdns/Makefile | 12 + lib/nss_mdns/nss_mdns.c | 642 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 655 insertions(+) create mode 100644 lib/nss_mdns/Makefile create mode 100644 lib/nss_mdns/nss_mdns.c diff --git a/include/nsswitch.h b/include/nsswitch.h index 1a97cea..eb2e806 100644 --- a/include/nsswitch.h +++ b/include/nsswitch.h @@ -63,6 +63,7 @@ #define NSSRC_NIS "nis" /* YP/NIS */ #define NSSRC_COMPAT "compat" /* passwd,group in YP compat mode */ #define NSSRC_CACHE "cache" /* nscd daemon */ +#define NSSRC_MDNS "mdns" /* multicast DNS */ #define NSSRC_FALLBACK "__fallback" /* internal fallback source */ /* diff --git a/lib/nss_mdns/Makefile b/lib/nss_mdns/Makefile new file mode 100644 index 0000000..00dfdfc --- /dev/null +++ b/lib/nss_mdns/Makefile @@ -0,0 +1,12 @@ +# $FreeBSD$ + +CFLAGS+= -ggdb +LIB= nss_mdns +SHLIB_MAJOR= 1 +SHLIB_NAME= ${LIB}.so.${SHLIB_MAJOR} + +SRCS= nss_mdns.c + +WITHOUT_MAN= + +.include diff --git a/lib/nss_mdns/nss_mdns.c b/lib/nss_mdns/nss_mdns.c new file mode 100644 index 0000000..259dcf0 --- /dev/null +++ b/lib/nss_mdns/nss_mdns.c @@ -0,0 +1,642 @@ +/*- + * Copyright (c) 2013 Rui Paulo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct mdns_handle { + int s; + int af; + enum { + MDNS_REQ_NAME, + MDNS_REQ_ADDR + } reqtype; +}; + +struct mdns_result { + char name[64]; + int ifindex; + union { + in_addr_t v4; + struct in6_addr v6; + } addr; +}; + +static int mdns_matches_local(const char *); +static int mdns_matches_addr(int, void *); +static int mdns_issue_name_query(int, const char *, struct mdns_handle *); +static int mdns_issue_addr_query(int, const void *, struct mdns_handle *); +static int mdns_parse_result(struct mdns_handle *, struct mdns_result *); + +ns_mtab * nss_module_register(const char *, unsigned int *, + nss_module_unregister_fn *); + +static NSS_METHOD_PROTOTYPE(mdns_getaddrinfo); +static NSS_METHOD_PROTOTYPE(mdns_gethostbyaddr_r); +static NSS_METHOD_PROTOTYPE(mdns_gethostbyname2_r); + +static ns_mtab ns_methods[] = { + { NSDB_HOSTS, "getaddrinfo", mdns_getaddrinfo, NULL }, + { NSDB_HOSTS, "gethostbyaddr_r", mdns_gethostbyaddr_r, NULL }, + { NSDB_HOSTS, "gethostbyname2_r", mdns_gethostbyname2_r, NULL }, +}; + +ns_mtab * +nss_module_register(const char *src __unused, unsigned int *mtabsize, + nss_module_unregister_fn *f) +{ + + *mtabsize = sizeof(ns_methods) / sizeof(*ns_methods); + *f = NULL; + + return (ns_methods); +} + +static struct addrinfo * +ai_fill(int family, struct addrinfo *oai, struct mdns_result *md_res) +{ + struct addrinfo *ai; + struct sockaddr_storage *ss; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + size_t len; + + ai = malloc(sizeof(*ai) + sizeof(struct sockaddr_storage)); + if (ai == NULL) + return (NULL); + ai->ai_flags = oai->ai_flags; + /* N.B.: oai->ai_family might have been PF_UNSPEC. */ + ai->ai_family = family; + ai->ai_socktype = oai->ai_socktype; + ai->ai_protocol = oai->ai_protocol; + /* N.B.: ai_addrlen set below */ + ai->ai_addr = (struct sockaddr *)(ai + 1); + ss = (struct sockaddr_storage *)ai->ai_addr; + bzero(ss, sizeof(*ss)); + ss->ss_family = ai->ai_family; + len = strlen(md_res->name) + 1; + ai->ai_canonname = malloc(len); + if (ai->ai_canonname == NULL) { + free(ai); + return (NULL); + } + strlcpy(ai->ai_canonname, md_res->name, len); + ai->ai_next = NULL; + switch (ss->ss_family) { + case AF_INET: + ai->ai_addrlen = sizeof(*sin); + ss->ss_len = ai->ai_addrlen; + sin = (struct sockaddr_in *)ss; + memcpy(&sin->sin_addr, &md_res->addr, + sizeof(in_addr_t)); + break; + case AF_INET6: + ai->ai_addrlen = sizeof(*sin6); + ss->ss_len = ai->ai_addrlen; + sin6 = (struct sockaddr_in6 *)ss; + memcpy(&sin6->sin6_addr, &md_res->addr, + sizeof(struct in6_addr)); + if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) + sin6->sin6_scope_id = md_res->ifindex; + break; + } + + return (ai); +} + +/* + * We only need to handle the FQDN lookup because the NSS API will handle + * numeric addresses, ports, flags, etc. + */ +static int +mdns_getaddrinfo(void *ret, void *data __unused, va_list ap) +{ + const char *name; + struct mdns_handle md_handle; + struct mdns_result md_res; + struct addrinfo *v4ai = NULL, *v6ai = NULL, *oai, **retp; + + /* + * Parse and validate the arguments. + */ + name = va_arg(ap, const char *); + if (!mdns_matches_local(name)) + return (NS_UNAVAIL); + oai = va_arg(ap, struct addrinfo *); + if (oai->ai_family != AF_UNSPEC && + oai->ai_family != AF_INET && + oai->ai_family != AF_INET6) + return (NS_UNAVAIL); + retp = (struct addrinfo **)ret; + *retp = NULL; + + /* + * In the getaddrinfo() case, we are not passed any buffer (see + * below). We will allocate space for addrinfo and ai_canonname which + * will later be freed by freeaddrinfo(). + */ + if (oai->ai_family == AF_INET6 || oai->ai_family == AF_UNSPEC) { + if (mdns_issue_name_query(AF_INET6, name, &md_handle) >= 0 && + mdns_parse_result(&md_handle, &md_res) == 0) + v6ai = ai_fill(AF_INET6, oai, &md_res); + } + if (oai->ai_family == AF_INET || oai->ai_family == AF_UNSPEC) { + if (mdns_issue_name_query(AF_INET, name, &md_handle) >= 0 && + mdns_parse_result(&md_handle, &md_res) == 0) + v4ai = ai_fill(AF_INET, oai, &md_res); + } + + if (v6ai) { + *retp = v6ai; + if (v4ai) + v6ai->ai_next = v4ai; + } else if (v4ai) + *retp = v4ai; + + if (*retp) + return (NS_SUCCESS); + else + return (NS_NOTFOUND); +} + +static int +mdns_gethostbyaddr_r(void *ret, void *data __unused, va_list ap) +{ + void *addr; + socklen_t len; + int af, error, *h_error; + struct hostent *he; + char *buf; + size_t buflen; + struct hostent **retp; + struct mdns_handle md_handle; + struct mdns_result md_res; + + /* + * Parse and validate the arguments. + */ + addr = va_arg(ap, void *); + len = va_arg(ap, socklen_t); + af = va_arg(ap, int); + if (af != AF_INET && af != AF_INET6) + return (NS_UNAVAIL); + if (!mdns_matches_addr(af, addr)) + return (NS_UNAVAIL); + he = va_arg(ap, struct hostent *); + if (he == NULL) + return (NS_UNAVAIL); + buf = va_arg(ap, char *); + buflen = va_arg(ap, size_t); + error = va_arg(ap, int); + h_error = va_arg(ap, int *); + + retp = (struct hostent **)ret; + *retp = NULL; + + if (mdns_issue_addr_query(af, addr, &md_handle) < 0) + return (NS_UNAVAIL); + if (mdns_parse_result(&md_handle, &md_res) == 0) { + /* + * We were given a buffer (buf) to hold the data. This is how + * it looks like in memory: + * + * buf hostent + * ------------------- + * | md_res.name[0] | <- h_name + * | md_res.name[1] | + * | ... | + * | NUL | + * | NULL | <- h_aliases + * | addr[0] | + * | addr[1] | + * | ... | + * | ptr to addr[0] | <- h_addr_list + * | NULL | + * |------------------ + */ + if (strlen(md_res.name) + 1 + sizeof(void *) + + len + 2 * sizeof(void *) > buflen) + return (NS_UNAVAIL); + he->h_addrtype = af; + he->h_length = len; + strlcpy(buf, md_res.name, strlen(md_res.name) + 1); + he->h_name = buf; + buf += strlen(md_res.name) + 1; + + bzero(buf, sizeof(void *)); + he->h_aliases = (char **)buf; + buf += sizeof(void *); + + memcpy(buf, addr, he->h_length); + buf += he->h_length; + he->h_addr_list = (char **)buf; + he->h_addr_list[0] = (buf - he->h_length); + he->h_addr_list[1] = NULL; + *h_error = 0; + *retp = he; + + return (NS_SUCCESS); + } else + return (NS_UNAVAIL); +} + +static int +mdns_gethostbyname2_r(void *ret, void *data __unused, va_list ap) +{ + const char *name; + struct hostent *he; + char *buf; + size_t len; + int af, error, *h_error; + struct hostent **retp; + struct mdns_handle md_handle; + struct mdns_result md_res; + + /* + * Parse and validate the arguments. + */ + name = va_arg(ap, char *); + if (!mdns_matches_local(name)) + return (NS_UNAVAIL); + af = va_arg(ap, int); + if (af != AF_INET && af != AF_INET6) + return (NS_UNAVAIL); + he = va_arg(ap, struct hostent *); + if (he == NULL) + return (NS_UNAVAIL); + buf = va_arg(ap, char *); + len = va_arg(ap, size_t); + error = va_arg(ap, int); + h_error = va_arg(ap, int *); + + retp = (struct hostent **)ret; + *retp = NULL; + + if (mdns_issue_name_query(af, name, &md_handle) < 0) + return (NS_UNAVAIL); + if (mdns_parse_result(&md_handle, &md_res) == 0) { + /* + * We were given a buffer (buf) to hold the data. This is how + * it looks like in memory: + * + * buf hostent + * ------------------- + * | md_res.name[0] | <- h_name + * | md_res.name[1] | + * | ... | + * | NUL | + * | NULL | <- h_aliases + * | md_res.addr[0] | [1] + * | md_res.addr[1] | + * | ... | + * | ptr to addr[0] | <- h_addr_list + * | NULL | + * |------------------ + */ + he->h_addrtype = af; + he->h_length = af == AF_INET ? sizeof(md_res.addr.v4) : + sizeof(md_res.addr.v6); + + if (strlen(md_res.name) + 1 + sizeof(void *) + + he->h_length + 2 * sizeof(void *) > len) + return (NS_UNAVAIL); + + strlcpy(buf, md_res.name, strlen(md_res.name) + 1); + he->h_name = buf; + buf += strlen(md_res.name) + 1; + + bzero(buf, sizeof(void *)); + he->h_aliases = (char **)buf; + buf += sizeof(void *); + + memcpy(buf, &md_res.addr, he->h_length); + buf += he->h_length; + he->h_addr_list = (char **)buf; + he->h_addr_list[0] = (buf - he->h_length); + he->h_addr_list[1] = NULL; + *h_error = 0; + *retp = he; + + return (NS_SUCCESS); + } else + return (NS_NOTFOUND); + + + return 0; +} + +/* + * Avahi hooks. + * + * When Avahi is running, there is a Unix socket living at AVAHI_PATH. + * To resolve mDNS names, we connect to that Unix socket and issue one + * command. Avahi only accepts 1 command at a time, so we need to connect + * twice if we want to resolve IPv6 and IPv4. + * + * Here's the list of available commands: + * + * HELP + * + Available commands are: + * + RESOLVE-HOSTNAME + * + RESOLVE-HOSTNAME-IPV6 + * + RESOLVE-HOSTNAME-IPV4 + * + RESOLVE-ADDRESS
+ * + BROWSE-DNS-SERVERS + * + BROWSE-DNS-SERVERS-IPV4 + * + BROWSE-DNS-SERVERS-IPV6 + * + * It's also worth noting that RESOLVE-HOSTNAME will pick IPv4 or IPv6 at + * random which doesn't suit our purposes. + */ + +#define AVAHI_PATH "/var/run/avahi-daemon/socket" + +static int +avahi_connect(int s) +{ + struct sockaddr_un un; + + un.sun_family = AF_LOCAL; + strlcpy(un.sun_path, AVAHI_PATH, sizeof(un.sun_path)); + un.sun_len = SUN_LEN(&un); + + return (connect(s, (struct sockaddr *)&un, sizeof(un))); +} + +static void +avahi_issue_name_query(int s, int af, const char *name) +{ + char q[128]; + size_t len; + + len = snprintf(q, sizeof(q), "RESOLVE-HOSTNAME-IPV%c %s\n", + af == AF_INET ? '4' : '6', name); + write(s, q, len); +} + +static void +avahi_issue_addr_query(int s, const char *addr) +{ + char q[64]; + size_t len; + + len = snprintf(q, sizeof(q), "RESOLVE-ADDRESS %s\n", addr); + write(s, q, len); +} + +static int +avahi_parse_name_result(struct mdns_handle *md_handle, char *buf, + struct mdns_result *md_res) +{ + unsigned char addr[64]; + + if (buf[0] == '+') { + sscanf(buf, "%*c %d %*d %64s %64s", + &md_res->ifindex, md_res->name, + addr); + inet_pton(md_handle->af, addr, &md_res->addr); + return (0); + } + + return (1); + +} + +static int +avahi_parse_addr_result(char *buf, struct mdns_result *md_res) +{ + + if (buf[0] == '+') { + sscanf(buf, "%*c %d %*d %64s", + &md_res->ifindex, md_res->name); + return (0); + } + + return (1); + +} + +static int +mdns_matches_local(const char *name) +{ + static const char *domain = ".local"; + static const size_t dlen = 6; + size_t len; + + len = strlen(name); + if (len <= dlen) + return (0); + + return (strcmp(name + len - dlen, domain) == 0); +} + +static int +mdns_matches_addr(int af, void *addr) +{ + in_addr_t *v4 = NULL; + struct in6_addr *v6 = NULL; + int s, ret = 0; + char buf[sizeof(struct rt_msghdr) + sizeof(struct sockaddr_in6)]; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + struct rt_msghdr *rtm; + size_t len; + static int seq = 1; + pid_t pid; + + /* + * Handle the fast path: we can determine the network segment where + * the node is by looking at the address type. + */ + if (af == AF_INET) { + v4 = (in_addr_t *)addr; + if (IN_LINKLOCAL(ntohl(*v4)) || IN_PRIVATE(ntohl(*v4))) + return (1); + } else { + v6 = (struct in6_addr *)addr; + if (IN6_IS_ADDR_LINKLOCAL(v6)) + return (1); + } + /* + * Otherwise, we have to consult the routing table. + */ + s = socket(PF_ROUTE, SOCK_RAW, af); + if (s < 0) + return (1); + pid = getpid(); + bzero(&buf, sizeof(buf)); + rtm = (struct rt_msghdr *)buf; + len = sizeof(*rtm); + if (v6) + len += sizeof(*sin6); + else + len += sizeof(*sin); + rtm->rtm_msglen = len; + rtm->rtm_version = RTM_VERSION; + rtm->rtm_type = RTM_GET; + rtm->rtm_flags = RTF_UP|RTF_GATEWAY|RTF_HOST; + rtm->rtm_addrs = RTA_DST; + rtm->rtm_pid = pid; + rtm->rtm_seq = seq; + if (v6) { + sin6 = (struct sockaddr_in6 *)(rtm + 1); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(*sin6); + memcpy(&sin6->sin6_addr, v6, sizeof(*v6)); + } else { + sin = (struct sockaddr_in *)(rtm + 1); + sin->sin_family = AF_INET; + sin->sin_len = sizeof(*sin); + memcpy(&sin->sin_addr, v4, sizeof(*v4)); + } + send(s, buf, len, 0); + while (recv(s, buf, sizeof(buf), 0) > 0) { + rtm = (struct rt_msghdr *)buf; + if (rtm->rtm_pid == pid && rtm->rtm_seq == seq && + rtm->rtm_flags & RTF_DONE) { + ret = rtm->rtm_flags & RTF_GATEWAY ? 0 : 1; + break; + } + } + if (seq == INT_MAX) + seq = 0; + seq++; + close(s); + + return (ret); +} + +static int +mdns_socket(void) +{ + int s; + static int on = 1; + + s = socket(AF_LOCAL, SOCK_STREAM, 0); + if (s < 0) + return (-1); + setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on)); + + return (s); +} + + +static int +mdns_connect(int s) +{ + + return (avahi_connect(s)); +} + +static int +mdns_issue_name_query(int af, const char *name, struct mdns_handle *md_handle) +{ + int s; + + bzero(md_handle, sizeof(*md_handle)); + s = mdns_socket(); + if (s < 0) + return (-1); + if (mdns_connect(s) < 0) { + close(s); + return (-1); + } + md_handle->s = s; + md_handle->af = af; + md_handle->reqtype = MDNS_REQ_NAME; + avahi_issue_name_query(s, af, name); + + return (0); +} + +static int +mdns_issue_addr_query(int af, const void *addr, struct mdns_handle *md_handle) +{ + int s; + char name[64]; + + s = mdns_socket(); + if (s < 0) + return (-1); + if (mdns_connect(s) < 0) { + close(s); + return (-1); + } + md_handle->s = s; + md_handle->af = af; + md_handle->reqtype = MDNS_REQ_ADDR; + inet_ntop(af, addr, name, sizeof(name)); + avahi_issue_addr_query(s, name); + + return (s); +} + +static int +mdns_parse_result(struct mdns_handle *md_handle, struct mdns_result *md_res) +{ + unsigned char buf[128]; + int error; + fd_set fdset; + struct timeval tv; + + error = 0; + bzero(md_res, sizeof(*md_res)); + FD_ZERO(&fdset); + FD_SET(md_handle->s, &fdset); + tv.tv_usec = 0; + tv.tv_sec = 10; + if (select(md_handle->s + 1, &fdset, NULL, NULL, &tv) <= 0) { + error = -1; + goto out; + } + + if (read(md_handle->s, buf, sizeof(buf)) < 0) { + error = -1; + goto out; + } + if (md_handle->reqtype == MDNS_REQ_NAME) + error = avahi_parse_name_result(md_handle, buf, md_res); + else if (md_handle->reqtype == MDNS_REQ_ADDR) + error = avahi_parse_addr_result(buf, md_res); +out: + close(md_handle->s); + + return (error); +} -- 1.8.2