/* $NetBSD: res_send.c,v 1.9 2006/01/24 17:41:25 christos Exp $ */ /* * Copyright 2008 Android Open Source Project (source port randomization) * Copyright (c) 1985, 1989, 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. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. 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 (c) 2004 by Internet Systems Consortium, Inc. ("ISC") * Portions Copyright (c) 1996-1999 by Internet Software Consortium. * * 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 ISC DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC 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 <sys/cdefs.h> #if defined(LIBC_SCCS) && !defined(lint) #ifdef notdef static const char sccsid[] = "@(#)res_send.c 8.1 (Berkeley) 6/4/93"; static const char rcsid[] = "Id: res_send.c,v 1.5.2.2.4.5 2004/08/10 02:19:56 marka Exp"; #else __RCSID("$NetBSD: res_send.c,v 1.9 2006/01/24 17:41:25 christos Exp $"); #endif #endif /* LIBC_SCCS and not lint */ /* set to 1 to use our small/simple/limited DNS cache */ #define USE_RESOLV_CACHE 1 /* * Send query to name server and wait for reply. */ #include <sys/types.h> #include <sys/param.h> #include <sys/time.h> #include <sys/socket.h> #include <sys/uio.h> #include <netinet/in.h> #include <arpa/nameser.h> #include <arpa/inet.h> #include <errno.h> #include <fcntl.h> #include <netdb.h> #ifdef ANDROID_CHANGES #include "resolv_netid.h" #include "resolv_private.h" #else #include <resolv.h> #endif #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> #include <isc/eventlib.h> #if USE_RESOLV_CACHE # include <resolv_cache.h> #endif #include "private/libc_logging.h" #ifndef DE_CONST #define DE_CONST(c,v) v = ((c) ? \ strchr((const void *)(c), *(const char *)(const void *)(c)) : NULL) #endif /* Options. Leave them on. */ #ifndef DEBUG #define DEBUG #endif #include "res_debug.h" #include "res_private.h" #define EXT(res) ((res)->_u._ext) #define DBG 0 static const int highestFD = FD_SETSIZE - 1; /* Forward. */ static int get_salen __P((const struct sockaddr *)); static struct sockaddr * get_nsaddr __P((res_state, size_t)); static int send_vc(res_state, const u_char *, int, u_char *, int, int *, int); static int send_dg(res_state, const u_char *, int, u_char *, int, int *, int, int *, int *); static void Aerror(const res_state, FILE *, const char *, int, const struct sockaddr *, int); static void Perror(const res_state, FILE *, const char *, int); static int sock_eq(struct sockaddr *, struct sockaddr *); #ifdef NEED_PSELECT static int pselect(int, void *, void *, void *, struct timespec *, const sigset_t *); #endif void res_pquery(const res_state, const u_char *, int, FILE *); static int connect_with_timeout(int sock, const struct sockaddr *nsap, socklen_t salen, int sec); static int retrying_select(const int sock, fd_set *readset, fd_set *writeset, const struct timespec *finish); /* BIONIC-BEGIN: implement source port randomization */ typedef union { struct sockaddr sa; struct sockaddr_in sin; struct sockaddr_in6 sin6; } _sockaddr_union; static int random_bind( int s, int family ) { _sockaddr_union u; int j; socklen_t slen; /* clear all, this also sets the IP4/6 address to 'any' */ memset( &u, 0, sizeof u ); switch (family) { case AF_INET: u.sin.sin_family = family; slen = sizeof u.sin; break; case AF_INET6: u.sin6.sin6_family = family; slen = sizeof u.sin6; break; default: errno = EPROTO; return -1; } /* first try to bind to a random source port a few times */ for (j = 0; j < 10; j++) { /* find a random port between 1025 .. 65534 */ int port = 1025 + (res_randomid() % (65535-1025)); if (family == AF_INET) u.sin.sin_port = htons(port); else u.sin6.sin6_port = htons(port); if ( !bind( s, &u.sa, slen ) ) return 0; } /* nothing after 10 tries, our network table is probably busy */ /* let the system decide which port is best */ if (family == AF_INET) u.sin.sin_port = 0; else u.sin6.sin6_port = 0; return bind( s, &u.sa, slen ); } /* BIONIC-END */ static const int niflags = NI_NUMERICHOST | NI_NUMERICSERV; /* Public. */ /* int * res_isourserver(ina) * looks up "ina" in _res.ns_addr_list[] * returns: * 0 : not found * >0 : found * author: * paul vixie, 29may94 */ __LIBC_HIDDEN__ int res_ourserver_p(const res_state statp, const struct sockaddr *sa) { const struct sockaddr_in *inp, *srv; const struct sockaddr_in6 *in6p, *srv6; int ns; switch (sa->sa_family) { case AF_INET: inp = (const struct sockaddr_in *)(const void *)sa; for (ns = 0; ns < statp->nscount; ns++) { srv = (struct sockaddr_in *)(void *)get_nsaddr(statp, (size_t)ns); if (srv->sin_family == inp->sin_family && srv->sin_port == inp->sin_port && (srv->sin_addr.s_addr == INADDR_ANY || srv->sin_addr.s_addr == inp->sin_addr.s_addr)) return (1); } break; case AF_INET6: if (EXT(statp).ext == NULL) break; in6p = (const struct sockaddr_in6 *)(const void *)sa; for (ns = 0; ns < statp->nscount; ns++) { srv6 = (struct sockaddr_in6 *)(void *)get_nsaddr(statp, (size_t)ns); if (srv6->sin6_family == in6p->sin6_family && srv6->sin6_port == in6p->sin6_port && #ifdef HAVE_SIN6_SCOPE_ID (srv6->sin6_scope_id == 0 || srv6->sin6_scope_id == in6p->sin6_scope_id) && #endif (IN6_IS_ADDR_UNSPECIFIED(&srv6->sin6_addr) || IN6_ARE_ADDR_EQUAL(&srv6->sin6_addr, &in6p->sin6_addr))) return (1); } break; default: break; } return (0); } /* int * res_nameinquery(name, type, class, buf, eom) * look for (name,type,class) in the query section of packet (buf,eom) * requires: * buf + HFIXEDSZ <= eom * returns: * -1 : format error * 0 : not found * >0 : found * author: * paul vixie, 29may94 */ int res_nameinquery(const char *name, int type, int class, const u_char *buf, const u_char *eom) { const u_char *cp = buf + HFIXEDSZ; int qdcount = ntohs(((const HEADER*)(const void *)buf)->qdcount); while (qdcount-- > 0) { char tname[MAXDNAME+1]; int n, ttype, tclass; n = dn_expand(buf, eom, cp, tname, sizeof tname); if (n < 0) return (-1); cp += n; if (cp + 2 * INT16SZ > eom) return (-1); ttype = ns_get16(cp); cp += INT16SZ; tclass = ns_get16(cp); cp += INT16SZ; if (ttype == type && tclass == class && ns_samename(tname, name) == 1) return (1); } return (0); } /* int * res_queriesmatch(buf1, eom1, buf2, eom2) * is there a 1:1 mapping of (name,type,class) * in (buf1,eom1) and (buf2,eom2)? * returns: * -1 : format error * 0 : not a 1:1 mapping * >0 : is a 1:1 mapping * author: * paul vixie, 29may94 */ int res_queriesmatch(const u_char *buf1, const u_char *eom1, const u_char *buf2, const u_char *eom2) { const u_char *cp = buf1 + HFIXEDSZ; int qdcount = ntohs(((const HEADER*)(const void *)buf1)->qdcount); if (buf1 + HFIXEDSZ > eom1 || buf2 + HFIXEDSZ > eom2) return (-1); /* * Only header section present in replies to * dynamic update packets. */ if ((((const HEADER *)(const void *)buf1)->opcode == ns_o_update) && (((const HEADER *)(const void *)buf2)->opcode == ns_o_update)) return (1); if (qdcount != ntohs(((const HEADER*)(const void *)buf2)->qdcount)) return (0); while (qdcount-- > 0) { char tname[MAXDNAME+1]; int n, ttype, tclass; n = dn_expand(buf1, eom1, cp, tname, sizeof tname); if (n < 0) return (-1); cp += n; if (cp + 2 * INT16SZ > eom1) return (-1); ttype = ns_get16(cp); cp += INT16SZ; tclass = ns_get16(cp); cp += INT16SZ; if (!res_nameinquery(tname, ttype, tclass, buf2, eom2)) return (0); } return (1); } int res_nsend(res_state statp, const u_char *buf, int buflen, u_char *ans, int anssiz) { int gotsomewhere, terrno, try, v_circuit, resplen, ns, n; char abuf[NI_MAXHOST]; #if USE_RESOLV_CACHE ResolvCacheStatus cache_status = RESOLV_CACHE_UNSUPPORTED; #endif #if !USE_RESOLV_CACHE if (statp->nscount == 0) { errno = ESRCH; return (-1); } #endif if (anssiz < HFIXEDSZ) { errno = EINVAL; return (-1); } DprintQ((statp->options & RES_DEBUG) || (statp->pfcode & RES_PRF_QUERY), (stdout, ";; res_send()\n"), buf, buflen); v_circuit = (statp->options & RES_USEVC) || buflen > PACKETSZ; gotsomewhere = 0; terrno = ETIMEDOUT; #if USE_RESOLV_CACHE int anslen = 0; cache_status = _resolv_cache_lookup( statp->netid, buf, buflen, ans, anssiz, &anslen); if (cache_status == RESOLV_CACHE_FOUND) { return anslen; } else if (cache_status != RESOLV_CACHE_UNSUPPORTED) { // had a cache miss for a known network, so populate the thread private // data so the normal resolve path can do its thing _resolv_populate_res_for_net(statp); } if (statp->nscount == 0) { // We have no nameservers configured, so there's no point trying. // Tell the cache the query failed, or any retries and anyone else asking the same // question will block for PENDING_REQUEST_TIMEOUT seconds instead of failing fast. _resolv_cache_query_failed(statp->netid, buf, buflen); errno = ESRCH; return (-1); } #endif /* * If the ns_addr_list in the resolver context has changed, then * invalidate our cached copy and the associated timing data. */ if (EXT(statp).nscount != 0) { int needclose = 0; struct sockaddr_storage peer; socklen_t peerlen; if (EXT(statp).nscount != statp->nscount) needclose++; else for (ns = 0; ns < statp->nscount; ns++) { if (statp->nsaddr_list[ns].sin_family && !sock_eq((struct sockaddr *)(void *)&statp->nsaddr_list[ns], (struct sockaddr *)(void *)&EXT(statp).ext->nsaddrs[ns])) { needclose++; break; } if (EXT(statp).nssocks[ns] == -1) continue; peerlen = sizeof(peer); if (getpeername(EXT(statp).nssocks[ns], (struct sockaddr *)(void *)&peer, &peerlen) < 0) { needclose++; break; } if (!sock_eq((struct sockaddr *)(void *)&peer, get_nsaddr(statp, (size_t)ns))) { needclose++; break; } } if (needclose) { res_nclose(statp); EXT(statp).nscount = 0; } } /* * Maybe initialize our private copy of the ns_addr_list. */ if (EXT(statp).nscount == 0) { for (ns = 0; ns < statp->nscount; ns++) { EXT(statp).nstimes[ns] = RES_MAXTIME; EXT(statp).nssocks[ns] = -1; if (!statp->nsaddr_list[ns].sin_family) continue; EXT(statp).ext->nsaddrs[ns].sin = statp->nsaddr_list[ns]; } EXT(statp).nscount = statp->nscount; } /* * Some resolvers want to even out the load on their nameservers. * Note that RES_BLAST overrides RES_ROTATE. */ if ((statp->options & RES_ROTATE) != 0U && (statp->options & RES_BLAST) == 0U) { union res_sockaddr_union inu; struct sockaddr_in ina; int lastns = statp->nscount - 1; int fd; u_int16_t nstime; if (EXT(statp).ext != NULL) inu = EXT(statp).ext->nsaddrs[0]; ina = statp->nsaddr_list[0]; fd = EXT(statp).nssocks[0]; nstime = EXT(statp).nstimes[0]; for (ns = 0; ns < lastns; ns++) { if (EXT(statp).ext != NULL) EXT(statp).ext->nsaddrs[ns] = EXT(statp).ext->nsaddrs[ns + 1]; statp->nsaddr_list[ns] = statp->nsaddr_list[ns + 1]; EXT(statp).nssocks[ns] = EXT(statp).nssocks[ns + 1]; EXT(statp).nstimes[ns] = EXT(statp).nstimes[ns + 1]; } if (EXT(statp).ext != NULL) EXT(statp).ext->nsaddrs[lastns] = inu; statp->nsaddr_list[lastns] = ina; EXT(statp).nssocks[lastns] = fd; EXT(statp).nstimes[lastns] = nstime; } /* * Send request, RETRY times, or until successful. */ for (try = 0; try < statp->retry; try++) { for (ns = 0; ns < statp->nscount; ns++) { struct sockaddr *nsap; int nsaplen; nsap = get_nsaddr(statp, (size_t)ns); nsaplen = get_salen(nsap); statp->_flags &= ~RES_F_LASTMASK; statp->_flags |= (ns << RES_F_LASTSHIFT); same_ns: if (statp->qhook) { int done = 0, loops = 0; do { res_sendhookact act; act = (*statp->qhook)(&nsap, &buf, &buflen, ans, anssiz, &resplen); switch (act) { case res_goahead: done = 1; break; case res_nextns: res_nclose(statp); goto next_ns; case res_done: return (resplen); case res_modified: /* give the hook another try */ if (++loops < 42) /*doug adams*/ break; /*FALLTHROUGH*/ case res_error: /*FALLTHROUGH*/ default: goto fail; } } while (!done); } Dprint(((statp->options & RES_DEBUG) && getnameinfo(nsap, (socklen_t)nsaplen, abuf, sizeof(abuf), NULL, 0, niflags) == 0), (stdout, ";; Querying server (# %d) address = %s\n", ns + 1, abuf)); if (v_circuit) { /* Use VC; at most one attempt per server. */ try = statp->retry; n = send_vc(statp, buf, buflen, ans, anssiz, &terrno, ns); if (DBG) { __libc_format_log(ANDROID_LOG_DEBUG, "libc", "used send_vc %d\n", n); } if (n < 0) goto fail; if (n == 0) goto next_ns; resplen = n; } else { /* Use datagrams. */ if (DBG) { __libc_format_log(ANDROID_LOG_DEBUG, "libc", "using send_dg\n"); } n = send_dg(statp, buf, buflen, ans, anssiz, &terrno, ns, &v_circuit, &gotsomewhere); if (DBG) { __libc_format_log(ANDROID_LOG_DEBUG, "libc", "used send_dg %d\n",n); } if (n < 0) goto fail; if (n == 0) goto next_ns; if (DBG) { __libc_format_log(ANDROID_LOG_DEBUG, "libc", "time=%ld\n", time(NULL)); } if (v_circuit) goto same_ns; resplen = n; } Dprint((statp->options & RES_DEBUG) || ((statp->pfcode & RES_PRF_REPLY) && (statp->pfcode & RES_PRF_HEAD1)), (stdout, ";; got answer:\n")); DprintQ((statp->options & RES_DEBUG) || (statp->pfcode & RES_PRF_REPLY), (stdout, "%s", ""), ans, (resplen > anssiz) ? anssiz : resplen); #if USE_RESOLV_CACHE if (cache_status == RESOLV_CACHE_NOTFOUND) { _resolv_cache_add(statp->netid, buf, buflen, ans, resplen); } #endif /* * If we have temporarily opened a virtual circuit, * or if we haven't been asked to keep a socket open, * close the socket. */ if ((v_circuit && (statp->options & RES_USEVC) == 0U) || (statp->options & RES_STAYOPEN) == 0U) { res_nclose(statp); } if (statp->rhook) { int done = 0, loops = 0; do { res_sendhookact act; act = (*statp->rhook)(nsap, buf, buflen, ans, anssiz, &resplen); switch (act) { case res_goahead: case res_done: done = 1; break; case res_nextns: res_nclose(statp); goto next_ns; case res_modified: /* give the hook another try */ if (++loops < 42) /*doug adams*/ break; /*FALLTHROUGH*/ case res_error: /*FALLTHROUGH*/ default: goto fail; } } while (!done); } return (resplen); next_ns: ; } /*foreach ns*/ } /*foreach retry*/ res_nclose(statp); if (!v_circuit) { if (!gotsomewhere) errno = ECONNREFUSED; /* no nameservers found */ else errno = ETIMEDOUT; /* no answer obtained */ } else errno = terrno; #if USE_RESOLV_CACHE _resolv_cache_query_failed(statp->netid, buf, buflen); #endif return (-1); fail: #if USE_RESOLV_CACHE _resolv_cache_query_failed(statp->netid, buf, buflen); #endif res_nclose(statp); return (-1); } /* Private */ static int get_salen(sa) const struct sockaddr *sa; { #ifdef HAVE_SA_LEN /* There are people do not set sa_len. Be forgiving to them. */ if (sa->sa_len) return (sa->sa_len); #endif if (sa->sa_family == AF_INET) return (sizeof(struct sockaddr_in)); else if (sa->sa_family == AF_INET6) return (sizeof(struct sockaddr_in6)); else return (0); /* unknown, die on connect */ } /* * pick appropriate nsaddr_list for use. see res_init() for initialization. */ static struct sockaddr * get_nsaddr(statp, n) res_state statp; size_t n; { if (!statp->nsaddr_list[n].sin_family && EXT(statp).ext) { /* * - EXT(statp).ext->nsaddrs[n] holds an address that is larger * than struct sockaddr, and * - user code did not update statp->nsaddr_list[n]. */ return (struct sockaddr *)(void *)&EXT(statp).ext->nsaddrs[n]; } else { /* * - user code updated statp->nsaddr_list[n], or * - statp->nsaddr_list[n] has the same content as * EXT(statp).ext->nsaddrs[n]. */ return (struct sockaddr *)(void *)&statp->nsaddr_list[n]; } } static int get_timeout(const res_state statp, const int ns) { int timeout = (statp->retrans << ns); if (ns > 0) { timeout /= statp->nscount; } if (timeout <= 0) { timeout = 1; } if (DBG) { __libc_format_log(ANDROID_LOG_DEBUG, "libc", "using timeout of %d sec\n", timeout); } return timeout; } static int send_vc(res_state statp, const u_char *buf, int buflen, u_char *ans, int anssiz, int *terrno, int ns) { const HEADER *hp = (const HEADER *)(const void *)buf; HEADER *anhp = (HEADER *)(void *)ans; struct sockaddr *nsap; int nsaplen; int truncating, connreset, resplen, n; struct iovec iov[2]; u_short len; u_char *cp; void *tmp; if (DBG) { __libc_format_log(ANDROID_LOG_DEBUG, "libc", "using send_vc\n"); } nsap = get_nsaddr(statp, (size_t)ns); nsaplen = get_salen(nsap); connreset = 0; same_ns: truncating = 0; /* Are we still talking to whom we want to talk to? */ if (statp->_vcsock >= 0 && (statp->_flags & RES_F_VC) != 0) { struct sockaddr_storage peer; socklen_t size = sizeof peer; unsigned old_mark; socklen_t mark_size = sizeof(old_mark); if (getpeername(statp->_vcsock, (struct sockaddr *)(void *)&peer, &size) < 0 || !sock_eq((struct sockaddr *)(void *)&peer, nsap) || getsockopt(statp->_vcsock, SOL_SOCKET, SO_MARK, &old_mark, &mark_size) < 0 || old_mark != statp->_mark) { res_nclose(statp); statp->_flags &= ~RES_F_VC; } } if (statp->_vcsock < 0 || (statp->_flags & RES_F_VC) == 0) { if (statp->_vcsock >= 0) res_nclose(statp); statp->_vcsock = socket(nsap->sa_family, SOCK_STREAM | SOCK_CLOEXEC, 0); if (statp->_vcsock > highestFD) { res_nclose(statp); errno = ENOTSOCK; } if (statp->_vcsock < 0) { switch (errno) { case EPROTONOSUPPORT: #ifdef EPFNOSUPPORT case EPFNOSUPPORT: #endif case EAFNOSUPPORT: Perror(statp, stderr, "socket(vc)", errno); return (0); default: *terrno = errno; Perror(statp, stderr, "socket(vc)", errno); return (-1); } } if (statp->_mark != MARK_UNSET) { if (setsockopt(statp->_vcsock, SOL_SOCKET, SO_MARK, &statp->_mark, sizeof(statp->_mark)) < 0) { *terrno = errno; Perror(statp, stderr, "setsockopt", errno); return -1; } } errno = 0; if (random_bind(statp->_vcsock,nsap->sa_family) < 0) { *terrno = errno; Aerror(statp, stderr, "bind/vc", errno, nsap, nsaplen); res_nclose(statp); return (0); } if (connect_with_timeout(statp->_vcsock, nsap, (socklen_t)nsaplen, get_timeout(statp, ns)) < 0) { *terrno = errno; Aerror(statp, stderr, "connect/vc", errno, nsap, nsaplen); res_nclose(statp); return (0); } statp->_flags |= RES_F_VC; } /* * Send length & message */ ns_put16((u_short)buflen, (u_char*)(void *)&len); iov[0] = evConsIovec(&len, INT16SZ); DE_CONST(buf, tmp); iov[1] = evConsIovec(tmp, (size_t)buflen); if (writev(statp->_vcsock, iov, 2) != (INT16SZ + buflen)) { *terrno = errno; Perror(statp, stderr, "write failed", errno); res_nclose(statp); return (0); } /* * Receive length & response */ read_len: cp = ans; len = INT16SZ; while ((n = read(statp->_vcsock, (char *)cp, (size_t)len)) > 0) { cp += n; if ((len -= n) == 0) break; } if (n <= 0) { *terrno = errno; Perror(statp, stderr, "read failed", errno); res_nclose(statp); /* * A long running process might get its TCP * connection reset if the remote server was * restarted. Requery the server instead of * trying a new one. When there is only one * server, this means that a query might work * instead of failing. We only allow one reset * per query to prevent looping. */ if (*terrno == ECONNRESET && !connreset) { connreset = 1; res_nclose(statp); goto same_ns; } res_nclose(statp); return (0); } resplen = ns_get16(ans); if (resplen > anssiz) { Dprint(statp->options & RES_DEBUG, (stdout, ";; response truncated\n") ); truncating = 1; len = anssiz; } else len = resplen; if (len < HFIXEDSZ) { /* * Undersized message. */ Dprint(statp->options & RES_DEBUG, (stdout, ";; undersized: %d\n", len)); *terrno = EMSGSIZE; res_nclose(statp); return (0); } cp = ans; while (len != 0 && (n = read(statp->_vcsock, (char *)cp, (size_t)len)) > 0){ cp += n; len -= n; } if (n <= 0) { *terrno = errno; Perror(statp, stderr, "read(vc)", errno); res_nclose(statp); return (0); } if (truncating) { /* * Flush rest of answer so connection stays in synch. */ anhp->tc = 1; len = resplen - anssiz; while (len != 0) { char junk[PACKETSZ]; n = read(statp->_vcsock, junk, (len > sizeof junk) ? sizeof junk : len); if (n > 0) len -= n; else break; } } /* * If the calling applicating has bailed out of * a previous call and failed to arrange to have * the circuit closed or the server has got * itself confused, then drop the packet and * wait for the correct one. */ if (hp->id != anhp->id) { DprintQ((statp->options & RES_DEBUG) || (statp->pfcode & RES_PRF_REPLY), (stdout, ";; old answer (unexpected):\n"), ans, (resplen > anssiz) ? anssiz: resplen); goto read_len; } /* * All is well, or the error is fatal. Signal that the * next nameserver ought not be tried. */ return (resplen); } /* return -1 on error (errno set), 0 on success */ static int connect_with_timeout(int sock, const struct sockaddr *nsap, socklen_t salen, int sec) { int res, origflags; fd_set rset, wset; struct timespec now, timeout, finish; origflags = fcntl(sock, F_GETFL, 0); fcntl(sock, F_SETFL, origflags | O_NONBLOCK); res = __connect(sock, nsap, salen); if (res < 0 && errno != EINPROGRESS) { res = -1; goto done; } if (res != 0) { now = evNowTime(); timeout = evConsTime((long)sec, 0L); finish = evAddTime(now, timeout); if (DBG) { __libc_format_log(ANDROID_LOG_DEBUG, "libc", " %d send_vc\n", sock); } res = retrying_select(sock, &rset, &wset, &finish); if (res <= 0) { res = -1; } } done: fcntl(sock, F_SETFL, origflags); if (DBG) { __libc_format_log(ANDROID_LOG_DEBUG, "libc", " %d connect_with_timeout returning %d\n", sock, res); } return res; } static int retrying_select(const int sock, fd_set *readset, fd_set *writeset, const struct timespec *finish) { struct timespec now, timeout; int n, error; socklen_t len; retry: if (DBG) { __libc_format_log(ANDROID_LOG_DEBUG, "libc", " %d retying_select\n", sock); } now = evNowTime(); if (readset) { FD_ZERO(readset); FD_SET(sock, readset); } if (writeset) { FD_ZERO(writeset); FD_SET(sock, writeset); } if (evCmpTime(*finish, now) > 0) timeout = evSubTime(*finish, now); else timeout = evConsTime(0L, 0L); n = pselect(sock + 1, readset, writeset, NULL, &timeout, NULL); if (n == 0) { if (DBG) { __libc_format_log(ANDROID_LOG_DEBUG, " libc", " %d retrying_select timeout\n", sock); } errno = ETIMEDOUT; return 0; } if (n < 0) { if (errno == EINTR) goto retry; if (DBG) { __libc_format_log(ANDROID_LOG_DEBUG, "libc", " %d retrying_select got error %d\n",sock, n); } return n; } if ((readset && FD_ISSET(sock, readset)) || (writeset && FD_ISSET(sock, writeset))) { len = sizeof(error); if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len) < 0 || error) { errno = error; if (DBG) { __libc_format_log(ANDROID_LOG_DEBUG, "libc", " %d retrying_select dot error2 %d\n", sock, errno); } return -1; } } if (DBG) { __libc_format_log(ANDROID_LOG_DEBUG, "libc", " %d retrying_select returning %d\n",sock, n); } return n; } static int send_dg(res_state statp, const u_char *buf, int buflen, u_char *ans, int anssiz, int *terrno, int ns, int *v_circuit, int *gotsomewhere) { const HEADER *hp = (const HEADER *)(const void *)buf; HEADER *anhp = (HEADER *)(void *)ans; const struct sockaddr *nsap; int nsaplen; struct timespec now, timeout, finish; fd_set dsmask; struct sockaddr_storage from; socklen_t fromlen; int resplen, seconds, n, s; nsap = get_nsaddr(statp, (size_t)ns); nsaplen = get_salen(nsap); if (EXT(statp).nssocks[ns] == -1) { EXT(statp).nssocks[ns] = socket(nsap->sa_family, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (EXT(statp).nssocks[ns] > highestFD) { res_nclose(statp); errno = ENOTSOCK; } if (EXT(statp).nssocks[ns] < 0) { switch (errno) { case EPROTONOSUPPORT: #ifdef EPFNOSUPPORT case EPFNOSUPPORT: #endif case EAFNOSUPPORT: Perror(statp, stderr, "socket(dg)", errno); return (0); default: *terrno = errno; Perror(statp, stderr, "socket(dg)", errno); return (-1); } } if (statp->_mark != MARK_UNSET) { if (setsockopt(EXT(statp).nssocks[ns], SOL_SOCKET, SO_MARK, &(statp->_mark), sizeof(statp->_mark)) < 0) { res_nclose(statp); return -1; } } #ifndef CANNOT_CONNECT_DGRAM /* * On a 4.3BSD+ machine (client and server, * actually), sending to a nameserver datagram * port with no nameserver will cause an * ICMP port unreachable message to be returned. * If our datagram socket is "connected" to the * server, we get an ECONNREFUSED error on the next * socket operation, and select returns if the * error message is received. We can thus detect * the absence of a nameserver without timing out. */ if (random_bind(EXT(statp).nssocks[ns], nsap->sa_family) < 0) { Aerror(statp, stderr, "bind(dg)", errno, nsap, nsaplen); res_nclose(statp); return (0); } if (__connect(EXT(statp).nssocks[ns], nsap, (socklen_t)nsaplen) < 0) { Aerror(statp, stderr, "connect(dg)", errno, nsap, nsaplen); res_nclose(statp); return (0); } #endif /* !CANNOT_CONNECT_DGRAM */ Dprint(statp->options & RES_DEBUG, (stdout, ";; new DG socket\n")) } s = EXT(statp).nssocks[ns]; #ifndef CANNOT_CONNECT_DGRAM if (send(s, (const char*)buf, (size_t)buflen, 0) != buflen) { Perror(statp, stderr, "send", errno); res_nclose(statp); return (0); } #else /* !CANNOT_CONNECT_DGRAM */ if (sendto(s, (const char*)buf, buflen, 0, nsap, nsaplen) != buflen) { Aerror(statp, stderr, "sendto", errno, nsap, nsaplen); res_nclose(statp); return (0); } #endif /* !CANNOT_CONNECT_DGRAM */ /* * Wait for reply. */ seconds = get_timeout(statp, ns); now = evNowTime(); timeout = evConsTime((long)seconds, 0L); finish = evAddTime(now, timeout); retry: n = retrying_select(s, &dsmask, NULL, &finish); if (n == 0) { Dprint(statp->options & RES_DEBUG, (stdout, ";; timeout\n")); *gotsomewhere = 1; return (0); } if (n < 0) { Perror(statp, stderr, "select", errno); res_nclose(statp); return (0); } errno = 0; fromlen = sizeof(from); resplen = recvfrom(s, (char*)ans, (size_t)anssiz,0, (struct sockaddr *)(void *)&from, &fromlen); if (resplen <= 0) { Perror(statp, stderr, "recvfrom", errno); res_nclose(statp); return (0); } *gotsomewhere = 1; if (resplen < HFIXEDSZ) { /* * Undersized message. */ Dprint(statp->options & RES_DEBUG, (stdout, ";; undersized: %d\n", resplen)); *terrno = EMSGSIZE; res_nclose(statp); return (0); } if (hp->id != anhp->id) { /* * response from old query, ignore it. * XXX - potential security hazard could * be detected here. */ #ifdef ANDROID_CHANGES __libc_android_log_event_uid(BIONIC_EVENT_RESOLVER_OLD_RESPONSE); #endif DprintQ((statp->options & RES_DEBUG) || (statp->pfcode & RES_PRF_REPLY), (stdout, ";; old answer:\n"), ans, (resplen > anssiz) ? anssiz : resplen); goto retry; } if (!(statp->options & RES_INSECURE1) && !res_ourserver_p(statp, (struct sockaddr *)(void *)&from)) { /* * response from wrong server? ignore it. * XXX - potential security hazard could * be detected here. */ #ifdef ANDROID_CHANGES __libc_android_log_event_uid(BIONIC_EVENT_RESOLVER_WRONG_SERVER); #endif DprintQ((statp->options & RES_DEBUG) || (statp->pfcode & RES_PRF_REPLY), (stdout, ";; not our server:\n"), ans, (resplen > anssiz) ? anssiz : resplen); goto retry; } #ifdef RES_USE_EDNS0 if (anhp->rcode == FORMERR && (statp->options & RES_USE_EDNS0) != 0U) { /* * Do not retry if the server do not understand EDNS0. * The case has to be captured here, as FORMERR packet do not * carry query section, hence res_queriesmatch() returns 0. */ DprintQ(statp->options & RES_DEBUG, (stdout, "server rejected query with EDNS0:\n"), ans, (resplen > anssiz) ? anssiz : resplen); /* record the error */ statp->_flags |= RES_F_EDNS0ERR; res_nclose(statp); return (0); } #endif if (!(statp->options & RES_INSECURE2) && !res_queriesmatch(buf, buf + buflen, ans, ans + anssiz)) { /* * response contains wrong query? ignore it. * XXX - potential security hazard could * be detected here. */ #ifdef ANDROID_CHANGES __libc_android_log_event_uid(BIONIC_EVENT_RESOLVER_WRONG_QUERY); #endif DprintQ((statp->options & RES_DEBUG) || (statp->pfcode & RES_PRF_REPLY), (stdout, ";; wrong query name:\n"), ans, (resplen > anssiz) ? anssiz : resplen); goto retry;; } if (anhp->rcode == SERVFAIL || anhp->rcode == NOTIMP || anhp->rcode == REFUSED) { DprintQ(statp->options & RES_DEBUG, (stdout, "server rejected query:\n"), ans, (resplen > anssiz) ? anssiz : resplen); res_nclose(statp); /* don't retry if called from dig */ if (!statp->pfcode) return (0); } if (!(statp->options & RES_IGNTC) && anhp->tc) { /* * To get the rest of answer, * use TCP with same server. */ Dprint(statp->options & RES_DEBUG, (stdout, ";; truncated answer\n")); *v_circuit = 1; res_nclose(statp); return (1); } /* * All is well, or the error is fatal. Signal that the * next nameserver ought not be tried. */ return (resplen); } static void Aerror(const res_state statp, FILE *file, const char *string, int error, const struct sockaddr *address, int alen) { int save = errno; char hbuf[NI_MAXHOST]; char sbuf[NI_MAXSERV]; if ((statp->options & RES_DEBUG) != 0U) { if (getnameinfo(address, (socklen_t)alen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), niflags)) { strncpy(hbuf, "?", sizeof(hbuf) - 1); hbuf[sizeof(hbuf) - 1] = '\0'; strncpy(sbuf, "?", sizeof(sbuf) - 1); sbuf[sizeof(sbuf) - 1] = '\0'; } fprintf(file, "res_send: %s ([%s].%s): %s\n", string, hbuf, sbuf, strerror(error)); } errno = save; } static void Perror(const res_state statp, FILE *file, const char *string, int error) { int save = errno; if ((statp->options & RES_DEBUG) != 0U) fprintf(file, "res_send: %s: %s\n", string, strerror(error)); errno = save; } static int sock_eq(struct sockaddr *a, struct sockaddr *b) { struct sockaddr_in *a4, *b4; struct sockaddr_in6 *a6, *b6; if (a->sa_family != b->sa_family) return 0; switch (a->sa_family) { case AF_INET: a4 = (struct sockaddr_in *)(void *)a; b4 = (struct sockaddr_in *)(void *)b; return a4->sin_port == b4->sin_port && a4->sin_addr.s_addr == b4->sin_addr.s_addr; case AF_INET6: a6 = (struct sockaddr_in6 *)(void *)a; b6 = (struct sockaddr_in6 *)(void *)b; return a6->sin6_port == b6->sin6_port && #ifdef HAVE_SIN6_SCOPE_ID a6->sin6_scope_id == b6->sin6_scope_id && #endif IN6_ARE_ADDR_EQUAL(&a6->sin6_addr, &b6->sin6_addr); default: return 0; } } #ifdef NEED_PSELECT /* XXX needs to move to the porting library. */ static int pselect(int nfds, void *rfds, void *wfds, void *efds, struct timespec *tsp, const sigset_t *sigmask) { struct timeval tv, *tvp; sigset_t sigs; int n; if (tsp) { tvp = &tv; tv = evTimeVal(*tsp); } else tvp = NULL; if (sigmask) sigprocmask(SIG_SETMASK, sigmask, &sigs); n = select(nfds, rfds, wfds, efds, tvp); if (sigmask) sigprocmask(SIG_SETMASK, &sigs, NULL); if (tsp) *tsp = evTimeSpec(tv); return (n); } #endif