Revision: 1.12, Mon Dec 6 16:12:12 2004 UTC (6 months, 3 weeks ago) by dossy
Branch: MAIN
CVS Tags: HEAD
Changes since 1.11: +46 -19 lines
Implement Ns_GetAllAddrByHost() and [ns_addrbyhost -all].  Closes SF
RFE #999452.
/*
 * The contents of this file are subject to the AOLserver Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://aolserver.com/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of GPL are applicable instead of those above.  If you wish
 * to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the
 * License, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the GPL.
 * If you do not delete the provisions above, a recipient may use your
 * version of this file under either the License or the GPL.
 */


/*
 * dns.c --
 *
 *      DNS lookup routines.
 */

static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsd/dns.c,v 1.12 2004/12/06 16:12:12 dossy Exp $, compiled: " __DATE__ " " __TIME__;

#include "nsd.h"

#ifndef INADDR_NONE
#define INADDR_NONE (-1)
#endif

#ifndef NETDB_INTERNAL
#  ifdef h_NETDB_INTERNAL
#  define NETDB_INTERNAL h_NETDB_INTERNAL
#  endif
#endif

#ifdef NEED_HERRNO
extern int h_errno;
#endif

/*
 * The following structure maintains a cached lookup value.
 */

typedef struct Value {
    time_t      expires;
    char	value[1];
} Value;

typedef int (GetProc)(Ns_DString *dsPtr, char *key);

/*
 * Static variables defined in this file
 */

static Ns_Cache *hostCache;
static Ns_Cache *addrCache;
static Ns_Mutex lock;
static int cachetimeout;

/*
 * Static functions defined in this file
 */

static GetProc GetAddr;
static GetProc GetHost;
static int DnsGet(GetProc *getProc, Ns_DString *dsPtr,
	Ns_Cache **cachePtr, char *key, int all);

#if !defined(HAVE_GETADDRINFO) && !defined(HAVE_GETNAMEINFO)
static void LogError(char *func, int h_errnop);
#endif


/*
 *----------------------------------------------------------------------
 * Ns_GetHostByAddr, Ns_GetAddrByHost --
 *
 *      Convert an IP address to a hostname or vice versa.
 *
 * Results:
 *	See DnsGet().
 *
 * Side effects:
 *      A new entry is entered into the hash table.
 *
 *----------------------------------------------------------------------
 */

int
Ns_GetHostByAddr(Ns_DString *dsPtr, char *addr)
{
    return DnsGet(GetHost, dsPtr, &hostCache, addr, 0);
}

int
Ns_GetAddrByHost(Ns_DString *dsPtr, char *host)
{
    return DnsGet(GetAddr, dsPtr, &addrCache, host, 0);
}

int
Ns_GetAllAddrByHost(Ns_DString *dsPtr, char *host)
{
    return DnsGet(GetAddr, dsPtr, &addrCache, host, 1);
}

static int
DnsGet(GetProc *getProc, Ns_DString *dsPtr, Ns_Cache **cachePtr, char *key, int all)
{
    int             status = NS_FALSE, new, timeout;
    Value   	   *vPtr  = NULL;
    Ns_Entry       *ePtr  = NULL;
    Ns_Cache	   *cache = NULL;
    time_t	    now;

    /*
     * Get the cache, if enabled.
     */

    Ns_MutexLock(&lock);
    cache = *cachePtr;
    timeout = cachetimeout;
    Ns_MutexUnlock(&lock);

    /*
     * Call getProc directly or through cache.
     */

    if (cache == NULL) {
        status = (*getProc)(dsPtr, key);
    } else {
	time(&now);
	Ns_CacheLock(cache);
	ePtr = Ns_CacheCreateEntry(cache, key, &new);
	if (!new) {
	    while (ePtr != NULL &&
		    (vPtr = Ns_CacheGetValue(ePtr)) == NULL) {
		Ns_CacheWait(cache);
		ePtr = Ns_CacheFindEntry(cache, key);
	    }
	    if (ePtr == NULL) {
	        status = NS_FALSE;
	    } else if (vPtr->expires < now) {
		Ns_CacheUnsetValue(ePtr);
		new = 1;
	    } else {
		Ns_DStringAppend(dsPtr, vPtr->value);
		status = NS_TRUE;
	    }
	}
	if (new) {
	    Ns_CacheUnlock(cache);
	    status = (*getProc)(dsPtr, key);
	    Ns_CacheLock(cache);
	    ePtr = Ns_CacheCreateEntry(cache, key, &new);
	    if (status != NS_TRUE) {
		Ns_CacheFlushEntry(ePtr);
	    } else {
	    	Ns_CacheUnsetValue(ePtr);
		vPtr = ns_malloc(sizeof(Value) + dsPtr->length);
		vPtr->expires = now + timeout;
		strcpy(vPtr->value, dsPtr->string);
		Ns_CacheSetValueSz(ePtr, vPtr, 1);
	    }
	    Ns_CacheBroadcast(cache);
	}
	Ns_CacheUnlock(cache);
    }

    if (status == NS_TRUE && getProc == GetAddr && !all) {
        char *p = dsPtr->string;
        while (*p && !isspace(UCHAR(*p))) {
            ++p;
        }
        Tcl_DStringSetLength(dsPtr, p - dsPtr->string);
    }

    return status;
}


/*
 *----------------------------------------------------------------------
 * NsEnableDNSCache --
 *
 *      Enable DNS results caching.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *	Futher DNS lookups will be cached up to given timeout.
 *
 *----------------------------------------------------------------------
 */

void
NsEnableDNSCache(int timeout, int maxentries)
{
    Ns_MutexSetName(&lock, "ns:dns");
    Ns_MutexLock(&lock);
    cachetimeout = timeout;
    hostCache = Ns_CacheCreateSz("ns:dnshost", TCL_STRING_KEYS,
	(size_t) maxentries, ns_free);
    addrCache = Ns_CacheCreateSz("ns:dnsaddr", TCL_STRING_KEYS,
	(size_t) maxentries, ns_free);
    Ns_MutexUnlock(&lock);
}


/*
 *----------------------------------------------------------------------
 * GetHost, GetAddr --
 *
 *      Perform the actual lookup by host or address.
 *
 *	NOTE: A critical section is used instead of a mutex
 *	to ensure waiting on a condition and not mutex spin waiting.
 *
 * Results:
 *      If a name can be found, the function returns NS_TRUE; otherwise, 
 *	it returns NS_FALSE.
 *
 * Side effects:
 *      Result is appended to dsPtr.
 *
 *----------------------------------------------------------------------
 */

#if defined(HAVE_GETNAMEINFO)

static int
GetHost(Ns_DString *dsPtr, char *addr)
{
    struct sockaddr_in sa;
    char buf[NI_MAXHOST];
    int result;
    int status = NS_FALSE;

    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = inet_addr(addr);
    if ((result = getnameinfo((const struct sockaddr *) &sa,
                    sizeof(struct sockaddr_in), buf, sizeof(buf),
                    NULL, 0, NI_NAMEREQD)) != 0) {
        Ns_Log(Error, "dns: getnameinfo failed: %s", gai_strerror(result));
    } else {
        Ns_DStringAppend(dsPtr, buf);
        status = NS_TRUE;
        
    }
    return status;
}

#elif defined(HAVE_GETHOSTBYADDR_R)

static int
GetHost(Ns_DString *dsPtr, char *addr)
{
    struct hostent he, *hePtr;
    struct sockaddr_in sa;
    char buf[2048];
    int h_errnop;
    int status = NS_FALSE;

    sa.sin_addr.s_addr = inet_addr(addr);
    hePtr = gethostbyaddr_r((char *) &sa.sin_addr, sizeof(struct in_addr),
            AF_INET, &he, buf, sizeof(buf), &h_errnop);
    if (hePtr == NULL) {
        LogError("gethostbyaddr_r", h_errnop);
    } else if (he.h_name != NULL) {
        Ns_DStringAppend(dsPtr, he.h_name);
        status = NS_TRUE;
    }
    return status;
}

#else

/*
 * This version is not thread-safe, but we have no thread-safe
 * alternative on this platform.  Use critsec to try and serialize
 * calls, but beware: Tcl core as of 8.4.6 still calls gethostbyaddr()
 * as well, so it's still possible for two threads to call it at
 * the same time.
 */

static int
GetHost(Ns_DString *dsPtr, char *addr)
{
    struct hostent *he;
    struct sockaddr_in sa;
    static Ns_Cs cs;
    int status = NS_FALSE;

    sa.sin_addr.s_addr = inet_addr(addr);
    if (sa.sin_addr.s_addr != INADDR_NONE) {
	Ns_CsEnter(&cs);
        he = gethostbyaddr((char *) &sa.sin_addr,
			   sizeof(struct in_addr), AF_INET);
	if (he == NULL) {
	    LogError("gethostbyaddr", h_errno);
	} else if (he->h_name != NULL) {
	    Ns_DStringAppend(dsPtr, he->h_name);
	    status = NS_TRUE;
	}
	Ns_CsLeave(&cs);
    }
    return status;
}

#endif

#if defined(HAVE_GETADDRINFO)

static int
GetAddr(Ns_DString *dsPtr, char *host)
{
    struct addrinfo hints;
    struct addrinfo *res, *ptr;
    int result;
    int status = NS_FALSE;
    
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = PF_INET;
    hints.ai_socktype = SOCK_STREAM;
    if ((result = getaddrinfo(host, NULL, &hints, &res)) != 0) {
        Ns_Log(Error, "dns: getaddrinfo failed for %s: %s", host,
                gai_strerror(result));
    } else {
        ptr = res;
        while (ptr != NULL) {
            Tcl_DStringAppendElement(dsPtr, ns_inet_ntoa(
                        ((struct sockaddr_in *) ptr->ai_addr)->sin_addr));
            ptr = ptr->ai_next;
        }
        freeaddrinfo(res);
        status = NS_TRUE;
    }
    return status;
}

#elif defined(HAVE_GETHOSTBYNAME_R)

static int
GetAddr(Ns_DString *dsPtr, char *host)
{
    struct hostent he, *res;
    struct in_addr ia, *ptr;
#ifdef HAVE_GETHOSTBYNAME_R_3
    struct hostent_data data;
#endif
    char buf[2048];
    int result;
    int i = 0;
    int h_errnop;
    int status = NS_FALSE;
    
#if defined(HAVE_GETHOSTBYNAME_R_6)
    result = gethostbyname_r(host, &he, buf, sizeof(buf), &res, &h_errnop);
#elif defined(HAVE_GETHOSTBYNAME_R_5)
    result = 0;
    res = gethostbyname_r(host, &he, buf, sizeof(buf), &h_errnop);
    if (res == NULL) {
        result = -1;
    }
#elif defined(HAVE_GETHOSTBYNAME_R_3)
    result = gethostbyname_r(host, &he, &data);
    h_errnop = h_errno;
#endif

    if (result != 0) { 
	LogError("gethostbyname_r", h_errnop);
    } else {
        ptr = (struct in_addr *) he.h_addr_list[i];
        while (ptr != NULL) {
            ia.s_addr = ptr->s_addr;
            Tcl_DStringAppendElement(dsPtr, ns_inet_ntoa(ia));
            status = NS_TRUE;
        }
    }
    return status;
}

#else

/*
 * This version is not thread-safe, but we have no thread-safe
 * alternative on this platform.  Use critsec to try and serialize
 * calls, but beware: Tcl core as of 8.4.6 still calls gethostbyname()
 * as well, so it's still possible for two threads to call it at
 * the same time.
 */

static int
GetAddr(Ns_DString *dsPtr, char *host)
{
    struct hostent *he;
    struct in_addr ia, *ptr;
    static Ns_Cs cs;
    int i = 0;
    int status = NS_FALSE;

    Ns_CsEnter(&cs);
    he = gethostbyname(host);
    if (he == NULL) {
	LogError("gethostbyname", h_errno);
    } else {
        ptr = (struct in_addr *) he.h_addr_list[i];
        while (ptr != NULL) {
            ia.s_addr = ptr->s_addr;
            Tcl_DStringAppendElement(dsPtr, ns_inet_ntoa(ia));
            status = NS_TRUE;
        }
    }
    Ns_CsLeave(&cs);
    return status;
}

#endif


/*
 *----------------------------------------------------------------------
 * LogError -
 *
 *      Log errors which may indicate a failure in the underlying
 *	resolver.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

#if !defined(HAVE_GETADDRINFO) && !defined(HAVE_GETNAMEINFO)

static void
LogError(char *func, int h_errnop)
{
    char *h, *e, buf[20];

    e = NULL;
    switch (h_errnop) {
	case HOST_NOT_FOUND:
	    /* Log nothing. */
	    return;
	    break;

	case TRY_AGAIN:
	    h = "temporary error - try again";
	    break;

	case NO_RECOVERY:
	    h = "unexpected server failure";
	    break;

	case NO_DATA:
	    h = "no valid IP address";
	    break;

#ifdef NETDB_INTERNAL
	case NETDB_INTERNAL:
	    h = "netdb internal error: ";
	    e = strerror(errno);
	    break;
#endif

	default:
	    sprintf(buf, "unknown error #%d", h_errnop);
	    h = buf;
    }

    Ns_Log(Error, "dns: %s failed: %s%s", func, h, e ? e : "");
}

#endif


Back to SourceForge.net

Powered by ViewCVS 1.0-dev