/*
* 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: (1) source distributions retain this entire copyright
* notice and comment, and (2) distributions including binaries display
* the following acknowledgement: ``This product includes software
* developed by the University of California, Berkeley and its contributors''
* in the documentation or other materials provided with the distribution
* and in all advertising materials mentioning features or use of this
* software. 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 ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#ifndef lint
static char Version[] = "@(#)send.c e07@nikhef.nl (Eric Wassenaar) 991331";
#endif
#include "host.h"
char *dbprefix = DBPREFIX; /* prefix for debug messages to stdout */
ipaddr_t srcaddr = INADDR_ANY; /* explicit source ip address */
int minport = 0; /* first source port in explicit range */
int maxport = 0; /* last source port in explicit range */
static int timeout; /* connection read timeout */
#if !defined(NO_CONNECTED_DGRAM)
static bool connected = TRUE; /* we can use connected datagram sockets */
#else
static bool connected = FALSE; /* connected datagram sockets unavailable */
#endif
static struct sockaddr_in from; /* address of inbound packet */
static struct sockaddr *from_sa = (struct sockaddr *)&from;
#ifdef HOST_RES_SEND
/*
** RES_SEND -- Send nameserver query and retrieve answer
** -----------------------------------------------------
**
** Returns:
** Length of (untruncated) nameserver answer, if obtained.
** -1 if an error occurred (errno set appropriately).
**
** This is a simplified version of the BIND 4.8.3 res_send().
** - Always use connected datagrams to get proper error messages.
** - Do not only return ETIMEDOUT or ECONNREFUSED in datagram mode,
** but also host or network unreachable status if appropriate.
** - Never leave a connection open after we got an answer.
** - No special ECONNRESET handling when using virtual circuits.
**
** Note that this private version of res_send() is not only called
** directly by 'host' but also indirectly by gethostbyname() or by
** gethostbyaddr() via their resolver interface routines.
*/
int
res_send(query, querylen, answer, anslen)
input CONST qbuf_t *query; /* location of formatted query buffer */
input int querylen; /* length of query buffer */
output qbuf_t *answer; /* location of buffer to store answer */
input int anslen; /* maximum size of answer buffer */
{
HEADER *bp = (HEADER *)answer;
struct sockaddr_in *addr; /* the server address to connect to */
int v_circuit; /* virtual circuit or datagram switch */
int servfail[MAXNS]; /* saved failure codes per nameserver */
register int try, ns;
register int n;
/* make sure resolver has been initialized */
if (!bitset(RES_INIT, _res.options) && res_init() == -1)
return(-1);
if (bitset(RES_DEBUG, _res.options))
{
printf("%sres_send()\n", dbprefix);
pr_query(query, querylen, stdout);
}
/* use virtual circuit if requested or if necessary */
v_circuit = bitset(RES_USEVC, _res.options) || (querylen > PACKETSZ);
/* reset server failure codes */
for (ns = 0; ns < MAXNS; ns++)
servfail[ns] = 0;
/*
* Do _res.retry attempts for each of the _res.nscount addresses.
* Upon failure, the current server will be marked bad if we got
* an error condition which makes it unlikely that we will succeed
* the next time we try this server.
* Internal operating system failures, such as temporary lack of
* resources, do not fall in that category.
*/
for (try = 0; try < _res.retry; try++)
{
for (ns = 0; ns < _res.nscount; ns++)
{
/* skip retry if server failed permanently */
if (servfail[ns])
continue;
/* fetch server address */
addr = &nslist(ns);
retry:
if (bitset(RES_DEBUG, _res.options))
{
printf("%sQuerying server (# %d) %s address = %s\n",
dbprefix, ns+1, v_circuit ? "tcp" : "udp",
inet_ntoa(addr->sin_addr));
}
if (v_circuit)
{
/* at most one attempt per server */
try = _res.retry;
/* connect via virtual circuit */
n = send_stream(addr, query, querylen, answer, anslen);
}
else
{
/* set datagram read timeout for recv_sock() */
timeout = (_res.retrans << try);
if (try > 0)
timeout /= _res.nscount;
if (timeout <= 0)
timeout = 1;
/* connect via datagram */
n = send_dgram(addr, query, querylen, answer, anslen);
/* check truncation; use v_circuit with same server */
if ((n > 0) && bp->tc)
{
if (bitset(RES_DEBUG, _res.options))
{
printf("%struncated answer, %d bytes\n",
dbprefix, n);
}
if (!bitset(RES_IGNTC, _res.options))
{
v_circuit = 1;
goto retry;
}
}
}
if (n <= 0)
{
switch (errno)
{
case ECONNREFUSED:
case ENETDOWN:
case ENETUNREACH:
case EHOSTDOWN:
case EHOSTUNREACH:
servfail[ns] = errno;
break;
}
/* try next server */
continue;
}
if (bitset(RES_DEBUG, _res.options))
{
printf("%sgot answer, %d bytes:\n", dbprefix, n);
pr_query(answer, (n > anslen) ? anslen : n, stdout);
}
/* we have an answer; clear possible error condition */
seterrno(0);
return(n);
}
}
/* no answer obtained; return error condition */
return(-1);
}
/*
** _RES_CLOSE -- Close an open stream or dgram connection
** ------------------------------------------------------
**
** Returns:
** None.
*/
static int srvsock = -1; /* socket descriptor */
void
_res_close()
{
int save_errno = errno; /* preserve state */
/* close the connection if open */
if (srvsock >= 0)
{
(void) close(srvsock);
srvsock = -1;
}
/* restore state */
seterrno(save_errno);
}
/*
** CHECK_FROM -- Make sure the response comes from a known server
** --------------------------------------------------------------
**
** Returns:
** Nonzero if the source address is known.
** Zero otherwise.
*/
static bool
check_from()
{
struct sockaddr_in *addr;
register int ns;
for (ns = 0; ns < _res.nscount; ns++)
{
/* fetch server address */
addr = &nslist(ns);
if (from.sin_family != addr->sin_family)
continue;
if (from.sin_port != addr->sin_port)
continue;
/* this allows a reply from any responding server */
if (addr->sin_addr.s_addr == INADDR_ANY)
return(TRUE);
if (from.sin_addr.s_addr == addr->sin_addr.s_addr)
return(TRUE);
}
/* matches none of the known addresses */
return(FALSE);
}
/*
** SEND_STREAM -- Query nameserver via virtual circuit
** ---------------------------------------------------
**
** Returns:
** Length of (untruncated) nameserver answer, if obtained.
** -1 if an error occurred.
**
** A new socket is allocated for each call, and it is never
** left open. Checking the packet id is rather redundant.
**
** Note that connect() is the call that is allowed to fail
** under normal circumstances. All other failures generate
** an unconditional error message.
** Note that truncation is handled within _res_read().
*/
static int
send_stream(addr, query, querylen, answer, anslen)
input struct sockaddr_in *addr; /* the server address to connect to */
input qbuf_t *query; /* location of formatted query buffer */
input int querylen; /* length of query buffer */
output qbuf_t *answer; /* location of buffer to store answer */
input int anslen; /* maximum size of answer buffer */
{
char *host = NULL; /* name of server is unknown */
HEADER *qp = (HEADER *)query;
HEADER *bp = (HEADER *)answer;
register int n;
/*
* Setup a virtual circuit connection.
*/
srvsock = _res_socket(AF_INET, SOCK_STREAM, 0);
if (srvsock < 0)
{
_res_perror(addr, host, "socket");
return(-1);
}
if (_res_connect(srvsock, addr, sizeof(*addr)) < 0)
{
if (bitset(RES_DEBUG, _res.options))
_res_perror(addr, host, "connect");
_res_close();
return(-1);
}
if (bitset(RES_DEBUG, _res.options))
{
printf("%sconnected to %s\n",
dbprefix, inet_ntoa(addr->sin_addr));
}
/*
* Send the query buffer.
*/
if (_res_write(srvsock, addr, host, (char *)query, querylen) < 0)
{
_res_close();
return(-1);
}
/*
* Read the answer buffer.
*/
wait:
n = _res_read(srvsock, addr, host, (char *)answer, anslen);
if (n <= 0)
{
_res_close();
return(-1);
}
/*
* Make sure it is the proper response by checking the packet id.
*/
if (qp->id != bp->id)
{
if (bitset(RES_DEBUG, _res.options))
{
printf("%sunexpected answer:\n", dbprefix);
pr_query(answer, (n > anslen) ? anslen : n, stdout);
}
goto wait;
}
/*
* Never leave the socket open.
*/
_res_close();
return(n);
}
/*
** SEND_DGRAM -- Query nameserver via datagram
** -------------------------------------------
**
** Returns:
** Length of nameserver answer, if obtained.
** -1 if an error occurred.
**
** Inputs:
** The global variable ``timeout'' should have been
** set with the desired timeout value in seconds.
**
** Sending to a nameserver datagram port with no nameserver running
** will cause an ICMP port unreachable message to be returned. If the
** socket is connected, we get an ECONNREFUSED error on the next socket
** operation, and select returns if the error message is received.
** Also, we get ENETUNREACH or EHOSTUNREACH errors if appropriate.
** We thus get a proper error status before timing out.
** This method usually works only if BSD >= 43.
**
** Note that send() and recvfrom() are now the calls that are allowed
** to fail under normal circumstances.
*/
static int
send_dgram(addr, query, querylen, answer, anslen)
input struct sockaddr_in *addr; /* the server address to connect to */
input qbuf_t *query; /* location of formatted query buffer */
input int querylen; /* length of query buffer */
output qbuf_t *answer; /* location of buffer to store answer */
input int anslen; /* maximum size of answer buffer */
{
char *host = NULL; /* name of server is unknown */
HEADER *qp = (HEADER *)query;
HEADER *bp = (HEADER *)answer;
register int n;
/*
* Setup a connected (if possible) datagram socket.
*/
srvsock = _res_socket(AF_INET, SOCK_DGRAM, 0);
if (srvsock < 0)
{
_res_perror(addr, host, "socket");
return(-1);
}
if (connected)
{
if (connect(srvsock, (struct sockaddr *)addr, sizeof(*addr)) < 0)
{
_res_perror(addr, host, "connect");
_res_close();
return(-1);
}
}
/*
* Send the query buffer.
*/
if (connected)
n = send(srvsock, (char *)query, querylen, 0);
else
n = sendto(srvsock, (char *)query, querylen, 0,
(struct sockaddr *)addr, sizeof(*addr));
if (n != querylen)
{
if (bitset(RES_DEBUG, _res.options))
_res_perror(addr, host, "send");
_res_close();
return(-1);
}
/*
* Wait for the arrival of a reply, timeout, or error message.
*/
wait:
n = recv_sock(srvsock, (char *)answer, anslen);
if (n <= 0)
{
if (bitset(RES_DEBUG, _res.options))
_res_perror(addr, host, "recvfrom");
_res_close();
return(-1);
}
/*
* Make sure it is the proper response by checking the packet id.
*/
if (qp->id != bp->id)
{
if (bitset(RES_DEBUG, _res.options))
{
printf("%sold answer:\n", dbprefix);
pr_query(answer, (n > anslen) ? anslen : n, stdout);
}
goto wait;
}
/*
* Make sure it comes from a known server.
*/
if (!check_from())
{
if (bitset(RES_DEBUG, _res.options))
{
printf("%sunknown server %s:\n",
dbprefix, inet_ntoa(from.sin_addr));
pr_query(answer, (n > anslen) ? anslen : n, stdout);
}
goto wait;
}
/*
* Never leave the socket open.
*/
_res_close();
return(n);
}
#endif /*HOST_RES_SEND*/
/*
** _RES_SOCKET -- Obtain a socket and set additional parameters
** ------------------------------------------------------------
**
** Returns:
** socket descriptor if successfully obtained.
** -1 in case of failure.
**
** In special circumstances it may be necessary to assign
** an explicit port number to the client communication socket,
** e.g. when we are behind a packet filtering firewall that
** only allows incoming traffic with port numbers in a certain
** specific range.
**
** In the case of a stream (tcp) socket, we could have set
** the SO_REUSEADDR socket option, but this has side-effects.
** Therefore a single explicit tcp port cannot be used.
**
** An explicit source IP address may be necessary in case of
** multi-homed hosts with asymmetric routing policy.
*/
int
_res_socket(family, type, protocol)
input int family;
input int type;
input int protocol;
{
struct sockaddr_in sin;
register int port;
int sock;
/* try to obtain the desired socket */
sock = socket(family, type, protocol);
if (sock < 0)
return(-1);
/* set an explicit source address/port if so requested */
for (port = minport; port > 0 || srcaddr != INADDR_ANY; port++)
{
/* setup source address */
bzero((char *)&sin, sizeof(sin));
sin.sin_family = family;
sin.sin_addr.s_addr = srcaddr;
sin.sin_port = htons((u_short)port);
if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0)
{
int save_errno = errno;
if (port > 0 && errno == EADDRINUSE)
{
/* save_errno = EAGAIN; */
if (port < maxport)
continue;
}
/* bad source address, or no free port numbers */
(void) close(sock);
seterrno(save_errno);
return(-1);
}
if (bitset(RES_DEBUG, _res.options))
{
if (srcaddr == INADDR_ANY)
printf("%susing source port %d\n",
dbprefix, port);
else if (port == 0)
printf("%susing source address %s\n",
dbprefix, inet_ntoa(sin.sin_addr));
else
printf("%susing source address %s port %d\n",
dbprefix, inet_ntoa(sin.sin_addr), port);
}
/* socket with well-defined source address/port */
return(sock);
}
/* socket with random source address/port */
return(sock);
}
/*
** _RES_BLOCKING -- Use blocking or non-blocking socket I/O
** --------------------------------------------------------
**
** Returns:
** 0 if new mode was successfully set.
** -1 in case of failure.
**
** Under obscure circumstances when reading input from the socket,
** select() may indicate that data is available, whereas in practice
** there isn't any, and the subsequent recvfrom() will hang forever.
** This condition can be detected by reading in non-blocking mode,
** and restarting the select() if appropriate.
*/
int
_res_blocking(sock, blocking)
input int sock;
input bool blocking; /* indicate blocking or not */
{
int flags;
register int n;
#if !defined(WINNT)
/* fetch current settings */
n = fcntl(sock, F_GETFL, 0);
flags = (n == -1) ? 0 : n;
/* update settings appropriately */
flags = blocking ? (flags & ~O_NDELAY) : (flags | O_NDELAY);
n = fcntl(sock, F_SETFL, flags);
#else /*WINNT*/
/* just set the desired mode */
flags = blocking ? 0 : 1;
n = ioctlsocket(sock, FIONBIO, (u_long *)&flags);
#endif /*WINNT*/
return(n);
}
/*
** _RES_CONNECT -- Connect to a stream (virtual circuit) socket
** ------------------------------------------------------------
**
** Returns:
** 0 if successfully connected.
** -1 in case of failure or timeout.
**
** Note that we use _res.retrans to override the default
** connect timeout value.
*/
static jmp_buf timer_buf;
static sigtype_t
/*ARGSUSED*/
timer(sig)
int sig;
{
longjmp(timer_buf, 1);
/*NOTREACHED*/
}
int
_res_connect(sock, addr, addrlen)
input int sock;
input struct sockaddr_in *addr; /* the server address to connect to */
input int addrlen;
{
if (setjmp(timer_buf) != 0)
{
seterrno(ETIMEDOUT);
setalarm(0);
return(-1);
}
setsignal(SIGALRM, timer);
setalarm(_res.retrans);
if (connect(sock, (struct sockaddr *)addr, addrlen) < 0)
{
if (errno == EINTR)
seterrno(ETIMEDOUT);
setalarm(0);
return(-1);
}
setalarm(0);
return(0);
}
/*
** _RES_WRITE -- Write the query buffer via a stream socket
** --------------------------------------------------------
**
** Returns:
** Length of buffer if successfully transmitted.
** -1 in case of failure (error message is issued).
**
** The query is sent in two steps: first a single word with
** the length of the buffer, followed by the buffer itself.
*/
int
_res_write(sock, addr, host, buf, bufsize)
input int sock;
input struct sockaddr_in *addr; /* the server address to connect to */
input char *host; /* name of server to connect to */
input char *buf; /* location of formatted query buffer */
input int bufsize; /* length of query buffer */
{
u_short len;
/*
* Protect against remote peer prematurely closing the connection.
*/
/* setsignal(SIGPIPE, SIG_IGN); done in main() */
/*
* Write the length of the query buffer.
*/
/* len = htons((u_short)bufsize); */
putshort((u_short)bufsize, (u_char *)&len);
if (send(sock, (char *)&len, INT16SZ, 0) != INT16SZ)
{
_res_perror(addr, host, "write query length");
return(-1);
}
/*
* Write the query buffer itself.
*/
if (send(sock, buf, bufsize, 0) != bufsize)
{
_res_perror(addr, host, "write query");
return(-1);
}
/*
* Use non-blocking I/O to read the answer.
*/
(void) _res_blocking(sock, FALSE);
return(bufsize);
}
/*
** _RES_READ -- Read the answer buffer via a stream socket
** -------------------------------------------------------
**
** Returns:
** Length of (untruncated) answer if successfully received.
** -1 in case of failure (error message is issued).
**
** The answer is read in two steps: first a single word which
** gives the length of the buffer, followed by the buffer itself.
** If the answer is too long to fit into the supplied buffer,
** only the portion that fits will be stored, the residu will be
** flushed, and the truncation flag will be set.
**
** Note. The returned length is that of the *un*truncated answer,
** however, and not the amount of data that is actually available.
** This may give the caller a hint about new buffer reallocation.
*/
int
_res_read(sock, addr, host, buf, bufsize)
input int sock;
input struct sockaddr_in *addr; /* the server address to connect to */
input char *host; /* name of server to connect to */
output char *buf; /* location of buffer to store answer */
input int bufsize; /* maximum size of answer buffer */
{
u_short len;
char *buffer;
int buflen;
int reslen;
register int n;
/* set stream timeout for recv_sock() */
timeout = READTIMEOUT;
/*
* Read the length of answer buffer.
*/
buffer = (char *)&len;
buflen = INT16SZ;
while (buflen > 0 && (n = recv_sock(sock, buffer, buflen)) > 0)
{
buffer += n;
buflen -= n;
}
if (buflen != 0)
{
_res_perror(addr, host, "read answer length");
return(-1);
}
/*
* Terminate if length is zero.
*/
/* len = ntohs(len); */
len = _getshort((u_char *)&len);
if (len == 0)
return(0);
/*
* Check for truncation.
* Do not chop the returned length in case of buffer overflow.
*/
reslen = 0;
if ((int)len > bufsize)
{
reslen = len - bufsize;
/* len = bufsize; */
}
/*
* Read the answer buffer itself.
* Truncate the answer is the supplied buffer is not big enough.
*/
buffer = buf;
buflen = (reslen > 0) ? bufsize : len;
while (buflen > 0 && (n = recv_sock(sock, buffer, buflen)) > 0)
{
buffer += n;
buflen -= n;
}
if (buflen != 0)
{
_res_perror(addr, host, "read answer");
return(-1);
}
/*
* Discard the residu to keep connection in sync.
*/
if (reslen > 0)
{
HEADER *bp = (HEADER *)buf;
char resbuf[PACKETSZ];
buffer = resbuf;
buflen = (reslen < sizeof(resbuf)) ? reslen : sizeof(resbuf);
while (reslen > 0 && (n = recv_sock(sock, buffer, buflen)) > 0)
{
reslen -= n;
buflen = (reslen < sizeof(resbuf)) ? reslen : sizeof(resbuf);
}
if (reslen != 0)
{
_res_perror(addr, host, "read residu");
return(-1);
}
if (bitset(RES_DEBUG, _res.options))
{
printf("%sresponse truncated to %d bytes\n",
dbprefix, bufsize);
}
/* set truncation flag */
bp->tc = 1;
}
return(len);
}
/*
** RECV_SOCK -- Read from stream or datagram socket with timeout
** -------------------------------------------------------------
**
** Returns:
** Length of buffer if successfully received.
** -1 in case of failure or timeout.
** Inputs:
** The global variable ``timeout'' should have been
** set with the desired timeout value in seconds.
** Outputs:
** Sets ``from'' to the address of the packet sender.
*/
static int
recv_sock(sock, buffer, buflen)
input int sock;
output char *buffer; /* current buffer address */
input int buflen; /* remaining buffer size */
{
fd_set fds;
struct timeval wait;
int fromlen;
register int n;
wait.tv_sec = timeout;
wait.tv_usec = 0;
rewait:
/* FD_ZERO(&fds); */
bzero((char *)&fds, sizeof(fds));
FD_SET(sock, &fds);
/* wait for the arrival of data, or timeout */
n = select(FD_SETSIZE, &fds, (fd_set *)NULL, (fd_set *)NULL, &wait);
if (n < 0 && errno == EINTR)
goto rewait;
if (n == 0)
seterrno(ETIMEDOUT);
if (n <= 0)
return(-1);
reread:
/* fake an error if nothing was actually read */
fromlen = sizeof(from);
n = recvfrom(sock, buffer, buflen, 0, from_sa, &fromlen);
if (n < 0 && errno == EINTR)
goto reread;
if (n < 0 && errno == EWOULDBLOCK)
goto rewait;
if (n == 0)
seterrno(ECONNRESET);
return(n);
}
/*
* Alternative version for systems with broken networking code.
*
* The select() system call may fail on the solaris 2.4 platform
* without appropriate patches. However, these patches are reported
* to break client NFS.
*
* This version uses an alarm() instead of select(). This introduces
* additional system call overhead.
* Note that we cannot use non-blocking I/O in this mode.
*/
#ifdef BROKEN_SELECT
static int
recv_sock(sock, buffer, buflen)
input int sock;
output char *buffer; /* current buffer address */
input int buflen; /* remaining buffer size */
{
int fromlen;
register int n;
if (setjmp(timer_buf) != 0)
{
seterrno(ETIMEDOUT);
setalarm(0);
return(-1);
}
setsignal(SIGALRM, timer);
setalarm(timeout);
reread:
/* fake an error if nothing was actually read */
fromlen = sizeof(from);
n = recvfrom(sock, buffer, buflen, 0, from_sa, &fromlen);
if (n < 0 && errno == EINTR)
goto reread;
if (n == 0)
seterrno(ECONNRESET);
setalarm(0);
return(n);
}
#endif /*BROKEN_SELECT*/
/*
** _RES_PERROR -- Issue perror message including host info
** -------------------------------------------------------
**
** Returns:
** None.
*/
void
_res_perror(addr, host, message)
input struct sockaddr_in *addr; /* the server address to connect to */
input char *host; /* name of server to connect to */
input char *message; /* perror message string */
{
int save_errno = errno; /* preserve state */
/* prepend server address and name */
if (addr != NULL)
(void) fprintf(stderr, "%s ", inet_ntoa(addr->sin_addr));
if (host != NULL)
(void) fprintf(stderr, "(%s) ", host);
/* issue actual message */
seterrno(save_errno);
perror(message);
/* restore state */
seterrno(save_errno);
}
syntax highlighted by Code2HTML, v. 0.9.1