/* $NetBSD: gethnamaddr.c,v 1.91 2014/06/19 15:08:18 christos Exp $ */ /* * ++Copyright++ 1985, 1988, 1993 * - * Copyright (c) 1985, 1988, 1993 * The Regents of the University of California. 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. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. * - * Portions Copyright (c) 1993 by Digital Equipment Corporation. * * 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, and that * the name of Digital Equipment Corporation not be used in advertising or * publicity pertaining to distribution of the document or software without * specific, written prior permission. * * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DIGITAL EQUIPMENT * CORPORATION 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. * - * --Copyright-- */ #define LOG_TAG "resolv" #include "gethnamaddr.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Experiments.h" #include "hostent.h" #include "netd_resolv/resolv.h" #include "res_comp.h" #include "res_debug.h" // p_class(), p_type() #include "resolv_cache.h" #include "resolv_private.h" #include "stats.pb.h" using android::net::NetworkDnsEventReported; constexpr int MAXADDRS = 35; typedef union { HEADER hdr; uint8_t buf[MAXPACKET]; } querybuf; static void pad_v4v6_hostent(struct hostent* hp, char** bpp, char* ep); static int dns_gethtbyaddr(const unsigned char* uaddr, int len, int af, const android_net_context* netcontext, getnamaddr* info, NetworkDnsEventReported* event); static int dns_gethtbyname(ResState* res, const char* name, int af, getnamaddr* info); #define BOUNDED_INCR(x) \ do { \ BOUNDS_CHECK(cp, x); \ cp += (x); \ } while (0) #define BOUNDS_CHECK(ptr, count) \ do { \ if (eom - (ptr) < (count)) goto no_recovery; \ } while (0) static struct hostent* getanswer(const querybuf* _Nonnull answer, int anslen, const char* qname, int qtype, struct hostent* hent, char* buf, size_t buflen, int* he) { const HEADER* hp; const uint8_t* cp; int n; size_t qlen; const uint8_t *eom, *erdata; char *bp, **hap, *ep; int ancount, qdcount; int haveanswer, had_error; int toobig = 0; char tbuf[MAXDNAME]; char* addr_ptrs[MAXADDRS]; const char* tname; std::vector aliases; tname = qname; hent->h_name = NULL; eom = answer->buf + anslen; bool (*name_ok)(const char* dn); switch (qtype) { case T_A: case T_AAAA: name_ok = res_hnok; break; case T_PTR: name_ok = res_dnok; break; default: *he = NO_RECOVERY; return NULL; /* XXX should be abort(); */ } /* * find first satisfactory answer */ hp = &answer->hdr; ancount = ntohs(hp->ancount); qdcount = ntohs(hp->qdcount); bp = buf; ep = buf + buflen; cp = answer->buf; BOUNDED_INCR(HFIXEDSZ); if (qdcount != 1) goto no_recovery; n = dn_expand(answer->buf, eom, cp, bp, (int) (ep - bp)); if ((n < 0) || !name_ok(bp)) goto no_recovery; BOUNDED_INCR(n + QFIXEDSZ); if (qtype == T_A || qtype == T_AAAA) { /* res_send() has already verified that the query name is the * same as the one we sent; this just gets the expanded name * (i.e., with the succeeding search-domain tacked on). */ n = (int) strlen(bp) + 1; /* for the \0 */ if (n >= MAXHOSTNAMELEN) goto no_recovery; hent->h_name = bp; bp += n; /* The qname can be abbreviated, but h_name is now absolute. */ qname = hent->h_name; } hent->h_addr_list = hap = addr_ptrs; *hap = NULL; haveanswer = 0; had_error = 0; while (ancount-- > 0 && cp < eom && !had_error) { n = dn_expand(answer->buf, eom, cp, bp, (int) (ep - bp)); if ((n < 0) || !name_ok(bp)) { had_error++; continue; } cp += n; /* name */ BOUNDS_CHECK(cp, 3 * INT16SZ + INT32SZ); int type = ntohs(*reinterpret_cast(cp)); cp += INT16SZ; /* type */ int cl = ntohs(*reinterpret_cast(cp)); cp += INT16SZ + INT32SZ; /* class, TTL */ n = ntohs(*reinterpret_cast(cp)); cp += INT16SZ; /* len */ BOUNDS_CHECK(cp, n); erdata = cp + n; if (cl != C_IN) { /* XXX - debug? syslog? */ cp += n; continue; /* XXX - had_error++ ? */ } if ((qtype == T_A || qtype == T_AAAA) && type == T_CNAME) { n = dn_expand(answer->buf, eom, cp, tbuf, (int) sizeof tbuf); if ((n < 0) || !name_ok(tbuf)) { had_error++; continue; } cp += n; if (cp != erdata) goto no_recovery; /* Store alias. */ aliases.push_back(bp); n = (int) strlen(bp) + 1; /* for the \0 */ if (n >= MAXHOSTNAMELEN) { had_error++; continue; } bp += n; /* Get canonical name. */ n = (int) strlen(tbuf) + 1; /* for the \0 */ if (n > ep - bp || n >= MAXHOSTNAMELEN) { had_error++; continue; } strlcpy(bp, tbuf, (size_t)(ep - bp)); hent->h_name = bp; bp += n; continue; } if (qtype == T_PTR && type == T_CNAME) { n = dn_expand(answer->buf, eom, cp, tbuf, (int) sizeof tbuf); if (n < 0 || !res_dnok(tbuf)) { had_error++; continue; } cp += n; if (cp != erdata) goto no_recovery; /* Get canonical name. */ n = (int) strlen(tbuf) + 1; /* for the \0 */ if (n > ep - bp || n >= MAXHOSTNAMELEN) { had_error++; continue; } strlcpy(bp, tbuf, (size_t)(ep - bp)); tname = bp; bp += n; continue; } if (type != qtype) { if (type != T_KEY && type != T_SIG) LOG(DEBUG) << __func__ << ": asked for \"" << qname << " " << p_class(C_IN) << " " << p_type(qtype) << "\", got type \"" << p_type(type) << "\""; cp += n; continue; /* XXX - had_error++ ? */ } switch (type) { case T_PTR: if (strcasecmp(tname, bp) != 0) { LOG(DEBUG) << __func__ << ": asked for \"" << qname << "\", got \"" << bp << "\""; cp += n; continue; /* XXX - had_error++ ? */ } n = dn_expand(answer->buf, eom, cp, bp, (int) (ep - bp)); if ((n < 0) || !res_hnok(bp)) { had_error++; break; } cp += n; if (cp != erdata) goto no_recovery; if (!haveanswer) hent->h_name = bp; else aliases.push_back(bp); if (n != -1) { n = (int) strlen(bp) + 1; /* for the \0 */ if (n >= MAXHOSTNAMELEN) { had_error++; break; } bp += n; } break; case T_A: case T_AAAA: { if (hent->h_name == NULL) goto no_recovery; const char* h_name = hent->h_name; if (strcasecmp(h_name, bp) != 0) { LOG(DEBUG) << __func__ << ": asked for \"" << h_name << "\", got \"" << bp << "\""; cp += n; continue; /* XXX - had_error++ ? */ } if (n != hent->h_length) { cp += n; continue; } if (type == T_AAAA) { struct in6_addr in6; memcpy(&in6, cp, NS_IN6ADDRSZ); if (IN6_IS_ADDR_V4MAPPED(&in6)) { cp += n; continue; } } if (!haveanswer) { int nn; hent->h_name = bp; nn = (int) strlen(bp) + 1; /* for the \0 */ bp += nn; } bp = align_ptr(bp); if (bp + n >= ep) { LOG(DEBUG) << __func__ << ": size (" << n << ") too big"; had_error++; continue; } if (hap >= &addr_ptrs[MAXADDRS - 1]) { if (!toobig++) { LOG(DEBUG) << __func__ << ": Too many addresses (" << MAXADDRS << ")"; } cp += n; continue; } (void) memcpy(*hap++ = bp, cp, (size_t) n); bp += n; cp += n; if (cp != erdata) goto no_recovery; break; } default: abort(); } if (!had_error) haveanswer++; } if (haveanswer) { *hap = NULL; if (!hent->h_name) { n = (int) strlen(qname) + 1; /* for the \0 */ if (n > ep - bp || n >= MAXHOSTNAMELEN) goto no_recovery; strlcpy(bp, qname, (size_t)(ep - bp)); hent->h_name = bp; bp += n; } if (hent->h_addrtype == AF_INET) pad_v4v6_hostent(hent, &bp, ep); goto success; } no_recovery: *he = NO_RECOVERY; return NULL; success: bp = align_ptr(bp); aliases.push_back(nullptr); qlen = aliases.size() * sizeof(*hent->h_aliases); if ((size_t)(ep - bp) < qlen) goto nospc; hent->h_aliases = (char**) bp; memcpy(bp, aliases.data(), qlen); bp += qlen; n = (int) (hap - addr_ptrs); qlen = (n + 1) * sizeof(*hent->h_addr_list); if ((size_t)(ep - bp) < qlen) goto nospc; hent->h_addr_list = (char**) bp; memcpy(bp, addr_ptrs, qlen); *he = NETDB_SUCCESS; return hent; nospc: errno = ENOSPC; *he = NETDB_INTERNAL; return NULL; } int resolv_gethostbyname(const char* name, int af, hostent* hp, char* buf, size_t buflen, const android_net_context* netcontext, hostent** result, NetworkDnsEventReported* event) { if (name == nullptr || hp == nullptr) { return EAI_SYSTEM; } getnamaddr info; ResState res(netcontext, event); setMdnsFlag(name, res.netid, &(res.flags)); size_t size; switch (af) { case AF_INET: size = NS_INADDRSZ; break; case AF_INET6: size = NS_IN6ADDRSZ; break; default: return EAI_FAMILY; } if (buflen < size) goto nospc; hp->h_addrtype = af; hp->h_length = (int) size; /* * disallow names consisting only of digits/dots, unless * they end in a dot. */ if (isdigit((uint8_t)name[0])) { for (const char* cp = name;; ++cp) { if (!*cp) { if (*--cp == '.') break; /* * All-numeric, no dot at the end. * Fake up a hostent as if we'd actually * done a lookup. */ goto fake; } if (!isdigit((uint8_t)*cp) && *cp != '.') break; } } if ((isxdigit((uint8_t)name[0]) && strchr(name, ':') != NULL) || name[0] == ':') { for (const char* cp = name;; ++cp) { if (!*cp) { if (*--cp == '.') break; /* * All-IPv6-legal, no dot at the end. * Fake up a hostent as if we'd actually * done a lookup. */ goto fake; } if (!isxdigit((uint8_t)*cp) && *cp != ':' && *cp != '.') break; } } info.hp = hp; info.buf = buf; info.buflen = buflen; if (_hf_gethtbyname2(name, af, &info)) { int error = dns_gethtbyname(&res, name, af, &info); if (error != 0) return error; } *result = hp; return 0; nospc: return EAI_MEMORY; fake: HENT_ARRAY(hp->h_addr_list, 1, buf, buflen); HENT_ARRAY(hp->h_aliases, 0, buf, buflen); hp->h_aliases[0] = NULL; if (size > buflen) goto nospc; if (inet_pton(af, name, buf) <= 0) { return EAI_NODATA; } hp->h_addr_list[0] = buf; hp->h_addr_list[1] = NULL; buf += size; buflen -= size; HENT_SCOPY(hp->h_name, name, buf, buflen); *result = hp; return 0; } int resolv_gethostbyaddr(const void* _Nonnull addr, socklen_t len, int af, hostent* hp, char* buf, size_t buflen, const struct android_net_context* netcontext, hostent** result, NetworkDnsEventReported* event) { const uint8_t* uaddr = (const uint8_t*)addr; socklen_t size; struct getnamaddr info; if (af == AF_INET6 && len == NS_IN6ADDRSZ && (IN6_IS_ADDR_LINKLOCAL((const struct in6_addr*) addr) || IN6_IS_ADDR_SITELOCAL((const struct in6_addr*) addr))) { return EAI_NODATA; } if (af == AF_INET6 && len == NS_IN6ADDRSZ && (IN6_IS_ADDR_V4MAPPED((const struct in6_addr*) addr) || IN6_IS_ADDR_V4COMPAT((const struct in6_addr*) addr))) { /* Unmap. */ uaddr += NS_IN6ADDRSZ - NS_INADDRSZ; addr = uaddr; af = AF_INET; len = NS_INADDRSZ; } switch (af) { case AF_INET: size = NS_INADDRSZ; break; case AF_INET6: size = NS_IN6ADDRSZ; break; default: return EAI_FAMILY; } if (size != len) { // TODO: Consider converting to a private extended EAI_* error code. // Currently, the EAI_* value has no corresponding error code for invalid argument socket // length. In order to not rely on errno, convert the original error code pair, EAI_SYSTEM // and EINVAL, to EAI_FAIL. return EAI_FAIL; } info.hp = hp; info.buf = buf; info.buflen = buflen; if (_hf_gethtbyaddr(uaddr, len, af, &info)) { int error = dns_gethtbyaddr(uaddr, len, af, netcontext, &info, event); if (error != 0) return error; } *result = hp; return 0; } // TODO: Consider leaving function without returning error code as _gethtent() does because // the error code of the caller does not currently return to netd. struct hostent* netbsd_gethostent_r(FILE* hf, struct hostent* hent, char* buf, size_t buflen, int* he) { char *name; char* cp; int af, len; size_t anum; struct in6_addr host_addr; std::vector aliases; if (hf == NULL) { *he = NETDB_INTERNAL; errno = EINVAL; return NULL; } char* p = NULL; // Allocate a new space to read file lines like upstream does. const size_t line_buf_size = MAXPACKET; if ((p = (char*) malloc(line_buf_size)) == NULL) { goto nospc; } for (;;) { if (!fgets(p, line_buf_size, hf)) { free(p); *he = HOST_NOT_FOUND; return NULL; } if (*p == '#') { continue; } if (!(cp = strpbrk(p, "#\n"))) { continue; } *cp = '\0'; if (!(cp = strpbrk(p, " \t"))) continue; *cp++ = '\0'; if (inet_pton(AF_INET6, p, &host_addr) > 0) { af = AF_INET6; len = NS_IN6ADDRSZ; } else { if (inet_pton(AF_INET, p, &host_addr) <= 0) continue; af = AF_INET; len = NS_INADDRSZ; } /* if this is not something we're looking for, skip it. */ if (hent->h_addrtype != 0 && hent->h_addrtype != af) continue; if (hent->h_length != 0 && hent->h_length != len) continue; while (*cp == ' ' || *cp == '\t') cp++; if ((cp = strpbrk(name = cp, " \t")) != NULL) *cp++ = '\0'; while (cp && *cp) { if (*cp == ' ' || *cp == '\t') { cp++; continue; } aliases.push_back(cp); if ((cp = strpbrk(cp, " \t")) != NULL) *cp++ = '\0'; } break; } hent->h_length = len; hent->h_addrtype = af; HENT_ARRAY(hent->h_addr_list, 1, buf, buflen); anum = aliases.size(); HENT_ARRAY(hent->h_aliases, anum, buf, buflen); HENT_COPY(hent->h_addr_list[0], &host_addr, hent->h_length, buf, buflen); hent->h_addr_list[1] = NULL; /* Reserve space for mapping IPv4 address to IPv6 address in place */ if (hent->h_addrtype == AF_INET) { HENT_COPY(buf, NAT64_PAD, sizeof(NAT64_PAD), buf, buflen); } HENT_SCOPY(hent->h_name, name, buf, buflen); for (size_t i = 0; i < anum; i++) HENT_SCOPY(hent->h_aliases[i], aliases[i], buf, buflen); hent->h_aliases[anum] = NULL; *he = NETDB_SUCCESS; free(p); return hent; nospc: free(p); errno = ENOSPC; *he = NETDB_INTERNAL; return NULL; } /* Reserve space for mapping IPv4 address to IPv6 address in place */ static void pad_v4v6_hostent(struct hostent* _Nonnull hp, char** _Nonnull bpp, char* _Nonnull ep) { if (hp->h_addrtype != AF_INET || hp->h_length != NS_INADDRSZ) return; for (char** ap = hp->h_addr_list; *ap; ap++) { char* const bp = align_ptr(*bpp); if (ep - bp < NS_IN6ADDRSZ) { // Out of space. Truncate address list here. *ap = nullptr; return; } memcpy(bp, *ap, NS_INADDRSZ); memcpy(bp + NS_INADDRSZ, NAT64_PAD, sizeof(NAT64_PAD)); *ap = bp; *bpp = bp + NS_IN6ADDRSZ; } } static int dns_gethtbyname(ResState* res, const char* name, int addr_type, getnamaddr* info) { int n, type; info->hp->h_addrtype = addr_type; switch (info->hp->h_addrtype) { case AF_INET: info->hp->h_length = NS_INADDRSZ; type = T_A; break; case AF_INET6: info->hp->h_length = NS_IN6ADDRSZ; type = T_AAAA; break; default: return EAI_FAMILY; } auto buf = std::make_unique(); int he; n = res_nsearch(res, name, C_IN, type, {buf->buf, (int)sizeof(buf->buf)}, &he); if (n < 0) { LOG(DEBUG) << __func__ << ": res_nsearch failed (" << n << ")"; // Return h_errno (he) to catch more detailed errors rather than EAI_NODATA. // Note that res_nsearch() doesn't set the pair NETDB_INTERNAL and errno. // See also herrnoToAiErrno(). return herrnoToAiErrno(he); } hostent* hp = getanswer(buf.get(), n, name, type, info->hp, info->buf, info->buflen, &he); if (hp == NULL) return herrnoToAiErrno(he); return 0; } static int dns_gethtbyaddr(const unsigned char* uaddr, int len, int af, const android_net_context* netcontext, getnamaddr* info, NetworkDnsEventReported* event) { char qbuf[MAXDNAME + 1], *qp, *ep; int n; int advance; info->hp->h_length = len; info->hp->h_addrtype = af; switch (info->hp->h_addrtype) { case AF_INET: (void) snprintf(qbuf, sizeof(qbuf), "%u.%u.%u.%u.in-addr.arpa", (uaddr[3] & 0xff), (uaddr[2] & 0xff), (uaddr[1] & 0xff), (uaddr[0] & 0xff)); break; case AF_INET6: qp = qbuf; ep = qbuf + sizeof(qbuf) - 1; for (n = NS_IN6ADDRSZ - 1; n >= 0; n--) { advance = snprintf(qp, (size_t)(ep - qp), "%x.%x.", uaddr[n] & 0xf, ((unsigned int) uaddr[n] >> 4) & 0xf); if (advance > 0 && qp + advance < ep) qp += advance; else { // TODO: Consider converting to a private extended EAI_* error code. // Currently, the EAI_* value has no corresponding error code for an internal // out of buffer space. In order to not rely on errno, convert the original // error code EAI_SYSTEM to EAI_MEMORY. return EAI_MEMORY; } } if (strlcat(qbuf, "ip6.arpa", sizeof(qbuf)) >= sizeof(qbuf)) { // TODO: Consider converting to a private extended EAI_* error code. // Currently, the EAI_* value has no corresponding error code for an internal // out of buffer space. In order to not rely on errno, convert the original // error code EAI_SYSTEM to EAI_MEMORY. return EAI_MEMORY; } break; default: return EAI_FAMILY; } auto buf = std::make_unique(); ResState res(netcontext, event); int he; n = res_nquery(&res, qbuf, C_IN, T_PTR, {buf->buf, (int)sizeof(buf->buf)}, &he); if (n < 0) { LOG(DEBUG) << __func__ << ": res_nquery failed (" << n << ")"; // Note that res_nquery() doesn't set the pair NETDB_INTERNAL and errno. // Return h_errno (he) to catch more detailed errors rather than EAI_NODATA. // See also herrnoToAiErrno(). return herrnoToAiErrno(he); } hostent* hp = getanswer(buf.get(), n, qbuf, T_PTR, info->hp, info->buf, info->buflen, &he); if (hp == NULL) return herrnoToAiErrno(he); char* bf = (char*) (hp->h_addr_list + 2); size_t blen = (size_t)(bf - info->buf); if (blen + info->hp->h_length > info->buflen) goto nospc; hp->h_addr_list[0] = bf; hp->h_addr_list[1] = NULL; memcpy(bf, uaddr, (size_t) info->hp->h_length); /* Reserve enough space for mapping IPv4 address to IPv6 address in place */ if (info->hp->h_addrtype == AF_INET) { if (blen + NS_IN6ADDRSZ > info->buflen) goto nospc; // Pad zero to the unused address space memcpy(bf + NS_INADDRSZ, NAT64_PAD, sizeof(NAT64_PAD)); } return 0; nospc: return EAI_MEMORY; } int herrnoToAiErrno(int he) { switch (he) { // extended h_errno case NETD_RESOLV_H_ERRNO_EXT_TIMEOUT: return NETD_RESOLV_TIMEOUT; // legacy h_errno case NETDB_SUCCESS: return 0; case HOST_NOT_FOUND: // TODO: Perhaps convert HOST_NOT_FOUND to EAI_NONAME instead case NO_DATA: // NO_ADDRESS return EAI_NODATA; case TRY_AGAIN: return EAI_AGAIN; case NETDB_INTERNAL: // TODO: Remove ENOSPC and call abort() immediately whenever any allocation fails. if (errno == ENOSPC) return EAI_MEMORY; // Theoretically, this should not happen. Leave this here just in case. // Currently, getanswer() of {gethnamaddr, getaddrinfo}.cpp, res_nsearch() and // res_searchN() use this function to convert error code. Only getanswer() // of gethnamaddr.cpp may return the error code pair, herrno NETDB_INTERNAL and // errno ENOSPC, which has already converted to EAI_MEMORY. The remaining functions // don't set the pair herrno and errno. return EAI_SYSTEM; // see errno for detail case NO_RECOVERY: default: return EAI_FAIL; // TODO: Perhaps convert default to EAI_MAX (unknown error) instead } } void setMdnsFlag(std::string_view hostname, unsigned netid, uint32_t* flags) { if (hostname.ends_with(".local") && is_mdns_supported_network(netid) && android::net::Experiments::getInstance()->getFlag("mdns_resolution", 1)) *flags |= RES_F_MDNS; } bool isMdnsResolution(uint32_t flags) { return flags & RES_F_MDNS; }