Make the resolver(3) and many associated interfaces much more reentrant.

The getaddrinfo(3), getipnodebyname(3) and resolver(3) can coincide now
with what should be totally reentrant, and h_errno values will now
be preserved correctly, but this does not affect interfaces such as
gethostbyname(3) which are still mostly non-reentrant.

In all of these relevant functions, the thread-safety has been pushed
down as far as it seems possible right now.  This means that operations
that are selected via nsdispatch(3) (i.e. files, yp, dns) are protected
still under global locks that getaddrinfo(3) defines, but where possible
the locking is greatly reduced.  The most noticeable improvement is
that multiple DNS lookups can now be run at the same time, and this
shows major improvement in performance of DNS-lookup threaded programs,
and solves the "Mozilla tab serialization" problem.

No single-threaded applications need to be recompiled.  Multi-threaded
applications that reference "_res" to change resolver(3) options will
need to be recompiled, and ones which reference "h_errno" will also
if they desire the correct h_errno values.  If the applications already
understood that _res and h_errno were not thread-safe and had their own
locking, they will see no performance improvement but will not
actually break in any way.

Please note that when NSS modules are used, or when nsdispatch(3)
defaults to adding any lookups of its own to the individual libc
_nsdispatch() calls, those MUST be reentrant as well.
This commit is contained in:
Brian Feldman 2004-02-25 21:03:46 +00:00
parent 59b4f7f45e
commit 33dee81933
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=126243
15 changed files with 289 additions and 55 deletions

View File

@ -82,7 +82,7 @@ typedef __socklen_t socklen_t;
#define _PATH_PROTOCOLS "/etc/protocols"
#define _PATH_SERVICES "/etc/services"
extern int h_errno;
#define h_errno (*__h_error())
/*
* Structures returned by network data base library. All addresses are
@ -135,7 +135,7 @@ struct addrinfo {
/*
* Error return codes from gethostbyname() and gethostbyaddr()
* (left in extern int h_errno).
* (left in h_errno).
*/
#define NETDB_INTERNAL -1 /* see errno */
@ -254,6 +254,7 @@ void setservent(int);
*/
/* DO NOT USE THESE, THEY ARE SUBJECT TO CHANGE AND ARE NOT PORTABLE!!! */
int * __h_error(void);
void _sethosthtent(int);
void _endhosthtent(void);
void _sethostdnsent(int);

View File

@ -200,7 +200,12 @@ struct res_sym {
char * humanname; /* Its fun name, like "mail exchanger" */
};
extern struct __res_state _res;
__BEGIN_DECLS
extern struct __res_state *___res(void);
extern struct __res_state_ext *___res_ext(void);
__END_DECLS
#define _res (*___res())
#define _res_ext (*___res_ext())
/* for INET6 */
extern struct __res_state_ext _res_ext;

View File

@ -94,10 +94,12 @@
#define mutex_t pthread_mutex_t
#define cond_t pthread_cond_t
#define rwlock_t pthread_rwlock_t
#define once_t pthread_once_t
#define thread_key_t pthread_key_t
#define MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER
#define RWLOCK_INITIALIZER PTHREAD_RWLOCK_INITIALIZER
#define ONCE_INITIALIZER PTHREAD_ONCE_INIT
#define mutex_init(m, a) _pthread_mutex_init(m, a)
#define mutex_lock(m) if (__isthreaded) \
@ -127,6 +129,7 @@
#define thr_getspecific(k) _pthread_getspecific(k)
#define thr_sigsetmask(f, n, o) _pthread_sigmask(f, n, o)
#define thr_once(o, i) _pthread_once(o, i)
#define thr_self() _pthread_self()
#define thr_exit(x) _pthread_exit(x)
#define thr_main() _pthread_main_np()

View File

@ -618,10 +618,14 @@ and documented in
(RFC2553).
.\"
.Sh BUGS
Though the current implementation should be thread-safe, using
Although the current implementation is otherwise thread-safe, using
.Fn getaddrinfo
in conjunction with
.Fn gethostby*
breaks thread-safeness.
(see
.Xr gethostbyname 3 )
or
.Xr yp 8
breaks the thread-safety of both.
.Pp
The text was shamelessly copied from RFC2553.

View File

@ -305,10 +305,10 @@ static struct ai_errlist {
};
/*
* XXX: Our res_*() is not thread-safe. So, we share lock between
* XXX: Many dependencies are not thread-safe. So, we share lock between
* getaddrinfo() and getipnodeby*(). Still, we cannot use
* getaddrinfo() and getipnodeby*() in conjunction with other
* functions which call res_*().
* functions which call them.
*/
pthread_mutex_t __getaddrinfo_thread_lock = PTHREAD_MUTEX_INITIALIZER;
#define THREAD_LOCK() \
@ -1348,9 +1348,13 @@ get_port(ai, servname, matchonly)
break;
}
if ((sp = getservbyname(servname, proto)) == NULL)
THREAD_LOCK();
if ((sp = getservbyname(servname, proto)) == NULL) {
THREAD_UNLOCK();
return EAI_SERVICE;
}
port = sp->s_port;
THREAD_UNLOCK();
}
if (!matchonly) {
@ -1501,15 +1505,11 @@ explore_fqdn(pai, hostname, servname, res)
result = NULL;
THREAD_LOCK();
/*
* if the servname does not match socktype/protocol, ignore it.
*/
if (get_portmatch(pai, servname) != 0) {
THREAD_UNLOCK();
if (get_portmatch(pai, servname) != 0)
return 0;
}
switch (_nsdispatch(&result, dtab, NSDB_HOSTS, "getaddrinfo",
default_dns_files, hostname, pai)) {
@ -1530,14 +1530,12 @@ explore_fqdn(pai, hostname, servname, res)
}
break;
}
THREAD_UNLOCK();
*res = result;
return 0;
free:
THREAD_UNLOCK();
if (result)
freeaddrinfo(result);
return error;
@ -2037,6 +2035,7 @@ _files_getaddrinfo(rv, cb_data, ap)
memset(&sentinel, 0, sizeof(sentinel));
cur = &sentinel;
THREAD_LOCK();
_sethtent();
while ((p = _gethtent(name, pai)) != NULL) {
cur->ai_next = p;
@ -2044,6 +2043,7 @@ _files_getaddrinfo(rv, cb_data, ap)
cur = cur->ai_next;
}
_endhtent();
THREAD_UNLOCK();
*((struct addrinfo **)rv) = sentinel.ai_next;
if (sentinel.ai_next == NULL)
@ -2152,9 +2152,12 @@ _yp_getaddrinfo(rv, cb_data, ap)
memset(&sentinel, 0, sizeof(sentinel));
cur = &sentinel;
THREAD_LOCK();
if (!__ypdomain) {
if (_yp_check(&__ypdomain) == 0)
if (_yp_check(&__ypdomain) == 0) {
THREAD_UNLOCK();
return NS_UNAVAIL;
}
}
if (__ypcurrent)
free(__ypcurrent);
@ -2189,6 +2192,7 @@ _yp_getaddrinfo(rv, cb_data, ap)
cur = cur->ai_next;
}
}
THREAD_UNLOCK();
if (sentinel.ai_next == NULL) {
h_errno = HOST_NOT_FOUND;
@ -2202,7 +2206,6 @@ _yp_getaddrinfo(rv, cb_data, ap)
/* resolver logic */
extern const char *__hostalias(const char *);
extern int h_errno;
/*
* Formulate a normal query, send, and await answer.

View File

@ -116,7 +116,6 @@ typedef union {
char ac;
} align;
extern int h_errno;
int _dns_ttl_;
#ifdef DEBUG

View File

@ -369,6 +369,12 @@ version 4.9.4.
These functions use static data storage;
if the data is needed for future use, it should be
copied before any subsequent calls overwrite it.
Threaded applications should never use them, as they will also conflict
with the
.Xr getaddrinfo 3
and
.Xr getipnodebyname 3
families of functions (which should be used instead).
Only the Internet
address format is currently understood.
.Pp

View File

@ -48,8 +48,6 @@ __FBSDID("$FreeBSD$");
#define MAXALIASES 35
#define MAXADDRS 35
extern int h_errno;
#ifdef YP
static char *host_aliases[MAXALIASES];
static char hostaddr[MAXADDRS];

View File

@ -455,6 +455,15 @@ are documented in
(RFC2553).
.\"
.Sh BUGS
Although the current implementation is otherwise thread-safe, using
it in conjunction with
.Fn gethostby*
(see
.Xr gethostbyname 3 )
or
.Xr yp 8
breaks the thread-safety of both.
.Pp
The
.Fn getipnodebyname
and
@ -469,6 +478,4 @@ and
.Fn getnameinfo 3
are recommended.
.Pp
The current implementation is not thread-safe.
.Pp
The text was shamelessly copied from RFC2553.

View File

@ -69,9 +69,7 @@ const char *h_errlist[] = {
"Unknown server error", /* 3 NO_RECOVERY */
"No address associated with name", /* 4 NO_ADDRESS */
};
int h_nerr = { sizeof h_errlist / sizeof h_errlist[0] };
int h_errno;
const int h_nerr = { sizeof h_errlist / sizeof h_errlist[0] };
/*
* herror --
@ -110,3 +108,6 @@ hstrerror(err)
return (h_errlist[err]);
return ("Unknown resolver error");
}
#undef h_errno
int h_errno;

View File

@ -211,10 +211,10 @@ static int _icmp_ghbyaddr(void *, void *, va_list);
#endif /* ICMPNL */
/*
* XXX: Our res_*() is not thread-safe. So, we share lock between
* XXX: Many dependencies are not thread-safe. So, we share lock between
* getaddrinfo() and getipnodeby*(). Still, we cannot use
* getaddrinfo() and getipnodeby*() in conjunction with other
* functions which call res_*().
* functions which call them.
*/
#include "libc_private.h"
extern pthread_mutex_t __getaddrinfo_thread_lock;
@ -309,10 +309,8 @@ _ghbyname(const char *name, int af, int flags, int *errp)
}
}
THREAD_LOCK();
rval = _nsdispatch(&hp, dtab, NSDB_HOSTS, "ghbyname", default_src,
name, af, errp);
THREAD_UNLOCK();
return (rval == NS_SUCCESS) ? hp : NULL;
}
@ -456,10 +454,8 @@ getipnodebyaddr(const void *src, size_t len, int af, int *errp)
return NULL;
}
THREAD_LOCK();
rval = _nsdispatch(&hp, dtab, NSDB_HOSTS, "ghbyaddr", default_src,
src, len, af, errp);
THREAD_UNLOCK();
return (rval == NS_SUCCESS) ? hp : NULL;
}
@ -1169,9 +1165,11 @@ _nis_ghbyname(void *rval, void *cb_data, va_list ap)
if (af == AF_UNSPEC)
af = AF_INET;
if (af == AF_INET) {
THREAD_LOCK();
hp = _gethostbynisname(name, af);
if (hp != NULL)
hp = _hpcopy(hp, errp);
THREAD_UNLOCK();
}
*(struct hostent **)rval = hp;
@ -1193,9 +1191,11 @@ _nis_ghbyaddr(void *rval, void *cb_data, va_list ap)
af = va_arg(ap, int);
if (af == AF_INET) {
THREAD_LOCK();
hp = _gethostbynisaddr(addr, addrlen, af);
if (hp != NULL)
hp = _hpcopy(hp, errp);
THREAD_UNLOCK();
}
*(struct hostent **)rval = hp;
return (hp != NULL) ? NS_SUCCESS : NS_NOTFOUND;
@ -1932,18 +1932,17 @@ _icmp_fqdn_query(const struct in6_addr *addr, int ifindex)
struct timeval tout;
int len;
char *name;
static int pid;
static struct _icmp_host_cache *hc_head;
THREAD_LOCK();
for (hc = hc_head; hc; hc = hc->hc_next) {
if (hc->hc_ifindex == ifindex
&& IN6_ARE_ADDR_EQUAL(&hc->hc_addr, addr))
return hc->hc_name;
&& IN6_ARE_ADDR_EQUAL(&hc->hc_addr, addr)) {
THREAD_UNLOCK();
return hc->hc_name; /* XXX: never freed */
}
}
if (pid == 0)
pid = getpid();
ICMP6_FILTER_SETBLOCKALL(&filter);
ICMP6_FILTER_SETPASS(ICMP6_FQDN_REPLY, &filter);
@ -1955,7 +1954,7 @@ _icmp_fqdn_query(const struct in6_addr *addr, int ifindex)
fq->icmp6_fqdn_type = ICMP6_FQDN_QUERY;
fq->icmp6_fqdn_code = 0;
fq->icmp6_fqdn_cksum = 0;
fq->icmp6_fqdn_id = (u_short)pid;
fq->icmp6_fqdn_id = (u_short)getpid();
fq->icmp6_fqdn_unused = 0;
fq->icmp6_fqdn_cookie[0] = 0;
fq->icmp6_fqdn_cookie[1] = 0;
@ -2038,8 +2037,10 @@ _icmp_fqdn_query(const struct in6_addr *addr, int ifindex)
hc->hc_ifindex = ifindex;
hc->hc_addr = *addr;
hc->hc_name = strdup(name);
THREAD_LOCK();
hc->hc_next = hc_head;
hc_head = hc;
THREAD_UNLOCK();
return hc->hc_name;
}

View File

@ -91,7 +91,14 @@ __FBSDID("$FreeBSD$");
#include <unistd.h>
#include <netdb.h>
#include "namespace.h"
#include "reentrant.h"
#include "un-namespace.h"
#include "res_config.h"
#include "res_send_private.h"
#undef h_errno
extern int h_errno;
static void res_setoptions(char *, char *);
@ -106,16 +113,14 @@ static u_int32_t net_mask(struct in_addr);
#endif
/*
* Resolver state default settings.
* Check structure for failed per-thread allocations.
*/
struct __res_state _res
# if defined(__BIND_RES_TEXT)
= { RES_TIMEOUT, } /* Motorola, et al. */
# endif
;
struct __res_state_ext _res_ext;
static struct res_per_thread {
struct __res_state res_state;
struct __res_state_ext res_state_ext;
struct __res_send_private res_send_private;
int h_errno;
} _res_per_thread_bogus = { .res_send_private = { .s = -1 } }; /* socket */
/*
* Set up default settings. If the configuration file exist, the values
@ -142,6 +147,7 @@ int
res_init()
{
FILE *fp;
struct __res_send_private *rsp;
char *cp, **pp;
int n;
char buf[MAXDNAME];
@ -156,6 +162,19 @@ res_init()
int dots;
#endif
/*
* If allocation of memory for this thread's resolver has failed,
* return the error to the user.
*/
if (&_res == &_res_per_thread_bogus.res_state)
return (-1);
rsp = ___res_send_private();
rsp->s = -1;
rsp->connected = 0;
rsp->vc = 0;
rsp->af = 0;
rsp->Qhook = NULL;
rsp->Rhook = NULL;
/*
* These three fields used to be statically initialized. This made
* it hard to use this code in a shared library. It is necessary,
@ -597,6 +616,97 @@ res_randomid()
return (0xffff & (now.tv_sec ^ now.tv_usec ^ getpid()));
}
/*
* Resolver state default settings.
*/
#undef _res
#undef _res_ext
#ifdef __BIND_RES_TEXT
struct __res_state _res = { RES_TIMEOUT }; /* Motorola, et al. */
#else
struct __res_state _res;
#endif
struct __res_state_ext _res_ext;
static struct __res_send_private _res_send_private = { .s = -1 }; /* socket */
static thread_key_t res_key;
static once_t res_init_once = ONCE_INITIALIZER;
static int res_thr_keycreated = 0;
static void
free_res(void *ptr)
{
struct res_per_thread *myrsp = ptr;
if (myrsp->res_state.options & RES_INIT)
res_close();
free(myrsp);
}
static void
res_keycreate(void)
{
res_thr_keycreated = thr_keycreate(&res_key, free_res) == 0;
}
static struct res_per_thread *
allocate_res(void)
{
struct res_per_thread *myrsp;
if (thr_once(&res_init_once, res_keycreate) != 0 ||
!res_thr_keycreated)
return (&_res_per_thread_bogus);
myrsp = thr_getspecific(res_key);
if (myrsp != NULL)
return (myrsp);
myrsp = calloc(1, sizeof(*myrsp));
if (myrsp == NULL)
return (&_res_per_thread_bogus);
#ifdef __BIND_RES_TEXT
myrsp->res_state.options = RES_TIMEOUT; /* Motorola, et al. */
#endif
myrsp->res_send_private.s = -1; /* socket */
if (thr_setspecific(res_key, myrsp) == 0)
return (myrsp);
free(myrsp);
return (&_res_per_thread_bogus);
}
struct __res_state *
___res(void)
{
if (thr_main() != 0)
return (&_res);
return (&allocate_res()->res_state);
}
struct __res_state_ext *
___res_ext(void)
{
if (thr_main() != 0)
return (&_res_ext);
return (&allocate_res()->res_state_ext);
}
struct __res_send_private *
___res_send_private(void)
{
if (thr_main() != 0)
return (&_res_send_private);
return (&allocate_res()->res_send_private);
}
int *
__h_error(void)
{
if (thr_main() != 0)
return (&h_errno);
return (&allocate_res()->h_errno);
}
/*
* Weak aliases for applications that use certain private entry points,
* and fail to include <resolv.h>.

View File

@ -101,14 +101,15 @@ __FBSDID("$FreeBSD$");
#include "un-namespace.h"
#include "res_config.h"
#include "res_send_private.h"
static int s = -1; /* socket used for communications */
static int connected = 0; /* is the socket connected */
static int vc = 0; /* is the socket a virtual circuit? */
static int af = 0; /* address family of socket */
static res_send_qhook Qhook = NULL;
static res_send_rhook Rhook = NULL;
#define s ___res_send_private()->s
#define connected ___res_send_private()->connected
#define vc ___res_send_private()->vc
#define af ___res_send_private()->af
#define Qhook ___res_send_private()->Qhook
#define Rhook ___res_send_private()->Rhook
#define CAN_RECONNECT 1
@ -123,8 +124,6 @@ static res_send_rhook Rhook = NULL;
fprintf args;\
__fp_nquery(query, size, stdout);\
} else {}
static char abuf[NI_MAXHOST];
static char pbuf[NI_MAXSERV];
static void Aerror(FILE *, char *, int, struct sockaddr *);
static void Perror(FILE *, char *, int);
@ -138,6 +137,9 @@ static void Perror(FILE *, char *, int);
int save = errno;
if (_res.options & RES_DEBUG) {
char abuf[NI_MAXHOST];
char pbuf[NI_MAXSERV];
if (getnameinfo(address, address->sa_len, abuf, sizeof(abuf),
pbuf, sizeof(pbuf),
NI_NUMERICHOST|NI_NUMERICSERV|NI_WITHSCOPEID) != 0) {
@ -388,6 +390,7 @@ res_send(buf, buflen, ans, anssiz)
*/
for (try = 0; try < _res.retry; try++) {
for (ns = 0; ns < _res.nscount; ns++) {
char abuf[NI_MAXHOST];
struct sockaddr *nsap = get_nsaddr(ns);
socklen_t salen;

View File

@ -0,0 +1,82 @@
/* $FreeBSD$ */
/*
* 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.
*/
/*
* Portions Copyright (c) 1996 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 INTERNET SOFTWARE CONSORTIUM DISCLAIMS
* ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
* CONSORTIUM 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.
*/
struct __res_send_private {
int s; /* socket used for communications */
int connected; /* is the socket connected */
int vc; /* is the socket a virtual circuit? */
int af; /* address family of socket */
res_send_qhook Qhook;
res_send_rhook Rhook;
};
struct __res_send_private *___res_send_private(void);

View File

@ -396,6 +396,17 @@ function puts a 32-bit quantity
.Fa src
to a buffer pointed to by
.Fa dst .
.Sh RETURN VALUES
The
.Fn res_init
function will return 0 on success, or -1 in a threaded program if
per-thread storage could not be allocated.
.Sh IMPLEMENTATION NOTES
This implementation of the resolver is thread-safe, but it will not
function properly if the programmer attempts to declare his or her own
.Va _res
structure in an attempt to replace the per-thread version referred to
by that macro.
.Sh FILES
.Bl -tag -width /etc/resolv.conf
.It Pa /etc/resolv.conf