-
/*
- * Copyright (c) 1985 Regents of the University of California.
- * All rights reserved. The Berkeley software License Agreement
- * specifies the terms and conditions for redistribution.
+ * Copyright (c) 1985, 1989 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by the University of California, Berkeley. The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#if defined(LIBC_SCCS) && !defined(lint)
-static char sccsid[] = "@(#)res_send.c 6.16 (Berkeley) %G%";
-#endif LIBC_SCCS and not lint
+static char sccsid[] = "@(#)res_send.c 6.24 (Berkeley) %G%";
+#endif /* LIBC_SCCS and not lint */
/*
* Send query to name server and wait for reply.
extern int errno;
static int s = -1; /* socket used for communications */
+static struct sockaddr no_addr;
#ifndef FD_SET
#define FD_ZERO(p) bzero((char *)(p), sizeof(*(p)))
#endif
-#define KEEPOPEN (RES_USEVC|RES_STAYOPEN)
-
res_send(buf, buflen, answer, anslen)
char *buf;
int buflen;
int anslen;
{
register int n;
- int retry, v_circuit, resplen, ns;
- int gotsomewhere = 0;
+ int try, v_circuit, resplen, ns;
+ int gotsomewhere = 0, connected = 0;
+ int connreset = 0;
u_short id, len;
char *cp;
fd_set dsmask;
HEADER *anhp = (HEADER *) answer;
struct iovec iov[2];
int terrno = ETIMEDOUT;
+ char junk[512];
#ifdef DEBUG
if (_res.options & RES_DEBUG) {
/*
* Send request, RETRY times, or until successful
*/
- for (retry = _res.retry; retry > 0; retry--) {
+ for (try = 0; try < _res.retry; try++) {
for (ns = 0; ns < _res.nscount; ns++) {
#ifdef DEBUG
if (_res.options & RES_DEBUG)
printf("Querying server (# %d) address = %s\n", ns+1,
- inet_ntoa(_res.nsaddr_list[ns].sin_addr.s_addr));
+ inet_ntoa(_res.nsaddr_list[ns].sin_addr));
#endif DEBUG
+ usevc:
if (v_circuit) {
+ int truncated = 0;
+
/*
- * Use virtual circuit.
+ * Use virtual circuit;
+ * at most one attempt per server.
*/
+ try = _res.retry;
if (s < 0) {
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) {
terrno = errno;
#ifdef DEBUG
if (_res.options & RES_DEBUG)
- perror("socket failed");
+ perror("socket (vc) failed");
#endif DEBUG
continue;
}
#endif DEBUG
(void) close(s);
s = -1;
+ /*
+ * 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;
+ ns--;
+ }
continue;
}
cp = answer;
- resplen = len = ntohs(*(u_short *)cp);
+ if ((resplen = ntohs(*(u_short *)cp)) > anslen) {
+#ifdef DEBUG
+ if (_res.options & RES_DEBUG)
+ fprintf(stderr, "response truncated\n");
+#endif DEBUG
+ len = anslen;
+ truncated = 1;
+ } else
+ len = resplen;
while (len != 0 &&
(n = read(s, (char *)cp, (int)len)) > 0) {
cp += n;
s = -1;
continue;
}
+ if (truncated) {
+ /*
+ * Flush rest of answer
+ * so connection stays in synch.
+ */
+ anhp->tc = 1;
+ len = resplen - anslen;
+ while (len != 0) {
+ n = (len > sizeof(junk) ?
+ sizeof(junk) : len);
+ if ((n = read(s, junk, n)) > 0)
+ len -= n;
+ else
+ break;
+ }
+ }
} else {
/*
* Use datagrams.
*/
- if (s < 0)
+ if (s < 0) {
s = socket(AF_INET, SOCK_DGRAM, 0);
+ if (s < 0) {
+ terrno = errno;
+#ifdef DEBUG
+ if (_res.options & RES_DEBUG)
+ perror("socket (dg) failed");
+#endif DEBUG
+ continue;
+ }
+ }
#if BSD >= 43
- if (_res.nscount == 1) {
+ /*
+ * I'm tired of answering this question, so:
+ * 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 we have sent queries to at least two servers,
+ * however, we don't want to remain connected,
+ * as we wish to receive answers from the first
+ * server to respond.
+ */
+ if (_res.nscount == 1 || (try == 0 && ns == 0)) {
/*
- * Connect/Send is detrimental if you
- * are going to poll more than one server
+ * Don't use connect if we might
+ * still receive a response
+ * from another server.
*/
- if (connect(s, &_res.nsaddr_list[ns],
- sizeof(struct sockaddr)) < 0 ||
- send(s, buf, buflen, 0) != buflen) {
+ if (connected == 0) {
+ if (connect(s, &_res.nsaddr_list[ns],
+ sizeof(struct sockaddr)) < 0) {
+#ifdef DEBUG
+ if (_res.options & RES_DEBUG)
+ perror("connect");
+#endif DEBUG
+ continue;
+ }
+ connected = 1;
+ }
+ if (send(s, buf, buflen, 0) != buflen) {
#ifdef DEBUG
if (_res.options & RES_DEBUG)
- perror("connect");
+ perror("send");
#endif DEBUG
continue;
}
- } else
+ } else {
+ /*
+ * Disconnect if we want to listen
+ * for responses from more than one server.
+ */
+ if (connected) {
+ (void) connect(s, &no_addr,
+ sizeof(no_addr));
+ connected = 0;
+ }
#endif BSD
- if (sendto(s, buf, buflen, 0, &_res.nsaddr_list[ns],
- sizeof(struct sockaddr)) != buflen) {
+ if (sendto(s, buf, buflen, 0,
+ &_res.nsaddr_list[ns],
+ sizeof(struct sockaddr)) != buflen) {
#ifdef DEBUG
- if (_res.options & RES_DEBUG)
- perror("sendto");
+ if (_res.options & RES_DEBUG)
+ perror("sendto");
#endif DEBUG
- continue;
+ continue;
+ }
+#if BSD >= 43
}
+#endif
/*
* Wait for reply
*/
- timeout.tv_sec = (_res.retrans << (_res.retry - retry))
- / _res.nscount;
+ timeout.tv_sec = (_res.retrans << try);
+ if (try > 0)
+ timeout.tv_sec /= _res.nscount;
if (timeout.tv_sec <= 0)
timeout.tv_sec = 1;
timeout.tv_usec = 0;
if (_res.options & RES_DEBUG)
printf("timeout\n");
#endif DEBUG
+#if BSD >= 43
gotsomewhere = 1;
+#endif
continue;
}
if ((resplen = recv(s, answer, anslen, 0)) <= 0) {
}
if (!(_res.options & RES_IGNTC) && anhp->tc) {
/*
- * get rest of answer
+ * get rest of answer;
+ * use TCP with same server.
*/
#ifdef DEBUG
if (_res.options & RES_DEBUG)
#endif DEBUG
(void) close(s);
s = -1;
- /*
- * retry decremented on continue
- * to desired starting value
- */
- retry = _res.retry + 1;
v_circuit = 1;
- continue;
+ goto usevc;
}
}
#ifdef DEBUG
}
#endif DEBUG
/*
- * We are going to assume that the first server is preferred
- * over the rest (i.e. it is on the local machine) and only
- * keep that one open.
+ * If using virtual circuits, we assume that the first server
+ * is preferred * over the rest (i.e. it is on the local
+ * machine) and only keep that one open.
+ * If we have temporarily opened a virtual circuit,
+ * or if we haven't been asked to keep a socket open,
+ * close the socket.
*/
- if ((_res.options & KEEPOPEN) == KEEPOPEN && ns == 0) {
- return (resplen);
- } else {
+ if ((v_circuit &&
+ ((_res.options & RES_USEVC) == 0 || ns != 0)) ||
+ (_res.options & RES_STAYOPEN) == 0) {
(void) close(s);
s = -1;
- return (resplen);
}
+ return (resplen);
}
}
if (s >= 0) {
}
if (v_circuit == 0)
if (gotsomewhere == 0)
- errno = ECONNREFUSED;
+ errno = ECONNREFUSED; /* no nameservers found */
else
- errno = ETIMEDOUT;
+ errno = ETIMEDOUT; /* no answer obtained */
else
errno = terrno;
return (-1);