| /* $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_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 "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 |
| struct resolv_cache* 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 |
| // get the cache associated with the interface |
| cache = __get_res_cache(statp->iface); |
| if (cache != NULL) { |
| int anslen = 0; |
| cache_status = _resolv_cache_lookup( |
| cache, buf, buflen, |
| ans, anssiz, &anslen); |
| |
| if (cache_status == RESOLV_CACHE_FOUND) { |
| return anslen; |
| } else { |
| // had a cache miss for a known interface, so populate the thread private |
| // data so the normal resolve path can do its thing |
| _resolv_populate_res_for_iface(statp); |
| } |
| } |
| |
| if (statp->nscount == 0) { |
| 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=%d, %d\n",time(NULL), time(NULL)%2); |
| } |
| 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(cache, 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(cache, buf, buflen); |
| #endif |
| |
| return (-1); |
| fail: |
| #if USE_RESOLV_CACHE |
| _resolv_cache_query_failed(cache, 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; |
| |
| if (getpeername(statp->_vcsock, |
| (struct sockaddr *)(void *)&peer, &size) < 0 || |
| !sock_eq((struct sockaddr *)(void *)&peer, nsap)) { |
| 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, 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); |
| } |
| } |
| 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 %s\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 for %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, 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); |
| } |
| } |
| #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]; |
| |
| alen = alen; |
| |
| 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 |